From cc29364f06178f8f6b457384f2ec37a042bd9d43 Mon Sep 17 00:00:00 2001 From: tpearson Date: Wed, 1 Sep 2010 00:37:02 +0000 Subject: * Massive set of changes to bring in all fixes and enhancements from the Enterprise PIM branch * Ensured that the Trinity changes were applied on top of those enhancements, and any redundancy removed * Added journal read support to the CalDAV resource * Fixed CalDAV resource to use events URL for tasks and journals when separate URL checkbox unchecked git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdepim@1170461 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- kmail/KMail.desktop | 4 +- kmail/Makefile.am | 23 +- kmail/accountdialog.cpp | 2 +- kmail/accountwizard.cpp | 4 +- kmail/acljobs.cpp | 2 + kmail/acljobs.h | 8 + kmail/annotationjobs.cpp | 2 +- kmail/antispamwizard.cpp | 12 +- kmail/antispamwizard.h | 4 +- kmail/app_octetstream.cpp | 2 +- kmail/application_octetstream.desktop | 2 - kmail/archivefolderdialog.cpp | 209 +++++++ kmail/archivefolderdialog.h | 62 ++ kmail/attachmentcollector.h | 18 +- kmail/attachmentstrategy.cpp | 87 ++- kmail/attachmentstrategy.h | 3 +- kmail/backupjob.cpp | 500 ++++++++++++++++ kmail/backupjob.h | 109 ++++ kmail/bodypartformatter.cpp | 3 +- kmail/cachedimapjob.cpp | 141 +++-- kmail/cachedimapjob.h | 9 + kmail/callback.cpp | 82 ++- kmail/callback.h | 4 + kmail/composer.h | 13 + kmail/configuredialog.cpp | 181 ++++-- kmail/configuredialog.h | 1 + kmail/configuredialog_p.h | 12 +- kmail/customtemplates.cpp | 120 +++- kmail/customtemplates.h | 20 +- kmail/customtemplates_base.ui | 478 ++++++++------- kmail/customtemplates_kfg.kcfg | 6 + kmail/dcopimap.desktop | 1 - kmail/dcopmail.desktop | 1 - kmail/dictionarycombobox.cpp | 1 + kmail/distributionlistdialog.cpp | 47 +- kmail/editorwatcher.cpp | 13 +- kmail/editorwatcher.h | 11 +- kmail/encodingdetector.cpp | 82 +-- kmail/eventsrc | 8 +- kmail/expirypropertiesdialog.cpp | 51 +- kmail/favoritefolderview.cpp | 31 +- kmail/filterimporterexporter.cpp | 166 +++--- kmail/filterimporterexporter.h | 33 +- kmail/folderdiaacltab.cpp | 41 +- kmail/folderdiaacltab.h | 2 + kmail/foldersetselector.cpp | 88 +++ kmail/foldersetselector.h | 45 ++ kmail/folderstorage.cpp | 49 +- kmail/folderstorage.h | 18 +- kmail/foldertreebase.cpp | 12 +- kmail/folderutil.cpp | 113 ++++ kmail/folderutil.h | 65 +++ kmail/globalsettings_base.kcfgc | 2 +- kmail/headeritem.cpp | 38 +- kmail/headeritem.h | 3 + kmail/headerlistquicksearch.cpp | 15 +- kmail/headerstyle.cpp | 98 ++-- kmail/identitydialog.cpp | 46 +- kmail/identitydialog.h | 2 + kmail/imapaccountbase.cpp | 42 +- kmail/imapaccountbase.h | 20 +- kmail/imapjob.cpp | 2 + kmail/importarchivedialog.cpp | 107 ++++ kmail/importarchivedialog.h | 55 ++ kmail/importjob.cpp | 396 +++++++++++++ kmail/importjob.h | 126 ++++ kmail/interfaces/bodypartformatter.h | 6 +- kmail/interfaces/urlhandler.h | 35 ++ kmail/isubject.cpp | 11 +- kmail/keyresolver.cpp | 343 +++++++++-- kmail/khtmlparthtmlwriter.cpp | 2 +- kmail/kleo_util.h | 7 + kmail/kmacctcachedimap.cpp | 33 +- kmail/kmacctcachedimap.h | 7 +- kmail/kmacctimap.cpp | 2 +- kmail/kmacctimap.h | 2 +- kmail/kmacctlocal.cpp | 1 + kmail/kmaddrbook.cpp | 1 - kmail/kmail.kcfg | 66 ++- kmail/kmailIface.h | 16 +- kmail/kmail_config_accounts.desktop | 6 +- kmail/kmail_config_appearance.desktop | 6 +- kmail/kmail_config_composer.desktop | 3 - kmail/kmail_config_identity.desktop | 9 +- kmail/kmail_config_misc.desktop | 9 +- kmail/kmail_config_security.desktop | 9 +- kmail/kmail_part.rc | 3 +- kmail/kmailicalIface.h | 6 + kmail/kmailicalifaceimpl.cpp | 167 ++++-- kmail/kmailicalifaceimpl.h | 8 + kmail/kmcommands.cpp | 204 ++++--- kmail/kmcommands.h | 22 +- kmail/kmcomposewin.cpp | 360 +++++++++--- kmail/kmcomposewin.h | 54 +- kmail/kmedit.cpp | 264 +++++++-- kmail/kmedit.h | 79 ++- kmail/kmfawidgets.cpp | 7 +- kmail/kmfawidgets.h | 4 + kmail/kmfilteraction.cpp | 214 ++++--- kmail/kmfilterdlg.cpp | 25 +- kmail/kmfilterdlg.h | 2 + kmail/kmfiltermgr.cpp | 2 - kmail/kmfolder.cpp | 58 ++ kmail/kmfolder.h | 23 + kmail/kmfoldercachedimap.cpp | 588 +++++++++++++------ kmail/kmfoldercachedimap.h | 92 ++- kmail/kmfolderdia.cpp | 157 +++-- kmail/kmfolderdia.h | 2 + kmail/kmfolderdir.cpp | 57 +- kmail/kmfolderdir.h | 19 +- kmail/kmfolderimap.cpp | 34 +- kmail/kmfolderimap.h | 19 +- kmail/kmfolderindex.cpp | 112 +++- kmail/kmfolderindex.h | 24 +- kmail/kmfoldermaildir.cpp | 13 +- kmail/kmfoldermbox.cpp | 26 +- kmail/kmfoldersearch.cpp | 1 + kmail/kmfolderseldlg.cpp | 445 +------------- kmail/kmfolderseldlg.h | 53 +- kmail/kmfoldertree.cpp | 95 ++- kmail/kmfoldertree.h | 15 +- kmail/kmgroupware.cpp | 4 +- kmail/kmheaders.cpp | 156 +++-- kmail/kmheaders.h | 20 +- kmail/kmkernel.cpp | 182 ++++-- kmail/kmkernel.h | 22 + kmail/kmlineeditspell.cpp | 93 ++- kmail/kmmainwidget.cpp | 276 +++++---- kmail/kmmainwidget.h | 30 +- kmail/kmmainwin.cpp | 2 +- kmail/kmmainwin.rc | 3 +- kmail/kmmessage.cpp | 439 +++++++++----- kmail/kmmessage.h | 143 +++-- kmail/kmmimeparttree.cpp | 2 - kmail/kmmsgbase.cpp | 51 +- kmail/kmmsgbase.h | 33 +- kmail/kmmsgdict.cpp | 15 +- kmail/kmmsginfo.cpp | 96 +++- kmail/kmmsginfo.h | 4 + kmail/kmmsgpart.cpp | 5 +- kmail/kmmsgpartdlg.cpp | 5 +- kmail/kmpopheaders.cpp | 3 +- kmail/kmreadermainwin.cpp | 78 ++- kmail/kmreadermainwin.h | 21 +- kmail/kmreaderwin.cpp | 735 ++++++++++++++++-------- kmail/kmreaderwin.h | 86 ++- kmail/kmsearchpattern.cpp | 21 +- kmail/kmsearchpattern.h | 8 +- kmail/kmsearchpatternedit.cpp | 22 +- kmail/kmsearchpatternedit.h | 3 +- kmail/kmsender.cpp | 5 +- kmail/kmsystemtray.cpp | 9 +- kmail/kmsystemtray.h | 5 +- kmail/kmversion.h | 2 +- kmail/managesievescriptsdialog.cpp | 46 +- kmail/managesievescriptsdialog.h | 3 +- kmail/managesievescriptsdialog_p.h | 3 +- kmail/messageactions.cpp | 18 +- kmail/messageactions.h | 17 +- kmail/messagecomposer.cpp | 36 +- kmail/messageproperty.cpp | 35 +- kmail/messageproperty.h | 4 + kmail/newfolderdialog.cpp | 102 +--- kmail/newfolderdialog.h | 2 +- kmail/objecttreeparser.cpp | 706 ++++++++++++++++------- kmail/objecttreeparser.h | 74 ++- kmail/objecttreeparser_p.cpp | 350 +++++++++++ kmail/objecttreeparser_p.h | 203 +++++++ kmail/partNode.cpp | 236 ++++++-- kmail/partNode.h | 69 ++- kmail/partmetadata.h | 4 + kmail/partnodebodypart.cpp | 4 +- kmail/pics/Makefile.am | 2 +- kmail/pics/kmmsginvitation.png | Bin 0 -> 978 bytes kmail/profiles/profile-default-rc.desktop | 8 +- kmail/profiles/profile-high-contrast-rc.desktop | 2 - kmail/profiles/profile-html-rc.desktop | 3 +- kmail/profiles/profile-purist-rc.desktop | 4 +- kmail/profiles/profile-secure-rc.desktop | 5 +- kmail/recipientseditor.cpp | 5 +- kmail/recipientspicker.cpp | 6 +- kmail/redirectdialog.cpp | 9 + kmail/redirectdialog.h | 2 +- kmail/searchwindow.cpp | 87 +-- kmail/searchwindow.h | 10 +- kmail/sievedebugdialog.cpp | 65 ++- kmail/sievejob.cpp | 6 + kmail/sievejob.h | 2 + kmail/simplefoldertree.h | 249 ++++++++ kmail/simplestringlisteditor.cpp | 16 +- kmail/simplestringlisteditor.h | 1 + kmail/snippetdlg.cpp | 23 +- kmail/snippetdlg.h | 11 +- kmail/snippetdlgbase.ui | 3 + kmail/snippetitem.cpp | 10 +- kmail/snippetwidget.cpp | 4 +- kmail/stl_util.h | 17 + kmail/stringutil.cpp | 49 ++ kmail/stringutil.h | 43 ++ kmail/subscriptiondialog.cpp | 2 +- kmail/templateparser.cpp | 187 +++++- kmail/templateparser.h | 99 +++- kmail/templatesconfiguration.cpp | 21 +- kmail/templatesinsertcommand.cpp | 4 + kmail/templatesinsertcommand.h | 3 +- kmail/treebase.cpp | 235 ++++++++ kmail/treebase.h | 83 +++ kmail/urlhandlermanager.cpp | 207 ++++++- kmail/urlhandlermanager.h | 3 + kmail/vacation.cpp | 15 +- kmail/vacationdialog.cpp | 36 +- kmail/vacationdialog.h | 7 +- kmail/vcardviewer.cpp | 10 +- kmail/vcardviewer.h | 41 +- 214 files changed, 10507 insertions(+), 3360 deletions(-) create mode 100644 kmail/archivefolderdialog.cpp create mode 100644 kmail/archivefolderdialog.h create mode 100644 kmail/backupjob.cpp create mode 100644 kmail/backupjob.h create mode 100644 kmail/foldersetselector.cpp create mode 100644 kmail/foldersetselector.h create mode 100644 kmail/folderutil.cpp create mode 100644 kmail/folderutil.h create mode 100644 kmail/importarchivedialog.cpp create mode 100644 kmail/importarchivedialog.h create mode 100644 kmail/importjob.cpp create mode 100644 kmail/importjob.h create mode 100644 kmail/objecttreeparser_p.cpp create mode 100644 kmail/objecttreeparser_p.h create mode 100644 kmail/pics/kmmsginvitation.png create mode 100644 kmail/simplefoldertree.h create mode 100644 kmail/stringutil.cpp create mode 100644 kmail/stringutil.h create mode 100644 kmail/treebase.cpp create mode 100644 kmail/treebase.h (limited to 'kmail') diff --git a/kmail/KMail.desktop b/kmail/KMail.desktop index 52e31f0e3..556c94098 100644 --- a/kmail/KMail.desktop +++ b/kmail/KMail.desktop @@ -46,7 +46,6 @@ GenericName[id]=Klien Mail GenericName[is]=Póstforrit GenericName[it]=Programma di posta elettronica GenericName[ja]=メールクライアント -GenericName[ka]=ფოსტის კლიენტი GenericName[kk]=Эл.пошта клиент бағдарламасы GenericName[km]=កម្មវិធី​អ៊ីមែល GenericName[lt]=Pašto klientas @@ -77,8 +76,7 @@ GenericName[tg]=Клиенти почтавӣ GenericName[th]=ไคลเอนต์จดหมายอิเล็กทรอนิกส์ GenericName[tr]=E-posta İstemcisi GenericName[uk]=Поштовий клієнт -GenericName[uz]=Xat-xabar klienti -GenericName[uz@cyrillic]=Хат-хабар клиенти +GenericName[uz]=Хат-хабар клиенти GenericName[ven]=Mushumisani na poso GenericName[xh]=Umxhasi Weposi GenericName[zh_CN]=邮件客户程序 diff --git a/kmail/Makefile.am b/kmail/Makefile.am index 56b89dbcf..629390928 100644 --- a/kmail/Makefile.am +++ b/kmail/Makefile.am @@ -1,5 +1,5 @@ #KDE_OPTIONS = nofinal -KDE_CXXFLAGS = $(USE_RTTI) +KDE_CXXFLAGS = $(USE_RTTI) -fexceptions SUBDIRS = interfaces . about pics profiles avscripts tests @@ -91,6 +91,7 @@ libkmailprivate_la_SOURCES = kmmessage.cpp kmmainwin.cpp configuredialog.cpp \ headerstrategy.cpp headerstyle.cpp khtmlparthtmlwriter.cpp \ filehtmlwriter.cpp teehtmlwriter.cpp \ mailcomposerIface.skel objecttreeparser.cpp \ + objecttreeparser_p.cpp \ attachmentcollector.cpp \ bodypartformatter.cpp bodypartformatterfactory.cpp \ partNode.cpp \ @@ -150,7 +151,15 @@ libkmailprivate_la_SOURCES = kmmessage.cpp kmmainwin.cpp configuredialog.cpp \ snippetsettingsbase.ui \ scalix.cpp \ messageactions.cpp \ - korghelper.cpp + korghelper.cpp \ + foldersetselector.cpp \ + stringutil.cpp \ + treebase.cpp \ + backupjob.cpp \ + importjob.cpp \ + folderutil.cpp \ + archivefolderdialog.cpp \ + importarchivedialog.cpp libkmailprivate_la_COMPILE_FIRST = globalsettings_base.h customtemplates_base.h templatesconfiguration_base.h @@ -209,7 +218,7 @@ update_SCRIPTS = upgrade-transport.pl kmail-pgpidentity.pl \ kmail-3.3b1-misc.pl \ kmail-3.4-misc.pl \ kmail-3.4.1-update-status-filters.pl \ - kmail-3.5-filter-icons.pl \ + kmail-3.5-filter-icons.pl \ kmail-3.5-trigger-flag-migration.pl confdir = $(kde_confdir) @@ -226,10 +235,10 @@ kde_services_DATA = kmail_config_misc.desktop kmail_config_appearance.desktop \ kmail_config_security.desktop messages: rc.cpp - rm -f tips.txt - $(PREPARETIPS) > tips.txt - $(XGETTEXT) -ktranslate *.cpp *.txt *.h -o $(podir)/kmail.pot - rm -f tips.txt + rm -f tips.cpp + $(PREPARETIPS) > tips.cpp + $(XGETTEXT) -ktranslate *.cpp *.h -o $(podir)/kmail.pot + rm -f tips.cpp kde_kcfg_DATA = kmail.kcfg replyphrases.kcfg custommimeheader.kcfg \ templatesconfiguration_kfg.kcfg customtemplates_kfg.kcfg diff --git a/kmail/accountdialog.cpp b/kmail/accountdialog.cpp index 6203d3e87..8254bac9a 100644 --- a/kmail/accountdialog.cpp +++ b/kmail/accountdialog.cpp @@ -984,7 +984,7 @@ void AccountDialog::makeImapAccountPage( bool connected ) ++row; mImap.subscribedFoldersCheck = new TQCheckBox( - i18n("Show only s&ubscribed folders"), page1); + i18n("Show only serverside s&ubscribed folders"), page1); grid->addMultiCellWidget( mImap.subscribedFoldersCheck, row, row, 0, 1 ); ++row; diff --git a/kmail/accountwizard.cpp b/kmail/accountwizard.cpp index de6c43178..589ef5d63 100644 --- a/kmail/accountwizard.cpp +++ b/kmail/accountwizard.cpp @@ -146,7 +146,7 @@ void AccountWizard::showPage( TQWidget *page ) const KPIM::Identity &identity = manager->defaultIdentity(); mRealName->setText( identity.fullName() ); - mEMailAddress->setText( identity.emailAddr() ); + mEMailAddress->setText( identity.primaryEmailAddress() ); mOrganization->setText( identity.organization() ); } } else if ( page == mLoginInformationPage ) { @@ -356,7 +356,7 @@ void AccountWizard::accept() KPIM::Identity &identity = manager->modifyIdentityForUoid( manager->defaultIdentity().uoid() ); identity.setFullName( mRealName->text() ); - identity.setEmailAddr( mEMailAddress->text() ); + identity.setPrimaryEmailAddress( mEMailAddress->text() ); identity.setOrganization( mOrganization->text() ); manager->commit(); diff --git a/kmail/acljobs.cpp b/kmail/acljobs.cpp index 41931829e..1f9b6bb5d 100644 --- a/kmail/acljobs.cpp +++ b/kmail/acljobs.cpp @@ -48,7 +48,9 @@ static unsigned int IMAPRightsToPermission( const TQString& str, const KURL& url case 'w': perm |= ACLJobs::WriteFlags; break; case 'i': perm |= ACLJobs::Insert; break; case 'p': perm |= ACLJobs::Post; break; + case 'k': // fall through case 'c': perm |= ACLJobs::Create; break; + case 'x': // fall through case 'd': perm |= ACLJobs::Delete; break; case 'a': perm |= ACLJobs::Administer; break; default: break; diff --git a/kmail/acljobs.h b/kmail/acljobs.h index 7abc8065b..2a868540b 100644 --- a/kmail/acljobs.h +++ b/kmail/acljobs.h @@ -59,6 +59,14 @@ namespace KMail { */ namespace ACLJobs { + // Used by KMFolderCachedImap and KMFolderImap, no better place for that yet, until we have a + // common base class for both + enum ACLFetchState { + NotFetchedYet, ///< The user rights/ACL have not been fetched from the server yet, we don't know them + Ok, ///< The user rights/ACL have been fetched from the server sucessfully + FetchFailed ///< The attempt to fetch the user rights/ACL from the server failed + }; + /// Bitfield modelling the possible permissions. /// This is modelled after the imap4 permissions except that Read is "rs". /// The semantics of the bits is protocol-dependent. diff --git a/kmail/annotationjobs.cpp b/kmail/annotationjobs.cpp index 6b9cd8e86..4e5dc2015 100644 --- a/kmail/annotationjobs.cpp +++ b/kmail/annotationjobs.cpp @@ -117,7 +117,7 @@ void AnnotationJobs::MultiGetAnnotationJob::slotResult( KIO::Job *job ) GetAnnotationJob* getJob = static_cast( job ); const AnnotationList& lst = getJob->annotations(); for ( unsigned int i = 0 ; i < lst.size() ; ++ i ) { - kdDebug(5006) << "found annotation " << lst[i].name << " = " << lst[i].value << endl; + //kdDebug(5006) << "found annotation " << lst[i].name << " = " << lst[i].value << endl; if ( lst[i].name.startsWith( "value." ) ) { // value.priv or value.shared found = true; value = lst[i].value; diff --git a/kmail/antispamwizard.cpp b/kmail/antispamwizard.cpp index e01093fd3..17d7ed1ed 100644 --- a/kmail/antispamwizard.cpp +++ b/kmail/antispamwizard.cpp @@ -34,7 +34,6 @@ #include "kmfilteraction.h" #include "kmfiltermgr.h" #include "kmkernel.h" -#include "kmfolderseldlg.h" #include "kmfoldertree.h" #include "kmmainwin.h" #include "networkaccount.h" @@ -428,7 +427,13 @@ void AntiSpamWizard::accept() classHamFilter->setConfigureShortcut( true ); classHamFilter->setConfigureToolbar( true ); filterList.append( classHamFilter ); - } + } + + /* Now that all the filters have been added to the list, tell + * the filter manager about it. That will emit filterListUpdate + * which will result in the filter list in kmmainwidget being + * initialized. This should happend only once. */ + KMKernel::self()->filterMgr()->appendFilters( filterList ); /* Now that all the filters have been added to the list, tell * the filter manager about it. That will emit filterListUpdate @@ -834,9 +839,8 @@ ASWizPage::ASWizPage( TQWidget * parent, const char * name, banner = *bannerName; mLayout = new TQHBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() ); - mPixmap = new TQPixmap( UserIcon(banner) ); mBannerLabel = new TQLabel( this ); - mBannerLabel->setPixmap( *mPixmap ); + mBannerLabel->setPixmap( UserIcon(banner) ); mBannerLabel->setScaledContents( false ); mBannerLabel->setFrameShape( TQFrame::StyledPanel ); mBannerLabel->setFrameShadow( TQFrame::Sunken ); diff --git a/kmail/antispamwizard.h b/kmail/antispamwizard.h index e9707e822..738d8516a 100644 --- a/kmail/antispamwizard.h +++ b/kmail/antispamwizard.h @@ -29,6 +29,8 @@ #ifndef KMAIL_ANTISPAMWIZARD_H #define KMAIL_ANTISPAMWIZARD_H +#include "simplefoldertree.h" + #include #include #include @@ -44,7 +46,6 @@ class TQLabel; namespace KMail { - class SimpleFolderTree; class FolderRequester; class ASWizInfoPage; @@ -289,7 +290,6 @@ namespace KMail { TQBoxLayout *mLayout; private: - TQPixmap *mPixmap; TQLabel *mBannerLabel; }; diff --git a/kmail/app_octetstream.cpp b/kmail/app_octetstream.cpp index 735946452..c740f02df 100644 --- a/kmail/app_octetstream.cpp +++ b/kmail/app_octetstream.cpp @@ -38,7 +38,7 @@ namespace { class Formatter : public KMail::Interface::BodyPartFormatter { public: - Result format( KMail::Interface::BodyPart *, KMail::HtmlWriter * ) const { return AsIcon; } + Result format( KMail::Interface::BodyPart *, KMail::HtmlWriter *, KMail::Callback & ) const { return AsIcon; } }; class Plugin : public KMail::Interface::BodyPartFormatterPlugin { diff --git a/kmail/application_octetstream.desktop b/kmail/application_octetstream.desktop index a0272b6bb..ec2bbf1ec 100644 --- a/kmail/application_octetstream.desktop +++ b/kmail/application_octetstream.desktop @@ -14,7 +14,6 @@ Name[fy]=Applikaasje octetstream Name[gl]=Aplicación Octetstream Name[hu]=Alkalmazás-adatfolyam Name[ja]=アプリケーション オクテット ストリーム -Name[ka]=რვადინებიანი პროგრამა Name[kk]=Қолданбаның бинарлы ағымы Name[km]=Octetstream កម្មវិធី Name[ms]=Aliran Oktet Aplikasi @@ -55,7 +54,6 @@ Comment[hu]=Formázómodul application/octet-stream típusú adatfolyamokhoz Comment[is]=Sniðmátstól fyrir application/octet-stream Comment[it]=Un plugin per formattare application/octet-stream Comment[ja]=application/octet-stream の Bodypart フォーマッタプラグイン -Comment[ka]=ნაწილიბრივი დამფორმატებელი მოდული პროგრამისთვის/octet-stream Comment[kk]=Application/octet-stream үшін пішімдегіш модулі Comment[km]=កម្មវិធី​ជំនួយ​កម្មវិធី​ធ្វើ​ទ្រង់ទ្រាយ​ផ្នែក​តួ សម្រាប់​កម្មវិធី/octet-stream Comment[lt]=application/octet-stream formatavimo priedas diff --git a/kmail/archivefolderdialog.cpp b/kmail/archivefolderdialog.cpp new file mode 100644 index 000000000..5bf2cf93f --- /dev/null +++ b/kmail/archivefolderdialog.cpp @@ -0,0 +1,209 @@ +/* Copyright 2009 Klarälvdalens Datakonsult AB + + 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) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 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, see . +*/ +#include "archivefolderdialog.h" + +#include "backupjob.h" +#include "kmkernel.h" +#include "kmfolder.h" +#include "kmmainwidget.h" +#include "folderrequester.h" +#include "util.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace KMail; + +static TQString standardArchivePath( const TQString &folderName ) +{ + TQString currentPath = KGlobalSettings::documentPath(); + TQDir dir( currentPath ); + if( !dir.exists() ) { + currentPath = TQDir::homeDirPath() + '/'; + } + return currentPath + + i18n( "Start of the filename for a mail archive file" , "Archive" ) + "_" + folderName + + "_" + TQDate::currentDate().toString( TQt::ISODate ) + ".tar.bz2"; +} + +ArchiveFolderDialog::ArchiveFolderDialog( TQWidget *parent ) + : KDialogBase( parent, "archive_folder_dialog", false, i18n( "Archive Folder" ), + KDialogBase::Ok | KDialogBase::Cancel, + KDialogBase::Ok, true ), + mParentWidget( parent ) +{ + TQWidget *mainWidget = new TQWidget( this ); + TQGridLayout *mainLayout = new TQGridLayout( mainWidget ); + mainLayout->setSpacing( KDialog::spacingHint() ); + mainLayout->setMargin( KDialog::marginHint() ); + setMainWidget( mainWidget ); + + int row = 0; + + // TODO: better label for "Ok" button + // TODO: Explaination label + // TODO: Use QFormLayout in KDE4 + + TQLabel *folderLabel = new TQLabel( i18n( "&Folder:" ), mainWidget ); + mainLayout->addWidget( folderLabel, row, 0 ); + mFolderRequester = new FolderRequester( mainWidget, kmkernel->getKMMainWidget()->folderTree() ); + mFolderRequester->setMustBeReadWrite( false ); + connect( mFolderRequester, TQT_SIGNAL(folderChanged(KMFolder *)), + TQT_SLOT(slotFolderChanged(KMFolder *)) ); + folderLabel->setBuddy( mFolderRequester ); + mainLayout->addWidget( mFolderRequester, row, 1 ); + row++; + + TQLabel *formatLabel = new TQLabel( i18n( "F&ormat:" ), mainWidget ); + mainLayout->addWidget( formatLabel, row, 0 ); + mFormatComboBox = new KComboBox( mainWidget ); + formatLabel->setBuddy( mFormatComboBox ); + + // These combobox values have to stay in sync with the ArchiveType enum from BackupJob! + mFormatComboBox->insertItem( i18n( "Compressed Zip Archive (.zip)" ) ); + mFormatComboBox->insertItem( i18n( "Uncompressed Archive (.tar)" ) ); + mFormatComboBox->insertItem( i18n( "BZ2-Compressed Tar Archive (.tar.bz2)" ) ); + mFormatComboBox->insertItem( i18n( "GZ-Compressed Tar Archive (.tar.gz)" ) ); + mFormatComboBox->setCurrentItem( 2 ); + connect( mFormatComboBox, TQT_SIGNAL(activated(int)), + this, TQT_SLOT(slotFixFileExtension()) ); + mainLayout->addWidget( mFormatComboBox, row, 1 ); + row++; + + TQLabel *fileNameLabel = new TQLabel( i18n( "&Archive File:" ), mainWidget ); + mainLayout->addWidget( fileNameLabel, row, 0 ); + mUrlRequester = new KURLRequester( mainWidget ); + mUrlRequester->setMode( KFile::LocalOnly ); + mUrlRequester->setFilter( "*.tar *.zip *.tar.gz *.tar.bz2" ); + mUrlRequester->fileDialog()->setKeepLocation( true ); + fileNameLabel->setBuddy( mUrlRequester ); + connect( mUrlRequester->lineEdit(), TQT_SIGNAL(textChanged(const TQString &)), + TQT_SLOT(slotUrlChanged(const TQString &)) ); + connect( mUrlRequester, TQT_SIGNAL(urlSelected(const TQString&)), + this, TQT_SLOT(slotFixFileExtension()) ); + mainLayout->addWidget( mUrlRequester, row, 1 ); + row++; + + // TODO: Make this appear more dangerous! + mDeleteCheckBox = new TQCheckBox( i18n( "&Delete folders after completion" ), mainWidget ); + mainLayout->addMultiCellWidget( mDeleteCheckBox, row, row, 0, 1, TQt::AlignLeft ); + row++; + + // TODO: what's this, tooltips + + // TODO: Warn that user should do mail check for online IMAP and possibly cached IMAP as well + + mainLayout->setColStretch( 1, 1 ); + mainLayout->addItem( new TQSpacerItem( 1, 1, TQSizePolicy::Expanding, TQSizePolicy::Expanding ), row, 0 ); + + // Make it a bit bigger, else the folder requester cuts off the text too early + resize( 500, minimumSize().height() ); +} + +void ArchiveFolderDialog::slotUrlChanged( const TQString &text ) +{ + enableButton( Ok, !text.isEmpty() ); +} + +bool canRemoveFolder( KMFolder *folder ) +{ + return + folder && + folder->canDeleteMessages() && + !folder->noContent() && + !folder->isSystemFolder(); +} + +void ArchiveFolderDialog::slotFolderChanged( KMFolder *folder ) +{ + mDeleteCheckBox->setEnabled( canRemoveFolder( folder ) ); + enableButton( Ok, folder && !folder->noContent()); +} + +void ArchiveFolderDialog::setFolder( KMFolder *defaultFolder ) +{ + mFolderRequester->setFolder( defaultFolder ); + // TODO: what if the file already exists? + mUrlRequester->setURL( standardArchivePath( defaultFolder->name() ) ); + mDeleteCheckBox->setEnabled( canRemoveFolder( defaultFolder ) ); +} + +void ArchiveFolderDialog::slotOk() +{ + if ( !Util::checkOverwrite( mUrlRequester->url(), this ) ) { + return; + } + + if ( !mFolderRequester->folder() ) { + KMessageBox::information( this, i18n( "Please select the folder that should be archived." ), + i18n( "No folder selected" ) ); + return; + } + + // TODO: check if url is empty. or better yet, disable ok button until file is chosen + KMail::BackupJob *backupJob = new KMail::BackupJob( mParentWidget ); + backupJob->setRootFolder( mFolderRequester->folder() ); + backupJob->setSaveLocation( mUrlRequester->url() ); + backupJob->setArchiveType( static_cast( mFormatComboBox->currentItem() ) ); + backupJob->setDeleteFoldersAfterCompletion( mDeleteCheckBox->isEnabled() && + mDeleteCheckBox->isChecked() ); + backupJob->start(); + accept(); +} + +void ArchiveFolderDialog::slotFixFileExtension() +{ + // KDE4: use KMimeType::extractKnownExtension() here + const int numExtensions = 4; + + // These extensions are sorted differently, .tar has to come last, or it will match before giving + // the more specific ones time to match. + const char *sortedExtensions[numExtensions] = { ".zip", ".tar.bz2", ".tar.gz", ".tar" }; + + // The extensions here are also sorted, like the enum order of BackupJob::ArchiveType + const char *extensions[numExtensions] = { ".zip", ".tar", ".tar.bz2", ".tar.gz" }; + + TQString fileName = mUrlRequester->url(); + if ( fileName.isEmpty() ) { + fileName = standardArchivePath( mFolderRequester->folder() ? + mFolderRequester->folder()->name() : "" ); + } + + // First, try to find the extension of the file name and remove it + for( int i = 0; i < numExtensions; i++ ) { + int index = fileName.lower().findRev( sortedExtensions[i] ); + if ( index != -1 ) { + fileName = fileName.left( fileName.length() - TQString( sortedExtensions[i] ).length() ); + break; + } + } + + // Now, we've got a filename without an extension, simply append the correct one + fileName += extensions[mFormatComboBox->currentItem()]; + mUrlRequester->setURL( fileName ); +} + +#include "archivefolderdialog.moc" diff --git a/kmail/archivefolderdialog.h b/kmail/archivefolderdialog.h new file mode 100644 index 000000000..806d5b127 --- /dev/null +++ b/kmail/archivefolderdialog.h @@ -0,0 +1,62 @@ +/* Copyright 2009 Klarälvdalens Datakonsult AB + + 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) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 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, see . +*/ +#ifndef ARCHIVEFOLDERDIALOG_H +#define ARCHIVEFOLDERDIALOG_H + +#include + +class TQCheckBox; +class KURLRequester; +class KComboBox; +class KMFolder; + +namespace KMail +{ +class FolderRequester; + +class ArchiveFolderDialog : public KDialogBase +{ + Q_OBJECT + + public: + + ArchiveFolderDialog( TQWidget *parent = 0 ); + void setFolder( KMFolder *defaultFolder ); + + protected slots: + + void slotFixFileExtension(); + void slotFolderChanged( KMFolder * ); + void slotUrlChanged( const TQString & ); + + /** reimp */ + virtual void slotOk(); + + private: + + TQWidget *mParentWidget; + TQCheckBox *mDeleteCheckBox; + FolderRequester *mFolderRequester; + KComboBox *mFormatComboBox; + KURLRequester *mUrlRequester; +}; + +} + +#endif diff --git a/kmail/attachmentcollector.h b/kmail/attachmentcollector.h index 52454e8d2..e74496072 100644 --- a/kmail/attachmentcollector.h +++ b/kmail/attachmentcollector.h @@ -40,20 +40,7 @@ namespace KMail { class AttachmentCollector { public: - AttachmentCollector() - : mDiveIntoEncryptions( true ), - mDiveIntoSignatures( true ), - mDiveIntoMessages( false ) {} - - void setDiveIntoEncryptions( bool dive ) { - mDiveIntoEncryptions = dive; - } - void setDiveIntoSignatures( bool dive ) { - mDiveIntoSignatures = dive; - } - void setDiveIntoMessages( bool dive ) { - mDiveIntoMessages = dive; - } + AttachmentCollector() {} void collectAttachmentsFrom( partNode * node ); @@ -61,9 +48,6 @@ namespace KMail { private: std::vector mAttachments; - bool mDiveIntoEncryptions : 1; - bool mDiveIntoSignatures : 1; - bool mDiveIntoMessages : 1; private: // disabled AttachmentCollector( const AttachmentCollector & ); diff --git a/kmail/attachmentstrategy.cpp b/kmail/attachmentstrategy.cpp index 8d1e59a90..6e147b946 100644 --- a/kmail/attachmentstrategy.cpp +++ b/kmail/attachmentstrategy.cpp @@ -45,6 +45,21 @@ namespace KMail { +static AttachmentStrategy::Display smartDisplay( const partNode *node ) +{ + if ( node->hasContentDispositionInline() ) + // explict "inline" disposition: + return AttachmentStrategy::Inline; + if ( node->isAttachment() ) + // explicit "attachment" disposition: + return AttachmentStrategy::AsIcon; + if ( node->type() == DwMime::kTypeText && + node->msgPart().fileName().stripWhiteSpace().isEmpty() && + node->msgPart().name().stripWhiteSpace().isEmpty() ) + // text/* w/o filename parameter: + return AttachmentStrategy::Inline; + return AttachmentStrategy::AsIcon; +} // // IconicAttachmentStrategy: @@ -60,7 +75,7 @@ namespace KMail { public: const char * name() const { return "iconic"; } const AttachmentStrategy * next() const { return smart(); } - const AttachmentStrategy * prev() const { return hidden(); } + const AttachmentStrategy * prev() const { return headerOnly(); } bool inlineNestedMessages() const { return false; } Display defaultDisplay( const partNode * ) const { return AsIcon; } @@ -86,18 +101,7 @@ namespace KMail { bool inlineNestedMessages() const { return true; } Display defaultDisplay( const partNode * node ) const { - if ( node->hasContentDispositionInline() ) - // explict "inline" disposition: - return Inline; - if ( node->isAttachment() ) - // explicit "attachment" disposition: - return AsIcon; - if ( node->type() == DwMime::kTypeText && - node->msgPart().fileName().stripWhiteSpace().isEmpty() && - node->msgPart().name().stripWhiteSpace().isEmpty() ) - // text/* w/o filename parameter: - return Inline; - return AsIcon; + return smartDisplay( node ); } }; @@ -134,13 +138,45 @@ namespace KMail { public: const char * name() const { return "hidden"; } - const AttachmentStrategy * next() const { return iconic(); } + const AttachmentStrategy * next() const { return headerOnly(); } const AttachmentStrategy * prev() const { return inlined(); } bool inlineNestedMessages() const { return false; } Display defaultDisplay( const partNode * ) const { return None; } }; + class HeaderOnlyAttachmentStrategy : public AttachmentStrategy { + friend class ::KMail::AttachmentStrategy; + protected: + HeaderOnlyAttachmentStrategy() : AttachmentStrategy() {} + virtual ~HeaderOnlyAttachmentStrategy() {} + + public: + const char * name() const { return "headerOnly"; } + const AttachmentStrategy * next() const { return iconic(); } + const AttachmentStrategy * prev() const { return hidden(); } + + bool inlineNestedMessages() const { + return true; + } + + Display defaultDisplay( const partNode *node ) const { + if ( node->isInEncapsulatedMessage() ) { + return smartDisplay( node ); + } + + partNode::AttachmentDisplayInfo info = node->attachmentDisplayInfo(); + if ( info.displayInHeader ) { + // The entire point about this attachment strategy: Hide attachments in the body that are + // already displayed in the attachment quick list + return None; + } else { + return smartDisplay( node ); + } + } + }; + + // // AttachmentStrategy abstract base: @@ -156,10 +192,11 @@ namespace KMail { const AttachmentStrategy * AttachmentStrategy::create( Type type ) { switch ( type ) { - case Iconic: return iconic(); - case Smart: return smart(); - case Inlined: return inlined(); - case Hidden: return hidden(); + case Iconic: return iconic(); + case Smart: return smart(); + case Inlined: return inlined(); + case Hidden: return hidden(); + case HeaderOnly: return headerOnly(); } kdFatal( 5006 ) << "AttachmentStrategy::create(): Unknown attachment startegy ( type == " << (int)type << " ) requested!" << endl; @@ -168,10 +205,11 @@ namespace KMail { const AttachmentStrategy * AttachmentStrategy::create( const TQString & type ) { TQString lowerType = type.lower(); - if ( lowerType == "iconic" ) return iconic(); + if ( lowerType == "iconic" ) return iconic(); //if ( lowerType == "smart" ) return smart(); // not needed, see below - if ( lowerType == "inlined" ) return inlined(); - if ( lowerType == "hidden" ) return hidden(); + if ( lowerType == "inlined" ) return inlined(); + if ( lowerType == "hidden" ) return hidden(); + if ( lowerType == "headeronly" ) return headerOnly(); // don't kdFatal here, b/c the strings are user-provided // (KConfig), so fail gracefully to the default: return smart(); @@ -181,6 +219,7 @@ namespace KMail { static const AttachmentStrategy * smartStrategy = 0; static const AttachmentStrategy * inlinedStrategy = 0; static const AttachmentStrategy * hiddenStrategy = 0; + static const AttachmentStrategy * headerOnlyStrategy = 0; const AttachmentStrategy * AttachmentStrategy::iconic() { if ( !iconicStrategy ) @@ -206,4 +245,10 @@ namespace KMail { return hiddenStrategy; } + const AttachmentStrategy * AttachmentStrategy::headerOnly() { + if ( !headerOnlyStrategy ) + headerOnlyStrategy = new HeaderOnlyAttachmentStrategy(); + return headerOnlyStrategy; + } + } // namespace KMail diff --git a/kmail/attachmentstrategy.h b/kmail/attachmentstrategy.h index e4440b246..835768949 100644 --- a/kmail/attachmentstrategy.h +++ b/kmail/attachmentstrategy.h @@ -46,7 +46,7 @@ namespace KMail { // // Factory methods: // - enum Type { Iconic, Smart, Inlined, Hidden }; + enum Type { Iconic, Smart, Inlined, Hidden, HeaderOnly }; static const AttachmentStrategy * create( Type type ); static const AttachmentStrategy * create( const TQString & type ); @@ -55,6 +55,7 @@ namespace KMail { static const AttachmentStrategy * smart(); static const AttachmentStrategy * inlined(); static const AttachmentStrategy * hidden(); + static const AttachmentStrategy * headerOnly(); // // Navigation methods: diff --git a/kmail/backupjob.cpp b/kmail/backupjob.cpp new file mode 100644 index 000000000..fd53997b3 --- /dev/null +++ b/kmail/backupjob.cpp @@ -0,0 +1,500 @@ +/* Copyright 2009 Klarälvdalens Datakonsult AB + + 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) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 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, see . +*/ +#include "backupjob.h" + +#include "kmmsgdict.h" +#include "kmfolder.h" +#include "kmfoldercachedimap.h" +#include "kmfolderdir.h" +#include "folderutil.h" + +#include "progressmanager.h" + +#include "kzip.h" +#include "ktar.h" +#include "kmessagebox.h" + +#include "tqfile.h" +#include "tqfileinfo.h" +#include "tqstringlist.h" + +using namespace KMail; + +BackupJob::BackupJob( TQWidget *parent ) + : TQObject( parent ), + mArchiveType( Zip ), + mRootFolder( 0 ), + mArchive( 0 ), + mParentWidget( parent ), + mCurrentFolderOpen( false ), + mArchivedMessages( 0 ), + mArchivedSize( 0 ), + mProgressItem( 0 ), + mAborted( false ), + mDeleteFoldersAfterCompletion( false ), + mCurrentFolder( 0 ), + mCurrentMessage( 0 ), + mCurrentJob( 0 ) +{ +} + +BackupJob::~BackupJob() +{ + mPendingFolders.clear(); + if ( mArchive ) { + delete mArchive; + mArchive = 0; + } +} + +void BackupJob::setRootFolder( KMFolder *rootFolder ) +{ + mRootFolder = rootFolder; +} + +void BackupJob::setSaveLocation( const KURL &savePath ) +{ + mMailArchivePath = savePath; +} + +void BackupJob::setArchiveType( ArchiveType type ) +{ + mArchiveType = type; +} + +void BackupJob::setDeleteFoldersAfterCompletion( bool deleteThem ) +{ + mDeleteFoldersAfterCompletion = deleteThem; +} + +TQString BackupJob::stripRootPath( const TQString &path ) const +{ + TQString ret = path; + ret = ret.remove( mRootFolder->path() ); + if ( ret.startsWith( "/" ) ) + ret = ret.right( ret.length() - 1 ); + return ret; +} + +void BackupJob::queueFolders( KMFolder *root ) +{ + mPendingFolders.append( root ); + KMFolderDir *dir = root->child(); + if ( dir ) { + for ( KMFolderNode * node = dir->first() ; node ; node = dir->next() ) { + if ( node->isDir() ) + continue; + KMFolder *folder = static_cast( node ); + queueFolders( folder ); + } + } +} + +bool BackupJob::hasChildren( KMFolder *folder ) const +{ + KMFolderDir *dir = folder->child(); + if ( dir ) { + for ( KMFolderNode * node = dir->first() ; node ; node = dir->next() ) { + if ( !node->isDir() ) + return true; + } + } + return false; +} + +void BackupJob::cancelJob() +{ + abort( i18n( "The operation was canceled by the user." ) ); +} + +void BackupJob::abort( const TQString &errorMessage ) +{ + // We could be called this twice, since killing the current job below will cause the job to fail, + // and that will call abort() + if ( mAborted ) + return; + + mAborted = true; + if ( mCurrentFolderOpen && mCurrentFolder ) { + mCurrentFolder->close( "BackupJob" ); + mCurrentFolder = 0; + } + if ( mArchive && mArchive->isOpened() ) { + mArchive->close(); + } + if ( mCurrentJob ) { + mCurrentJob->kill(); + mCurrentJob = 0; + } + if ( mProgressItem ) { + mProgressItem->setComplete(); + mProgressItem = 0; + // The progressmanager will delete it + } + + TQString text = i18n( "Failed to archive the folder '%1'." ).arg( mRootFolder->name() ); + text += "\n" + errorMessage; + KMessageBox::sorry( mParentWidget, text, i18n( "Archiving failed." ) ); + deleteLater(); + // Clean up archive file here? +} + +void BackupJob::finish() +{ + if ( mArchive->isOpened() ) { + mArchive->close(); + if ( !mArchive->closeSucceeded() ) { + abort( i18n( "Unable to finalize the archive file." ) ); + return; + } + } + + mProgressItem->setStatus( i18n( "Archiving finished" ) ); + mProgressItem->setComplete(); + mProgressItem = 0; + + TQFileInfo archiveFileInfo( mMailArchivePath.path() ); + TQString text = i18n( "Archiving folder '%1' successfully completed. " + "The archive was written to the file '%2'." ) + .arg( mRootFolder->name() ).arg( mMailArchivePath.path() ); + text += "\n" + i18n( "1 message of size %1 was archived.", + "%n messages with the total size of %1 were archived.", mArchivedMessages ) + .arg( KIO::convertSize( mArchivedSize ) ); + text += "\n" + i18n( "The archive file has a size of %1." ) + .arg( KIO::convertSize( archiveFileInfo.size() ) ); + KMessageBox::information( mParentWidget, text, i18n( "Archiving finished." ) ); + + if ( mDeleteFoldersAfterCompletion ) { + // Some saftey checks first... + if ( archiveFileInfo.size() > 0 && ( mArchivedSize > 0 || mArchivedMessages == 0 ) ) { + // Sorry for any data loss! + FolderUtil::deleteFolder( mRootFolder, mParentWidget ); + } + } + + deleteLater(); +} + +void BackupJob::archiveNextMessage() +{ + if ( mAborted ) + return; + + mCurrentMessage = 0; + if ( mPendingMessages.isEmpty() ) { + kdDebug(5006) << "===> All messages done in folder " << mCurrentFolder->name() << endl; + mCurrentFolder->close( "BackupJob" ); + mCurrentFolderOpen = false; + archiveNextFolder(); + return; + } + + unsigned long serNum = mPendingMessages.front(); + mPendingMessages.pop_front(); + + KMFolder *folder; + mMessageIndex = -1; + KMMsgDict::instance()->getLocation( serNum, &folder, &mMessageIndex ); + if ( mMessageIndex == -1 ) { + kdWarning(5006) << "Failed to get message location for sernum " << serNum << endl; + abort( i18n( "Unable to retrieve a message for folder '%1'." ).arg( mCurrentFolder->name() ) ); + return; + } + + Q_ASSERT( folder == mCurrentFolder ); + const KMMsgBase *base = mCurrentFolder->getMsgBase( mMessageIndex ); + mUnget = base && !base->isMessage(); + KMMessage *message = mCurrentFolder->getMsg( mMessageIndex ); + if ( !message ) { + kdWarning(5006) << "Failed to retrieve message with index " << mMessageIndex << endl; + abort( i18n( "Unable to retrieve a message for folder '%1'." ).arg( mCurrentFolder->name() ) ); + return; + } + + kdDebug(5006) << "Going to get next message with subject " << message->subject() << ", " + << mPendingMessages.size() << " messages left in the folder." << endl; + + if ( message->isComplete() ) { + // Use a singleshot timer, or otherwise we risk ending up in a very big recursion + // for folders that have many messages + mCurrentMessage = message; + TQTimer::singleShot( 0, this, TQT_SLOT( processCurrentMessage() ) ); + } + else if ( message->parent() ) { + mCurrentJob = message->parent()->createJob( message ); + mCurrentJob->setCancellable( false ); + connect( mCurrentJob, TQT_SIGNAL( messageRetrieved( KMMessage* ) ), + this, TQT_SLOT( messageRetrieved( KMMessage* ) ) ); + connect( mCurrentJob, TQT_SIGNAL( result( KMail::FolderJob* ) ), + this, TQT_SLOT( folderJobFinished( KMail::FolderJob* ) ) ); + mCurrentJob->start(); + } + else { + kdWarning(5006) << "Message with subject " << mCurrentMessage->subject() + << " is neither complete nor has a parent!" << endl; + abort( i18n( "Internal error while trying to retrieve a message from folder '%1'." ) + .arg( mCurrentFolder->name() ) ); + } + + mProgressItem->setProgress( ( mProgressItem->progress() + 5 ) ); +} + +static int fileInfoToUnixPermissions( const TQFileInfo &fileInfo ) +{ + int perm = 0; + if ( fileInfo.permission( TQFileInfo::ExeOther ) ) perm += S_IXOTH; + if ( fileInfo.permission( TQFileInfo::WriteOther ) ) perm += S_IWOTH; + if ( fileInfo.permission( TQFileInfo::ReadOther ) ) perm += S_IROTH; + if ( fileInfo.permission( TQFileInfo::ExeGroup ) ) perm += S_IXGRP; + if ( fileInfo.permission( TQFileInfo::WriteGroup ) ) perm += S_IWGRP; + if ( fileInfo.permission( TQFileInfo::ReadGroup ) ) perm += S_IRGRP; + if ( fileInfo.permission( TQFileInfo::ExeOwner ) ) perm += S_IXUSR; + if ( fileInfo.permission( TQFileInfo::WriteOwner ) ) perm += S_IWUSR; + if ( fileInfo.permission( TQFileInfo::ReadOwner ) ) perm += S_IRUSR; + return perm; +} + +void BackupJob::processCurrentMessage() +{ + if ( mAborted ) + return; + + if ( mCurrentMessage ) { + kdDebug(5006) << "Processing message with subject " << mCurrentMessage->subject() << endl; + const DwString &messageDWString = mCurrentMessage->asDwString(); + const uint messageSize = messageDWString.size(); + const char *messageString = mCurrentMessage->asDwString().c_str(); + TQString messageName; + TQFileInfo fileInfo; + if ( messageName.isEmpty() ) { + messageName = TQString::number( mCurrentMessage->getMsgSerNum() ); // IMAP doesn't have filenames + if ( mCurrentMessage->storage() ) { + fileInfo.setFile( mCurrentMessage->storage()->location() ); + // TODO: what permissions etc to take when there is no storage file? + } + } + else { + // TODO: What if the message is not in the "cur" directory? + fileInfo.setFile( mCurrentFolder->location() + "/cur/" + mCurrentMessage->fileName() ); + messageName = mCurrentMessage->fileName(); + } + + const TQString fileName = stripRootPath( mCurrentFolder->location() ) + + "/cur/" + messageName; + + TQString user; + TQString group; + mode_t permissions = 0700; + time_t creationTime = time( 0 ); + time_t modificationTime = time( 0 ); + time_t accessTime = time( 0 ); + if ( !fileInfo.fileName().isEmpty() ) { + user = fileInfo.owner(); + group = fileInfo.group(); + permissions = fileInfoToUnixPermissions( fileInfo ); + creationTime = fileInfo.created().toTime_t(); + modificationTime = fileInfo.lastModified().toTime_t(); + accessTime = fileInfo.lastRead().toTime_t(); + } + else { + kdWarning(5006) << "Unable to find file for message " << fileName << endl; + } + + if ( !mArchive->writeFile( fileName, user, group, messageSize, permissions, accessTime, + modificationTime, creationTime, messageString ) ) { + abort( i18n( "Failed to write a message into the archive folder '%1'." ).arg( mCurrentFolder->name() ) ); + return; + } + + if ( mUnget ) { + Q_ASSERT( mMessageIndex >= 0 ); + mCurrentFolder->unGetMsg( mMessageIndex ); + } + + mArchivedMessages++; + mArchivedSize += messageSize; + } + else { + // No message? According to ImapJob::slotGetMessageResult(), that means the message is no + // longer on the server. So ignore this one. + kdWarning(5006) << "Unable to download a message for folder " << mCurrentFolder->name() << endl; + } + archiveNextMessage(); +} + +void BackupJob::messageRetrieved( KMMessage *message ) +{ + mCurrentMessage = message; + processCurrentMessage(); +} + +void BackupJob::folderJobFinished( KMail::FolderJob *job ) +{ + if ( mAborted ) + return; + + // The job might finish after it has emitted messageRetrieved(), in which case we have already + // started a new job. Don't set the current job to 0 in that case. + if ( job == mCurrentJob ) { + mCurrentJob = 0; + } + + if ( job->error() ) { + if ( mCurrentFolder ) + abort( i18n( "Downloading a message in folder '%1' failed." ).arg( mCurrentFolder->name() ) ); + else + abort( i18n( "Downloading a message in the current folder failed." ) ); + } +} + +bool BackupJob::writeDirHelper( const TQString &directoryPath, const TQString &permissionPath ) +{ + TQFileInfo fileInfo( permissionPath ); + TQString user = fileInfo.owner(); + TQString group = fileInfo.group(); + mode_t permissions = fileInfoToUnixPermissions( fileInfo ); + time_t creationTime = fileInfo.created().toTime_t(); + time_t modificationTime = fileInfo.lastModified().toTime_t(); + time_t accessTime = fileInfo.lastRead().toTime_t(); + return mArchive->writeDir( stripRootPath( directoryPath ), user, group, permissions, accessTime, + modificationTime, creationTime ); +} + +void BackupJob::archiveNextFolder() +{ + if ( mAborted ) + return; + + if ( mPendingFolders.isEmpty() ) { + finish(); + return; + } + + mCurrentFolder = mPendingFolders.take( 0 ); + kdDebug(5006) << "===> Archiving next folder: " << mCurrentFolder->name() << endl; + mProgressItem->setStatus( i18n( "Archiving folder %1" ).arg( mCurrentFolder->name() ) ); + if ( mCurrentFolder->open( "BackupJob" ) != 0 ) { + abort( i18n( "Unable to open folder '%1'.").arg( mCurrentFolder->name() ) ); + return; + } + mCurrentFolderOpen = true; + + const TQString folderName = mCurrentFolder->name(); + bool success = true; + if ( hasChildren( mCurrentFolder ) ) { + if ( !writeDirHelper( mCurrentFolder->subdirLocation(), mCurrentFolder->subdirLocation() ) ) + success = false; + } + if ( !writeDirHelper( mCurrentFolder->location(), mCurrentFolder->location() ) ) + success = false; + if ( !writeDirHelper( mCurrentFolder->location() + "/cur", mCurrentFolder->location() ) ) + success = false; + if ( !writeDirHelper( mCurrentFolder->location() + "/new", mCurrentFolder->location() ) ) + success = false; + if ( !writeDirHelper( mCurrentFolder->location() + "/tmp", mCurrentFolder->location() ) ) + success = false; + if ( !success ) { + abort( i18n( "Unable to create folder structure for folder '%1' within archive file." ) + .arg( mCurrentFolder->name() ) ); + return; + } + + for ( int i = 0; i < mCurrentFolder->count( false /* no cache */ ); i++ ) { + unsigned long serNum = KMMsgDict::instance()->getMsgSerNum( mCurrentFolder, i ); + if ( serNum == 0 ) { + // Uh oh + kdWarning(5006) << "Got serial number zero in " << mCurrentFolder->name() + << " at index " << i << "!" << endl; + // TODO: handle error in a nicer way. this is _very_ bad + abort( i18n( "Unable to backup messages in folder '%1', the index file is corrupted." ) + .arg( mCurrentFolder->name() ) ); + return; + } + else + mPendingMessages.append( serNum ); + } + archiveNextMessage(); +} + +// TODO +// - error handling +// - import +// - connect to progressmanager, especially abort +// - messagebox when finished (?) +// - ui dialog +// - use correct permissions +// - save index and serial number? +// - guarded pointers for folders +// - online IMAP: check mails first, so sernums are up-to-date? +// - "ignore errors"-mode, with summary how many messages couldn't be archived? +// - do something when the user quits KMail while the backup job is running +// - run in a thread? +// - delete source folder after completion. dangerous!!! +// +// BUGS +// - Online IMAP: Test Mails -> Test%20Mails +// - corrupted sernums indices stop backup job +void BackupJob::start() +{ + Q_ASSERT( !mMailArchivePath.isEmpty() ); + Q_ASSERT( mRootFolder ); + + queueFolders( mRootFolder ); + + switch ( mArchiveType ) { + case Zip: { + KZip *zip = new KZip( mMailArchivePath.path() ); + zip->setCompression( KZip::DeflateCompression ); + mArchive = zip; + break; + } + case Tar: { + mArchive = new KTar( mMailArchivePath.path(), "application/x-tar" ); + break; + } + case TarGz: { + mArchive = new KTar( mMailArchivePath.path(), "application/x-gzip" ); + break; + } + case TarBz2: { + mArchive = new KTar( mMailArchivePath.path(), "application/x-bzip2" ); + break; + } + } + + kdDebug(5006) << "Starting backup." << endl; + if ( !mArchive->open( IO_WriteOnly ) ) { + abort( i18n( "Unable to open archive for writing." ) ); + return; + } + + mProgressItem = KPIM::ProgressManager::createProgressItem( + "BackupJob", + i18n( "Archiving" ), + TQString(), + true ); + mProgressItem->setUsesBusyIndicator( true ); + connect( mProgressItem, TQT_SIGNAL(progressItemCanceled(KPIM::ProgressItem*)), + this, TQT_SLOT(cancelJob()) ); + + archiveNextFolder(); +} + +#include "backupjob.moc" + diff --git a/kmail/backupjob.h b/kmail/backupjob.h new file mode 100644 index 000000000..f6383d669 --- /dev/null +++ b/kmail/backupjob.h @@ -0,0 +1,109 @@ +/* Copyright 2009 Klarälvdalens Datakonsult AB + + 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) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 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, see . +*/ +#ifndef BACKUPJOB_H +#define BACKUPJOB_H + +#include +#include + +#include + +class KMFolder; +class KMMessage; +class KArchive; +class KProcess; +class TQWidget; + +namespace KPIM { + class ProgressItem; +} + +namespace KMail +{ + class FolderJob; + +/** + * Writes an entire folder structure to an archive file. + * The archive is structured like a hierarchy of maildir folders. However, every type of folder + * works as the source, i.e. also online IMAP folders. + * + * The job deletes itself after it finished. + */ +class BackupJob : public TQObject +{ + Q_OBJECT + + public: + + // These enum values have to stay in sync with the format combobox of ArchiveFolderDialog! + enum ArchiveType { Zip = 0, Tar = 1, TarBz2 = 2, TarGz = 3 }; + + explicit BackupJob( TQWidget *parent = 0 ); + ~BackupJob(); + void setRootFolder( KMFolder *rootFolder ); + void setSaveLocation( const KURL &savePath ); + void setArchiveType( ArchiveType type ); + void setDeleteFoldersAfterCompletion( bool deleteThem ); + void start(); + + private slots: + + void messageRetrieved( KMMessage *message ); + void folderJobFinished( KMail::FolderJob *job ); + void processCurrentMessage(); + void cancelJob(); + + private: + + void queueFolders( KMFolder *root ); + void archiveNextFolder(); + void archiveNextMessage(); + TQString stripRootPath( const TQString &path ) const; + bool hasChildren( KMFolder *folder ) const; + void finish(); + void abort( const TQString &errorMessage ); + bool writeDirHelper( const TQString &directoryPath, const TQString &permissionPath ); + + KURL mMailArchivePath; + ArchiveType mArchiveType; + KMFolder *mRootFolder; + KArchive *mArchive; + TQWidget *mParentWidget; + bool mCurrentFolderOpen; + int mArchivedMessages; + uint mArchivedSize; + KPIM::ProgressItem *mProgressItem; + bool mAborted; + bool mDeleteFoldersAfterCompletion; + + // True if we obtained ownership of the kMMessage after calling getMsg(), since we need + // to call ungetMsg() then. For that, we also remember the original index. + bool mUnget; + int mMessageIndex; + + TQPtrList mPendingFolders; + KMFolder *mCurrentFolder; + TQValueList mPendingMessages; + KMMessage *mCurrentMessage; + FolderJob *mCurrentJob; +}; + +} + +#endif diff --git a/kmail/bodypartformatter.cpp b/kmail/bodypartformatter.cpp index efe12f672..6b61b9bce 100644 --- a/kmail/bodypartformatter.cpp +++ b/kmail/bodypartformatter.cpp @@ -39,6 +39,7 @@ #include "objecttreeparser.h" #include "partNode.h" +#include "callback.h" #include #include @@ -53,7 +54,7 @@ namespace { public KMail::Interface::BodyPartFormatter { static const AnyTypeBodyPartFormatter * self; public: - Result format( KMail::Interface::BodyPart *, KMail::HtmlWriter * ) const { + Result format( KMail::Interface::BodyPart *, KMail::HtmlWriter *, KMail::Callback & ) const { kdDebug(5006) << "AnyTypeBodyPartFormatter::format() acting as a KMail::Interface::BodyPartFormatter!" << endl; return AsIcon; } diff --git a/kmail/cachedimapjob.cpp b/kmail/cachedimapjob.cpp index 3b8ff4115..2393b2576 100644 --- a/kmail/cachedimapjob.cpp +++ b/kmail/cachedimapjob.cpp @@ -452,18 +452,21 @@ void CachedImapJob::slotPutMessageDataReq(KIO::Job *job, TQByteArray &data) } //---------------------------------------------------------------------------- -void CachedImapJob::slotPutMessageInfoData(KIO::Job *job, const TQString &data) +void CachedImapJob::slotPutMessageInfoData( KIO::Job *job, const TQString &data ) { - KMFolderCachedImap * imapFolder = static_cast(mDestFolder->storage()); - KMAcctCachedImap *account = imapFolder->account(); - ImapAccountBase::JobIterator it = account->findJob( job ); - if ( it == account->jobsEnd() ) return; + KMFolderCachedImap *imapFolder = static_cast( mDestFolder->storage() ); + if ( imapFolder ) { + KMAcctCachedImap *account = imapFolder->account(); + ImapAccountBase::JobIterator it = account->findJob( job ); + if ( it == account->jobsEnd() ) { + return; + } - if ( data.find("UID") != -1 && mMsg ) - { - int uid = (data.right(data.length()-4)).toInt(); - kdDebug( 5006 ) << k_funcinfo << "Server told us uid is: " << uid << endl; - mMsg->setUID( uid ); + if ( data.find( "UID" ) != -1 && mMsg ) { + int uid = ( data.right( data.length() - 4 ) ).toInt(); + kdDebug( 5006 ) << k_funcinfo << "Server told us uid is: " << uid << endl; + mMsg->setUID( uid ); + } } } @@ -728,20 +731,23 @@ void CachedImapJob::slotCheckUidValidityResult(KIO::Job * job) void CachedImapJob::renameFolder( const TQString &newName ) { + mNewName = newName; + // Set the source URL KURL urlSrc = mAccount->getUrl(); - urlSrc.setPath( mFolder->imapPath() ); + mOldImapPath = mFolder->imapPath(); + urlSrc.setPath( mOldImapPath ); // Set the destination URL - this is a bit trickier KURL urlDst = mAccount->getUrl(); - TQString imapPath( mFolder->imapPath() ); + mNewImapPath = mFolder->imapPath(); // Destination url = old imappath - oldname + new name - imapPath.truncate( imapPath.length() - mFolder->folder()->name().length() - 1); - imapPath += newName + '/'; - urlDst.setPath( imapPath ); + mNewImapPath.truncate( mNewImapPath.length() - mFolder->folder()->name().length() - 1); + mNewImapPath += newName + '/'; + urlDst.setPath( mNewImapPath ); ImapAccountBase::jobData jd( newName, mFolder->folder() ); - jd.path = imapPath; + jd.path = mNewImapPath; KIO::SimpleJob *simpleJob = KIO::rename( urlSrc, urlDst, false ); KIO::Scheduler::assignJobToSlave( mAccount->slave(), simpleJob ); @@ -774,6 +780,70 @@ static void renameChildFolders( KMFolderDir* dir, const TQString& oldPath, } } +void CachedImapJob::revertLabelChange() +{ + TQMap::ConstIterator renit = mAccount->renamedFolders().find( mFolder->imapPath() ); + Q_ASSERT( renit != mAccount->renamedFolders().end() ); + if ( renit != mAccount->renamedFolders().end() ) { + mFolder->folder()->setLabel( (*renit).mOldLabel ); + mAccount->removeRenamedFolder( mFolder->imapPath() ); + kmkernel->dimapFolderMgr()->contentsChanged(); + } +} + +void CachedImapJob::renameOnDisk() +{ + TQString oldName = mFolder->name(); + TQString oldPath = mFolder->imapPath(); + mAccount->removeRenamedFolder( oldPath ); + mFolder->setImapPath( mNewImapPath ); + mFolder->FolderStorage::rename( mNewName ); + + if( oldPath.endsWith( "/" ) ) oldPath.truncate( oldPath.length() -1 ); + TQString newPath = mFolder->imapPath(); + if( newPath.endsWith( "/" ) ) newPath.truncate( newPath.length() -1 ); + renameChildFolders( mFolder->folder()->child(), oldPath, newPath ); + kmkernel->dimapFolderMgr()->contentsChanged(); +} + +void CachedImapJob::slotSubscribtionChange1Failed( const TQString &errorMessage ) +{ + KMessageBox::sorry( 0, i18n( "Error while trying to subscribe to the renamed folder %1.\n" + "Renaming itself was successful, but the renamed folder might disappear " + "from the folder list after the next sync since it is unsubscribed on the server.\n" + "You can try to manually subscribe to the folder yourself.\n\n" + "%2" ) + .arg( mFolder->label() ).arg( errorMessage ) ); + delete this; +} + +void CachedImapJob::slotSubscribtionChange2Failed( const TQString &errorMessage ) +{ + kdWarning(5006) << k_funcinfo << errorMessage << endl; + // Ignore this error, not something user-visible anyway + delete this; +} + +void CachedImapJob::slotSubscribtionChange1Done( const TQString&, bool ) +{ + disconnect( mAccount, TQT_SIGNAL( subscriptionChanged( const TQString&, bool ) ), + this, TQT_SLOT( slotSubscribtionChange1Done( const TQString&, bool ) ) ); + connect( mAccount, TQT_SIGNAL( subscriptionChanged( const TQString&, bool ) ), + this, TQT_SLOT( slotSubscribtionChange2Done( const TQString&, bool ) ) ); + disconnect( mAccount, TQT_SIGNAL( subscriptionChangeFailed( const TQString& ) ), + this, TQT_SLOT( slotSubscribtionChange1Failed( const TQString& ) ) ); + connect( mAccount, TQT_SIGNAL( subscriptionChangeFailed( const TQString& ) ), + this, TQT_SLOT( slotSubscribtionChange2Failed( const TQString& ) ) ); + + mAccount->changeSubscription( false, mOldImapPath, true /* quiet */ ); +} + +void CachedImapJob::slotSubscribtionChange2Done( const TQString&, bool ) +{ + // Finally done with everything! + delete this; +} + void CachedImapJob::slotRenameFolderResult( KIO::Job *job ) { KMAcctCachedImap::JobIterator it = mAccount->findJob(job); @@ -782,34 +852,25 @@ void CachedImapJob::slotRenameFolderResult( KIO::Job *job ) return; } - if( job->error() ) { - // Error, revert label change - TQMap::ConstIterator renit = mAccount->renamedFolders().find( mFolder->imapPath() ); - Q_ASSERT( renit != mAccount->renamedFolders().end() ); - if ( renit != mAccount->renamedFolders().end() ) { - mFolder->folder()->setLabel( (*renit).mOldLabel ); - mAccount->removeRenamedFolder( mFolder->imapPath() ); - } - mAccount->handleJobError( job, i18n( "Error while trying to rename folder %1" ).arg( mFolder->label() ) + '\n' ); + revertLabelChange(); + const TQString errorMessage = i18n( "Error while trying to rename folder %1" ).arg( mFolder->label() ); + mAccount->handleJobError( job, errorMessage ); + delete this; } else { - // Okay, the folder seems to be renamed on the server, - // now rename it on disk - TQString oldName = mFolder->name(); - TQString oldPath = mFolder->imapPath(); - mAccount->removeRenamedFolder( oldPath ); - mFolder->setImapPath( (*it).path ); - mFolder->FolderStorage::rename( (*it).url ); - - if( oldPath.endsWith( "/" ) ) oldPath.truncate( oldPath.length() -1 ); - TQString newPath = mFolder->imapPath(); - if( newPath.endsWith( "/" ) ) newPath.truncate( newPath.length() -1 ); - renameChildFolders( mFolder->folder()->child(), oldPath, newPath ); - kmkernel->dimapFolderMgr()->contentsChanged(); - mAccount->removeJob(it); + mAccount->removeJob( it ); + renameOnDisk(); + + // Okay, the folder seems to be renamed on the server and on disk. + // Now unsubscribe from the old folder name and subscribe to the new folder name, + // so that the folder doesn't suddenly disappear after renaming it + connect( mAccount, TQT_SIGNAL( subscriptionChangeFailed( const TQString& ) ), + this, TQT_SLOT( slotSubscribtionChange1Failed( const TQString& ) ) ); + connect( mAccount, TQT_SIGNAL( subscriptionChanged( const TQString&, bool ) ), + this, TQT_SLOT( slotSubscribtionChange1Done( const TQString&, bool ) ) ); + mAccount->changeSubscription( true, mNewImapPath, true /* quiet */ ); } - delete this; } void CachedImapJob::slotListMessagesResult( KIO::Job * job ) diff --git a/kmail/cachedimapjob.h b/kmail/cachedimapjob.h index 6581ec742..4a2885b00 100644 --- a/kmail/cachedimapjob.h +++ b/kmail/cachedimapjob.h @@ -121,8 +121,16 @@ protected slots: virtual void slotListMessagesResult( KIO::Job * job ); void slotDeleteNextMessages( KIO::Job* job = 0 ); void slotProcessedSize( KIO::Job *, KIO::filesize_t processed ); + void slotSubscribtionChange1Done( const TQString&, bool ); + void slotSubscribtionChange2Done( const TQString&, bool ); + void slotSubscribtionChange1Failed( const TQString &errorMessage ); + void slotSubscribtionChange2Failed( const TQString &errorMessage ); private: + + void renameOnDisk(); + void revertLabelChange(); + KMFolderCachedImap *mFolder; KMAcctCachedImap *mAccount; TQValueList mFolderList; @@ -133,6 +141,7 @@ private: TQStringList mFoldersOrMessages; // Folder deletion: path list. Message deletion: sets of uids KMMessage* mMsg; TQString mString; // Used as uids and as rename target + TQString mOldImapPath, mNewImapPath, mNewName; // used for renaming KMFolderCachedImap *mParentFolder; }; diff --git a/kmail/callback.cpp b/kmail/callback.cpp index 0e25758c1..be787ff8e 100644 --- a/kmail/callback.cpp +++ b/kmail/callback.cpp @@ -41,6 +41,7 @@ #include "composer.h" #include "kmreaderwin.h" #include "secondarywindow.h" +#include "transportmanager.h" #include @@ -56,6 +57,32 @@ Callback::Callback( KMMessage* msg, KMReaderWin* readerWin ) { } +TQString Callback::askForTransport( bool nullIdentity ) const +{ + const TQStringList transports = KMail::TransportManager::transportNames(); + if ( transports.size() == 1 ) + return transports.first(); + + const TQString defaultTransport = GlobalSettings::self()->defaultTransport(); + const int defaultIndex = QMAX( 0, transports.findIndex( defaultTransport ) ); + + TQString text; + if ( nullIdentity ) + text = i18n( "The receiver of this invitation doesn't match any of your identities.
" + "Please select the transport which should be used to send your reply.
" ); + else + text = i18n( "The identity matching the receiver of this invitation doesn't have an " + "associated transport configured.
" + "Please select the transport which should be used to send your reply.
"); + bool ok; + const TQString transport = KInputDialog::getItem( i18n( "Select Transport" ), text, + transports, defaultIndex, FALSE, &ok, kmkernel->mainWin() ); + if ( !ok ) + return TQString(); + + return transport; +} + bool Callback::mailICal( const TQString& to, const TQString &iCal, const TQString& subject, const TQString &status, bool delMessage ) const @@ -67,13 +94,13 @@ bool Callback::mailICal( const TQString& to, const TQString &iCal, msg->setSubject( subject ); if ( GlobalSettings::self()->exchangeCompatibleInvitations() ) { if ( status == TQString("cancel") ) - msg->setSubject( TQString("Declined: %1").arg(subject).replace("Answer: ","") ); + msg->setSubject( i18n( "Declined: %1" ).arg(subject).replace("Answer: ","") ); else if ( status == TQString("tentative") ) - msg->setSubject(TQString("Tentative: %1").arg(subject).replace("Answer: ","") ); + msg->setSubject( i18n( "Tentative: %1" ).arg(subject).replace("Answer: ","") ); else if ( status == TQString("accepted") ) - msg->setSubject( TQString("Accepted: %1").arg(subject).replace("Answer: ","") ); + msg->setSubject( i18n( "Accepted: %1" ).arg(subject).replace("Answer: ","") ); else if ( status == TQString("delegated") ) - msg->setSubject( TQString("Delegated: %1").arg(subject).replace("Answer: ","") ); + msg->setSubject( i18n( "Delegated: %1" ).arg(subject).replace("Answer: ","") ); } msg->setTo( to ); msg->setFrom( receiver() ); @@ -89,23 +116,40 @@ bool Callback::mailICal( const TQString& to, const TQString &iCal, * has been sent successfully. Set a link header which accomplishes that. */ msg->link( mMsg, KMMsgStatusDeleted ); + // Try and match the receiver with an identity. + // Setting the identity here is important, as that is used to select the correct + // transport later + const KPIM::Identity& identity = kmkernel->identityManager()->identityForAddress( receiver() ); + const bool nullIdentity = ( identity == KPIM::Identity::null() ); + if ( !nullIdentity ) { + msg->setHeaderField("X-KMail-Identity", TQString::number( identity.uoid() )); + } + + const bool identityHasTransport = !identity.transport().isEmpty(); + if ( !nullIdentity && identityHasTransport ) + msg->setHeaderField( "X-KMail-Transport", identity.transport() ); + else if ( !nullIdentity && identity.isDefault() ) + msg->setHeaderField( "X-KMail-Transport", GlobalSettings::self()->defaultTransport() ); + else { + const TQString transport = askForTransport( nullIdentity ); + if ( transport.isEmpty() ) + return false; // user canceled transport selection dialog + msg->setHeaderField( "X-KMail-Transport", transport ); + } + // Outlook will only understand the reply if the From: header is the // same as the To: header of the invitation message. KConfigGroup options( KMKernel::config(), "Groupware" ); if( !options.readBoolEntry( "LegacyMangleFromToHeaders", true ) ) { - // Try and match the receiver with an identity - const KPIM::Identity& identity = - kmkernel->identityManager()->identityForAddress( receiver() ); if( identity != KPIM::Identity::null() ) { - // Identity found. Use this msg->setFrom( identity.fullEmailAddr() ); - msg->setHeaderField("X-KMail-Identity", TQString::number( identity.uoid() )); } // Remove BCC from identity on ical invitations (https://intevation.de/roundup/kolab/issue474) msg->setBcc( "" ); } KMail::Composer * cWin = KMail::makeComposer(); + cWin->ignoreStickyFields(); cWin->setMsg( msg, false /* mayAutoSign */ ); // cWin->setCharset( "", true ); cWin->disableWordWrap(); @@ -126,6 +170,8 @@ bool Callback::mailICal( const TQString& to, const TQString &iCal, cWin->addAttach( msgPart ); } + cWin->disableRecipientNumberCheck(); + cWin->disableForgottenAttachmentsCheck(); if ( options.readBoolEntry( "AutomaticSending", true ) ) { cWin->setAutoDeleteWindow( true ); cWin->slotSendNow(); @@ -170,7 +216,7 @@ TQString Callback::receiver() const selectMessage = i18n("None of your identities match the " "receiver of this message,
please " "choose which of the following addresses " - "is yours, if any:"); + "is yours, if any, or select one of your identities to use in the reply:"); addrs += kmkernel->identityManager()->allEmails(); } else { selectMessage = i18n("Several of your identities match the " @@ -179,10 +225,14 @@ TQString Callback::receiver() const "is yours:"); } + // select default identity by default + const TQString defaultAddr = kmkernel->identityManager()->defaultIdentity().primaryEmailAddress(); + const int defaultIndex = QMAX( 0, addrs.findIndex( defaultAddr ) ); + mReceiver = KInputDialog::getItem( i18n( "Select Address" ), selectMessage, - addrs+ccaddrs, 0, FALSE, &ok, kmkernel->mainWin() ); + addrs+ccaddrs, defaultIndex, FALSE, &ok, kmkernel->mainWin() ); if( !ok ) mReceiver = TQString::null; } @@ -213,6 +263,16 @@ bool Callback::deleteInvitationAfterReply() const return GlobalSettings::self()->deleteInvitationEmailsAfterSendingReply(); } +bool Callback::exchangeCompatibleInvitations() const +{ + return GlobalSettings::self()->exchangeCompatibleInvitations(); +} + +bool Callback::outlookCompatibleInvitationReplyComments() const +{ + return GlobalSettings::self()->outlookCompatibleInvitationReplyComments(); +} + TQString Callback::sender() const { return mMsg->from(); diff --git a/kmail/callback.h b/kmail/callback.h index 9bd188899..c7f26058f 100644 --- a/kmail/callback.h +++ b/kmail/callback.h @@ -77,8 +77,12 @@ public: bool askForComment( KCal::Attendee::PartStat status ) const; bool deleteInvitationAfterReply() const; + bool exchangeCompatibleInvitations() const; + bool outlookCompatibleInvitationReplyComments() const; private: + TQString askForTransport( bool nullIdentity ) const; + KMMessage* mMsg; KMReaderWin* mReaderWin; mutable TQString mReceiver; diff --git a/kmail/composer.h b/kmail/composer.h index 4c176aa14..91c473445 100644 --- a/kmail/composer.h +++ b/kmail/composer.h @@ -66,12 +66,19 @@ namespace KMail { virtual void setMsg( KMMessage * newMsg, bool mayAutoSign=true, bool allowDecryption=false, bool isModified=false) = 0; + /** + * Returns @c true while the message composing is in progress. + */ + virtual bool isComposing() const = 0; + public: // kmkernel /** * Set the filename which is used for autosaving. */ virtual void setAutoSaveFilename( const TQString & filename ) = 0; + + public: // kmkernel, callback /** * If this flag is set the message of the composer is deleted when @@ -141,6 +148,12 @@ namespace KMail { virtual void disableWordWrap() = 0; + virtual void disableRecipientNumberCheck() = 0; + + virtual void disableForgottenAttachmentsCheck() = 0; + + virtual void ignoreStickyFields() = 0; + public: // kmcommand /** * Add an attachment to the list. diff --git a/kmail/configuredialog.cpp b/kmail/configuredialog.cpp index ea0536a9f..32399f815 100644 --- a/kmail/configuredialog.cpp +++ b/kmail/configuredialog.cpp @@ -99,6 +99,7 @@ using KMime::DateFormatter; #include #include #include +#include // Qt headers: #include @@ -252,13 +253,15 @@ ConfigureDialog::~ConfigureDialog() { } void ConfigureDialog::slotApply() { - GlobalSettings::self()->writeConfig(); KCMultiDialog::slotApply(); + GlobalSettings::self()->writeConfig(); + emit configChanged(); } void ConfigureDialog::slotOk() { - GlobalSettings::self()->writeConfig(); KCMultiDialog::slotOk(); + GlobalSettings::self()->writeConfig(); + emit configChanged(); } void ConfigureDialog::slotUser2() { @@ -767,7 +770,6 @@ void AccountsPage::SendingTab::slotSetDefaultTransport() } else { item->setText( 1, i18n( "sendmail (Default)" )); } - GlobalSettings::self()->setDefaultTransport( item->text(0) ); } @@ -901,6 +903,10 @@ void AccountsPage::SendingTab::slotRemoveSelectedTransport() TQListViewItem *item = mTransportList->selectedItem(); if ( !item ) return; + bool selectedTransportWasDefault = false; + if ( item->text( 0 ) == GlobalSettings::self()->defaultTransport() ) { + selectedTransportWasDefault = true; + } TQStringList changedIdents; KPIM::IdentityManager * im = kmkernel->identityManager(); for ( KPIM::IdentityManager::Iterator it = im->modifyBegin(); it != im->modifyEnd(); ++it ) { @@ -930,25 +936,25 @@ void AccountsPage::SendingTab::slotRemoveSelectedTransport() KMTransportInfo ti; - TQListViewItem *newCurrent = item->itemBelow(); - if ( !newCurrent ) newCurrent = item->itemAbove(); - //mTransportList->removeItem( item ); - if ( newCurrent ) { - mTransportList->setCurrentItem( newCurrent ); - mTransportList->setSelected( newCurrent, true ); - GlobalSettings::self()->setDefaultTransport( newCurrent->text(0) ); - ti.readConfig( KMTransportInfo::findTransport( newCurrent->text(0) )); - if ( item->text( 0 ) == GlobalSettings::self()->defaultTransport() ) { + if( selectedTransportWasDefault ) + { + TQListViewItem *newCurrent = item->itemBelow(); + if ( !newCurrent ) newCurrent = item->itemAbove(); + //mTransportList->removeItem( item ); + if ( newCurrent ) { + mTransportList->setCurrentItem( newCurrent ); + mTransportList->setSelected( newCurrent, true ); + GlobalSettings::self()->setDefaultTransport( newCurrent->text(0) ); + ti.readConfig( KMTransportInfo::findTransport( newCurrent->text(0) )); if ( ti.type != "sendmail" ) { newCurrent->setText( 1, i18n("smtp (Default)") ); } else { newCurrent->setText( 1, i18n("sendmail (Default)" )); } + } else { + GlobalSettings::self()->setDefaultTransport( TQString::null ); } - } else { - GlobalSettings::self()->setDefaultTransport( TQString::null ); } - delete item; mTransportInfoList.remove( it ); @@ -1760,6 +1766,7 @@ AppearancePageColorsTab::AppearancePageColorsTab( TQWidget * parent, const char mCloseToQuotaThreshold = new TQSpinBox( 0, 100, 1, this ); connect( mCloseToQuotaThreshold, TQT_SIGNAL( valueChanged( int ) ), this, TQT_SLOT( slotEmitChanged( void ) ) ); + mCloseToQuotaThreshold->setEnabled( false ); mCloseToQuotaThreshold->setSuffix( i18n("%")); hbox->addWidget( mCloseToQuotaThreshold ); hbox->addWidget( new TQWidget(this), 2 ); @@ -1771,6 +1778,8 @@ AppearancePageColorsTab::AppearancePageColorsTab( TQWidget * parent, const char mRecycleColorCheck, TQT_SLOT(setEnabled(bool)) ); connect( mCustomColorCheck, TQT_SIGNAL(toggled(bool)), l, TQT_SLOT(setEnabled(bool)) ); + connect( mCustomColorCheck, TQT_SIGNAL(toggled(bool)), + mCloseToQuotaThreshold, TQT_SLOT(setEnabled(bool)) ); connect( mCustomColorCheck, TQT_SIGNAL( stateChanged( int ) ), this, TQT_SLOT( slotEmitChanged( void ) ) ); @@ -1909,10 +1918,6 @@ AppearancePageLayoutTab::AppearancePageLayoutTab( TQWidget * parent, const char connect( mFavoriteFolderViewCB, TQT_SIGNAL(toggled(bool)), TQT_SLOT(slotEmitChanged()) ); vlay->addWidget( mFavoriteFolderViewCB ); - mFolderQuickSearchCB = new TQCheckBox( i18n("Show folder quick search field"), this ); - connect( mFolderQuickSearchCB, TQT_SIGNAL(toggled(bool)), TQT_SLOT(slotEmitChanged()) ); - vlay->addWidget( mFolderQuickSearchCB ); - // "show reader window" radio buttons: populateButtonGroup( mReaderWindowModeGroup = new TQVButtonGroup( this ), readerWindowMode ); vlay->addWidget( mReaderWindowModeGroup ); @@ -1943,7 +1948,6 @@ void AppearancePage::LayoutTab::doLoadOther() { loadWidget( mMIMETreeModeGroup, reader, mimeTreeMode ); loadWidget( mReaderWindowModeGroup, geometry, readerWindowMode ); mFavoriteFolderViewCB->setChecked( GlobalSettings::self()->enableFavoriteFolderView() ); - mFolderQuickSearchCB->setChecked( GlobalSettings::self()->enableFolderQuickSearch() ); } void AppearancePage::LayoutTab::installProfile( KConfig * profile ) { @@ -1965,7 +1969,6 @@ void AppearancePage::LayoutTab::save() { saveButtonGroup( mMIMETreeModeGroup, reader, mimeTreeMode ); saveButtonGroup( mReaderWindowModeGroup, geometry, readerWindowMode ); GlobalSettings::self()->setEnableFavoriteFolderView( mFavoriteFolderViewCB->isChecked() ); - GlobalSettings::self()->setEnableFolderQuickSearch( mFolderQuickSearchCB->isChecked() ); } // @@ -2003,8 +2006,6 @@ AppearancePageHeadersTab::AppearancePageHeadersTab( TQWidget * parent, const cha group = new TQVButtonGroup( i18n( "General Options" ), this ); group->layout()->setSpacing( KDialog::spacingHint() ); - mShowQuickSearch = new TQCheckBox( i18n("Show Quick Search"), group ); - mMessageSizeCheck = new TQCheckBox( i18n("Display messa&ge sizes"), group ); mCryptoIconsCheck = new TQCheckBox( i18n( "Show crypto &icons" ), group ); @@ -2014,8 +2015,6 @@ AppearancePageHeadersTab::AppearancePageHeadersTab( TQWidget * parent, const cha mNestedMessagesCheck = new TQCheckBox( i18n("&Threaded message list"), group ); - connect( mShowQuickSearch, TQT_SIGNAL( stateChanged( int ) ), - this, TQT_SLOT( slotEmitChanged( void ) ) ); connect( mMessageSizeCheck, TQT_SIGNAL( stateChanged( int ) ), this, TQT_SLOT( slotEmitChanged( void ) ) ); connect( mAttachmentCheck, TQT_SIGNAL( stateChanged( int ) ), @@ -2123,7 +2122,6 @@ void AppearancePage::HeadersTab::doLoadOther() { mMessageSizeCheck->setChecked( general.readBoolEntry( "showMessageSize", false ) ); mCryptoIconsCheck->setChecked( general.readBoolEntry( "showCryptoIcons", false ) ); mAttachmentCheck->setChecked( general.readBoolEntry( "showAttachmentIcon", true ) ); - mShowQuickSearch->setChecked( GlobalSettings::self()->quickSearchActive() ); // "Message Header Threading Options": int num = geometry.readNumEntry( "nestingPolicy", 3 ); @@ -2204,7 +2202,6 @@ void AppearancePage::HeadersTab::save() { general.writeEntry( "showMessageSize", mMessageSizeCheck->isChecked() ); general.writeEntry( "showCryptoIcons", mCryptoIconsCheck->isChecked() ); general.writeEntry( "showAttachmentIcon", mAttachmentCheck->isChecked() ); - GlobalSettings::self()->setQuickSearchActive( mShowQuickSearch->isChecked() ); int dateDisplayID = mDateDisplay->id( mDateDisplay->selected() ); // check bounds: @@ -2220,6 +2217,10 @@ void AppearancePage::HeadersTab::save() { // +static const BoolConfigEntry closeAfterReplyOrForward = { + "Reader", "CloseAfterReplyOrForward", I18N_NOOP("Close message window after replying or forwarding"), false +}; + static const BoolConfigEntry showColorbarMode = { "Reader", "showColorbar", I18N_NOOP("Show HTML stat&us bar"), false }; @@ -2251,6 +2252,15 @@ AppearancePageReaderTab::AppearancePageReaderTab( TQWidget * parent, { TQVBoxLayout *vlay = new TQVBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() ); + // "close message window after replying or forwarding" checkbox + populateCheckBox( mCloseAfterReplyOrForwardCheck = new TQCheckBox( this ), + closeAfterReplyOrForward ); + TQToolTip::add( mCloseAfterReplyOrForwardCheck, + i18n( "Close the standalone message window after replying or forwarding the message" ) ); + vlay->addWidget( mCloseAfterReplyOrForwardCheck ); + connect( mCloseAfterReplyOrForwardCheck, TQT_SIGNAL ( stateChanged( int ) ), + this, TQT_SLOT( slotEmitChanged() ) ); + // "show colorbar" check box: populateCheckBox( mShowColorbarCheck = new TQCheckBox( this ), showColorbarMode ); vlay->addWidget( mShowColorbarCheck ); @@ -2403,6 +2413,7 @@ void AppearancePage::ReaderTab::readCurrentOverrideCodec() void AppearancePage::ReaderTab::doLoadFromGlobalSettings() { + mCloseAfterReplyOrForwardCheck->setChecked( GlobalSettings::self()->closeAfterReplyOrForward() ); mShowEmoticonsCheck->setChecked( GlobalSettings::self()->showEmoticons() ); mShrinkQuotesCheck->setChecked( GlobalSettings::self()->shrinkQuotes() ); mShowExpandQuotesMark->setChecked( GlobalSettings::self()->showExpandQuotesMark() ); @@ -2423,6 +2434,7 @@ void AppearancePage::ReaderTab::save() { KConfigGroup reader( KMKernel::config(), "Reader" ); saveCheckBox( mShowColorbarCheck, reader, showColorbarMode ); saveCheckBox( mShowSpamStatusCheck, reader, showSpamStatusMode ); + GlobalSettings::self()->setCloseAfterReplyOrForward( mCloseAfterReplyOrForwardCheck->isChecked() ); GlobalSettings::self()->setShowEmoticons( mShowEmoticonsCheck->isChecked() ); GlobalSettings::self()->setShrinkQuotes( mShrinkQuotesCheck->isChecked() ); GlobalSettings::self()->setShowExpandQuotesMark( mShowExpandQuotesMark->isChecked() ); @@ -2439,6 +2451,7 @@ void AppearancePage::ReaderTab::save() { void AppearancePage::ReaderTab::installProfile( KConfig * /* profile */ ) { const KConfigGroup reader( KMKernel::config(), "Reader" ); + loadProfile( mCloseAfterReplyOrForwardCheck, reader, closeAfterReplyOrForward ); loadProfile( mShowColorbarCheck, reader, showColorbarMode ); loadProfile( mShowSpamStatusCheck, reader, showSpamStatusMode ); loadProfile( mShowEmoticonsCheck, reader, showEmoticons ); @@ -2605,10 +2618,29 @@ ComposerPageGeneralTab::ComposerPageGeneralTab( TQWidget * parent, const char * mSmartQuoteCheck = new TQCheckBox( GlobalSettings::self()->smartQuoteItem()->label(), this, "kcfg_SmartQuote" ); + TQToolTip::add( mSmartQuoteCheck, + i18n( "When replying, add quote signs in front of all lines of the quoted text,\n" + "even when the line was created by adding an additional linebreak while\n" + "word-wrapping the text." ) ); vlay->addWidget( mSmartQuoteCheck ); connect( mSmartQuoteCheck, TQT_SIGNAL( stateChanged(int) ), this, TQT_SLOT( slotEmitChanged( void ) ) ); + mQuoteSelectionOnlyCheck = new TQCheckBox( GlobalSettings::self()->quoteSelectionOnlyItem()->label(), + this, "kcfg_QuoteSelectionOnly" ); + TQToolTip::add( mQuoteSelectionOnlyCheck, + i18n( "When replying, only quote the selected text instead of the complete message " + "when there is text selected in the message window." ) ); + vlay->addWidget( mQuoteSelectionOnlyCheck ); + connect( mQuoteSelectionOnlyCheck, TQT_SIGNAL( stateChanged(int) ), + this, TQT_SLOT( slotEmitChanged(void) ) ); + + mStripSignatureCheck = new TQCheckBox( GlobalSettings::self()->stripSignatureItem()->label(), + this, "kcfg_StripSignature" ); + vlay->addWidget( mStripSignatureCheck ); + connect( mStripSignatureCheck, TQT_SIGNAL( stateChanged(int) ), + this, TQT_SLOT( slotEmitChanged( void ) ) ); + mAutoRequestMDNCheck = new TQCheckBox( GlobalSettings::self()->requestMDNItem()->label(), this, "kcfg_RequestMDN" ); @@ -2645,6 +2677,41 @@ ComposerPageGeneralTab::ComposerPageGeneralTab( TQWidget * parent, const char * connect( mWordWrapCheck, TQT_SIGNAL(toggled(bool)), mWrapColumnSpin, TQT_SLOT(setEnabled(bool)) ); + // a checkbox for "too many recipient warning" and a spinbox for the recipient threshold + hlay = new TQHBoxLayout( vlay ); // inherits spacing + mRecipientCheck = new TQCheckBox( + GlobalSettings::self()->tooManyRecipientsItem()->label(), + this, "kcfg_TooManyRecipients" ); + hlay->addWidget( mRecipientCheck ); + connect( mRecipientCheck, TQT_SIGNAL( stateChanged(int) ), + this, TQT_SLOT( slotEmitChanged( void ) ) ); + + TQString recipientCheckWhatsthis = + i18n( GlobalSettings::self()->tooManyRecipientsItem()->whatsThis().utf8() ); + TQWhatsThis::add( mRecipientCheck, recipientCheckWhatsthis ); + TQToolTip::add( mRecipientCheck, + i18n( "Warn if too many recipients are specified" ) ); + + mRecipientSpin = new KIntSpinBox( 1/*min*/, 100/*max*/, 1/*step*/, + 5/*init*/, 10 /*base*/, this, "kcfg_RecipientThreshold" ); + mRecipientSpin->setEnabled( false ); + connect( mRecipientSpin, TQT_SIGNAL( valueChanged(int) ), + this, TQT_SLOT( slotEmitChanged( void ) ) ); + + TQString recipientWhatsthis = + i18n( GlobalSettings::self()->recipientThresholdItem()->whatsThis().utf8() ); + TQWhatsThis::add( mRecipientSpin, recipientWhatsthis ); + TQToolTip::add( mRecipientSpin, + i18n( "Warn if more than this many recipients are specified" ) ); + + + hlay->addWidget( mRecipientSpin ); + hlay->addStretch( 1 ); + // only enable the spinbox if the checkbox is checked: + connect( mRecipientCheck, TQT_SIGNAL(toggled(bool)), + mRecipientSpin, TQT_SLOT(setEnabled(bool)) ); + + hlay = new TQHBoxLayout( vlay ); // inherits spacing mAutoSave = new KIntSpinBox( 0, 60, 1, 1, 10, this, "kcfg_AutosaveInterval" ); label = new TQLabel( mAutoSave, @@ -2657,6 +2724,20 @@ ComposerPageGeneralTab::ComposerPageGeneralTab( TQWidget * parent, const char * connect( mAutoSave, TQT_SIGNAL( valueChanged(int) ), this, TQT_SLOT( slotEmitChanged( void ) ) ); + hlay = new TQHBoxLayout( vlay ); // inherits spacing + mForwardTypeCombo = new KComboBox( false, this ); + label = new TQLabel( mForwardTypeCombo, + i18n( "Default Forwarding Type:" ), + this ); + mForwardTypeCombo->insertStringList( TQStringList() + << i18n( "Inline" ) + << i18n( "As Attachment" ) ); + hlay->addWidget( label ); + hlay->addWidget( mForwardTypeCombo ); + hlay->addStretch( 1 ); + connect( mForwardTypeCombo, TQT_SIGNAL(activated(int)), + this, TQT_SLOT( slotEmitChanged( void ) ) ); + hlay = new TQHBoxLayout( vlay ); // inherits spacing TQPushButton *completionOrderBtn = new TQPushButton( i18n( "Configure Completion Order" ), this ); connect( completionOrderBtn, TQT_SIGNAL( clicked() ), @@ -2721,11 +2802,19 @@ void ComposerPage::GeneralTab::doLoadFromGlobalSettings() { GlobalSettings::self()->autoTextSignature()=="auto" ); mTopQuoteCheck->setChecked( GlobalSettings::self()->prependSignature() ); mSmartQuoteCheck->setChecked( GlobalSettings::self()->smartQuote() ); + mQuoteSelectionOnlyCheck->setChecked( GlobalSettings::self()->quoteSelectionOnly() ); + mStripSignatureCheck->setChecked( GlobalSettings::self()->stripSignature() ); mAutoRequestMDNCheck->setChecked( GlobalSettings::self()->requestMDN() ); mWordWrapCheck->setChecked( GlobalSettings::self()->wordWrap() ); mWrapColumnSpin->setValue( GlobalSettings::self()->lineWrapWidth() ); + mRecipientCheck->setChecked( GlobalSettings::self()->tooManyRecipients() ); + mRecipientSpin->setValue( GlobalSettings::self()->recipientThreshold() ); mAutoSave->setValue( GlobalSettings::self()->autosaveInterval() ); + if ( GlobalSettings::self()->forwardingInlineByDefault() ) + mForwardTypeCombo->setCurrentItem( 0 ); + else + mForwardTypeCombo->setCurrentItem( 1 ); // editor group: mExternalEditorCheck->setChecked( GlobalSettings::self()->useExternalEditor() ); @@ -2744,12 +2833,20 @@ void ComposerPage::GeneralTab::installProfile( KConfig * profile ) { mTopQuoteCheck->setChecked( composer.readBoolEntry( "prepend-signature" ) ); if ( composer.hasKey( "smart-quote" ) ) mSmartQuoteCheck->setChecked( composer.readBoolEntry( "smart-quote" ) ); + if ( composer.hasKey( "StripSignature" ) ) + mStripSignatureCheck->setChecked( composer.readBoolEntry( "StripSignature" ) ); + if ( composer.hasKey( "QuoteSelectionOnly" ) ) + mQuoteSelectionOnlyCheck->setChecked( composer.readBoolEntry( "QuoteSelectionOnly" ) ); if ( composer.hasKey( "request-mdn" ) ) mAutoRequestMDNCheck->setChecked( composer.readBoolEntry( "request-mdn" ) ); if ( composer.hasKey( "word-wrap" ) ) mWordWrapCheck->setChecked( composer.readBoolEntry( "word-wrap" ) ); if ( composer.hasKey( "break-at" ) ) mWrapColumnSpin->setValue( composer.readNumEntry( "break-at" ) ); + if ( composer.hasKey( "too-many-recipients" ) ) + mRecipientCheck->setChecked( composer.readBoolEntry( "too-many-recipients" ) ); + if ( composer.hasKey( "recipient-threshold" ) ) + mRecipientSpin->setValue( composer.readNumEntry( "recipient-threshold" ) ); if ( composer.hasKey( "autosave" ) ) mAutoSave->setValue( composer.readNumEntry( "autosave" ) ); @@ -2765,11 +2862,16 @@ void ComposerPage::GeneralTab::save() { mAutoAppSignFileCheck->isChecked() ? "auto" : "manual" ); GlobalSettings::self()->setPrependSignature( mTopQuoteCheck->isChecked()); GlobalSettings::self()->setSmartQuote( mSmartQuoteCheck->isChecked() ); + GlobalSettings::self()->setQuoteSelectionOnly( mQuoteSelectionOnlyCheck->isChecked() ); + GlobalSettings::self()->setStripSignature( mStripSignatureCheck->isChecked() ); GlobalSettings::self()->setRequestMDN( mAutoRequestMDNCheck->isChecked() ); GlobalSettings::self()->setWordWrap( mWordWrapCheck->isChecked() ); GlobalSettings::self()->setLineWrapWidth( mWrapColumnSpin->value() ); + GlobalSettings::self()->setTooManyRecipients( mRecipientCheck->isChecked() ); + GlobalSettings::self()->setRecipientThreshold( mRecipientSpin->value() ); GlobalSettings::self()->setAutosaveInterval( mAutoSave->value() ); + GlobalSettings::self()->setForwardingInlineByDefault( mForwardTypeCombo->currentItem() == 0 ); // editor group: GlobalSettings::self()->setUseExternalEditor( mExternalEditorCheck->isChecked() ); @@ -4491,7 +4593,11 @@ MiscPageFolderTab::MiscPageFolderTab( TQWidget * parent, const char * name ) << i18n("continuation of \"When entering a folder:\"", "Jump to First Unread or New Message") << i18n("continuation of \"When entering a folder:\"", - "Jump to Last Selected Message")); + "Jump to Last Selected Message") + << i18n("continuation of \"When entering a folder:\"", + "Jump to Newest Message") + << i18n("continuation of \"When entering a folder:\"", + "Jump to Oldest Message") ); hlay->addWidget( label ); hlay->addWidget( mActionEnterFolder, 1 ); connect( mActionEnterFolder, TQT_SIGNAL( activated( int ) ), @@ -4720,7 +4826,7 @@ MiscPageGroupwareTab::MiscPageGroupwareTab( TQWidget* parent, const char* name ) mStorageFormatCombo = new TQComboBox( false, mBox ); storageFormatLA->setBuddy( mStorageFormatCombo ); TQStringList formatLst; - formatLst << i18n("Standard (Ical / Vcard)") << i18n("Kolab (XML)"); + formatLst << i18n("Deprecated Kolab1 (iCal/vCard)") << i18n("Kolab2 (XML)"); mStorageFormatCombo->insertStringList( formatLst ); grid->addWidget( mStorageFormatCombo, 0, 1 ); TQToolTip::add( mStorageFormatCombo, toolTip ); @@ -4806,8 +4912,8 @@ MiscPageGroupwareTab::MiscPageGroupwareTab( TQWidget* parent, const char* name ) i18n( "Synchronize groupware changes in disconnected IMAP folders immediately when being online." ) ); connect( mSyncImmediately, TQT_SIGNAL(toggled(bool)), TQT_SLOT(slotEmitChanged()) ); grid->addMultiCellWidget( mSyncImmediately, 4, 4, 0, 1 ); - - mDeleteInvitations = new TQCheckBox( + + mDeleteInvitations = new TQCheckBox( i18n( GlobalSettings::self()->deleteInvitationEmailsAfterSendingReplyItem()->label().utf8() ), mBox ); TQWhatsThis::add( mDeleteInvitations, i18n( GlobalSettings::self() ->deleteInvitationEmailsAfterSendingReplyItem()->whatsThis().utf8() ) ); @@ -4844,12 +4950,19 @@ MiscPageGroupwareTab::MiscPageGroupwareTab( TQWidget* parent, const char* name ) this, TQT_SLOT( slotEmitChanged( void ) ) ); mExchangeCompatibleInvitations = new TQCheckBox( i18n( "Exchange compatible invitation naming" ), gBox ); - TQToolTip::add( mExchangeCompatibleInvitations, i18n( "Microsoft Outlook, when used in combination with a Microsoft Exchange server, has a problem understanding standards-compliant groupware e-mail. Turn this option on to send groupware invitations in a way that Microsoft Exchange understands." ) ); + TQToolTip::add( mExchangeCompatibleInvitations, i18n( "Outlook(tm), when used in combination with a Microsoft Exchange server,\nhas a problem understanding standards-compliant groupware e-mail.\nTurn this option on to send groupware invitations and replies in an Exchange compatible way." ) ); TQWhatsThis::add( mExchangeCompatibleInvitations, i18n( GlobalSettings::self()-> exchangeCompatibleInvitationsItem()->whatsThis().utf8() ) ); connect( mExchangeCompatibleInvitations, TQT_SIGNAL( stateChanged( int ) ), this, TQT_SLOT( slotEmitChanged( void ) ) ); + mOutlookCompatibleInvitationComments = new TQCheckBox( i18n( "Outlook compatible invitation reply comments" ), gBox ); + TQToolTip::add( mOutlookCompatibleInvitationComments, i18n( "Send invitation reply comments in a way that Microsoft Outlook(tm) understands." ) ); + TQWhatsThis::add( mOutlookCompatibleInvitationComments, i18n( GlobalSettings::self()-> + outlookCompatibleInvitationReplyCommentsItem()->whatsThis().utf8() ) ); + connect( mOutlookCompatibleInvitationComments, TQT_SIGNAL( stateChanged( int ) ), + this, TQT_SLOT( slotEmitChanged( void ) ) ); + mAutomaticSending = new TQCheckBox( i18n( "Automatic invitation sending" ), gBox ); TQToolTip::add( mAutomaticSending, i18n( "When this is on, the user will not see the mail composer window. Invitation mails are sent automatically" ) ); TQWhatsThis::add( mAutomaticSending, i18n( GlobalSettings::self()-> @@ -4897,6 +5010,8 @@ void MiscPage::GroupwareTab::doLoadFromGlobalSettings() { mExchangeCompatibleInvitations->setChecked( GlobalSettings::self()->exchangeCompatibleInvitations() ); + mOutlookCompatibleInvitationComments->setChecked( GlobalSettings::self()->outlookCompatibleInvitationReplyComments() ); + mAutomaticSending->setChecked( GlobalSettings::self()->automaticSending() ); mAutomaticSending->setEnabled( !mLegacyBodyInvites->isChecked() ); @@ -4959,6 +5074,7 @@ void MiscPage::GroupwareTab::save() { groupware.writeEntry( "LegacyMangleFromToHeaders", mLegacyMangleFromTo->isChecked() ); groupware.writeEntry( "LegacyBodyInvites", mLegacyBodyInvites->isChecked() ); groupware.writeEntry( "ExchangeCompatibleInvitations", mExchangeCompatibleInvitations->isChecked() ); + groupware.writeEntry( "OutlookCompatibleInvitationReplyComments", mOutlookCompatibleInvitationComments->isChecked() ); groupware.writeEntry( "AutomaticSending", mAutomaticSending->isChecked() ); if ( mEnableGwCB ) { @@ -4967,6 +5083,7 @@ void MiscPage::GroupwareTab::save() { GlobalSettings::self()->setLegacyMangleFromToHeaders( mLegacyMangleFromTo->isChecked() ); GlobalSettings::self()->setLegacyBodyInvites( mLegacyBodyInvites->isChecked() ); GlobalSettings::self()->setExchangeCompatibleInvitations( mExchangeCompatibleInvitations->isChecked() ); + GlobalSettings::self()->setOutlookCompatibleInvitationReplyComments( mOutlookCompatibleInvitationComments->isChecked() ); GlobalSettings::self()->setAutomaticSending( mAutomaticSending->isChecked() ); int format = mStorageFormatCombo->currentItem(); diff --git a/kmail/configuredialog.h b/kmail/configuredialog.h index f9c32731c..062bbf783 100644 --- a/kmail/configuredialog.h +++ b/kmail/configuredialog.h @@ -48,6 +48,7 @@ signals: included in the file. */ void installProfile( KConfig *profile ); + void configChanged(); protected: void hideEvent( TQHideEvent *i ); protected slots: diff --git a/kmail/configuredialog_p.h b/kmail/configuredialog_p.h index 54db5c8e7..6a1f538e6 100644 --- a/kmail/configuredialog_p.h +++ b/kmail/configuredialog_p.h @@ -58,6 +58,7 @@ class KFontRequester; class KIconButton; class KKeyButton; class TQSpinBox; +class KComboBox; namespace Kpgp { class Config; @@ -485,7 +486,6 @@ private: // data TQButtonGroup *mMIMETreeModeGroup; TQButtonGroup *mReaderWindowModeGroup; TQCheckBox *mFavoriteFolderViewCB; - TQCheckBox *mFolderQuickSearchCB; }; class AppearancePageHeadersTab : public ConfigModuleTab { @@ -505,7 +505,6 @@ private: // methods void setDateDisplay( int id, const TQString & format ); private: // data - TQCheckBox *mShowQuickSearch; TQCheckBox *mMessageSizeCheck; TQCheckBox *mAttachmentCheck; TQCheckBox *mNestedMessagesCheck; @@ -533,6 +532,7 @@ private: void readCurrentOverrideCodec(); private: // data + TQCheckBox *mCloseAfterReplyOrForwardCheck; TQCheckBox *mShowColorbarCheck; TQCheckBox *mShowSpamStatusCheck; TQCheckBox *mShowEmoticonsCheck; @@ -611,13 +611,18 @@ private: TQCheckBox *mAutoAppSignFileCheck; TQCheckBox *mTopQuoteCheck; TQCheckBox *mSmartQuoteCheck; + TQCheckBox *mStripSignatureCheck; + TQCheckBox *mQuoteSelectionOnlyCheck; TQCheckBox *mAutoRequestMDNCheck; - QCheckBox *mShowRecentAddressesInComposer; + TQCheckBox *mShowRecentAddressesInComposer; TQCheckBox *mWordWrapCheck; KIntSpinBox *mWrapColumnSpin; + TQCheckBox *mRecipientCheck; + KIntSpinBox *mRecipientSpin; KIntSpinBox *mAutoSave; TQCheckBox *mExternalEditorCheck; KURLRequester *mEditorRequester; + KComboBox *mForwardTypeCombo; }; class ComposerPagePhrasesTab : public ConfigModuleTab { @@ -1020,6 +1025,7 @@ private: TQCheckBox* mLegacyMangleFromTo; TQCheckBox* mLegacyBodyInvites; TQCheckBox* mExchangeCompatibleInvitations; + TQCheckBox* mOutlookCompatibleInvitationComments; TQCheckBox* mAutomaticSending; }; diff --git a/kmail/customtemplates.cpp b/kmail/customtemplates.cpp index 93b24e8f7..6a0a7f4cf 100644 --- a/kmail/customtemplates.cpp +++ b/kmail/customtemplates.cpp @@ -20,15 +20,19 @@ #include -#include -#include #include #include #include +#include #include #include -#include +#include +#include #include + +#include +#include +#include #include #include #include @@ -44,11 +48,14 @@ #include "globalsettings.h" #include "kmkernel.h" #include "kmmainwidget.h" +#include "kmfawidgets.h" #include "customtemplates.h" CustomTemplates::CustomTemplates( TQWidget *parent, const char *name ) - :CustomTemplatesBase( parent, name ), mCurrentItem( 0 ) + :CustomTemplatesBase( parent, name ), + mCurrentItem( 0 ), + mBlockChangeSignal( false ) { TQFont f = KGlobalSettings::fixedFont(); mEdit->setFont( f ); @@ -61,8 +68,14 @@ CustomTemplates::CustomTemplates( TQWidget *parent, const char *name ) mEditFrame->setEnabled( false ); + connect( mName, TQT_SIGNAL( textChanged ( const TQString &) ), + this, TQT_SLOT( slotNameChanged( const TQString & ) ) ); connect( mEdit, TQT_SIGNAL( textChanged() ), this, TQT_SLOT( slotTextChanged( void ) ) ); + connect( mToEdit, TQT_SIGNAL( textChanged(const TQString&) ), + this, TQT_SLOT( slotTextChanged( void ) ) ); + connect( mCCEdit, TQT_SIGNAL( textChanged(const TQString&) ), + this, TQT_SLOT( slotTextChanged( void ) ) ); connect( mInsertCommand, TQT_SIGNAL( insertCommand(TQString, int) ), this, TQT_SLOT( slotInsertCommand(TQString, int) ) ); @@ -105,6 +118,29 @@ CustomTemplates::CustomTemplates( TQWidget *parent, const char *name ) "You cannot bind keyboard shortcut to Universal templates.

" "
" ); mHelp->setText( i18n( "How does this work?" ).arg( help ) ); + + const TQString toToolTip = i18n( "Additional recipients of the message when forwarding" ); + const TQString ccToolTip = i18n( "Additional recipients who get a copy of the message when forwarding" ); + const TQString toWhatsThis = i18n( "When using this template for forwarding, the default recipients are those you enter here. This is a comma-separated list of mail addresses." ); + const TQString ccWhatsThis = i18n( "When using this template for forwarding, the recipients you enter here will by default get a copy of this message. This is a comma-separated list of mail addresses." ); + + // We only want to set the tooltip/whatsthis to the lineedit, not the complete widget, + // so we use the name here to find the lineedit. This is similar to what KMFilterActionForward + // does. + KLineEdit *ccLineEdit = dynamic_cast( mCCEdit->child( "addressEdit" ) ); + KLineEdit *toLineEdit = dynamic_cast( mToEdit->child( "addressEdit" ) ); + Q_ASSERT( ccLineEdit && toLineEdit ); + + TQToolTip::add( mCCLabel, ccToolTip ); + TQToolTip::add( ccLineEdit, ccToolTip ); + TQToolTip::add( mToLabel, toToolTip ); + TQToolTip::add( toLineEdit, toToolTip ); + TQWhatsThis::add( mCCLabel, ccWhatsThis ); + TQWhatsThis::add( ccLineEdit, ccWhatsThis ); + TQWhatsThis::add( mToLabel, toWhatsThis ); + TQWhatsThis::add( toLineEdit, toWhatsThis ); + + slotNameChanged( mName->text() ); } CustomTemplates::~CustomTemplates() @@ -118,6 +154,19 @@ CustomTemplates::~CustomTemplates() } } +void CustomTemplates::setRecipientsEditsEnabled( bool enabled ) +{ + mToEdit->setHidden( !enabled ); + mCCEdit->setHidden( !enabled ); + mToLabel->setHidden( !enabled ); + mCCLabel->setHidden( !enabled ); +} + +void CustomTemplates::slotNameChanged( const TQString& text ) +{ + mAdd->setEnabled( !text.isEmpty() ); +} + TQString CustomTemplates::indexToType( int index ) { TQString typeStr; @@ -141,7 +190,8 @@ TQString CustomTemplates::indexToType( int index ) void CustomTemplates::slotTextChanged() { - emit changed(); + if ( !mBlockChangeSignal ) + emit changed(); } void CustomTemplates::load() @@ -155,7 +205,7 @@ void CustomTemplates::load() CustomTemplateItem *vitem = new CustomTemplateItem( *it, t.content(), shortcut, - static_cast( t.type() ) ); + static_cast( t.type() ), t.to(), t.cC() ); mItemList.insert( *it, vitem ); TQListViewItem *item = new TQListViewItem( mList, typeStr, *it, t.content() ); switch ( t.type() ) { @@ -178,11 +228,22 @@ void CustomTemplates::load() void CustomTemplates::save() { + // Before saving the new templates, delete the old ones. That needs to be done before + // saving, since otherwise a new template with the new name wouldn't get saved. + for ( TQStringList::const_iterator it = mItemsToDelete.constBegin(); + it != mItemsToDelete.constEnd(); ++it ) { + CTemplates t( (*it) ); + const TQString configGroup = t.currentGroup(); + kmkernel->config()->deleteGroup( configGroup ); + } + if ( mCurrentItem ) { CustomTemplateItem *vitem = mItemList[ mCurrentItem->text( 1 ) ]; if ( vitem ) { vitem->mContent = mEdit->text(); vitem->mShortcut = mKeyButton->shortcut(); + vitem->mTo = mToEdit->text(); + vitem->mCC = mCCEdit->text(); } } TQStringList list; @@ -191,8 +252,7 @@ void CustomTemplates::save() list.append( (*lit)->text( 1 ) ); ++lit; } - TQDictIterator it( mItemList ); - for ( ; it.current() ; ++it ) { + for ( TQDictIterator it( mItemList ); it.current() ; ++it ) { // list.append( (*it)->mName ); CTemplates t( (*it)->mName ); TQString &content = (*it)->mContent; @@ -202,6 +262,8 @@ void CustomTemplates::save() t.setContent( content ); t.setShortcut( (*it)->mShortcut.toString() ); t.setType( (*it)->mType ); + t.setTo( (*it)->mTo ); + t.setCC( (*it)->mCC ); t.writeConfig(); } GlobalSettings::self()->setCustomTemplates( list ); @@ -229,13 +291,15 @@ void CustomTemplates::slotAddClicked() if ( !str.isEmpty() ) { CustomTemplateItem *vitem = mItemList[ str ]; if ( !vitem ) { - vitem = new CustomTemplateItem( str, "", KShortcut::null(), TUniversal ); + vitem = new CustomTemplateItem( str, "", KShortcut::null(), TUniversal, + TQString(), TQString() ); mItemList.insert( str, vitem ); TQListViewItem *item = new TQListViewItem( mList, indexToType( TUniversal ), str, "" ); mList->setSelected( item, true ); mKeyButton->setEnabled( false ); - emit changed(); + if ( !mBlockChangeSignal ) + emit changed(); } } } @@ -243,13 +307,14 @@ void CustomTemplates::slotAddClicked() void CustomTemplates::slotRemoveClicked() { if ( mCurrentItem ) { - CustomTemplateItem *vitem = mItemList.take( mCurrentItem->text( 1 ) ); - if ( vitem ) { - delete vitem; - } + const TQString templateName = mCurrentItem->text( 1 ); + mItemsToDelete.append( templateName ); + CustomTemplateItem *vitem = mItemList.take( templateName ); + delete vitem; delete mCurrentItem; mCurrentItem = 0; - emit changed(); + if ( !mBlockChangeSignal ) + emit changed(); } } @@ -268,16 +333,14 @@ void CustomTemplates::slotListSelectionChanged() mCurrentItem = item; CustomTemplateItem *vitem = mItemList[ mCurrentItem->text( 1 ) ]; if ( vitem ) { - // avoid emit changed() - disconnect( mEdit, TQT_SIGNAL( textChanged() ), - this, TQT_SLOT( slotTextChanged( void ) ) ); + mBlockChangeSignal = true; mEdit->setText( vitem->mContent ); mKeyButton->setShortcut( vitem->mShortcut, false ); mType->setCurrentItem( vitem->mType ); - - connect( mEdit, TQT_SIGNAL( textChanged() ), - this, TQT_SLOT( slotTextChanged( void ) ) ); + mToEdit->setText( vitem->mTo ); + mCCEdit->setText( vitem->mCC ); + mBlockChangeSignal = false; if ( vitem->mType == TUniversal ) { @@ -285,11 +348,15 @@ void CustomTemplates::slotListSelectionChanged() } else { mKeyButton->setEnabled( true ); } + setRecipientsEditsEnabled( vitem->mType == TForward || + vitem->mType == TUniversal ); } } else { mEditFrame->setEnabled( false ); mCurrentItem = 0; mEdit->clear(); + mToEdit->clear(); + mCCEdit->clear(); mKeyButton->setShortcut( KShortcut::null(), false ); mType->setCurrentItem( 0 ); } @@ -324,8 +391,14 @@ void CustomTemplates::slotTypeActivated( int index ) } else { mKeyButton->setEnabled( true ); } - emit changed(); + + setRecipientsEditsEnabled( vitem->mType == TForward || + vitem->mType == TUniversal ); + if ( !mBlockChangeSignal ) + emit changed(); } + else + setRecipientsEditsEnabled( false ); } void CustomTemplates::slotShortcutCaptured( const KShortcut &shortcut ) @@ -369,7 +442,8 @@ void CustomTemplates::slotShortcutCaptured( const KShortcut &shortcut ) } if ( assign ) { mKeyButton->setShortcut( sc, false ); - emit changed(); + if ( !mBlockChangeSignal ) + emit changed(); } } diff --git a/kmail/customtemplates.h b/kmail/customtemplates.h index dc454bf6c..a8c240619 100644 --- a/kmail/customtemplates.h +++ b/kmail/customtemplates.h @@ -26,6 +26,8 @@ #include "customtemplates_base.h" #include "templatesinsertcommand.h" +#include + struct CustomTemplateItem; typedef TQDict CustomTemplateItemList; class KShortcut; @@ -59,20 +61,29 @@ class CustomTemplates : public CustomTemplatesBase void slotListSelectionChanged(); void slotTypeActivated( int index ); void slotShortcutCaptured( const KShortcut &shortcut ); - + void slotNameChanged( const TQString& ); signals: void changed(); protected: + void setRecipientsEditsEnabled( bool enabled ); + TQListViewItem *mCurrentItem; CustomTemplateItemList mItemList; + /// These templates will be deleted when we're saving. + TQStringList mItemsToDelete; + TQPixmap mReplyPix; TQPixmap mReplyAllPix; TQPixmap mForwardPix; + /// Whether or not to emit the changed() signal. This is useful to disable when loading + /// templates, which changes the UI without user action + bool mBlockChangeSignal; + }; struct CustomTemplateItem @@ -81,12 +92,15 @@ struct CustomTemplateItem CustomTemplateItem( const TQString &name, const TQString &content, KShortcut &shortcut, - CustomTemplates::Type type ) : - mName( name ), mContent( content ), mShortcut(shortcut), mType( type ) {} + CustomTemplates::Type type, + TQString to, TQString cc ) : + mName( name ), mContent( content ), mShortcut(shortcut), mType( type ), + mTo( to ), mCC( cc ) {} TQString mName, mContent; KShortcut mShortcut; CustomTemplates::Type mType; + TQString mTo, mCC; }; #endif // CUSTOMTEMPLATES_H diff --git a/kmail/customtemplates_base.ui b/kmail/customtemplates_base.ui index 5a4cb9d44..6a578030e 100644 --- a/kmail/customtemplates_base.ui +++ b/kmail/customtemplates_base.ui @@ -1,267 +1,312 @@ CustomTemplatesBase - - Form1 - 0 0 - 600 - 480 + 589 + 463 - + unnamed - + - splitter2 - - - Horizontal + layout9 - + - layout9 + unnamed - + + 0 + + - unnamed - - - 0 + layout8 - + - layout8 + unnamed - + + 0 + + - unnamed + mName - - 0 + + + 3 + 0 + 0 + 0 + - - - mName - - - - 3 - 0 - 0 - 0 - - - - - 100 - 0 - - - - - - mAdd - - - - - - - - mRemove - - - - - - - - - - - Type - - - true + + + 100 + 0 + - - true + + + + mAdd - - - Name + - - true + + + + mRemove - - true + + - - - mList + + + + + + + Type + + + true - - - 5 - 7 - 0 - 0 - + + true + + + + + Name + + + true - + true - - - - + + + mList + + + + 7 + 7 + 3 + 1 + + + + true + + + + + mHelp + + + How does this work? + + + + + + + mEditFrame + + + + 7 + 5 + 12 + 0 + + + + NoFrame + + + Raised + + - mEditFrame - - - - 7 - 5 - 12 - 0 - + unnamed - - NoFrame + + 0 - - Raised - - + - unnamed - - - 0 + layout8 - - - mEdit - - - - 7 - 7 - 3 - 1 - - - - PlainText - - - NoWrap - - - + - layout4 + unnamed - + - unnamed + layout6 - - - - Universal + + + unnamed + + + + mToLabel - - - Reply + To: - - - - Reply to All + + + + mCCLabel - - - Forward + CC: - - - mType - - - + + + + + + layout7 + + - textLabel1_2 + unnamed + + + mToEdit + + + + + mCCEdit + + + + + + + + + mEdit + + + + 7 + 7 + 3 + 1 + + + + PlainText + + + NoWrap + + + + + layout4 + + + + unnamed + + + - Shortc&ut: - - - AlignVCenter|AlignRight - - - mKeyButton - - - - - textLabel1 - - - - 5 - 5 - 1 - 0 - + Universal + + - &Template type: - - - AlignVCenter|AlignRight - - - mType - - - - - mInsertCommand - - - - - mKeyButton + Reply + + - None - - - - - mHelp + Reply to All + + - How does this work? + Forward - - - - - + + + mType + + + + + textLabel1_2 + + + Shortc&ut: + + + AlignVCenter|AlignRight + + + mKeyButton + + + + + textLabel1 + + + + 5 + 5 + 1 + 0 + + + + &Template type: + + + AlignVCenter|AlignRight + + + mType + + + + + mInsertCommand + + + + + mKeyButton + + + None + + + + + - + @@ -281,10 +326,29 @@ image0 insertCommand(int) + + KMFilterActionWithAddressWidget +
kmfawidgets.h
+ + 50 + 10 + + 1 + + 7 + 5 + 0 + 0 + + image1 +
- 89504e470d0a1a0a0000000d49484452000000100000001008060000001ff3ff61000002ee49444154388d7d93cd6b5c6514c67fe7bdef9d8f24d349c66412a203b1b4282d21140918824816418a0b454d75e1a28ab8c8dfe1c25d70e34e0812ea4644d13610c43a1121b5d84cab6943486227499d869ae6633e32f7de79ef71d58644f0593dabdf39cf7938a2aa4c4f4f9b300c0b954ae56ca55249876128aa2a9c90738eadadada0542afd954ea7cb636363a19d9f9f4755cff6f4a53e4d67ed78f7b37ebb8b05e3810818017d0a68f1dcc36463a7ea8a4b8bdb9fcccdcdddf6eaf5bae4f3f9f7bb7a773faee94fed81b983a6d6d9ab6eb2f5f73affec6de1a536c9e4d7497696c9f63cf237d66b85b5e570cf3977c7aeaeae9a42a130d03d506bdbaddda3d1805faec6dc5a68e2a208e30124187a39c9ebefa6c8f544f84949a9ea69e75cd60641c0e1e1a1b8b849142ac51f226ece1f32fc4a86f31732f86db076178ad70e20765cfae8142207009eaafac639270022c2fd15a1b470c8f81b59defed0637024c44596c269cb3b1fe4585c68f2c76f0e23064000639e5c5804caab2d8c24187ed5c3265adc5bf4b9f2f91e377eae73fe254b6fbfc7ea7283588f9a314756089b31ed5925dd61b87b2bc5d75fd4a91d843c7fa68d5caf23fb4c8246fdc9f01300e3412e9f60f7313c285b6edf0c696bf338f3620773df3de2ea15d8de0ce92f2448f8472bd8a700515e18146e5c3714afd5197f3343ae3b463ce1cbcf94efbf7a48b6cb303462595c3801505514280c182ebed5ce37333bec3f8e387721859f80da7e8c114714097b3be65804eb799e562a95e641358e4d47cb8cbc66c9f5f6f1eb8f55967eafa140bedfe7e27b7dac2c4588a7ec6cb7622004629b4c2629954a7fda6c6b3f5f08baac1f61bd2683c3867a358931865339c8e69a9c1b323cb87fc0fa72d81091351189646262426667677b620e2fabea25942e504144fef34d80735a752daeab9a6f7ddf5f9162b1c8e4e4a45f2e974f0541d0a3aa19c03f16f4b862a061adddc96432bba2aaccccccc8d4d494b7b1b1910c8220a1aae6ff00d6da56676767303a3a1afd0b29d2596f22d0b7b20000000049454e44ae426082 + 89504e470d0a1a0a0000000d49484452000000100000001008060000001ff3ff61000002b449444154388d8d93cd6b546714c67fef7bdf3b73274e9c38662621ed8015c1e2108248c0108a642145ba68699bb6db16ff9fe0a6bb6ea4d84d29143f02a15463e9255634a3259ac8cc6092b1d76063e27c6466ee9df79e2e4293545df481036771f8719e737894effbacacac481886044140100484618888f0baacb5d46a354aa512a9548aa9a9290c8088901bf648650c83efb8d858a11d500ab402d903f478f77992cd8665697183b9b9399c7abd2ef97c9e23435b34e557bafa21e255d96eac53fbabcadfdb351c6f9dfe7c95e4c02a99dc0bd6aa4d2acb21d65a4cb95ca650283078acc956f3313b3bf0dbf598fb0b1d6c14a11d80046367937cf4a5473617e1261522b20be876bbb4db6d6cdc210a85f96b11776fb719ffa09fe2e97edc3ea83c82f91b75882d5f5c3c8c5275feb5aeadb50028a578fa44515a6873fee30c9f7de3303a11622343e1b8e1f3afb32c2e74f8f30f8b567aefb07b9d52b05aeea15582f1730e26d1e3f1a2cb956fb7b973ab45f18c6168c4a1bcbc437ce041fb28146127e6504648a5358fee7bfcf85d8b663de4bd137d64872c99a309765abbb36f00b403d97c82ad97f06cd5f0e06e485f9fc389f7d3ccfdfc82eb5760633d64a49020e1eeaf60f6004a3839aab87353337fa3c5f94ffac90ec6284771f99270f587e7648e68c6260c8b0baf014404010ac734173e3dc44fdf6ff2ea65c4a9d31e6e029aaf62b4b24491627b53ffc782711c872008a8376274bac7c48786ecd030bfffd260e95e1301f2232e17be1ae6c952847284cd8dde3e20994c522a9530991ef94217e34618a7c3e8b8a6d548a2b5e6701632d90ea7c634cf9ed6a92e8728a5766b7a7a5a6667678969ef06480004943ab0e8c14009b607221ad775c1f77d8ac5a2a4d369715d578c31ffab3ccf935c2e27caf77d2a958accccccb0b6b646b7db7d6b940fca18c3c0c000939393fc038f463fba9bedfea40000000049454e44ae426082 + + + 89504e470d0a1a0a0000000d4948445200000016000000160806000000c4b46c3b000003b149444154388dad945f4c5b551cc73fe7dc4b7b4bcba0762d45c43114323599ee6192609c51d883892ce083f1718b3ebb185f8dc91e972cf39d2d2a2f1af664b6f1e0fe3863a0718969700eb0c52142da0242a1bd6d696f7bcff101585203ceb8fd9ece39f99dcff9fe7edf939f88c562ec465f5f9fe609442c161362173c3e3eae7b7a7ac8e7f36432196cdbfe4f907c3e4f2291201e8fe338cec3737357e9e8e828aded1e229d650e1f2d51754b082110124c13a4dc5ea341eb9dc284c0558a853f3ce8cb0677ef500fde7d39d2596679e326597b8e9abb85d7a770ab16ab6983ec5a05b487a70e36f0f4e10afe408d6a558310980108478dba4a1e8233990c5d474b64ed39aa3a8fe5f3317fbf81dbd70bccfeb205947632fd74f6589c1c6ea2f70d03a58ba0c1f2c9bdc1b66de3b8256a6e11cbe7e3ee1d181b590124fe2693aeee08d223c82c3a2c24b7b874bec8f26288774f7bd054504aef0dde6e99c0eb83f9fb266323cb80a27fb0958141836044605a2ee5523393371cc646fee2da37195aa35d0c0c5b4859ac03d7e91712dcaac5adab3650a3ff9d08ef7dd8404bb48869e5d958b5b87dadc4c9a1464e9f0d0326df7ebd86bd2e310cb1bf62d384d59441f2d70a070e1c60e09489929b988681bdd9cc97170bcc4c65595f71f8e0e3301337fc24a7732467831875a47f289652b0be5e4151e6d07316c1b0c0340d8ab92023e76d66a6b2840e36d2fb7a13fee632475e6edc367ea98a90fb98b7dd6310ca0328a44761582e1bab41befabcc0ec940d28bc5e93b68e064cab84e1d9beaeb48934eac1f53b01c1b000fca496aa54b61a99fcde61662a4b4b4b23d1680be9d426173e4df3602a48ea411989a4fd590f52a8fd156b05ed9d350e3defe3cfdf4b4c7ce770ea7d3fb9f520afbe1620daeee5c26735d20b9b9cfb6811a754a439e4e5c5639a4caa1e5caf586bfc0197b78702005cb9b4cae4cd3267ce8638fe964bd72b393e39d74928d242617303a756a37f284447770dcdbffc6384a05a85de1306e9a52057c7527c7131c3c42d3f475eb2303c82d4fc3276d6811db37efeb148723082d9b08f79f97c1e5729109a9a28307cc622d2d6cdf52b2b24efe548dedb00142009862cfa879ee1a71f6cec928353511472fbf4389148b0b0e0c108081412458dfe21c9f11351e67e7358595468246d1d1e5e38a6e9e851bc39d84ab502a669331dafec0d8ec7e3e8cb06e1a881d727d1ae40180a434a8c9db129a54126ad48a7358c2b4c5352c8c374bcccdab2bb37d8719cba79fab8211f9df218e0582c261e95f8bfc04f1a1e8bc5c4dfe0a190172af6a9690000000049454e44ae426082 @@ -296,6 +360,8 @@ klineedit.h kpushbutton.h kpushbutton.h + kmfawidgets.h + kmfawidgets.h templatesinsertcommand.h kkeybutton.h kactivelabel.h diff --git a/kmail/customtemplates_kfg.kcfg b/kmail/customtemplates_kfg.kcfg index f5689810e..5e3c88664 100644 --- a/kmail/customtemplates_kfg.kcfg +++ b/kmail/customtemplates_kfg.kcfg @@ -23,6 +23,12 @@ 0 + + + + + + diff --git a/kmail/dcopimap.desktop b/kmail/dcopimap.desktop index c1ed3737e..0e6afdeba 100644 --- a/kmail/dcopimap.desktop +++ b/kmail/dcopimap.desktop @@ -26,7 +26,6 @@ Comment[hu]=Levelezőprogram DCOP felülettel Comment[is]=Póstforrit með DCOP viðmóti Comment[it]=Programma di posta con un'interfaccia DCOP Comment[ja]=DCOP インターフェース付のメールプログラム -Comment[ka]=საფოსტო პროგრამა DCOP ინტერფეისით Comment[kk]=DCOP интерфейсті пошта бағдарламасы Comment[km]=កម្មវិធី​អ៊ីមែល​ដែល​មាន​ចំណុច​ប្រទាក់ DCOP Comment[lt]=Pašto programa su DCOP sąsaja diff --git a/kmail/dcopmail.desktop b/kmail/dcopmail.desktop index 6772a1f6e..26aaaa569 100644 --- a/kmail/dcopmail.desktop +++ b/kmail/dcopmail.desktop @@ -26,7 +26,6 @@ Comment[hu]=Levelezőprogram DCOP felülettel Comment[is]=Póstforrit með DCOP viðmóti Comment[it]=Programma di posta con un'interfaccia DCOP Comment[ja]=DCOP インターフェース付のメールプログラム -Comment[ka]=საფოსტო პროგრამა DCOP ინტერფეისით Comment[kk]=DCOP интерфейсті пошта бағдарламасы Comment[km]=កម្មវិធី​អ៊ីមែល​ដែល​មាន​ចំណុច​ប្រទាក់ DCOP Comment[lt]=Pašto programa su DCOP sąsaja diff --git a/kmail/dictionarycombobox.cpp b/kmail/dictionarycombobox.cpp index 88b776231..947d1deb8 100644 --- a/kmail/dictionarycombobox.cpp +++ b/kmail/dictionarycombobox.cpp @@ -134,6 +134,7 @@ namespace KMail { mSpellConfig = new KSpellConfig( 0, 0, 0, false ); mSpellConfig->fillDicts( this, &mDictionaries ); mDefaultDictionary = currentItem(); + mSpellConfig->setDictionary( currentDictionary() ); } void DictionaryComboBox::slotDictionaryChanged( int idx ) diff --git a/kmail/distributionlistdialog.cpp b/kmail/distributionlistdialog.cpp index b591afd71..470518662 100644 --- a/kmail/distributionlistdialog.cpp +++ b/kmail/distributionlistdialog.cpp @@ -31,6 +31,7 @@ #ifdef KDEPIM_NEW_DISTRLISTS #include #endif +#include #include #include @@ -153,8 +154,6 @@ void DistributionListDialog::slotUser1() { bool isEmpty = true; - KABC::AddressBook *ab = KABC::StdAddressBook::self( true ); - TQListViewItem *i = mRecipientsList->firstChild(); while( i ) { DistributionListItem *item = static_cast( i ); @@ -188,6 +187,8 @@ void DistributionListDialog::slotUser1() return; } + KABC::AddressBook *ab = KABC::StdAddressBook::self( true ); + #ifdef KDEPIM_NEW_DISTRLISTS if ( !KPIM::DistributionList::findByName( ab, name ).isEmpty() ) { #else @@ -199,6 +200,18 @@ void DistributionListDialog::slotUser1() return; } + KABC::Resource* const resource = KAddrBookExternal::selectResourceForSaving( ab ); + if ( !resource ) + return; + + // Ask for a save ticket here, we use it for inserting the recipients into the addressbook and + // also for saving the addressbook, see https://issues.kolab.org/issue4281 + KABC::Ticket *ticket = ab->requestSaveTicket( resource ); + if ( !ticket ) { + kdWarning(5006) << "Unable to get save ticket!" << endl; + return; + } + #ifdef KDEPIM_NEW_DISTRLISTS KPIM::DistributionList dlist; dlist.setName( name ); @@ -209,7 +222,7 @@ void DistributionListDialog::slotUser1() if ( item->isOn() ) { kdDebug() << " " << item->addressee().fullEmail() << endl; if ( item->isTransient() ) { - ab->insertAddressee( item->addressee() ); + resource->insertAddressee( item->addressee() ); } if ( item->email() == item->addressee().preferredEmail() ) { dlist.insertEntry( item->addressee() ); @@ -220,7 +233,7 @@ void DistributionListDialog::slotUser1() i = i->nextSibling(); } - ab->insertAddressee( dlist ); + resource->insertAddressee( dlist ); #else KABC::DistributionList *dlist = new KABC::DistributionList( &manager, name ); i = mRecipientsList->firstChild(); @@ -229,7 +242,7 @@ void DistributionListDialog::slotUser1() if ( item->isOn() ) { kdDebug() << " " << item->addressee().fullEmail() << endl; if ( item->isTransient() ) { - ab->insertAddressee( item->addressee() ); + resource->insertAddressee( item->addressee() ); } if ( item->email() == item->addressee().preferredEmail() ) { dlist->insertEntry( item->addressee() ); @@ -241,21 +254,23 @@ void DistributionListDialog::slotUser1() } #endif - // FIXME: Ask the user which resource to save to instead of the default - bool saveError = true; - KABC::Ticket *ticket = ab->requestSaveTicket( 0 /*default resource */ ); - if ( ticket ) - if ( ab->save( ticket ) ) - saveError = false; - else - ab->releaseSaveTicket( ticket ); - - if ( saveError ) + if ( !ab->save( ticket ) ) { kdWarning(5006) << k_funcinfo << " Couldn't save new addresses in the distribution list just created to the address book" << endl; + ab->releaseSaveTicket( ticket ); + return; + } #ifndef KDEPIM_NEW_DISTRLISTS manager.save(); #endif - close(); + // Only accept when the dist list is really in the addressbook, since we can't detect if the + // user aborted saving in another way, since insertAddressee() lacks a return code. +#ifdef KDEPIM_NEW_DISTRLISTS + if ( !KPIM::DistributionList::findByName( ab, name ).isEmpty() ) { +#else + if ( manager.list( name ) ) { +#endif + accept(); + } } diff --git a/kmail/editorwatcher.cpp b/kmail/editorwatcher.cpp index 273c5997f..dbf5f3f96 100644 --- a/kmail/editorwatcher.cpp +++ b/kmail/editorwatcher.cpp @@ -48,12 +48,14 @@ using namespace KMail; -EditorWatcher::EditorWatcher(const KURL & url, const TQString &mimeType, bool openWith, TQObject * parent) : +EditorWatcher::EditorWatcher(const KURL & url, const TQString &mimeType, bool openWith, + TQObject * parent, TQWidget *parentWidget) : TQObject( parent ), mUrl( url ), mMimeType( mimeType ), mOpenWith( openWith ), mEditor( 0 ), + mParentWidget( parentWidget ), mHaveInotify( false ), mFileOpen( false ), mEditorRunning( false ), @@ -154,8 +156,13 @@ void EditorWatcher::checkEditDone() // nobody can edit that fast, we seem to be unable to detect // when the editor will be closed if ( mEditTime.elapsed() <= 3000 ) { - KMessageBox::error( 0, i18n("KMail is unable to detect when the choosen editor is closed. " - "To avoid data loss, editing the attachment will be aborted."), i18n("Unable to edit attachment") ); + KMessageBox::information( + mParentWidget, + i18n( "KMail is unable to detect when the chosen editor is closed. " + "To avoid data loss, editing the attachment will be aborted." ), + i18n( "Unable to edit attachment" ), + "UnableToEditAttachment" ); + } emit editDone( this ); diff --git a/kmail/editorwatcher.h b/kmail/editorwatcher.h index e0d609051..839386f84 100644 --- a/kmail/editorwatcher.h +++ b/kmail/editorwatcher.h @@ -39,7 +39,15 @@ class EditorWatcher : public QObject { Q_OBJECT public: - EditorWatcher( const KURL &url, const TQString &mimeType, bool openWith, TQObject *parent = 0 ); + /** + * Constructs an EditorWatcher. + * @param parent the parent object of this EditorWatcher, which will take care of deleting + * this EditorWatcher if the parent is deleted. + * @param parentWidget the parent widget of this EditorWatcher, which will be used as the parent + * widget for message dialogs. + */ + EditorWatcher( const KURL &url, const TQString &mimeType, bool openWith, + TQObject *parent, TQWidget *parentWidget ); bool start(); bool fileChanged() const { return mFileModified; } signals: @@ -55,6 +63,7 @@ class EditorWatcher : public QObject TQString mMimeType; bool mOpenWith; KProcess *mEditor; + TQWidget *mParentWidget; int mInotifyFd; int mInotifyWatch; diff --git a/kmail/encodingdetector.cpp b/kmail/encodingdetector.cpp index 60913c9fc..f036a193b 100644 --- a/kmail/encodingdetector.cpp +++ b/kmail/encodingdetector.cpp @@ -729,87 +729,6 @@ static TQCString automaticDetectionForWesternEuropean( const unsigned char* ptr, return ""; } -// Other browsers allow comments in the head section, so we need to also. -// It's important not to look for tags inside the comments. -static void skipComment(const char *&ptr, const char *pEnd) -{ - const char *p = ptr; - // Allow ; other browsers do. - if (*p=='>') - { - p++; - } - else - { - while (p!=pEnd) - { - if (*p=='-') - { - // This is the real end of comment, "-->". - if (p[1]=='-' && p[2]=='>') - { - p += 3; - break; - } - // This is the incorrect end of comment that other browsers allow, "--!>". - if (p[1] == '-' && p[2] == '!' && p[3] == '>') - { - p += 4; - break; - } - } - p++; - } - } - ptr=p; -} - -// Returns the position of the encoding string. -static int findXMLEncoding(const TQCString &str, int &encodingLength) -{ - int len = str.length(); - int pos = str.find("encoding"); - if (pos == -1) - return -1; - pos += 8; - - // Skip spaces and stray control characters. - while (pos=len || str[pos] != '=') - return -1; - ++pos; - - // Skip spaces and stray control characters. - while (pos= len) - return -1; - - // Skip quotation mark. - char quoteMark = str[pos]; - if (quoteMark != '"' && quoteMark != '\'') - return -1; - ++pos; - - // Find the trailing quotation mark. - int end=pos; - while (end=len) - return -1; - - encodingLength = end-pos; - return pos; -} - - bool EncodingDetector::errorsIfUtf8 (const char* data, int length) { if (d->m_codec->mibEnum()!=MibUtf8) @@ -905,6 +824,7 @@ EncodingDetector::EncodingChoiceSource EncodingDetector::encodingChoiceSource() const char* EncodingDetector::encoding() const { d->m_storeDecoderName = d->m_codec->name(); + d->m_storeDecoderName = d->m_storeDecoderName.lower().replace( "iso ", "iso-" ); return d->m_storeDecoderName.data(); } diff --git a/kmail/eventsrc b/kmail/eventsrc index 76720efeb..6f4c38dea 100644 --- a/kmail/eventsrc +++ b/kmail/eventsrc @@ -34,7 +34,6 @@ Name[hu]=Új levél érkezett Name[is]=Nýr póstur Name[it]=Nuova posta arrivata Name[ja]=新規メール着信 -Name[ka]=მიღებულია ახალი ფოსტა Name[kk]=Жаңа пошта келді Name[km]=មាន​អ៊ីមែល​ថ្មី​មក​ដល់ Name[lt]=Atėjo naujas paštas @@ -60,8 +59,7 @@ Name[ta]=புதிய அஞ்சல் வந்துள்ளது Name[tg]=Почтаи нав қабул шуд Name[tr]=Yeni E-posta Geldi Name[uk]=Отримана нова пошта -Name[uz]=Yangi xat keldi -Name[uz@cyrillic]=Янги хат келди +Name[uz]=Янги хат келди Name[zh_CN]=新邮件到达 Name[zh_TW]=您有新郵件 Comment=New mail arrived @@ -90,7 +88,6 @@ Comment[hu]=Új levél érkezett Comment[is]=Nýr póstur Comment[it]=Nuova posta Comment[ja]=新規メール着信 -Comment[ka]=მიღებულია ახალი ფოსტა Comment[kk]=Жаңа пошта келді Comment[km]=មាន​អ៊ីមែល​ថ្មី​មក​ដល់ Comment[lt]=Atėjo naujas paštas @@ -116,8 +113,7 @@ Comment[ta]=புதிய அஞ்சல் வந்துள்ளது Comment[tg]=Почтаи нав қабул шуд Comment[tr]=Yeni e-posta geldi Comment[uk]=Надійшла нова пошта -Comment[uz]=Yangi xat keldi -Comment[uz@cyrillic]=Янги хат келди +Comment[uz]=Янги хат келди Comment[zh_CN]=新邮件到达 Comment[zh_TW]=您有新郵件 default_sound= diff --git a/kmail/expirypropertiesdialog.cpp b/kmail/expirypropertiesdialog.cpp index a313cc88d..c524f8b32 100644 --- a/kmail/expirypropertiesdialog.cpp +++ b/kmail/expirypropertiesdialog.cpp @@ -28,8 +28,8 @@ using namespace KMail; * */ ExpiryPropertiesDialog::ExpiryPropertiesDialog( KMFolderTree* tree, KMFolder* folder ) - : KDialogBase( tree, "expiry_properties", false, i18n( "Mail Expiry Properties" ), - KDialogBase::Ok|KDialogBase::Cancel, + : KDialogBase( tree, "expiry_properties", false, i18n( "Mail Expiry Properties" ), + KDialogBase::Ok|KDialogBase::Cancel, KDialogBase::Ok, true ), mFolder( folder ) { @@ -37,10 +37,10 @@ ExpiryPropertiesDialog::ExpiryPropertiesDialog( KMFolderTree* tree, KMFolder* fo TQWidget* privateLayoutWidget = new TQWidget( this, "globalVBox" ); setMainWidget( privateLayoutWidget ); privateLayoutWidget->setGeometry( TQRect( 10, 20, 270, 138 ) ); - globalVBox = new TQVBoxLayout( privateLayoutWidget, 11, 6, "globalVBox"); + globalVBox = new TQVBoxLayout( privateLayoutWidget, 11, 6, "globalVBox"); globalVBox->setSpacing( 20 ); - readHBox = new TQHBoxLayout( 0, 0, 6, "readHBox"); + readHBox = new TQHBoxLayout( 0, 0, 6, "readHBox"); expireReadMailCB = new TQCheckBox( privateLayoutWidget, "expireReadMailCB" ); expireReadMailCB->setText( i18n( "Expire read mails after" ) ); @@ -58,7 +58,7 @@ ExpiryPropertiesDialog::ExpiryPropertiesDialog( KMFolderTree* tree, KMFolder* fo readHBox->addWidget( labelDays ); globalVBox->addLayout( readHBox ); - unreadHBox = new TQHBoxLayout( 0, 0, 6, "unreadHBox"); + unreadHBox = new TQHBoxLayout( 0, 0, 6, "unreadHBox"); expireUnreadMailCB = new TQCheckBox( privateLayoutWidget, "expireUnreadMailCB" ); expireUnreadMailCB->setText( i18n( "Expire unread mails after" ) ); @@ -77,18 +77,18 @@ ExpiryPropertiesDialog::ExpiryPropertiesDialog( KMFolderTree* tree, KMFolder* fo unreadHBox->addWidget( labelDays2 ); globalVBox->addLayout( unreadHBox ); - expiryActionHBox = new TQHBoxLayout( 0, 0, 6, "expiryActionHBox"); + expiryActionHBox = new TQHBoxLayout( 0, 0, 6, "expiryActionHBox"); expiryActionLabel = new TQLabel( privateLayoutWidget, "expiryActionLabel" ); expiryActionLabel->setText( i18n( "Expiry action:" ) ); expiryActionLabel->setAlignment( int( TQLabel::AlignVCenter ) ); expiryActionHBox->addWidget( expiryActionLabel ); - actionsHBox = new TQVBoxLayout( 0, 0, 6, "actionsHBox"); + actionsHBox = new TQVBoxLayout( 0, 0, 6, "actionsHBox"); actionsGroup = new TQButtonGroup( this ); actionsGroup->hide(); // for mutual exclusion of the radio buttons - moveToHBox = new TQHBoxLayout( 0, 0, 6, "moveToHBox"); + moveToHBox = new TQHBoxLayout( 0, 0, 6, "moveToHBox"); moveToRB = new TQRadioButton( privateLayoutWidget, "moveToRB" ); actionsGroup->insert( moveToRB ); @@ -119,14 +119,14 @@ ExpiryPropertiesDialog::ExpiryPropertiesDialog( KMFolderTree* tree, KMFolder* fo int daysToExpireRead, daysToExpireUnread; mFolder->daysToExpire( daysToExpireUnread, daysToExpireRead); - if ( expiryGloballyOn - && mFolder->getReadExpireUnits() != expireNever + if ( expiryGloballyOn + && mFolder->getReadExpireUnits() != expireNever && daysToExpireRead >= 0 ) { expireReadMailCB->setChecked( true ); expireReadMailSB->setValue( daysToExpireRead ); } if ( expiryGloballyOn - && mFolder->getUnreadExpireUnits() != expireNever + && mFolder->getUnreadExpireUnits() != expireNever && daysToExpireUnread >= 0 ) { expireUnreadMailCB->setChecked( true ); expireUnreadMailSB->setValue( daysToExpireUnread ); @@ -159,11 +159,29 @@ ExpiryPropertiesDialog::~ExpiryPropertiesDialog() void ExpiryPropertiesDialog::slotOk() { bool enableGlobally = expireReadMailCB->isChecked() || expireUnreadMailCB->isChecked(); - if ( enableGlobally && moveToRB->isChecked() && !folderSelector->folder() ) { - KMessageBox::error( this, i18n("Please select a folder to expire messages into."), - i18n( "No Folder Selected" ) ); + + KMFolder *expireToFolder = folderSelector->folder(); + if ( enableGlobally && moveToRB->isChecked() && !expireToFolder ) { + KMessageBox::error( + this, + i18n( "Please select a folder to expire messages into." ), + i18n( "No Folder Selected" ) ); return; - } + } + + if ( expireToFolder ) { + if ( expireToFolder->idString() == mFolder->idString() ) { + KMessageBox::error( + this, + i18n( "Please select a different folder than the current folder " + "to expire message into." ), + i18n( "Wrong Folder Selected" ) ); + return; + } else { + mFolder->setExpireToFolderId( expireToFolder->idString() ); + } + } + mFolder->setAutoExpire( enableGlobally ); // we always write out days now mFolder->setReadExpireAge( expireReadMailSB->value() ); @@ -175,9 +193,6 @@ void ExpiryPropertiesDialog::slotOk() mFolder->setExpireAction( KMFolder::ExpireDelete ); else mFolder->setExpireAction( KMFolder::ExpireMove ); - KMFolder* expireToFolder = folderSelector->folder(); - if ( expireToFolder ) - mFolder->setExpireToFolderId( expireToFolder->idString() ); // trigger immediate expiry if there is something to do if ( enableGlobally ) diff --git a/kmail/favoritefolderview.cpp b/kmail/favoritefolderview.cpp index 35b2e3ee4..5d8e3d13a 100644 --- a/kmail/favoritefolderview.cpp +++ b/kmail/favoritefolderview.cpp @@ -309,7 +309,8 @@ void FavoriteFolderView::dropped(TQDropEvent * e, TQListViewItem * after) KMFolderTreeItem *fti = static_cast( it.current() ); if ( !fti->folder() ) continue; - afterItem = addFolder( fti->folder(), prettyName( fti ), afterItem ); + if( !mFolderToItem.contains( fti->folder() ) ) + afterItem = addFolder( fti->folder(), prettyName( fti ), afterItem ); } e->accept(); } @@ -323,20 +324,25 @@ void FavoriteFolderView::contextMenu(TQListViewItem * item, const TQPoint & poin mContextMenuItem = fti; KPopupMenu contextMenu; if ( fti && fti->folder() ) { - contextMenu.insertItem( SmallIconSet("editdelete"), i18n("Remove From Favorites"), - this, TQT_SLOT(removeFolder()) ); - contextMenu.insertItem( SmallIconSet("edit"), i18n("Rename Favorite"), this, TQT_SLOT(renameFolder()) ); - contextMenu.insertSeparator(); - mainWidget()->action("mark_all_as_read")->plug( &contextMenu ); if ( fti->folder()->folderType() == KMFolderTypeImap || fti->folder()->folderType() == KMFolderTypeCachedImap ) mainWidget()->action("refresh_folder")->plug( &contextMenu ); if ( fti->folder()->isMailingListEnabled() ) mainWidget()->action("post_message")->plug( &contextMenu ); + mainWidget()->action("search_messages")->plug( &contextMenu ); + if ( fti->folder()->canDeleteMessages() && ( fti->folder()->count() > 0 ) ) + mainWidget()->action("empty")->plug( &contextMenu ); + contextMenu.insertSeparator(); contextMenu.insertItem( SmallIconSet("configure_shortcuts"), i18n("&Assign Shortcut..."), fti, TQT_SLOT(assignShortcut()) ); contextMenu.insertItem( i18n("Expire..."), fti, TQT_SLOT(slotShowExpiryProperties()) ); mainWidget()->action("modify")->plug( &contextMenu ); + contextMenu.insertSeparator(); + + contextMenu.insertItem( SmallIconSet("editdelete"), i18n("Remove From Favorites"), + this, TQT_SLOT(removeFolder()) ); + contextMenu.insertItem( SmallIconSet("edit"), i18n("Rename Favorite"), this, TQT_SLOT(renameFolder()) ); + } else { contextMenu.insertItem( SmallIconSet("bookmark_add"), i18n("Add Favorite Folder..."), this, TQT_SLOT(addFolder()) ); @@ -346,8 +352,13 @@ void FavoriteFolderView::contextMenu(TQListViewItem * item, const TQPoint & poin void FavoriteFolderView::removeFolder() { + KMFolderTreeItem *fti = mContextMenuItem; + KMFolder *folder = 0; + if( fti ) + folder = fti->folder(); delete mContextMenuItem; mContextMenuItem = 0; + removeFromFolderToItemMap(folder); notifyInstancesOnChange(); } @@ -446,6 +457,9 @@ void FavoriteFolderView::addFolder() KMFolder *folder = dlg.folder(); if ( !folder ) return; + if ( mFolderToItem.contains( folder ) ) + return; + KMFolderTreeItem *fti = findFolderTreeItem( folder ); addFolder( folder, fti ? prettyName( fti ) : folder->label() ); } @@ -454,7 +468,8 @@ void KMail::FavoriteFolderView::addFolder(KMFolderTreeItem * fti) { if ( !fti || !fti->folder() ) return; - addFolder( fti->folder(), prettyName( fti ) ); + if ( !mFolderToItem.contains( fti->folder() ) ) + addFolder( fti->folder(), prettyName( fti ) ); } KMFolderTreeItem * FavoriteFolderView::findFolderTreeItem(KMFolder * folder) const @@ -485,7 +500,7 @@ void FavoriteFolderView::checkMail() imap->getAndCheckFolder(); } else if ( fti->folder()->folderType() == KMFolderTypeCachedImap ) { KMFolderCachedImap* f = static_cast( fti->folder()->storage() ); - f->account()->processNewMailSingleFolder( fti->folder() ); + f->account()->processNewMailInFolder( fti->folder() ); } } } diff --git a/kmail/filterimporterexporter.cpp b/kmail/filterimporterexporter.cpp index bfe40170c..c5780431d 100644 --- a/kmail/filterimporterexporter.cpp +++ b/kmail/filterimporterexporter.cpp @@ -38,74 +38,105 @@ #include #include #include +#include #include +#include using namespace KMail; -class FilterSelectionDialog : public KDialogBase +FilterSelectionDialog::FilterSelectionDialog( TQWidget * parent ) + :KDialogBase( parent, "filterselection", true, i18n("Select Filters"), Ok|Cancel, Ok, true ), + wasCancelled( false ) { -public: - FilterSelectionDialog( TQWidget * parent = 0 ) - :KDialogBase( parent, "filterselection", true, i18n("Select Filters"), Ok|Cancel, Ok, true ), - wasCancelled( false ) - { - filtersListView = new KListView( this ); - setMainWidget(filtersListView); - filtersListView->setSorting( -1 ); - filtersListView->setSelectionMode( TQListView::NoSelection ); - filtersListView->addColumn( i18n("Filters"), 300 ); - filtersListView->setFullWidth( true ); - resize( 300, 350 ); - } + TQWidget *w = new TQWidget( this ); + TQVBoxLayout *top = new TQVBoxLayout( w ); + + filtersListView = new KListView( w ); + top->addWidget( filtersListView ); + setMainWidget(w); + filtersListView->setSorting( -1 ); + filtersListView->setSelectionMode( TQListView::NoSelection ); + filtersListView->addColumn( i18n("Filters"), 300 ); + filtersListView->setFullWidth( true ); + TQHBoxLayout *buttonLayout = new TQHBoxLayout( this ); + top->addLayout( buttonLayout ); + selectAllButton = new KPushButton( i18n( "Select All" ), w ); + buttonLayout->addWidget( selectAllButton ); + unselectAllButton = new KPushButton( i18n( "Unselect All" ), w ); + buttonLayout->addWidget( unselectAllButton ); + connect( selectAllButton, TQT_SIGNAL( clicked() ), this, TQT_SLOT( slotSelectAllButton() ) ); + connect( unselectAllButton, TQT_SIGNAL( clicked() ), this, TQT_SLOT( slotUnselectAllButton() ) ); + resize( 300, 350 ); +} - virtual ~FilterSelectionDialog() - { - } - - virtual void slotCancel() - { - wasCancelled = true; - KDialogBase::slotCancel(); - } - - bool cancelled() - { - return wasCancelled; - } +FilterSelectionDialog::~FilterSelectionDialog() +{ +} - void setFilters( const TQValueList& filters ) - { - originalFilters = filters; - filtersListView->clear(); - TQValueListConstIterator it = filters.constEnd(); - while ( it != filters.constBegin() ) { - --it; - KMFilter* filter = *it; - TQCheckListItem* item = new TQCheckListItem( filtersListView, filter->name(), TQCheckListItem::CheckBox ); - item->setOn( true ); - } - } - - TQValueList selectedFilters() const - { - TQValueList filters; - TQListViewItemIterator it( filtersListView ); - int i = 0; - while( it.current() ) { - TQCheckListItem* item = static_cast( it.current() ); - if ( item->isOn() ) - filters << originalFilters[i]; - ++i; ++it; - } - return filters; - } -private: - KListView *filtersListView; - TQValueList originalFilters; - bool wasCancelled; -}; +void FilterSelectionDialog::slotCancel() +{ + wasCancelled = true; + KDialogBase::slotCancel(); +} + +bool FilterSelectionDialog::cancelled() +{ + return wasCancelled; +} + +void FilterSelectionDialog::setFilters( const TQValueList& filters ) +{ + if ( filters.isEmpty() ) + { + enableButtonOK( false ); + return; + } + originalFilters = filters; + filtersListView->clear(); + TQValueListConstIterator it = filters.constEnd(); + while ( it != filters.constBegin() ) { + --it; + KMFilter* filter = *it; + TQCheckListItem* item = new TQCheckListItem( filtersListView, filter->name(), TQCheckListItem::CheckBox ); + item->setOn( true ); + } +} + +TQValueList FilterSelectionDialog::selectedFilters() const +{ + TQValueList filters; + TQListViewItemIterator it( filtersListView ); + int i = 0; + while( it.current() ) { + TQCheckListItem* item = static_cast( it.current() ); + if ( item->isOn() ) + filters << originalFilters[i]; + ++i; ++it; + } + return filters; +} + +void FilterSelectionDialog::slotUnselectAllButton() +{ + TQListViewItemIterator it( filtersListView ); + while( it.current() ) { + TQCheckListItem* item = static_cast( it.current() ); + item->setOn( false ); + ++it; + } +} + +void FilterSelectionDialog::slotSelectAllButton() +{ + TQListViewItemIterator it( filtersListView ); + while( it.current() ) { + TQCheckListItem* item = static_cast( it.current() ); + item->setOn( true ); + ++it; + } +} /* static */ TQValueList FilterImporterExporter::readFiltersFromConfig( KConfig* config, bool bPopFilter ) @@ -116,7 +147,7 @@ TQValueList FilterImporterExporter::readFiltersFromConfig( KConfig* c numFilters = config->readNumEntry("popfilters",0); else numFilters = config->readNumEntry("filters",0); - + TQValueList filters; for ( int i=0 ; i < numFilters ; ++i ) { TQString grpName; @@ -136,7 +167,7 @@ TQValueList FilterImporterExporter::readFiltersFromConfig( KConfig* c return filters; } -/* static */ +/* static */ void FilterImporterExporter::writeFiltersToConfig( const TQValueList& filters, KConfig* config, bool bPopFilter ) { // first, delete all groups: @@ -145,7 +176,7 @@ void FilterImporterExporter::writeFiltersToConfig( const TQValueList& for ( TQStringList::Iterator it = filterGroups.begin() ; it != filterGroups.end() ; ++it ) config->deleteGroup( *it ); - + int i = 0; for ( TQValueListConstIterator it = filters.constBegin() ; it != filters.constEnd() ; ++it ) { @@ -180,9 +211,9 @@ FilterImporterExporter::~FilterImporterExporter() TQValueList FilterImporterExporter::importFilters() { TQString fileName = KFileDialog::getOpenFileName( TQDir::homeDirPath(), TQString::null, mParent, i18n("Import Filters") ); - if ( fileName.isEmpty() ) + if ( fileName.isEmpty() ) return TQValueList(); // cancel - + { // scoping TQFile f( fileName ); if ( !f.open( IO_ReadOnly ) ) { @@ -190,7 +221,7 @@ TQValueList FilterImporterExporter::importFilters() return TQValueList(); } } - + KConfig config( fileName ); TQValueList imported = readFiltersFromConfig( &config, mPopFilter ); FilterSelectionDialog dlg( mParent ); @@ -202,10 +233,10 @@ TQValueList FilterImporterExporter::importFilters() void FilterImporterExporter::exportFilters(const TQValueList & filters ) { KURL saveUrl = KFileDialog::getSaveURL( TQDir::homeDirPath(), TQString::null, mParent, i18n("Export Filters") ); - + if ( saveUrl.isEmpty() || !Util::checkOverwrite( saveUrl, mParent ) ) return; - + KConfig config( saveUrl.path() ); FilterSelectionDialog dlg( mParent ); dlg.setFilters( filters ); @@ -214,3 +245,4 @@ void FilterImporterExporter::exportFilters(const TQValueList & filter writeFiltersToConfig( dlg.selectedFilters(), &config, mPopFilter ); } +#include "filterimporterexporter.moc" diff --git a/kmail/filterimporterexporter.h b/kmail/filterimporterexporter.h index 75f43d14f..8b9a63bb8 100644 --- a/kmail/filterimporterexporter.h +++ b/kmail/filterimporterexporter.h @@ -31,10 +31,11 @@ #define __FILTERIMPORTEREXPORTER_H__ #include - +#include class KMFilter; class KConfig; class TQWidget; +class KPushButton; namespace KMail { @@ -48,22 +49,44 @@ class FilterImporterExporter public: FilterImporterExporter( TQWidget* parent, bool popFilter = false ); virtual ~FilterImporterExporter(); - - /** Export the given filter rules to a file which + + /** Export the given filter rules to a file which * is asked from the user. The list to export is also * presented for confirmation/selection. */ void exportFilters( const TQValueList & ); - + /** Import filters. Ask the user where to import them from * and which filters to import. */ TQValueList importFilters(); - + static void writeFiltersToConfig( const TQValueList& filters, KConfig* config, bool bPopFilter ); static TQValueList readFiltersFromConfig( KConfig* config, bool bPopFilter ); private: TQWidget* mParent; bool mPopFilter; }; +class FilterSelectionDialog : public KDialogBase +{ + Q_OBJECT +public: + FilterSelectionDialog( TQWidget * parent = 0 ); + + virtual ~FilterSelectionDialog(); + virtual void slotCancel(); + bool cancelled(); + void setFilters( const TQValueList& filters ); + + TQValueList selectedFilters() const; +public slots: + void slotUnselectAllButton(); + void slotSelectAllButton(); +private: + KListView *filtersListView; + TQValueList originalFilters; + bool wasCancelled; + KPushButton *selectAllButton; + KPushButton *unselectAllButton; +}; } diff --git a/kmail/folderdiaacltab.cpp b/kmail/folderdiaacltab.cpp index d1f02a57c..bebd9c319 100644 --- a/kmail/folderdiaacltab.cpp +++ b/kmail/folderdiaacltab.cpp @@ -30,7 +30,7 @@ * your version. */ -#include +#include // FOR KDEPIM_NEW_DISTRLISTS #include "folderdiaacltab.h" #include "acljobs.h" @@ -90,7 +90,7 @@ KMail::ACLEntryDialog::ACLEntryDialog( IMAPUserIdFormat userIdFormat, const TQSt { TQWidget *page = new TQWidget( this ); setMainWidget(page); - TQGridLayout *topLayout = new TQGridLayout( page, 3 /*rows*/, 3 /*cols*/, 0, spacingHint() ); + TQGridLayout *topLayout = new TQGridLayout( page, 4 /*rows*/, 3 /*cols*/, 0, spacingHint() ); TQLabel *label = new TQLabel( i18n( "&User identifier:" ), page ); topLayout->addWidget( label, 0, 0 ); @@ -100,7 +100,7 @@ KMail::ACLEntryDialog::ACLEntryDialog( IMAPUserIdFormat userIdFormat, const TQSt label->setBuddy( mUserIdLineEdit ); TQWhatsThis::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." ) ); - TQPushButton* kabBtn = new TQPushButton( "...", page ); + TQPushButton* kabBtn = new TQPushButton( i18n( "Se&lect..." ), page ); topLayout->addWidget( kabBtn, 0, 2 ); mButtonGroup = new TQVButtonGroup( i18n( "Permissions" ), page ); @@ -115,6 +115,9 @@ KMail::ACLEntryDialog::ACLEntryDialog( IMAPUserIdFormat userIdFormat, const TQSt } topLayout->setRowStretch(2, 10); + TQLabel *noteLabel = new TQLabel( i18n( "Note: Renaming requires write permissions on the parent folder." ), page ); + topLayout->addMultiCellWidget( noteLabel, 2, 2, 0, 2 ); + connect( mUserIdLineEdit, TQT_SIGNAL( textChanged( const TQString& ) ), TQT_SLOT( slotChanged() ) ); connect( kabBtn, TQT_SIGNAL( clicked() ), TQT_SLOT( slotSelectAddresses() ) ); connect( mButtonGroup, TQT_SIGNAL( clicked( int ) ), TQT_SLOT( slotChanged() ) ); @@ -178,12 +181,7 @@ TQString KMail::ACLEntryDialog::userId() const TQStringList KMail::ACLEntryDialog::userIds() const { - TQStringList lst = TQStringList::split( ",", mUserIdLineEdit->text() ); - for( TQStringList::Iterator it = lst.begin(); it != lst.end(); ++it ) { - // Strip white space (in particular, due to ", ") - *it = (*it).stripWhiteSpace(); - } - return lst; + return KPIM::splitEmailAddrList( mUserIdLineEdit->text() ); } unsigned int KMail::ACLEntryDialog::permissions() const @@ -319,6 +317,7 @@ KMail::FolderDiaACLTab::FolderDiaACLTab( KMFolderDialog* dlg, TQWidget* parent, : FolderDiaTab( parent, name ), mImapAccount( 0 ), mUserRights( 0 ), + mUserRightsState( KMail::ACLJobs::NotFetchedYet ), mDlg( dlg ), mChanged( false ), mAccepting( false ), mSaving( false ) { @@ -344,7 +343,7 @@ KMail::FolderDiaACLTab::FolderDiaACLTab( KMFolderDialog* dlg, TQWidget* parent, TQT_SLOT(slotEditACL(TQListViewItem*)) ); connect( mListView, TQT_SIGNAL(returnPressed(TQListViewItem*)), TQT_SLOT(slotEditACL(TQListViewItem*)) ); - connect( mListView, TQT_SIGNAL(selectionChanged(TQListViewItem*)), + connect( mListView, TQT_SIGNAL(currentChanged(TQListViewItem*)), TQT_SLOT(slotSelectionChanged(TQListViewItem*)) ); TQVBox* buttonBox = new TQVBox( mACLWidget ); @@ -381,12 +380,14 @@ void KMail::FolderDiaACLTab::initializeWithValuesFromFolder( KMFolder* folder ) mImapPath = folderImap->imapPath(); mImapAccount = folderImap->account(); mUserRights = folderImap->userRights(); + mUserRightsState = folderImap->userRightsState(); } else if ( mFolderType == KMFolderTypeCachedImap ) { KMFolderCachedImap* folderImap = static_cast( folder->storage() ); mImapPath = folderImap->imapPath(); mImapAccount = folderImap->account(); mUserRights = folderImap->userRights(); + mUserRightsState = folderImap->userRightsState(); } else assert( 0 ); // see KMFolderDialog constructor @@ -422,13 +423,16 @@ void KMail::FolderDiaACLTab::load() 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 yet, please use \"Check Mail\"." ) ); + if ( mUserRightsState == KMail::ACLJobs::FetchFailed || + folderImap->aclListState() == KMail::ACLJobs::FetchFailed ) { + TQString text = i18n( "Error retrieving user permissions." ); + if ( mUserRightsState == KMail::ACLJobs::Ok ) { + text += "\n" + i18n( "You might not have enough permissions to see the permissions of this folder." ); + } + mLabel->setText( text ); + } else if ( mUserRightsState == KMail::ACLJobs::NotFetchedYet || + folderImap->aclListState() == KMail::ACLJobs::NotFetchedYet ) { + 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() ); } @@ -474,7 +478,7 @@ void KMail::FolderDiaACLTab::slotConnectionResult( int errorCode, const TQString return; } - if ( mUserRights == 0 ) { + if ( mUserRightsState != KMail::ACLJobs::Ok ) { connect( mImapAccount, TQT_SIGNAL( receivedUserRights( KMFolder* ) ), this, TQT_SLOT( slotReceivedUserRights( KMFolder* ) ) ); KMFolder* folder = mDlg->folder() ? mDlg->folder() : mDlg->parentFolder(); @@ -494,6 +498,7 @@ void KMail::FolderDiaACLTab::slotReceivedUserRights( KMFolder* folder ) if ( folder == mDlg->folder() ? mDlg->folder() : mDlg->parentFolder() ) { KMFolderImap* folderImap = static_cast( folder->storage() ); mUserRights = folderImap->userRights(); + mUserRightsState = folderImap->userRightsState(); startListing(); } } diff --git a/kmail/folderdiaacltab.h b/kmail/folderdiaacltab.h index 87b296eef..6576d13e6 100644 --- a/kmail/folderdiaacltab.h +++ b/kmail/folderdiaacltab.h @@ -33,6 +33,7 @@ #define FOLDERDIAACL_H #include "kmfolderdia.h" +#include "acljobs.h" #include "kmfoldertype.h" class KMFolderImap; @@ -137,6 +138,7 @@ private: TQString mImapPath; ImapAccountBase* mImapAccount; int mUserRights; + KMail::ACLJobs::ACLFetchState mUserRightsState; KMFolderType mFolderType; ACLList mInitialACLList; ACLList mACLList; // to be set diff --git a/kmail/foldersetselector.cpp b/kmail/foldersetselector.cpp new file mode 100644 index 000000000..c35fd0c1e --- /dev/null +++ b/kmail/foldersetselector.cpp @@ -0,0 +1,88 @@ +/* + Copyright (c) 2007 Volker Krause + + 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. +*/ + +#include "foldersetselector.h" + +#include "globalsettings.h" +#include "kmfoldertree.h" +#include "simplefoldertree.h" +#include "kmfoldercachedimap.h" + +#include + +using namespace KMail; + +FolderSetSelector::FolderSetSelector( KMFolderTree *ft, TQWidget * parent ) + : KDialogBase( parent, "FolderSetSelector", true, TQString(), Ok|Cancel, Ok, true ) +{ + assert( ft ); + + mTreeView = new KMail::SimpleFolderTreeBase( makeVBoxMainWidget(), ft, + GlobalSettings::self()->lastSelectedFolder(), false ); + mTreeView->setFocus(); + + TQListViewItemIterator it( mTreeView ); + while ( it.current() ) { + SimpleFolderTreeItem *item = dynamic_cast*>( it.current() ); + ++it; + if ( !item ) + continue; + if ( !item->folder() ) { + item->setEnabled( false ); + continue; + } + if ( item->folder()->folderType() == KMFolderTypeCachedImap + && static_cast( item->folder()->storage() )->imapPath() == "/INBOX/" ) { + item->setOn( true ); + } + if ( item->folder()->folderType() != KMFolderTypeCachedImap ) { + item->setEnabled( false ); + } + } + +} + +TQValueList< int > FolderSetSelector::selectedFolders() +{ + TQValueList rv; + TQListViewItemIterator it( mTreeView ); + while ( it.current() ) { + SimpleFolderTreeItem *item = dynamic_cast*>( it.current() ); + if ( item && item->isOn() && item->folder() ) + rv.append( item->folder()->id() ); + ++it; + } + return rv; +} + +void FolderSetSelector::setSelectedFolders(const TQValueList< int > & folderIds) +{ + TQListViewItemIterator it( mTreeView ); + while ( it.current() ) { + SimpleFolderTreeItem *item = dynamic_cast*>( it.current() ); + if ( item && item->folder() ) { + if ( folderIds.contains( item->folder()->id() ) ) + item->setOn( true ); + else + item->setOn( false ); + } + ++it; + } +} + +#include "foldersetselector.moc" diff --git a/kmail/foldersetselector.h b/kmail/foldersetselector.h new file mode 100644 index 000000000..5d51db31b --- /dev/null +++ b/kmail/foldersetselector.h @@ -0,0 +1,45 @@ +/* + Copyright (c) 2007 Volker Krause + + 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. +*/ + +#ifndef KMAIL_FOLDERSETSELECTOR_H +#define KMAIL_FOLDERSETSELECTOR_H + +#include + +class KListView; +class KMFolderTree; + +namespace KMail { + +class FolderSetSelector : public KDialogBase +{ + Q_OBJECT + + public: + FolderSetSelector( KMFolderTree *ft, TQWidget *parent = 0 ); + + TQValueList selectedFolders(); + void setSelectedFolders( const TQValueList &folderIds ); + + private: + KListView *mTreeView; +}; + +} + +#endif diff --git a/kmail/folderstorage.cpp b/kmail/folderstorage.cpp index c1d447a67..78724d502 100644 --- a/kmail/folderstorage.cpp +++ b/kmail/folderstorage.cpp @@ -85,8 +85,8 @@ FolderStorage::FolderStorage( KMFolder* folder, const char* aName ) mHasChildren = HasNoChildren; mContentsType = KMail::ContentsTypeMail; - - connect(this, TQT_SIGNAL(closed(KMFolder*)), mFolder, TQT_SIGNAL(closed())); + + connect(this, TQT_SIGNAL(closed(KMFolder*)), mFolder, TQT_SIGNAL(closed())); } //----------------------------------------------------------------------------- @@ -477,11 +477,11 @@ void FolderStorage::take(TQPtrList msgList) KMMessage* FolderStorage::getMsg(int idx) { if ( mOpenCount <= 0 ) { - kdWarning(5006) << "FolderStorage::getMsg was called on a closed folder: " << folder()->prettyURL() << endl; + kdWarning(5006) << "FolderStorage::getMsg was called on a closed folder: " << folder()->prettyURL() << endl; return 0; } - if ( idx < 0 || idx >= count() ) { - kdWarning(5006) << "FolderStorage::getMsg was asked for an invalid index. idx =" << idx << " count()=" << count() << endl; + if ( idx < 0 || idx >= count() ) { + kdWarning(5006) << "FolderStorage::getMsg was asked for an invalid index. idx =" << idx << " count()=" << count() << endl; return 0; } @@ -502,6 +502,10 @@ KMMessage* FolderStorage::getMsg(int idx) if (mCompactable && (!msg || (msg->subject().isEmpty() != mbSubject.isEmpty()))) { kdDebug(5006) << "Error: " << location() << " Index file is inconsistent with folder file. This should never happen." << endl; + + // We can't recreate the index at this point, since that would invalidate the current + // message list and delete KMMsgBase or KMMessage objects that are in use. + // Do it later in KMFolderIndex::readIndexHeader() instead. mCompactable = false; // Don't compact writeConfig(); } @@ -522,11 +526,16 @@ KMMessage* FolderStorage::getMsg(int idx) //----------------------------------------------------------------------------- KMMessage* FolderStorage::readTemporaryMsg(int idx) { - if(!(idx >= 0 && idx <= count())) + if(!(idx >= 0 && idx <= count())) { + kdDebug(5006) << k_funcinfo << "Invalid index " << idx << "!" << endl; return 0; + } KMMsgBase* mb = getMsgBase(idx); - if (!mb) return 0; + if (!mb) { + kdDebug(5006) << k_funcinfo << "getMsgBase() for " << idx << " failed!" << endl; + return 0; + } unsigned long sernum = mb->getMsgSerNum(); @@ -542,7 +551,11 @@ KMMessage* FolderStorage::readTemporaryMsg(int idx) msg = new KMMessage(*(KMMsgInfo*)mb); msg->setMsgSerNum(sernum); // before fromDwString so that readyToShow uses the right sernum msg->setComplete( true ); - msg->fromDwString(getDwString(idx)); + const DwString msgString = getDwString( idx ); + if ( msgString.size() <= 0 ) { + kdDebug(5006) << k_funcinfo << " Calling getDwString() failed!" << endl; + } + msg->fromDwString( msgString ); } msg->setEnableUndo(undo); return msg; @@ -914,7 +927,7 @@ void FolderStorage::readConfig() mCompactable = config->readBoolEntry("Compactable", true); if ( mSize == -1 ) mSize = config->readNum64Entry("FolderSize", -1); - + int type = config->readNumEntry( "ContentsType", 0 ); if ( type < 0 || type > KMail::ContentsTypeLast ) type = 0; setContentsType( static_cast( type ) ); @@ -1173,4 +1186,22 @@ KMAccount* FolderStorage::account() const return 0; } +bool FolderStorage::mailCheckInProgress() const +{ + return false; +} + +bool FolderStorage::canDeleteMessages() const +{ + return !isReadOnly(); +} + +void FolderStorage::setNoContent(bool aNoContent) +{ + const bool changed = aNoContent != mNoContent; + mNoContent = aNoContent; + if ( changed ) + emit noContentChanged(); +} + #include "folderstorage.moc" diff --git a/kmail/folderstorage.h b/kmail/folderstorage.h index e132c7b07..7d7165431 100644 --- a/kmail/folderstorage.h +++ b/kmail/folderstorage.h @@ -106,8 +106,7 @@ public: virtual bool noContent() const { return mNoContent; } /** Specify, that the folder can't contain mails. */ - virtual void setNoContent(bool aNoContent) - { mNoContent = aNoContent; } + virtual void setNoContent(bool aNoContent); /** Returns, if the folder can't have children */ virtual bool noChildren() const { return mNoChildren; } @@ -342,6 +341,9 @@ public: /** Is the folder read-only? */ virtual bool isReadOnly() const = 0; + /** Can messages in this folder be deleted? */ + virtual bool canDeleteMessages() const; + /** Returns the label of the folder for visualization. */ TQString label() const; @@ -417,6 +419,8 @@ public: virtual KMAccount* account() const; + virtual bool mailCheckInProgress() const; + signals: /** Emitted when the status, name, or associated accounts of this folder changed. */ @@ -432,7 +436,7 @@ signals: /** Emitted when the folder was closed and ticket owners have to reopen */ void closed( KMFolder* ); - + /** Emitted when the serial numbers of this folder were invalidated. */ void invalidated( KMFolder * ); @@ -451,6 +455,9 @@ signals: /** Emitted when the readonly status of the folder changes. */ void readOnlyChanged(KMFolder*); + /** Emitted when the no content state of the folder changes. */ + void noContentChanged(); + /** Emitted before a message is removed from the folder. */ void msgRemoved(KMFolder*, Q_UINT32 sernum); @@ -494,6 +501,11 @@ signals: /** Emitted when the folder's size changes. */ void folderSizeChanged(); + /** + * Emiitted when the sync state, i.e. mailCheckInProgress(), changes. + * Currently only supported for disconnected IMAP. + */ + void syncStateChanged(); public slots: /** Incrementally update the index if possible else call writeIndex */ diff --git a/kmail/foldertreebase.cpp b/kmail/foldertreebase.cpp index 3ca61845e..c35c88074 100644 --- a/kmail/foldertreebase.cpp +++ b/kmail/foldertreebase.cpp @@ -81,12 +81,12 @@ int FolderTreeBase::dndMode(bool alwaysAsk) action = DRAG_MOVE; } else { if ( GlobalSettings::self()->showPopupAfterDnD() || alwaysAsk ) { - KPopupMenu *menu = new KPopupMenu( this ); - menu->insertItem( i18n("&Move Here"), DRAG_MOVE, 0 ); - menu->insertItem( SmallIcon("editcopy"), i18n("&Copy Here"), DRAG_COPY, 1 ); - menu->insertSeparator(); - menu->insertItem( SmallIcon("cancel"), i18n("C&ancel"), DRAG_CANCEL, 3 ); - action = menu->exec( TQCursor::pos(), 0 ); + KPopupMenu menu; + menu.insertItem( i18n("&Move Here"), DRAG_MOVE, 0 ); + menu.insertItem( SmallIcon("editcopy"), i18n("&Copy Here"), DRAG_COPY, 1 ); + menu.insertSeparator(); + menu.insertItem( SmallIcon("cancel"), i18n("C&ancel"), DRAG_CANCEL, 3 ); + action = menu.exec( TQCursor::pos(), 0 ); } else action = DRAG_MOVE; diff --git a/kmail/folderutil.cpp b/kmail/folderutil.cpp new file mode 100644 index 000000000..8e65fff27 --- /dev/null +++ b/kmail/folderutil.cpp @@ -0,0 +1,113 @@ +/* Copyright 2009 Klarälvdalens Datakonsult AB + + 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) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 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, see . +*/ +#include "folderutil.h" + +#include "kmfolder.h" +#include "kmfolderimap.h" +#include "kmfoldercachedimap.h" +#include "kmfoldermgr.h" + +#include + +using namespace KMail; +using namespace FolderUtil; + +KMFolder *KMail::FolderUtil::createSubFolder( KMFolder *parentFolder, KMFolderDir *parentDir, + const TQString &folderName, const TQString &namespaceName, + KMFolderType localFolderType ) +{ + KMFolder *newFolder = 0; + + if ( parentFolder && parentFolder->folderType() == KMFolderTypeImap ) { + KMFolderImap* selectedStorage = static_cast( parentFolder->storage() ); + KMAcctImap *anAccount = selectedStorage->account(); + // check if a connection is available BEFORE creating the folder + if (anAccount->makeConnection() == ImapAccountBase::Connected) { + newFolder = kmkernel->imapFolderMgr()->createFolder( folderName, false, KMFolderTypeImap, parentDir ); + if ( newFolder ) { + TQString imapPath, parent; + if ( !namespaceName.isEmpty() ) { + // create folder with namespace + parent = anAccount->addPathToNamespace( namespaceName ); + imapPath = anAccount->createImapPath( parent, folderName ); + } else { + imapPath = anAccount->createImapPath( selectedStorage->imapPath(), folderName ); + } + KMFolderImap* newStorage = static_cast( newFolder->storage() ); + selectedStorage->createFolder(folderName, parent); // create it on the server + newStorage->initializeFrom( selectedStorage, imapPath, TQString::null ); + static_cast(parentFolder->storage())->setAccount( selectedStorage->account() ); + return newFolder; + } + } + } else if ( parentFolder && parentFolder->folderType() == KMFolderTypeCachedImap ) { + newFolder = kmkernel->dimapFolderMgr()->createFolder( folderName, false, KMFolderTypeCachedImap, + parentDir ); + if ( newFolder ) { + KMFolderCachedImap* selectedStorage = static_cast( parentFolder->storage() ); + KMFolderCachedImap* newStorage = static_cast( newFolder->storage() ); + newStorage->initializeFrom( selectedStorage ); + if ( !namespaceName.isEmpty() ) { + // create folder with namespace + TQString path = selectedStorage->account()->createImapPath( + namespaceName, folderName ); + newStorage->setImapPathForCreation( path ); + } + return newFolder; + } + } else { + // local folder + Q_ASSERT( localFolderType == KMFolderTypeMaildir || localFolderType == KMFolderTypeMbox ); + newFolder = kmkernel->folderMgr()->createFolder( folderName, false, localFolderType, + parentDir ); + return newFolder; + } + + return newFolder; +} + +void KMail::FolderUtil::deleteFolder( KMFolder *folderToDelete, TQWidget *parent ) +{ + if ( folderToDelete->hasAccounts() ) { + // this folder has an account, so we need to change that to the inbox + for ( AccountList::Iterator it (folderToDelete->acctList()->begin() ), + end( folderToDelete->acctList()->end() ); it != end; ++it ) { + (*it)->setFolder( kmkernel->inboxFolder() ); + KMessageBox::information(parent, + i18n("The folder you deleted was associated with the account " + "%1 which delivered mail into it. The folder the account " + "delivers new mail into was reset to the main Inbox folder.").arg( (*it)->name())); + } + } + if (folderToDelete->folderType() == KMFolderTypeImap) + kmkernel->imapFolderMgr()->remove(folderToDelete); + else if (folderToDelete->folderType() == KMFolderTypeCachedImap) { + // Deleted by user -> tell the account (see KMFolderCachedImap::listDirectory2) + KMFolderCachedImap* storage = static_cast( folderToDelete->storage() ); + KMAcctCachedImap* acct = storage->account(); + if ( acct ) + acct->addDeletedFolder( folderToDelete ); + + kmkernel->dimapFolderMgr()->remove(folderToDelete); + } + else if (folderToDelete->folderType() == KMFolderTypeSearch) + kmkernel->searchFolderMgr()->remove(folderToDelete); + else + kmkernel->folderMgr()->remove(folderToDelete); +} diff --git a/kmail/folderutil.h b/kmail/folderutil.h new file mode 100644 index 000000000..fbb4d0697 --- /dev/null +++ b/kmail/folderutil.h @@ -0,0 +1,65 @@ +/* Copyright 2009 Klarälvdalens Datakonsult AB + + 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) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 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, see . +*/ +#ifndef FOLDERUTIL_H +#define FOLDERUTIL_H + +#include "kmfoldertype.h" + +class KMFolder; +class KMFolderDir; +class TQString; +class TQWidget; + +namespace KMail +{ + +namespace FolderUtil +{ + +/** + * Low-level function to create a subfolder for a folder of any kind. + * + * @param parentFolder parent folder of the folder that should be created. Can be 0 in case of + * local folders + * @param parentDir parent folder directory, which should be the folder directory of parentFolder + * @param folderName the name the newly created folder should have + * @param namespaceName for (d)IMAP folders, the namespace the new folder should be in. Can be empty. + * @param localFolderType for local folders, this determines if the folder should be MBOX or maildir + * + * @return the newly created folder or 0 in case an error occured + */ +KMFolder *createSubFolder( KMFolder *parentFolder, KMFolderDir *parentDir, + const TQString &folderName, const TQString &namespaceName, + KMFolderType localFolderType ); + +/** + * Deletes a folder and all its subfolders. + * Handles all types of folders correctly, as well as folders with accounts + * + * @param folderToDelete the folder which is going to be deleted + * @param parent the parent widget, which is used when displaying a messagebox, + * which happens when removing a folder with an associated account + */ +void deleteFolder( KMFolder *folderToDelete, TQWidget *parent ); + +} + +} + +#endif diff --git a/kmail/globalsettings_base.kcfgc b/kmail/globalsettings_base.kcfgc index 7fbfbaf8a..3ca476056 100644 --- a/kmail/globalsettings_base.kcfgc +++ b/kmail/globalsettings_base.kcfgc @@ -4,4 +4,4 @@ Mutators=true Singleton=true ItemAccessors=true SetUserTexts=true -IncludeFiles=templatesconfiguration.h,kmglobal.h,templatesconfiguration_base.h +IncludeFiles=templatesconfiguration.h,kmglobal.h,templatesconfiguration_base.h,objecttreeparser.h diff --git a/kmail/headeritem.cpp b/kmail/headeritem.cpp index f950d35c7..188e05324 100644 --- a/kmail/headeritem.cpp +++ b/kmail/headeritem.cpp @@ -115,6 +115,28 @@ int HeaderItem::msgId() const return mMsgId; } +TQString HeaderItem::to() const +{ + KMHeaders * const headers = static_cast( listView() ); + KMMsgBase * const msgBase = headers->folder()->getMsgBase( mMsgId ); + if ( msgBase ) { + return msgBase->to(); + } else { + return TQString(); + } +} + +TQString HeaderItem::from() const +{ + KMHeaders * const headers = static_cast( listView() ); + KMMsgBase * const msgBase = headers->folder()->getMsgBase( mMsgId ); + if ( msgBase ) { + return msgBase->from(); + } else { + return TQString(); + } +} + // Return the serial number Q_UINT32 HeaderItem::msgSerNum() const { @@ -295,9 +317,14 @@ const TQPixmap *HeaderItem::pixmap(int col) const // Only merge the attachment icon in if that is configured. if ( headers->paintInfo()->showAttachmentIcon && !headers->paintInfo()->showAttachment && - msgBase->attachmentState() == KMMsgHasAttachment ) + msgBase->attachmentState() == KMMsgHasAttachment ) pixmaps << *KMHeaders::pixAttachment; + // Only merge the invitation icon in if that is configured. + if ( headers->paintInfo()->showInvitationIcon && + msgBase->invitationState() == KMMsgHasInvitation ) + pixmaps << *KMHeaders::pixInvitation; + // Only merge the crypto icons in if that is configured. if ( headers->paintInfo()->showCryptoIcons ) { const TQPixmap *pix; @@ -326,6 +353,10 @@ const TQPixmap *HeaderItem::pixmap(int col) const if ( msgBase->attachmentState() == KMMsgHasAttachment ) return KMHeaders::pixAttachment; } + else if ( col == headers->paintInfo()->invitationCol ) { + if ( msgBase->invitationState() == KMMsgHasInvitation ) + return KMHeaders::pixInvitation; + } else if ( col == headers->paintInfo()->importantCol ) { if ( msgBase->isImportant() ) return KMHeaders::pixFlag; @@ -485,6 +516,10 @@ TQString HeaderItem::generate_key( KMHeaders *headers, TQString s(msg->attachmentState() == KMMsgHasAttachment ? "1" : "0"); return ret + s + sortArrival; } + else if (column == paintInfo->invitationCol) { + TQString s(msg->invitationState() == KMMsgHasInvitation ? "1" : "0"); + return ret + s + sortArrival; + } else if (column == paintInfo->importantCol) { TQString s(msg->isImportant() ? "1" : "0"); return ret + s + sortArrival; @@ -558,6 +593,7 @@ int HeaderItem::compare( TQListViewItem *i, int col, bool ascending ) const if ( ( col == headers->paintInfo()->statusCol ) || ( col == headers->paintInfo()->sizeCol ) || ( col == headers->paintInfo()->attachmentCol ) || + ( col == headers->paintInfo()->invitationCol ) || ( col == headers->paintInfo()->importantCol ) || ( col == headers->paintInfo()->todoCol ) || ( col == headers->paintInfo()->spamHamCol ) || diff --git a/kmail/headeritem.h b/kmail/headeritem.h index 961c99b70..ac46cb959 100644 --- a/kmail/headeritem.h +++ b/kmail/headeritem.h @@ -179,6 +179,9 @@ public: /** Return the msgId of the message associated with this item. */ int msgId() const; + TQString to() const; + TQString from() const; + // Return the serial number of the message associated with this item; Q_UINT32 msgSerNum() const; diff --git a/kmail/headerlistquicksearch.cpp b/kmail/headerlistquicksearch.cpp index 9f20b7e80..69ac5b1f7 100644 --- a/kmail/headerlistquicksearch.cpp +++ b/kmail/headerlistquicksearch.cpp @@ -69,6 +69,7 @@ HeaderListQuickSearch::HeaderListQuickSearch( TQWidget *parent, TQLabel *label = new TQLabel( i18n("Stat&us:"), parent, "kde toolbar widget" ); mStatusCombo = new TQComboBox( parent, "quick search status combo box" ); + mStatusCombo->setSizeLimit( 12 ); mStatusCombo->insertItem( SmallIcon( "run" ), i18n("Any Status") ); insertStatus( StatusUnread ); @@ -78,6 +79,7 @@ HeaderListQuickSearch::HeaderListQuickSearch( TQWidget *parent, insertStatus( StatusForwarded ); insertStatus( StatusToDo ); insertStatus( StatusHasAttachment ); + insertStatus( StatusInvitation ); insertStatus( StatusWatched ); insertStatus( StatusIgnored ); mStatusCombo->setCurrentItem( 0 ); @@ -91,7 +93,7 @@ HeaderListQuickSearch::HeaderListQuickSearch( TQWidget *parent, 0, i18n( "Open Full Search" ) ); connect( btn, TQT_SIGNAL( clicked() ), TQT_SIGNAL( requestFullSearch() ) ); - /* Disable the signal connected by KListViewSearchLine since it will call + /* Disable the signal connected by KListViewSearchLine since it will call * itemAdded during KMHeaders::readSortOrder() which will in turn result * in getMsgBaseForItem( item ) wanting to access items which are no longer * there. Rather rely on KMHeaders::msgAdded and its signal. */ @@ -149,6 +151,17 @@ bool HeaderListQuickSearch::itemMatches(const TQListViewItem *item, const TQStri if ( !msg || ! ( msg->status() & mStatus ) ) return false; } + + // The full email address is not visible, but we still want it to be searchable. + // KListViewSearchLine::itemMatches() only searches in visible columns. + const HeaderItem *headerItem = static_cast( item ); + if ( headerItem->from().lower().contains( s.lower() ) ) { + return true; + } + if ( headerItem->to().lower().contains( s.lower() ) ) { + return true; + } + return KListViewSearchLine::itemMatches(item, s); } diff --git a/kmail/headerstyle.cpp b/kmail/headerstyle.cpp index 9e6e63156..29046112a 100644 --- a/kmail/headerstyle.cpp +++ b/kmail/headerstyle.cpp @@ -118,6 +118,7 @@ namespace KMail { TQString BriefHeaderStyle::format( const KMMessage * message, const HeaderStrategy * strategy, const TQString & vCardName, bool printing, bool topLevel ) const { + Q_UNUSED( topLevel ); if ( !message ) return TQString::null; if ( !strategy ) strategy = HeaderStrategy::brief(); @@ -216,6 +217,7 @@ namespace KMail { TQString PlainHeaderStyle::format( const KMMessage * message, const HeaderStrategy * strategy, const TQString & vCardName, bool printing, bool topLevel ) const { + Q_UNUSED( topLevel ); if ( !message ) return TQString::null; if ( !strategy ) strategy = HeaderStrategy::rich(); @@ -416,6 +418,7 @@ namespace KMail { TQString FancyHeaderStyle::format( const KMMessage * message, const HeaderStrategy * strategy, const TQString & vCardName, bool printing, bool topLevel ) const { + Q_UNUSED( topLevel ); if ( !message ) return TQString::null; if ( !strategy ) strategy = HeaderStrategy::rich(); @@ -785,23 +788,10 @@ namespace KMail { // reverse colors for encapsulated if( !topLevel ){ activeColorDark = activeColor.dark(50); - fontColor = qApp->palette().active().text(); - linkColor = ""; + fontColor = TQColor(TQt::black); + linkColor = "class =\"black\""; } - TQStringList headerParts; - if( strategy->showHeader( "to" ) ) - headerParts << KMMessage::emailAddrAsAnchor( message->to(), false, linkColor ); - - if ( strategy->showHeader( "cc" ) && !message->cc().isEmpty() ) - headerParts << i18n("CC: ") + KMMessage::emailAddrAsAnchor( message->cc(), true, linkColor ); - - if ( strategy->showHeader( "bcc" ) && !message->bcc().isEmpty() ) - headerParts << i18n("BCC: ") + KMMessage::emailAddrAsAnchor( message->bcc(), true, linkColor ); - - // remove all empty (modulo whitespace) entries and joins them via ", \n" - TQString headerPart = " " + headerParts.grep( TQRegExp( "\\S" ) ).join( ", " ); - // Prepare the date string (when printing always use the localized date) TQString dateString; if( printing ) { @@ -822,12 +812,12 @@ namespace KMail { if(topLevel) headerStr += "
 
" + "width: 10px; min-height: 100%;\"> " "
 
"; + "width: 10px; min-height: 100%;\"> "; headerStr += "" - "
"+dateString+"
" + "
"+dateString+"
" // #0057ae " \n" " \n" @@ -836,8 +826,15 @@ namespace KMail { " \n" " \n" " \n" - "
\n" - " \n"; + "
\n"; + + headerStr += + "
\n" + " \n" + "
\n"; + + headerStr += + " \n"; // subject //strToHtml( message->subject() ) @@ -845,7 +842,7 @@ namespace KMail { headerStr += " \n" " \n" - " \n" + " \n" " \n"; } @@ -858,7 +855,7 @@ namespace KMail { TQString fromPart = KMMessage::emailAddrAsAnchor( fromStr, true, linkColor ); if ( !vCardName.isEmpty() ) fromPart += "  " + i18n("[vCard]") + ""; - //TDDO strategy date + //TODO strategy date //if ( strategy->showHeader( "date" ) ) headerStr += " \n" @@ -867,14 +864,35 @@ namespace KMail { " "; } - // to, cc, bcc - headerStr += + // to line + if( strategy->showHeader( "to" ) ) + headerStr += + " " + " " + " " + " \n"; + + // cc line, if any + if ( strategy->showHeader( "cc" ) && !message->cc().isEmpty() ) + headerStr += " " - " " - " " + " " - " "; + " \n"; + + // bcc line, if any + if ( strategy->showHeader( "bcc" ) && !message->bcc().isEmpty() ) + headerStr += + " " + " " + " " + " \n"; // header-bottom headerStr += @@ -893,25 +911,27 @@ namespace KMail { // kmail icon if(topLevel) { - headerStr += - "
\n" - "\n" - "
\n"; // attachments headerStr += - "
" + "
" "
" "
\n"; } - headerStr += "
"; + if ( printing ) { + //provide a bit more left padding when printing + //kolab/issue3254 (printed mail cut at the left side) + headerStr += "
"; + } else { + headerStr += "
"; + } - // TODO - // spam status - // ### iterate over the rest of strategy->headerToDisplay() (or - // ### all headers if DefaultPolicy == Display) (elsewhere, too) - return headerStr; + // TODO + // spam status + // ### iterate over the rest of strategy->headerToDisplay() (or + // ### all headers if DefaultPolicy == Display) (elsewhere, too) + return headerStr; } // ##################### diff --git a/kmail/identitydialog.cpp b/kmail/identitydialog.cpp index c0a1952d7..22b5a4c81 100644 --- a/kmail/identitydialog.cpp +++ b/kmail/identitydialog.cpp @@ -48,6 +48,7 @@ using KMail::FolderRequester; #include "kmfolder.h" #include "templatesconfiguration.h" #include "templatesconfiguration_kfg.h" +#include "simplestringlisteditor.h" // other kdepim headers: // libkdepim @@ -76,6 +77,8 @@ using KMail::FolderRequester; #include #include #include +#include +#include // other headers: #include @@ -107,7 +110,7 @@ namespace KMail { tab = new TQWidget( tabWidget ); tabWidget->addTab( tab, i18n("&General") ); - glay = new TQGridLayout( tab, 4, 2, marginHint(), spacingHint() ); + glay = new TQGridLayout( tab, 5, 2, marginHint(), spacingHint() ); glay->setRowStretch( 3, 1 ); glay->setColStretch( 1, 1 ); @@ -140,19 +143,40 @@ namespace KMail { TQWhatsThis::add( mOrganizationEdit, msg ); // "Email Address" line edit and label: - // (row 3: spacer) ++row; mEmailEdit = new KLineEdit( tab ); glay->addWidget( mEmailEdit, row, 1 ); label = new TQLabel( mEmailEdit, i18n("&Email address:"), tab ); glay->addWidget( label, row, 0 ); msg = i18n("

Email address

" - "

This field should have your full email address.

" + "

This field should have your full email address

" + "

This address is the primary one, used for all outgoing mail. " + "If you have more than one address, either create a new identity, " + "or add additional alias addresses in the field below.

" "

If you leave this blank, or get it wrong, people " "will have trouble replying to you.

"); TQWhatsThis::add( label, msg ); TQWhatsThis::add( mEmailEdit, msg ); + // "Email Aliases" string list edit and label: + ++row; + mAliasEdit = new SimpleStringListEditor( tab ); + glay->addMultiCellWidget( mAliasEdit, row, row+1, 1, 1 ); + label = new TQLabel( mAliasEdit, i18n("Email a&liases:"), tab ); + glay->addWidget( label, row, 0, TQt::AlignTop ); + msg = i18n("

Email aliases

" + "

This field contains alias addresses that should also " + "be considered as belonging to this identity (as opposed " + "to representing a different identity).

" + "

Example:

" + "
"+message->subject()+""+message->subject()+"
" + i18n("To: ") + "" + + KMMessage::emailAddrAsAnchor( message->to(), false, linkColor ) + + "
"+i18n("To: ")+"" - +headerPart+ + " " + i18n("CC: ") + "" + + KMMessage::emailAddrAsAnchor( message->cc(), false, linkColor ) + "
" + i18n("BCC: ") + "" + + KMMessage::emailAddrAsAnchor( message->bcc(), false, linkColor ) + + "
" + "" + "" + "
Primary address:first.last@example.org
Aliases:first@example.org
last@example.org
" + "

Type one alias address per line.

"); + TQWhatsThis::add( label, msg ); + TQWhatsThis::add( mAliasEdit, msg ); + // // Tab Widget: Cryptography // @@ -499,6 +523,15 @@ void IdentityDialog::slotOk() { return; } + const TQStringList aliases = mAliasEdit->stringList(); + for ( TQStringList::const_iterator it = aliases.begin(), end = aliases.end() ; it != end ; ++it ) { + if ( !isValidSimpleEmailAddress( *it ) ) { + TQString errorMsg( simpleEmailAddressErrorMsg()); + KMessageBox::sorry( this, errorMsg, i18n("Invalid Email Alias \"%1\"").arg( *it ) ); + return; + } + } + if ( !validateAddresses( mReplyToEdit->text().stripWhiteSpace() ) ) { return; } @@ -584,7 +617,8 @@ void IdentityDialog::slotOk() { // "General" tab: mNameEdit->setText( ident.fullName() ); mOrganizationEdit->setText( ident.organization() ); - mEmailEdit->setText( ident.emailAddr() ); + mEmailEdit->setText( ident.primaryEmailAddress() ); + mAliasEdit->setStringList( ident.emailAliases() ); // "Cryptography" tab: mPGPSigningKeyRequester->setFingerprint( ident.pgpSigningKey() ); @@ -652,7 +686,9 @@ void IdentityDialog::slotOk() { ident.setFullName( mNameEdit->text() ); ident.setOrganization( mOrganizationEdit->text() ); TQString email = mEmailEdit->text(); - ident.setEmailAddr( email ); + ident.setPrimaryEmailAddress( email ); + const TQStringList aliases = mAliasEdit->stringList(); + ident.setEmailAliases( aliases ); // "Cryptography" tab: ident.setPGPSigningKey( mPGPSigningKeyRequester->fingerprint().latin1() ); ident.setPGPEncryptionKey( mPGPEncryptionKeyRequester->fingerprint().latin1() ); diff --git a/kmail/identitydialog.h b/kmail/identitydialog.h index 20bb55a70..f8e71073c 100644 --- a/kmail/identitydialog.h +++ b/kmail/identitydialog.h @@ -40,6 +40,7 @@ class TQCheckBox; class TQComboBox; class TQString; class TQStringList; +class SimpleStringListEditor; class TemplatesConfiguration; class KPushButton; namespace Kleo { @@ -87,6 +88,7 @@ namespace KMail { TQLineEdit *mNameEdit; TQLineEdit *mOrganizationEdit; TQLineEdit *mEmailEdit; + SimpleStringListEditor *mAliasEdit; // "cryptography" tab: TQWidget *mCryptographyTab; Kleo::SigningKeyRequester *mPGPSigningKeyRequester; diff --git a/kmail/imapaccountbase.cpp b/kmail/imapaccountbase.cpp index 778403988..b0f78c5aa 100644 --- a/kmail/imapaccountbase.cpp +++ b/kmail/imapaccountbase.cpp @@ -198,6 +198,7 @@ namespace KMail { setOnlyLocallySubscribedFolders( config.readBoolEntry( "locally-subscribed-folders", false ) ); setLoadOnDemand( config.readBoolEntry( "loadondemand", false ) ); setListOnlyOpenFolders( config.readBoolEntry( "listOnlyOpenFolders", false ) ); + mCapabilities = config.readListEntry( "capabilities", TQStringList() ); // read namespaces nsMap map; TQStringList list = config.readListEntry( TQString::number( PersonalNS ) ); @@ -237,6 +238,7 @@ namespace KMail { config.writeEntry( "locally-subscribed-folders", onlyLocallySubscribedFolders() ); config.writeEntry( "loadondemand", loadOnDemand() ); config.writeEntry( "listOnlyOpenFolders", listOnlyOpenFolders() ); + config.writeEntry( "capabilities", mCapabilities ); TQString data; for ( nsMap::Iterator it = mNamespaces.begin(); it != mNamespaces.end(); ++it ) { if ( !it.data().isEmpty() ) { @@ -357,7 +359,7 @@ namespace KMail { } //----------------------------------------------------------------------------- - void ImapAccountBase::changeSubscription( bool subscribe, const TQString& imapPath ) + void ImapAccountBase::changeSubscription( bool subscribe, const TQString& imapPath, bool quiet ) { // change the subscription of the folder KURL url = getUrl(); @@ -380,6 +382,7 @@ namespace KMail { // a bit of a hack to save one slot if (subscribe) jd.onlySubscribed = true; else jd.onlySubscribed = false; + jd.quiet = quiet; insertJob(job, jd); connect(job, TQT_SIGNAL(result(KIO::Job *)), @@ -396,7 +399,9 @@ namespace KMail { TQString path = static_cast(job)->url().path(); if (job->error()) { - handleJobError( job, i18n( "Error while trying to subscribe to %1:" ).arg( path ) + '\n' ); + if ( !(*it).quiet ) + handleJobError( job, i18n( "Error while trying to subscribe to %1:" ).arg( path ) + '\n' ); + emit subscriptionChangeFailed( job->errorString() ); // ## emit subscriptionChanged here in case anyone needs it to support continue/cancel } else @@ -416,9 +421,9 @@ namespace KMail { // don't even allow removing one's own admin permission, so this code won't hurt either). if ( imapPath == "/INBOX/" ) { if ( parent->folderType() == KMFolderTypeImap ) - static_cast( parent->storage() )->setUserRights( ACLJobs::All ); + static_cast( parent->storage() )->setUserRights( ACLJobs::All, ACLJobs::Ok ); else if ( parent->folderType() == KMFolderTypeCachedImap ) - static_cast( parent->storage() )->setUserRights( ACLJobs::All ); + static_cast( parent->storage() )->setUserRights( ACLJobs::All, ACLJobs::Ok ); emit receivedUserRights( parent ); // warning, you need to connect first to get that one return; } @@ -452,12 +457,15 @@ namespace KMail { #ifndef NDEBUG //kdDebug(5006) << "User Rights: " << ACLJobs::permissionsToString( job->permissions() ) << endl; #endif - // Store the permissions - if ( folder->folderType() == KMFolderTypeImap ) - static_cast( folder->storage() )->setUserRights( job->permissions() ); - else if ( folder->folderType() == KMFolderTypeCachedImap ) - static_cast( folder->storage() )->setUserRights( job->permissions() ); } + // Store the permissions + if ( folder->folderType() == KMFolderTypeImap ) + static_cast( folder->storage() )->setUserRights( job->permissions(), + job->error() ? KMail::ACLJobs::FetchFailed : KMail::ACLJobs::Ok ); + else if ( folder->folderType() == KMFolderTypeCachedImap ) + static_cast( folder->storage() )->setUserRights( job->permissions(), + job->error() ? KMail::ACLJobs::FetchFailed : KMail::ACLJobs::Ok ); + if (mSlave) removeJob(job); emit receivedUserRights( folder ); } @@ -802,7 +810,7 @@ namespace KMail { //----------------------------------------------------------------------------- TQString ImapAccountBase::delimiterForNamespace( const TQString& prefix ) { - kdDebug(5006) << "delimiterForNamespace " << prefix << endl; + //kdDebug(5006) << "delimiterForNamespace " << prefix << endl; // try to match exactly if ( mNamespaceToDelimiter.contains(prefix) ) { return mNamespaceToDelimiter[prefix]; @@ -826,7 +834,7 @@ namespace KMail { return mNamespaceToDelimiter[""]; } // well, we tried - kdDebug(5006) << "delimiterForNamespace - not found" << endl; + //kdDebug(5006) << "delimiterForNamespace - not found" << endl; return TQString::null; } @@ -893,17 +901,17 @@ namespace KMail { bool readOnly = false; if (it != mapJobData.end()) { const KMFolder * const folder = (*it).parent; - assert(folder); + if( !folder ) return _error; const KMFolderCachedImap * const imap = dynamic_cast( folder->storage() ); if ( imap ) { - quotaAsString = imap->quotaInfo().toString(); + quotaAsString = imap->quotaInfo().toString(); } readOnly = folder->isReadOnly(); } error = i18n("The folder is too close to its quota limit. (%1)").arg( quotaAsString ); if ( readOnly ) { error += i18n("\nSince you do not have write privileges on this folder, " - "please ask the owner of the folder to free up some space in it."); + "please ask the owner of the folder to free up some space in it."); } return error; } @@ -1015,12 +1023,12 @@ namespace KMail { } //----------------------------------------------------------------------------- - void ImapAccountBase::processNewMailSingleFolder(KMFolder* folder) + void ImapAccountBase::processNewMailInFolder( KMFolder* folder, FolderListType type /*= Single*/ ) { if ( mFoldersQueuedForChecking.contains( folder ) ) return; - mFoldersQueuedForChecking.append(folder); - mCheckingSingleFolder = true; + mFoldersQueuedForChecking.append( folder ); + mCheckingSingleFolder = ( type == Single ); if ( checkingMail() ) { disconnect( this, TQT_SIGNAL( finishedCheck( bool, CheckStatus ) ), diff --git a/kmail/imapaccountbase.h b/kmail/imapaccountbase.h index 26d7a4b01..39fb33b69 100644 --- a/kmail/imapaccountbase.h +++ b/kmail/imapaccountbase.h @@ -187,8 +187,10 @@ namespace KMail { * Subscribe (@p subscribe = TRUE) / Unsubscribe the folder * identified by @p imapPath. * Emits subscriptionChanged signal on success. + * Emits subscriptionChangeFailed signal when it fails. + * @param quiet if false, an error message will be displayed if the job fails. */ - void changeSubscription(bool subscribe, const TQString& imapPath); + void changeSubscription(bool subscribe, const TQString& imapPath, bool quiet = false ); /** * Returns whether the account is locally subscribed to the @@ -252,9 +254,10 @@ namespace KMail { virtual void cancelMailCheck(); /** - * Init a new-mail-check for a single folder + * Init a new-mail-check for a single folder, and optionally its subfolders. */ - void processNewMailSingleFolder(KMFolder* folder); + enum FolderListType { Single, Recursive }; + void processNewMailInFolder( KMFolder* folder, FolderListType type = Single ); /** * Return true if we are processing a mailcheck for a single folder @@ -323,7 +326,7 @@ namespace KMail { /** * Returns the root folder of this account */ - virtual FolderStorage* const rootFolder() const = 0; + virtual FolderStorage* rootFolder() const = 0; /** * Progress item for listDir @@ -585,6 +588,12 @@ namespace KMail { */ void subscriptionChanged(const TQString& imapPath, bool subscribed); + /** + * Emitted when changeSubscription() failed. + * @param errorMessage the error message that contains the reason for the failure + */ + void subscriptionChangeFailed( const TQString &errorMessage ); + /** * Emitted upon completion of the job for setting the status for a group of UIDs, * as a result of a setImapStatus call. @@ -595,7 +604,8 @@ namespace KMail { /** * Emitted when the get-user-rights job is done, * as a result of a getUserRights call. - * Use userRights() to retrieve them, they will still be on 0 if the job failed. + * Use userRights() to retrieve them after using userRightsState() to see if the results are + * valid. */ void receivedUserRights( KMFolder* folder ); diff --git a/kmail/imapjob.cpp b/kmail/imapjob.cpp index 50db423cb..560343689 100644 --- a/kmail/imapjob.cpp +++ b/kmail/imapjob.cpp @@ -420,6 +420,8 @@ void ImapJob::slotGetMessageResult( KIO::Job * job ) // do not know if the message has no attachment or we simply did not load the header if (msg->attachmentState() != KMMsgHasAttachment) msg->updateAttachmentState(); + if (msg->invitationState() != KMMsgHasInvitation) + msg->updateInvitationState(); } } else { kdDebug(5006) << "ImapJob::slotGetMessageResult - got no data for " << mPartSpecifier << endl; diff --git a/kmail/importarchivedialog.cpp b/kmail/importarchivedialog.cpp new file mode 100644 index 000000000..bdae8054d --- /dev/null +++ b/kmail/importarchivedialog.cpp @@ -0,0 +1,107 @@ +/* Copyright 2009 Klarälvdalens Datakonsult AB + + 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) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 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, see . +*/ +#include "importarchivedialog.h" + +#include "kmfolder.h" +#include "folderrequester.h" +#include "kmmainwidget.h" +#include "importjob.h" + +#include +#include +#include + +#include +#include + +using namespace KMail; + +ImportArchiveDialog::ImportArchiveDialog( TQWidget *parent, TQt::WidgetFlags flags ) + : KDialogBase( parent, "import_archive_dialog", false, i18n( "Import Archive" ), + KDialogBase::Ok | KDialogBase::Cancel, + KDialogBase::Ok, true ), + mParentWidget( parent ) +{ + setWFlags( flags ); + TQWidget *mainWidget = new TQWidget( this ); + TQGridLayout *mainLayout = new TQGridLayout( mainWidget ); + mainLayout->setSpacing( KDialog::spacingHint() ); + mainLayout->setMargin( KDialog::marginHint() ); + setMainWidget( mainWidget ); + + int row = 0; + + // TODO: Explaination label + // TODO: Use QFormLayout in KDE4 + // TODO: better label for "Ok" button + + TQLabel *folderLabel = new TQLabel( i18n( "&Folder:" ), mainWidget ); + mainLayout->addWidget( folderLabel, row, 0 ); + mFolderRequester = new FolderRequester( mainWidget, kmkernel->getKMMainWidget()->folderTree() ); + folderLabel->setBuddy( mFolderRequester ); + mainLayout->addWidget( mFolderRequester, row, 1 ); + row++; + + TQLabel *fileNameLabel = new TQLabel( i18n( "&Archive File:" ), mainWidget ); + mainLayout->addWidget( fileNameLabel, row, 0 ); + mUrlRequester = new KURLRequester( mainWidget ); + mUrlRequester->setMode( KFile::LocalOnly ); + mUrlRequester->setFilter( "*.tar *.zip *.tar.gz *.tar.bz2" ); + fileNameLabel->setBuddy( mUrlRequester ); + mainLayout->addWidget( mUrlRequester, row, 1 ); + row++; + + // TODO: what's this, tooltips + + mainLayout->setColStretch( 1, 1 ); + mainLayout->addItem( new TQSpacerItem( 1, 1, TQSizePolicy::Expanding, TQSizePolicy::Expanding ), row, 0 ); + + // Make it a bit bigger, else the folder requester cuts off the text too early + resize( 500, minimumSize().height() ); +} + +void ImportArchiveDialog::setFolder( KMFolder *defaultFolder ) +{ + mFolderRequester->setFolder( defaultFolder ); +} + +void ImportArchiveDialog::slotOk() +{ + if ( !TQFile::exists( mUrlRequester->url() ) ) { + KMessageBox::information( this, i18n( "Please select an archive file that should be imported." ), + i18n( "No archive file selected" ) ); + return; + } + + if ( !mFolderRequester->folder() ) { + KMessageBox::information( this, i18n( "Please select the folder where the archive should be imported to." ), + i18n( "No target folder selected" ) ); + return; + } + + // TODO: check if url is empty. or better yet, disable ok button until file is chosen + + ImportJob *importJob = new KMail::ImportJob( mParentWidget ); + importJob->setFile( mUrlRequester->url() ); + importJob->setRootFolder( mFolderRequester->folder() ); + importJob->start(); + accept(); +} + +#include "importarchivedialog.moc" diff --git a/kmail/importarchivedialog.h b/kmail/importarchivedialog.h new file mode 100644 index 000000000..0b04d9b84 --- /dev/null +++ b/kmail/importarchivedialog.h @@ -0,0 +1,55 @@ +/* Copyright 2009 Klarälvdalens Datakonsult AB + + 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) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 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, see . +*/ +#ifndef IMPORTARCHIVEDIALOG_H +#define IMPORTARCHIVEDIALOG_H + +#include + +class KMFolder; +class KURLRequester; + +namespace KMail +{ +class FolderRequester; + +// TODO: Common base class for ArchiveFolderDialog and ImportArchiveDialog? +class ImportArchiveDialog : public KDialogBase +{ + Q_OBJECT + + public: + + ImportArchiveDialog( TQWidget *parent, TQt::WidgetFlags flags ); + void setFolder( KMFolder *defaultFolder ); + + protected slots: + + /** reimp */ + virtual void slotOk(); + + private: + + TQWidget *mParentWidget; + FolderRequester *mFolderRequester; + KURLRequester *mUrlRequester; +}; + +} + +#endif diff --git a/kmail/importjob.cpp b/kmail/importjob.cpp new file mode 100644 index 000000000..3a7de1981 --- /dev/null +++ b/kmail/importjob.cpp @@ -0,0 +1,396 @@ +/* Copyright 2009 Klarälvdalens Datakonsult AB + + 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) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 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, see . +*/ +#include "importjob.h" + +#include "kmfolder.h" +#include "folderutil.h" +#include "kmfolderdir.h" +#include "kmfolderimap.h" +#include "imapjob.h" + +#include "progressmanager.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace KMail; + +KMail::ImportJob::ImportJob( TQWidget *parentWidget ) + : TQObject( parentWidget ), + mArchive( 0 ), + mRootFolder( 0 ), + mParentWidget( parentWidget ), + mNumberOfImportedMessages( 0 ), + mCurrentFolder( 0 ), + mCurrentMessage( 0 ), + mCurrentMessageFile( 0 ), + mProgressItem( 0 ), + mAborted( false ) +{ +} + +KMail::ImportJob::~ImportJob() +{ + if ( mArchive && mArchive->isOpened() ) { + mArchive->close(); + } + delete mArchive; + mArchive = 0; +} + +void KMail::ImportJob::setFile( const KURL &archiveFile ) +{ + mArchiveFile = archiveFile; +} + +void KMail::ImportJob::setRootFolder( KMFolder *rootFolder ) +{ + mRootFolder = rootFolder; +} + +void KMail::ImportJob::finish() +{ + kdDebug(5006) << "Finished import job." << endl; + mProgressItem->setComplete(); + mProgressItem = 0; + TQString text = i18n( "Importing the archive file '%1' into the folder '%2' succeeded." ) + .arg( mArchiveFile.path() ).arg( mRootFolder->name() ); + text += "\n" + i18n( "1 message was imported.", "%n messages were imported.", mNumberOfImportedMessages ); + KMessageBox::information( mParentWidget, text, i18n( "Import finished." ) ); + deleteLater(); +} + +void KMail::ImportJob::cancelJob() +{ + abort( i18n( "The operation was canceled by the user." ) ); +} + +void KMail::ImportJob::abort( const TQString &errorMessage ) +{ + if ( mAborted ) + return; + + mAborted = true; + TQString text = i18n( "Failed to import the archive into folder '%1'." ).arg( mRootFolder->name() ); + text += "\n" + errorMessage; + if ( mProgressItem ) { + mProgressItem->setComplete(); + mProgressItem = 0; + // The progressmanager will delete it + } + KMessageBox::sorry( mParentWidget, text, i18n( "Importing archive failed." ) ); + deleteLater(); +} + +KMFolder * KMail::ImportJob::createSubFolder( KMFolder *parent, const TQString &folderName, mode_t permissions ) +{ + KMFolder *newFolder = FolderUtil::createSubFolder( parent, parent->child(), folderName, TQString(), + KMFolderTypeMaildir ); + if ( !newFolder ) { + abort( i18n( "Unable to create subfolder for folder '%1'." ).arg( parent->name() ) ); + return 0; + } + else { + newFolder->createChildFolder(); // TODO: Just creating a child folder here is wasteful, only do + // that if really needed. We do it here so we can set the + // permissions + chmod( newFolder->location().latin1(), permissions | S_IXUSR ); + chmod( newFolder->subdirLocation().latin1(), permissions | S_IXUSR ); + // TODO: chown? + // TODO: what about subdirectories like "cur"? + return newFolder; + } +} + +void KMail::ImportJob::enqueueMessages( const KArchiveDirectory *dir, KMFolder *folder ) +{ + const KArchiveDirectory *messageDir = dynamic_cast( dir->entry( "cur" ) ); + if ( messageDir ) { + Messages messagesToQueue; + messagesToQueue.parent = folder; + const TQStringList entries = messageDir->entries(); + for ( uint i = 0; i < entries.size(); i++ ) { + const KArchiveEntry *entry = messageDir->entry( entries[i] ); + Q_ASSERT( entry ); + if ( entry->isDirectory() ) { + kdWarning(5006) << "Unexpected subdirectory in archive folder " << dir->name() << endl; + } + else { + kdDebug(5006) << "Queueing message " << entry->name() << endl; + const KArchiveFile *file = static_cast( entry ); + messagesToQueue.files.append( file ); + } + } + mQueuedMessages.append( messagesToQueue ); + } + else { + kdWarning(5006) << "No 'cur' subdirectory for archive directory " << dir->name() << endl; + } +} + +void KMail::ImportJob::messageAdded() +{ + mNumberOfImportedMessages++; + if ( mCurrentFolder->folderType() == KMFolderTypeMaildir || + mCurrentFolder->folderType() == KMFolderTypeCachedImap ) { + const TQString messageFile = mCurrentFolder->location() + "/cur/" + mCurrentMessage->fileName(); + // TODO: what if the file is not in the "cur" subdirectory? + if ( TQFile::exists( messageFile ) ) { + chmod( messageFile.latin1(), mCurrentMessageFile->permissions() ); + // TODO: changing user/group he requires a bit more work, requires converting the strings + // to uid_t and gid_t + //getpwnam() + //chown( messageFile, + } + else { + kdWarning(5006) << "Unable to change permissions for newly created file: " << messageFile << endl; + } + } + // TODO: Else? + + mCurrentMessage = 0; + mCurrentMessageFile = 0; + TQTimer::singleShot( 0, this, TQT_SLOT( importNextMessage() ) ); +} + +void KMail::ImportJob::importNextMessage() +{ + if ( mAborted ) + return; + + if ( mQueuedMessages.isEmpty() ) { + kdDebug(5006) << "importNextMessage(): Processed all messages in the queue." << endl; + if ( mCurrentFolder ) { + mCurrentFolder->close( "ImportJob" ); + } + mCurrentFolder = 0; + importNextDirectory(); + return; + } + + Messages &messages = mQueuedMessages.front(); + if ( messages.files.isEmpty() ) { + mQueuedMessages.pop_front(); + importNextMessage(); + return; + } + + KMFolder *folder = messages.parent; + if ( folder != mCurrentFolder ) { + kdDebug(5006) << "importNextMessage(): Processed all messages in the current folder of the queue." << endl; + if ( mCurrentFolder ) { + mCurrentFolder->close( "ImportJob" ); + } + mCurrentFolder = folder; + if ( mCurrentFolder->open( "ImportJob" ) != 0 ) { + abort( i18n( "Unable to open folder '%1'." ).arg( mCurrentFolder->name() ) ); + return; + } + kdDebug(5006) << "importNextMessage(): Current folder of queue is now: " << mCurrentFolder->name() << endl; + mProgressItem->setStatus( i18n( "Importing folder %1" ).arg( mCurrentFolder->name() ) ); + } + + mProgressItem->setProgress( ( mProgressItem->progress() + 5 ) ); + + mCurrentMessageFile = messages.files.first(); + Q_ASSERT( mCurrentMessageFile ); + messages.files.removeFirst(); + + mCurrentMessage = new KMMessage(); + mCurrentMessage->fromByteArray( mCurrentMessageFile->data(), true /* setStatus */ ); + int retIndex; + + // If this is not an IMAP folder, we can add the message directly. Otherwise, the whole thing is + // async, for online IMAP. While addMsg() fakes a sync call, we rather do it the async way here + // ourselves, as otherwise the whole thing gets pretty much messed up with regards to folder + // refcounting. Furthermore, the completion dialog would be shown before the messages are actually + // uploaded. + if ( mCurrentFolder->folderType() != KMFolderTypeImap ) { + if ( mCurrentFolder->addMsg( mCurrentMessage, &retIndex ) != 0 ) { + abort( i18n( "Failed to add a message to the folder '%1'." ).arg( mCurrentFolder->name() ) ); + return; + } + messageAdded(); + } + else { + ImapJob *imapJob = new ImapJob( mCurrentMessage, ImapJob::tPutMessage, + dynamic_cast( mCurrentFolder->storage() ) ); + connect( imapJob, TQT_SIGNAL(result(KMail::FolderJob*)), + TQT_SLOT(messagePutResult(KMail::FolderJob*)) ); + imapJob->start(); + } +} + +void KMail::ImportJob::messagePutResult( KMail::FolderJob *job ) +{ + if ( mAborted ) + return; + + if ( job->error() ) { + abort( i18n( "Failed to upload a message to the IMAP server." ) ); + return; + } else { + + KMFolderImap *imap = dynamic_cast( mCurrentFolder->storage() ); + Q_ASSERT( imap ); + + // Ok, we uploaded the message, but we still need to add it to the folder. Use addMsgQuiet(), + // otherwise it will be uploaded again. + imap->addMsgQuiet( mCurrentMessage ); + messageAdded(); + } +} + +// Input: .inbox.directory +// Output: inbox +// Can also return an empty string if this is no valid dir name +static TQString folderNameForDirectoryName( const TQString &dirName ) +{ + Q_ASSERT( dirName.startsWith( "." ) ); + const TQString end = ".directory"; + const int expectedIndex = dirName.length() - end.length(); + if ( dirName.lower().find( end ) != expectedIndex ) + return TQString(); + TQString returnName = dirName.left( dirName.length() - end.length() ); + returnName = returnName.right( returnName.length() - 1 ); + return returnName; +} + +KMFolder* KMail::ImportJob::getOrCreateSubFolder( KMFolder *parentFolder, const TQString &subFolderName, + mode_t subFolderPermissions ) +{ + if ( !parentFolder->createChildFolder() ) { + abort( i18n( "Unable to create subfolder for folder '%1'." ).arg( parentFolder->name() ) ); + return 0; + } + + KMFolder *subFolder = 0; + subFolder = dynamic_cast( parentFolder->child()->hasNamedFolder( subFolderName ) ); + + if ( !subFolder ) { + subFolder = createSubFolder( parentFolder, subFolderName, subFolderPermissions ); + } + return subFolder; +} + +void KMail::ImportJob::importNextDirectory() +{ + if ( mAborted ) + return; + + if ( mQueuedDirectories.isEmpty() ) { + finish(); + return; + } + + Folder folder = mQueuedDirectories.first(); + KMFolder *currentFolder = folder.parent; + mQueuedDirectories.pop_front(); + kdDebug(5006) << "importNextDirectory(): Working on directory " << folder.archiveDir->name() << endl; + + TQStringList entries = folder.archiveDir->entries(); + for ( uint i = 0; i < entries.size(); i++ ) { + const KArchiveEntry *entry = folder.archiveDir->entry( entries[i] ); + Q_ASSERT( entry ); + kdDebug(5006) << "Queueing entry " << entry->name() << endl; + if ( entry->isDirectory() ) { + const KArchiveDirectory *dir = static_cast( entry ); + if ( !dir->name().startsWith( "." ) ) { + + kdDebug(5006) << "Queueing messages in folder " << entry->name() << endl; + KMFolder *subFolder = getOrCreateSubFolder( currentFolder, entry->name(), entry->permissions() ); + if ( !subFolder ) + return; + + enqueueMessages( dir, subFolder ); + } + + // Entry starts with a dot, so we assume it is a subdirectory + else { + + const TQString folderName = folderNameForDirectoryName( entry->name() ); + if ( folderName.isEmpty() ) { + abort( i18n( "Unexpected subdirectory named '%1'." ).arg( entry->name() ) ); + return; + } + KMFolder *subFolder = getOrCreateSubFolder( currentFolder, folderName, entry->permissions() ); + if ( !subFolder ) + return; + + Folder newFolder; + newFolder.archiveDir = dir; + newFolder.parent = subFolder; + kdDebug(5006) << "Enqueueing directory " << entry->name() << endl; + mQueuedDirectories.push_back( newFolder ); + } + } + } + + importNextMessage(); +} + +// TODO: +// BUGS: +// Online IMAP can fail spectacular, for example when cancelling upload +// Online IMAP: Inform that messages are still being uploaded on finish()! +void KMail::ImportJob::start() +{ + Q_ASSERT( mRootFolder ); + Q_ASSERT( mArchiveFile.isValid() ); + + KMimeType::Ptr mimeType = KMimeType::findByURL( mArchiveFile, 0, true /* local file */ ); + if ( !mimeType->patterns().grep( "tar", false /* no case-sensitive */ ).isEmpty() ) + mArchive = new KTar( mArchiveFile.path() ); + else if ( !mimeType->patterns().grep( "zip", false ).isEmpty() ) + mArchive = new KZip( mArchiveFile.path() ); + else { + abort( i18n( "The file '%1' does not appear to be a valid archive." ).arg( mArchiveFile.path() ) ); + return; + } + + if ( !mArchive->open( IO_ReadOnly ) ) { + abort( i18n( "Unable to open archive file '%1'" ).arg( mArchiveFile.path() ) ); + return; + } + + mProgressItem = KPIM::ProgressManager::createProgressItem( + "ImportJob", + i18n( "Importing Archive" ), + TQString(), + true ); + mProgressItem->setUsesBusyIndicator( true ); + connect( mProgressItem, TQT_SIGNAL(progressItemCanceled(KPIM::ProgressItem*)), + this, TQT_SLOT(cancelJob()) ); + + Folder nextFolder; + nextFolder.archiveDir = mArchive->directory(); + nextFolder.parent = mRootFolder; + mQueuedDirectories.push_back( nextFolder ); + importNextDirectory(); +} + +#include "importjob.moc" diff --git a/kmail/importjob.h b/kmail/importjob.h new file mode 100644 index 000000000..ee7a0ac8b --- /dev/null +++ b/kmail/importjob.h @@ -0,0 +1,126 @@ +/* Copyright 2009 Klarälvdalens Datakonsult AB + + 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) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 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, see . +*/ +#ifndef IMPORTJOB_H +#define IMPORTJOB_H + +#include + +#include +#include +#include + +#include + +class TQWidget; +class KArchive; +class KArchiveDirectory; +class KArchiveFile; +class KMFolder; +class KMMessage; + +namespace KPIM +{ + class ProgressItem; +} + +namespace KMail +{ + class FolderJob; + +/** + * Imports an archive that was previously backed up with an BackupJob. + * This job will re-create the folder structure, under the root folder given in setRootFolder(). + * + * The job deletes itself after it finished. + */ +class ImportJob : public TQObject +{ + Q_OBJECT + + public: + + explicit ImportJob( TQWidget *parentWidget = 0 ); + ~ImportJob(); + void start(); + void setFile( const KURL &archiveFile ); + void setRootFolder( KMFolder *rootFolder ); + + private slots: + + void importNextMessage(); + void cancelJob(); + void messagePutResult( KMail::FolderJob *job ); + + private: + + struct Folder + { + KMFolder *parent; + const KArchiveDirectory *archiveDir; + }; + + struct Messages + { + KMFolder *parent; + TQPtrList files; + }; + + void finish(); + void abort( const TQString &errorMessage ); + void queueFolders(); + void importNextDirectory(); + KMFolder* createSubFolder( KMFolder *parent, const TQString &folderName, mode_t permissions ); + KMFolder* getOrCreateSubFolder( KMFolder *parentFolder, const TQString &subFolderName, + mode_t subFolderPermissions ); + void enqueueMessages( const KArchiveDirectory *dir, KMFolder *folder ); + void messageAdded(); + + KArchive *mArchive; + + // The root folder which the user has selected as the folder to which everything should be + // imported + KMFolder *mRootFolder; + + TQWidget *mParentWidget; + KURL mArchiveFile; + int mNumberOfImportedMessages; + + // List of archive folders with their corresponding KMail parent folder that are awaiting + // processing + TQValueList mQueuedDirectories; + + // List of list of messages and their parent folders which are awaiting processing + TQValueList mQueuedMessages; + + // The folder to which we are currently importing messages + KMFolder *mCurrentFolder; + + // The message which is currently being added + KMMessage *mCurrentMessage; + + // The archive file of the current message that is being added + KArchiveFile *mCurrentMessageFile; + + KPIM::ProgressItem *mProgressItem; + bool mAborted; +}; + +} + +#endif diff --git a/kmail/interfaces/bodypartformatter.h b/kmail/interfaces/bodypartformatter.h index b31cb26b7..9bc947f53 100644 --- a/kmail/interfaces/bodypartformatter.h +++ b/kmail/interfaces/bodypartformatter.h @@ -35,7 +35,7 @@ #define __KMAIL_INTERFACE_BODYPARTFORMATTER_H__ namespace KMail { - + class Callback; class HtmlWriter; namespace Interface { @@ -47,7 +47,7 @@ namespace KMail { public: virtual ~BodyPartFormatter() {} - /** + /** @li Ok returned when format() generated some HTML @li NeedContent returned when format() needs the body of the part @li AsIcon returned when the part should be shown iconified @@ -61,7 +61,7 @@ namespace KMail { @return the result code (see above) */ - virtual Result format( BodyPart * part, KMail::HtmlWriter * writer ) const = 0; + virtual Result format( BodyPart * part, KMail::HtmlWriter * writer, Callback &c ) const = 0; }; /** diff --git a/kmail/interfaces/urlhandler.h b/kmail/interfaces/urlhandler.h index fba673d65..38ada083f 100644 --- a/kmail/interfaces/urlhandler.h +++ b/kmail/interfaces/urlhandler.h @@ -57,6 +57,41 @@ namespace KMail { false otherwise. */ virtual bool handleClick( const KURL & url, KMReaderWin * w ) const = 0; + + /** + * Called when shift-clicking the link in the reader. + * @return true if the click was handled by this URLHandler, false otherwise + */ + virtual bool handleShiftClick( const KURL &url, KMReaderWin *window ) const { + Q_UNUSED( url ); + Q_UNUSED( window ); + return false; + } + + /** + * @return should return true if this URLHandler will handle the drag + */ + virtual bool willHandleDrag( const KURL &url, const TQString &imagePath, + KMReaderWin *window ) const { + Q_UNUSED( url ); + Q_UNUSED( window ); + Q_UNUSED( imagePath ); + return false; + } + + /** + * Called when starting a drag with the given URL. + * If the drag is handled, you should create a drag object. + * @return true if the click was handled by this URLHandler, false otherwise + */ + virtual bool handleDrag( const KURL &url, const TQString &imagePath, + KMReaderWin *window ) const { + Q_UNUSED( url ); + Q_UNUSED( window ); + Q_UNUSED( imagePath ); + return false; + } + /** Called when RMB-clicking on a link in the reader. Should show a context menu at the specified point with the specified widget as parent. diff --git a/kmail/isubject.cpp b/kmail/isubject.cpp index 7219dc0e9..d231d365c 100644 --- a/kmail/isubject.cpp +++ b/kmail/isubject.cpp @@ -31,8 +31,15 @@ namespace KMail { void ISubject::notify() { kdDebug(5006) << "ISubject::notify " << mObserverList.size() << endl; - for ( TQValueVector::iterator it = mObserverList.begin() ; it != mObserverList.end() ; ++it ) - (*it)->update( this ); + + // iterate over a copy (to prevent crashes when + // {attach(),detach()} is called from an Observer::update() + const TQValueVector copy = mObserverList; + for ( TQValueVector::const_iterator it = copy.begin() ; it != copy.end() ; ++it ) { + if ( (*it) ) { + (*it)->update( this ); + } + } } } diff --git a/kmail/keyresolver.cpp b/kmail/keyresolver.cpp index e35a7f5f9..3c1a952ca 100644 --- a/kmail/keyresolver.cpp +++ b/kmail/keyresolver.cpp @@ -42,6 +42,7 @@ #include "kcursorsaver.h" #include "kleo_util.h" +#include "stl_util.h" #include #include @@ -103,7 +104,7 @@ static inline bool WithRespectToKeyID( const GpgME::Key & left, const GpgME::Key return qstrcmp( left.keyID(), right.keyID() ) == 0 ; } -static bool ValidTrustedOpenPGPEncryptionKey( const GpgME::Key & key ) { +static bool ValidOpenPGPEncryptionKey( const GpgME::Key & key ) { if ( key.protocol() != GpgME::Context::OpenPGP ) { return false; } @@ -119,9 +120,15 @@ static bool ValidTrustedOpenPGPEncryptionKey( const GpgME::Key & key ) { #endif if ( key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canEncrypt() ) return false; + return true; +} + +static bool ValidTrustedOpenPGPEncryptionKey( const GpgME::Key & key ) { + if ( !ValidOpenPGPEncryptionKey( key ) ) + return false; const std::vector uids = key.userIDs(); for ( std::vector::const_iterator it = uids.begin() ; it != uids.end() ; ++it ) { - if ( !it->isRevoked() && it->validity() != GpgME::UserID::Marginal ) + if ( !it->isRevoked() && it->validity() >= GpgME::UserID::Marginal ) return true; #if 0 else @@ -134,7 +141,7 @@ static bool ValidTrustedOpenPGPEncryptionKey( const GpgME::Key & key ) { return false; } -static bool ValidTrustedSMIMEEncryptionKey( const GpgME::Key & key ) { +static bool ValidSMIMEEncryptionKey( const GpgME::Key & key ) { if ( key.protocol() != GpgME::Context::CMS ) return false; if ( key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canEncrypt() ) @@ -142,6 +149,12 @@ static bool ValidTrustedSMIMEEncryptionKey( const GpgME::Key & key ) { return true; } +static bool ValidTrustedSMIMEEncryptionKey( const GpgME::Key & key ) { + if ( !ValidSMIMEEncryptionKey( key ) ) + return false; + return true; +} + static inline bool ValidTrustedEncryptionKey( const GpgME::Key & key ) { switch ( key.protocol() ) { case GpgME::Context::OpenPGP: @@ -153,6 +166,17 @@ static inline bool ValidTrustedEncryptionKey( const GpgME::Key & key ) { } } +static inline bool ValidEncryptionKey( const GpgME::Key & key ) { + switch ( key.protocol() ) { + case GpgME::Context::OpenPGP: + return ValidOpenPGPEncryptionKey( key ); + case GpgME::Context::CMS: + return ValidSMIMEEncryptionKey( key ); + default: + return false; + } +} + static inline bool ValidSigningKey( const GpgME::Key & key ) { if ( key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canSign() ) return false; @@ -171,14 +195,26 @@ static inline bool NotValidTrustedOpenPGPEncryptionKey( const GpgME::Key & key ) return !ValidTrustedOpenPGPEncryptionKey( key ); } +static inline bool NotValidOpenPGPEncryptionKey( const GpgME::Key & key ) { + return !ValidOpenPGPEncryptionKey( key ); +} + static inline bool NotValidTrustedSMIMEEncryptionKey( const GpgME::Key & key ) { return !ValidTrustedSMIMEEncryptionKey( key ); } +static inline bool NotValidSMIMEEncryptionKey( const GpgME::Key & key ) { + return !ValidSMIMEEncryptionKey( key ); +} + static inline bool NotValidTrustedEncryptionKey( const GpgME::Key & key ) { return !ValidTrustedEncryptionKey( key ); } +static inline bool NotValidEncryptionKey( const GpgME::Key & key ) { + return !ValidEncryptionKey( key ); +} + static inline bool NotValidSigningKey( const GpgME::Key & key ) { return !ValidSigningKey( key ); } @@ -191,6 +227,40 @@ static inline bool NotValidSMIMESigningKey( const GpgME::Key & key ) { return !ValidSMIMESigningKey( key ); } +namespace { + struct ByTrustScore { + static int score( const GpgME::UserID & uid ) { + return uid.isRevoked() || uid.isInvalid() ? -1 : uid.validity() ; + } + bool operator()( const GpgME::UserID & lhs, const GpgME::UserID & rhs ) const { + return score( lhs ) < score( rhs ) ; + } + }; +} + +static std::vector matchingUIDs( const std::vector & uids, const TQString & address ) { + if ( address.isEmpty() ) + return std::vector(); + + std::vector result; + result.reserve( uids.size() ); + for ( std::vector::const_iterator it = uids.begin(), end = uids.end() ; it != end ; ++it ) + // PENDING(marc) check DN for an EMAIL, too, in case of X.509 certs... :/ + if ( const char * email = it->email() ) + if ( *email && TQString::fromUtf8( email ).stripWhiteSpace().lower() == address ) + result.push_back( *it ); + return result; +} + +static GpgME::UserID findBestMatchUID( const GpgME::Key & key, const TQString & address ) { + const std::vector all = key.userIDs(); + if ( all.empty() ) + return GpgME::UserID(); + const std::vector matching = matchingUIDs( all, address.lower() ); + const std::vector & v = matching.empty() ? all : matching ; + return *std::max_element( v.begin(), v.end(), ByTrustScore() ); +} + static TQStringList keysAsStrings( const std::vector& keys ) { TQStringList strings; for ( std::vector::const_iterator it = keys.begin() ; it != keys.end() ; ++it ) { @@ -205,35 +275,40 @@ static TQStringList keysAsStrings( const std::vector& keys ) { return strings; } -static inline std::vector TrustedOrConfirmed( const std::vector & keys ) { +static std::vector trustedOrConfirmed( const std::vector & keys, const TQString & address, bool & canceled ) { + // PENDING(marc) work on UserIDs here? std::vector fishies; std::vector ickies; + std::vector rewookies; std::vector::const_iterator it = keys.begin(); const std::vector::const_iterator end = keys.end(); for ( ; it != end ; it++ ) { - const GpgME::Key key = *it; - assert( ValidTrustedEncryptionKey( key ) ); - const std::vector uids = key.userIDs(); - for ( std::vector::const_iterator it = uids.begin() ; it != uids.end() ; ++it ) { - if ( !it->isRevoked() && it->validity() == GpgME::UserID::Marginal ) { + const GpgME::Key & key = *it; + assert( ValidEncryptionKey( key ) ); + const GpgME::UserID uid = findBestMatchUID( key, address ); + if ( uid.isRevoked() ) { + rewookies.push_back( key ); + } + if ( !uid.isRevoked() && uid.validity() == GpgME::UserID::Marginal ) { fishies.push_back( key ); - break; - } - if ( !it->isRevoked() && it->validity() < GpgME::UserID::Never ) { + } + if ( !uid.isRevoked() && uid.validity() < GpgME::UserID::Never ) { ickies.push_back( key ); - break; - } } } - if ( fishies.empty() && ickies.empty() ) + if ( fishies.empty() && ickies.empty() && rewookies.empty() ) return keys; // if some keys are not fully trusted, let the user confirm their use - TQString msg = i18n("One or more of your configured OpenPGP encryption " - "keys or S/MIME certificates is not fully trusted " - "for encryption."); + TQString msg = address.isEmpty() + ? i18n("One or more of your configured OpenPGP encryption " + "keys or S/MIME certificates is not fully trusted " + "for encryption.") + : i18n("One or more of the OpenPGP encryption keys or S/MIME " + "certificates for recipient \"%1\" is not fully trusted " + "for encryption.").arg(address) ; if ( !fishies.empty() ) { // certificates can't have marginal trust @@ -244,6 +319,10 @@ static inline std::vector TrustedOrConfirmed( const std::vectorrevoked: \n"); + msg += keysAsStrings( rewookies ).join(","); + } if( KMessageBox::warningContinueCancel( 0, msg, i18n("Not Fully Trusted Encryption Keys"), KStdGuiItem::cont(), @@ -251,7 +330,8 @@ static inline std::vector TrustedOrConfirmed( const std::vector(); + canceled = true; + return std::vector(); } namespace { @@ -266,6 +346,20 @@ namespace { const Kleo::CryptoMessageFormat format; }; + + struct IsForFormat : std::unary_function { + explicit IsForFormat( Kleo::CryptoMessageFormat f ) + : protocol( isOpenPGP( f ) ? GpgME::Context::OpenPGP : + isSMIME( f ) ? GpgME::Context::CMS : + /* else */ GpgME::Context::Unknown ) {} + + bool operator()( const GpgME::Key & key ) const { + return key.protocol() == protocol ; + } + + const GpgME::Context::Protocol protocol; + }; + } @@ -334,6 +428,11 @@ public: } void operator()( Item & item ); + template + void process( Container & c ) { + *this = std::for_each( c.begin(), c.end(), *this ); + } + #define make_int_accessor(x) unsigned int num##x() const { return m##x; } make_int_accessor(NoKey) make_int_accessor(NeverEncrypt) @@ -346,6 +445,7 @@ public: #undef make_int_accessor private: EncryptionPreference mDefaultPreference; + bool mNoOps; unsigned int mTotal; unsigned int mNoKey; unsigned int mNeverEncrypt, mUnknownPreference, mAlwaysEncrypt, @@ -353,12 +453,14 @@ private: }; void Kleo::KeyResolver::EncryptionPreferenceCounter::operator()( Item & item ) { + if ( _this ) { if ( item.needKeys ) item.keys = _this->getEncryptionKeys( item.address, true ); if ( item.keys.empty() ) { ++mNoKey; return; } + } switch ( !item.pref ? mDefaultPreference : item.pref ) { #define CASE(x) case Kleo::x: ++m##x; break CASE(NeverEncrypt); @@ -427,13 +529,13 @@ namespace { void EncryptionFormatPreferenceCounter::operator()( const Kleo::KeyResolver::Item & item ) { if ( item.format & (Kleo::InlineOpenPGPFormat|Kleo::OpenPGPMIMEFormat) && std::find_if( item.keys.begin(), item.keys.end(), - ValidTrustedOpenPGPEncryptionKey ) != item.keys.end() ) { + ValidTrustedOpenPGPEncryptionKey ) != item.keys.end() ) { // -= trusted? CASE(OpenPGPMIME); CASE(InlineOpenPGP); } if ( item.format & (Kleo::SMIMEFormat|Kleo::SMIMEOpaqueFormat) && std::find_if( item.keys.begin(), item.keys.end(), - ValidTrustedSMIMEEncryptionKey ) != item.keys.end() ) { + ValidTrustedSMIMEEncryptionKey ) != item.keys.end() ) { // -= trusted? CASE(SMIME); CASE(SMIMEOpaque); } @@ -523,13 +625,108 @@ Kpgp::Result Kleo::KeyResolver::checkKeyNearExpiry( const GpgME::Key & key, cons const GpgME::Subkey subkey = key.subkey(0); if ( d->alreadyWarnedFingerprints.count( subkey.fingerprint() ) ) return Kpgp::Ok; // already warned about this one (and so about it's issuers) - d->alreadyWarnedFingerprints.insert( subkey.fingerprint() ); if ( subkey.neverExpires() ) return Kpgp::Ok; static const double secsPerDay = 24 * 60 * 60; - const int daysTillExpiry = - 1 + int( ::difftime( subkey.expirationTime(), time(0) ) / secsPerDay ); + const double secsTillExpiry = ::difftime( subkey.expirationTime(), time(0) ); + if ( secsTillExpiry <= 0 ) { + const int daysSinceExpiry = 1 + int( -secsTillExpiry / secsPerDay ); + kdDebug() << "Key 0x" << key.shortKeyID() << " expired less than " + << daysSinceExpiry << " days ago" << endl; + const TQString msg = + key.protocol() == GpgME::Context::OpenPGP + ? ( mine ? sign + ? i18n("

Your OpenPGP signing key

%1 (KeyID 0x%2)

" + "

expired less than a day ago.

", + "

Your OpenPGP signing key

%1 (KeyID 0x%2)

" + "

expired %n days ago.

", + daysSinceExpiry ) + : i18n("

Your OpenPGP encryption key

%1 (KeyID 0x%2)

" + "

expired less than a day ago.

", + "

Your OpenPGP encryption key

%1 (KeyID 0x%2)

" + "

expired %n days ago.

", + daysSinceExpiry ) + : i18n("

The OpenPGP key for

%1 (KeyID 0x%2)

" + "

expired less than a day ago.

", + "

The OpenPGP key for

%1 (KeyID 0x%2)

" + "

expired %n days ago.

", + daysSinceExpiry ) ).arg( TQString::fromUtf8( key.userID(0).id() ), + key.shortKeyID() ) + : ( ca + ? ( key.isRoot() + ? ( mine ? sign + ? i18n("

The root certificate

%3

" + "

for your S/MIME signing certificate

%1 (serial number %2)

" + "

expired less than a day ago.

", + "

The root certificate

%3

" + "

for your S/MIME signing certificate

%1 (serial number %2)

" + "

expired %n days ago.

", + daysSinceExpiry ) + : i18n("

The root certificate

%3

" + "

for your S/MIME encryption certificate

%1 (serial number %2)

" + "

expired less than a day ago.

", + "

The root certificate

%3

" + "

for your S/MIME encryption certificate

%1 (serial number %2)

" + "

expired %n days ago.

", + daysSinceExpiry ) + : i18n("

The root certificate

%3

" + "

for S/MIME certificate

%1 (serial number %2)

" + "

expired less than a day ago.

", + "

The root certificate

%3

" + "

for S/MIME certificate

%1 (serial number %2)

" + "

expired %n days ago.

", + daysSinceExpiry ) ) + : ( mine ? sign + ? i18n("

The intermediate CA certificate

%3

" + "

for your S/MIME signing certificate

%1 (serial number %2)

" + "

expired less than a day ago.

", + "

The intermediate CA certificate

%3

" + "

for your S/MIME signing certificate

%1 (serial number %2)

" + "

expired %n days ago.

", + daysSinceExpiry ) + : i18n("

The intermediate CA certificate

%3

" + "

for your S/MIME encryption certificate

%1 (serial number %2)

" + "

expired less than a day ago.

", + "

The intermediate CA certificate

%3

" + "

for your S/MIME encryption certificate

%1 (serial number %2)

" + "

expired %n days ago.

", + daysSinceExpiry ) + : i18n("

The intermediate CA certificate

%3

" + "

for S/MIME certificate

%1 (serial number %2)

" + "

expired less than a day ago.

", + "

The intermediate CA certificate

%3

" + "

for S/MIME certificate

%1 (serial number %2)

" + "

expired %n days ago.

", + daysSinceExpiry ) ) ).arg( Kleo::DN( orig.userID(0).id() ).prettyDN(), + orig.issuerSerial(), + Kleo::DN( key.userID(0).id() ).prettyDN() ) + : ( mine ? sign + ? i18n("

Your S/MIME signing certificate

%1 (serial number %2)

" + "

expired less than a day ago.

", + "

Your S/MIME signing certificate

%1 (serial number %2)

" + "

expired %n days ago.

", + daysSinceExpiry ) + : i18n("

Your S/MIME encryption certificate

%1 (serial number %2)

" + "

expired less than a day ago.

", + "

Your S/MIME encryption certificate

%1 (serial number %2)

" + "

expired %n days ago.

", + daysSinceExpiry ) + : i18n("

The S/MIME certificate for

%1 (serial number %2)

" + "

expired less than a day ago.

", + "

The S/MIME certificate for

%1 (serial number %2)

" + "

expired %n days ago.

", + daysSinceExpiry ) ).arg( Kleo::DN( key.userID(0).id() ).prettyDN(), + key.issuerSerial() ) ); + d->alreadyWarnedFingerprints.insert( subkey.fingerprint() ); + if ( KMessageBox::warningContinueCancel( 0, msg, + key.protocol() == GpgME::Context::OpenPGP + ? i18n("OpenPGP Key Expired" ) + : i18n("S/MIME Certificate Expired" ), + KStdGuiItem::cont(), dontAskAgainName ) == KMessageBox::Cancel ) + return Kpgp::Canceled; + } else { + const int daysTillExpiry = 1 + int( secsTillExpiry / secsPerDay ); kdDebug() << "Key 0x" << key.shortKeyID() << " expires in less than " << daysTillExpiry << " days" << endl; const int threshold = @@ -629,6 +826,7 @@ Kpgp::Result Kleo::KeyResolver::checkKeyNearExpiry( const GpgME::Key & key, cons "

expires in less than %n days.

", daysTillExpiry ) ).arg( Kleo::DN( key.userID(0).id() ).prettyDN(), key.issuerSerial() ) ); + d->alreadyWarnedFingerprints.insert( subkey.fingerprint() ); if ( KMessageBox::warningContinueCancel( 0, msg, key.protocol() == GpgME::Context::OpenPGP ? i18n("OpenPGP Key Expires Soon" ) @@ -637,6 +835,7 @@ Kpgp::Result Kleo::KeyResolver::checkKeyNearExpiry( const GpgME::Key & key, cons == KMessageBox::Cancel ) return Kpgp::Canceled; } + } if ( key.isRoot() ) return Kpgp::Ok; else if ( const char * chain_id = key.chainID() ) { @@ -657,10 +856,10 @@ Kpgp::Result Kleo::KeyResolver::setEncryptToSelfKeys( const TQStringList & finge std::vector keys = lookup( fingerprints ); std::remove_copy_if( keys.begin(), keys.end(), std::back_inserter( d->mOpenPGPEncryptToSelfKeys ), - NotValidTrustedOpenPGPEncryptionKey ); + NotValidTrustedOpenPGPEncryptionKey ); // -= trusted? std::remove_copy_if( keys.begin(), keys.end(), std::back_inserter( d->mSMIMEEncryptToSelfKeys ), - NotValidTrustedSMIMEEncryptionKey ); + NotValidTrustedSMIMEEncryptionKey ); // -= trusted? if ( d->mOpenPGPEncryptToSelfKeys.size() + d->mSMIMEEncryptToSelfKeys.size() < keys.size() ) { @@ -814,6 +1013,20 @@ Kleo::Action Kleo::KeyResolver::checkEncryptionPreferences( bool encryptionReque d->mOpenPGPEncryptToSelfKeys.empty() && d->mSMIMEEncryptToSelfKeys.empty() ) return Impossible; + if ( !encryptionRequested && !mOpportunisticEncyption ) { + // try to minimize crypto ops (including key lookups) by only + // looking up keys when at least one the the encryption + // preferences needs it: + EncryptionPreferenceCounter count( 0, UnknownPreference ); + count.process( d->mPrimaryEncryptionKeys ); + count.process( d->mSecondaryEncryptionKeys ); + if ( !count.numAlwaysEncrypt() && + !count.numAlwaysAskForEncryption() && // this guy might not need a lookup, when declined, but it's too complex to implement that here + !count.numAlwaysEncryptIfPossible() && + !count.numAskWheneverPossible() ) + return DontDoIt; + } + EncryptionPreferenceCounter count( this, mOpportunisticEncyption ? AskWheneverPossible : UnknownPreference ); count = std::for_each( d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(), count ); @@ -859,9 +1072,10 @@ Kpgp::Result Kleo::KeyResolver::resolveAllKeys( bool& signingRequested, bool& en result = resolveEncryptionKeys( signingRequested ); if ( result != Kpgp::Ok ) return result; - if ( signingRequested ) - if ( encryptionRequested ) + if ( signingRequested ) { + if ( encryptionRequested ) { result = resolveSigningKeysForEncryption(); + } else { result = resolveSigningKeysForSigningOnly(); if ( result == Kpgp::Failure ) { @@ -869,6 +1083,7 @@ Kpgp::Result Kleo::KeyResolver::resolveAllKeys( bool& signingRequested, bool& en return Kpgp::Ok; } } + } return result; } @@ -1334,10 +1549,10 @@ Kpgp::Result Kleo::KeyResolver::showKeyApprovalDialog() { std::remove_copy_if( senderKeys.begin(), senderKeys.end(), std::back_inserter( d->mOpenPGPEncryptToSelfKeys ), - NotValidTrustedOpenPGPEncryptionKey ); + NotValidTrustedOpenPGPEncryptionKey ); // -= trusted (see above, too)? std::remove_copy_if( senderKeys.begin(), senderKeys.end(), std::back_inserter( d->mSMIMEEncryptToSelfKeys ), - NotValidTrustedSMIMEEncryptionKey ); + NotValidTrustedSMIMEEncryptionKey ); // -= trusted (see above, too)? return Kpgp::Ok; } @@ -1364,16 +1579,21 @@ std::vector Kleo::KeyResolver::signingKeys( CryptoMessageFormat f ) std::vector Kleo::KeyResolver::selectKeys( const TQString & person, const TQString & msg, const std::vector & selectedKeys ) const { + const bool opgp = containsOpenPGP( mCryptoMessageFormats ); + const bool x509 = containsSMIME( mCryptoMessageFormats ); + Kleo::KeySelectionDialog dlg( i18n("Encryption Key Selection"), - msg, selectedKeys, - Kleo::KeySelectionDialog::ValidEncryptionKeys, + msg, KPIM::getEmailAddress(person), selectedKeys, + Kleo::KeySelectionDialog::ValidEncryptionKeys + & ~(opgp ? 0 : Kleo::KeySelectionDialog::OpenPGPKeys) + & ~(x509 ? 0 : Kleo::KeySelectionDialog::SMIMEKeys), true, true ); // multi-selection and "remember choice" box if ( dlg.exec() != TQDialog::Accepted ) return std::vector(); std::vector keys = dlg.selectedKeys(); keys.erase( std::remove_if( keys.begin(), keys.end(), - NotValidTrustedEncryptionKey ), + NotValidTrustedEncryptionKey ), // -= trusted? keys.end() ); if ( !keys.empty() && dlg.rememberSelection() ) setKeysForAddress( person, dlg.pgpKeyFingerprints(), dlg.smimeFingerprints() ); @@ -1396,70 +1616,80 @@ std::vector Kleo::KeyResolver::getEncryptionKeys( const TQString & p if ( !keys.empty() ) { // Check if all of the keys are trusted and valid encryption keys if ( std::find_if( keys.begin(), keys.end(), - NotValidTrustedEncryptionKey ) != keys.end() ) { + NotValidTrustedEncryptionKey ) != keys.end() ) { // -= trusted? // not ok, let the user select: this is not conditional on !quiet, // since it's a bug in the configuration and the user should be // notified about it as early as possible: keys = selectKeys( person, i18n("if in your language something like " - "'key(s)' isn't possible please " + "'certificate(s)' isn't possible please " "use the plural in the translation", "There is a problem with the " - "encryption key(s) for \"%1\".\n\n" - "Please re-select the key(s) which should " + "encryption certificate(s) for \"%1\".\n\n" + "Please re-select the certificate(s) which should " "be used for this recipient.").arg(person), keys ); } - keys = TrustedOrConfirmed( keys ); + bool canceled = false; + keys = trustedOrConfirmed( keys, address, canceled ); + if ( canceled ) + return std::vector(); if ( !keys.empty() ) return keys; - // hmmm, should we not return the keys in any case here? + // keys.empty() is considered cancel by callers, so go on } } // Now search all public keys for matching keys std::vector matchingKeys = lookup( person ); matchingKeys.erase( std::remove_if( matchingKeys.begin(), matchingKeys.end(), - NotValidTrustedEncryptionKey ), + NotValidEncryptionKey ), matchingKeys.end() ); // if no keys match the complete address look for keys which match // the canonical mail address if ( matchingKeys.empty() ) { matchingKeys = lookup( address ); matchingKeys.erase( std::remove_if( matchingKeys.begin(), matchingKeys.end(), - NotValidTrustedEncryptionKey ), + NotValidEncryptionKey ), matchingKeys.end() ); } // if called with quite == true (from EncryptionPreferenceCounter), we only want to // check if there are keys for this recipients, not (yet) their validity, so // don't show the untrusted encryption key warning in that case + bool canceled = false; if ( !quiet ) - matchingKeys = TrustedOrConfirmed( matchingKeys ); + matchingKeys = trustedOrConfirmed( matchingKeys, address, canceled ); + if ( canceled ) + return std::vector(); if ( quiet || matchingKeys.size() == 1 ) return matchingKeys; // no match until now, or more than one key matches; let the user // choose the key(s) // FIXME: let user get the key from keyserver - return TrustedOrConfirmed( selectKeys( person, + return trustedOrConfirmed( selectKeys( person, matchingKeys.empty() ? i18n("if in your language something like " - "'key(s)' isn't possible please " + "'certificate(s)' isn't possible please " "use the plural in the translation", - "No valid and trusted encryption key was " - "found for \"%1\".\n\n" - "Select the key(s) which should " - "be used for this recipient.").arg(person) + "No valid and trusted encryption certificate was " + "found for \"%1\".

" + "Select the certificate(s) which should " + "be used for this recipient. If there is no suitable certificate in the list " + "you can also search for external certificates by clicking the button: search for external certificates.
") + .arg( TQStyleSheet::escape(person) ) : i18n("if in your language something like " - "'key(s)' isn't possible please " + "'certificate(s)' isn't possible please " "use the plural in the translation", - "More than one key matches \"%1\".\n\n" - "Select the key(s) which should " - "be used for this recipient.").arg(person), - matchingKeys ) ); + "More than one certificate matches \"%1\".\n\n" + "Select the certificate(s) which should " + "be used for this recipient.").arg( TQStyleSheet::escape(person) ), + matchingKeys ), address, canceled ); + // we can ignore 'canceled' here, since trustedOrConfirmed() returns + // an empty vector when canceled == true, and we'd just do the same } @@ -1513,8 +1743,11 @@ void Kleo::KeyResolver::addKeys( const std::vector & items ) { SplitInfo si( it->address ); CryptoMessageFormat f = AutoFormat; for ( unsigned int i = 0 ; i < numConcreteCryptoMessageFormats ; ++i ) { - if ( concreteCryptoMessageFormats[i] & it->format ) { - f = concreteCryptoMessageFormats[i]; + const CryptoMessageFormat fmt = concreteCryptoMessageFormats[i]; + if ( ( fmt & it->format ) && + kdtools::any( it->keys.begin(), it->keys.end(), IsForFormat( fmt ) ) ) + { + f = fmt; break; } } diff --git a/kmail/khtmlparthtmlwriter.cpp b/kmail/khtmlparthtmlwriter.cpp index dfc9fd358..2d29d3c9e 100644 --- a/kmail/khtmlparthtmlwriter.cpp +++ b/kmail/khtmlparthtmlwriter.cpp @@ -49,7 +49,7 @@ namespace KMail { KHtmlPartHtmlWriter::KHtmlPartHtmlWriter( KHTMLPart * part, TQObject * parent, const char * name ) : TQObject( parent, name ), HtmlWriter(), - mHtmlPart( part ), mState( Ended ), mHtmlTimer( 0, "mHtmlTimer" ) + mHtmlPart( part ), mHtmlTimer( 0, "mHtmlTimer" ), mState( Ended ) { assert( part ); connect( &mHtmlTimer, TQT_SIGNAL(timeout()), TQT_SLOT(slotWriteNextHtmlChunk()) ); diff --git a/kmail/kleo_util.h b/kmail/kleo_util.h index b2378e9fa..dc0eb6d2c 100644 --- a/kmail/kleo_util.h +++ b/kmail/kleo_util.h @@ -77,5 +77,12 @@ static inline bool isOpenPGP( Kleo::CryptoMessageFormat f ) { return f == Kleo::InlineOpenPGPFormat || f == Kleo::OpenPGPMIMEFormat ; } +static inline bool containsSMIME( unsigned int f ) { + return f & (Kleo::SMIMEFormat|Kleo::SMIMEOpaqueFormat) ; +} + +static inline bool containsOpenPGP( unsigned int f ) { + return f & (Kleo::OpenPGPMIMEFormat|Kleo::InlineOpenPGPFormat) ; +} #endif // __KDEPIM_KMAIL_KLEO_UTIL_H__ diff --git a/kmail/kmacctcachedimap.cpp b/kmail/kmacctcachedimap.cpp index 2305b0809..b673b5e14 100644 --- a/kmail/kmacctcachedimap.cpp +++ b/kmail/kmacctcachedimap.cpp @@ -179,21 +179,6 @@ void KMAcctCachedImap::cancelMailCheck() } } -//----------------------------------------------------------------------------- -void KMAcctCachedImap::killJobsForItem(KMFolderTreeItem * fti) -{ - TQMap::Iterator it = mapJobData.begin(); - while (it != mapJobData.end()) - { - if (it.data().parent == fti->folder()) - { - killAllJobs(); - break; - } - else ++it; - } -} - // Reimplemented from ImapAccountBase because we only check one folder at a time void KMAcctCachedImap::slotCheckQueuedFolders() { @@ -217,7 +202,12 @@ void KMAcctCachedImap::processNewMail( bool /*interactive*/ ) else { KMFolder* f = mMailCheckFolders.front(); mMailCheckFolders.pop_front(); - processNewMail( static_cast( f->storage() ), false ); + + // Only check mail if the folder really exists, it might have been removed by the sync in + // the meantime. + if ( f ) { + processNewMail( static_cast( f->storage() ), !checkingSingleFolder() ); + } } } @@ -234,7 +224,7 @@ void KMAcctCachedImap::processNewMail( KMFolderCachedImap* folder, mNoopTimer.stop(); // reset namespace todo - if ( folder == mFolder ) { + if ( folder == mFolder && !namespaces().isEmpty() ) { TQStringList nsToList = namespaces()[PersonalNS]; TQStringList otherNSToCheck = namespaces()[OtherUsersNS]; otherNSToCheck += namespaces()[SharedNS]; @@ -361,8 +351,8 @@ void KMAcctCachedImap::invalidateIMAPFolders( KMFolderCachedImap* folder ) TQStringList strList; TQValueList > folderList; kmkernel->dimapFolderMgr()->createFolderList( &strList, &folderList, - folder->folder()->child(), TQString::null, - false ); + folder->folder()->child(), TQString::null, + false ); TQValueList >::Iterator it; mCountLastUnread = 0; mUnreadBeforeCheck.clear(); @@ -374,13 +364,12 @@ void KMAcctCachedImap::invalidateIMAPFolders( KMFolderCachedImap* folder ) // This invalidates the folder completely cfolder->setUidValidity("INVALID"); cfolder->writeUidCache(); - processNewMailSingleFolder( f ); } } folder->setUidValidity("INVALID"); folder->writeUidCache(); - processNewMailSingleFolder( folder->folder() ); + processNewMailInFolder( folder->folder(), Recursive ); } //----------------------------------------------------------------------------- @@ -462,7 +451,7 @@ void KMAcctCachedImap::slotProgressItemCanceled( ProgressItem* ) } } -FolderStorage* const KMAcctCachedImap::rootFolder() const +FolderStorage* KMAcctCachedImap::rootFolder() const { return mFolder; } diff --git a/kmail/kmacctcachedimap.h b/kmail/kmacctcachedimap.h index e8e428204..72ed5c7db 100644 --- a/kmail/kmacctcachedimap.h +++ b/kmail/kmacctcachedimap.h @@ -75,11 +75,6 @@ public: virtual TQString type() const; virtual void processNewMail( bool interactive ); - /** - * Kill all jobs related the the specified folder - */ - void killJobsForItem(KMFolderTreeItem * fti); - /** * Kill the slave if any jobs are active */ @@ -179,7 +174,7 @@ public: /** * Returns the root folder of this account */ - virtual FolderStorage* const rootFolder() const; + virtual FolderStorage* rootFolder() const; /** return if the account passed the annotation test */ bool annotationCheckPassed(){ return mAnnotationCheckPassed;}; diff --git a/kmail/kmacctimap.cpp b/kmail/kmacctimap.cpp index bf96c79a1..4ead745c7 100644 --- a/kmail/kmacctimap.cpp +++ b/kmail/kmacctimap.cpp @@ -547,7 +547,7 @@ void KMAcctImap::slotMailCheckCanceled() } //----------------------------------------------------------------------------- -FolderStorage* const KMAcctImap::rootFolder() const +FolderStorage* KMAcctImap::rootFolder() const { return mFolder; } diff --git a/kmail/kmacctimap.h b/kmail/kmacctimap.h index c69bca899..fac17eebe 100644 --- a/kmail/kmacctimap.h +++ b/kmail/kmacctimap.h @@ -87,7 +87,7 @@ public: /** * Returns the root folder of this account */ - virtual FolderStorage* const rootFolder() const; + virtual FolderStorage* rootFolder() const; /** * Queues a message for automatic filtering diff --git a/kmail/kmacctlocal.cpp b/kmail/kmacctlocal.cpp index b486b5378..d1e15f83a 100644 --- a/kmail/kmacctlocal.cpp +++ b/kmail/kmacctlocal.cpp @@ -220,6 +220,7 @@ bool KMAcctLocal::fetchMsg() msg->setSignatureStateChar( msg->headerField( "X-KMail-SignatureState" ).at(0)); msg->setComplete(true); msg->updateAttachmentState(); + msg->updateInvitationState(); mAddedOk = processNewMsg(msg); diff --git a/kmail/kmaddrbook.cpp b/kmail/kmaddrbook.cpp index b115f54e2..055b29fcc 100644 --- a/kmail/kmaddrbook.cpp +++ b/kmail/kmaddrbook.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #include #include diff --git a/kmail/kmail.kcfg b/kmail/kmail.kcfg index afb228cfe..4192d234f 100644 --- a/kmail/kmail.kcfg +++ b/kmail/kmail.kcfg @@ -24,6 +24,8 @@ + + SelectLastSelected @@ -153,7 +155,7 @@ - 85 + 80 @@ -184,6 +186,12 @@ false + + + When replying to invitations, send the reply comment in way that Microsoft Outlook understands. + false + + When this is checked, you will not see the mail composer window. Instead, all invitation mails are sent automatically. If you want to see the mail before sending it, you can uncheck this option. However, be aware that the text in the composer window is in iCalendar syntax, and you should not try modifying it by hand. @@ -200,15 +208,25 @@ AskForAllButAcceptance - + When this is checked, received invitation emails that have been replied to will be moved to the Trash folder, once the reply has been successfully sent. true - - + + + true + + + + + KMail::ObjectTreeParser::defaultToltecReplacementText() + + + + @@ -253,14 +271,12 @@ 0 - - true - - false + + true @@ -293,10 +309,6 @@ This option enables or disables the search line edit above the message list which can be used to quickly search the information shown in the message list. true - - - false - true @@ -333,6 +345,11 @@ Remember this mail transport, so that it will be used in future composer windows as well. false + + Remember this dictionary, so that it will be used in future composer windows as well. + + false + true @@ -347,8 +364,21 @@ 30 255 + + + true + If the number of recipients is larger than this value, KMail will warn and ask for a confirmation before sending the mail. The warning can be turned off. + + + + 5 + 1 + 100 + If the number of recipients is larger than this value, KMail will warn and ask for a confirmation before sending the mail. The warning can be turned off. + + @@ -436,6 +466,14 @@ true + + + true + + + + true + @@ -515,6 +553,10 @@ + + + false + true diff --git a/kmail/kmailIface.h b/kmail/kmailIface.h index fd47f0485..2338a4c40 100644 --- a/kmail/kmailIface.h +++ b/kmail/kmailIface.h @@ -60,6 +60,19 @@ k_dcop: const TQString &attachParamValue, const TQCString &attachContDisp, const TQCString &attachCharset) = 0; + virtual int openComposer (const TQString &to, const TQString &cc, + const TQString &bcc, const TQString &subject, + const TQString &body, int hidden, + const TQString &attachName, + const TQCString &attachCte, + const TQCString &attachData, + const TQCString &attachType, + const TQCString &attachSubType, + const TQCString &attachParamAttr, + const TQString &attachParamValue, + const TQCString &attachContDisp, + const TQCString &attachCharset, + uint identity) = 0; /** Open composer and return reference to DCOP interface of composer window. If hidden is true, the window will not be shown. If you use that option, it's your responsibility to call the send() function of the composer in @@ -102,6 +115,7 @@ k_dcop: virtual int dcopAddMessage(const TQString & foldername, const KURL & messagefile, const TQString & MsgStatusFlags = TQString()) = 0; + virtual void showImportArchiveDialog() = 0; virtual TQStringList folderList() const =0; virtual DCOPRef getFolder( const TQString& vpath ) =0; @@ -211,7 +225,7 @@ k_dcop_hidden: /** Clears the list of added message ids which is used to filter out duplicates. */ virtual void dcopResetAddMessage() = 0; - + virtual void loadProfile( const TQString& path ) = 0; virtual void saveToProfile( const TQString& path ) const = 0; }; diff --git a/kmail/kmail_config_accounts.desktop b/kmail/kmail_config_accounts.desktop index 4c2c9f92c..c2e58a03e 100644 --- a/kmail/kmail_config_accounts.desktop +++ b/kmail/kmail_config_accounts.desktop @@ -39,7 +39,6 @@ Name[hu]=Fiókok Name[is]=Tengingar Name[it]=Account Name[ja]=アカウント -Name[ka]=ანგარიშები Name[kk]=Тіркелгілері Name[km]=គណនី Name[lt]=Paskyros @@ -64,8 +63,7 @@ Name[ta]=கணக்குகள் Name[tg]=Қайдҳои баҳисобгирӣ Name[tr]=Hesaplar Name[uk]=Рахунки -Name[uz]=Hisoblar -Name[uz@cyrillic]=Ҳисоблар +Name[uz]=Ҳисоблар Name[zh_CN]=账户 Name[zh_TW]=帳號 Comment=Setup for Sending and Receiving Messages @@ -91,7 +89,6 @@ Comment[hu]=Küldési és fogadási beállítások Comment[is]=Uppsetning fyrir sendingu og móttöku af tölvupósti Comment[it]=Impostazioni per spedire e ricevere messaggi Comment[ja]=メッセージを送受信するための設定 -Comment[ka]=შეტყობინებების გაგზავნისა და მიღების კონფიგურაცია Comment[kk]=Хаттарды жіберу мен қабылдауды баптау Comment[km]=រៀបចំ​ដើម្បី​ផ្ញើ និង​ទទួល​សារ Comment[lt]=Laiškų siuntimo ir gavimo sąranka @@ -139,7 +136,6 @@ Keywords[he]=kmail,accounts,דוא"ל, חשבון, חשבונות Keywords[hu]=kmail,azonosítók Keywords[is]=kmail,accounts,tengingar Keywords[it]=kmail,account -Keywords[ka]=kmail,ანგარიშები Keywords[km]=kmail,គណនី Keywords[lt]=kmail,accounts,paskyros Keywords[mk]=kmail,accounts,кпошта,сметка,сметки diff --git a/kmail/kmail_config_appearance.desktop b/kmail/kmail_config_appearance.desktop index 09df7ea4a..3008ea543 100644 --- a/kmail/kmail_config_appearance.desktop +++ b/kmail/kmail_config_appearance.desktop @@ -39,7 +39,6 @@ Name[hu]=Megjelenés Name[is]=Útlit Name[it]=Aspetto Name[ja]=外観 -Name[ka]=იერსახე Name[kk]=Сыртқы көрінісі Name[km]=រូបរាង Name[ko]=모양 @@ -66,8 +65,7 @@ Name[ta]=தோற்றம் Name[tg]=Намуди зоҳирӣ Name[tr]=Görünüm Name[uk]=Вигляд -Name[uz]=Koʻrinishi -Name[uz@cyrillic]=Кўриниши +Name[uz]=Кўриниши Name[zh_CN]=外观 Comment=Customize Visual Appearance Comment[af]=Pasmaak die visuele voorkoms @@ -93,7 +91,6 @@ Comment[hu]=A grafikai megjelenés testreszabása Comment[is]=Stilla útlit Comment[it]=Personalizza l'aspetto Comment[ja]=外観をカスタマイズ -Comment[ka]=ვიზუალური იერსახის დაყენება Comment[kk]=Сыртқы көрінісін ыңғайлау Comment[km]=ប្ដូរ​រូបរាង​មើល​ឃើញ​តាម​បំណង Comment[lt]=Derinti vizualinę išvaizdą @@ -143,7 +140,6 @@ Keywords[hu]=kmail,megjelenés Keywords[is]=kmail,útlit Keywords[it]=kmail,aspetto Keywords[ja]=kmail,外観 -Keywords[ka]=kmail,იერსახე Keywords[km]=kmail,រូបរាង Keywords[lt]=kmail,appearance,išvaizda Keywords[mk]=kmail,appearance,кпошта,појава,изглед,визуелно diff --git a/kmail/kmail_config_composer.desktop b/kmail/kmail_config_composer.desktop index 5c75a1c4c..4cc868bbc 100644 --- a/kmail/kmail_config_composer.desktop +++ b/kmail/kmail_config_composer.desktop @@ -35,7 +35,6 @@ Name[he]=עורך Name[hu]=Szerkesztő Name[it]=Compositore Name[ja]=メール作成 -Name[ka]=წერილების რედაქტორი Name[kk]=Құрастарғыш Name[km]=កម្មវិធី​តែង Name[lt]=Redaktorius @@ -80,7 +79,6 @@ Comment[fr]=Modèles et comportement général Comment[fy]=Sjabloanen en algemien gedrach Comment[gl]=Planteis e Comportamento Xeral Comment[hu]=Sablonok, általános működési jellemzők -Comment[is]=Forsnið & almenn hegðun Comment[it]=Modelli e comportamento generale Comment[ja]=テンプレートと全般的な動作 Comment[kk]=Үлгілер мен Жалпы тәртібі @@ -124,7 +122,6 @@ Keywords[he]=kmail,composer,כתבן Keywords[hu]=kmail,szerkesztő Keywords[is]=kmail,ritill Keywords[it]=kmail,compositore -Keywords[ka]=kmail,წერილების რედაქტორი Keywords[km]=kmail កម្មវិធី​តែង Keywords[lt]=kmail,composer,redaktorius Keywords[mk]=kmail,composer,кпошта,составувач diff --git a/kmail/kmail_config_identity.desktop b/kmail/kmail_config_identity.desktop index 8cf28eb02..fe985db67 100644 --- a/kmail/kmail_config_identity.desktop +++ b/kmail/kmail_config_identity.desktop @@ -37,7 +37,6 @@ Name[hu]=Azonosítók Name[is]=Auðkenni Name[it]=Identità Name[ja]=個人情報 -Name[ka]=პროფილები Name[kk]=Іс-әлпеттері Name[km]=អត្តសញ្ញាណ Name[lt]=Tapatybės @@ -62,8 +61,7 @@ Name[ta]=அடையாளங்கள் Name[tg]=Профилҳо Name[tr]=Kimlikler Name[uk]=Профілі -Name[uz]=Shaxsiyatlar -Name[uz@cyrillic]=Шахсиятлар +Name[uz]=Шахсиятлар Name[zh_CN]=身份 Name[zh_TW]=身份 Comment=Manage Identities @@ -92,7 +90,6 @@ Comment[hu]=Az azonosítók kezelése Comment[is]=Stjórna auðkennum Comment[it]=Gestisce le identità Comment[ja]=個人情報の管理 -Comment[ka]=პროფილების მართვა Comment[kk]=Іс-әлпеттерді басқару Comment[km]=គ្រប់គ្រង​អត្តសញ្ញាណ Comment[lt]=Tvarkyti tapatybes @@ -143,7 +140,6 @@ Keywords[hu]=kmail,azonosító Keywords[is]=kmail,auðkenni Keywords[it]=kmail,identità Keywords[ja]=kmail,個人情報 -Keywords[ka]=kmail,პროფილი Keywords[km]=kmail,អត្តសញ្ញាណ Keywords[lt]=kmail,identity,tapatybė Keywords[mk]=kmail,identity,кпошта,идентитет,идентитети @@ -165,6 +161,5 @@ Keywords[ta]=கேஅஞ்சல்,அடையாளம் Keywords[tg]=kmail,identity,профил Keywords[tr]=kmail,kimlikler Keywords[uk]=kmail,профіль -Keywords[uz]=kmail,shaxsiyat -Keywords[uz@cyrillic]=kmail,шахсият +Keywords[uz]=kmail,шахсият Keywords[zh_CN]=kmail,identity, 身份 diff --git a/kmail/kmail_config_misc.desktop b/kmail/kmail_config_misc.desktop index 6a33c6432..7c1d946fd 100644 --- a/kmail/kmail_config_misc.desktop +++ b/kmail/kmail_config_misc.desktop @@ -36,7 +36,6 @@ Name[hu]=Egyéb Name[is]=Ýmislegt Name[it]=Varie Name[ja]=その他 -Name[ka]=სხვადასხვა Name[kk]=Басқалары Name[km]=ផ្សេងៗ Name[lt]=Įvairūs @@ -62,8 +61,7 @@ Name[ta]=இதர Name[tg]=Ғайра Name[tr]=Çeşitli Name[uk]=Різне -Name[uz]=Har xil -Name[uz@cyrillic]=Ҳар хил +Name[uz]=Ҳар хил Name[zh_CN]=杂项 Name[zh_TW]=其他 Comment=Settings that don't fit elsewhere @@ -89,7 +87,6 @@ Comment[hu]=A máshová nem besorolható beállítások Comment[is]=Stillingar sem passa ekki annars staðar Comment[it]=Impostazioni che non rientrano in altre categorie Comment[ja]=その他の設定 -Comment[ka]=პარამეტრები,რომლებიც სხვას არ ერგება Comment[kk]=Басқа параметрлері Comment[km]=ការ​កំណត់​ដែល​មិន​ត្រូវ​នឹង​កន្លែង​ផ្សេង Comment[lt]=Kiti nustatymai @@ -138,7 +135,6 @@ Keywords[hu]=kmail,egyéb Keywords[is]=kmail,ýmislegt Keywords[it]=kmail,varie Keywords[ja]=kmail,その他 -Keywords[ka]=kmail,სხვადასხვა Keywords[km]=kmail,ផ្សេងៗ Keywords[lt]=kmail,misc,įvairūs Keywords[mk]=kmail,misc,кпошта,разно @@ -161,6 +157,5 @@ Keywords[ta]=கேஅஞ்சல், இதர Keywords[tg]=kmail,misc,ғайра,дигар Keywords[tr]=kmail,çeşitli Keywords[uk]=kmail,різне -Keywords[uz]=kmail,har xil -Keywords[uz@cyrillic]=kmail,ҳар хил +Keywords[uz]=kmail,ҳар хил Keywords[zh_CN]=kmail,misc,杂项 diff --git a/kmail/kmail_config_security.desktop b/kmail/kmail_config_security.desktop index 1b61544e4..4d1a2a402 100644 --- a/kmail/kmail_config_security.desktop +++ b/kmail/kmail_config_security.desktop @@ -38,7 +38,6 @@ Name[hu]=Biztonság Name[is]=Öryggi Name[it]=Sicurezza Name[ja]=セキュリティ -Name[ka]=უსაფრთხოება Name[kk]=Қауіпсіздік Name[km]=សុវត្ថិភាព Name[lt]=Saugumas @@ -64,8 +63,7 @@ Name[ta]=பாதுகாப்பு Name[tg]=Амният Name[tr]=Güvenlik Name[uk]=Безпека -Name[uz]=Xavfsizlik -Name[uz@cyrillic]=Хавфсизлик +Name[uz]=Хавфсизлик Name[zh_CN]=安全 Name[zh_TW]=安全性 Comment=Security & Privacy Settings @@ -93,7 +91,6 @@ Comment[hu]=Biztonsági és adatvédelmi beállítások Comment[is]=Öryggis & einkalífsstillingar Comment[it]=Impostazioni sicurezza e privacy Comment[ja]=セキュリティ & プライバシーの設定 -Comment[ka]=უსაფრთხოებისა და პირადულობის პარამეტრები Comment[kk]=Қауіпсіздігі пен Дербестік параметрлері Comment[km]=ការ​កំណត់​សុវត្ថិភាព & ភាព​ឯកជន Comment[lt]=Saugumo ir privatumo nustatymai @@ -144,7 +141,6 @@ Keywords[hu]=kmail,biztonság Keywords[is]=kmail,öryggi Keywords[it]=kmail,sicurezza Keywords[ja]=kmail,セキュリティ -Keywords[ka]=kmail,უსაფრთხოება Keywords[km]=kmail,សុវត្ថិភាព Keywords[lt]=kmail,security,saugumas Keywords[mk]=kmail,security,кпошта,безбедност @@ -167,6 +163,5 @@ Keywords[ta]=கேஅஞ்சல்,பாதுகாப்பு Keywords[tg]=kmail,security,амният Keywords[tr]=kmail,güvenlik Keywords[uk]=kmail,безпека -Keywords[uz]=kmail,xavfsizlik -Keywords[uz@cyrillic]=kmail,хавфсизлик +Keywords[uz]=kmail,хавфсизлик Keywords[zh_CN]=kmail,security,安全 diff --git a/kmail/kmail_part.rc b/kmail/kmail_part.rc index 72cf6ec35..fb4794dfd 100644 --- a/kmail/kmail_part.rc +++ b/kmail/kmail_part.rc @@ -2,7 +2,7 @@ the same menu entries at the same place in KMail and Kontact --> - + &File @@ -100,6 +100,7 @@ + diff --git a/kmail/kmailicalIface.h b/kmail/kmailicalIface.h index d57611035..fc1b7a815 100644 --- a/kmail/kmailicalIface.h +++ b/kmail/kmailicalIface.h @@ -127,10 +127,16 @@ k_dcop: */ virtual bool removeSubresource( const TQString& resource ) = 0; + /** + * Returns the number of dimap folders in the account manager. + */ + virtual int dimapAccounts() = 0; + /** * Causes all resource folders of the given type to be synced with the server. */ virtual bool triggerSync( const TQString & ) = 0; + virtual void changeResourceUIName( const TQString &folderPath, const TQString &newName ) = 0; k_dcop_signals: void incidenceAdded( const TQString& type, const TQString& folder, diff --git a/kmail/kmailicalifaceimpl.cpp b/kmail/kmailicalifaceimpl.cpp index c1f626f85..05524e027 100644 --- a/kmail/kmailicalifaceimpl.cpp +++ b/kmail/kmailicalifaceimpl.cpp @@ -67,6 +67,7 @@ using KMail::AccountManager; #include #include +#include #include #include #include @@ -75,6 +76,8 @@ using KMail::AccountManager; using namespace KMail; +TQMap *KMailICalIfaceImpl::mSubResourceUINamesMap = new TQMap; + // Local helper methods static void vPartMicroParser( const TQString& str, TQString& s ); static void reloadFolderTree(); @@ -548,10 +551,12 @@ TQMap KMailICalIfaceImpl::incidencesKolab( const TQString& m f->open( "incidences" ); + kdDebug(5006) << k_funcinfo << "Getting incidences (" << mimetype << ") for folder " << f->label() + << ", starting with index " << startIndex << ", " << nbMessages << " messages." << endl; + kdDebug(5006) << "The folder has " << f->count() << " messages." << endl; + int stopIndex = nbMessages == -1 ? f->count() : QMIN( f->count(), startIndex + nbMessages ); - kdDebug(5006) << "KMailICalIfaceImpl::incidencesKolab( " << mimetype << ", " - << resource << " ) from " << startIndex << " to " << stopIndex << endl; for(int i = startIndex; i < stopIndex; ++i) { #if 0 @@ -590,6 +595,8 @@ TQMap KMailICalIfaceImpl::incidencesKolab( const TQString& m #else delete msg; #endif + } else { + kdDebug(5006) << k_funcinfo << " Unable to retrieve message " << i << " for incidence!" << endl; } } f->close( "incidences" ); @@ -652,10 +659,20 @@ static int dimapAccountCount() return count; } +int KMailICalIfaceImpl::dimapAccounts() +{ + return dimapAccountCount(); +} + static TQString subresourceLabelForPresentation( const KMFolder * folder ) { + if( KMailICalIfaceImpl::getResourceMap()->contains( folder->location() ) ) { + return folder->label(); + } + TQString label = folder->prettyURL(); TQStringList parts = TQStringList::split( TQString::fromLatin1("/"), label ); + // In the common special case of some other user's folder shared with us // the url looks like "Server Name/user/$USERNAME/Folder/Name". Make // those a bit nicer. @@ -678,9 +695,15 @@ static TQString subresourceLabelForPresentation( const KMFolder * folder ) remainder.pop_front(); remainder.pop_front(); if ( dimapAccountCount() > 1 ) { + // Fix kolab issue 2531 folder->storage() )->account() can be null + if( folder->storage() && static_cast( folder->storage() )->account() ) { label = i18n( "My %1 (%2)") .arg( remainder.join( TQString::fromLatin1("/") ), static_cast( folder->storage() )->account()->name() ); + } else { + label = i18n("My %1") + .arg( remainder.join( TQString::fromLatin1("/") ) ); + } } else { label = i18n("My %1") .arg( remainder.join( TQString::fromLatin1("/") ) ); @@ -700,9 +723,9 @@ TQValueList KMailICalIfaceImpl::subresourcesKol KMFolder* f = folderFromType( contentsType, TQString::null ); if ( f ) { subResources.append( SubResource( f->location(), subresourceLabelForPresentation( f ), - !f->isReadOnly(), folderIsAlarmRelevant( f ) ) ); + f->isWritable(), folderIsAlarmRelevant( f ) ) ); kdDebug(5006) << "Adding(1) folder " << f->location() << " " << - ( f->isReadOnly() ? "readonly" : "" ) << endl; + ( !f->isWritable() ? "readonly" : "" ) << endl; } // get the extra ones @@ -712,9 +735,9 @@ TQValueList KMailICalIfaceImpl::subresourcesKol f = it.current()->folder; if ( f && f->storage()->contentsType() == t ) { subResources.append( SubResource( f->location(), subresourceLabelForPresentation( f ), - !f->isReadOnly(), folderIsAlarmRelevant( f ) ) ); + f->isWritable(), folderIsAlarmRelevant( f ) ) ); kdDebug(5006) << "Adding(2) folder " << f->location() << " " << - ( f->isReadOnly() ? "readonly" : "" ) << endl; + ( !f->isWritable() ? "readonly" : "" ) << endl; } } @@ -743,7 +766,9 @@ bool KMailICalIfaceImpl::triggerSync( const TQString& contentsType ) imap->getAndCheckFolder(); } else if ( f->folderType() == KMFolderTypeCachedImap ) { KMFolderCachedImap* cached = static_cast( f->storage() ); - cached->account()->processNewMailSingleFolder( f ); + if ( cached->account() ) { + cached->account()->processNewMailInFolder( f ); + } } } return true; @@ -758,7 +783,7 @@ bool KMailICalIfaceImpl::isWritableFolder( const TQString& type, // Definitely not writable return false; - return !f->isReadOnly(); + return f->isWritable(); } /* Used by the resource to query the storage format of the folder. */ @@ -1432,7 +1457,7 @@ void KMailICalIfaceImpl::folderContentsTypeChanged( KMFolder* folder, } // Tell about the new resource subresourceAdded( folderContentsType( contentsType ), location, subresourceLabelForPresentation(folder), - !folder->isReadOnly(), folderIsAlarmRelevant( folder ) ); + folder->isWritable(), folderIsAlarmRelevant( folder ) ); } KMFolder* KMailICalIfaceImpl::extraFolder( const TQString& type, @@ -1577,8 +1602,7 @@ void KMailICalIfaceImpl::slotFolderPropertiesChanged( KMFolder* folder ) subresourceDeleted( contentsTypeStr, location ); subresourceAdded( contentsTypeStr, location, subresourceLabelForPresentation( folder ), - !folder->isReadOnly(), folderIsAlarmRelevant( folder ) ); - + folder->isWritable(), folderIsAlarmRelevant( folder ) ); } } @@ -1629,6 +1653,33 @@ KMFolder* KMailICalIfaceImpl::findResourceFolder( const TQString& resource ) return 0; } +void KMailICalIfaceImpl::changeResourceUIName( const TQString &folderPath, const TQString &newName ) +{ + kdDebug() << "Folder path " << folderPath << endl; + KMFolder *f = findResourceFolder( folderPath ); + if ( f ) { + KMailICalIfaceImpl::getResourceMap()->insert( folderPath, newName ); + kmkernel->folderMgr()->renameFolder( f, newName ); + KConfigGroup configGroup( kmkernel->config(), "Resource UINames" ); + configGroup.writeEntry( folderPath, newName ); + } +} + +// Builds a folder list from the dimap and the local folder list. +static void createFolderList( TQStringList &folderNames, TQValueList > &folderList ) +{ + TQStringList dimapFolderNames; + TQStringList localFolderNames; + TQValueList > dimapFolderList; + TQValueList > localFolderList; + kmkernel->dimapFolderMgr()->createFolderList( &dimapFolderNames, &dimapFolderList ); + kmkernel->folderMgr()->createFolderList( &localFolderNames, &localFolderList ); + folderNames += dimapFolderNames; + folderNames += localFolderNames; + folderList += dimapFolderList; + folderList += localFolderList; +} + /**************************** * The config stuff */ @@ -1790,22 +1841,22 @@ void KMailICalIfaceImpl::readConfig() if ( mNotes->folderType() == KMFolderTypeCachedImap ) static_cast( mNotes->storage() )->updateAnnotationFolderType(); - // BEGIN TILL TODO The below only uses the dimap folder manager, which - // will fail for all other folder types. Adjust. - - kdDebug(5006) << k_funcinfo << "mCalendar=" << mCalendar << " " << mCalendar->location() << endl; - kdDebug(5006) << k_funcinfo << "mContacts=" << mContacts << " " << mContacts->location() << endl; - kdDebug(5006) << k_funcinfo << "mNotes=" << mNotes << " " << mNotes->location() << endl; + //kdDebug(5006) << k_funcinfo << "mCalendar=" << mCalendar << " " << mCalendar->location() << endl; + //kdDebug(5006) << k_funcinfo << "mContacts=" << mContacts << " " << mContacts->location() << endl; + //kdDebug(5006) << k_funcinfo << "mNotes=" << mNotes << " " << mNotes->location() << endl; // Find all extra folders TQStringList folderNames; TQValueList > folderList; - kmkernel->dimapFolderMgr()->createFolderList(&folderNames, &folderList); - for(TQValueList >::iterator it = folderList.begin(); - it != folderList.end(); ++it) + createFolderList( folderNames, folderList ); + for( TQValueList >::iterator it = folderList.begin(); + it != folderList.end(); ++it ) { - FolderStorage* storage = (*it)->storage(); - if ( storage->contentsType() != 0 ) { + FolderStorage *storage = (*it)->storage(); + KMFolderCachedImap* dimapStorage = dynamic_cast( storage ); + if ( storage && storage->contentsType() != 0 ) { + if ( dimapStorage ) + dimapStorage->updateAnnotationFolderType(); folderContentsTypeChanged( *it, storage->contentsType() ); } } @@ -1818,8 +1869,6 @@ void KMailICalIfaceImpl::readConfig() mExtraFolders.remove( mContacts->location() ); mExtraFolders.remove( mNotes->location() ); - // END TILL TODO - subresourceAdded( folderContentsType( KMail::ContentsTypeCalendar ), mCalendar->location(), mCalendar->label(), true, true ); subresourceAdded( folderContentsType( KMail::ContentsTypeTask ), mTasks->location(), mTasks->label(), true, true ); subresourceAdded( folderContentsType( KMail::ContentsTypeJournal ), mJournals->location(), mJournals->label(), true, false ); @@ -1901,6 +1950,10 @@ void KMailICalIfaceImpl::readConfig() subresourceAdded( folderContentsType( KMail::ContentsTypeNote ), mNotes->location(), mNotes->label(), true, false ); } + KConfig *config = kmkernel->config(); + config->setGroup("Resource UINames"); + *KMailICalIfaceImpl::mSubResourceUINamesMap = config->entryMap( "Resource UINames" ); + reloadFolderTree(); } @@ -1930,6 +1983,19 @@ KMFolder* KMailICalIfaceImpl::initFolder( KMail::FolderContentsType contentsType // Find the folder StandardFolderSearchResult result = findStandardResourceFolder( mFolderParentDir, contentsType ); + + // deal with multiple default groupware folders + if ( result.folders.count() > 1 && result.found == StandardFolderSearchResult::FoundAndStandard ) { + TQStringList labels; + for ( TQValueList::ConstIterator it = result.folders.begin(); it != result.folders.end(); ++it ) + labels << (*it)->prettyURL(); + const TQString selected = KInputDialog::getItem( i18n("Default folder"), + i18n("There are multiple %1 default folders, please choose one:") + .arg( localizedDefaultFolderName( contentsType ) ), labels ); + if ( !selected.isEmpty() ) + result.folder = result.folders[ labels.findIndex( selected ) ]; + } + KMFolder* folder = result.folder; if ( !folder ) { @@ -2125,21 +2191,22 @@ static void vPartMicroParser( const TQString& str, TQString& s ) } // Returns the first child folder having the given annotation -static KMFolder* findFolderByAnnotation( KMFolderDir* folderParentDir, const TQString& annotation ) -{ - TQPtrListIterator it( *folderParentDir ); - for ( ; it.current(); ++it ) { - if ( !it.current()->isDir() ) { - KMFolder* folder = static_cast( it.current() ); - if ( folder->folderType() == KMFolderTypeCachedImap ) { - TQString folderAnnotation = static_cast( folder->storage() )->annotationFolderType(); - //kdDebug(5006) << "findStandardResourceFolder: " << folder->name() << " has annotation " << folderAnnotation << endl; - if ( folderAnnotation == annotation ) - return folder; - } +static TQValueList findFolderByAnnotation( KMFolderDir* folderParentDir, const TQString& annotation ) +{ + TQValueList rv; + TQPtrListIterator it( *folderParentDir ); + for ( ; it.current(); ++it ) { + if ( !it.current()->isDir() ) { + KMFolder* folder = static_cast( it.current() ); + if ( folder->folderType() == KMFolderTypeCachedImap ) { + TQString folderAnnotation = static_cast( folder->storage() )->annotationFolderType(); + //kdDebug(5006) << "findStandardResourceFolder: " << folder->name() << " has annotation " << folderAnnotation << endl; + if ( folderAnnotation == annotation ) + rv.append( folder ); } } - return 0; + } + return rv; } KMailICalIfaceImpl::StandardFolderSearchResult KMailICalIfaceImpl::findStandardResourceFolder( KMFolderDir* folderParentDir, KMail::FolderContentsType contentsType ) @@ -2147,14 +2214,14 @@ KMailICalIfaceImpl::StandardFolderSearchResult KMailICalIfaceImpl::findStandardR if ( GlobalSettings::self()->theIMAPResourceStorageFormat() == GlobalSettings::EnumTheIMAPResourceStorageFormat::XML ) { // Look for a folder with an annotation like "event.default" - KMFolder* folder = findFolderByAnnotation( folderParentDir, TQString( s_folderContentsType[contentsType].annotation ) + ".default" ); - if ( folder ) - return StandardFolderSearchResult( folder, StandardFolderSearchResult::FoundAndStandard ); + TQValueList folders = findFolderByAnnotation( folderParentDir, TQString( s_folderContentsType[contentsType].annotation ) + ".default" ); + if ( !folders.isEmpty() ) + return StandardFolderSearchResult( folders, StandardFolderSearchResult::FoundAndStandard ); // Fallback: look for a folder with an annotation like "event" - folder = findFolderByAnnotation( folderParentDir, TQString( s_folderContentsType[contentsType].annotation ) ); - if ( folder ) - return StandardFolderSearchResult( folder, StandardFolderSearchResult::FoundByType ); + folders = findFolderByAnnotation( folderParentDir, TQString( s_folderContentsType[contentsType].annotation ) ); + if ( !folders.isEmpty() ) + return StandardFolderSearchResult( folders, StandardFolderSearchResult::FoundByType ); // Fallback: look for the folder by name (we'll need to change its type) KMFolderNode* node = folderParentDir->hasNamedFolder( localizedDefaultFolderName( contentsType ) ); @@ -2188,12 +2255,14 @@ bool KMailICalIfaceImpl::folderIsAlarmRelevant( const KMFolder *folder ) if ( folder->folderType() == KMFolderTypeImap ) { const KMFolderImap *imapFolder = static_cast( folder->storage() ); administerRights = - imapFolder->userRights() <= 0 || imapFolder->userRights() & KMail::ACLJobs::Administer; + imapFolder->userRightsState() != KMail::ACLJobs::Ok || + imapFolder->userRights() & KMail::ACLJobs::Administer; } if ( folder->folderType() == KMFolderTypeCachedImap ) { const KMFolderCachedImap *dimapFolder = static_cast( folder->storage() ); administerRights = - dimapFolder->userRights() <= 0 || dimapFolder->userRights() & KMail::ACLJobs::Administer; + dimapFolder->userRightsState() != KMail::ACLJobs::Ok || + dimapFolder->userRights() & KMail::ACLJobs::Administer; relevantForOwner = !dimapFolder->alarmsBlocked() && ( dimapFolder->incidencesFor () == KMFolderCachedImap::IncForAdmins ); relevantForEveryone = !dimapFolder->alarmsBlocked() && ( dimapFolder->incidencesFor() == KMFolderCachedImap::IncForReaders ); } @@ -2227,6 +2296,12 @@ bool KMailICalIfaceImpl::addSubresource( const TQString& resource, KMFolderDir *parentFolderDir = !parent.isEmpty() && folder ? folder->createChildFolder(): mFolderParentDir; if ( !parentFolderDir || parentFolderDir->hasNamedFolder( resource ) ) return false; + TQString msg; + if ( parentFolderDir->owner() && !parentFolderDir->owner()->isValidName( resource, msg ) ) { + KMessageBox::error( 0, msg ); + return false; + } + KMFolderType type = mFolderType; if( type == KMFolderTypeUnknown ) type = KMFolderTypeMaildir; @@ -2291,7 +2366,7 @@ void KMailICalIfaceImpl::syncFolder(KMFolder * folder) const else return; } - dimapFolder->account()->processNewMailSingleFolder( folder ); + dimapFolder->account()->processNewMailInFolder( folder ); } #include "kmailicalifaceimpl.moc" diff --git a/kmail/kmailicalifaceimpl.h b/kmail/kmailicalifaceimpl.h index 5a92ffe92..c0308f928 100644 --- a/kmail/kmailicalifaceimpl.h +++ b/kmail/kmailicalifaceimpl.h @@ -113,6 +113,7 @@ public: const TQString& resource, int startIndex, int nbMessages ); + int dimapAccounts(); TQValueList subresourcesKolab( const TQString& contentsType ); @@ -223,6 +224,8 @@ public: bool isResourceQuiet() const; void setResourceQuiet(bool q); + static TQMap* getResourceMap() { return mSubResourceUINamesMap; } + public slots: /* (Re-)Read configuration file */ void readConfig(); @@ -235,6 +238,7 @@ public slots: // Called when a folder is made readonly or readwrite, or renamed, // or any other similar change that affects the resources void slotFolderPropertiesChanged( KMFolder* folder ); + void changeResourceUIName( const TQString &folderPath, const TQString &newName ); private slots: void slotRefreshFolder( KMFolder* ); @@ -259,7 +263,10 @@ private: enum FoundEnum { FoundAndStandard, NotFound, FoundByType, FoundByName }; StandardFolderSearchResult() : folder( 0 ) {} StandardFolderSearchResult( KMFolder* f, FoundEnum e ) : folder( f ), found( e ) {} + StandardFolderSearchResult( const TQValueList &f, FoundEnum e ) : + folder( f.first() ), folders( f ), found( e ) {} KMFolder* folder; // NotFound implies folder==0 of course. + TQValueList folders; // in case we found multiple default folders (which should not happen) FoundEnum found; }; @@ -341,6 +348,7 @@ private: TQMap mTheUnGetMes; TQMap mPendingUpdates; TQMap mInTransit; + static TQMap *mSubResourceUINamesMap; }; diff --git a/kmail/kmcommands.cpp b/kmail/kmcommands.cpp index e0b911df7..7bf978b07 100644 --- a/kmail/kmcommands.cpp +++ b/kmail/kmcommands.cpp @@ -101,6 +101,7 @@ using KMail::ActionScheduler; #include "kcursorsaver.h" #include "partNode.h" #include "objecttreeparser.h" +#include "csshelper.h" using KMail::ObjectTreeParser; using KMail::FolderJob; #include "chiasmuskeyselector.h" @@ -243,13 +244,16 @@ void KMCommand::slotStart() return; } - for (KMMsgBase *mb = mMsgList.first(); mb; mb = mMsgList.next()) - if (!mb->parent()) { - emit messagesTransfered( Failed ); - return; - } else { - keepFolderOpen( mb->parent() ); + for ( KMMsgBase *mb = mMsgList.first(); mb; mb = mMsgList.next() ) { + if ( mb ) { + if ( !mb->parent() ) { + emit messagesTransfered( Failed ); + return; + } else { + keepFolderOpen( mb->parent() ); + } } + } // transfer the selected messages first transferSelectedMsgs(); @@ -446,7 +450,7 @@ void KMCommand::slotTransferCancelled() void KMCommand::keepFolderOpen( KMFolder *folder ) { - folder->open("kmcommand"); + folder->open( "kmcommand" ); mFolders.append( folder ); } @@ -757,10 +761,25 @@ KMCommand::Result KMShowMsgSrcCommand::execute() return OK; } -static KURL subjectToUrl( const TQString & subject ) { - return KFileDialog::getSaveURL( subject.stripWhiteSpace() - .replace( TQDir::separator(), '_' ), - "*.mbox" ); +static KURL subjectToUrl( const TQString & subject ) +{ + // We need to replace colons with underscores since those cause problems with KFileDialog (bug + // in KFileDialog though) and also on Windows filesystems. + // We also look at the special case of ": ", since converting that to "_ " would look strange, + // simply "_" looks better. + // We also don't allow filenames starting with a dot, since then the file is hidden and the poor + // user can't find it anymore. + // Don't allow filenames starting with a tilde either, since that will cause the file dialog to + // discard the filename entirely. + // https://issues.kolab.org/issue3805 + const TQString filter = i18n( "*.mbox|email messages (*.mbox)\n*|all files (*)" ); + TQString cleanSubject = subject.stripWhiteSpace() + .replace( TQDir::separator(), '_' ) + .replace( ": ", "_" ) + .replace( ':', '_' ) + .replace( '.', '_' ) + .replace( '~', '_' ); + return KFileDialog::getSaveURL( cleanSubject, filter ); } KMSaveMsgCommand::KMSaveMsgCommand( TQWidget *parent, KMMessage * msg ) @@ -857,16 +876,23 @@ void KMSaveMsgCommand::slotSaveDataReq() assert( p ); assert( idx >= 0 ); //kdDebug() << "SERNUM: " << mMsgList[mMsgListIndex] << " idx: " << idx << " folder: " << p->prettyURL() << endl; + + const bool alreadyGot = p->isMessage( idx ); + msg = p->getMsg(idx); if ( msg ) { + // Only unGet the message if it isn't already got. + if ( !alreadyGot ) { + mUngetMsgs.append( msg ); + } if ( msg->transferInProgress() ) { TQByteArray data = TQByteArray(); mJob->sendAsyncData( data ); } msg->setTransferInProgress( true ); - if (msg->isComplete() ) { - slotMessageRetrievedForSaving( msg ); + if ( msg->isComplete() ) { + slotMessageRetrievedForSaving( msg ); } else { // retrieve Message first if ( msg->parent() && !msg->isComplete() ) { @@ -918,7 +944,8 @@ void KMSaveMsgCommand::slotMessageRetrievedForSaving(KMMessage *msg) } ++mMsgListIndex; // Get rid of the message. - if ( msg && msg->parent() && msg->getMsgSerNum() ) { + if ( msg && msg->parent() && msg->getMsgSerNum() && + mUngetMsgs.contains( msg ) ) { int idx = -1; KMFolder * p = 0; KMMsgDict::instance()->getLocation( msg, &p, &idx ); @@ -1229,9 +1256,9 @@ KMCommand::Result KMForwardInlineCommand::execute() // fwdMsg->setBody( msgText ); for ( KMMessage *msg = linklist.first(); msg; msg = linklist.next() ) { - TemplateParser parser( fwdMsg, TemplateParser::Forward, - msg->body(), false, false, false, false); - parser.process( msg, 0, true ); + TemplateParser parser( fwdMsg, TemplateParser::Forward ); + parser.setSelection( msg->body() ); // FIXME: Why is this needed? + parser.process( msg, 0, true ); fwdMsg->link( msg, KMMsgStatusForwarded ); } @@ -1256,7 +1283,6 @@ KMCommand::Result KMForwardInlineCommand::execute() { KMail::Composer * win = KMail::makeComposer( fwdMsg, id ); win->setCharset( fwdMsg->codec()->mimeName(), true ); - win->setBody( fwdMsg->bodyToUnicode() ); win->show(); } } @@ -1459,7 +1485,7 @@ KMCommand::Result KMCustomReplyToCommand::execute() return Failed; } KMMessage *reply = msg->createReply( KMail::ReplySmart, mSelection, - false, true, false, mTemplate ); + false, true, mTemplate ); KMail::Composer * win = KMail::makeComposer( reply ); win->setCharset( msg->codec()->mimeName(), true ); win->setReplyFocus(); @@ -1484,7 +1510,7 @@ KMCommand::Result KMCustomReplyAllToCommand::execute() return Failed; } KMMessage *reply = msg->createReply( KMail::ReplyAll, mSelection, - false, true, false, mTemplate ); + false, true, mTemplate ); KMail::Composer * win = KMail::makeComposer( reply ); win->setCharset( msg->codec()->mimeName(), true ); win->setReplyFocus(); @@ -1533,9 +1559,9 @@ KMCommand::Result KMCustomForwardCommand::execute() // fwdMsg->setBody( msgText ); for ( KMMessage *msg = linklist.first(); msg; msg = linklist.next() ) { - TemplateParser parser( fwdMsg, TemplateParser::Forward, - msg->body(), false, false, false, false); - parser.process( msg, 0, true ); + TemplateParser parser( fwdMsg, TemplateParser::Forward ); + parser.setSelection( msg->body() ); // FIXME: Why is this needed? + parser.process( msg, 0, true ); fwdMsg->link( msg, KMMsgStatusForwarded ); } @@ -1567,14 +1593,24 @@ KMCommand::Result KMCustomForwardCommand::execute() } -KMPrintCommand::KMPrintCommand( TQWidget *parent, - KMMessage *msg, bool htmlOverride, bool htmlLoadExtOverride, - bool useFixedFont, const TQString & encoding ) - : KMCommand( parent, msg ), mHtmlOverride( htmlOverride ), +KMPrintCommand::KMPrintCommand( TQWidget *parent, KMMessage *msg, + const KMail::HeaderStyle *headerStyle, + const KMail::HeaderStrategy *headerStrategy, + bool htmlOverride, bool htmlLoadExtOverride, + bool useFixedFont, const TQString & encoding ) + : KMCommand( parent, msg ), + mHeaderStyle( headerStyle ), mHeaderStrategy( headerStrategy ), + mHtmlOverride( htmlOverride ), mHtmlLoadExtOverride( htmlLoadExtOverride ), mUseFixedFont( useFixedFont ), mEncoding( encoding ) { - mOverrideFont = KGlobalSettings::generalFont(); + if ( GlobalSettings::useDefaultFonts() ) + mOverrideFont = KGlobalSettings::generalFont(); + else { + KConfigGroup fonts( KMKernel::config(), "Fonts" ); + TQString tmp = fonts.readEntry( "print-font", KGlobalSettings::generalFont().toString() ); + mOverrideFont.fromString( tmp ); + } } @@ -1588,11 +1624,13 @@ KMCommand::Result KMPrintCommand::execute() KMReaderWin printWin( 0, 0, 0 ); printWin.setPrinting( true ); printWin.readConfig(); + if ( mHeaderStyle != 0 && mHeaderStrategy != 0 ) + printWin.setHeaderStyleAndStrategy( mHeaderStyle, mHeaderStrategy ); printWin.setHtmlOverride( mHtmlOverride ); printWin.setHtmlLoadExtOverride( mHtmlLoadExtOverride ); printWin.setUseFixedFont( mUseFixedFont ); printWin.setOverrideEncoding( mEncoding ); - printWin.setPrintFont( mOverrideFont ); + printWin.cssHelper()->setPrintFont( mOverrideFont ); printWin.setDecryptMessageOverwrite( true ); printWin.setMsg( retrievedMessage(), true ); printWin.printMsg(); @@ -2149,14 +2187,21 @@ KMCommand::Result KMMoveCommand::execute() mProgressItem->setTotalItems( mSerNumList.count() ); for ( TQValueList::ConstIterator it = mSerNumList.constBegin(); it != mSerNumList.constEnd(); ++it ) { - KMFolder *srcFolder; + if ( *it == 0 ) { + kdDebug(5006) << k_funcinfo << "serial number == 0!" << endl; + continue; // invalid message + } + KMFolder *srcFolder = 0; int idx = -1; KMMsgDict::instance()->getLocation( *it, &srcFolder, &idx ); if (srcFolder == mDestFolder) continue; + assert(srcFolder); assert(idx != -1); - srcFolder->open( "kmmovecommand" ); - mOpenedFolders.append( srcFolder ); + if ( !srcFolder->isOpened() ) { + srcFolder->open( "kmmovecommand" ); + mOpenedFolders.append( srcFolder ); + } msg = srcFolder->getMsg(idx); if ( !msg ) { kdDebug(5006) << k_funcinfo << "No message found for serial number " << *it << endl; @@ -2328,6 +2373,11 @@ KMDeleteMsgCommand::KMDeleteMsgCommand( KMFolder* srcFolder, KMMessage * msg ) KMDeleteMsgCommand::KMDeleteMsgCommand( Q_UINT32 sernum ) :KMMoveCommand( sernum ) { + if ( !sernum ) { + setDestFolder( 0 ); + return; + } + KMFolder *srcFolder = 0; int idx; KMMsgDict::instance()->getLocation( sernum, &srcFolder, &idx ); @@ -3146,7 +3196,7 @@ void KMHandleAttachmentCommand::atmSave() parts.append( mNode ); // save, do not leave encoded KMSaveAttachmentsCommand *command = - new KMSaveAttachmentsCommand( 0, parts, mMsg, false ); + new KMSaveAttachmentsCommand( parentWidget(), parts, mMsg, false ); command->start(); } @@ -3306,6 +3356,13 @@ AttachmentModifyCommand::AttachmentModifyCommand(partNode * node, KMMessage * ms { } +AttachmentModifyCommand::AttachmentModifyCommand( int nodeId, KMMessage *msg, TQWidget *parent ) + : KMCommand( parent, msg ), + mPartIndex( nodeId ), + mSernum( 0 ) +{ +} + AttachmentModifyCommand::~ AttachmentModifyCommand() { } @@ -3370,31 +3427,14 @@ void AttachmentModifyCommand::messageDeleteResult(KMCommand * cmd) deleteLater(); } -DwBodyPart * AttachmentModifyCommand::findPart(KMMessage* msg, int index) -{ - int accu = 0; - return findPartInternal( msg->getTopLevelPart(), index, accu ); -} - -DwBodyPart * AttachmentModifyCommand::findPartInternal(DwEntity * root, int index, int & accu) +KMDeleteAttachmentCommand::KMDeleteAttachmentCommand(partNode * node, KMMessage * msg, TQWidget * parent) : + AttachmentModifyCommand( node, msg, parent ) { - accu++; - if ( index < accu ) // should not happen - return 0; - DwBodyPart *current = dynamic_cast( root ); - if ( index == accu ) - return current; - DwBodyPart *rv = 0; - if ( root->Body().FirstBodyPart() ) - rv = findPartInternal( root->Body().FirstBodyPart(), index, accu ); - if ( !rv && current && current->Next() ) - rv = findPartInternal( current->Next(), index, accu ); - return rv; + kdDebug(5006) << k_funcinfo << endl; } - -KMDeleteAttachmentCommand::KMDeleteAttachmentCommand(partNode * node, KMMessage * msg, TQWidget * parent) : - AttachmentModifyCommand( node, msg, parent ) +KMDeleteAttachmentCommand::KMDeleteAttachmentCommand( int nodeId, KMMessage *msg, TQWidget *parent ) + : AttachmentModifyCommand( nodeId, msg, parent ) { kdDebug(5006) << k_funcinfo << endl; } @@ -3407,37 +3447,8 @@ KMDeleteAttachmentCommand::~KMDeleteAttachmentCommand() KMCommand::Result KMDeleteAttachmentCommand::doAttachmentModify() { KMMessage *msg = retrievedMessage(); - KMMessagePart part; - DwBodyPart *dwpart = findPart( msg, mPartIndex ); - if ( !dwpart ) + if ( !msg || !msg->deleteBodyPart( mPartIndex ) ) return Failed; - KMMessage::bodyPart( dwpart, &part, true ); - if ( !part.isComplete() ) - return Failed; - - DwBody *parentNode = dynamic_cast( dwpart->Parent() ); - if ( !parentNode ) - return Failed; - parentNode->RemoveBodyPart( dwpart ); - - // add dummy part to show that a attachment has been deleted - KMMessagePart dummyPart; - dummyPart.duplicate( part ); - TQString comment = i18n("This attachment has been deleted."); - if ( !part.fileName().isEmpty() ) - comment = i18n( "The attachment '%1' has been deleted." ).arg( part.fileName() ); - dummyPart.setContentDescription( comment ); - dummyPart.setBodyEncodedBinary( TQByteArray() ); - TQCString cd = dummyPart.contentDisposition(); - if ( cd.find( "inline", 0, false ) == 0 ) { - cd.replace( 0, 10, "attachment" ); - dummyPart.setContentDisposition( cd ); - } else if ( cd.isEmpty() ) { - dummyPart.setContentDisposition( "attachment" ); - } - DwBodyPart* newDwPart = msg->createDWBodyPart( &dummyPart ); - parentNode->AddBodyPart( newDwPart ); - msg->getTopLevelPart()->Assemble(); KMMessage *newMsg = new KMMessage(); newMsg->fromDwString( msg->asDwString() ); @@ -3455,6 +3466,13 @@ KMEditAttachmentCommand::KMEditAttachmentCommand(partNode * node, KMMessage * ms mTempFile.setAutoDelete( true ); } +KMEditAttachmentCommand::KMEditAttachmentCommand( int nodeId, KMMessage *msg, TQWidget *parent ) + : AttachmentModifyCommand( nodeId, msg, parent ) +{ + kdDebug(5006) << k_funcinfo << endl; + mTempFile.setAutoDelete( true ); +} + KMEditAttachmentCommand::~ KMEditAttachmentCommand() { } @@ -3462,8 +3480,11 @@ KMEditAttachmentCommand::~ KMEditAttachmentCommand() KMCommand::Result KMEditAttachmentCommand::doAttachmentModify() { KMMessage *msg = retrievedMessage(); + if ( !msg ) + return Failed; + KMMessagePart part; - DwBodyPart *dwpart = findPart( msg, mPartIndex ); + DwBodyPart *dwpart = msg->findPart( mPartIndex ); if ( !dwpart ) return Failed; KMMessage::bodyPart( dwpart, &part, true ); @@ -3476,7 +3497,10 @@ KMCommand::Result KMEditAttachmentCommand::doAttachmentModify() mTempFile.file()->writeBlock( part.bodyDecodedBinary() ); mTempFile.file()->flush(); - KMail::EditorWatcher *watcher = new KMail::EditorWatcher( KURL(mTempFile.file()->name()), part.typeStr() + "/" + part.subtypeStr(), false, this ); + KMail::EditorWatcher *watcher = + new KMail::EditorWatcher( KURL( mTempFile.file()->name() ), + part.typeStr() + "/" + part.subtypeStr(), + false, this, parentWidget() ); connect( watcher, TQT_SIGNAL(editDone(KMail::EditorWatcher*)), TQT_SLOT(editDone(KMail::EditorWatcher*)) ); if ( !watcher->start() ) return Failed; @@ -3502,7 +3526,7 @@ void KMEditAttachmentCommand::editDone(KMail::EditorWatcher * watcher) // build the new message KMMessage *msg = retrievedMessage(); KMMessagePart part; - DwBodyPart *dwpart = findPart( msg, mPartIndex ); + DwBodyPart *dwpart = msg->findPart( mPartIndex ); KMMessage::bodyPart( dwpart, &part, true ); DwBody *parentNode = dynamic_cast( dwpart->Parent() ); @@ -3549,8 +3573,8 @@ KMCommand::Result CreateTodoCommand::execute() tf.close(); KCalendarIface_stub *iface = new KCalendarIface_stub( kapp->dcopClient(), "korganizer", "CalendarIface" ); - iface->openTodoEditor( i18n("Mail: %1").arg( msg->subject() ), txt, - uri, tf.name(), TQStringList(), "message/rfc822" ); + iface->openTodoEditor( i18n("Mail: %1").arg( msg->subject() ), txt, uri, + tf.name(), TQStringList(), "message/rfc822", true ); delete iface; return OK; diff --git a/kmail/kmcommands.h b/kmail/kmcommands.h index 7d1385a66..781a80873 100644 --- a/kmail/kmcommands.h +++ b/kmail/kmcommands.h @@ -39,6 +39,8 @@ namespace KMail { class Composer; class FolderJob; class EditorWatcher; + class HeaderStyle; + class HeaderStrategy; } namespace GpgME { class Error; } namespace Kleo { class SpecialJob; } @@ -83,9 +85,11 @@ public slots: void slotProgress( unsigned long done, unsigned long total ); signals: + + /// @param result The status of the command. void messagesTransfered( KMCommand::Result result ); - /** Emitted when the command has completed. - * @param result The status of the command. */ + + /// Emitted when the command has completed. void completed( KMCommand *command ); protected: @@ -342,6 +346,7 @@ private: static const int MAX_CHUNK_SIZE = 64*1024; KURL mUrl; TQValueList mMsgList; + TQValueList mUngetMsgs; unsigned int mMsgListIndex; KMMessage *mStandAloneMessage; TQByteArray mData; @@ -601,8 +606,10 @@ class KDE_EXPORT KMPrintCommand : public KMCommand public: KMPrintCommand( TQWidget *parent, KMMessage *msg, - bool htmlOverride=false, - bool htmlLoadExtOverride=false, + const KMail::HeaderStyle *headerStyle = 0, + const KMail::HeaderStrategy *headerStrategy = 0, + bool htmlOverride = false, + bool htmlLoadExtOverride = false, bool useFixedFont = false, const TQString & encoding = TQString() ); @@ -611,6 +618,8 @@ public: private: virtual Result execute(); + const KMail::HeaderStyle *mHeaderStyle; + const KMail::HeaderStrategy *mHeaderStrategy; bool mHtmlOverride; bool mHtmlLoadExtOverride; bool mUseFixedFont; @@ -1036,11 +1045,11 @@ class KDE_EXPORT AttachmentModifyCommand : public KMCommand Q_OBJECT public: AttachmentModifyCommand( partNode *node, KMMessage *msg, TQWidget *parent ); + AttachmentModifyCommand( int nodeId, KMMessage *msg, TQWidget *parent ); ~AttachmentModifyCommand(); protected: void storeChangedMessage( KMMessage* msg ); - DwBodyPart* findPart( KMMessage* msg, int index ); virtual Result doAttachmentModify() = 0; protected: @@ -1049,7 +1058,6 @@ class KDE_EXPORT AttachmentModifyCommand : public KMCommand private: Result execute(); - DwBodyPart* findPartInternal( DwEntity* root, int index, int &accu ); private slots: void messageStoreResult( KMFolderImap* folder, bool success ); @@ -1064,6 +1072,7 @@ class KDE_EXPORT KMDeleteAttachmentCommand : public AttachmentModifyCommand Q_OBJECT public: KMDeleteAttachmentCommand( partNode *node, KMMessage *msg, TQWidget *parent ); + KMDeleteAttachmentCommand( int nodeId, KMMessage *msg, TQWidget *parent ); ~KMDeleteAttachmentCommand(); protected: @@ -1076,6 +1085,7 @@ class KDE_EXPORT KMEditAttachmentCommand : public AttachmentModifyCommand Q_OBJECT public: KMEditAttachmentCommand( partNode *node, KMMessage *msg, TQWidget *parent ); + KMEditAttachmentCommand( int nodeId, KMMessage *msg, TQWidget *parent ); ~KMEditAttachmentCommand(); protected: diff --git a/kmail/kmcomposewin.cpp b/kmail/kmcomposewin.cpp index 592248a0a..a1ab32d8a 100644 --- a/kmail/kmcomposewin.cpp +++ b/kmail/kmcomposewin.cpp @@ -69,8 +69,6 @@ using KRecentAddress::RecentAddresses; #include #include -#include -#include #include #include "klistboxdialog.h" @@ -159,6 +157,7 @@ KMComposeWin::KMComposeWin( KMMessage *aMsg, uint id ) mSpellCheckInProgress( false ), mDone( false ), mAtmModified( false ), + mAtmSelectNew( 0 ), mMsg( 0 ), mAttachMenu( 0 ), mSigningAndEncryptionExplicitlyDisabled( false ), @@ -183,7 +182,11 @@ KMComposeWin::KMComposeWin( KMMessage *aMsg, uint id ) mLabelWidth( 0 ), mAutoSaveTimer( 0 ), mLastAutoSaveErrno( 0 ), mSignatureStateIndicator( 0 ), mEncryptionStateIndicator( 0 ), - mPreserveUserCursorPosition( false ) + mPreserveUserCursorPosition( false ), + mPreventFccOverwrite( false ), + mCheckForRecipients( true ), + mCheckForForgottenAttachments( true ), + mIgnoreStickyFields( false ) { mClassicalRecipients = GlobalSettings::self()->recipientsEditorType() == GlobalSettings::EnumRecipientsEditorType::Classic; @@ -200,17 +203,29 @@ KMComposeWin::KMComposeWin( KMMessage *aMsg, uint id ) TQVBoxLayout *v = new TQVBoxLayout( mMainWidget ); v->addWidget( mHeadersToEditorSplitter ); mIdentity = new KPIM::IdentityCombo(kmkernel->identityManager(), mHeadersArea); + TQToolTip::add( mIdentity, + i18n( "Select an identity for this message" ) ); + mDictionaryCombo = new DictionaryComboBox( mHeadersArea ); + TQToolTip::add( mDictionaryCombo, + i18n( "Select the dictionary to use when spell-checking this message" ) ); + mFcc = new KMFolderComboBox(mHeadersArea); mFcc->showOutboxFolder( false ); + TQToolTip::add( mFcc, + i18n( "Select the sent-mail folder where a copy of this message will be saved" ) ); + mTransport = new TQComboBox(true, mHeadersArea); + TQToolTip::add( mTransport, + i18n( "Select the outgoing account to use for sending this message" ) ); + mEdtFrom = new KMLineEdit(false,mHeadersArea, "fromLine"); + TQToolTip::add( mEdtFrom, + i18n( "Set the \"From:\" email address for this message" ) ); mEdtReplyTo = new KMLineEdit(true,mHeadersArea, "replyToLine"); - mLblReplyTo = new TQLabel(mHeadersArea); - mBtnReplyTo = new TQPushButton("...",mHeadersArea); - mBtnReplyTo->setFocusPolicy(TQWidget::NoFocus); - connect(mBtnReplyTo,TQT_SIGNAL(clicked()),TQT_SLOT(slotAddrBookReplyTo())); + TQToolTip::add( mEdtReplyTo, + i18n( "Set the \"Reply-To:\" email address for this message" ) ); connect(mEdtReplyTo,TQT_SIGNAL(completionModeChanged(KGlobalSettings::Completion)), TQT_SLOT(slotCompletionModeChanged(KGlobalSettings::Completion))); @@ -234,7 +249,6 @@ KMComposeWin::KMComposeWin( KMMessage *aMsg, uint id ) TQToolTip::add( mBtnTo, tip ); TQToolTip::add( mBtnCc, tip ); TQToolTip::add( mBtnBcc, tip ); - TQToolTip::add( mBtnReplyTo, tip ); mBtnTo->setFocusPolicy(TQWidget::NoFocus); mBtnCc->setFocusPolicy(TQWidget::NoFocus); @@ -277,16 +291,30 @@ KMComposeWin::KMComposeWin( KMMessage *aMsg, uint id ) mRecipientsEditor->setFocus(); } mEdtSubject = new KMLineEditSpell(false,mHeadersArea, "subjectLine"); - mLblIdentity = new TQLabel(mHeadersArea); - mDictionaryLabel = new TQLabel( mHeadersArea ); - mLblFcc = new TQLabel(mHeadersArea); - mLblTransport = new TQLabel(mHeadersArea); - mLblFrom = new TQLabel(mHeadersArea); - mLblSubject = new TQLabel(mHeadersArea); + TQToolTip::add( mEdtSubject, + i18n( "Set a subject for this message" ) ); + + mLblIdentity = new TQLabel( i18n("&Identity:"), mHeadersArea ); + mDictionaryLabel = new TQLabel( i18n("&Dictionary:"), mHeadersArea ); + mLblFcc = new TQLabel( i18n("&Sent-Mail folder:"), mHeadersArea ); + mLblTransport = new TQLabel( i18n("&Mail transport:"), mHeadersArea ); + mLblFrom = new TQLabel( i18n("sender address field", "&From:"), mHeadersArea ); + mLblReplyTo = new TQLabel( i18n("&Reply to:"), mHeadersArea ); + mLblSubject = new TQLabel( i18n("S&ubject:"), mHeadersArea ); + TQString sticky = i18n("Sticky"); mBtnIdentity = new TQCheckBox(sticky,mHeadersArea); + TQToolTip::add( mBtnIdentity, + i18n( "Use the selected value as your identity for future messages" ) ); mBtnFcc = new TQCheckBox(sticky,mHeadersArea); + TQToolTip::add( mBtnFcc, + i18n( "Use the selected value as your sent-mail folder for future messages" ) ); mBtnTransport = new TQCheckBox(sticky,mHeadersArea); + TQToolTip::add( mBtnTransport, + i18n( "Use the selected value as your outgoing account for future messages" ) ); + mBtnDictionary = new TQCheckBox( sticky, mHeadersArea ); + TQToolTip::add( mBtnDictionary, + i18n( "Use the selected value as your dictionary for future messages" ) ); //setWFlags( WType_TopLevel | WStyle_Dialog ); mHtmlMarkup = GlobalSettings::self()->useHtmlMarkup(); @@ -350,6 +378,8 @@ KMComposeWin::KMComposeWin( KMMessage *aMsg, uint id ) GlobalSettings::self()->stickyFccItem()->whatsThis() ); TQWhatsThis::add( mBtnTransport, GlobalSettings::self()->stickyTransportItem()->whatsThis() ); + TQWhatsThis::add( mBtnTransport, + GlobalSettings::self()->stickyDictionaryItem()->whatsThis() ); mSpellCheckInProgress=false; @@ -359,6 +389,7 @@ KMComposeWin::KMComposeWin( KMMessage *aMsg, uint id ) mBtnIdentity->setFocusPolicy(TQWidget::NoFocus); mBtnFcc->setFocusPolicy(TQWidget::NoFocus); mBtnTransport->setFocusPolicy(TQWidget::NoFocus); + mBtnDictionary->setFocusPolicy( TQWidget::NoFocus ); mAtmListView = new AttachmentListView( this, mSplitter, "attachment list view" ); @@ -657,6 +688,7 @@ void KMComposeWin::readConfig( bool reload /* = false */ ) } mBtnFcc->setChecked( GlobalSettings::self()->stickyFcc() ); mBtnTransport->setChecked( GlobalSettings::self()->stickyTransport() ); + mBtnDictionary->setChecked( GlobalSettings::self()->stickyDictionary() ); TQStringList transportHistory = GlobalSettings::self()->transportHistory(); TQString currentTransport = GlobalSettings::self()->currentTransport(); @@ -711,8 +743,6 @@ void KMComposeWin::readConfig( bool reload /* = false */ ) const KPIM::Identity & ident = kmkernel->identityManager()->identityForUoid( mIdentity->currentIdentity() ); - mDictionaryCombo->setCurrentByDictionary( ident.dictionary() ); - mTransport->clear(); mTransport->insertStringList( KMTransportInfo::availableTransports() ); while ( transportHistory.count() > (uint)GlobalSettings::self()->maxTransportEntries() ) @@ -723,6 +753,12 @@ void KMComposeWin::readConfig( bool reload /* = false */ ) setTransport( currentTransport ); } + if ( mBtnDictionary->isChecked() ) { + mDictionaryCombo->setCurrentByDictionaryName( GlobalSettings::self()->previousDictionary() ); + } else { + mDictionaryCombo->setCurrentByDictionary( ident.dictionary() ); + } + TQString fccName = ""; if ( mBtnFcc->isChecked() ) { fccName = GlobalSettings::self()->previousFcc(); @@ -737,12 +773,16 @@ void KMComposeWin::readConfig( bool reload /* = false */ ) void KMComposeWin::writeConfig(void) { GlobalSettings::self()->setHeaders( mShowHeaders ); - GlobalSettings::self()->setStickyTransport( mBtnTransport->isChecked() ); - GlobalSettings::self()->setStickyIdentity( mBtnIdentity->isChecked() ); GlobalSettings::self()->setStickyFcc( mBtnFcc->isChecked() ); - GlobalSettings::self()->setPreviousIdentity( mIdentity->currentIdentity() ); - GlobalSettings::self()->setCurrentTransport( mTransport->currentText() ); + if ( !mIgnoreStickyFields ) { + GlobalSettings::self()->setCurrentTransport( mTransport->currentText() ); + GlobalSettings::self()->setStickyTransport( mBtnTransport->isChecked() ); + GlobalSettings::self()->setStickyDictionary( mBtnDictionary->isChecked() ); + GlobalSettings::self()->setStickyIdentity( mBtnIdentity->isChecked() ); + GlobalSettings::self()->setPreviousIdentity( mIdentity->currentIdentity() ); + } GlobalSettings::self()->setPreviousFcc( mFcc->getFolder()->idString() ); + GlobalSettings::self()->setPreviousDictionary( mDictionaryCombo->currentDictionaryName() ); GlobalSettings::self()->setAutoSpellChecking( mAutoSpellCheckingAction->isChecked() ); TQStringList transportHistory = GlobalSettings::self()->transportHistory(); @@ -950,7 +990,7 @@ void KMComposeWin::rethinkFields(bool fromSlot) mGrid->setColStretch(0, 1); mGrid->setColStretch(1, 100); mGrid->setColStretch(2, 1); - mGrid->setRowStretch(mNumHeaders, 100); + mGrid->setRowStretch( mNumHeaders + 1, 100 ); row = 0; kdDebug(5006) << "KMComposeWin::rethinkFields" << endl; @@ -967,37 +1007,37 @@ void KMComposeWin::rethinkFields(bool fromSlot) if (!fromSlot) mAllFieldsAction->setChecked(showHeaders==HDR_ALL); if (!fromSlot) mIdentityAction->setChecked(abs(mShowHeaders)&HDR_IDENTITY); - rethinkHeaderLine(showHeaders,HDR_IDENTITY, row, i18n("&Identity:"), + rethinkHeaderLine(showHeaders,HDR_IDENTITY, row, mLblIdentity, mIdentity, mBtnIdentity); if (!fromSlot) mDictionaryAction->setChecked(abs(mShowHeaders)&HDR_DICTIONARY); - rethinkHeaderLine(showHeaders,HDR_DICTIONARY, row, i18n("&Dictionary:"), - mDictionaryLabel, mDictionaryCombo, 0 ); + rethinkHeaderLine(showHeaders,HDR_DICTIONARY, row, + mDictionaryLabel, mDictionaryCombo, mBtnDictionary ); if (!fromSlot) mFccAction->setChecked(abs(mShowHeaders)&HDR_FCC); - rethinkHeaderLine(showHeaders,HDR_FCC, row, i18n("&Sent-Mail folder:"), + rethinkHeaderLine(showHeaders,HDR_FCC, row, mLblFcc, mFcc, mBtnFcc); if (!fromSlot) mTransportAction->setChecked(abs(mShowHeaders)&HDR_TRANSPORT); - rethinkHeaderLine(showHeaders,HDR_TRANSPORT, row, i18n("&Mail transport:"), + rethinkHeaderLine(showHeaders,HDR_TRANSPORT, row, mLblTransport, mTransport, mBtnTransport); if (!fromSlot) mFromAction->setChecked(abs(mShowHeaders)&HDR_FROM); - rethinkHeaderLine(showHeaders,HDR_FROM, row, i18n("sender address field", "&From:"), + rethinkHeaderLine(showHeaders,HDR_FROM, row, mLblFrom, mEdtFrom /*, mBtnFrom */ ); TQWidget *prevFocus = mEdtFrom; if (!fromSlot) mReplyToAction->setChecked(abs(mShowHeaders)&HDR_REPLY_TO); - rethinkHeaderLine(showHeaders,HDR_REPLY_TO,row,i18n("&Reply to:"), - mLblReplyTo, mEdtReplyTo, mBtnReplyTo); + rethinkHeaderLine(showHeaders,HDR_REPLY_TO,row, + mLblReplyTo, mEdtReplyTo, 0); if ( showHeaders & HDR_REPLY_TO ) { prevFocus = connectFocusMoving( prevFocus, mEdtReplyTo ); } if ( mClassicalRecipients ) { if (!fromSlot) mToAction->setChecked(abs(mShowHeaders)&HDR_TO); - rethinkHeaderLine(showHeaders, HDR_TO, row, i18n("recipient address field", "&To:"), + rethinkHeaderLine(showHeaders, HDR_TO, row, mLblTo, mEdtTo, mBtnTo, i18n("Primary Recipients"), i18n("The email addresses you put " @@ -1007,7 +1047,7 @@ void KMComposeWin::rethinkFields(bool fromSlot) } if (!fromSlot) mCcAction->setChecked(abs(mShowHeaders)&HDR_CC); - rethinkHeaderLine(showHeaders, HDR_CC, row, i18n("&Copy to (CC):"), + rethinkHeaderLine(showHeaders, HDR_CC, row, mLblCc, mEdtCc, mBtnCc, i18n("Additional Recipients"), i18n("The email addresses you put " @@ -1022,7 +1062,7 @@ void KMComposeWin::rethinkFields(bool fromSlot) } if (!fromSlot) mBccAction->setChecked(abs(mShowHeaders)&HDR_BCC); - rethinkHeaderLine(showHeaders,HDR_BCC, row, i18n("&Blind copy to (BCC):"), + rethinkHeaderLine(showHeaders,HDR_BCC, row, mLblBcc, mEdtBcc, mBtnBcc, i18n("Hidden Recipients"), i18n("Essentially the same thing " @@ -1057,7 +1097,7 @@ void KMComposeWin::rethinkFields(bool fromSlot) prevFocus = mRecipientsEditor; } if (!fromSlot) mSubjectAction->setChecked(abs(mShowHeaders)&HDR_SUBJECT); - rethinkHeaderLine(showHeaders,HDR_SUBJECT, row, i18n("S&ubject:"), + rethinkHeaderLine(showHeaders,HDR_SUBJECT, row, mLblSubject, mEdtSubject); connectFocusMoving( mEdtSubject, mEditor ); @@ -1100,13 +1140,12 @@ TQWidget *KMComposeWin::connectFocusMoving( TQWidget *prev, TQWidget *next ) //----------------------------------------------------------------------------- void KMComposeWin::rethinkHeaderLine(int aValue, int aMask, int& aRow, - const TQString &aLabelStr, TQLabel* aLbl, + TQLabel* aLbl, TQLineEdit* aEdt, TQPushButton* aBtn, const TQString &toolTip, const TQString &whatsThis ) { if (aValue & aMask) { - aLbl->setText(aLabelStr); if ( !toolTip.isEmpty() ) TQToolTip::add( aLbl, toolTip ); if ( !whatsThis.isEmpty() ) @@ -1137,12 +1176,11 @@ void KMComposeWin::rethinkHeaderLine(int aValue, int aMask, int& aRow, //----------------------------------------------------------------------------- void KMComposeWin::rethinkHeaderLine(int aValue, int aMask, int& aRow, - const TQString &aLabelStr, TQLabel* aLbl, + TQLabel* aLbl, TQComboBox* aCbx, TQCheckBox* aChk) { if (aValue & aMask) { - aLbl->setText(aLabelStr); aLbl->adjustSize(); aLbl->resize((int)aLbl->sizeHint().width(),aLbl->sizeHint().height() + 6); aLbl->setMinimumSize(aLbl->size()); @@ -1310,11 +1348,15 @@ void KMComposeWin::setupActions(void) (void) new KAction (i18n("Paste as Attac&hment"),0,this,TQT_SLOT( slotPasteClipboardAsAttachment()), actionCollection(), "paste_att"); - mAddQuoteChars = new KAction(i18n("Add &Quote Characters"), 0, this, + KAction * addq = new KAction(i18n("Add &Quote Characters"), 0, this, TQT_SLOT(slotAddQuotes()), actionCollection(), "tools_quote"); + connect( mEditor, TQT_SIGNAL(selectionAvailable(bool)), + addq, TQT_SLOT(setEnabled(bool)) ); - mRemQuoteChars = new KAction(i18n("Re&move Quote Characters"), 0, this, + KAction * remq = new KAction(i18n("Re&move Quote Characters"), 0, this, TQT_SLOT(slotRemoveQuotes()), actionCollection(), "tools_unquote"); + connect( mEditor, TQT_SIGNAL(selectionAvailable(bool)), + remq, TQT_SLOT(setEnabled(bool)) ); (void) new KAction (i18n("Cl&ean Spaces"), 0, this, TQT_SLOT(slotCleanSpace()), @@ -1509,6 +1551,7 @@ void KMComposeWin::setupActions(void) actionCollection(), "options_select_crypto" ); mCryptoModuleAction->setItems( l ); mCryptoModuleAction->setCurrentItem( format2cb( ident.preferredCryptoMessageFormat() ) ); + mCryptoModuleAction->setToolTip( i18n( "Select a cryptographic format for this message" ) ); slotSelectCryptoModule( true /* initialize */ ); TQStringList styleItems; @@ -1523,14 +1566,17 @@ void KMComposeWin::setupActions(void) listAction = new KSelectAction( i18n( "Select Style" ), 0, actionCollection(), "text_list" ); listAction->setItems( styleItems ); + listAction->setToolTip( i18n( "Select a list style" ) ); connect( listAction, TQT_SIGNAL( activated( const TQString& ) ), TQT_SLOT( slotListAction( const TQString& ) ) ); fontAction = new KFontAction( "Select Font", 0, actionCollection(), "text_font" ); + fontAction->setToolTip( i18n( "Select a font" ) ); connect( fontAction, TQT_SIGNAL( activated( const TQString& ) ), TQT_SLOT( slotFontAction( const TQString& ) ) ); fontSizeAction = new KFontSizeAction( "Select Size", 0, actionCollection(), "text_size" ); + fontSizeAction->setToolTip( i18n( "Select a font size" ) ); connect( fontSizeAction, TQT_SIGNAL( fontSizeChanged( int ) ), TQT_SLOT( slotSizeAction( int ) ) ); @@ -1821,7 +1867,7 @@ void KMComposeWin::setMsg(KMMessage* newMsg, bool mayAutoSign, } mEdtSubject->setText(mMsg->subject()); - const bool stickyIdentity = mBtnIdentity->isChecked(); + const bool stickyIdentity = mBtnIdentity->isChecked() && !mIgnoreStickyFields; const bool messageHasIdentity = !newMsg->headerField("X-KMail-Identity").isEmpty(); if (!stickyIdentity && messageHasIdentity) mId = newMsg->headerField("X-KMail-Identity").stripWhiteSpace().toUInt(); @@ -1933,7 +1979,8 @@ void KMComposeWin::setMsg(KMMessage* newMsg, bool mayAutoSign, !ident.pgpEncryptionKey().isEmpty() ); TQString transport = newMsg->headerField("X-KMail-Transport"); - if (!mBtnTransport->isChecked() && !transport.isEmpty()) + const bool stickyTransport = mBtnTransport->isChecked() && !mIgnoreStickyFields; + if (!stickyTransport && !transport.isEmpty()) setTransport( transport ); if (!mBtnFcc->isChecked()) @@ -1944,7 +1991,10 @@ void KMComposeWin::setMsg(KMMessage* newMsg, bool mayAutoSign, setFcc(ident.fcc()); } - mDictionaryCombo->setCurrentByDictionary( ident.dictionary() ); + const bool stickyDictionary = mBtnDictionary->isChecked() && !mIgnoreStickyFields; + if ( !stickyDictionary ) { + mDictionaryCombo->setCurrentByDictionary( ident.dictionary() ); + } partNode * root = partNode::fromMessage( mMsg ); @@ -1952,10 +2002,6 @@ void KMComposeWin::setMsg(KMMessage* newMsg, bool mayAutoSign, otp.parseObjectTree( root ); KMail::AttachmentCollector ac; - ac.setDiveIntoEncryptions( true ); - ac.setDiveIntoSignatures( true ); - ac.setDiveIntoMessages( false ); - ac.collectAttachmentsFrom( root ); for ( std::vector::const_iterator it = ac.attachments().begin() ; it != ac.attachments().end() ; ++it ) @@ -2122,6 +2168,9 @@ void KMComposeWin::setMsg(KMMessage* newMsg, bool mayAutoSign, // do this even for new messages mEditor->setCursorPositionFromStart( (unsigned int) mMsg->getCursorPos() ); + + // honor "keep reply in this folder" setting even when the identity is changed later on + mPreventFccOverwrite = ( !newMsg->fcc().isEmpty() && ident.fcc() != newMsg->fcc() ); } @@ -2217,7 +2266,8 @@ bool KMComposeWin::queryClose () //----------------------------------------------------------------------------- bool KMComposeWin::userForgotAttachment() { - bool checkForForgottenAttachments = GlobalSettings::self()->showForgottenAttachmentWarning(); + bool checkForForgottenAttachments = + mCheckForForgottenAttachments && GlobalSettings::self()->showForgottenAttachmentWarning(); if ( !checkForForgottenAttachments || ( mAtmList.count() > 0 ) ) return false; @@ -2482,6 +2532,13 @@ void KMComposeWin::removeAttach(const TQString &aUrl) void KMComposeWin::removeAttach(int idx) { mAtmModified = true; + + KMAtmListViewItem *item = static_cast( mAtmItemList.at( idx ) ); + if ( item->itemBelow() ) + mAtmSelectNew = item->itemBelow(); + else if ( item->itemAbove() ) + mAtmSelectNew = item->itemAbove(); + mAtmList.remove(idx); delete mAtmItemList.take(idx); @@ -2692,11 +2749,19 @@ void KMComposeWin::slotAttachFile() // We will not care about any permissions, existence or whatsoever in // this function. - KFileDialog fdlg(TQString::null, TQString::null, this, 0, true); + // Handle the case where the last savedir is gone. kolab/issue4057 + TQString recent; + KURL recentURL = KFileDialog::getStartURL( TQString::null, recent ); + if ( !recentURL.url().isEmpty() && + !KIO::NetAccess::exists( recentURL, true, this ) ) { + recentURL = KURL( TQDir::homeDirPath() ); + } + + KFileDialog fdlg( recentURL.url(), TQString::null, this, 0, true ); fdlg.setOperationMode( KFileDialog::Other ); - fdlg.setCaption(i18n("Attach File")); - fdlg.okButton()->setGuiItem(KGuiItem(i18n("&Attach"),"fileopen")); - fdlg.setMode(KFile::Files); + fdlg.setCaption( i18n( "Attach File" ) ); + fdlg.okButton()->setGuiItem( KGuiItem( i18n( "&Attach" ),"fileopen" ) ); + fdlg.setMode( KFile::Files ); fdlg.exec(); KURL::List files = fdlg.selectedURLs(); @@ -2831,7 +2896,9 @@ void KMComposeWin::slotAttachFileResult(KIO::Job *job) mMapAtmLoadData.remove(it); - msgPart->setCharset(partCharset); + if ( msgPart->typeStr().lower() == "text" ) { + msgPart->setCharset(partCharset); + } // show message part dialog, if not configured away (default): KConfigGroup composer(KMKernel::config(), "Composer"); @@ -2859,7 +2926,6 @@ void KMComposeWin::slotAttachFileResult(KIO::Job *job) } } mAtmModified = true; - if (msgPart->typeStr().lower() != "text") msgPart->setCharset(TQCString()); // add the new attachment to the list addAttach(msgPart); @@ -3180,7 +3246,6 @@ void KMComposeWin::slotAttachProperties() if (idx < 0) return; KMMessagePart* msgPart = mAtmList.at(idx); - msgPart->setCharset(mCharset); KMMsgPartDialogCompat dlg(mMainWidget); dlg.setMsgPart(msgPart); @@ -3495,7 +3560,9 @@ void KMComposeWin::editAttach(int index, bool openWith) atmTempFile->file()->flush(); - KMail::EditorWatcher *watcher = new KMail::EditorWatcher( KURL( atmTempFile->name() ), contentTypeStr, openWith, this ); + KMail::EditorWatcher *watcher = + new KMail::EditorWatcher( KURL( atmTempFile->name() ), contentTypeStr, openWith, + this, this ); connect( watcher, TQT_SIGNAL(editDone(KMail::EditorWatcher*)), TQT_SLOT(slotEditDone(KMail::EditorWatcher*)) ); if ( watcher->start() ) { mEditorMap.insert( watcher, msgPart ); @@ -3516,7 +3583,7 @@ void KMComposeWin::slotAttachSave() pname = msgPart->name(); if (pname.isEmpty()) pname="unnamed"; - KURL url = KFileDialog::getSaveURL(TQString::null, TQString::null, 0, i18n("Save Attachment As")); + KURL url = KFileDialog::getSaveURL(pname, TQString::null, 0, i18n("Save Attachment As")); if( url.isEmpty() ) return; @@ -3528,6 +3595,7 @@ void KMComposeWin::slotAttachSave() //----------------------------------------------------------------------------- void KMComposeWin::slotAttachRemove() { + mAtmSelectNew = 0; bool attachmentRemoved = false; int i = 0; for ( TQPtrListIterator it(mAtmItemList); *it; ) { @@ -3544,6 +3612,10 @@ void KMComposeWin::slotAttachRemove() if ( attachmentRemoved ) { setModified( true ); slotUpdateAttachActions(); + if ( mAtmSelectNew ) { + mAtmListView->setSelected( mAtmSelectNew, true ); + mAtmListView->setCurrentItem( mAtmSelectNew ); + } } } @@ -3861,6 +3933,7 @@ void KMComposeWin::slotEncryptToggled(bool on) //----------------------------------------------------------------------------- void KMComposeWin::setEncryption( bool encrypt, bool setByUser ) { + bool wasModified = isModified(); if ( setByUser ) setModified( true ); if ( !mEncryptAction->isEnabled() ) @@ -3868,7 +3941,7 @@ void KMComposeWin::setEncryption( bool encrypt, bool setByUser ) // check if the user wants to encrypt messages to himself and if he defined // an encryption key for the current identity else if ( encrypt && encryptToSelf() && !mLastIdentityHasEncryptionKey ) { - if ( setByUser ) + if ( setByUser ) { KMessageBox::sorry( this, i18n("

You have requested that messages be " "encrypted to yourself, but the currently selected " @@ -3878,6 +3951,8 @@ void KMComposeWin::setEncryption( bool encrypt, bool setByUser ) "in the identity configuration.

" "
"), i18n("Undefined Encryption Key") ); + setModified( wasModified ); + } encrypt = false; } @@ -3912,6 +3987,7 @@ void KMComposeWin::slotSignToggled(bool on) //----------------------------------------------------------------------------- void KMComposeWin::setSigning( bool sign, bool setByUser ) { + bool wasModified = isModified(); if ( setByUser ) setModified( true ); if ( !mSignAction->isEnabled() ) @@ -3919,7 +3995,7 @@ void KMComposeWin::setSigning( bool sign, bool setByUser ) // check if the user defined a signing key for the current identity if ( sign && !mLastIdentityHasSigningKey ) { - if ( setByUser ) + if ( setByUser ) { KMessageBox::sorry( this, i18n("

In order to be able to sign " "this message you first have to " @@ -3929,6 +4005,8 @@ void KMComposeWin::setSigning( bool sign, bool setByUser ) "in the identity configuration.

" "
"), i18n("Undefined Signing Key") ); + setModified( wasModified ); + } sign = false; } @@ -3966,6 +4044,26 @@ void KMComposeWin::disableWordWrap() mEditor->setWordWrap( TQTextEdit::NoWrap ); } +void KMComposeWin::disableRecipientNumberCheck() +{ + mCheckForRecipients = false; +} + +void KMComposeWin::disableForgottenAttachmentsCheck() +{ + mCheckForForgottenAttachments = false; +} + +void KMComposeWin::ignoreStickyFields() +{ + mIgnoreStickyFields = true; + mBtnTransport->setChecked( false ); + mBtnDictionary->setChecked( false ); + mBtnIdentity->setChecked( false ); + mBtnTransport->setEnabled( false ); + mBtnDictionary->setEnabled( false ); + mBtnIdentity->setEnabled( false ); +} //----------------------------------------------------------------------------- void KMComposeWin::slotPrint() @@ -4275,11 +4373,24 @@ void KMComposeWin::slotContinueDoSend( bool sentOk ) return; } +bool KMComposeWin::checkTransport() const +{ + if ( KMail::TransportManager::transportNames().isEmpty() ) { + KMessageBox::information( mMainWidget, + i18n("Please create an account for sending and try again.") ); + return false; + } + return true; +} //---------------------------------------------------------------------------- void KMComposeWin::slotSendLater() { + if ( !checkTransport() ) + return; + if ( !checkRecipientNumber() ) + return; if ( mEditor->checkExternalEditorFinished() ) doSend( KMail::MessageSender::SendLater ); } @@ -4322,6 +4433,10 @@ void KMComposeWin::slotSendLaterVia( int item ) void KMComposeWin::slotSendNow() { if ( !mEditor->checkExternalEditorFinished() ) return; + if ( !checkTransport() ) + return; + if ( !checkRecipientNumber() ) + return; if ( GlobalSettings::self()->confirmBeforeSend() ) { int rc = KMessageBox::warningYesNoCancel( mMainWidget, @@ -4339,6 +4454,26 @@ void KMComposeWin::slotSendNow() { doSend( KMail::MessageSender::SendImmediate ); } + +//---------------------------------------------------------------------------- +bool KMComposeWin::checkRecipientNumber() const +{ + uint thresHold = GlobalSettings::self()->recipientThreshold(); + if ( mCheckForRecipients && + GlobalSettings::self()->tooManyRecipients() && + mRecipientsEditor->recipients().count() > thresHold ) { + if ( KMessageBox::questionYesNo( mMainWidget, + i18n("You are trying to send the mail to more than %1 recipients. Send message anyway?").arg(thresHold), + i18n("Too many receipients"), + i18n("&Send as Is"), + i18n("&Edit Recipients")) == KMessageBox::No ) { + return false; + } + } + return true; +} + + //---------------------------------------------------------------------------- void KMComposeWin::slotAppendSignature() { @@ -4348,17 +4483,17 @@ void KMComposeWin::slotAppendSignature() //---------------------------------------------------------------------------- void KMComposeWin::slotPrependSignature() { - insertSignature( false ); + insertSignature( Prepend ); } //---------------------------------------------------------------------------- void KMComposeWin::slotInsertSignatureAtCursor() { - insertSignature( false, mEditor->currentLine() ); + insertSignature( AtCursor ); } //---------------------------------------------------------------------------- -void KMComposeWin::insertSignature( bool append, int pos ) +void KMComposeWin::insertSignature( SignaturePlacement placement ) { bool mod = mEditor->isModified(); @@ -4370,12 +4505,36 @@ void KMComposeWin::insertSignature( bool append, int pos ) if( !mOldSigText.isEmpty() ) { - mEditor->sync(); - if ( append ) { + mEditor->sync(); + int paragraph, index; + mEditor->getCursorPosition( ¶graph, &index ); + index = mEditor->indexOfCurrentLineStart( paragraph, index ); + + switch( placement ) { + case Append: mEditor->setText( mEditor->text() + mOldSigText ); - } else { - mOldSigText = "\n\n"+mOldSigText+"\n"; - mEditor->insertAt(mOldSigText, pos, 0); + break; + case Prepend: + mOldSigText = "\n\n" + mOldSigText + "\n"; + mEditor->insertAt( mOldSigText, paragraph, index ); + break; + case AtCursor: + + // If there is text in the same line, add a newline so that the stuff in + // the current line moves after the signature. Also remove a leading newline, it is not + // needed here. + if ( mEditor->paragraphLength( paragraph ) > 0 ) + mOldSigText = mOldSigText + "\n"; + if ( mOldSigText.startsWith( "\n" ) ) + mOldSigText = mOldSigText.remove( 0, 1 ); + + // If we are inserting into a wordwrapped line, add a newline at the start to make + // the text edit hard-wrap the line here + if ( index != 0 ) + mOldSigText = "\n" + mOldSigText; + + mEditor->insertAt( mOldSigText, paragraph, index ); + break; } mEditor->update(); mEditor->setModified(mod); @@ -4390,8 +4549,13 @@ void KMComposeWin::insertSignature( bool append, int pos ) } else { // for append and prepend, move the cursor to 0,0, for insertAt, // keep it in the same row, but move to first column - mEditor->setCursorPosition( pos, 0 ); - if ( !append && pos == 0 ) + if ( index == 0 ) { + mEditor->setCursorPosition( paragraph, 0 ); + } else { + // For word-wrapped lines, we have created a new paragraph, so change to that one + mEditor->setCursorPosition( paragraph + 1, 0 ); + } + if ( placement == Prepend || placement == Append ) mEditor->setContentsPos( 0, 0 ); } mEditor->sync(); @@ -4715,7 +4879,7 @@ void KMComposeWin::slotIdentityChanged( uint uoid ) } } - if ( !mBtnTransport->isChecked() ) { + if ( !mBtnTransport->isChecked() && !mIgnoreStickyFields ) { TQString transp = ident.transport(); if ( transp.isEmpty() ) { @@ -4727,10 +4891,12 @@ void KMComposeWin::slotIdentityChanged( uint uoid ) setTransport( transp ); } - mDictionaryCombo->setCurrentByDictionary( ident.dictionary() ); + if ( !mBtnDictionary->isChecked() && !mIgnoreStickyFields ) { + mDictionaryCombo->setCurrentByDictionary( ident.dictionary() ); + } - if ( !mBtnFcc->isChecked() ) { - setFcc( ident.fcc() ); + if ( !mBtnFcc->isChecked() && !mPreventFccOverwrite ) { + setFcc( ident.fcc() ); } TQString edtText = mEditor->text(); @@ -4741,27 +4907,40 @@ void KMComposeWin::slotIdentityChanged( uint uoid ) identityManager()-> identityForUoidOrDefault( mMsg->headerField( "X-KMail-Identity" ). stripWhiteSpace().toUInt() ); - mOldSigText = id.signatureText(); + mOldSigText = GlobalSettings::self()->prependSignature() ? id.signature().rawText() : id.signatureText(); } - // try to truncate the old sig - // First remove any trailing whitespace - while ( !edtText.isEmpty() && edtText[edtText.length()-1].isSpace() ) - edtText.truncate( edtText.length() - 1 ); - // From the sig too, just in case - while ( !mOldSigText.isEmpty() && mOldSigText[mOldSigText.length()-1].isSpace() ) - mOldSigText.truncate( mOldSigText.length() - 1 ); - if( edtText.endsWith( mOldSigText ) ) - edtText.truncate( edtText.length() - mOldSigText.length() ); + if ( !GlobalSettings::prependSignature() ) { + // try to truncate the old sig + // First remove any trailing whitespace + while ( !edtText.isEmpty() && edtText[edtText.length()-1].isSpace() ) + edtText.truncate( edtText.length() - 1 ); + // From the sig too, just in case + while ( !mOldSigText.isEmpty() && mOldSigText[mOldSigText.length()-1].isSpace() ) + mOldSigText.truncate( mOldSigText.length() - 1 ); + + if ( edtText.endsWith( mOldSigText ) ) + edtText.truncate( edtText.length() - mOldSigText.length() ); - // now append the new sig - mOldSigText = ident.signatureText(); - if( ( !mOldSigText.isEmpty() ) && - ( GlobalSettings::self()->autoTextSignature() == "auto" ) ) { - edtText.append( mOldSigText ); + // now append the new sig + mOldSigText = ident.signatureText(); + if( ( !mOldSigText.isEmpty() ) && + ( GlobalSettings::self()->autoTextSignature() == "auto" ) ) { + edtText.append( mOldSigText ); + } + mEditor->setText( edtText ); + } else { + const int pos = edtText.find( mOldSigText ); + if ( pos >= 0 && !mOldSigText.isEmpty() ) { + const int oldLength = mOldSigText.length(); + mOldSigText = "\n\n"+ ident.signature().rawText() + "\n"; // see insertSignature() + edtText = edtText.replace( pos, oldLength, mOldSigText ); + mEditor->setText( edtText ); + } else { + insertSignature( Append ); + } } - mEditor->setText( edtText ); // disable certain actions if there is no PGP user identity set // for this profile @@ -4897,9 +5076,6 @@ void KMComposeWin::updateAutoSave() void KMComposeWin::setAutoSaveFilename( const TQString & filename ) { - if ( !mAutoSaveFilename.isEmpty() ) - KMFolderMaildir::removeFile( KMKernel::localDataPath() + "autosave", - mAutoSaveFilename ); mAutoSaveFilename = filename; } @@ -4957,8 +5133,6 @@ void KMComposeWin::slotFolderRemoved(KMFolder* folder) void KMComposeWin::editorFocusChanged(bool gained) { mPasteQuotation->setEnabled(gained); - mAddQuoteChars->setEnabled(gained); - mRemQuoteChars->setEnabled(gained); } void KMComposeWin::slotSetAlwaysSend( bool bAlways ) diff --git a/kmail/kmcomposewin.h b/kmail/kmcomposewin.h index 7f003d44a..f38a8f682 100644 --- a/kmail/kmcomposewin.h +++ b/kmail/kmcomposewin.h @@ -96,7 +96,7 @@ namespace GpgME { } //----------------------------------------------------------------------------- -class KMComposeWin : public KMail::Composer, virtual public MailComposerIface +class KMComposeWin : public KMail::Composer, public MailComposerIface { Q_OBJECT friend class ::KMEdit; @@ -161,6 +161,32 @@ public: // kmkernel, kmcommands, callback void disableWordWrap(); + /** Don't check if there are too many recipients for a mail, + * eg. when sending out invitations. + */ + void disableRecipientNumberCheck(); + + /** Don't check for forgotten attachments for a mail, + * eg. when sending out invitations. + */ + void disableForgottenAttachmentsCheck(); + + /** + * Ignore the "sticky" setting of the transport combo box and prefer the X-KMail-Transport + * header field of the message instead. + * Do the same for the identity combo box, don't obey the "sticky" setting but use the + * X-KMail-Identity header field instead. + * + * This is useful when sending out invitations, since you don't see the GUI and want the + * identity and transport to be set to the values stored in the messages. + */ + void ignoreStickyFields(); + + /** + * Returns @c true while the message composing is in progress. + */ + bool isComposing() const { return mComposer != 0; } + private: // kmedit /** * Returns message of the composer. To apply the user changes to the @@ -524,14 +550,22 @@ private: */ void rethinkHeaderLine( int aValue, int aMask, int& aRow, - const TQString &aLabelStr, TQLabel* aLbl, + TQLabel* aLbl, TQLineEdit* aEdt, TQPushButton* aBtn = 0, const TQString &toolTip = TQString::null, const TQString &whatsThis = TQString::null ); void rethinkHeaderLine( int value, int mask, int& row, - const TQString& labelStr, TQLabel* lbl, - TQComboBox* cbx, TQCheckBox *chk ); + TQLabel* lbl, TQComboBox* cbx, TQCheckBox *chk ); + + /** + * Checks how many recipients are and warns if there are too many. + * @return true, if the user accepted the warning and the message should be sent + */ + bool checkRecipientNumber() const; + + + bool checkTransport() const; /** * Initialization methods @@ -691,11 +725,13 @@ private: */ void setTransport( const TQString & transport ); + enum SignaturePlacement { Append, Prepend, AtCursor }; + /** * Helper to insert the signature of the current identy at the * beginning or end of the editor. */ - void insertSignature( bool append = true, int pos = 0 ); + void insertSignature( SignaturePlacement placement = Append ); private slots: /** * Compress an attachemnt with the given index @@ -717,11 +753,12 @@ private: TQLabel *mLblIdentity, *mLblTransport, *mLblFcc; TQLabel *mLblFrom, *mLblReplyTo, *mLblTo, *mLblCc, *mLblBcc, *mLblSubject; TQLabel *mDictionaryLabel; - TQCheckBox *mBtnIdentity, *mBtnTransport, *mBtnFcc; + TQCheckBox *mBtnIdentity, *mBtnDictionary, *mBtnTransport, *mBtnFcc; TQPushButton *mBtnTo, *mBtnCc, *mBtnBcc, /* *mBtnFrom, */ *mBtnReplyTo; bool mSpellCheckInProgress; bool mDone; bool mAtmModified; + TQListViewItem *mAtmSelectNew; KMEdit* mEditor; TQGridLayout* mGrid; @@ -906,6 +943,11 @@ private: * accidentally moving the cursor. */ bool mPreserveUserCursorPosition; + + bool mPreventFccOverwrite; + bool mCheckForRecipients; + bool mCheckForForgottenAttachments; + bool mIgnoreStickyFields; }; #endif diff --git a/kmail/kmedit.cpp b/kmail/kmedit.cpp index bb1bb88f6..6fdc3352c 100644 --- a/kmail/kmedit.cpp +++ b/kmail/kmedit.cpp @@ -200,10 +200,10 @@ void KMEdit::contentsDropEvent(TQDropEvent *e) } else kdDebug(5006) << "KMEdit::contentsDropEvent, unable to add dropped object" << endl; - } + } else if( e->provides("text/x-textsnippet") ) { emit insertSnippet(); - } + } else { KEdit::contentsDropEvent(e); } @@ -214,7 +214,8 @@ KMEdit::KMEdit(TQWidget *parent, KMComposeWin* composer, const char *name) : KEdit( parent, name ), mComposer( composer ), - mKSpell( 0 ), + mKSpellForDialog( 0 ), + mSpeller( 0 ), mSpellConfig( autoSpellConfig ), mSpellingFilter( 0 ), mExtEditorTempFile( 0 ), @@ -222,19 +223,30 @@ KMEdit::KMEdit(TQWidget *parent, KMComposeWin* composer, mExtEditorProcess( 0 ), mUseExtEditor( false ), mWasModifiedBeforeSpellCheck( false ), - mSpellChecker( 0 ), + mHighlighter( 0 ), mSpellLineEdit( false ), mPasteMode( QClipboard::Clipboard ) { + connect( this, TQT_SIGNAL(selectionChanged()), this, TQT_SLOT(slotSelectionChanged()) ); installEventFilter(this); KCursor::setAutoHideCursor( this, true, true ); setOverwriteEnabled( true ); + createSpellers(); + connect( mSpellConfig, TQT_SIGNAL( configChanged() ), + this, TQT_SLOT( createSpellers() ) ); + connect( mSpeller, TQT_SIGNAL( death() ), + this, TQT_SLOT( spellerDied() ) ); } +void KMEdit::createSpellers() +{ + delete mSpeller; + mSpeller = new KMSpell( this, TQT_SLOT( spellerReady( KSpell * ) ), mSpellConfig ); +} void KMEdit::initializeAutoSpellChecking() { - if ( mSpellChecker ) + if ( mHighlighter ) return; // already initialized TQColor defaultColor1( 0x00, 0x80, 0x00 ); // defaults from kmreaderwin.cpp TQColor defaultColor2( 0x00, 0x70, 0x00 ); @@ -252,14 +264,14 @@ void KMEdit::initializeAutoSpellChecking() TQColor col3 = readerConfig.readColorEntry( "QuotedText2", &defaultColor2 ); TQColor col4 = readerConfig.readColorEntry( "QuotedText1", &defaultColor1 ); TQColor misspelled = readerConfig.readColorEntry( "MisspelledColor", &c ); - mSpellChecker = new KDictSpellingHighlighter( this, /*active*/ true, - /*autoEnabled*/ false, - /*spellColor*/ misspelled, - /*colorQuoting*/ true, - col1, col2, col3, col4, - mSpellConfig ); - - connect( mSpellChecker, TQT_SIGNAL(newSuggestions(const TQString&, const TQStringList&, unsigned int)), + mHighlighter = new KMSyntaxHighter( this, /*active*/ true, + /*autoEnabled*/ false, + /*spellColor*/ misspelled, + /*colorQuoting*/ true, + col1, col2, col3, col4, + mSpellConfig ); + + connect( mHighlighter, TQT_SIGNAL(newSuggestions(const TQString&, const TQStringList&, unsigned int)), this, TQT_SLOT(addSuggestion(const TQString&, const TQStringList&, unsigned int)) ); } @@ -279,8 +291,8 @@ TQPopupMenu *KMEdit::createPopupMenu( const TQPoint& pos ) void KMEdit::deleteAutoSpellChecking() { // because the highlighter doesn't support RichText, delete its instance. - delete mSpellChecker; - mSpellChecker =0; + delete mHighlighter; + mHighlighter =0; } void KMEdit::addSuggestion(const TQString& text, const TQStringList& lst, unsigned int ) @@ -290,8 +302,8 @@ void KMEdit::addSuggestion(const TQString& text, const TQStringList& lst, unsign void KMEdit::setSpellCheckingActive(bool spellCheckingActive) { - if ( mSpellChecker ) { - mSpellChecker->setActive(spellCheckingActive); + if ( mHighlighter ) { + mHighlighter->setActive(spellCheckingActive); } } @@ -300,10 +312,16 @@ KMEdit::~KMEdit() { removeEventFilter(this); - delete mKSpell; - delete mSpellChecker; - mSpellChecker = 0; + if ( mSpeller ) { + // The speller needs some time to clean up, so trigger cleanup and let it delete itself + mSpeller->setAutoDelete( true ); + mSpeller->cleanUp(); + mSpeller = 0; + } + delete mKSpellForDialog; + delete mHighlighter; + mHighlighter = 0; } @@ -343,6 +361,55 @@ unsigned int KMEdit::lineBreakColumn() const return lineBreakColumn; } +KMSpell::KMSpell( TQObject *receiver, const char *slot, KSpellConfig *spellConfig ) + : KSpell( 0, TQString(), receiver, slot, spellConfig ) +{ +} + +KMSyntaxHighter::KMSyntaxHighter( TQTextEdit *textEdit, + bool spellCheckingActive, + bool autoEnable, + const TQColor& spellColor, + bool colorQuoting, + const TQColor& QuoteColor0, + const TQColor& QuoteColor1, + const TQColor& QuoteColor2, + const TQColor& QuoteColor3, + KSpellConfig *spellConfig ) + : KDictSpellingHighlighter( textEdit, spellCheckingActive, autoEnable, spellColor, colorQuoting, + QuoteColor0, QuoteColor1, QuoteColor2, QuoteColor3, spellConfig ) +{ +} + +bool KMSyntaxHighter::isMisspelled( const TQString &word ) +{ + if ( mIgnoredWords.contains( word ) ) { + return false; + } + else { + return KDictSpellingHighlighter::isMisspelled( word ); + } +} + +void KMSyntaxHighter::ignoreWord( const TQString &word ) +{ + mIgnoredWords << word; +} + +TQStringList KMSyntaxHighter::ignoredWords() const +{ + return mIgnoredWords; +} + +void KMEdit::spellerDied() +{ + mSpeller = 0; +} + +void KMEdit::spellerReady( KSpell *spell ) +{ + Q_ASSERT( mSpeller == spell ); +} bool KMEdit::eventFilter(TQObject*o, TQEvent* e) { @@ -439,7 +506,6 @@ bool KMEdit::eventFilter(TQObject*o, TQEvent* e) if( !word.isEmpty() && mReplacements.contains( word ) ) { KPopupMenu p; - p.insertTitle( i18n("Suggestions") ); //Add the suggestions to the popup menu TQStringList reps = mReplacements[word]; @@ -453,13 +519,34 @@ bool KMEdit::eventFilter(TQObject*o, TQEvent* e) } else { - p.insertItem( TQString::fromLatin1("No Suggestions"), -2 ); + p.setItemEnabled( p.insertItem( i18n( "No Suggestions" ), -2 ), false ); + } + + int addToDictionaryId = -42; + int ignoreId = -43; + if ( mSpeller && mSpeller->status() == KSpell::Running ) { + p.insertSeparator(); + addToDictionaryId = p.insertItem( i18n( "Add to Dictionary" ) ); + ignoreId = p.insertItem( i18n( "Ignore All" ) ); } //Execute the popup inline - int id = p.exec( mapToGlobal( event->pos() ) ); + const int id = p.exec( mapToGlobal( event->pos() ) ); - if( id > -1 ) + if ( id == ignoreId ) { + mHighlighter->ignoreWord( word ); + mHighlighter->rehighlight(); + } + if ( id == addToDictionaryId ) { + mSpeller->addPersonal( word ); + mSpeller->writePersonalDictionary(); + if ( mHighlighter ) { + // Wait a bit until reloading the highlighter, the mSpeller first needs to finish saving + // the personal word list. + TQTimer::singleShot( 200, mHighlighter, TQT_SLOT( slotLocalSpellConfigChanged() ) ); + } + } + else if( id > -1 ) { //Save the cursor position int parIdx = 1, txtIdx = 1; @@ -472,6 +559,12 @@ bool KMEdit::eventFilter(TQObject*o, TQEvent* e) txtIdx += mReplacements[word][id].length() - word.length(); setCursorPosition(parIdx, txtIdx); } + + if ( id == addToDictionaryId || id == ignoreId ) { + // No longer misspelled: Either added to dictionary or ignored + mReplacements.remove( word ); + } + //Cancel original event return true; } @@ -494,10 +587,10 @@ int KMEdit::autoSpellChecking( bool on ) KMessageBox::sorry(this, i18n("Automatic spellchecking is not possible on text with markup.")); return -1; } - if ( mSpellChecker ) { + if ( mHighlighter ) { // don't autoEnable spell checking if the user turned spell checking off - mSpellChecker->setAutomatic( on ); - mSpellChecker->setActive( on ); + mHighlighter->setAutomatic( on ); + mHighlighter->setActive( on ); } return 1; } @@ -551,54 +644,57 @@ bool KMEdit::checkExternalEditorFinished() { void KMEdit::spellcheck() { - if ( mKSpell ) + if ( mKSpellForDialog ) return; mWasModifiedBeforeSpellCheck = isModified(); mSpellLineEdit = !mSpellLineEdit; // maybe for later, for now plaintext is given to KSpell // if (textFormat() == Qt::RichText ) { // kdDebug(5006) << "KMEdit::spellcheck, spellchecking for RichText" << endl; -// mKSpell = new KSpell(this, i18n("Spellcheck - KMail"), this, +// mKSpellForDialog = new KSpell(this, i18n("Spellcheck - KMail"), this, // TQT_SLOT(slotSpellcheck2(KSpell*)),0,true,false,KSpell::HTML); // } // else { - mKSpell = new KSpell(this, i18n("Spellcheck - KMail"), this, - TQT_SLOT(slotSpellcheck2(KSpell*))); + + // Don't use mSpellConfig here. Reason is that the spell dialog, KSpellDlg, uses its own + // spell config, and therefore the two wouldn't be in sync. + mKSpellForDialog = new KSpell( this, i18n("Spellcheck - KMail"), this, + TQT_SLOT(slotSpellcheck2(KSpell*))/*, mSpellConfig*/ ); // } TQStringList l = KSpellingHighlighter::personalWords(); for ( TQStringList::Iterator it = l.begin(); it != l.end(); ++it ) { - mKSpell->addPersonal( *it ); + mKSpellForDialog->addPersonal( *it ); } - connect (mKSpell, TQT_SIGNAL( death()), + connect (mKSpellForDialog, TQT_SIGNAL( death()), this, TQT_SLOT (slotSpellDone())); - connect (mKSpell, TQT_SIGNAL (misspelling (const TQString &, const TQStringList &, unsigned int)), + connect (mKSpellForDialog, TQT_SIGNAL (misspelling (const TQString &, const TQStringList &, unsigned int)), this, TQT_SLOT (slotMisspelling (const TQString &, const TQStringList &, unsigned int))); - connect (mKSpell, TQT_SIGNAL (corrected (const TQString &, const TQString &, unsigned int)), + connect (mKSpellForDialog, TQT_SIGNAL (corrected (const TQString &, const TQString &, unsigned int)), this, TQT_SLOT (slotCorrected (const TQString &, const TQString &, unsigned int))); - connect (mKSpell, TQT_SIGNAL (done(const TQString &)), + connect (mKSpellForDialog, TQT_SIGNAL (done(const TQString &)), this, TQT_SLOT (slotSpellResult (const TQString&))); } void KMEdit::cut() { KEdit::cut(); - if ( textFormat() != Qt::RichText && mSpellChecker ) - mSpellChecker->restartBackgroundSpellCheck(); + if ( textFormat() != Qt::RichText && mHighlighter ) + mHighlighter->restartBackgroundSpellCheck(); } void KMEdit::clear() { KEdit::clear(); - if ( textFormat() != Qt::RichText && mSpellChecker ) - mSpellChecker->restartBackgroundSpellCheck(); + if ( textFormat() != Qt::RichText && mHighlighter ) + mHighlighter->restartBackgroundSpellCheck(); } void KMEdit::del() { KEdit::del(); - if ( textFormat() != Qt::RichText && mSpellChecker ) - mSpellChecker->restartBackgroundSpellCheck(); + if ( textFormat() != Qt::RichText && mHighlighter ) + mHighlighter->restartBackgroundSpellCheck(); } void KMEdit::paste() @@ -620,6 +716,54 @@ void KMEdit::contentsMouseReleaseEvent( TQMouseEvent * e ) mPasteMode = QClipboard::Clipboard; } +void KMEdit::contentsMouseDoubleClickEvent( TQMouseEvent *e ) +{ + bool handled = false; + if ( e->button() == TQt::LeftButton ) { + + // Get the cursor position for the place where the user clicked to + int paragraphPos; + int charPos = charAt ( e->pos(), ¶graphPos ); + TQString paraText = text( paragraphPos ); + + // Now select the word under the cursor + if ( charPos >= 0 && static_cast( charPos ) <= paraText.length() ) { + + // Start the selection where the user clicked + int start = charPos; + unsigned int end = charPos; + + // Extend the selection to the left, until we reach a non-letter and non-digit char + for (;;) { + if ( ( start - 1 ) < 0 ) + break; + TQChar charToTheLeft = paraText.at( start - 1 ); + if ( charToTheLeft.isLetter() || charToTheLeft.isDigit() ) + start--; + else + break; + } + + // Extend the selection to the left, until we reach a non-letter and non-digit char + for (;;) { + if ( ( end + 1 ) >= paraText.length() ) + break; + TQChar charToTheRight = paraText.at( end + 1 ); + if ( charToTheRight.isLetter() || charToTheRight.isDigit() ) + end++; + else + break; + } + + setSelection( paragraphPos, start, paragraphPos, end + 1 ); + handled = true; + } + } + + if ( !handled ) + return KEdit::contentsMouseDoubleClickEvent( e ); +} + void KMEdit::slotMisspelling(const TQString &text, const TQStringList &lst, unsigned int pos) { kdDebug(5006)<<"void KMEdit::slotMisspelling(const TQString &text, const TQStringList &lst, unsigned int pos) : "<ignoredWords().size(); i++ ) + mKSpellForDialog->ignore( mHighlighter->ignoredWords()[i] ); + } + if( !mSpellLineEdit) { spellcheck_start(); @@ -682,10 +832,10 @@ void KMEdit::slotSpellcheck2(KSpell*) mSpellingFilter = new SpellingFilter(plaintext.text(), quotePrefix, SpellingFilter::FilterUrls, SpellingFilter::FilterEmailAddresses); - mKSpell->check(mSpellingFilter->filteredText()); + mKSpellForDialog->check(mSpellingFilter->filteredText()); } else if( mComposer ) - mKSpell->check( mComposer->sujectLineWidget()->text()); + mKSpellForDialog->check( mComposer->sujectLineWidget()->text()); } void KMEdit::slotSpellResult(const TQString &s) @@ -693,7 +843,7 @@ void KMEdit::slotSpellResult(const TQString &s) if( !mSpellLineEdit) spellcheck_stop(); - int dlgResult = mKSpell->dlgResult(); + int dlgResult = mKSpellForDialog->dlgResult(); if ( dlgResult == KS_CANCEL ) { if( mSpellLineEdit) @@ -711,7 +861,7 @@ void KMEdit::slotSpellResult(const TQString &s) setModified(true); } } - mKSpell->cleanUp(); + mKSpellForDialog->cleanUp(); KDictSpellingHighlighter::dictionaryChanged(); emit spellcheck_done( dlgResult ); @@ -720,9 +870,9 @@ void KMEdit::slotSpellResult(const TQString &s) void KMEdit::slotSpellDone() { kdDebug(5006)<<" void KMEdit::slotSpellDone()\n"; - KSpell::spellStatus status = mKSpell->status(); - delete mKSpell; - mKSpell = 0; + KSpell::spellStatus status = mKSpellForDialog->status(); + delete mKSpellForDialog; + mKSpellForDialog = 0; kdDebug(5006) << "spelling: delete SpellingFilter" << endl; delete mSpellingFilter; @@ -763,4 +913,20 @@ void KMEdit::setCursorPositionFromStart( unsigned int pos ) { ensureCursorVisible(); } +int KMEdit::indexOfCurrentLineStart( int paragraph, int index ) +{ + Q_ASSERT( paragraph >= 0 && paragraph < paragraphs() ); + Q_ASSERT( index >= 0 && ( index == 0 || index < paragraphLength( paragraph ) ) ); + + const int startLine = lineOfChar( paragraph, index ); + Q_ASSERT( startLine >= 0 && startLine < linesOfParagraph( paragraph ) ); + for ( int curIndex = index; curIndex >= 0; curIndex-- ) { + const int line = lineOfChar( paragraph, curIndex ); + if ( line != startLine ) { + return curIndex + 1; + } + } + return 0; +} + #include "kmedit.moc" diff --git a/kmail/kmedit.h b/kmail/kmedit.h index b8419cda8..855c29ead 100644 --- a/kmail/kmedit.h +++ b/kmail/kmedit.h @@ -7,20 +7,61 @@ #include #include +#include +#include #include #include #include class KMComposeWin; class KSpellConfig; -class KSpell; class SpellingFilter; class KTempFile; -class KDictSpellingHighlighter; class KDirWatch; class KProcess; class TQPopupMenu; +/** + * Reimplemented to make writePersonalDictionary() public, which we call everytime after + * adding a word to the dictionary (for safety's sake and because the highlighter needs to reload + * the personal word list, and for that, it needs to be written to disc) + */ +class KMSpell : public KSpell +{ + public: + + KMSpell( TQObject *receiver, const char *slot, KSpellConfig *spellConfig ); + using KSpell::writePersonalDictionary; +}; + +/** + * Reimplemented to add support for ignored words + */ +class KMSyntaxHighter : public KDictSpellingHighlighter +{ + public: + + KMSyntaxHighter( TQTextEdit *textEdit, + bool spellCheckingActive = true, + bool autoEnable = true, + const TQColor& spellColor = red, + bool colorQuoting = false, + const TQColor& QuoteColor0 = black, + const TQColor& QuoteColor1 = TQColor( 0x00, 0x80, 0x00 ), + const TQColor& QuoteColor2 = TQColor( 0x00, 0x70, 0x00 ), + const TQColor& QuoteColor3 = TQColor( 0x00, 0x60, 0x00 ), + KSpellConfig *spellConfig = 0 ); + + /** Reimplemented */ + virtual bool isMisspelled( const TQString &word ); + + void ignoreWord( const TQString &word ); + + TQStringList ignoredWords() const; + + private: + TQStringList mIgnoredWords; +}; class KMEdit : public KEdit { Q_OBJECT @@ -74,12 +115,15 @@ public: /** set cursor to absolute position pos */ void setCursorPositionFromStart(unsigned int pos); + int indexOfCurrentLineStart( int paragraph, int index ); + signals: void spellcheck_done(int result); void attachPNGImageData(const TQByteArray &image); void pasteImage(); void focusUp(); void focusChanged( bool ); + void selectionAvailable( bool ); void insertSnippet(); public slots: void initializeAutoSpellChecking(); @@ -100,11 +144,29 @@ protected: */ bool eventFilter(TQObject*, TQEvent*); void keyPressEvent( TQKeyEvent* ); - + void contentsMouseReleaseEvent( TQMouseEvent * e ); + /// Reimplemented to select words under the cursor on double-clicks in our way, + /// not the broken TQt way (https://issues.kolab.org/issue4089) + virtual void contentsMouseDoubleClickEvent( TQMouseEvent *e ); + private slots: void slotExternalEditorTempFileChanged( const TQString & fileName ); + void slotSelectionChanged() { + // use !text.isEmpty() here, as null-selections exist, but make no sense + emit selectionAvailable( !selectedText().isEmpty() ); + } + + /// Called when mSpeller is ready to rumble. Does nothing, but KSpell requires a slot as otherwise + /// it will show a dialog itself, which we want to avoid. + void spellerReady( KSpell *speller ); + + /// Called when mSpeller died for some reason. + void spellerDied(); + + /// Re-creates the spellers, called when the dictionary is changed + void createSpellers(); private: void killExternalEditor(); @@ -112,7 +174,14 @@ private: private: KMComposeWin* mComposer; - KSpell *mKSpell; + // This is the speller used for the spellcheck dialog. It is only active as long as the spellcheck + // dialog is shown + KSpell *mKSpellForDialog; + + // This is the speller used when right-clicking a word and choosing "add to dictionary". It lives + // as long as the composer lives. + KMSpell *mSpeller; + KSpellConfig *mSpellConfig; TQMap mReplacements; SpellingFilter* mSpellingFilter; @@ -122,7 +191,7 @@ private: bool mUseExtEditor; TQString mExtEditor; bool mWasModifiedBeforeSpellCheck; - KDictSpellingHighlighter *mSpellChecker; + KMSyntaxHighter *mHighlighter; bool mSpellLineEdit; QClipboard::Mode mPasteMode; }; diff --git a/kmail/kmfawidgets.cpp b/kmail/kmfawidgets.cpp index f5426f255..370d796cb 100644 --- a/kmail/kmfawidgets.cpp +++ b/kmail/kmfawidgets.cpp @@ -17,6 +17,7 @@ #include #include +#include //============================================================================= // @@ -30,14 +31,18 @@ KMFilterActionWithAddressWidget::KMFilterActionWithAddressWidget( TQWidget* pare TQHBoxLayout *hbl = new TQHBoxLayout(this); hbl->setSpacing(4); mLineEdit = new KLineEdit(this); + mLineEdit->setName( "addressEdit" ); hbl->addWidget( mLineEdit, 1 /*stretch*/ ); mBtn = new TQPushButton( TQString::null ,this ); mBtn->setPixmap( BarIcon( "contents", KIcon::SizeSmall ) ); mBtn->setFixedHeight( mLineEdit->sizeHint().height() ); + TQToolTip::add( mBtn, i18n( "Open Address Book" ) ); hbl->addWidget( mBtn ); connect( mBtn, TQT_SIGNAL(clicked()), - this, TQT_SLOT(slotAddrBook()) ); + this, TQT_SLOT(slotAddrBook()) ); + connect( mLineEdit, TQT_SIGNAL( textChanged(const TQString&) ), + this, TQT_SIGNAL( textChanged(const TQString&) ) ); } void KMFilterActionWithAddressWidget::slotAddrBook() diff --git a/kmail/kmfawidgets.h b/kmail/kmfawidgets.h index 716cd2aae..5a6990a8b 100644 --- a/kmail/kmfawidgets.h +++ b/kmail/kmfawidgets.h @@ -25,6 +25,10 @@ public: TQString text() const { return mLineEdit->text(); } void setText( const TQString & aString ) { mLineEdit->setText( aString ); } +signals: + // Forwarded from the internal text edit + void textChanged(const TQString&); + protected slots: void slotAddrBook(); diff --git a/kmail/kmfilteraction.cpp b/kmail/kmfilteraction.cpp index 9fce0565b..9b277d8e7 100644 --- a/kmail/kmfilteraction.cpp +++ b/kmail/kmfilteraction.cpp @@ -9,6 +9,8 @@ #include "kmfilteraction.h" +#include "customtemplates.h" +#include "customtemplates_kfg.h" #include "kmcommands.h" #include "kmmsgpart.h" #include "kmfiltermgr.h" @@ -27,7 +29,6 @@ using KPIM::CollectingProcess; #include "folderrequester.h" using KMail::FolderRequester; #include "kmmsgbase.h" -#include "templateparser.h" #include "messageproperty.h" #include "actionscheduler.h" using KMail::MessageProperty; @@ -48,6 +49,8 @@ using KMail::RegExpLineEdit; #include #include #include +#include +#include #include @@ -1433,14 +1436,26 @@ bool KMFilterActionCopy::requiresBody(KMMsgBase*) const //============================================================================= // KMFilterActionForward - forward to -// Forward message to another user +// Forward message to another user, with a defined template //============================================================================= class KMFilterActionForward: public KMFilterActionWithAddress { public: KMFilterActionForward(); - virtual ReturnCode process(KMMessage* msg) const; + virtual ReturnCode process( KMMessage* msg ) const; + virtual TQWidget* createParamWidget( TQWidget* parent ) const; + virtual void applyParamWidgetValue( TQWidget* paramWidget ); + virtual void setParamWidgetValue( TQWidget* paramWidget ) const; + virtual void clearParamWidget( TQWidget* paramWidget ) const; + virtual void argsFromString( const TQString argsStr ); + virtual const TQString argsAsString() const; + virtual const TQString displayString() const; + static KMFilterAction* newAction(void); + +private: + + mutable TQString mTemplate; }; KMFilterAction* KMFilterActionForward::newAction(void) @@ -1460,89 +1475,152 @@ KMFilterAction::ReturnCode KMFilterActionForward::process(KMMessage* aMsg) const // avoid endless loops when this action is used in a filter // which applies to sent messages - if ( KMMessage::addressIsInAddressList( mParameter, aMsg->to() ) ) + if ( KMMessage::addressIsInAddressList( mParameter, aMsg->to() ) ) { + kdWarning(5006) << "Attempt to forward to receipient of original message, ignoring." << endl; return ErrorButGoOn; + } + + KMMessage *fwdMsg = aMsg->createForward( mTemplate ); + fwdMsg->setTo( fwdMsg->to() + ',' + mParameter ); + + if ( !kmkernel->msgSender()->send( fwdMsg, KMail::MessageSender::SendDefault ) ) { + kdWarning(5006) << "KMFilterAction: could not forward message (sending failed)" << endl; + return ErrorButGoOn; // error: couldn't send + } + else + sendMDN( aMsg, KMime::MDN::Dispatched ); - // Create the forwarded message by hand to make forwarding of messages with - // attachments work. - // Note: This duplicates a lot of code from KMMessage::createForward() and - // KMComposeWin::applyChanges(). - // ### FIXME: Remove the code duplication again. + // (the msgSender takes ownership of the message, so don't delete it here) - KMMessage* msg = new KMMessage; + return GoOn; +} - msg->initFromMessage( aMsg ); +TQWidget* KMFilterActionForward::createParamWidget( TQWidget* parent ) const +{ + TQWidget *addressAndTemplate = new TQWidget( parent ); + TQHBoxLayout *hBox = new TQHBoxLayout( addressAndTemplate ); + TQWidget *addressEdit = KMFilterActionWithAddress::createParamWidget( addressAndTemplate ); + addressEdit->setName( "addressEdit" ); + hBox->addWidget( addressEdit ); - // TQString st = TQString::fromUtf8( aMsg->createForwardBody() ); + KLineEdit *lineEdit = dynamic_cast( addressEdit->child( "addressEdit" ) ); + Q_ASSERT( lineEdit ); + TQToolTip::add( lineEdit, i18n( "The addressee the message will be forwarded to" ) ); + TQWhatsThis::add( lineEdit, i18n( "The filter will forward the message to the addressee entered here." ) ); - TemplateParser parser( msg, TemplateParser::Forward, - aMsg->body(), false, false, false, false); - parser.process( aMsg ); + TQComboBox *templateCombo = new TQComboBox( addressAndTemplate ); + templateCombo->setName( "templateCombo" ); + hBox->addWidget( templateCombo ); - QCString - encoding = KMMsgBase::autoDetectCharset( aMsg->charset(), - KMMessage::preferredCharsets(), - msg->body() ); - if( encoding.isEmpty() ) - encoding = "utf-8"; - TQCString str = KMMsgBase::codecForName( encoding )->fromUnicode( msg->body() ); + templateCombo->insertItem( i18n( "Default Template" ) ); + TQStringList templateNames = GlobalSettingsBase::self()->customTemplates(); + for ( TQStringList::const_iterator it = templateNames.begin(); it != templateNames.end(); + it++ ) { + CTemplates templat( *it ); + if ( templat.type() == CustomTemplates::TForward || + templat.type() == CustomTemplates::TUniversal ) + templateCombo->insertItem( *it ); + } + templateCombo->setEnabled( templateCombo->count() > 1 ); + TQToolTip::add( templateCombo, i18n( "The template used when forwarding" ) ); + TQWhatsThis::add( templateCombo, i18n( "Set the forwarding template that will be used with this filter." ) ); - msg->setCharset( encoding ); - msg->setTo( mParameter ); - msg->setSubject( "Fwd: " + aMsg->subject() ); + return addressAndTemplate; +} - bool isQP = kmkernel->msgSender()->sendQuotedPrintable(); +void KMFilterActionForward::applyParamWidgetValue( TQWidget* paramWidget ) +{ + // Use findChildren when porting to KDE 4 + TQWidget *addressEdit = dynamic_cast( paramWidget->child( "addressEdit" ) ); + Q_ASSERT( addressEdit ); + KMFilterActionWithAddress::applyParamWidgetValue( addressEdit ); - if( aMsg->numBodyParts() == 0 ) - { - msg->setAutomaticFields( true ); - msg->setHeaderField( "Content-Type", "text/plain" ); - // msg->setCteStr( isQP ? "quoted-printable": "8bit" ); - TQValueList dummy; - msg->setBodyAndGuessCte(str, dummy, !isQP); - msg->setCharset( encoding ); - if( isQP ) - msg->setBodyEncoded( str ); - else - msg->setBody( str ); + TQComboBox *templateCombo = dynamic_cast( paramWidget->child( "templateCombo" ) ); + Q_ASSERT( templateCombo ); + + if ( templateCombo->currentItem() == 0 ) { + // Default template, so don't use a custom one + mTemplate = TQString::null; } - else - { - KMMessagePart bodyPart, msgPart; - - msg->removeHeaderField( "Content-Type" ); - msg->removeHeaderField( "Content-Transfer-Encoding" ); - msg->setAutomaticFields( true ); - msg->setBody( "This message is in MIME format.\n\n" ); - - bodyPart.setTypeStr( "text" ); - bodyPart.setSubtypeStr( "plain" ); - // bodyPart.setCteStr( isQP ? "quoted-printable": "8bit" ); - TQValueList dummy; - bodyPart.setBodyAndGuessCte(str, dummy, !isQP); - bodyPart.setCharset( encoding ); - bodyPart.setBodyEncoded( str ); - msg->addBodyPart( &bodyPart ); - - for( int i = 0; i < aMsg->numBodyParts(); i++ ) - { - aMsg->bodyPart( i, &msgPart ); - if( i > 0 || qstricmp( msgPart.typeStr(), "text" ) != 0 ) - msg->addBodyPart( &msgPart ); + else { + mTemplate = templateCombo->currentText(); + } +} + +void KMFilterActionForward::setParamWidgetValue( TQWidget* paramWidget ) const +{ + TQWidget *addressEdit = dynamic_cast( paramWidget->child( "addressEdit" ) ); + Q_ASSERT( addressEdit ); + KMFilterActionWithAddress::setParamWidgetValue( addressEdit ); + + TQComboBox *templateCombo = dynamic_cast( paramWidget->child( "templateCombo" ) ); + Q_ASSERT( templateCombo ); + + if ( mTemplate.isEmpty() ) { + templateCombo->setCurrentItem( 0 ); + } + else { + // WTF: TQt3's combobox has no indexOf? Search it manually, then. + int templateIndex = -1; + for ( int i = 1; i < templateCombo->count(); i++ ) { + if ( templateCombo->text( i ) == mTemplate ) { + templateIndex = i; + break; + } + } + + if ( templateIndex != -1 ) { + templateCombo->setCurrentItem( templateIndex ); + } + else { + mTemplate = TQString::null; } } - msg->cleanupHeader(); - msg->link( aMsg, KMMsgStatusForwarded ); +} - sendMDN( aMsg, KMime::MDN::Dispatched ); +void KMFilterActionForward::clearParamWidget( TQWidget* paramWidget ) const +{ + TQWidget *addressEdit = dynamic_cast( paramWidget->child( "addressEdit" ) ); + Q_ASSERT( addressEdit ); + KMFilterActionWithAddress::clearParamWidget( addressEdit ); - if ( !kmkernel->msgSender()->send( msg, KMail::MessageSender::SendLater ) ) { - kdDebug(5006) << "KMFilterAction: could not forward message (sending failed)" << endl; - return ErrorButGoOn; // error: couldn't send + TQComboBox *templateCombo = dynamic_cast( paramWidget->child( "templateCombo" ) ); + Q_ASSERT( templateCombo ); + + templateCombo->setCurrentItem( 0 ); +} + +// We simply place a "@$$@" between the two parameters. The template is the last +// parameter in the string, for compatibility reasons. +static const TQString forwardFilterArgsSeperator = "@$$@"; + +void KMFilterActionForward::argsFromString( const TQString argsStr ) +{ + int seperatorPos = argsStr.find( forwardFilterArgsSeperator ); + + if ( seperatorPos == - 1 ) { + // Old config, assume that the whole string is the addressee + KMFilterActionWithAddress::argsFromString( argsStr ); } - return GoOn; + else { + TQString addressee = argsStr.left( seperatorPos ); + mTemplate = argsStr.mid( seperatorPos + forwardFilterArgsSeperator.length() ); + KMFilterActionWithAddress::argsFromString( addressee ); + } +} + +const TQString KMFilterActionForward::argsAsString() const +{ + return KMFilterActionWithAddress::argsAsString() + forwardFilterArgsSeperator + mTemplate; } +const TQString KMFilterActionForward::displayString() const +{ + if ( mTemplate.isEmpty() ) + return i18n( "Forward to %1 with default template " ).arg( mParameter ); + else + return i18n( "Forward to %1 with template %2" ).arg( mParameter, mTemplate ); +} //============================================================================= // KMFilterActionRedirect - redirect to diff --git a/kmail/kmfilterdlg.cpp b/kmail/kmfilterdlg.cpp index c5589c943..558031f05 100644 --- a/kmail/kmfilterdlg.cpp +++ b/kmail/kmfilterdlg.cpp @@ -15,6 +15,8 @@ using KMail::AccountManager; #include "filterimporterexporter.h" using KMail::FilterImporterExporter; +#include "foldersetselector.h" +#include "globalsettings.h" // other KDE headers: #include @@ -45,6 +47,8 @@ using KMail::FilterImporterExporter; // other headers: #include +using namespace KMail; + // What's this help texts const char * _wt_filterlist = @@ -634,6 +638,14 @@ KMFilterListBox::KMFilterListBox( const TQString & title, TQWidget *parent, cons TQWhatsThis::add( mBtnDelete, i18n(_wt_filterlist_delete) ); TQWhatsThis::add( mBtnRename, i18n(_wt_filterlist_rename) ); + // third row + if ( !popFilter ) { + hb = new TQHBox( this ); + hb->setSpacing( 4 ); + TQPushButton *btn = new TQPushButton( i18n("Select Source Folders"), hb ); + connect( btn, TQT_SIGNAL(clicked()), TQT_SLOT(slotSelectSourceFolders()) ); + } + //----------- now connect everything connect( mListBox, TQT_SIGNAL(highlighted(int)), @@ -699,7 +711,7 @@ void KMFilterListBox::slotUpdateFilterName() if ( mFilterList.at(mIdxSelItem)->isAutoNaming() ) { // auto-naming of patterns - if ( p->first() && !p->first()->field().stripWhiteSpace().isEmpty() ) + if ( !p->isEmpty() && p->first() && !p->first()->field().stripWhiteSpace().isEmpty() ) shouldBeName = TQString( "<%1>: %2" ).arg( p->first()->field() ).arg( p->first()->contents() ); else shouldBeName = "<" + i18n("unnamed") + ">"; @@ -955,6 +967,17 @@ void KMFilterListBox::slotRename() slotUpdateFilterName(); } +void KMFilterListBox::slotSelectSourceFolders() +{ + FolderSetSelector dlg( kmkernel->getKMMainWidget()->folderTree(), this ); + dlg.setCaption( i18n( "Select Folders to Filter" ) ); + if ( !GlobalSettings::filterSourceFolders().isEmpty() ) + dlg.setSelectedFolders( GlobalSettings::filterSourceFolders() ); + if ( dlg.exec() == TQDialog::Accepted ) { + GlobalSettings::setFilterSourceFolders( dlg.selectedFolders() ); + } +} + void KMFilterListBox::enableControls() { bool theFirst = ( mIdxSelItem == 0 ); diff --git a/kmail/kmfilterdlg.h b/kmail/kmfilterdlg.h index 1ab185cab..18da9b568 100644 --- a/kmail/kmfilterdlg.h +++ b/kmail/kmfilterdlg.h @@ -149,6 +149,8 @@ protected slots: dialog prompting to enter the new name. */ void slotRename(); + void slotSelectSourceFolders(); + protected: /** The deep copy of the filter list. */ TQPtrList mFilterList; diff --git a/kmail/kmfiltermgr.cpp b/kmail/kmfiltermgr.cpp index 13e5e0f1e..beb3e60a5 100644 --- a/kmail/kmfiltermgr.cpp +++ b/kmail/kmfiltermgr.cpp @@ -42,8 +42,6 @@ KMFilterMgr::KMFilterMgr( bool popFilter ) mBufferedFolderTarget( true ), mRefCount( 0 ) { - if (bPopFilter) - kdDebug(5006) << "pPopFilter set" << endl; connect( kmkernel, TQT_SIGNAL( folderRemoved( KMFolder* ) ), this, TQT_SLOT( slotFolderRemoved( KMFolder* ) ) ); } diff --git a/kmail/kmfolder.cpp b/kmail/kmfolder.cpp index cae870710..68fb52f04 100644 --- a/kmail/kmfolder.cpp +++ b/kmail/kmfolder.cpp @@ -124,6 +124,10 @@ KMFolder::KMFolder( KMFolderDir* aParent, const TQString& aFolderName, TQT_SIGNAL( numUnreadMsgsChanged( KMFolder* ) ) ); connect( mStorage, TQT_SIGNAL( removed( KMFolder*, bool ) ), TQT_SIGNAL( removed( KMFolder*, bool ) ) ); + connect( mStorage, TQT_SIGNAL(noContentChanged()), + TQT_SIGNAL(noContentChanged()) ); + connect( mStorage, TQT_SIGNAL(syncStateChanged()), + TQT_SIGNAL(syncStateChanged()) ); connect( mStorage, TQT_SIGNAL( contentsTypeChanged( KMail::FolderContentsType ) ), this, TQT_SLOT( slotContentsTypeChanged( KMail::FolderContentsType ) ) ); @@ -559,6 +563,21 @@ bool KMFolder::isReadOnly() const return mStorage->isReadOnly(); } +bool KMFolder::mailCheckInProgress() const +{ + return mStorage->mailCheckInProgress(); +} + +bool KMFolder::isWritable() const +{ + return !mStorage->isReadOnly() && mStorage->canDeleteMessages(); +} + +bool KMFolder::canDeleteMessages() const +{ + return mStorage->canDeleteMessages(); +} + TQString KMFolder::label() const { if ( !mSystemLabel.isEmpty() ) @@ -877,5 +896,44 @@ void KMFolder::slotFolderSizeChanged() } } +bool KMFolder::isValidName( const TQString &folderName, TQString &message ) +{ + KMFolderType fldType = folderType(); + + // names of local folders must not contain a '/' + if ( folderName.find( '/' ) != -1 && + fldType != KMFolderTypeImap && + fldType != KMFolderTypeCachedImap ) { + message = i18n( "Folder names cannot contain the / (slash) character; please choose another folder name." ); + return false; + } + + // folder names must not start with a '.' + if ( folderName.startsWith( "." ) ) { + message = i18n( "Folder names cannot start with a . (dot) character; please choose another folder name." ); + return false; + } + + // names of IMAP folders must not contain the folder delimiter + if ( fldType == KMFolderTypeImap || fldType == KMFolderTypeCachedImap ) { + TQString delimiter; + if ( fldType == KMFolderTypeImap ) { + KMAcctImap *ai = static_cast( mStorage )->account(); + if ( ai ) { + delimiter = ai->delimiterForFolder( mStorage ); + } + } else { + KMAcctCachedImap *ai = static_cast( mStorage )->account(); + if ( ai ) { + delimiter = ai->delimiterForFolder( mStorage ); + } + } + if ( !delimiter.isEmpty() && folderName.find( delimiter ) != -1 ) { + message = i18n( "Your IMAP server does not allow the character '%1'; please choose another folder name." ).arg( delimiter ); + return false; + } + } + return true; +} #include "kmfolder.moc" diff --git a/kmail/kmfolder.h b/kmail/kmfolder.h index 32518cd75..3ff1d67de 100644 --- a/kmail/kmfolder.h +++ b/kmail/kmfolder.h @@ -353,6 +353,13 @@ public: /** Is the folder read-only? */ bool isReadOnly() const; + /** Can we write into and delete from this folder (on IMAP that's not necessarily !isReadOnly()) */ + bool isWritable() const; + + bool mailCheckInProgress() const; + + /** Can messages in this folder be deleted? */ + bool canDeleteMessages() const; /** Returns true if the folder is a kmail system folder. These are the folders 'inbox', 'outbox', 'sent', 'trash', 'drafts', 'templates'. @@ -532,6 +539,13 @@ public: /** Sets the move-in-progress flag. */ void setMoveInProgress( bool b ) { mMoveInProgress = b; } + /** + * Returns true if the name is valid for a child of this folder. + * If the name contains invalid characters then false is returned and message will contain + * an explanation that can be presented to the user. + */ + bool isValidName( const TQString &folderName, TQString &message ); + signals: /** Emitted when the status, name, or associated accounts of this folder changed. */ @@ -590,6 +604,15 @@ signals: /** Emitted when the folder's size changes. */ void folderSizeChanged( KMFolder * ); + /** Emitted when the no content state changed. */ + void noContentChanged(); + + /** + * Emiitted when the sync state, i.e. mailCheckInProgress(), changes. + * Currently only supported for disconnected IMAP. + */ + void syncStateChanged(); + public slots: /** Incrementally update the index if possible else call writeIndex */ int updateIndex(); diff --git a/kmail/kmfoldercachedimap.cpp b/kmail/kmfoldercachedimap.cpp index 05f01b778..a1c71726b 100644 --- a/kmail/kmfoldercachedimap.cpp +++ b/kmail/kmfoldercachedimap.cpp @@ -129,11 +129,11 @@ DImapTroubleShootDialog::DImapTroubleShootDialog( TQWidget* parent, "and all its subfolders.

" ); topLayout->addWidget( new TQLabel( txt, page ) ); - TQButtonGroup *group = new TQButtonGroup( 0 ); + mButtonGroup = new TQButtonGroup( 0 ); mIndexButton = new TQRadioButton( page ); mIndexButton->setText( i18n( "Rebuild &Index" ) ); - group->insert( mIndexButton ); + mButtonGroup->insert( mIndexButton ); topLayout->addWidget( mIndexButton ); TQHBox *hbox = new TQHBox( page ); @@ -148,15 +148,16 @@ DImapTroubleShootDialog::DImapTroubleShootDialog( TQWidget* parent, mCacheButton = new TQRadioButton( page ); mCacheButton->setText( i18n( "Refresh &Cache" ) ); - group->insert( mCacheButton ); + mButtonGroup->insert( mCacheButton ); topLayout->addWidget( mCacheButton ); enableButtonSeparator( true ); connect ( mIndexButton, TQT_SIGNAL(toggled(bool)), mIndexScope, TQT_SLOT(setEnabled(bool)) ); connect ( mIndexButton, TQT_SIGNAL(toggled(bool)), scopeLabel, TQT_SLOT(setEnabled(bool)) ); - + connect( mButtonGroup, TQT_SIGNAL( clicked( int ) ), TQT_SLOT( slotChanged() ) ); connect( this, TQT_SIGNAL( okClicked () ), this, TQT_SLOT( slotDone() ) ); + enableButtonOK( false ); } int DImapTroubleShootDialog::run() @@ -166,6 +167,11 @@ int DImapTroubleShootDialog::run() return d.rc; } +void DImapTroubleShootDialog::slotChanged() +{ + enableButtonOK( mButtonGroup->selected() != 0 ); +} + void DImapTroubleShootDialog::slotDone() { rc = None; @@ -181,17 +187,24 @@ KMFolderCachedImap::KMFolderCachedImap( KMFolder* folder, const char* aName ) mSyncState( SYNC_STATE_INITIAL ), mContentState( imapNoInformation ), mSubfolderState( imapNoInformation ), mIncidencesFor( IncForAdmins ), + mSharedSeenFlags( false ), mIsSelected( false ), mCheckFlags( true ), mReadOnly( false ), mAccount( NULL ), uidMapDirty( true ), uidWriteTimer( -1 ), mLastUid( 0 ), mTentativeHighestUid( 0 ), mFoundAnIMAPDigest( false ), - mUserRights( 0 ), mOldUserRights( 0 ), mSilentUpload( false ), + mUserRights( 0 ), mOldUserRights( 0 ), mUserRightsState( KMail::ACLJobs::NotFetchedYet ), + mACLListState( KMail::ACLJobs::NotFetchedYet ), + mSilentUpload( false ), /*mHoldSyncs( false ),*/ mFolderRemoved( false ), mRecurse( true ), - mStatusChangedLocally( false ), mAnnotationFolderTypeChanged( false ), - mIncidencesForChanged( false ), mPersonalNamespacesCheckDone( true ), - mQuotaInfo(), mAlarmsBlocked( false ), + mQuotaOnly( false ), + mAnnotationFolderTypeChanged( false ), + mIncidencesForChanged( false ), + mSharedSeenFlagsChanged( false ), + mStatusChangedLocally( false ), + mPersonalNamespacesCheckDone( true ), + mQuotaInfo(), mSomeSubFolderCloseToQuotaChanged( false ), mAlarmsBlocked( false ), mRescueCommandCount( 0 ), mPermanentFlags( 31 ) // assume standard flags by default (see imap4/imapinfo.h for bit fields values) { @@ -215,6 +228,7 @@ KMFolderCachedImap::KMFolderCachedImap( KMFolder* folder, const char* aName ) KMFolderCachedImap::~KMFolderCachedImap() { if (kmkernel->undoStack()) kmkernel->undoStack()->folderDestroyed( folder() ); + writeConfig(); } void KMFolderCachedImap::reallyDoClose( const char* owner ) @@ -231,7 +245,7 @@ void KMFolderCachedImap::initializeFrom( KMFolderCachedImap* parent ) // Now that we have an account, tell it that this folder was created: // if this folder was just removed, then we don't really want to remove it from the server. mAccount->removeDeletedFolder( imapPath() ); - setUserRights( parent->userRights() ); + setUserRights( parent->userRights(), parent->userRightsState() ); } void KMFolderCachedImap::readConfig() @@ -262,8 +276,11 @@ void KMFolderCachedImap::readConfig() mAlarmsBlocked = config->readBoolEntry( "AlarmsBlocked", false ); // kdDebug(5006) << ( mImapPath.isEmpty() ? label() : mImapPath ) // << " readConfig: mIncidencesFor=" << mIncidencesFor << endl; + mSharedSeenFlags = config->readBoolEntry( "SharedSeenFlags", false ); - mUserRights = config->readNumEntry( "UserRights", 0 ); // default is we don't know + mUserRights = config->readNumEntry( "UserRights", 0 ); + mUserRightsState = static_cast( + config->readNumEntry( "UserRightsState", KMail::ACLJobs::NotFetchedYet ) ); mOldUserRights = mUserRights; int storageQuotaUsage = config->readNumEntry( "StorageQuotaUsage", -1 ); @@ -283,19 +300,25 @@ void KMFolderCachedImap::readConfig() mStatusChangedLocally = config->readBoolEntry( "StatusChangedLocally", false ); + TQStringList uidsChanged = config->readListEntry( "UIDStatusChangedLocally" ); + for ( TQStringList::iterator it = uidsChanged.begin(); it != uidsChanged.end(); it++ ) { + mUIDsOfLocallyChangedStatuses.insert( ( *it ).toUInt() ); + } mAnnotationFolderTypeChanged = config->readBoolEntry( "AnnotationFolderTypeChanged", false ); mIncidencesForChanged = config->readBoolEntry( "IncidencesForChanged", false ); + mSharedSeenFlagsChanged = config->readBoolEntry( "SharedSeenFlagsChanged", false ); + if ( mImapPath.isEmpty() ) { mImapPathCreation = config->readEntry("ImapPathCreation"); } - TQStringList uids = config->readListEntry( "UIDSDeletedSinceLastSync" ); + TQStringList delUids = config->readListEntry( "UIDSDeletedSinceLastSync" ); #if MAIL_LOSS_DEBUGGING kdDebug( 5006 ) << "READING IN UIDSDeletedSinceLastSync: " << folder()->prettyURL() << endl << uids << endl; #endif - for ( TQStringList::iterator it = uids.begin(); it != uids.end(); it++ ) { - mDeletedUIDsSinceLastSync.insert( (*it).toULong(), 0); + for ( TQStringList::iterator it = delUids.begin(); it != delUids.end(); it++ ) { + mDeletedUIDsSinceLastSync.insert( (*it).toULong(), 0); } } @@ -311,7 +334,15 @@ void KMFolderCachedImap::writeConfig() configGroup.writeEntry( "NoContent", mNoContent ); configGroup.writeEntry( "ReadOnly", mReadOnly ); configGroup.writeEntry( "FolderAttributes", mFolderAttributes ); - configGroup.writeEntry( "StatusChangedLocally", mStatusChangedLocally ); + + // StatusChangedLocally is always false, as we use UIDStatusChangedLocally now + configGroup.writeEntry( "StatusChangedLocally", false ); + TQStringList uidsToWrite; + for( std::set::iterator it = mUIDsOfLocallyChangedStatuses.begin(); + it != mUIDsOfLocallyChangedStatuses.end(); it++ ) { + uidsToWrite.append( TQString::number( (*it) ) ); + } + configGroup.writeEntry( "UIDStatusChangedLocally", uidsToWrite ); if ( !mImapPathCreation.isEmpty() ) { if ( mImapPath.isEmpty() ) { configGroup.writeEntry( "ImapPathCreation", mImapPathCreation ); @@ -346,7 +377,12 @@ void KMFolderCachedImap::writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig configGroup.writeEntry( "IncidencesForChanged", mIncidencesForChanged ); configGroup.writeEntry( "IncidencesFor", incidencesForToString( mIncidencesFor ) ); configGroup.writeEntry( "AlarmsBlocked", mAlarmsBlocked ); - configGroup.writeEntry( "UserRights", mUserRights ); + configGroup.writeEntry( "SharedSeenFlags", mSharedSeenFlags ); + configGroup.writeEntry( "SharedSeenFlagsChanged", mSharedSeenFlagsChanged ); + if ( mUserRightsState != KMail::ACLJobs::FetchFailed ) { // No point in overwriting valid results with invalid ones + configGroup.writeEntry( "UserRights", mUserRights ); + configGroup.writeEntry( "UserRightsState", mUserRightsState ); + } configGroup.deleteEntry( "StorageQuotaUsage"); configGroup.deleteEntry( "StorageQuotaRoot"); @@ -503,11 +539,22 @@ int KMFolderCachedImap::addMsgInternal( KMMessage* msg, bool newMail, // Add the message rc = KMFolderMaildir::addMsg(msg, index_return); - if( newMail && ( imapPath() == "/INBOX/" || ( !GlobalSettings::self()->filterOnlyDIMAPInbox() - && (userRights() <= 0 || userRights() & ACLJobs::Administer ) + if( newMail && ( imapPath() == "/INBOX/" || + ( ( mUserRights != ACLJobs::Ok || userRights() & ACLJobs::Administer) && (contentsType() == ContentsTypeMail || GlobalSettings::self()->filterGroupwareFolders()) ) ) ) - // This is a new message. Filter it - mAccount->processNewMsg( msg ); + { + // This is a new message. Filter it - maybe + bool filter = false; + if ( GlobalSettings::filterSourceFolders().isEmpty() ) { + if ( imapPath() == "/INBOX/" ) + filter = true; + } else { + if ( GlobalSettings::filterSourceFolders().contains( folder()->id() ) ) + filter = true; + } + if ( filter ) + mAccount->processNewMsg( msg ); + } return rc; } @@ -534,6 +581,9 @@ void KMFolderCachedImap::rememberDeletion( int idx ) /* Reimplemented from KMFolderMaildir */ void KMFolderCachedImap::removeMsg(int idx, bool imapQuiet) { + if ( contentsType() != ContentsTypeMail ) { + kdDebug(5006) << k_funcinfo << "Deleting message with idx " << idx << " in folder " << label() << endl; + } uidMapDirty = true; rememberDeletion( idx ); // Remove it from disk @@ -556,18 +606,20 @@ bool KMFolderCachedImap::canRemoveFolder() const { int KMFolderCachedImap::rename( const TQString& aName, KMFolderDir* /*aParent*/ ) { + if ( account() == 0 || imapPath().isEmpty() ) { + // This can happen when creating a folder and then renaming it without syncing before, + // see https://issues.kolab.org/issue3658 + TQString err = i18n("You must synchronize with the server before renaming IMAP folders."); + KMessageBox::error( 0, err ); + return -1; + } + TQString oldName = mAccount->renamedFolder( imapPath() ); if ( oldName.isEmpty() ) oldName = name(); if ( aName == oldName ) // Stupid user trying to rename it to it's old name :) return 0; - if( account() == 0 || imapPath().isEmpty() ) { // I don't think any of this can happen anymore - TQString err = i18n("You must synchronize with the server before renaming IMAP folders."); - KMessageBox::error( 0, err ); - return -1; - } - // Make the change appear to the user with setLabel, but we'll do the change // on the server during the next sync. The name() is the name at the time of // the last sync. Only rename if the new one is different. If it's the same, @@ -719,7 +771,7 @@ void KMFolderCachedImap::slotTroubleshoot() } } -void KMFolderCachedImap::serverSync( bool recurse ) +void KMFolderCachedImap::serverSync( bool recurse, bool quotaOnly ) { if( mSyncState != SYNC_STATE_INITIAL ) { if( KMessageBox::warningYesNo( 0, i18n("Folder %1 is not in initial sync state (state was %2). Do you want to reset it to initial sync state and sync anyway?" ).arg( imapPath() ).arg( mSyncState ), TQString::null, i18n("Reset && Sync"), KStdGuiItem::cancel() ) == KMessageBox::Yes ) { @@ -728,6 +780,7 @@ void KMFolderCachedImap::serverSync( bool recurse ) } mRecurse = recurse; + mQuotaOnly = quotaOnly; assert( account() ); ProgressItem *progressItem = mAccount->mailCheckProgressItem(); @@ -756,31 +809,33 @@ void KMFolderCachedImap::serverSync( bool recurse ) TQString KMFolderCachedImap::state2String( int state ) const { switch( state ) { - case SYNC_STATE_INITIAL: return "SYNC_STATE_INITIAL"; - case SYNC_STATE_GET_USERRIGHTS: return "SYNC_STATE_GET_USERRIGHTS"; - case SYNC_STATE_PUT_MESSAGES: return "SYNC_STATE_PUT_MESSAGES"; - case SYNC_STATE_UPLOAD_FLAGS: return "SYNC_STATE_UPLOAD_FLAGS"; - case SYNC_STATE_CREATE_SUBFOLDERS: return "SYNC_STATE_CREATE_SUBFOLDERS"; - case SYNC_STATE_LIST_SUBFOLDERS: return "SYNC_STATE_LIST_SUBFOLDERS"; - case SYNC_STATE_LIST_NAMESPACES: return "SYNC_STATE_LIST_NAMESPACES"; - case SYNC_STATE_LIST_SUBFOLDERS2: return "SYNC_STATE_LIST_SUBFOLDERS2"; - case SYNC_STATE_DELETE_SUBFOLDERS: return "SYNC_STATE_DELETE_SUBFOLDERS"; - case SYNC_STATE_LIST_MESSAGES: return "SYNC_STATE_LIST_MESSAGES"; - case SYNC_STATE_DELETE_MESSAGES: return "SYNC_STATE_DELETE_MESSAGES"; - case SYNC_STATE_GET_MESSAGES: return "SYNC_STATE_GET_MESSAGES"; - case SYNC_STATE_EXPUNGE_MESSAGES: return "SYNC_STATE_EXPUNGE_MESSAGES"; - case SYNC_STATE_HANDLE_INBOX: return "SYNC_STATE_HANDLE_INBOX"; - case SYNC_STATE_TEST_ANNOTATIONS: return "SYNC_STATE_TEST_ANNOTATIONS"; - case SYNC_STATE_GET_ANNOTATIONS: return "SYNC_STATE_GET_ANNOTATIONS"; - case SYNC_STATE_SET_ANNOTATIONS: return "SYNC_STATE_SET_ANNOTATIONS"; - case SYNC_STATE_GET_ACLS: return "SYNC_STATE_GET_ACLS"; - case SYNC_STATE_SET_ACLS: return "SYNC_STATE_SET_ACLS"; - case SYNC_STATE_GET_QUOTA: return "SYNC_STATE_GET_QUOTA"; - case SYNC_STATE_FIND_SUBFOLDERS: return "SYNC_STATE_FIND_SUBFOLDERS"; - case SYNC_STATE_SYNC_SUBFOLDERS: return "SYNC_STATE_SYNC_SUBFOLDERS"; - case SYNC_STATE_RENAME_FOLDER: return "SYNC_STATE_RENAME_FOLDER"; - case SYNC_STATE_CHECK_UIDVALIDITY: return "SYNC_STATE_CHECK_UIDVALIDITY"; - default: return "Unknown state"; + case SYNC_STATE_INITIAL: return "SYNC_STATE_INITIAL"; + case SYNC_STATE_GET_USERRIGHTS: return "SYNC_STATE_GET_USERRIGHTS"; + case SYNC_STATE_PUT_MESSAGES: return "SYNC_STATE_PUT_MESSAGES"; + case SYNC_STATE_UPLOAD_FLAGS: return "SYNC_STATE_UPLOAD_FLAGS"; + case SYNC_STATE_CREATE_SUBFOLDERS: return "SYNC_STATE_CREATE_SUBFOLDERS"; + case SYNC_STATE_LIST_SUBFOLDERS: return "SYNC_STATE_LIST_SUBFOLDERS"; + case SYNC_STATE_LIST_NAMESPACES: return "SYNC_STATE_LIST_NAMESPACES"; + case SYNC_STATE_LIST_SUBFOLDERS2: return "SYNC_STATE_LIST_SUBFOLDERS2"; + case SYNC_STATE_DELETE_SUBFOLDERS: return "SYNC_STATE_DELETE_SUBFOLDERS"; + case SYNC_STATE_LIST_MESSAGES: return "SYNC_STATE_LIST_MESSAGES"; + case SYNC_STATE_DELETE_MESSAGES: return "SYNC_STATE_DELETE_MESSAGES"; + case SYNC_STATE_GET_MESSAGES: return "SYNC_STATE_GET_MESSAGES"; + case SYNC_STATE_EXPUNGE_MESSAGES: return "SYNC_STATE_EXPUNGE_MESSAGES"; + case SYNC_STATE_HANDLE_INBOX: return "SYNC_STATE_HANDLE_INBOX"; + case SYNC_STATE_TEST_ANNOTATIONS: return "SYNC_STATE_TEST_ANNOTATIONS"; + case SYNC_STATE_GET_ANNOTATIONS: return "SYNC_STATE_GET_ANNOTATIONS"; + case SYNC_STATE_SET_ANNOTATIONS: return "SYNC_STATE_SET_ANNOTATIONS"; + case SYNC_STATE_GET_ACLS: return "SYNC_STATE_GET_ACLS"; + case SYNC_STATE_SET_ACLS: return "SYNC_STATE_SET_ACLS"; + case SYNC_STATE_GET_QUOTA: return "SYNC_STATE_GET_QUOTA"; + case SYNC_STATE_FIND_SUBFOLDERS: return "SYNC_STATE_FIND_SUBFOLDERS"; + case SYNC_STATE_SYNC_SUBFOLDERS: return "SYNC_STATE_SYNC_SUBFOLDERS"; + case SYNC_STATE_RENAME_FOLDER: return "SYNC_STATE_RENAME_FOLDER"; + case SYNC_STATE_CHECK_UIDVALIDITY: return "SYNC_STATE_CHECK_UIDVALIDITY"; + case SYNC_STATE_CLOSE: return "SYNC_STATE_CLOSE"; + case SYNC_STATE_GET_SUBFOLDER_QUOTA: return "SYNC_STATE_GET_SUBFOLDER_QUOTA"; + default: return "Unknown state"; } } @@ -866,11 +921,14 @@ void KMFolderCachedImap::serverSyncInternal() case SYNC_STATE_GET_USERRIGHTS: + + // Now we have started the sync, emit changed() so that the folder tree can update the status + emit syncStateChanged(); //kdDebug(5006) << "===== Syncing " << ( mImapPath.isEmpty() ? label() : mImapPath ) << endl; mSyncState = SYNC_STATE_RENAME_FOLDER; - if( !noContent() && mAccount->hasACLSupport() ) { + if( !mQuotaOnly && !noContent() && mAccount->hasACLSupport() ) { // Check the user's own rights. We do this every time in case they changed. mOldUserRights = mUserRights; newState( mProgress, i18n("Checking permissions")); @@ -880,6 +938,14 @@ void KMFolderCachedImap::serverSyncInternal() break; } + else if ( !mQuotaOnly && noContent() && mAccount->hasACLSupport() ) { + // This is a no content folder. The server would simply say that mailbox does not exist when + // querying the rights for it. So pretend we have no rights. + mUserRights = 0; + mUserRightsState = KMail::ACLJobs::Ok; + writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig(); + } + case SYNC_STATE_RENAME_FOLDER: { mSyncState = SYNC_STATE_CHECK_UIDVALIDITY; @@ -890,7 +956,7 @@ void KMFolderCachedImap::serverSyncInternal() newState( mProgress, i18n("Renaming folder") ); CachedImapJob *job = new CachedImapJob( newName, CachedImapJob::tRenameFolder, this ); connect( job, TQT_SIGNAL( result(KMail::FolderJob *) ), this, TQT_SLOT( slotIncreaseProgress() ) ); - connect( job, TQT_SIGNAL( finished() ), this, TQT_SLOT( serverSyncInternal() ) ); + connect( job, TQT_SIGNAL( finished() ), this, TQT_SLOT( slotRenameFolderFinished() ) ); job->start(); break; } @@ -898,7 +964,7 @@ void KMFolderCachedImap::serverSyncInternal() case SYNC_STATE_CHECK_UIDVALIDITY: mSyncState = SYNC_STATE_CREATE_SUBFOLDERS; - if( !noContent() ) { + if( !mQuotaOnly && !noContent() ) { checkUidValidity(); break; } @@ -906,33 +972,36 @@ void KMFolderCachedImap::serverSyncInternal() case SYNC_STATE_CREATE_SUBFOLDERS: mSyncState = SYNC_STATE_PUT_MESSAGES; - createNewFolders(); - break; + if ( !mQuotaOnly ) { + createNewFolders(); + break; + } case SYNC_STATE_PUT_MESSAGES: mSyncState = SYNC_STATE_UPLOAD_FLAGS; - if( !noContent() ) { + if( !mQuotaOnly && !noContent() ) { uploadNewMessages(); break; } // Else carry on case SYNC_STATE_UPLOAD_FLAGS: mSyncState = SYNC_STATE_LIST_NAMESPACES; - if( !noContent() ) { + if( !mQuotaOnly && !noContent() ) { // We haven't downloaded messages yet, so we need to build the map. if( uidMapDirty ) reloadUidMap(); // Upload flags, unless we know from the ACL that we're not allowed // to do that or they did not change locally - if ( mUserRights <= 0 || ( mUserRights & (KMail::ACLJobs::WriteFlags ) ) ) { - if ( mStatusChangedLocally ) { + if ( mUserRightsState != KMail::ACLJobs::Ok || + ( mUserRights & (KMail::ACLJobs::WriteFlags ) ) ) { + if ( !mUIDsOfLocallyChangedStatuses.empty() || mStatusChangedLocally ) { uploadFlags(); break; } else { //kdDebug(5006) << "Skipping flags upload, folder unchanged: " << label() << endl; } } else if ( mUserRights & KMail::ACLJobs::WriteSeenFlag ) { - if ( mStatusChangedLocally ) { + if ( !mUIDsOfLocallyChangedStatuses.empty() || mStatusChangedLocally ) { uploadSeenFlags(); break; } @@ -941,7 +1010,7 @@ void KMFolderCachedImap::serverSyncInternal() // Else carry on case SYNC_STATE_LIST_NAMESPACES: - if ( this == mAccount->rootFolder() ) { + if ( !mQuotaOnly && this == mAccount->rootFolder() ) { listNamespaces(); break; } @@ -951,22 +1020,26 @@ void KMFolderCachedImap::serverSyncInternal() case SYNC_STATE_LIST_SUBFOLDERS: newState( mProgress, i18n("Retrieving folderlist")); mSyncState = SYNC_STATE_LIST_SUBFOLDERS2; - if( !listDirectory() ) { - mSyncState = SYNC_STATE_INITIAL; - KMessageBox::error(0, i18n("Error while retrieving the folderlist")); + if ( !mQuotaOnly ) { + if( !listDirectory() ) { + mSyncState = SYNC_STATE_INITIAL; + KMessageBox::error(0, i18n("Error while retrieving the folderlist")); + } + break; } - break; case SYNC_STATE_LIST_SUBFOLDERS2: mSyncState = SYNC_STATE_DELETE_SUBFOLDERS; mProgress += 10; - newState( mProgress, i18n("Retrieving subfolders")); - listDirectory2(); - break; + if ( !mQuotaOnly ) { + newState( mProgress, i18n("Retrieving subfolders")); + listDirectory2(); + break; + } case SYNC_STATE_DELETE_SUBFOLDERS: mSyncState = SYNC_STATE_LIST_MESSAGES; - if( !foldersForDeletionOnServer.isEmpty() ) { + if( !mQuotaOnly && !foldersForDeletionOnServer.isEmpty() ) { newState( mProgress, i18n("Deleting folders from server")); CachedImapJob* job = new CachedImapJob( foldersForDeletionOnServer, CachedImapJob::tDeleteFolders, this ); @@ -981,7 +1054,7 @@ void KMFolderCachedImap::serverSyncInternal() case SYNC_STATE_LIST_MESSAGES: mSyncState = SYNC_STATE_DELETE_MESSAGES; - if( !noContent() ) { + if( !mQuotaOnly && !noContent() ) { newState( mProgress, i18n("Retrieving message list")); listMessages(); break; @@ -990,7 +1063,7 @@ void KMFolderCachedImap::serverSyncInternal() case SYNC_STATE_DELETE_MESSAGES: mSyncState = SYNC_STATE_EXPUNGE_MESSAGES; - if( !noContent() ) { + if( !mQuotaOnly && !noContent() ) { if( deleteMessages() ) { // Fine, we will continue with the next state } else { @@ -1005,7 +1078,7 @@ void KMFolderCachedImap::serverSyncInternal() case SYNC_STATE_EXPUNGE_MESSAGES: mSyncState = SYNC_STATE_GET_MESSAGES; - if( !noContent() ) { + if( !mQuotaOnly && !noContent() ) { newState( mProgress, i18n("Expunging deleted messages")); CachedImapJob *job = new CachedImapJob( TQString::null, CachedImapJob::tExpungeFolder, this ); @@ -1018,9 +1091,9 @@ void KMFolderCachedImap::serverSyncInternal() case SYNC_STATE_GET_MESSAGES: mSyncState = SYNC_STATE_HANDLE_INBOX; - if( !noContent() ) { + if( !mQuotaOnly && !noContent() ) { if( !mMsgsForDownload.isEmpty() ) { - newState( mProgress, i18n("Retrieving new messages")); + newState( mProgress, i18n("Retrieving one new message","Retrieving %n new messages",mMsgsForDownload.size())); CachedImapJob *job = new CachedImapJob( mMsgsForDownload, CachedImapJob::tGetMessage, this ); @@ -1061,8 +1134,8 @@ void KMFolderCachedImap::serverSyncInternal() case SYNC_STATE_TEST_ANNOTATIONS: mSyncState = SYNC_STATE_GET_ANNOTATIONS; // The first folder with user rights to write annotations - if( !mAccount->annotationCheckPassed() && - ( mUserRights <= 0 || ( mUserRights & ACLJobs::Administer ) ) + if( !mQuotaOnly && !mAccount->annotationCheckPassed() && + ( mUserRightsState != KMail::ACLJobs::Ok || ( mUserRights & ACLJobs::Administer ) ) && !imapPath().isEmpty() && imapPath() != "/" ) { kdDebug(5006) << "Setting test attribute on folder: "<< folder()->prettyURL() << endl; newState( mProgress, i18n("Checking annotation support")); @@ -1088,11 +1161,12 @@ void KMFolderCachedImap::serverSyncInternal() case SYNC_STATE_GET_ANNOTATIONS: { #define KOLAB_FOLDERTYPE "/vendor/kolab/folder-type" #define KOLAB_INCIDENCESFOR "/vendor/kolab/incidences-for" +#define KOLAB_SHAREDSEEN "/vendor/cmu/cyrus-imapd/sharedseen" //#define KOLAB_FOLDERTYPE "/comment" //for testing, while cyrus-imap doesn't support /vendor/* mSyncState = SYNC_STATE_SET_ANNOTATIONS; bool needToGetInitialAnnotations = false; - if ( !noContent() ) { + if ( !mQuotaOnly && !noContent() ) { // for a folder we didn't create ourselves: get annotation from server if ( mAnnotationFolderType == "FROMSERVER" ) { needToGetInitialAnnotations = true; @@ -1104,13 +1178,15 @@ void KMFolderCachedImap::serverSyncInternal() // First retrieve the annotation, so that we know we have to set it if it's not set. // On the other hand, if the user changed the contentstype, there's no need to get first. - if ( !noContent() && mAccount->hasAnnotationSupport() && + if ( !mQuotaOnly && !noContent() && mAccount->hasAnnotationSupport() && ( kmkernel->iCalIface().isEnabled() || needToGetInitialAnnotations ) ) { TQStringList annotations; // list of annotations to be fetched if ( !mAnnotationFolderTypeChanged || mAnnotationFolderType.isEmpty() ) annotations << KOLAB_FOLDERTYPE; if ( !mIncidencesForChanged ) annotations << KOLAB_INCIDENCESFOR; + if ( !mSharedSeenFlagsChanged ) + annotations << KOLAB_SHAREDSEEN; if ( !annotations.isEmpty() ) { newState( mProgress, i18n("Retrieving annotations")); KURL url = mAccount->getUrl(); @@ -1132,8 +1208,8 @@ void KMFolderCachedImap::serverSyncInternal() case SYNC_STATE_SET_ANNOTATIONS: mSyncState = SYNC_STATE_SET_ACLS; - if ( !noContent() && mAccount->hasAnnotationSupport() && - ( mUserRights <= 0 || ( mUserRights & ACLJobs::Administer ) ) ) { + if ( !mQuotaOnly && !noContent() && mAccount->hasAnnotationSupport() && + ( mUserRightsState != KMail::ACLJobs::Ok || ( mUserRights & ACLJobs::Administer ) ) ) { newState( mProgress, i18n("Setting annotations")); KURL url = mAccount->getUrl(); url.setPath( imapPath() ); @@ -1149,6 +1225,12 @@ void KMFolderCachedImap::serverSyncInternal() annotations.append( attr ); kdDebug(5006) << "Setting incidences-for annotation for " << label() << " to " << val << endl; } + if ( mSharedSeenFlagsChanged ) { + const TQString val = mSharedSeenFlags ? "true" : "false"; + KMail::AnnotationAttribute attr( KOLAB_SHAREDSEEN, "value.shared", val ); + annotations.append( attr ); + kdDebug(5006) << k_funcinfo << "Setting sharedseen annotation for " << label() << " to " << val << endl; + } if ( !annotations.isEmpty() ) { KIO::Job* job = AnnotationJobs::multiSetAnnotation( mAccount->slave(), url, annotations ); @@ -1167,8 +1249,8 @@ void KMFolderCachedImap::serverSyncInternal() case SYNC_STATE_SET_ACLS: mSyncState = SYNC_STATE_GET_ACLS; - if( !noContent() && mAccount->hasACLSupport() && - ( mUserRights <= 0 || ( mUserRights & ACLJobs::Administer ) ) ) { + if( !mQuotaOnly && !noContent() && mAccount->hasACLSupport() && + ( mUserRightsState != KMail::ACLJobs::Ok || ( mUserRights & ACLJobs::Administer ) ) ) { bool hasChangedACLs = false; ACLList::ConstIterator it = mACLList.begin(); for ( ; it != mACLList.end() && !hasChangedACLs; ++it ) { @@ -1191,19 +1273,36 @@ void KMFolderCachedImap::serverSyncInternal() } case SYNC_STATE_GET_ACLS: - mSyncState = SYNC_STATE_GET_QUOTA; + mSyncState = SYNC_STATE_FIND_SUBFOLDERS; - if( !noContent() && mAccount->hasACLSupport() ) { + if( !mQuotaOnly && !noContent() && mAccount->hasACLSupport() ) { newState( mProgress, i18n( "Retrieving permissions" ) ); mAccount->getACL( folder(), mImapPath ); connect( mAccount, TQT_SIGNAL(receivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )), this, TQT_SLOT(slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )) ); break; } + case SYNC_STATE_FIND_SUBFOLDERS: + { + mSyncState = SYNC_STATE_SYNC_SUBFOLDERS; + mSomeSubFolderCloseToQuotaChanged = false; + buildSubFolderList(); + } + + // Carry on + case SYNC_STATE_SYNC_SUBFOLDERS: + syncNextSubFolder( false ); + break; + case SYNC_STATE_GET_SUBFOLDER_QUOTA: + + // Sync the subfolders again, so that the quota information is updated for all. This state is + // only entered if the close to quota property of one subfolder changed in the previous state. + syncNextSubFolder( true ); + break; case SYNC_STATE_GET_QUOTA: - // Continue with the subfolders - mSyncState = SYNC_STATE_FIND_SUBFOLDERS; + mSyncState = SYNC_STATE_CLOSE; if( !noContent() && mAccount->hasQuotaSupport() ) { + mProgress = 98; newState( mProgress, i18n("Getting quota information")); KURL url = mAccount->getUrl(); url.setPath( imapPath() ); @@ -1216,79 +1315,114 @@ void KMFolderCachedImap::serverSyncInternal() TQT_SLOT(slotQuotaResult(KIO::Job *)) ); break; } - case SYNC_STATE_FIND_SUBFOLDERS: + case SYNC_STATE_CLOSE: { - mProgress = 98; - newState( mProgress, i18n("Updating cache file")); - - mSyncState = SYNC_STATE_SYNC_SUBFOLDERS; - mSubfoldersForSync.clear(); - mCurrentSubfolder = 0; - if( folder() && folder()->child() ) { - KMFolderNode *node = folder()->child()->first(); - while( node ) { - if( !node->isDir() ) { - KMFolderCachedImap* storage = static_cast(static_cast(node)->storage()); - // Only sync folders that have been accepted by the server - if ( !storage->imapPath().isEmpty() - // and that were not just deleted from it - && !foldersForDeletionOnServer.contains( storage->imapPath() ) ) { - mSubfoldersForSync << storage; - } else { - kdDebug(5006) << "Do not add " << storage->label() - << " to synclist" << endl; - } - } - node = folder()->child()->next(); - } - } - - // All done for this folder. - mProgress = 100; // all done - newState( mProgress, i18n("Synchronization done")); + mProgress = 100; // all done + newState( mProgress, i18n("Synchronization done")); KURL url = mAccount->getUrl(); url.setPath( imapPath() ); kmkernel->iCalIface().folderSynced( folder(), url ); - } - - if ( !mRecurse ) // "check mail for this folder" only - mSubfoldersForSync.clear(); - // Carry on - case SYNC_STATE_SYNC_SUBFOLDERS: - { - if( mCurrentSubfolder ) { - disconnect( mCurrentSubfolder, TQT_SIGNAL( folderComplete(KMFolderCachedImap*, bool) ), - this, TQT_SLOT( slotSubFolderComplete(KMFolderCachedImap*, bool) ) ); - mCurrentSubfolder = 0; - } - - if( mSubfoldersForSync.isEmpty() ) { - mSyncState = SYNC_STATE_INITIAL; - mAccount->addUnreadMsgCount( this, countUnread() ); // before closing - close("cachedimap"); - emit folderComplete( this, true ); - } else { - mCurrentSubfolder = mSubfoldersForSync.front(); - mSubfoldersForSync.pop_front(); - connect( mCurrentSubfolder, TQT_SIGNAL( folderComplete(KMFolderCachedImap*, bool) ), - this, TQT_SLOT( slotSubFolderComplete(KMFolderCachedImap*, bool) ) ); - - //kdDebug(5006) << "Sync'ing subfolder " << mCurrentSubfolder->imapPath() << endl; - assert( !mCurrentSubfolder->imapPath().isEmpty() ); - mCurrentSubfolder->setAccount( account() ); - bool recurse = mCurrentSubfolder->noChildren() ? false : true; - mCurrentSubfolder->serverSync( recurse ); - } + mSyncState = SYNC_STATE_INITIAL; + mAccount->addUnreadMsgCount( this, countUnread() ); // before closing + close( "cachedimap" ); + emit syncStateChanged(); + emit folderComplete( this, true ); } break; default: kdDebug(5006) << "KMFolderCachedImap::serverSyncInternal() WARNING: no such state " - << mSyncState << endl; + << mSyncState << endl; } } +void KMFolderCachedImap::syncNextSubFolder( bool secondSync ) +{ + if( mCurrentSubfolder ) { + disconnectSubFolderSignals(); + } + + if( mSubfoldersForSync.isEmpty() ) { + + // Sync finished, and a close to quota property of an subfolder changed, therefore go into + // the SYNC_STATE_GET_SUBFOLDER_QUOTA state and sync again + if ( mSomeSubFolderCloseToQuotaChanged && mRecurse && !secondSync ) { + buildSubFolderList(); + mSyncState = SYNC_STATE_GET_SUBFOLDER_QUOTA; + serverSyncInternal(); + } + + else { + + // Quota checking has to come after syncing subfolder, otherwise the quota information would + // be outdated, since the subfolders can change in size during the syncing. + // https://issues.kolab.org/issue4066 + mSyncState = SYNC_STATE_GET_QUOTA; + serverSyncInternal(); + } + } else { + mCurrentSubfolder = mSubfoldersForSync.front(); + mSubfoldersForSync.pop_front(); + if ( mCurrentSubfolder ) { + connect( mCurrentSubfolder, TQT_SIGNAL( folderComplete(KMFolderCachedImap*, bool) ), + this, TQT_SLOT( slotSubFolderComplete(KMFolderCachedImap*, bool) ) ); + connect( mCurrentSubfolder, TQT_SIGNAL( closeToQuotaChanged() ), + this, TQT_SLOT( slotSubFolderCloseToQuotaChanged() ) ); + + //kdDebug(5006) << "Sync'ing subfolder " << mCurrentSubfolder->imapPath() << endl; + assert( !mCurrentSubfolder->imapPath().isEmpty() ); + mCurrentSubfolder->setAccount( account() ); + const bool recurse = mCurrentSubfolder->noChildren() ? false : true; + const bool quotaOnly = secondSync || mQuotaOnly; + mCurrentSubfolder->serverSync( recurse, quotaOnly ); + } + else { + // mCurrentSubfolder is empty, probably because it was deleted while syncing. Go on with the + // next subfolder instead. + syncNextSubFolder( secondSync ); + } + } +} + +void KMFolderCachedImap::buildSubFolderList() +{ + mSubfoldersForSync.clear(); + mCurrentSubfolder = 0; + if( folder() && folder()->child() ) { + KMFolderNode *node = folder()->child()->first(); + while( node ) { + if( !node->isDir() ) { + KMFolderCachedImap* storage = static_cast(static_cast(node)->storage()); + const bool folderIsNew = mNewlyCreatedSubfolders.contains( TQGuardedPtr( storage ) ); + // Only sync folders that have been accepted by the server + if ( !storage->imapPath().isEmpty() + // and that were not just deleted from it + && !foldersForDeletionOnServer.contains( storage->imapPath() ) ) { + if ( mRecurse || folderIsNew ) { + mSubfoldersForSync << storage; + } + } else { + kdDebug(5006) << "Do not add " << storage->label() + << " to synclist" << endl; + } + } + node = folder()->child()->next(); + } + } + + mNewlyCreatedSubfolders.clear(); +} + +void KMFolderCachedImap::disconnectSubFolderSignals() +{ + disconnect( mCurrentSubfolder, TQT_SIGNAL( folderComplete(KMFolderCachedImap*, bool) ), + this, TQT_SLOT( slotSubFolderComplete(KMFolderCachedImap*, bool) ) ); + disconnect( mCurrentSubfolder, TQT_SIGNAL( closeToQuotaChanged() ), + this, TQT_SLOT( slotSubFolderCloseToQuotaChanged() ) ); + mCurrentSubfolder = 0; +} + /* Connected to the imap account's connectionResult signal. Emitted when the slave connected or failed to connect. */ @@ -1326,7 +1460,7 @@ void KMFolderCachedImap::uploadNewMessages() { TQValueList newMsgs = findNewMessages(); if( !newMsgs.isEmpty() ) { - if ( mUserRights <= 0 || ( mUserRights & ( KMail::ACLJobs::Insert ) ) ) { + if ( mUserRightsState != KMail::ACLJobs::Ok || ( mUserRights & ( KMail::ACLJobs::Insert ) ) ) { newState( mProgress, i18n("Uploading messages to server")); CachedImapJob *job = new CachedImapJob( newMsgs, CachedImapJob::tPutMessage, this ); connect( job, TQT_SIGNAL( progress( unsigned long, unsigned long) ), @@ -1377,6 +1511,11 @@ void KMFolderCachedImap::uploadFlags() if( !msg || msg->UID() == 0 ) // Either not a valid message or not one that is on the server yet continue; + if ( mUIDsOfLocallyChangedStatuses.find( msg->UID() ) == mUIDsOfLocallyChangedStatuses.end() + && !mStatusChangedLocally ) { + // This message has not had its status changed locally + continue; + } TQString flags = KMFolderImap::statusToFlags(msg->status(), mPermanentFlags); // Collect uids for each typem of flags. @@ -1420,6 +1559,12 @@ void KMFolderCachedImap::uploadSeenFlags() // Either not a valid message or not one that is on the server yet continue; + if ( mUIDsOfLocallyChangedStatuses.find( msg->UID() ) == mUIDsOfLocallyChangedStatuses.end() + && !mStatusChangedLocally ) { + // This message has not had its status changed locally + continue; + } + if ( msg->status() & KMMsgStatusOld || msg->status() & KMMsgStatusRead ) seenUids.append( msg->UID() ); else @@ -1476,13 +1621,21 @@ void KMFolderCachedImap::slotImapStatusChanged(KMFolder* folder, const TQString& void KMFolderCachedImap::setStatus( int idx, KMMsgStatus status, bool toggle) { KMFolderMaildir::setStatus( idx, status, toggle ); - mStatusChangedLocally = true; + const KMMsgBase *msg = getMsgBase( idx ); + Q_ASSERT( msg ); + if ( msg ) + mUIDsOfLocallyChangedStatuses.insert( msg->UID() ); } void KMFolderCachedImap::setStatus(TQValueList& ids, KMMsgStatus status, bool toggle) { KMFolderMaildir::setStatus(ids, status, toggle); - mStatusChangedLocally = true; + for (TQValueList::iterator it = ids.begin(); it != ids.end(); it++ ) { + const KMMsgBase *msg = getMsgBase( *it ); + Q_ASSERT( msg ); + if ( msg ) + mUIDsOfLocallyChangedStatuses.insert( msg->UID() ); + } } /* Upload new folders to server */ @@ -1528,7 +1681,7 @@ TQValueList KMFolderCachedImap::findNewFolders() bool KMFolderCachedImap::deleteMessages() { /* Delete messages from cache that are gone from the server */ - TQPtrList msgsForDeletion; + TQPtrList msgsForDeletion; // It is not possible to just go over all indices and remove // them one by one because the index list can get resized under @@ -1540,11 +1693,15 @@ bool KMFolderCachedImap::deleteMessages() ulong uid ( it.key() ); if( uid!=0 && !uidsOnServer.find( uid ) ) { uids << TQString::number( uid ); - msgsForDeletion.append( getMsg( *it ) ); + msgsForDeletion.append( getMsgBase( *it ) ); } } if( !msgsForDeletion.isEmpty() ) { + if ( contentsType() != ContentsTypeMail ) { + kdDebug(5006) << k_funcinfo << label() << " Going to locally delete " << msgsForDeletion.count() + << " messages, with the uids " << uids.join( "," ) << endl; + } #if MAIL_LOSS_DEBUGGING if ( KMessageBox::warningYesNo( 0, i18n( "

Mails on the server in folder %1 were deleted. " @@ -1554,7 +1711,7 @@ bool KMFolderCachedImap::deleteMessages() removeMsg( msgsForDeletion ); } - if ( mUserRights > 0 && !( mUserRights & KMail::ACLJobs::Delete ) ) + if ( mUserRightsState == KMail::ACLJobs::Ok && !( mUserRights & KMail::ACLJobs::Delete ) ) return false; /* Delete messages from the server that we dont have anymore */ @@ -1688,7 +1845,7 @@ void KMFolderCachedImap::slotGetMessagesData(KIO::Job * job, const TQByteArray & // updated when selecting the folder again, which might not happen if using // RMB / Check Mail in this folder. We don't need two (potentially conflicting) // sources for the readonly setting, in any case. - if (a != -1 && mUserRights == -1 ) { + if (a != -1 && mUserRightsState != KMail::ACLJobs::Ok ) { int b = (*it).cdata.find("\r\n", a + 12); const TQString access = (*it).cdata.mid(a + 12, b - a - 12); setReadOnly( access == "Read only" ); @@ -1751,7 +1908,7 @@ void KMFolderCachedImap::slotGetMessagesData(KIO::Job * job, const TQByteArray & #endif // double check we deleted it since the last sync if ( mDeletedUIDsSinceLastSync.contains(uid) ) { - if ( mUserRights <= 0 || ( mUserRights & KMail::ACLJobs::Delete ) ) { + if ( mUserRightsState != KMail::ACLJobs::Ok || ( mUserRights & KMail::ACLJobs::Delete ) ) { #if MAIL_LOSS_DEBUGGING kdDebug(5006) << "message with uid " << uid << " is gone from local cache. Must be deleted on server!!!" << endl; #endif @@ -1821,7 +1978,8 @@ void KMFolderCachedImap::getMessagesResult( KMail::FolderJob *job, bool lastSet } else { if( lastSet ) { // always true here (this comes from online-imap...) mContentState = imapFinished; - mStatusChangedLocally = false; // we are up to date again + mUIDsOfLocallyChangedStatuses.clear(); // we are up to date again + mStatusChangedLocally = false; } } serverSyncInternal(); @@ -1830,10 +1988,13 @@ void KMFolderCachedImap::getMessagesResult( KMail::FolderJob *job, bool lastSet void KMFolderCachedImap::slotProgress(unsigned long done, unsigned long total) { int progressSpan = 100 - 5 - mProgress; - //kdDebug(5006) << "KMFolderCachedImap::slotProgress done=" << done << " total=" << total << "=> mProgress=" << mProgress + ( progressSpan * done ) / total << endl; + int additionalProgress = ( total == 0 ) ? + progressSpan : + ( progressSpan * done ) / total; + // Progress info while retrieving new emails // (going from mProgress to mProgress+progressSpan) - newState( mProgress + (progressSpan * done) / total, TQString::null ); + newState( mProgress + additionalProgress, TQString::null ); } void KMFolderCachedImap::setAccount(KMAcctCachedImap *aAccount) @@ -2010,7 +2171,7 @@ void KMFolderCachedImap::slotListResult( const TQStringList& folderNames, mSubfolderMimeTypes = folderMimeTypes; mSubfolderState = imapFinished; mSubfolderAttributes = folderAttributes; - kdDebug(5006) << "##### setting subfolder attributes: " << mSubfolderAttributes << endl; + //kdDebug(5006) << "##### setting subfolder attributes: " << mSubfolderAttributes << endl; folder()->createChildFolder(); KMFolderNode *node = folder()->child()->first(); @@ -2209,6 +2370,7 @@ void KMFolderCachedImap::createFoldersNewOnServerAndFinishListing( const TQValue f->setNoChildren(mSubfolderMimeTypes[idx] == "message/digest"); f->setImapPath(mSubfolderPaths[idx]); f->mFolderAttributes = mSubfolderAttributes[idx]; + mNewlyCreatedSubfolders.append( TQGuardedPtr( f ) ); kdDebug(5006) << " ####### Attributes: " << f->mFolderAttributes <dimapFolderMgr()->contentsChanged(); @@ -2267,18 +2429,26 @@ void KMFolderCachedImap::slotSubFolderComplete(KMFolderCachedImap* sub, bool suc // success == false means the sync was aborted. if ( mCurrentSubfolder ) { Q_ASSERT( sub == mCurrentSubfolder ); - disconnect( mCurrentSubfolder, TQT_SIGNAL( folderComplete(KMFolderCachedImap*, bool) ), - this, TQT_SLOT( slotSubFolderComplete(KMFolderCachedImap*, bool) ) ); - mCurrentSubfolder = 0; + disconnectSubFolderSignals(); } + // Next step would be to check quota limits and then to close the folder, but don't bother with + // both and close the folder right here, since we aborted. mSubfoldersForSync.clear(); mSyncState = SYNC_STATE_INITIAL; close("cachedimap"); + emit syncStateChanged(); emit folderComplete( this, false ); } } +void KMFolderCachedImap::slotSubFolderCloseToQuotaChanged() +{ + if ( !mQuotaOnly ) { + mSomeSubFolderCloseToQuotaChanged = true; + } +} + void KMFolderCachedImap::slotSimpleData(KIO::Job * job, const TQByteArray & data) { KMAcctCachedImap::JobIterator it = mAccount->findJob(job); @@ -2312,9 +2482,10 @@ KMFolderCachedImap::doCreateJob( TQPtrList& msgList, const TQString& } void -KMFolderCachedImap::setUserRights( unsigned int userRights ) +KMFolderCachedImap::setUserRights( unsigned int userRights, KMail::ACLJobs::ACLFetchState state ) { mUserRights = userRights; + mUserRightsState = state; writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig(); } @@ -2324,10 +2495,9 @@ KMFolderCachedImap::slotReceivedUserRights( KMFolder* folder ) if ( folder->storage() == this ) { disconnect( mAccount, TQT_SIGNAL( receivedUserRights( KMFolder* ) ), this, TQT_SLOT( slotReceivedUserRights( KMFolder* ) ) ); - if ( mUserRights == 0 ) // didn't work - mUserRights = -1; // error code (used in folderdia) - else + if ( mUserRightsState == KMail::ACLJobs::Ok ) { setReadOnly( ( mUserRights & KMail::ACLJobs::Insert ) == 0 ); + } mProgress += 5; serverSyncInternal(); } @@ -2343,11 +2513,12 @@ KMFolderCachedImap::setReadOnly( bool readOnly ) } void -KMFolderCachedImap::slotReceivedACL( KMFolder* folder, KIO::Job*, const KMail::ACLList& aclList ) +KMFolderCachedImap::slotReceivedACL( KMFolder* folder, KIO::Job* job, const KMail::ACLList& aclList ) { if ( folder->storage() == this ) { disconnect( mAccount, TQT_SIGNAL(receivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )), this, TQT_SLOT(slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )) ); + mACLListState = job->error() ? KMail::ACLJobs::FetchFailed : KMail::ACLJobs::Ok; mACLList = aclList; serverSyncInternal(); } @@ -2362,8 +2533,12 @@ KMFolderCachedImap::slotStorageQuotaResult( const QuotaInfo& info ) void KMFolderCachedImap::setQuotaInfo( const QuotaInfo & info ) { if ( info != mQuotaInfo ) { + const bool wasCloseToQuota = isCloseToQuota(); mQuotaInfo = info; writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig(); + if ( wasCloseToQuota != isCloseToQuota() ) { + emit closeToQuotaChanged(); + } emit folderSizeChanged(); } } @@ -2372,6 +2547,7 @@ void KMFolderCachedImap::setACLList( const ACLList& arr ) { mACLList = arr; + mACLListState = KMail::ACLJobs::Ok; } void @@ -2413,6 +2589,7 @@ void KMFolderCachedImap::resetSyncState() { if ( mSyncState == SYNC_STATE_INITIAL ) return; mSubfoldersForSync.clear(); + mNewlyCreatedSubfolders.clear(); mSyncState = SYNC_STATE_INITIAL; close("cachedimap"); // Don't use newState here, it would revert to mProgress (which is < current value when listing messages) @@ -2421,6 +2598,7 @@ void KMFolderCachedImap::resetSyncState() if (progressItem) progressItem->setStatus( str ); emit statusMsg( str ); + emit syncStateChanged(); } void KMFolderCachedImap::slotIncreaseProgress() @@ -2472,6 +2650,16 @@ void KMFolderCachedImap::setImapPath(const TQString &path) mImapPath = path; } +static bool isFolderTypeKnownToUs( const TQString &type ) +{ + for ( uint i = 0 ; i <= ContentsTypeLast; ++i ) { + FolderContentsType contentsType = static_cast( i ); + if ( type == KMailICalIfaceImpl::annotationForContentsType( contentsType ) ) + return true; + } + return false; +} + // mAnnotationFolderType is the annotation as known to the server (and stored in kmailrc) // It is updated from the folder contents type and whether it's a standard resource folder. // This happens during the syncing phase and during initFolder for a new folder. @@ -2493,12 +2681,18 @@ void KMFolderCachedImap::updateAnnotationFolderType() newType = KMailICalIfaceImpl::annotationForContentsType( mContentsType ); if ( kmkernel->iCalIface().isStandardResourceFolder( folder() ) ) newSubType = "default"; - else - newSubType = oldSubType; // preserve unknown subtypes, like drafts etc. And preserve ".default" too. + else if ( oldSubType != "default" ) + newSubType = oldSubType; // preserve unknown subtypes, like drafts etc. } + // We do not want to overwrite custom folder types (which we treat as mail folders). + // So only overwrite custom folder types if the user changed the folder type himself to something + // other than mail. + const bool changingTypeAllowed = isFolderTypeKnownToUs( oldType ) || + ( mContentsType != ContentsTypeMail ); + //kdDebug(5006) << mImapPath << ": updateAnnotationFolderType: " << newType << " " << newSubType << endl; - if ( newType != oldType || newSubType != oldSubType ) { + if ( ( newType != oldType || newSubType != oldSubType ) && changingTypeAllowed ) { mAnnotationFolderType = newType + ( newSubType.isEmpty() ? TQString::null : "."+newSubType ); mAnnotationFolderTypeChanged = true; // force a "set annotation" on next sync kdDebug(5006) << mImapPath << ": updateAnnotationFolderType: '" << mAnnotationFolderType << "', was (" << oldType << " " << oldSubType << ") => mAnnotationFolderTypeChanged set to TRUE" << endl; @@ -2515,6 +2709,14 @@ void KMFolderCachedImap::setIncidencesFor( IncidencesFor incfor ) } } +void KMFolderCachedImap::setSharedSeenFlags(bool b) +{ + if ( mSharedSeenFlags != b ) { + mSharedSeenFlags = b; + mSharedSeenFlagsChanged = true; + } +} + void KMFolderCachedImap::slotAnnotationResult(const TQString& entry, const TQString& value, bool found) { if ( entry == KOLAB_FOLDERTYPE ) { @@ -2559,16 +2761,21 @@ void KMFolderCachedImap::slotAnnotationResult(const TQString& entry, const TQStr if ( contentsType != ContentsTypeMail ) markUnreadAsRead(); - // Ensure that further readConfig()s don't lose mAnnotationFolderType - writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig(); break; } } - if ( !foundKnownType && !mReadOnly ) { - //kdDebug(5006) << "slotGetAnnotationResult: no known type of annotation found, will need to set it" << endl; - // Case 4: server has strange content-type, set it to what we need - mAnnotationFolderTypeChanged = true; + if ( !foundKnownType ) { + //kdDebug(5006) << "slotGetAnnotationResult: no known type of annotation found, leaving it untouched" << endl; + + // Case 4: Server has strange content-type. We must not overwrite it, see https://issues.kolab.org/issue2069. + // Treat the content-type as mail until we change it ourselves. + mAnnotationFolderTypeChanged = false; + mAnnotationFolderType = value; + setContentsType( ContentsTypeMail ); } + + // Ensure that further readConfig()s don't lose mAnnotationFolderType + writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig(); // TODO handle subtype (inbox, drafts, sentitems, junkemail) } else if ( !mReadOnly ) { @@ -2581,6 +2788,10 @@ void KMFolderCachedImap::slotAnnotationResult(const TQString& entry, const TQStr mIncidencesFor = incidencesForFromString( value ); Q_ASSERT( mIncidencesForChanged == false ); } + } else if ( entry == KOLAB_SHAREDSEEN ) { + if ( found ) { + mSharedSeenFlags = value == "true"; + } } } @@ -2700,6 +2911,8 @@ KMFolderCachedImap::slotAnnotationChanged( const TQString& entry, const TQString // The incidences-for changed, we must trigger the freebusy creation. // HACK: in theory we would need a new enum value for this. kmkernel->iCalIface().addFolderChange( folder(), KMailICalIfaceImpl::ACL ); + } else if ( entry == KOLAB_SHAREDSEEN ) { + mSharedSeenFlagsChanged = false; } } @@ -2989,4 +3202,27 @@ void KMFolderCachedImap::slotRescueDone(KMCommand * command) serverSyncInternal(); } +void KMFolderCachedImap::slotRenameFolderFinished() +{ + // The syncing code assumes the folder was opened by us, and later closes it. So better + // make sure the reference count is correct, since the folder was force-closed by the rename. + // Otherwise bad things can happen, see https://issues.kolab.org/issue3853. + open( "cachedimap" ); + serverSyncInternal(); +} + +bool KMFolderCachedImap::canDeleteMessages() const +{ + if ( isReadOnly() ) + return false; + if ( mUserRightsState == KMail::ACLJobs::Ok && !(userRights() & ACLJobs::Delete) ) + return false; + return true; +} + +bool KMFolderCachedImap::mailCheckInProgress() const +{ + return mSyncState != SYNC_STATE_INITIAL; +} + #include "kmfoldercachedimap.moc" diff --git a/kmail/kmfoldercachedimap.h b/kmail/kmfoldercachedimap.h index 5ead77b25..21abeabf9 100644 --- a/kmail/kmfoldercachedimap.h +++ b/kmail/kmfoldercachedimap.h @@ -47,6 +47,8 @@ #include "cachedimapjob.h" #include "quotajobs.h" +#include + using KMail::FolderJob; using KMail::QuotaInfo; class KMCommand; @@ -79,10 +81,11 @@ public: private slots: void slotDone(); - + void slotChanged(); private: TQRadioButton *mIndexButton, *mCacheButton; TQComboBox *mIndexScope; + TQButtonGroup *mButtonGroup; int rc; }; @@ -122,7 +125,7 @@ public: virtual void remove(); /** Synchronize this folder and it's subfolders with the server */ - virtual void serverSync( bool recurse ); + virtual void serverSync( bool recurse, bool quotaOnly = false ); /** Force the sync state to be done. */ void resetSyncState( ); @@ -138,7 +141,7 @@ public: enum imapState { imapNoInformation=0, imapInProgress=1, imapFinished=2 }; - virtual imapState getContentState() { return mContentState; } + virtual imapState getContentState() const { return mContentState; } virtual void setContentState(imapState state) { mContentState = state; } virtual imapState getSubfolderState() { return mSubfolderState; } @@ -206,11 +209,12 @@ public: /* Reimplemented from KMFolderMaildir */ virtual void removeMsg(int i, bool imapQuiet = false); - virtual void removeMsg(TQPtrList msgList, bool imapQuiet = false) + virtual void removeMsg( const TQPtrList & msgList, bool imapQuiet = false) { FolderStorage::removeMsg(msgList, imapQuiet); } /// Is the folder readonly? bool isReadOnly() const { return KMFolderMaildir::isReadOnly() || mReadOnly; } + bool canDeleteMessages() const; /** @@ -249,12 +253,14 @@ public: /** * The user's rights on this folder - see bitfield in ACLJobs namespace. - * @return 0 when not known yet, -1 if there was an error fetching them + * Note that the returned value is only valid if userRightsState() returns Ok, so + * that should be checked first. */ int userRights() const { return mUserRights; } + KMail::ACLJobs::ACLFetchState userRightsState() const { return mUserRightsState; } /// Set the user's rights on this folder - called by getUserRights - void setUserRights( unsigned int userRights ); + void setUserRights( unsigned int userRights, KMail::ACLJobs::ACLFetchState state ); /** * The quota information for this folder. @@ -271,6 +277,7 @@ public: /// Return the list of ACL for this folder typedef TQValueVector ACLList; const ACLList& aclList() const { return mACLList; } + KMail::ACLJobs::ACLFetchState aclListState() const { return mACLListState; }; /// Set the list of ACL for this folder (for FolderDiaACLTab) void setACLList( const ACLList& arr ); @@ -298,6 +305,11 @@ public: /// For the folder properties dialog void setIncidencesFor( IncidencesFor incfor ); + /** Returns wether the seen flag is shared among all users or every users has her own seen flags (default). */ + bool sharedSeenFlags() const { return mSharedSeenFlags; } + /** Enable shared seen flags (requires server support). */ + void setSharedSeenFlags( bool b ); + /** Returns true if this folder can be moved */ virtual bool isMoveable() const; @@ -324,6 +336,8 @@ public: TQString folderAttributes() const { return mFolderAttributes; } + virtual bool mailCheckInProgress() const; + protected slots: void slotGetMessagesData(KIO::Job * job, const TQByteArray & data); void getMessagesResult(KMail::FolderJob *, bool lastSet); @@ -333,6 +347,7 @@ protected slots: //virtual void slotCheckValidityResult(KIO::Job * job); void slotSubFolderComplete(KMFolderCachedImap*, bool); + void slotSubFolderCloseToQuotaChanged(); // Connected to the imap account void slotConnectionResult( int errorCode, const TQString& errorMsg ); @@ -426,15 +441,16 @@ private slots: void slotUpdateLastUid(); void slotFolderDeletionOnServerFinished(); void slotRescueDone( KMCommand* command ); + void slotRenameFolderFinished(); signals: void folderComplete(KMFolderCachedImap *folder, bool success); void listComplete( KMFolderCachedImap* ); - /** emitted when we enter the state "state" and - have to process "number" items (for example messages - */ - void syncState( int state, int number ); + /** + * Emitted when isCloseToQuota() changes during syncing + */ + void closeToQuotaChanged(); private: void setReadOnly( bool readOnly ); @@ -448,6 +464,24 @@ private: /** Recursive helper function calling the above method. */ void rescueUnsyncedMessagesAndDeleteFolder( KMFolder *folder, bool root = true ); + /** + * Small helper function that disconnects the signals from the current subfolder, which where + * connected when starting the sync of that subfolder + */ + void disconnectSubFolderSignals(); + + /** + * Sync the next subfolder in the list of subfolders (mSubfoldersForSync). + * When finished, this will switch either to the state SYNC_STATE_GET_SUBFOLDER_QUOTA or + * to SYNC_STATE_GET_QUOTA. + */ + void syncNextSubFolder( bool secondSync ); + + /** + * Creates the mSubfoldersForSync list + */ + void buildSubFolderList(); + /** State variable for the synchronization mechanism */ enum { SYNC_STATE_INITIAL, @@ -473,7 +507,9 @@ private: SYNC_STATE_FIND_SUBFOLDERS, SYNC_STATE_SYNC_SUBFOLDERS, SYNC_STATE_CHECK_UIDVALIDITY, - SYNC_STATE_RENAME_FOLDER + SYNC_STATE_RENAME_FOLDER, + SYNC_STATE_CLOSE, + SYNC_STATE_GET_SUBFOLDER_QUOTA } mSyncState; int mProgress; @@ -487,6 +523,7 @@ private: TQString mFolderAttributes; TQString mAnnotationFolderType; IncidencesFor mIncidencesFor; + bool mSharedSeenFlags; bool mHasInbox; bool mIsSelected; @@ -500,7 +537,7 @@ private: TQValueList mUidsForDownload; TQStringList foldersForDeletionOnServer; - TQValueList mSubfoldersForSync; + TQValueList< TQGuardedPtr > mSubfoldersForSync; KMFolderCachedImap* mCurrentSubfolder; /** Mapping uid -> index @@ -533,21 +570,36 @@ private: bool mFoundAnIMAPDigest; int mUserRights, mOldUserRights; + KMail::ACLJobs::ACLFetchState mUserRightsState; ACLList mACLList; + KMail::ACLJobs::ACLFetchState mACLListState; bool mSilentUpload; bool mFolderRemoved; //bool mHoldSyncs; bool mRecurse; - /** Set to true by setStatus. Indicates that the client has changed - the status of at least one mail. The mail flags will therefore be - uploaded to the server, overwriting the server's notion of the status - of the mails in this folder. */ - bool mStatusChangedLocally; + bool mQuotaOnly; + /// Set to true when the foldertype annotation needs to be set on the next sync bool mAnnotationFolderTypeChanged; /// Set to true when the "incidences-for" annotation needs to be set on the next sync bool mIncidencesForChanged; + /// Set to true when the "sharedseen" annotation needs to be set on the next sync + bool mSharedSeenFlagsChanged; + + /** + * UIDs added by setStatus. Indicates that the client has changed + * the status of those mails. The mail flags for changed mails will be + * uploaded to the server, overwriting the server's notion of the status + * of the mails in this folder. + */ + std::set mUIDsOfLocallyChangedStatuses; + + /** + * Same as above, but uploads the flags of all mails, even if not all changed. + * Only still here for config compatibility. + */ + bool mStatusChangedLocally; TQStringList mNamespacesToList; int mNamespacesToCheck; @@ -555,12 +607,18 @@ private: TQString mImapPathCreation; QuotaInfo mQuotaInfo; + + /// This is set during syncing of the current subfolder. If true, it means the closeToQuota info + /// for the current subfolder has changed during syncing + bool mSomeSubFolderCloseToQuotaChanged; + TQMap mDeletedUIDsSinceLastSync; bool mAlarmsBlocked; TQValueList mToBeDeletedAfterRescue; int mRescueCommandCount; + TQValueList< TQGuardedPtr > mNewlyCreatedSubfolders; int mPermanentFlags; }; diff --git a/kmail/kmfolderdia.cpp b/kmail/kmfolderdia.cpp index cf02fcddf..533173e5e 100644 --- a/kmail/kmfolderdia.cpp +++ b/kmail/kmfolderdia.cpp @@ -32,6 +32,7 @@ #include +#include "acljobs.h" #include "kmfolderdia.h" #include "kmacctfolder.h" #include "kmfoldermgr.h" @@ -247,20 +248,20 @@ static void addLine( TQWidget *parent, TQVBoxLayout* layout ) KMail::FolderDiaGeneralTab::FolderDiaGeneralTab( KMFolderDialog* dlg, const TQString& aName, TQWidget* parent, const char* name ) - : FolderDiaTab( parent, name ), mDlg( dlg ) + : FolderDiaTab( parent, name ), + mSharedSeenFlagsCheckBox( 0 ), + mDlg( dlg ) { - - mIsLocalSystemFolder = mDlg->folder()->isSystemFolder() && - mDlg->folder()->folderType() != KMFolderTypeImap && - mDlg->folder()->folderType() != KMFolderTypeCachedImap; + mIsLocalSystemFolder = mDlg->folder()->isSystemFolder(); + mIsResourceFolder = kmkernel->iCalIface().isStandardResourceFolder( mDlg->folder() ); TQLabel *label; TQVBoxLayout *topLayout = new TQVBoxLayout( this, 0, KDialog::spacingHint() ); - // Musn't be able to edit details for a system folder. - if ( !mIsLocalSystemFolder ) { + // Musn't be able to edit details for a non-resource, system folder. + if ( !mIsLocalSystemFolder || mIsResourceFolder ) { TQHBoxLayout *hl = new TQHBoxLayout( topLayout ); hl->setSpacing( KDialog::spacingHint() ); @@ -268,9 +269,65 @@ KMail::FolderDiaGeneralTab::FolderDiaGeneralTab( KMFolderDialog* dlg, label = new TQLabel( i18n("&Name:"), this ); hl->addWidget( label ); + // Determine if we are allowed to rename this folder. Only possible if the folder supports + // ACLs. + bool nameChangeAllowed = true; + if ( mDlg->folder() && mDlg->parentFolder() && + mDlg->folder()->storage() && mDlg->parentFolder()->storage() && + ( mDlg->folder()->folderType() == KMFolderTypeCachedImap || + mDlg->folder()->folderType() == KMFolderTypeImap ) ) { + ImapAccountBase *account = 0; + KMFolderCachedImap *dimap = 0; + KMFolderImap *imap = 0; + if ( mDlg->folder()->folderType() == KMFolderTypeCachedImap ) { + dimap = static_cast( mDlg->folder()->storage() ); + account = dynamic_cast( dimap->account() ); + } + if ( mDlg->folder()->folderType() == KMFolderTypeImap ) { + imap = static_cast( mDlg->folder()->storage() ); + account = dynamic_cast( imap->account() ); + } + + if ( account && account->hasACLSupport() ) { + int parentRights = -1; + int folderRights = -1; + bool parentRightsOk = false; + bool folderRightsOk = false; + if ( imap ) { + KMFolderImap * const parent = dynamic_cast( mDlg->parentFolder()->storage() ); + folderRights = imap->userRights(); + folderRightsOk = imap->userRightsState() == KMail::ACLJobs::Ok; + if ( parent ) { + parentRights = parent->userRights(); + parentRightsOk = parent->userRightsState() == KMail::ACLJobs::Ok; + } + } else if ( dimap ) { + KMFolderCachedImap * const parent = dynamic_cast( mDlg->parentFolder()->storage() ); + folderRights = dimap->userRights(); + folderRightsOk = dimap->userRightsState() == KMail::ACLJobs::Ok; + if ( parent ) { + parentRights = parent->userRights(); + parentRightsOk = parent->userRightsState() == KMail::ACLJobs::Ok; + } + } + + // For renaming, we need support for deleting the mailbox and then re-creating it. + if ( parentRightsOk && folderRightsOk && + ( !( parentRights & KMail::ACLJobs::Create ) || !( folderRights & KMail::ACLJobs::Delete ) ) ) { + nameChangeAllowed = false; + } + } + } + mNameEdit = new KLineEdit( this ); - if( !mDlg->folder() ) - mNameEdit->setFocus(); + if( !mDlg->folder() && nameChangeAllowed ) + mNameEdit->setFocus(); + mNameEdit->setEnabled( nameChangeAllowed ); + if ( !nameChangeAllowed ) { + TQToolTip::add( mNameEdit, i18n( "Not enough permissions to rename this folder.\n" + "The parent folder doesn't have write support.\n" + "A sync is needed after changing the permissions." ) ); + } mNameEdit->setText( mDlg->folder() ? mDlg->folder()->label() : i18n("unnamed") ); if (!aName.isEmpty()) mNameEdit->setText(aName); @@ -433,9 +490,10 @@ KMail::FolderDiaGeneralTab::FolderDiaGeneralTab( KMFolderDialog* dlg, "automatically. Identities can be set up in the main configuration " "dialog. (Settings -> Configure KMail)") ); - // folder contents - if ( !mIsLocalSystemFolder && kmkernel->iCalIface().isEnabled() ) { + if ( ( !mIsLocalSystemFolder || mIsResourceFolder ) && + kmkernel->iCalIface().isEnabled() && + mDlg->folder() && mDlg->folder()->folderType() != KMFolderTypeImap ) { // Only do make this settable, if the IMAP resource is enabled // and it's not the personal folders (those must not be changed) ++row; @@ -455,12 +513,12 @@ KMail::FolderDiaGeneralTab::FolderDiaGeneralTab( KMFolderDialog* dlg, mContentsComboBox->setCurrentItem( mDlg->folder()->storage()->contentsType() ); connect ( mContentsComboBox, TQT_SIGNAL ( activated( int ) ), this, TQT_SLOT( slotFolderContentsSelectionChanged( int ) ) ); - if ( mDlg->folder()->isReadOnly() ) + if ( mDlg->folder()->isReadOnly() || mIsResourceFolder ) mContentsComboBox->setEnabled( false ); } else { mContentsComboBox = 0; } - + mIncidencesForComboBox = 0; mAlarmsBlockedCheckBox = 0; @@ -478,7 +536,7 @@ KMail::FolderDiaGeneralTab::FolderDiaGeneralTab( KMFolderDialog* dlg, label->setBuddy( mIncidencesForComboBox ); gl->addWidget( mIncidencesForComboBox, row, 1 ); - const TQString whatsThisForMyOwnFolders = + const TQString whatsThisForMyOwnFolders = i18n( "This setting defines which users sharing " "this folder should get \"busy\" periods in their freebusy lists " "and should see the alarms for the events or tasks in this folder. " @@ -499,13 +557,10 @@ KMail::FolderDiaGeneralTab::FolderDiaGeneralTab( KMFolderDialog* dlg, mIncidencesForComboBox->insertItem( i18n( "All Readers of This Folder" ) ); ++row; const TQString whatsThisForReadOnlyFolders = - i18n( "This setting allows you to disable alarms for folders shared by " - "others. "); + i18n( "This setting allows you to disable alarms for folders shared by others. "); mAlarmsBlockedCheckBox = new TQCheckBox( this ); - gl->addWidget( mAlarmsBlockedCheckBox, row, 0 ); - label = new TQLabel( i18n( "Block free/&busy and alarms locally" ), this ); - gl->addWidget( label, row, 1 ); - label->setBuddy( mAlarmsBlockedCheckBox ); + mAlarmsBlockedCheckBox->setText( i18n( "Block alarms locally" ) ); + gl->addMultiCellWidget( mAlarmsBlockedCheckBox, row, row, 0, 1); TQWhatsThis::add( mAlarmsBlockedCheckBox, whatsThisForReadOnlyFolders ); if ( mDlg->folder()->storage()->contentsType() != KMail::ContentsTypeCalendar @@ -514,6 +569,17 @@ KMail::FolderDiaGeneralTab::FolderDiaGeneralTab( KMFolderDialog* dlg, mAlarmsBlockedCheckBox->setEnabled( false ); } } + + if ( mDlg->folder()->folderType() == KMFolderTypeCachedImap ) { + kdDebug() << k_funcinfo << mDlg->folder()->folderType() << endl; + mSharedSeenFlagsCheckBox = new TQCheckBox( this ); + mSharedSeenFlagsCheckBox->setText( i18n( "Share unread state with all users" ) ); + ++row; + gl->addMultiCellWidget( mSharedSeenFlagsCheckBox, row, row, 0, 1 ); + TQWhatsThis::add( mSharedSeenFlagsCheckBox, i18n( "If enabled, the unread state of messages in this folder will be the same " + "for all users having access to this folders. If disabled (the default), every user with access to this folder has her " + "own unread state." ) ); + } topLayout->addStretch( 100 ); // eat all superfluous space initializeWithValuesFromFolder( mDlg->folder() ); @@ -568,6 +634,16 @@ void FolderDiaGeneralTab::initializeWithValuesFromFolder( KMFolder* folder ) { KMFolderCachedImap* dimap = static_cast( folder->storage() ); mAlarmsBlockedCheckBox->setChecked( dimap->alarmsBlocked() ); } + if ( mSharedSeenFlagsCheckBox ) { + KMFolderCachedImap *dimap = static_cast( folder->storage() ); + ImapAccountBase *account = dynamic_cast( dimap->account() ); + mSharedSeenFlagsCheckBox->setChecked( dimap->sharedSeenFlags() ); + mSharedSeenFlagsCheckBox->setDisabled( folder->isReadOnly() ); + if ( account && account->hasCapability( "x-kmail-sharedseen" ) ) + mSharedSeenFlagsCheckBox->show(); + else + mSharedSeenFlagsCheckBox->hide(); + } } //----------------------------------------------------------------------------- @@ -614,11 +690,13 @@ bool FolderDiaGeneralTab::save() folder->setPutRepliesInSameFolder( mKeepRepliesInSameFolderCheckBox->isChecked() ); TQString fldName, oldFldName; - if ( !mIsLocalSystemFolder ) + KMFolderCachedImap* dimap = 0; + if ( folder->folderType() == KMFolderTypeCachedImap ) + dimap = static_cast( mDlg->folder()->storage() ); + + if ( !mIsLocalSystemFolder || mIsResourceFolder ) { - TQString acctName; oldFldName = mDlg->folder()->name(); - if (!mNameEdit->text().isEmpty()) fldName = mNameEdit->text(); else @@ -641,11 +719,11 @@ bool FolderDiaGeneralTab::save() folder->setIconPaths( "", "" ); } } - if ( folder->useCustomIcons() && + if ( folder->useCustomIcons() && ( (( mNormalIconButton->icon() != folder->normalIconPath() ) && ( !mNormalIconButton->icon().isEmpty())) || (( mUnreadIconButton->icon() != folder->unreadIconPath() ) && - ( !mUnreadIconButton->icon().isEmpty())) ) { + ( !mUnreadIconButton->icon().isEmpty())) ) ) { folder->setIconPaths( mNormalIconButton->icon(), mUnreadIconButton->icon() ); } @@ -656,8 +734,7 @@ bool FolderDiaGeneralTab::save() folder->storage()->setContentsType( type ); } - if ( folder->folderType() == KMFolderTypeCachedImap ) { - KMFolderCachedImap* dimap = static_cast( mDlg->folder()->storage() ); + if ( dimap ) { if ( mIncidencesForComboBox ) { KMFolderCachedImap::IncidencesFor incfor = KMFolderCachedImap::IncForAdmins; incfor = static_cast( mIncidencesForComboBox->currentItem() ); @@ -678,9 +755,23 @@ bool FolderDiaGeneralTab::save() imapFolder->setIncludeInMailCheck( mNewMailCheckBox->isChecked() ); } - // make sure everything is on disk, connected slots will call readConfig() - // when creating a new folder. - folder->storage()->writeConfig(); + } + + if ( dimap && mSharedSeenFlagsCheckBox && + mSharedSeenFlagsCheckBox->isChecked() != dimap->sharedSeenFlags() ) { + dimap->setSharedSeenFlags( mSharedSeenFlagsCheckBox->isChecked() ); + dimap->writeConfig(); + } + + // make sure everything is on disk, connected slots will call readConfig() + // when creating a new folder. + folder->storage()->writeConfig(); + + TQString msg; + if ( !folder->isValidName( fldName, msg ) ) { + KMessageBox::sorry( this, msg ); + return false; + } else { // Renamed an existing folder? We don't check for oldName == newName on // purpose here. The folder might be pending renaming on the next dimap // sync already, in which case the old name would still be around and @@ -694,6 +785,7 @@ bool FolderDiaGeneralTab::save() kmkernel->folderMgr()->contentsChanged(); } } + return true; } @@ -708,9 +800,8 @@ KMail::FolderDiaTemplatesTab::FolderDiaTemplatesTab( KMFolderDialog* dlg, : FolderDiaTab( parent, 0 ), mDlg( dlg ) { - mIsLocalSystemFolder = mDlg->folder()->isSystemFolder() && - mDlg->folder()->folderType() != KMFolderTypeImap && - mDlg->folder()->folderType() != KMFolderTypeCachedImap; + mIsLocalSystemFolder = mDlg->folder()->isSystemFolder(); + TQVBoxLayout *topLayout = new TQVBoxLayout( this, 0, KDialog::spacingHint() ); diff --git a/kmail/kmfolderdia.h b/kmail/kmfolderdia.h index 913685d85..4db30ab12 100644 --- a/kmail/kmfolderdia.h +++ b/kmail/kmfolderdia.h @@ -137,6 +137,7 @@ private: TQComboBox *mContentsComboBox; TQComboBox *mIncidencesForComboBox; TQCheckBox *mAlarmsBlockedCheckBox; + TQCheckBox *mSharedSeenFlagsCheckBox; TQLabel *mNormalIconLabel; KIconButton *mNormalIconButton; TQLabel *mUnreadIconLabel; @@ -151,6 +152,7 @@ private: KMFolderDialog* mDlg; bool mIsLocalSystemFolder; + bool mIsResourceFolder; }; /** diff --git a/kmail/kmfolderdir.cpp b/kmail/kmfolderdir.cpp index 46aba345c..1ecab637b 100644 --- a/kmail/kmfolderdir.cpp +++ b/kmail/kmfolderdir.cpp @@ -163,6 +163,31 @@ TQString KMFolderDir::prettyURL() const return label(); } +//----------------------------------------------------------------------------- +void KMFolderDir::addDirToParent( const TQString &dirName, KMFolder *parentFolder ) +{ + KMFolderDir* folderDir = new KMFolderDir( parentFolder, this, dirName, mDirType); + folderDir->reload(); + append( folderDir ); + parentFolder->setChild( folderDir ); +} + +// Get the default folder type of the given dir type. This function should only be used when +// needing to find out what the folder type of a missing folder is. +KMFolderType dirTypeToFolderType( KMFolderDirType dirType ) +{ + switch( dirType ) { + + // Use maildir for normal folder dirs, as this function is only called when finding a dir + // without a parent folder, which can only happen with maildir-like folders + case KMStandardDir: return KMFolderTypeMaildir; + + case KMImapDir: return KMFolderTypeImap; + case KMDImapDir: return KMFolderTypeCachedImap; + case KMSearchDir: return KMFolderTypeSearch; + default: Q_ASSERT( false ); return KMFolderTypeMaildir; + } +} //----------------------------------------------------------------------------- bool KMFolderDir::reload(void) @@ -272,6 +297,7 @@ bool KMFolderDir::reload(void) } } + TQStringList dirsWithoutFolder = diList; for (folder=folderList.first(); folder; folder=folderList.next()) { for(TQStringList::Iterator it = diList.begin(); @@ -279,13 +305,36 @@ bool KMFolderDir::reload(void) ++it) if (*it == "." + folder->fileName() + ".directory") { - KMFolderDir* folderDir = new KMFolderDir( folder, this, *it, mDirType); - folderDir->reload(); - append(folderDir); - folder->setChild(folderDir); + dirsWithoutFolder.remove( *it ); + addDirToParent( *it, folder ); break; } } + + // Check if the are any dirs without an associated folder. This can happen if the user messes + // with the on-disk folder structure, see kolab issue 2972. In that case, we don't want to loose + // the subfolders as well, so we recreate the folder so the folder/dir hierachy is OK again. + if ( type() == KMDImapDir ) { + for ( TQStringList::Iterator it = dirsWithoutFolder.begin(); + it != dirsWithoutFolder.end(); ++it ) { + + // .foo.directory => foo + TQString folderName = *it; + int right = folderName.find( ".directory" ); + int left = folderName.find( "." ); + Q_ASSERT( left != -1 && right != -1 ); + folderName = folderName.mid( left + 1, right - 1 ); + + kdDebug(5006) << "Found dir without associated folder: " << ( *it ) << ", recreating the folder " << folderName << "." << endl; + + // Recreate the missing folder + KMFolder *folder = new KMFolder( this, folderName, KMFolderTypeCachedImap ); + append( folder ); + folderList.append( folder ); + + addDirToParent( *it, folder ); + } + } return TRUE; } diff --git a/kmail/kmfolderdir.h b/kmail/kmfolderdir.h index 72f4c4326..c93317e52 100644 --- a/kmail/kmfolderdir.h +++ b/kmail/kmfolderdir.h @@ -19,11 +19,16 @@ class KMFolderDir: public KMFolderNode, public KMFolderNodeList public: KMFolderDir( KMFolder * owner, KMFolderDir * parent = 0, const TQString& path = TQString::null, - KMFolderDirType = KMStandardDir ); + KMFolderDirType = KMStandardDir ); virtual ~KMFolderDir(); virtual bool isDir() const { return true; } + /** + * Adds the given subdirectory of this directory to the associated folder. + */ + void addDirToParent( const TQString &dirName, KMFolder *parentFolder ); + /** Read contents of directory. */ virtual bool reload(); @@ -39,9 +44,9 @@ public: /** Create a mail folder in this directory with given name. If sysFldr==TRUE the folder is marked as a (KMail) system folder. Returns Folder on success. */ - virtual KMFolder* createFolder(const TQString& folderName, - bool sysFldr=false, - KMFolderType folderType=KMFolderTypeMbox); + virtual KMFolder* createFolder( const TQString& folderName, + bool sysFldr=false, + KMFolderType folderType=KMFolderTypeMbox ); /** Returns folder with given name or zero if it does not exist */ virtual KMFolderNode* hasNamedFolder(const TQString& name); @@ -67,9 +72,9 @@ class KMFolderRootDir: public KMFolderDir Q_OBJECT public: - KMFolderRootDir(KMFolderMgr* manager, - const TQString& path=TQString::null, - KMFolderDirType dirType = KMStandardDir); + KMFolderRootDir( KMFolderMgr* manager, + const TQString& path=TQString::null, + KMFolderDirType dirType = KMStandardDir ); virtual ~KMFolderRootDir(); virtual TQString path() const; diff --git a/kmail/kmfolderimap.cpp b/kmail/kmfolderimap.cpp index f32162f8a..e5c7bd827 100644 --- a/kmail/kmfolderimap.cpp +++ b/kmail/kmfolderimap.cpp @@ -49,6 +49,7 @@ using KMail::ListJob; using KMail::SearchJob; #include "renamejob.h" using KMail::RenameJob; +#include "acljobs.h" #include #include @@ -73,6 +74,7 @@ KMFolderImap::KMFolderImap(KMFolder* folder, const char* aName) mCheckMail = true; mCheckingValidity = false; mUserRights = 0; + mUserRightsState = KMail::ACLJobs::NotFetchedYet; mAlreadyRemoved = false; mHasChildren = ChildrenUnknown; mMailCheckProgressItem = 0; @@ -108,12 +110,6 @@ KMFolderImap::~KMFolderImap() //----------------------------------------------------------------------------- void KMFolderImap::reallyDoClose(const char* owner) { - if (isSelected()) { - kdWarning(5006) << "Trying to close the selected folder " << label() << - " - ignoring!" << endl; - return; - } - // FIXME is this still needed? if (account()) account()->ignoreJobsForFolder( folder() ); @@ -334,8 +330,6 @@ void KMFolderImap::addMsgQuiet(KMMessage* aMsg) int idx = aFolder->find( aMsg ); assert( idx != -1 ); aFolder->take( idx ); - } else { - kdDebug(5006) << k_funcinfo << "no parent" << endl; } if ( !account()->hasCapability("uidplus") ) { // Remember the status with the MD5 as key @@ -999,6 +993,13 @@ void KMFolderImap::initializeFrom( KMFolderImap* parent, TQString folderPath, setNoChildren( mimeType == "message/digest" ); } +//----------------------------------------------------------------------------- +bool KMFolderImap::mailCheckInProgress() const +{ + return getContentState() != imapNoInformation && + getContentState() != imapFinished; +} + //----------------------------------------------------------------------------- void KMFolderImap::setChildrenState( TQString attributes ) { @@ -1188,7 +1189,7 @@ void KMFolderImap::getAndCheckFolder(bool force) return getFolder(force); if ( account() ) - account()->processNewMailSingleFolder( folder() ); + account()->processNewMailInFolder( folder() ); if (force) { // force an update mCheckFlags = true; @@ -2261,10 +2262,10 @@ int KMFolderImap::expungeContents() //----------------------------------------------------------------------------- void -KMFolderImap::setUserRights( unsigned int userRights ) +KMFolderImap::setUserRights( unsigned int userRights, KMail::ACLJobs::ACLFetchState userRightsState ) { mUserRights = userRights; - kdDebug(5006) << imapPath() << " setUserRights: " << userRights << endl; + mUserRightsState = userRightsState; } //----------------------------------------------------------------------------- @@ -2392,7 +2393,7 @@ bool KMFolderImap::isMoveable() const } //----------------------------------------------------------------------------- -const ulong KMFolderImap::serNumForUID( ulong uid ) +ulong KMFolderImap::serNumForUID( ulong uid ) { if ( mUidMetaDataMap.find( uid ) ) { KMMsgMetaData *md = mUidMetaDataMap[uid]; @@ -2431,4 +2432,13 @@ void KMFolderImap::finishMailCheck( const char *dbg, imapState state ) close(dbg); } +bool KMFolderImap::canDeleteMessages() const +{ + if ( isReadOnly() ) + return false; + if ( mUserRightsState == KMail::ACLJobs::Ok && !(mUserRights & KMail::ACLJobs::Delete) ) + return false; + return true; +} + #include "kmfolderimap.moc" diff --git a/kmail/kmfolderimap.h b/kmail/kmfolderimap.h index 7b25520f1..76a3db98d 100644 --- a/kmail/kmfolderimap.h +++ b/kmail/kmfolderimap.h @@ -24,6 +24,7 @@ #ifndef kmfolderimap_h #define kmfolderimap_h +#include "acljobs.h" #include "kmacctimap.h" #include "kmfoldermbox.h" #include "kmmsgbase.h" @@ -65,8 +66,8 @@ public: KMMsgMetaData(KMMsgStatus aStatus, Q_UINT32 aSerNum) :mStatus(aStatus), mSerNum(aSerNum) {} ~KMMsgMetaData() {}; - const KMMsgStatus status() const { return mStatus; } - const Q_UINT32 serNum() const { return mSerNum; } + KMMsgStatus status() const { return mStatus; } + Q_UINT32 serNum() const { return mSerNum; } private: KMMsgStatus mStatus; Q_UINT32 mSerNum; @@ -91,7 +92,7 @@ public: imapFinished = 3 }; - virtual imapState getContentState() { return mContentState; } + virtual imapState getContentState() const { return mContentState; } virtual void setContentState(imapState state) { mContentState = state; } virtual imapState getSubfolderState() { return mSubfolderState; } @@ -250,7 +251,7 @@ public: /** * Get the serial number for the given UID (if available) */ - const ulong serNumForUID( ulong uid ); + ulong serNumForUID( ulong uid ); /** * Save the metadata for the UID @@ -294,15 +295,18 @@ public: /// Is the folder readonly? bool isReadOnly() const { return KMFolderMbox::isReadOnly() || mReadOnly; } + bool canDeleteMessages() const; /** * The user's rights on this folder - see bitfield in ACLJobs namespace. - * @return 0 when not known yet + * Note that the returned value is only valid if userRightsState() returns Ok, so + * that should be checked first. */ unsigned int userRights() const { return mUserRights; } + KMail::ACLJobs::ACLFetchState userRightsState() const { return mUserRightsState; } /** Set the user's rights on this folder - called by getUserRights */ - void setUserRights( unsigned int userRights ); + void setUserRights( unsigned int userRights, KMail::ACLJobs::ACLFetchState userRightsState ); /** * Search for messages @@ -321,6 +325,8 @@ public: /** Returns the IMAP flags that can be stored on the server. */ int permanentFlags() const { return mPermanentFlags; } + virtual bool mailCheckInProgress() const; + signals: void folderComplete(KMFolderImap *folder, bool success); @@ -515,6 +521,7 @@ protected: // the current uidvalidity TQString mUidValidity; unsigned int mUserRights; + KMail::ACLJobs::ACLFetchState mUserRightsState; private: // if we're checking validity currently diff --git a/kmail/kmfolderindex.cpp b/kmail/kmfolderindex.cpp index 6a3781570..95746ed18 100644 --- a/kmail/kmfolderindex.cpp +++ b/kmail/kmfolderindex.cpp @@ -18,6 +18,8 @@ #include "kmfolderindex.h" #include "kmfolder.h" +#include "kmfoldertype.h" +#include "kcursorsaver.h" #include #include #include @@ -31,7 +33,7 @@ #endif // Current version of the table of contents (index) files -#define INDEX_VERSION 1506 +#define INDEX_VERSION 1507 #ifndef MAX_LINE #define MAX_LINE 4096 @@ -110,9 +112,13 @@ int KMFolderIndex::updateIndex() return 0; bool dirty = mDirty; mDirtyTimer->stop(); - for (unsigned int i=0; !dirty && isyncIndexString(); + for ( unsigned int i = 0; !dirty && i < mMsgList.high(); i++ ) { + if ( mMsgList.at(i) ) { + if ( !mMsgList.at(i)->syncIndexString() ) { + dirty = true; + } + } + } if (!dirty) { // Update successful touchFolderIdsFile(); return 0; @@ -209,9 +215,11 @@ int KMFolderIndex::writeIndex( bool createEmptyIndex ) return 0; } - bool KMFolderIndex::readIndex() { + if ( contentsType() != KMail::ContentsTypeMail ) { + kdDebug(5006) << k_funcinfo << "Reading index for " << label() << endl; + } Q_INT32 len; KMMsgInfo* mi; @@ -224,6 +232,7 @@ bool KMFolderIndex::readIndex() setDirty( false ); if (!readIndexHeader(&version)) return false; + //kdDebug(5006) << "Index version for " << label() << " is " << version << endl; mUnreadMsgs = 0; mTotalMsgs = 0; @@ -234,15 +243,20 @@ bool KMFolderIndex::readIndex() { mi = 0; if(version >= 1505) { - if(!fread(&len, sizeof(len), 1, mIndexStream)) + if(!fread(&len, sizeof(len), 1, mIndexStream)) { + // Seems to be normal? + // kdDebug(5006) << k_funcinfo << " Unable to read length field!" << endl; break; + } if (mIndexSwapByteOrder) len = kmail_swap_32(len); off_t offs = ftell(mIndexStream); - if(fseek(mIndexStream, len, SEEK_CUR)) + if(fseek(mIndexStream, len, SEEK_CUR)) { + kdDebug(5006) << k_funcinfo << " Unable to seek to the end of the message!" << endl; break; + } mi = new KMMsgInfo(folder(), offs, len); } else @@ -251,16 +265,18 @@ bool KMFolderIndex::readIndex() fgets(line.data(), MAX_LINE, mIndexStream); if (feof(mIndexStream)) break; if (*line.data() == '\0') { - fclose(mIndexStream); - mIndexStream = 0; - clearIndex(); - return false; + fclose(mIndexStream); + mIndexStream = 0; + clearIndex(); + return false; } mi = new KMMsgInfo(folder()); mi->compat_fromOldIndexString(line, mConvertToUtf8); } - if(!mi) + if(!mi) { + kdDebug(5006) << k_funcinfo << " Unable to create message info object!" << endl; break; + } if (mi->isDeleted()) { @@ -290,7 +306,17 @@ bool KMFolderIndex::readIndex() setDirty( true ); writeIndex(); } + + if ( version < 1507 ) { + updateInvitationAndAddressFieldsFromContents(); + setDirty( true ); + writeIndex(); + } + mTotalMsgs = mMsgList.count(); + if ( contentsType() != KMail::ContentsTypeMail ) { + kdDebug(5006) << k_funcinfo << "Done reading the index for " << label() << ", we have " << mTotalMsgs << " messages." << endl; + } return true; } @@ -316,6 +342,15 @@ bool KMFolderIndex::readIndexHeader(int *gv) return false; // index file has invalid header if(gv) *gv = indexVersion; + + // Check if the index is corrupted ("not compactable") and recreate it if necessary. See + // FolderStorage::getMsg() for the detection code. + if ( !mCompactable ) { + kdWarning(5006) << "Index file " << indexLocation() << " is corrupted!!. Re-creating it." << endl; + recreateIndex( false /* don't call readIndex() afterwards */ ); + return false; + } + if (indexVersion < 1505 ) { if(indexVersion == 1503) { kdDebug(5006) << "Converting old index file " << indexLocation() << " to utf-8" << endl; @@ -323,7 +358,7 @@ bool KMFolderIndex::readIndexHeader(int *gv) } return true; } else if (indexVersion == 1505) { - } else if (indexVersion < INDEX_VERSION) { + } else if (indexVersion < INDEX_VERSION && indexVersion != 1506) { kdDebug(5006) << "Index file " << indexLocation() << " is out of date. Re-creating it." << endl; createIndexFromContents(); return false; @@ -434,6 +469,9 @@ bool KMFolderIndex::updateIndexStreamPtr(bool) KMFolderIndex::IndexStatus KMFolderIndex::indexStatus() { + if ( !mCompactable ) + return IndexCorrupt; + TQFileInfo contInfo(location()); TQFileInfo indInfo(indexLocation()); @@ -484,16 +522,56 @@ KMMsgInfo* KMFolderIndex::setIndexEntry( int idx, KMMessage *msg ) return msgInfo; } -void KMFolderIndex::recreateIndex() +void KMFolderIndex::recreateIndex( bool readIndexAfterwards ) { kapp->setOverrideCursor(KCursor::arrowCursor()); - KMessageBox::error(0, + KMessageBox::information(0, i18n("The mail index for '%1' is corrupted and will be regenerated now, " - "but some information, including status flags, will be lost.").arg(name())); + "but some information, like status flags, might get lost.").arg(name())); kapp->restoreOverrideCursor(); createIndexFromContents(); - readIndex(); + if ( readIndexAfterwards ) { + readIndex(); + } + + // Clear the corrupted flag + mCompactable = true; + writeConfig(); +} + +void KMFolderIndex::silentlyRecreateIndex() +{ + Q_ASSERT( !isOpened() ); + open( "silentlyRecreateIndex" ); + KCursorSaver busy( KBusyPtr::busy() ); + createIndexFromContents(); + mCompactable = true; + writeConfig(); + close( "silentlyRecreateIndex" ); } +void KMFolderIndex::updateInvitationAndAddressFieldsFromContents() +{ + kdDebug(5006) << "Updating index for " << label() << ", this might take a while." << endl; + for ( uint i = 0; i < mMsgList.size(); i++ ) { + KMMsgInfo * const msgInfo = dynamic_cast( mMsgList[i] ); + if ( msgInfo ) { + DwString msgString( getDwString( i ) ); + if ( msgString.size() > 0 ) { + KMMessage msg; + msg.fromDwString( msgString, false ); + msg.updateInvitationState(); + if ( msg.status() & KMMsgStatusHasInvitation ) { + msgInfo->setStatus( msgInfo->status() | KMMsgStatusHasInvitation ); + } + if ( msg.status() & KMMsgStatusHasNoInvitation ) { + msgInfo->setStatus( msgInfo->status() | KMMsgStatusHasNoInvitation ); + } + msgInfo->setFrom( msg.from() ); + msgInfo->setTo( msg.to() ); + } + } + } +} #include "kmfolderindex.moc" diff --git a/kmail/kmfolderindex.h b/kmail/kmfolderindex.h index 05453bb68..29039765d 100644 --- a/kmail/kmfolderindex.h +++ b/kmail/kmfolderindex.h @@ -48,6 +48,7 @@ public: */ enum IndexStatus { IndexOk, IndexMissing, + IndexCorrupt, IndexTooOld }; @@ -80,7 +81,16 @@ public: virtual TQString indexLocation() const; virtual int writeIndex( bool createEmptyIndex = false ); - void recreateIndex(); + void recreateIndex( bool readIndexAfterwards = true ); + void silentlyRecreateIndex(); + + /** Tests whether the contents of this folder is newer than the index. + Should return IndexTooOld if the index is older than the contents. + Should return IndexMissing if there is contents but no index. + Should return IndexOk if the folder doesn't exist anymore "physically" + or if the index is not older than the contents. + */ + virtual IndexStatus indexStatus() = 0; public slots: /** Incrementally update the index if possible else call writeIndex */ @@ -99,14 +109,6 @@ protected: bool updateIndexStreamPtr(bool just_close=FALSE); - /** Tests whether the contents of this folder is newer than the index. - Should return IndexTooOld if the index is older than the contents. - Should return IndexMissing if there is contents but no index. - Should return IndexOk if the folder doesn't exist anymore "physically" - or if the index is not older than the contents. - */ - virtual IndexStatus indexStatus() = 0; - /** Inserts messages into the message dictionary by iterating over the * message list. The messages will get new serial numbers. This is only * used on newly appeared folders, where there is no .ids file yet, or @@ -125,6 +127,10 @@ protected: int mIndexStreamPtrLength, mIndexId; bool mIndexSwapByteOrder; // Index file was written with swapped byte order int mIndexSizeOfLong; // Index file was written with longs of this size + +private: + void updateInvitationAndAddressFieldsFromContents(); + }; #endif /*kmfolderindex_h*/ diff --git a/kmail/kmfoldermaildir.cpp b/kmail/kmfoldermaildir.cpp index bfb606f7e..09800a94d 100644 --- a/kmail/kmfoldermaildir.cpp +++ b/kmail/kmfoldermaildir.cpp @@ -224,6 +224,7 @@ int KMFolderMaildir::create() //----------------------------------------------------------------------------- void KMFolderMaildir::reallyDoClose(const char* owner) { + Q_UNUSED( owner ); if (mAutoCreateIndex) { updateIndex(); @@ -465,9 +466,12 @@ if( fileD0.open( IO_WriteOnly ) ) { ++mTotalMsgs; mSize = -1; - if ( aMsg->attachmentState() == KMMsgAttachmentUnknown && - aMsg->readyToShow() ) + if ( aMsg->attachmentState() == KMMsgAttachmentUnknown && aMsg->readyToShow() ) { aMsg->updateAttachmentState(); + } + if ( aMsg->invitationState() == KMMsgInvitationUnknown && aMsg->readyToShow() ) { + aMsg->updateInvitationState(); + } // store information about the position in the folder file in the message aMsg->setParent(folder()); @@ -743,7 +747,7 @@ void KMFolderMaildir::readFileHeaderIntern(const TQString& dir, const TQString& } // Is this a long header line? - if (inHeader && line[0] == '\t' || line[0] == ' ') + if (inHeader && ( line[0] == '\t' || line[0] == ' ' ) ) { int i = 0; while (line[i] == '\t' || line[i] == ' ') @@ -901,6 +905,9 @@ int KMFolderMaildir::createIndexFromContents() KMFolderIndex::IndexStatus KMFolderMaildir::indexStatus() { + if ( !mCompactable ) + return KMFolderIndex::IndexCorrupt; + TQFileInfo new_info(location() + "/new"); TQFileInfo cur_info(location() + "/cur"); TQFileInfo index_info(indexLocation()); diff --git a/kmail/kmfoldermbox.cpp b/kmail/kmfoldermbox.cpp index dc35328db..c2e60a091 100644 --- a/kmail/kmfoldermbox.cpp +++ b/kmail/kmfoldermbox.cpp @@ -94,6 +94,7 @@ KMFolderMbox::~KMFolderMbox() //----------------------------------------------------------------------------- int KMFolderMbox::open(const char *owner) { + Q_UNUSED( owner ); int rc = 0; mOpenCount++; @@ -258,6 +259,7 @@ int KMFolderMbox::create() //----------------------------------------------------------------------------- void KMFolderMbox::reallyDoClose(const char* owner) { + Q_UNUSED( owner ); if (mAutoCreateIndex) { if (KMFolderIndex::IndexOk != indexStatus()) { @@ -521,6 +523,9 @@ int KMFolderMbox::unlock() //----------------------------------------------------------------------------- KMFolderIndex::IndexStatus KMFolderMbox::indexStatus() { + if ( !mCompactable ) + return KMFolderIndex::IndexCorrupt; + TQFileInfo contInfo(location()); TQFileInfo indInfo(indexLocation()); @@ -1065,9 +1070,12 @@ if( fileD1.open( IO_WriteOnly ) ) { ++mTotalMsgs; mSize = -1; - if ( aMsg->attachmentState() == KMMsgAttachmentUnknown && - aMsg->readyToShow() ) + if ( aMsg->attachmentState() == KMMsgAttachmentUnknown && aMsg->readyToShow() ) { aMsg->updateAttachmentState(); + } + if ( aMsg->invitationState() == KMMsgInvitationUnknown && aMsg->readyToShow() ) { + aMsg->updateInvitationState(); + } // store information about the position in the folder file in the message aMsg->setParent(folder()); @@ -1095,13 +1103,13 @@ if( fileD1.open( IO_WriteOnly ) ) { revert = ftell(mIndexStream); KMMsgBase * mb = &aMsg->toMsgBase(); - int len; - const uchar *buffer = mb->asIndexString(len); - fwrite(&len,sizeof(len), 1, mIndexStream); - mb->setIndexOffset( ftell(mIndexStream) ); - mb->setIndexLength( len ); - if(fwrite(buffer, len, 1, mIndexStream) != 1) - kdDebug(5006) << "Whoa! " << __FILE__ << ":" << __LINE__ << endl; + int len; + const uchar *buffer = mb->asIndexString(len); + fwrite(&len,sizeof(len), 1, mIndexStream); + mb->setIndexOffset( ftell(mIndexStream) ); + mb->setIndexLength( len ); + if(fwrite(buffer, len, 1, mIndexStream) != 1) + kdDebug(5006) << "Whoa! " << __FILE__ << ":" << __LINE__ << endl; fflush(mIndexStream); error = ferror(mIndexStream); diff --git a/kmail/kmfoldersearch.cpp b/kmail/kmfoldersearch.cpp index 85ba2040f..522785d11 100644 --- a/kmail/kmfoldersearch.cpp +++ b/kmail/kmfoldersearch.cpp @@ -547,6 +547,7 @@ void KMFolderSearch::sync() void KMFolderSearch::reallyDoClose(const char* owner) { + Q_UNUSED( owner ); if (mAutoCreateIndex) { if (mSearch) mSearch->write(location()); diff --git a/kmail/kmfolderseldlg.cpp b/kmail/kmfolderseldlg.cpp index 607b42435..00849b6e7 100644 --- a/kmail/kmfolderseldlg.cpp +++ b/kmail/kmfolderseldlg.cpp @@ -2,7 +2,6 @@ #include #include "kmfolderseldlg.h" -#include "kmfoldertree.h" #include "kmfolder.h" #include "kmmainwidget.h" #include "globalsettings.h" @@ -14,429 +13,10 @@ #include #include - -namespace KMail { - -class FolderItem : public KFolderTreeItem -{ - public: - FolderItem( KFolderTree * listView ); - FolderItem( KFolderTreeItem * listViewItem ); - - void setFolder( KMFolder * folder ) { mFolder = folder; }; - const KMFolder * folder() { return mFolder; }; - - // Redefine isAlternate() for proper row coloring behavior. - // KListViewItem::isAlternate() is not virtual! Therefore, - // it is necessary to overload paintCell() below. If it were - // made virtual, paintCell() would no longer be necessary. - bool isAlternate () { - return mAlternate; - } - - // Set the flag which determines if this is an alternate row - void setAlternate ( bool alternate ) { - mAlternate = alternate; - } - - // Must overload paintCell because neither KListViewItem::isAlternate() - // or KListViewItem::backgroundColor() are virtual! - virtual void paintCell( TQPainter *p, const TQColorGroup &cg, - int column, int width, int alignment ) - { - KListView* view = static_cast< KListView* >( listView() ); - - // Set alternate background to invalid - TQColor nocolor; - TQColor alt = view->alternateBackground(); - view->setAlternateBackground( nocolor ); - - // Set the base and text to the appropriate colors - TQColorGroup *cgroup = (TQColorGroup *)&view->viewport()->colorGroup(); - TQColor base = cgroup->base(); - TQColor text = cgroup->text(); - cgroup->setColor( TQColorGroup::Base, isAlternate() ? alt : base ); - cgroup->setColor( TQColorGroup::Text, isEnabled() ? text : Qt::lightGray ); - - // Call the parent paint routine - KListViewItem::paintCell( p, cg, column, width, alignment ); - - // Restore the base and alternate background - cgroup->setColor( TQColorGroup::Base, base ); - cgroup->setColor( TQColorGroup::Text, text ); - view->setAlternateBackground( alt ); - } - - private: - KMFolder * mFolder; - bool mAlternate; -}; - -//----------------------------------------------------------------------------- -FolderItem::FolderItem( KFolderTree * listView ) - : KFolderTreeItem( listView ), - mFolder( 0 ) -{} - -//----------------------------------------------------------------------------- -FolderItem::FolderItem( KFolderTreeItem * listViewItem ) - : KFolderTreeItem( listViewItem ), - mFolder( 0 ) -{} - -//----------------------------------------------------------------------------- -SimpleFolderTree::SimpleFolderTree( TQWidget * parent, - KMFolderTree * folderTree, - const TQString & preSelection, - bool mustBeReadWrite ) - : KFolderTree( parent ), mFolderTree( folderTree ) -{ - setSelectionModeExt( Single ); - mFolderColumn = addColumn( i18n( "Folder" ), 0 ); - mPathColumn = addColumn( i18n( "Path" ), 0 ); - setAllColumnsShowFocus( true ); - setAlternateBackground( TQColor( 0xf0, 0xf0, 0xf0 ) ); - - reload( mustBeReadWrite, true, true, preSelection ); - readColorConfig(); - - applyFilter( "" ); - - connect(this, TQT_SIGNAL(collapsed(TQListViewItem*)), TQT_SLOT(recolorRows())); - connect(this, TQT_SIGNAL(expanded(TQListViewItem*)), TQT_SLOT(recolorRows())); - - connect( this, TQT_SIGNAL( contextMenuRequested( TQListViewItem*, const TQPoint &, int ) ), - this, TQT_SLOT( slotContextMenuRequested( TQListViewItem*, const TQPoint & ) ) ); -} - -//----------------------------------------------------------------------------- -void SimpleFolderTree::reload( bool mustBeReadWrite, bool showOutbox, - bool showImapFolders, const TQString& preSelection ) -{ - mLastMustBeReadWrite = mustBeReadWrite; - mLastShowOutbox = showOutbox; - mLastShowImapFolders = showImapFolders; - - clear(); - FolderItem * lastItem = 0; - FolderItem * lastTopItem = 0; - FolderItem * selectedItem = 0; - int lastDepth = 0; - - TQString selected = preSelection; - if ( selected.isEmpty() && folder() ) - selected = folder()->idString(); - - mFilter = ""; - TQString path; - - for ( TQListViewItemIterator it( mFolderTree ) ; it.current() ; ++it ) - { - KMFolderTreeItem * fti = static_cast( it.current() ); - - // search folders are never shown - if ( !fti || fti->protocol() == KFolderTreeItem::Search ) - continue; - - // imap folders? - if ( fti->protocol() == KFolderTreeItem::Imap && !showImapFolders ) - continue; - - // the outbox? - if ( fti->type() == KFolderTreeItem::Outbox && !showOutbox ) - continue; - - int depth = fti->depth();// - 1; - FolderItem * item = 0; - if ( depth <= 0 ) { - // top level - first top level item or after last existing top level item - item = new FolderItem( this ); - if ( lastTopItem ) - item->moveItem( lastTopItem ); - lastTopItem = item; - depth = 0; - path = ""; - } - else { - if ( depth > lastDepth ) { - // next lower level - parent node will get opened - item = new FolderItem( lastItem ); - lastItem->setOpen(true); - } - else { - path = path.section( '/', 0, -2 - (lastDepth-depth) ); - - if ( depth == lastDepth ) { - // same level - behind previous item - item = new FolderItem( static_cast(lastItem->parent()) ); - item->moveItem( lastItem ); - } else if ( depth < lastDepth ) { - // above previous level - might be more than one level difference - // but highest possibility is top level - while ( ( depth <= --lastDepth ) && lastItem->parent() ) { - lastItem = static_cast( lastItem->parent() ); - } - if ( lastItem->parent() ) { - item = new FolderItem( static_cast(lastItem->parent()) ); - item->moveItem( lastItem ); - } else { - // chain somehow broken - what does cause this ??? - kdDebug( 5006 ) << "You shouldn't get here: depth=" << depth - << "folder name=" << fti->text( 0 ) << endl; - item = new FolderItem( this ); - lastTopItem = item; - } - } - } - } - - if ( depth > 0 ) - path += "/"; - path += fti->text( 0 ); - - item->setText( mFolderColumn, fti->text( 0 ) ); - item->setText( mPathColumn, path ); - - item->setProtocol( fti->protocol() ); - item->setType( fti->type() ); - - // Make items without folders and readonly items unselectable - // if we're told so - if ( mustBeReadWrite && ( !fti->folder() || fti->folder()->isReadOnly() ) ) { - item->setSelectable( false ); - } else { - if ( fti->folder() ) { - item->setFolder( fti->folder() ); - if ( selected == item->folder()->idString() ) - selectedItem = item; - } - } - lastItem = item; - lastDepth = depth; - } - - if ( selectedItem ) { - setSelected( selectedItem, true ); - ensureItemVisible( selectedItem ); - } -} - -//----------------------------------------------------------------------------- -const KMFolder * SimpleFolderTree::folder() const -{ - TQListViewItem * item = currentItem(); - if ( item ) { - const KMFolder * folder = static_cast( item )->folder(); - if( folder ) return folder; - } - return 0; -} - -//----------------------------------------------------------------------------- -void SimpleFolderTree::setFolder( KMFolder *folder ) -{ - for ( TQListViewItemIterator it( this ) ; it.current() ; ++it ) - { - const KMFolder *fld = static_cast( it.current() )->folder(); - if ( fld == folder ) - { - setSelected( it.current(), true ); - ensureItemVisible( it.current() ); - } - } -} - -//----------------------------------------------------------------------------- -void SimpleFolderTree::setFolder( const TQString& idString ) -{ - setFolder( kmkernel->findFolderById( idString ) ); -} - -//----------------------------------------------------------------------------- -void SimpleFolderTree::addChildFolder() -{ - const KMFolder *fld = folder(); - if ( fld ) { - mFolderTree->addChildFolder( (KMFolder *) fld, parentWidget() ); - reload( mLastMustBeReadWrite, mLastShowOutbox, mLastShowImapFolders ); - setFolder( (KMFolder *) fld ); - } -} - -//----------------------------------------------------------------------------- -void SimpleFolderTree::slotContextMenuRequested( TQListViewItem *lvi, - const TQPoint &p ) -{ - if (!lvi) - return; - setCurrentItem( lvi ); - setSelected( lvi, TRUE ); - - const KMFolder * folder = static_cast( lvi )->folder(); - if ( !folder || folder->noContent() || folder->noChildren() ) - return; - - KPopupMenu *folderMenu = new KPopupMenu; - folderMenu->insertTitle( folder->label() ); - folderMenu->insertSeparator(); - folderMenu->insertItem(SmallIconSet("folder_new"), - i18n("&New Subfolder..."), this, - TQT_SLOT(addChildFolder())); - kmkernel->setContextMenuShown( true ); - folderMenu->exec (p, 0); - kmkernel->setContextMenuShown( false ); - delete folderMenu; - folderMenu = 0; -} - -//----------------------------------------------------------------------------- -void SimpleFolderTree::readColorConfig (void) -{ - TQColor c1=TQColor(kapp->palette().active().text()); - TQColor c2=TQColor(kapp->palette().active().base()); - - mPaintInfo.colFore = c1; - mPaintInfo.colBack = c2; - - TQPalette newPal = kapp->palette(); - newPal.setColor( TQColorGroup::Base, mPaintInfo.colBack ); - newPal.setColor( TQColorGroup::Text, mPaintInfo.colFore ); - setPalette( newPal ); -} - - -//----------------------------------------------------------------------------- -static int recurseFilter( TQListViewItem * item, const TQString& filter, int column ) -{ - if ( item == 0 ) - return 0; - - TQListViewItem * child; - child = item->firstChild(); - - int enabled = 0; - while ( child ) { - enabled += recurseFilter( child, filter, column ); - child = child->nextSibling(); - } - - if ( filter.length() == 0 || - item->text( column ).find( filter, 0, false ) >= 0 ) { - item->setVisible( true ); - ++enabled; - } - else { - item->setVisible( !!enabled ); - item->setEnabled( false ); - } - - return enabled; -} - -void SimpleFolderTree::recolorRows() -{ - // Iterate through the list to set the alternate row flags. - int alt = 0; - TQListViewItemIterator it ( this ); - while ( it.current() ) { - FolderItem * item = static_cast< FolderItem* >( it.current() ); - - if ( item->isVisible() ) { - bool visible = true; - TQListViewItem * parent = item->parent(); - while ( parent ) { - if (!parent->isOpen()) { - visible = false; - break; - } - parent = parent->parent(); - } - - if ( visible ) { - item->setAlternate( alt ); - alt = !alt; - } - } - - ++it; - } -} - -void SimpleFolderTree::applyFilter( const TQString& filter ) -{ - // Reset all items to visible, enabled, and open - TQListViewItemIterator clean( this ); - while ( clean.current() ) { - TQListViewItem * item = clean.current(); - item->setEnabled( true ); - item->setVisible( true ); - item->setOpen( true ); - ++clean; - } - - mFilter = filter; - - if ( filter.isEmpty() ) { - setColumnText( mPathColumn, i18n("Path") ); - return; - } - - // Set the visibility and enabled status of each list item. - // The recursive algorithm is necessary because visiblity - // changes are automatically applied to child nodes by Qt. - TQListViewItemIterator it( this ); - while ( it.current() ) { - TQListViewItem * item = it.current(); - if ( item->depth() <= 0 ) - recurseFilter( item, filter, mPathColumn ); - ++it; - } - - // Recolor the rows appropriately - recolorRows(); - - // Iterate through the list to find the first selectable item - TQListViewItemIterator first ( this ); - while ( first.current() ) { - FolderItem * item = static_cast< FolderItem* >( first.current() ); - - if ( item->isVisible() && item->isSelectable() ) { - setSelected( item, true ); - ensureItemVisible( item ); - break; - } - - ++first; - } - - // Display and save the current filter - if ( filter.length() > 0 ) - setColumnText( mPathColumn, i18n("Path") + " ( " + filter + " )" ); - else - setColumnText( mPathColumn, i18n("Path") ); - - mFilter = filter; -} - -//----------------------------------------------------------------------------- -void SimpleFolderTree::keyPressEvent( TQKeyEvent *e ) { - int ch = e->ascii(); - - if ( ch >= 32 && ch <= 126 ) - applyFilter( mFilter + ch ); - - else if ( ch == 8 || ch == 127 ) { - if ( mFilter.length() > 0 ) { - mFilter.truncate( mFilter.length()-1 ); - applyFilter( mFilter ); - } - } - - else - KListView::keyPressEvent( e ); -} +#include +using namespace KMail; //----------------------------------------------------------------------------- KMFolderSelDlg::KMFolderSelDlg( KMMainWidget * parent, const TQString& caption, bool mustBeReadWrite, bool useGlobalSettings ) @@ -452,7 +32,9 @@ KMFolderSelDlg::KMFolderSelDlg( KMMainWidget * parent, const TQString& caption, TQString preSelection = mUseGlobalSettings ? GlobalSettings::self()->lastSelectedFolder() : TQString::null; - mTreeView = new KMail::SimpleFolderTree( makeVBoxMainWidget(), ft, + TQWidget * container = makeVBoxMainWidget(); + new TQLabel( i18n("You can start typing to filter the list of folders"), container ); + mTreeView = new KMail::SimpleFolderTree( container, ft, preSelection, mustBeReadWrite ); init(); } @@ -469,7 +51,9 @@ KMFolderSelDlg::KMFolderSelDlg( TQWidget * parent, KMFolderTree * tree, { TQString preSelection = mUseGlobalSettings ? GlobalSettings::self()->lastSelectedFolder() : TQString::null; - mTreeView = new KMail::SimpleFolderTree( makeVBoxMainWidget(), tree, + TQWidget * container = makeVBoxMainWidget(); + new TQLabel( i18n("You can start typing to filter the list of folders"), container ); + mTreeView = new KMail::SimpleFolderTree( container, tree, preSelection, mustBeReadWrite ); init(); } @@ -547,13 +131,13 @@ void KMFolderSelDlg::readConfig() TQValueList widths = config->readIntListEntry( "ColumnWidths" ); if ( !widths.isEmpty() ) { - mTreeView->setColumnWidth(mTreeView->mFolderColumn, widths[0]); - mTreeView->setColumnWidth(mTreeView->mPathColumn, widths[1]); + mTreeView->setColumnWidth(mTreeView->folderColumn(), widths[0]); + mTreeView->setColumnWidth(mTreeView->pathColumn(), widths[1]); } else { int colWidth = width() / 2; - mTreeView->setColumnWidth(mTreeView->mFolderColumn, colWidth); - mTreeView->setColumnWidth(mTreeView->mPathColumn, colWidth); + mTreeView->setColumnWidth(mTreeView->folderColumn(), colWidth); + mTreeView->setColumnWidth(mTreeView->pathColumn(), colWidth); } } @@ -564,11 +148,10 @@ void KMFolderSelDlg::writeConfig() config->writeEntry( "Size", size() ); TQValueList widths; - widths.push_back(mTreeView->columnWidth(mTreeView->mFolderColumn)); - widths.push_back(mTreeView->columnWidth(mTreeView->mPathColumn)); + widths.push_back(mTreeView->columnWidth(mTreeView->folderColumn())); + widths.push_back(mTreeView->columnWidth(mTreeView->pathColumn())); config->writeEntry( "ColumnWidths", widths ); } -} // namespace KMail #include "kmfolderseldlg.moc" diff --git a/kmail/kmfolderseldlg.h b/kmail/kmfolderseldlg.h index 119ceff78..f1637a2f8 100644 --- a/kmail/kmfolderseldlg.h +++ b/kmail/kmfolderseldlg.h @@ -7,61 +7,16 @@ #define kmfolderseldlg_h #include -#include +#include +#include +#include class KMFolder; class KMFolderTree; class KMMainWidget; +class SimpleFolderTree; namespace KMail { - - class SimpleFolderTree : public KFolderTree - { - Q_OBJECT - - public: - SimpleFolderTree( TQWidget * parent, KMFolderTree * folderTree, - const TQString & preSelection, bool mustBeReadWrite ); - - /** Reload the tree and select what folders to show and what not */ - void reload( bool mustBeReadWrite, bool showOutbox, bool showImapFolders, - const TQString& preSelection = TQString::null ); - - /** Return the current folder */ - const KMFolder * folder() const; - - /** Set the current folder */ - void setFolder( KMFolder* ); - void setFolder( const TQString& idString ); - - /** Apply the given filter. */ - void applyFilter( const TQString& filter ); - - public slots: - void addChildFolder(); - - protected slots: - void slotContextMenuRequested( TQListViewItem *, const TQPoint & ); - virtual void recolorRows(); - - protected: - /** Read color options and set palette. */ - virtual void readColorConfig(void); - virtual void keyPressEvent( TQKeyEvent *e ); - - /** Folder and path column IDs. */ - friend class KMFolderSelDlg; - int mFolderColumn; - int mPathColumn; - - private: - KMFolderTree* mFolderTree; - TQString mFilter; - bool mLastMustBeReadWrite; - bool mLastShowOutbox; - bool mLastShowImapFolders; -}; - //----------------------------------------------------------------------------- class KMFolderSelDlg: public KDialogBase { diff --git a/kmail/kmfoldertree.cpp b/kmail/kmfoldertree.cpp index e99e7058b..d2e098b8d 100644 --- a/kmail/kmfoldertree.cpp +++ b/kmail/kmfoldertree.cpp @@ -126,7 +126,13 @@ TQPixmap KMFolderTreeItem::normalIcon(int size) const case Trash: icon = "trashcan_empty"; break; case Drafts: icon = "edit"; break; case Templates: icon = "filenew"; break; - default: icon = kmkernel->iCalIface().folderPixmap( type() ); break; + default: + { + //If not a resource folder don't try to use icalIface folder pixmap + if(kmkernel->iCalIface().isResourceFolder( mFolder )) + icon = kmkernel->iCalIface().folderPixmap( type() ); + break; + } } // non-root search folders if ( protocol() == KMFolderTreeItem::Search ) { @@ -177,7 +183,8 @@ TQPixmap KMFolderTreeItem::unreadIcon(int size) const pm = il->loadIcon( "folder_grey_open", KIcon::Small, size, KIcon::DefaultState, 0, true ); } else { - pm = il->loadIcon( kmkernel->iCalIface().folderPixmap( type() ), + if( kmkernel->iCalIface().isResourceFolder( mFolder ) ) + pm = il->loadIcon( kmkernel->iCalIface().folderPixmap( type() ), KIcon::Small, size, KIcon::DefaultState, 0, true ); if ( pm.isNull() ) pm = il->loadIcon( "folder_open", KIcon::Small, size, @@ -245,8 +252,15 @@ void KMFolderTreeItem::slotIconsChanged() { kdDebug(5006) << k_funcinfo << endl; // this is prone to change, so better check + KFolderTreeItem::Type newType = type(); if( kmkernel->iCalIface().isResourceFolder( mFolder ) ) - setType( kmkernel->iCalIface().folderType(mFolder) ); + newType = kmkernel->iCalIface().folderType(mFolder); + + // reload the folder tree if the type changed, needed because of the + // various type-dependent folder hiding options + if ( type() != newType ) + static_cast( listView() )->delayedReload(); + setType( newType ); if ( unreadCount() > 0 ) setPixmap( 0, unreadIcon( iconSize() ) ); @@ -263,6 +277,12 @@ void KMFolderTreeItem::slotNameChanged() repaint(); } +void KMFolderTreeItem::slotNoContentChanged() +{ + // reload the folder tree if the no content state changed, needed because + // we hide no-content folders if their child nodes are hidden + TQTimer::singleShot( 0, static_cast( listView() ), TQT_SLOT(reload()) ); +} //----------------------------------------------------------------------------- bool KMFolderTreeItem::acceptDrag(TQDropEvent* e) const @@ -333,7 +353,7 @@ void KMFolderTreeItem::assignShortcut() kmkernel->getKMMainWidget(), listView() ); shorty->exec(); - return; + delete shorty; } //----------------------------------------------------------------------------- @@ -362,6 +382,7 @@ KMFolderTree::KMFolderTree( KMMainWidget *mainWidget, TQWidget *parent, oldSelected = 0; oldCurrent = 0; mLastItem = 0; + dropItem = 0; mMainWidget = mainWidget; mReloading = false; mCutFolder = false; @@ -375,7 +396,7 @@ KMFolderTree::KMFolderTree( KMMainWidget *mainWidget, TQWidget *parent, int namecol = addColumn( i18n("Folder"), 250 ); header()->setStretchEnabled( true, namecol ); - + setResizeMode( TQListView::NoColumn ); // connect connectSignals(); @@ -595,6 +616,16 @@ void KMFolderTree::reload(bool openFolders) connect(fti->folder(),TQT_SIGNAL(nameChanged()), fti,TQT_SLOT(slotNameChanged())); + disconnect( fti->folder(), TQT_SIGNAL(noContentChanged()), + fti, TQT_SLOT(slotNoContentChanged()) ); + connect( fti->folder(), TQT_SIGNAL(noContentChanged()), + fti, TQT_SLOT(slotNoContentChanged()) ); + + disconnect( fti->folder(), TQT_SIGNAL(syncStateChanged()), + this, TQT_SLOT(slotSyncStateChanged()) ); + connect( fti->folder(), TQT_SIGNAL(syncStateChanged()), + this, TQT_SLOT(slotSyncStateChanged()) ); + // we want to be noticed of changes to update the unread/total columns disconnect(fti->folder(), TQT_SIGNAL(msgAdded(KMFolder*,Q_UINT32)), this,TQT_SLOT(slotUpdateCountsDelayed(KMFolder*))); @@ -733,6 +764,8 @@ void KMFolderTree::addDirectory( KMFolderDir *fdir, KMFolderTreeItem* parent ) // It is removeFromFolderToItemMap( folder ); delete fti; + // still, it might change in the future, so we better check the change signals + connect ( folder, TQT_SIGNAL(noContentChanged()), TQT_SLOT(delayedReload()) ); continue; } @@ -986,7 +1019,6 @@ void KMFolderTree::doFolderSelected( TQListViewItem* qlvi, bool keepSelection ) KMFolder* folder = 0; if (fti) folder = fti->folder(); - if (mLastItem && mLastItem != fti && mLastItem->folder() && (mLastItem->folder()->folderType() == KMFolderTypeImap)) { @@ -1059,7 +1091,7 @@ void KMFolderTree::slotContextMenuRequested( TQListViewItem *lvi, TQString createChild = i18n("&New Subfolder..."); if (!fti->folder()) createChild = i18n("&New Folder..."); - if (fti->folder() || (fti->text(0) != i18n("Searches")) && !multiFolder) + if ( ( fti->folder() || (fti->text(0) != i18n("Searches")) ) && !multiFolder) folderMenu->insertItem(SmallIconSet("folder_new"), createChild, this, TQT_SLOT(addChildFolder())); @@ -1086,7 +1118,7 @@ void KMFolderTree::slotContextMenuRequested( TQListViewItem *lvi, folderToPopupMenu( CopyFolder, this, &mMenuToFolder, copyMenu ); folderMenu->insertItem( i18n("&Copy Folder To"), copyMenu ); - if ( fti->folder()->isMoveable() ) + if ( fti->folder()->isMoveable() && fti->folder()->canDeleteMessages() ) { TQPopupMenu *moveMenu = new TQPopupMenu( folderMenu ); folderToPopupMenu( MoveFolder, this, &mMenuToFolder, moveMenu ); @@ -1101,6 +1133,8 @@ void KMFolderTree::slotContextMenuRequested( TQListViewItem *lvi, if ( !multiFolder ) mMainWidget->action("search_messages")->plug(folderMenu); + mMainWidget->action( "archive_folder" )->plug( folderMenu ); + mMainWidget->action("compact")->plug(folderMenu); if ( GlobalSettings::self()->enableFavoriteFolderView() ) { @@ -1123,7 +1157,7 @@ void KMFolderTree::slotContextMenuRequested( TQListViewItem *lvi, fti->folder()->folderType() == KMFolderTypeCachedImap )) { folderMenu->insertItem(SmallIconSet("bookmark_folder"), - i18n("Subscription..."), mMainWidget, + i18n("Serverside Subscription..."), mMainWidget, TQT_SLOT(slotSubscriptionDialog())); folderMenu->insertItem(SmallIcon("bookmark_folder"), i18n("Local Subscription..."), mMainWidget, @@ -1157,7 +1191,7 @@ void KMFolderTree::slotContextMenuRequested( TQListViewItem *lvi, fti, TQT_SLOT(assignShortcut())); - if ( !fti->folder()->noContent() ) { + if ( !fti->folder()->noContent() && fti->folder()->canDeleteMessages() ) { folderMenu->insertItem( i18n("Expire..."), fti, TQT_SLOT( slotShowExpiryProperties() ) ); } @@ -1215,12 +1249,14 @@ static bool folderHasCreateRights( const KMFolder *folder ) bool createRights = true; // we don't have acls for local folders yet if ( folder && folder->folderType() == KMFolderTypeImap ) { const KMFolderImap *imapFolder = static_cast( folder->storage() ); - createRights = imapFolder->userRights() == 0 || // hack, we should get the acls - ( imapFolder->userRights() > 0 && ( imapFolder->userRights() & KMail::ACLJobs::Create ) ); + createRights = imapFolder->userRightsState() != KMail::ACLJobs::Ok || // hack, we should get the acls + ( imapFolder->userRightsState() == KMail::ACLJobs::Ok && + ( imapFolder->userRights() & KMail::ACLJobs::Create ) ); } else if ( folder && folder->folderType() == KMFolderTypeCachedImap ) { const KMFolderCachedImap *dimapFolder = static_cast( folder->storage() ); - createRights = dimapFolder->userRights() == 0 || - ( dimapFolder->userRights() > 0 && ( dimapFolder->userRights() & KMail::ACLJobs::Create ) ); + createRights = dimapFolder->userRightsState() != KMail::ACLJobs::Ok || + ( dimapFolder->userRightsState() == KMail::ACLJobs::Ok && + ( dimapFolder->userRights() & KMail::ACLJobs::Create ) ); } return createRights; } @@ -1241,8 +1277,7 @@ void KMFolderTree::addChildFolder( KMFolder *folder, TQWidget * parent ) if (!aFolder->createChildFolder()) return; if ( !folderHasCreateRights( aFolder ) ) { - // FIXME: change this message to "Cannot create folder under ..." or similar - const TQString message = i18n( "Cannot create folder %1 because of insufficient " + const TQString message = i18n( "Cannot create folder under %1 because of insufficient " "permissions on the server. If you think you should be able to create " "subfolders here, ask your administrator to grant you rights to do so." " " ).arg(aFolder->label()); @@ -1953,6 +1988,7 @@ void KMFolderTree::moveOrCopyFolder( TQValueList > source if ( parent->hasNamedFolder( sourceFolderName ) || sourceFolderNames.contains( sourceFolderName ) ) { KMessageBox::error( this, i18n("Cannot move or copy folder %1 here because a folder with the same name already exists.") .arg( sourceFolderName ) ); + setDragEnabled( true ); return; } sourceFolderNames.append( sourceFolderName ); @@ -1963,6 +1999,7 @@ void KMFolderTree::moveOrCopyFolder( TQValueList > source if ( f->moveInProgress() ) { KMessageBox::error( this, i18n("Cannot move or copy folder %1 because it is not completely copied itself.") .arg( sourceFolderName ) ); + setDragEnabled( true ); return; } if ( f->parent() ) @@ -1982,6 +2019,7 @@ void KMFolderTree::moveOrCopyFolder( TQValueList > source if ( folderDir->findRef( source ) != -1 ) { KMessageBox::error( this, message ); + setDragEnabled( true ); return; } folderDir = folderDir->parent(); @@ -1991,12 +2029,14 @@ void KMFolderTree::moveOrCopyFolder( TQValueList > source if( source && source->child() && parent && ( parent->path().find( source->child()->path() + "/" ) == 0 ) ) { KMessageBox::error( this, message ); + setDragEnabled( true ); return; } if( source && source->child() && ( parent == source->child() ) ) { KMessageBox::error( this, message ); + setDragEnabled( true ); return; } } @@ -2013,6 +2053,7 @@ void KMFolderTree::moveOrCopyFolder( TQValueList > source do { if ( parentDir == childDir || parentDir->findRef( childDir->owner() ) != -1 ) { KMessageBox::error( this, i18n("Moving the selected folders is not possible") ); + setDragEnabled( true ); return; } childDir = childDir->parent(); @@ -2106,6 +2147,23 @@ void KMFolderTree::updateCopyActions() paste->setEnabled( true ); } +void KMFolderTree::slotSyncStateChanged() +{ + // Only emit the signal when a selected folder changes, otherwise the folder menu is updated + // too often + TQValueList< TQGuardedPtr > folders = selectedFolders(); + TQValueList< TQGuardedPtr >::const_iterator it = folders.constBegin(); + TQValueList< TQGuardedPtr >::const_iterator end = folders.constEnd(); + while ( it != end ) { + TQGuardedPtr folder = *it; + if ( folder == sender() ) { + emit syncStateChanged(); + break; + } + ++it; + } +} + void KMFolderTree::slotAddToFavorites() { KMail::FavoriteFolderView *favView = mMainWidget->favoriteFolderView(); @@ -2123,4 +2181,9 @@ void KMFolderTree::slotUnhideLocalInbox() reload(); } +void KMFolderTree::delayedReload() +{ + TQTimer::singleShot( 0, this, TQT_SLOT(reload()) ); +} + #include "kmfoldertree.moc" diff --git a/kmail/kmfoldertree.h b/kmail/kmfoldertree.h index df23116ab..b7244c6c3 100644 --- a/kmail/kmfoldertree.h +++ b/kmail/kmfoldertree.h @@ -88,6 +88,7 @@ public slots: void slotShowExpiryProperties(); void slotIconsChanged(); void slotNameChanged(); + void slotNoContentChanged(); void updateCount(); protected: @@ -115,9 +116,6 @@ public: /** Save config options */ void writeConfig(); - /** Get/refresh the folder tree */ - virtual void reload(bool openFolders = false); - /** Recusively add folders in a folder directory to a listview item. */ virtual void addDirectory( KMFolderDir *fdir, KMFolderTreeItem* parent ); @@ -178,6 +176,9 @@ signals: /** The selected folder has changed to go to an unread message */ void folderSelectedUnread( KMFolder * ); + /** The sync state of the _selected_ folder has changed */ + void syncStateChanged(); + /** unread/total/size column has changed */ void columnsChanged(); @@ -188,6 +189,9 @@ signals: void nameChanged( KMFolderTreeItem * ); public slots: + /** Get/refresh the folder tree */ + virtual void reload(bool openFolders = false); + /** Select the next folder with unread messages */ void nextUnreadFolder(); @@ -231,6 +235,9 @@ public slots: /** Pastes a previously copied/cutted folder below the currently selected folder. */ void pasteFolder(); + /** Reload the folder tree (using a single shot timer) */ + void delayedReload(); + protected slots: // void slotRMB(int, int); /** called by the folder-manager when the list of folders changed */ @@ -282,6 +289,8 @@ protected slots: /** Updates copy/cut/paste actions */ void updateCopyActions(); + void slotSyncStateChanged(); + protected: virtual void contentsMousePressEvent( TQMouseEvent *e ); virtual void contentsMouseReleaseEvent(TQMouseEvent* me); diff --git a/kmail/kmgroupware.cpp b/kmail/kmgroupware.cpp index 08afc3316..47bcf33b1 100644 --- a/kmail/kmgroupware.cpp +++ b/kmail/kmgroupware.cpp @@ -55,8 +55,8 @@ bool vPartFoundAndDecoded( KMMessage* msg, TQString& s ) s = TQString::fromUtf8( msg->bodyDecoded() ); return true; } else if( DwMime::kTypeMultipart == msg->type() && - (DwMime::kSubtypeMixed == msg->subtype() ) || - (DwMime::kSubtypeAlternative == msg->subtype() )) + ( (DwMime::kSubtypeMixed == msg->subtype() ) || + (DwMime::kSubtypeAlternative == msg->subtype() ) )) { // kdDebug(5006) << "KMGroupware looking for TNEF data" << endl; DwBodyPart* dwPart = msg->findDwBodyPart( DwMime::kTypeApplication, diff --git a/kmail/kmheaders.cpp b/kmail/kmheaders.cpp index c0e5c4225..bcfc06f3b 100644 --- a/kmail/kmheaders.cpp +++ b/kmail/kmheaders.cpp @@ -85,6 +85,7 @@ TQPixmap* KMHeaders::pixUndefinedEncrypted = 0; TQPixmap* KMHeaders::pixEncryptionProblematic = 0; TQPixmap* KMHeaders::pixSignatureProblematic = 0; TQPixmap* KMHeaders::pixAttachment = 0; +TQPixmap* KMHeaders::pixInvitation = 0; TQPixmap* KMHeaders::pixReadFwd = 0; TQPixmap* KMHeaders::pixReadReplied = 0; TQPixmap* KMHeaders::pixReadFwdReplied = 0; @@ -93,7 +94,8 @@ TQPixmap* KMHeaders::pixReadFwdReplied = 0; //----------------------------------------------------------------------------- KMHeaders::KMHeaders(KMMainWidget *aOwner, TQWidget *parent, const char *name) : - KListView(parent, name) + KListView( parent, name ), + mIgnoreSortOrderChanges( false ) { static bool pixmapsLoaded = false; //qInitImageIO(); @@ -131,6 +133,7 @@ KMHeaders::KMHeaders(KMMainWidget *aOwner, TQWidget *parent, mPopup->insertItem(i18n("Important"), KPaintInfo::COL_IMPORTANT); mPopup->insertItem(i18n("Action Item"), KPaintInfo::COL_TODO); mPopup->insertItem(i18n("Attachment"), KPaintInfo::COL_ATTACHMENT); + mPopup->insertItem(i18n("Invitation"), KPaintInfo::COL_INVITATION); mPopup->insertItem(i18n("Spam/Ham"), KPaintInfo::COL_SPAM_HAM); mPopup->insertItem(i18n("Watched/Ignored"), KPaintInfo::COL_WATCHED_IGNORED); mPopup->insertItem(i18n("Signature"), KPaintInfo::COL_SIGNED); @@ -169,6 +172,7 @@ KMHeaders::KMHeaders(KMMainWidget *aOwner, TQWidget *parent, pixEncryptionProblematic = new TQPixmap( UserIcon( "kmmsgencryptionproblematic" ) ); pixSignatureProblematic = new TQPixmap( UserIcon( "kmmsgsignatureproblematic" ) ); pixAttachment = new TQPixmap( UserIcon( "kmmsgattachment" ) ); + pixInvitation = new TQPixmap( UserIcon( "kmmsginvitation" ) ); pixReadFwd = new TQPixmap( UserIcon( "kmmsgread_fwd" ) ); pixReadReplied = new TQPixmap( UserIcon( "kmmsgread_replied" ) ); pixReadFwdReplied = new TQPixmap( UserIcon( "kmmsgread_fwd_replied" ) ); @@ -187,6 +191,7 @@ KMHeaders::KMHeaders(KMMainWidget *aOwner, TQWidget *parent, mPaintInfo.importantCol = addColumn( *pixFlag , "", 0 ); mPaintInfo.todoCol = addColumn( *pixTodo , "", 0 ); mPaintInfo.attachmentCol = addColumn( *pixAttachment , "", 0 ); + mPaintInfo.invitationCol = addColumn( *pixInvitation , "", 0 ); mPaintInfo.spamHamCol = addColumn( *pixSpam , "", 0 ); mPaintInfo.watchedIgnoredCol = addColumn( *pixWatched , "", 0 ); mPaintInfo.signedCol = addColumn( *pixFullySigned , "", 0 ); @@ -277,6 +282,15 @@ void KMHeaders::slotToggleColumn(int id, int mode) moveToCol = 0; break; } + case KPaintInfo::COL_INVITATION: + { + show = &mPaintInfo.showInvitation; + col = &mPaintInfo.invitationCol; + width = pixAttachment->width() + 8; + if ( *col == header()->mapToIndex( *col ) ) + moveToCol = 0; + break; + } case KPaintInfo::COL_IMPORTANT: { show = &mPaintInfo.showImportant; @@ -475,6 +489,9 @@ void KMHeaders::readConfig (void) show = config->readBoolEntry("showAttachmentColumn"); slotToggleColumn(KPaintInfo::COL_ATTACHMENT, show); + show = config->readBoolEntry("showInvitationColumn"); + slotToggleColumn(KPaintInfo::COL_INVITATION, show); + show = config->readBoolEntry("showImportantColumn"); slotToggleColumn(KPaintInfo::COL_IMPORTANT, show); @@ -501,6 +518,7 @@ void KMHeaders::readConfig (void) mPaintInfo.showCryptoIcons = config->readBoolEntry( "showCryptoIcons", false ); mPaintInfo.showAttachmentIcon = config->readBoolEntry( "showAttachmentIcon", true ); + mPaintInfo.showInvitationIcon = config->readBoolEntry( "showInvitationIcon", false ); KMime::DateFormatter::FormatType t = (KMime::DateFormatter::FormatType) config->readNumEntry("dateFormat", KMime::DateFormatter::Fancy ) ; @@ -538,6 +556,16 @@ void KMHeaders::readConfig (void) } } +//----------------------------------------------------------------------------- +void KMHeaders::restoreColumnLayout( KConfig *config, const TQString &group ) +{ + // KListView::restoreLayout() will call setSorting(), which is reimplemented by us. + // We don't want to change the sort order, so we set a flag here that is checked in + // setSorting(). + mIgnoreSortOrderChanges = true; + restoreLayout( config, group ); + mIgnoreSortOrderChanges = false; +} //----------------------------------------------------------------------------- void KMHeaders::reset() @@ -593,7 +621,7 @@ void KMHeaders::readFolderConfig (void) mCurrentItem = config->readNumEntry("Current", 0); mCurrentItemSerNum = config->readNumEntry("CurrentSerialNum", 0); - mPaintInfo.orderOfArrival = config->readBoolEntry( "OrderOfArrival", true ); + mPaintInfo.orderOfArrival = config->readBoolEntry( "OrderOfArrival", false ); mPaintInfo.status = config->readBoolEntry( "Status", false ); { //area for config group "Geometry" @@ -636,6 +664,7 @@ void KMHeaders::writeConfig (void) KConfigGroupSaver saver(config, "General"); config->writeEntry("showMessageSize" , mPaintInfo.showSize); config->writeEntry("showAttachmentColumn" , mPaintInfo.showAttachment); + config->writeEntry("showInvitationColumn" , mPaintInfo.showInvitation); config->writeEntry("showImportantColumn" , mPaintInfo.showImportant); config->writeEntry("showTodoColumn" , mPaintInfo.showTodo); config->writeEntry("showSpamHamColumn" , mPaintInfo.showSpamHam); @@ -792,9 +821,19 @@ void KMHeaders::msgChanged() clear(); return; } - int i = topItemIndex(); - int cur = currentItemIndex(); if (!isUpdatesEnabled()) return; + + // Remember selected messages, current message and some scrollbar data, as we have to restore it + const TQValueList oldSelectedItems = selectedItems(); + const int oldCurrentItemIndex = currentItemIndex(); + const bool scrollbarAtTop = verticalScrollBar() && + verticalScrollBar()->value() == verticalScrollBar()->minValue(); + const bool scrollbarAtBottom = verticalScrollBar() && + verticalScrollBar()->value() == verticalScrollBar()->maxValue(); + const HeaderItem * const oldFirstVisibleItem = dynamic_cast( itemAt( TQPoint( 0, 0 ) ) ); + const int oldOffsetOfFirstVisibleItem = itemRect( oldFirstVisibleItem ).y(); + const uint oldSerNumOfFirstVisibleItem = oldFirstVisibleItem ? oldFirstVisibleItem->msgSerNum() : 0; + TQString msgIdMD5; TQListViewItem *item = currentItem(); HeaderItem *hi = dynamic_cast(item); @@ -808,27 +847,26 @@ void KMHeaders::msgChanged() // prevent IMAP messages from scrolling to top disconnect(this,TQT_SIGNAL(currentChanged(TQListViewItem*)), this,TQT_SLOT(highlightMessage(TQListViewItem*))); - // remember all selected messages - TQValueList curItems = selectedItems(); + updateMessageList(); // do not change the selection - // restore the old state, but move up when there are unread message just out of view - HeaderItem *topOfList = mItems[i]; - item = firstChild(); - TQListViewItem *unreadItem = 0; - while(item && item != topOfList) { - KMMsgBase *msg = mFolder->getMsgBase( static_cast(item)->msgId() ); - if ( msg->isUnread() || msg->isNew() ) { - if ( !unreadItem ) - unreadItem = item; - } else - unreadItem = 0; - item = item->itemBelow(); - } - if(unreadItem == 0) - unreadItem = topOfList; - setContentsPos( 0, itemPos( unreadItem )); - setCurrentMsg( cur ); - setSelectedByIndex( curItems, true ); + + // Restore scrollbar state and selected and current messages + setCurrentMsg( oldCurrentItemIndex ); + setSelectedByIndex( oldSelectedItems, true ); + if ( scrollbarAtTop ) { + setContentsPos( 0, 0 ); + } else if ( scrollbarAtBottom ) { + setContentsPos( 0, contentsHeight() ); + } else if ( oldSerNumOfFirstVisibleItem > 0 ) { + for ( uint i = 0; i < mItems.size(); ++i ) { + const KMMsgBase * const mMsgBase = mFolder->getMsgBase( i ); + if ( mMsgBase->getMsgSerNum() == oldSerNumOfFirstVisibleItem ) { + setContentsPos( 0, itemPos( mItems[i] ) - oldOffsetOfFirstVisibleItem ); + break; + } + } + } + connect(this,TQT_SIGNAL(currentChanged(TQListViewItem*)), this,TQT_SLOT(highlightMessage(TQListViewItem*))); @@ -1909,8 +1947,8 @@ void KMHeaders::findUnreadAux( HeaderItem*& item, if (!msgBase) continue; if (msgBase->isUnread() || msgBase->isNew()) foundUnreadMessage = true; - if (!onlyNew && (msgBase->isUnread() || msgBase->isNew()) - || onlyNew && msgBase->isNew()) + if ( ( !onlyNew && (msgBase->isUnread() || msgBase->isNew()) ) + || ( onlyNew && msgBase->isNew() ) ) lastUnread = newItem; if (newItem == item) break; newItem = static_cast(newItem->itemBelow()); @@ -1941,11 +1979,14 @@ int KMHeaders::findUnread(bool aDirNext, int aStartAt, bool onlyNew, bool accept if (!item) return -1; - if ( !acceptCurrent ) - if (aDirNext) + if ( !acceptCurrent ) { + if (aDirNext) { item = static_cast(item->itemBelow()); - else + } + else { item = static_cast(item->itemAbove()); + } + } } pitem = item; @@ -2413,9 +2454,7 @@ void KMHeaders::slotRMB() mOwner->useAction()->plug( menu ); } else { // show most used actions - if( !mFolder->isSent() ) { - mOwner->messageActions()->replyMenu()->plug( menu ); - } + mOwner->messageActions()->replyMenu()->plug( menu ); mOwner->forwardMenu()->plug( menu ); if( mOwner->sendAgainAction()->isEnabled() ) { mOwner->sendAgainAction()->plug( menu ); @@ -2430,7 +2469,7 @@ void KMHeaders::slotRMB() &mMenuToFolder, msgCopyMenu ); menu->insertItem(i18n("&Copy To"), msgCopyMenu); - if ( mFolder->isReadOnly() ) { + if ( !mFolder->canDeleteMessages() ) { int id = menu->insertItem( i18n("&Move To") ); menu->setItemEnabled( id, false ); } else { @@ -2577,6 +2616,9 @@ const KMMsgBase* KMHeaders::getMsgBaseForItem( const TQListViewItem *item ) cons //----------------------------------------------------------------------------- void KMHeaders::setSorting( int column, bool ascending ) { + if ( mIgnoreSortOrderChanges ) + return; + if (column != -1) { // carsten: really needed? // if (column != mSortCol) @@ -2659,7 +2701,9 @@ void KMHeaders::folderCleared() void KMHeaders::folderClosed() { - mFolder->open( "kmheaders" ); + if ( mFolder->open( "kmheaders" ) == 0 ) + updateMessageList(); + else folderCleared(); } @@ -3027,6 +3071,8 @@ bool KMHeaders::readSortOrder( bool set_selection, bool forceJumpToUnread ) bool jumpToUnread = (GlobalSettings::self()->actionEnterFolder() == GlobalSettings::EnumActionEnterFolder::SelectFirstUnreadNew) || forceJumpToUnread; + HeaderItem *oldestItem = 0; + HeaderItem *newestItem = 0; TQMemArray sortCache(mFolder->count()); bool error = false; @@ -3330,6 +3376,16 @@ bool KMHeaders::readSortOrder( bool set_selection, bool forceJumpToUnread ) { unread_exists = true; } + + if ( !oldestItem || mFolder->getMsgBase( oldestItem->msgId() )->date() > + mFolder->getMsgBase( new_kci->id() )->date() ) { + oldestItem = khi; + } + + if ( !newestItem || mFolder->getMsgBase( newestItem->msgId() )->date() < + mFolder->getMsgBase( new_kci->id() )->date() ) { + newestItem = khi; + } } // If we are sorting by date and ascending the top level items are sorted // ascending and the threads themselves are sorted descending. One wants @@ -3379,10 +3435,12 @@ bool KMHeaders::readSortOrder( bool set_selection, bool forceJumpToUnread ) } } - //show a message + // Select a message, depending on the "When entering a folder:" setting CREATE_TIMER(selection); START_TIMER(selection); if(set_selection) { + + // Search for the id of the first unread/new item, should there be any int first_unread = -1; if (unread_exists) { HeaderItem *item = static_cast(firstChild()); @@ -3401,19 +3459,33 @@ bool KMHeaders::readSortOrder( bool set_selection, bool forceJumpToUnread ) } } + // No unread message to select, so either select the newest, oldest or lastest selected if(first_unread == -1 ) { - setTopItemByIndex(mTopItem); - if ( mCurrentItem >= 0 ) + setTopItemByIndex( mTopItem ); + + if ( GlobalSettings::self()->actionEnterFolder() == + GlobalSettings::EnumActionEnterFolder::SelectNewest && newestItem != 0 ) { + setCurrentItemByIndex( newestItem->msgId() ); + } + else if ( GlobalSettings::self()->actionEnterFolder() == + GlobalSettings::EnumActionEnterFolder::SelectOldest && oldestItem != 0 ) { + setCurrentItemByIndex( oldestItem->msgId() ); + } + else if ( mCurrentItem >= 0 ) setCurrentItemByIndex( mCurrentItem ); else if ( mCurrentItemSerNum > 0 ) setCurrentItemBySerialNum( mCurrentItemSerNum ); else setCurrentItemByIndex( 0 ); + + // There is an unread item to select, so select it } else { setCurrentItemByIndex(first_unread); makeHeaderVisible(); center( contentsX(), itemPos(mItems[first_unread]), 0, 9.0 ); } + + // we are told to not change the selection } else { // only reset the selection if we have no current item if (mCurrentItem <= 0) { @@ -3503,7 +3575,7 @@ void KMHeaders::updateActions() cut->setEnabled( false ); } else { copy->setEnabled( true ); - if ( folder() && folder()->isReadOnly() ) + if ( folder() && !folder()->canDeleteMessages() ) cut->setEnabled( false ); else cut->setEnabled( true ); @@ -3534,7 +3606,9 @@ TQValueList< Q_UINT32 > KMHeaders::selectedSernums() if ( it.current()->isSelected() && it.current()->isVisible() ) { HeaderItem* item = static_cast( it.current() ); KMMsgBase *msgBase = mFolder->getMsgBase( item->msgId() ); - list.append( msgBase->getMsgSerNum() ); + if ( msgBase ) { + list.append( msgBase->getMsgSerNum() ); + } } } return list; @@ -3558,7 +3632,9 @@ TQValueList< Q_UINT32 > KMHeaders::selectedVisibleSernums() } HeaderItem *item = static_cast(it.current()); KMMsgBase *msgBase = mFolder->getMsgBase( item->msgId() ); - list.append( msgBase->getMsgSerNum() ); + if ( msgBase ) { + list.append( msgBase->getMsgSerNum() ); + } } ++it; } diff --git a/kmail/kmheaders.h b/kmail/kmheaders.h index f0f973d65..4055e3c3b 100644 --- a/kmail/kmheaders.h +++ b/kmail/kmheaders.h @@ -133,6 +133,15 @@ public: /** Read color options and set palette. */ virtual void readColorConfig(void); + /** + * Same as KListView::restoreLayout(), only that this does _not_ restore the sort order. + * This is useful since restoreLayout() doesn't restore the sort order correctly, as + * KListView doesn't know about our extended sort order like date of arrival. + * + * Note that if you want to restore the sort order correctly, call readConfig(). + */ + void restoreColumnLayout( KConfig *config, const TQString &group ); + /** Return the current message */ virtual KMMessage* currentMsg(); /** Return the current list view item */ @@ -197,6 +206,9 @@ public: */ bool isMessageCut( Q_UINT32 serNum ) const; + /** Write global config options. */ + virtual void writeConfig(void); + signals: /** emitted when the list view item corresponding to this message has been selected */ @@ -294,8 +306,8 @@ protected: *pixFullySigned, *pixPartiallySigned, *pixUndefinedSigned, *pixFullyEncrypted, *pixPartiallyEncrypted, *pixUndefinedEncrypted, *pixFiller, *pixEncryptionProblematic, - *pixSignatureProblematic, *pixAttachment, - *pixReadFwd, *pixReadReplied, *pixReadFwdReplied,*pixTodo; + *pixSignatureProblematic, *pixAttachment, *pixInvitation, + *pixReadFwd, *pixReadReplied, *pixReadFwdReplied, *pixTodo; /** Look for color changes */ virtual bool event(TQEvent *e); @@ -321,9 +333,6 @@ protected: /** Write per-folder config options. */ virtual void writeFolderConfig(void); - /** Write global config options. */ - virtual void writeConfig(void); - /** Handle shift and control selection */ virtual void contentsMousePressEvent(TQMouseEvent*); virtual void contentsMouseReleaseEvent(TQMouseEvent* e); @@ -389,6 +398,7 @@ private: NestingPolicy nestingPolicy; int mSortCol; bool mSortDescending; + bool mIgnoreSortOrderChanges; struct { uint ascending : 1; diff --git a/kmail/kmkernel.cpp b/kmail/kmkernel.cpp index 9bff9a72c..d0e706f90 100644 --- a/kmail/kmkernel.cpp +++ b/kmail/kmkernel.cpp @@ -23,6 +23,7 @@ using KPIM::BroadcastStatus; #include "kmacctcachedimap.h" #include "kmfiltermgr.h" #include "kmfilteraction.h" +#include "kmheaders.h" #define REALLY_WANT_KMSENDER #include "kmsender.h" #undef REALLY_WANT_KMSENDER @@ -43,6 +44,7 @@ using KRecentAddress::RecentAddresses; #include "kmcommands.h" #include "kmsystemtray.h" #include "transportmanager.h" +#include "importarchivedialog.h" #include #include "kmailicalifaceimpl.h" @@ -91,6 +93,7 @@ using KWallet::Wallet; #include KMKernel *KMKernel::mySelf = 0; +static bool s_askingToGoOnline = false; /********************************************************************/ /* Constructor and destructor */ @@ -316,12 +319,16 @@ bool KMKernel::handleCommandLine( bool noArgsOpensReader ) /********************************************************************/ void KMKernel::checkMail () //might create a new reader but won't show!! { + if ( !kmkernel->askToGoOnline() ) + return; kmkernel->acctMgr()->checkMail(false); } TQStringList KMKernel::accounts() { - return kmkernel->acctMgr()->getAccounts(); + if( kmkernel->acctMgr() ) + return kmkernel->acctMgr()->getAccounts(); + return TQStringList(); } void KMKernel::checkAccount (const TQString &account) //might create a new reader but won't show!! @@ -399,8 +406,7 @@ int KMKernel::openComposer (const TQString &to, const TQString &cc, if( !str.isEmpty() ) { msg->setBody( TQString::fromLocal8Bit( str ).utf8() ); } else { - TemplateParser parser( msg, TemplateParser::NewMessage, - "", false, false, false, false ); + TemplateParser parser( msg, TemplateParser::NewMessage ); parser.process( NULL, NULL ); } } @@ -410,8 +416,7 @@ int KMKernel::openComposer (const TQString &to, const TQString &cc, } else { - TemplateParser parser( msg, TemplateParser::NewMessage, - "", false, false, false, false ); + TemplateParser parser( msg, TemplateParser::NewMessage ); parser.process( NULL, NULL ); } @@ -480,6 +485,27 @@ int KMKernel::openComposer (const TQString &to, const TQString &cc, const TQString &attachParamValue, const TQCString &attachContDisp, const TQCString &attachCharset ) +{ + kdDebug(5006) << "KMKernel::openComposer called (deprecated version)" << endl; + return openComposer ( to, cc, bcc, subject, body, hidden, + attachName, attachCte, attachData, + attachType, attachSubType, attachParamAttr, + attachParamValue, attachContDisp, attachCharset, 0 ); +} + +int KMKernel::openComposer (const TQString &to, const TQString &cc, + const TQString &bcc, const TQString &subject, + const TQString &body, int hidden, + const TQString &attachName, + const TQCString &attachCte, + const TQCString &attachData, + const TQCString &attachType, + const TQCString &attachSubType, + const TQCString &attachParamAttr, + const TQString &attachParamValue, + const TQCString &attachContDisp, + const TQCString &attachCharset, + unsigned int identity ) { kdDebug(5006) << "KMKernel::openComposer()" << endl; @@ -491,11 +517,11 @@ int KMKernel::openComposer (const TQString &to, const TQString &cc, if ( !bcc.isEmpty() ) msg->setBcc(bcc); if ( !subject.isEmpty() ) msg->setSubject(subject); if ( !to.isEmpty() ) msg->setTo(to); + if ( identity > 0 ) msg->setHeaderField( "X-KMail-Identity", TQString::number( identity ) ); if ( !body.isEmpty() ) { msg->setBody(body.utf8()); } else { - TemplateParser parser( msg, TemplateParser::NewMessage, - "", false, false, false, false ); + TemplateParser parser( msg, TemplateParser::NewMessage ); parser.process( NULL, NULL ); } @@ -557,6 +583,11 @@ int KMKernel::openComposer (const TQString &to, const TQString &cc, if ( msgPart ) cWin->addAttach(msgPart); + if ( isICalInvitation ) { + cWin->disableRecipientNumberCheck(); + cWin->disableForgottenAttachmentsCheck(); + } + if ( hidden == 0 && !iCalAutoSend ) { cWin->show(); // Activate window - doing this instead of KWin::activateWindow(cWin->winId()); @@ -597,8 +628,7 @@ DCOPRef KMKernel::openComposer(const TQString &to, const TQString &cc, if (!body.isEmpty()) { msg->setBody(body.utf8()); } else { - TemplateParser parser( msg, TemplateParser::NewMessage, - "", false, false, false, false ); + TemplateParser parser( msg, TemplateParser::NewMessage ); parser.process( NULL, NULL ); } @@ -644,13 +674,11 @@ DCOPRef KMKernel::newMessage(const TQString &to, if (!bcc.isEmpty()) msg->setBcc(bcc); if ( useFolderId ) { - TemplateParser parser( msg, TemplateParser::NewMessage, - "", false, false, false, false ); + TemplateParser parser( msg, TemplateParser::NewMessage ); parser.process( NULL, folder ); win = makeComposer( msg, id ); } else { - TemplateParser parser( msg, TemplateParser::NewMessage, - "", false, false, false, false ); + TemplateParser parser( msg, TemplateParser::NewMessage ); parser.process( NULL, folder ); win = makeComposer( msg ); } @@ -1053,6 +1081,14 @@ int KMKernel::dcopAddMessage_fastImport( const TQString & foldername, return retval; } +void KMKernel::showImportArchiveDialog() +{ + KMMainWidget *mainWidget = getKMMainWidget(); + KMail::ImportArchiveDialog *importDialog = new KMail::ImportArchiveDialog( mainWidget, WDestructiveClose ); + importDialog->setFolder( mainWidget->folderTree()->currentFolder() ); + importDialog->show(); +} + TQStringList KMKernel::folderList() const { TQStringList folders; @@ -1235,7 +1271,13 @@ bool KMKernel::isOffline() bool KMKernel::askToGoOnline() { + // already asking means we are offline and need to wait anyhow + if ( s_askingToGoOnline ) { + return false; + } + if ( kmkernel->isOffline() ) { + s_askingToGoOnline = true; int rc = KMessageBox::questionYesNo( KMKernel::self()->mainWin(), i18n("KMail is currently in offline mode. " @@ -1244,6 +1286,7 @@ bool KMKernel::askToGoOnline() i18n("Work Online"), i18n("Work Offline")); + s_askingToGoOnline = false; if( rc == KMessageBox::No ) { return false; } else { @@ -1336,27 +1379,34 @@ void KMKernel::testDir(const char *_name) // Open a composer for each message found in the dead.letter folder void KMKernel::recoverDeadLetters() { - const TQString pathName = localDataPath(); - TQDir dir( pathName ); - if ( !dir.exists( "autosave" ) ) - return; - - KMFolder folder( 0, pathName + "autosave", KMFolderTypeMaildir, false /* no index */ ); - KMFolderOpener openFolder( &folder, "recover" ); - if ( !folder.isOpened() ) { - perror( "cannot open autosave folder" ); + TQDir dir( localDataPath() + "autosave/cur" ); + if ( !dir.exists() ) { + kdWarning(5006) << "Autosave directory " << dir.path() << " not found!" << endl; return; } - const int num = folder.count(); - for ( int i = 0; i < num; i++ ) { - KMMessage *msg = folder.take( 0 ); - if ( msg ) { - KMail::Composer * win = KMail::makeComposer(); - win->setMsg( msg, false, false, true ); - win->setAutoSaveFilename( msg->fileName() ); - win->show(); + const TQStringList entryList = dir.entryList( TQDir::Files | TQDir::NoSymLinks, TQDir::Unsorted ); + for ( unsigned int i = 0; i < entryList.size(); i++ ) { + const TQString fileName = entryList[i]; + TQFile file( dir.path() + '/' + fileName ); + if ( !file.open( IO_ReadOnly ) ) { + kdWarning(5006) << "Unable to open autosave file " << fileName << endl; + continue; } + const TQByteArray msgData = file.readAll(); + file.close(); + + if ( msgData.isEmpty() ) { + kdWarning(5006) << "autosave file " << fileName << " is empty!" << endl; + continue; + } + + KMMessage *msg = new KMMessage(); // Composer will take ownership + msg->fromByteArray( msgData ); + KMail::Composer * win = KMail::makeComposer(); + win->setMsg( msg, false, false, true ); + win->setAutoSaveFilename( fileName ); + win->show(); } } @@ -1475,6 +1525,7 @@ void KMKernel::init() the_folderMgr = new KMFolderMgr(foldersPath); the_imapFolderMgr = new KMFolderMgr( KMFolderImap::cacheLocation(), KMImapDir); the_dimapFolderMgr = new KMFolderMgr( KMFolderCachedImap::cacheLocation(), KMDImapDir); + recreateCorruptIndexFiles(); the_searchFolderMgr = new KMFolderMgr(locateLocal("data","kmail/search"), KMSearchDir); KMFolder *lsf = the_searchFolderMgr->find( i18n("Last Search") ); @@ -1533,6 +1584,20 @@ void KMKernel::init() #else mBackgroundTasksTimer->start( 5 * 60000, true ); // 5 minutes, singleshot #endif + + TQTextCodec *codec; + for ( int i = 0; ( codec = TQTextCodec::codecForIndex ( i ) ); i++ ) { + const TQString asciiString( "azAZ19,.-#+!?=()&" ); + const TQCString encodedString = codec->fromUnicode( asciiString ); + if ( TQString::fromAscii( encodedString ) != asciiString ) { + mNonAsciiCompatibleCodecs.append( codec ); + } + } +} + +bool KMKernel::isCodecAsciiCompatible( const TQTextCodec *codec ) +{ + return !mNonAsciiCompatibleCodecs.contains( codec ); } void KMKernel::readConfig() @@ -1624,6 +1689,43 @@ void KMKernel::cleanupImapFolders() the_dimapFolderMgr->quiet( false ); } +void KMKernel::recreateCorruptIndexFiles() +{ + TQValueList > folders; + TQValueList foldersWithBrokenIndex; + TQStringList strList; + the_folderMgr->createFolderList( &strList, &folders ); + the_imapFolderMgr->createFolderList( &strList, &folders ); + the_dimapFolderMgr->createFolderList( &strList, &folders ); + for ( int i = 0; folders.at(i) != folders.end(); i++ ) { + KMFolder * const folder = *folders.at(i); + if ( !folder || folder->isDir() || folder->isOpened() ) + continue; + KMFolderIndex * const index = dynamic_cast( folder->storage() ); + if ( index && index->indexStatus() != KMFolderIndex::IndexOk ) { + foldersWithBrokenIndex.append( index ); + } + } + + if ( !foldersWithBrokenIndex.isEmpty() ) { + TQStringList folderNames; + for ( uint i = 0; i < foldersWithBrokenIndex.size(); i++ ) { + folderNames << foldersWithBrokenIndex[i]->label(); + } + + KMessageBox::informationList( 0, i18n( "There is a problem with the mail index of the following " + "folders, the indices will now be regenerated.\n" + "This can happen because the index files are out of date, missing or corrupted.\n" + "Contact your administrator if this happens frequently.\n" + "Some information, like status flags, might get lost." ), + folderNames, i18n( "Problem with mail indices" ) ); + + for ( uint i = 0; i < foldersWithBrokenIndex.size(); i++ ) { + foldersWithBrokenIndex[i]->silentlyRecreateIndex(); + } + } +} + bool KMKernel::doSessionManagement() { @@ -1865,9 +1967,17 @@ void KMKernel::dumpDeadLetters() if ( !KMainWindow::memberList ) return; - for ( TQPtrListIterator it(*KMainWindow::memberList) ; it.current() != 0; ++it ) - if ( KMail::Composer * win = ::qt_cast( it.current() ) ) + for ( TQPtrListIterator it(*KMainWindow::memberList) ; it.current() != 0; ++it ) { + if ( KMail::Composer * win = ::qt_cast( it.current() ) ) { win->autoSaveMessage(); + // saving the message has to be finished right here, we are called from a dtor, + // therefore we have no chance to finish this later + // yes, this is ugly and potentially dangerous, but the alternative is losing + // currently composed messages... + while ( win->isComposing() ) + qApp->processEvents(); + } + } } @@ -1955,7 +2065,7 @@ void KMKernel::slotShowConfigurationDialog() { if( !mConfigureDialog ) { mConfigureDialog = new ConfigureDialog( 0, "configure", false ); - connect( mConfigureDialog, TQT_SIGNAL( configCommitted() ), + connect( mConfigureDialog, TQT_SIGNAL( configChanged() ), this, TQT_SLOT( slotConfigChanged() ) ); } @@ -1967,9 +2077,11 @@ void KMKernel::slotShowConfigurationDialog() KMMainWin * win = new KMMainWin; win->show(); } - if( mConfigureDialog->isHidden() ) + { + getKMMainWidget()->headers()->writeConfig(); mConfigureDialog->show(); + } else mConfigureDialog->raise(); } @@ -2326,7 +2438,7 @@ bool KMKernel::canQueryClose() if ( systray->mode() == GlobalSettings::EnumSystemTrayPolicy::ShowAlways ) { systray->hideKMail(); return false; - } else if ( systray->mode() == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ) { + } else if ( ( systray->mode() == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ) && ( systray->hasUnreadMail() )) { systray->show(); systray->hideKMail(); return false; diff --git a/kmail/kmkernel.h b/kmail/kmkernel.h index 02f3e437b..455e42334 100644 --- a/kmail/kmkernel.h +++ b/kmail/kmkernel.h @@ -135,6 +135,9 @@ public: const TQString &attachParamValue, const TQCString &attachContDisp); + /** For backward compatibility + * @deprecated + */ int openComposer (const TQString &to, const TQString &cc, const TQString &bcc, const TQString &subject, const TQString &body, int hidden, @@ -148,6 +151,20 @@ public: const TQCString &attachContDisp, const TQCString &attachCharset); + int openComposer (const TQString &to, const TQString &cc, + const TQString &bcc, const TQString &subject, + const TQString &body, int hidden, + const TQString &attachName, + const TQCString &attachCte, + const TQCString &attachData, + const TQCString &attachType, + const TQCString &attachSubType, + const TQCString &attachParamAttr, + const TQString &attachParamValue, + const TQCString &attachContDisp, + const TQCString &attachCharset, + unsigned int identity); + DCOPRef openComposer(const TQString &to, const TQString &cc, const TQString &bcc, const TQString &subject, const TQString &body,bool hidden); @@ -179,6 +196,7 @@ public: const TQString & MsgStatusFlags = TQString()); int dcopAddMessage_fastImport(const TQString & foldername, const KURL & messagefile, const TQString & MsgStatusFlags = TQString()); + void showImportArchiveDialog(); TQStringList folderList() const; DCOPRef getFolder( const TQString& vpath ); @@ -235,6 +253,7 @@ public: void init(); void readConfig(); void cleanupImapFolders(); + void recreateCorruptIndexFiles(); void testDir(const char *_name); void recoverDeadLetters(); void initFolders(KConfig* cfg); @@ -389,6 +408,8 @@ public: void loadProfile( const TQString& path ); void saveToProfile( const TQString& path ) const; + + bool isCodecAsciiCompatible( const TQTextCodec *codec ); public slots: /// Save contents of all open composer widnows to ~/dead.letter @@ -488,6 +509,7 @@ private: bool mContextMenuShown; TQValueList systemTrayApplets; + TQValueList mNonAsciiCompatibleCodecs; /* Weaver */ KPIM::ThreadWeaver::Weaver *the_weaver; diff --git a/kmail/kmlineeditspell.cpp b/kmail/kmlineeditspell.cpp index 835421873..dde827bec 100644 --- a/kmail/kmlineeditspell.cpp +++ b/kmail/kmlineeditspell.cpp @@ -8,6 +8,7 @@ #include "recentaddresses.h" #include "kmkernel.h" #include "globalsettings.h" +#include "stringutil.h" #include #include @@ -78,52 +79,74 @@ void KMLineEdit::insertEmails( const TQStringList & emails ) for ( TQStringList::const_iterator it = emails.begin(), end = emails.end() ; it != end; ++it ) menu.insertItem( *it ); const int result = menu.exec( TQCursor::pos() ); - if ( result < 0 ) + if ( result == -1 ) return; setText( contents + menu.text( result ) ); } -void KMLineEdit::dropEvent(TQDropEvent *event) +void KMLineEdit::dropEvent( TQDropEvent *event ) { - TQString vcards; - KVCardDrag::decode( event, vcards ); - if ( !vcards.isEmpty() ) { - KABC::VCardConverter converter; - KABC::Addressee::List list = converter.parseVCards( vcards ); + KURL::List urls; + + // Case one: The user dropped a text/directory (i.e. vcard), so decode its + // contents + if ( KVCardDrag::canDecode( event ) ) { + KABC::Addressee::List list; + KVCardDrag::decode( event, list ); + 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; - TQString fileName; - TQString caption( i18n( "vCard Import Failed" ) ); - for ( it = urls.begin(); it != urls.end(); ++it ) { - if ( KIO::NetAccess::download( *it, fileName, parentWidget() ) ) { + } + + // Case two: The user dropped a list or Urls. + // Iterate over that list. For mailto: Urls, just add the addressee to the list, + // and for other Urls, download the Url and assume it points to a vCard + else if ( KURLDrag::decode( event, urls ) ) { + KURL::List::Iterator it = urls.begin(); + KABC::Addressee::List list; + for ( it = urls.begin(); it != urls.end(); ++it ) { + + // First, let's deal with mailto Urls. The path() part contains the + // email-address. + if ( (*it).protocol() == "mailto" ) { + KABC::Addressee addressee; + addressee.insertEmail( KMail::StringUtil::decodeMailtoUrl( (*it).path() ), true /* preferred */ ); + list += addressee; + } + // Otherwise, download the vCard to which the Url points + else { + KABC::VCardConverter converter; + TQString fileName; + if ( KIO::NetAccess::download( (*it), fileName, parentWidget() ) ) { TQFile file( fileName ); file.open( IO_ReadOnly ); - TQByteArray rawData = file.readAll(); + const TQByteArray data = file.readAll(); file.close(); - TQString data = TQString::fromUtf8( rawData.data(), rawData.size() + 1 ); +#if defined(KABC_VCARD_ENCODING_FIX) + list += converter.parseVCardsRaw( data.data() ); +#else list += converter.parseVCards( data ); +#endif KIO::NetAccess::removeTempFile( fileName ); } else { - TQString text = i18n( "Unable to access %1." ); - KMessageBox::error( parentWidget(), text.arg( (*it).url() ), caption ); + TQString caption( i18n( "vCard Import Failed" ) ); + TQString text = i18n( "Unable to access %1." ).arg( (*it).url() ); + KMessageBox::error( parentWidget(), text, caption ); } - KABC::Addressee::List::Iterator ait; - for ( ait = list.begin(); ait != list.end(); ++ait ) - insertEmails((*ait).emails()); } - } else { - KPIM::AddresseeLineEdit::dropEvent( event ); + // Now, let the user choose which addressee to add. + KABC::Addressee::List::Iterator ait; + for ( ait = list.begin(); ait != list.end(); ++ait ) + insertEmails( (*ait).emails() ); } } + + // Case three: Let AddresseeLineEdit deal with the rest + else { + KPIM::AddresseeLineEdit::dropEvent( event ); + } } TQPopupMenu *KMLineEdit::createPopupMenu() @@ -156,7 +179,6 @@ void KMLineEdit::editRecentAddresses() //----------------------------------------------------------------------------- void KMLineEdit::loadContacts() { - // was: KABC::AddressLineEdit::loadAddresses() AddresseeLineEdit::loadContacts(); if ( GlobalSettings::self()->showRecentAddressesInComposer() ){ @@ -165,13 +187,22 @@ void KMLineEdit::loadContacts() KRecentAddress::RecentAddresses::self( KMKernel::config() )->addresses(); TQStringList::Iterator it = recent.begin(); TQString name, email; - int idx = addCompletionSource( i18n( "Recent Addresses" ) ); + + KConfig config( "kpimcompletionorder" ); + config.setGroup( "CompletionWeights" ); + int weight = config.readEntry( "Recent Addresses", "10" ).toInt(); + int idx = addCompletionSource( i18n( "Recent Addresses" ), weight ); for ( ; it != recent.end(); ++it ) { KABC::Addressee addr; KPIM::getNameAndMail(*it, name, email); - addr.setNameFromString( KPIM::quoteNameIfNecessary( name )); + name = KPIM::quoteNameIfNecessary( name ); + if ( ( name[0] == '"' ) && ( name[name.length() - 1] == '"' ) ) { + name.remove( 0, 1 ); + name.truncate( name.length() - 1 ); + } + addr.setNameFromString( name ); addr.insertEmail( email, true ); - addContact( addr, 120, idx ); // more weight than kabc entries and more than ldap results + addContact( addr, weight, idx ); } } } diff --git a/kmail/kmmainwidget.cpp b/kmail/kmmainwidget.cpp index 0b847ba64..27b04df0d 100644 --- a/kmail/kmmainwidget.cpp +++ b/kmail/kmmainwidget.cpp @@ -18,15 +18,13 @@ #include #include #include -#include #include +#include +#include +#include #include - #include -#include -#include - #include #include #include @@ -45,8 +43,6 @@ #include #include -#include - #include "globalsettings.h" #include "kcursorsaver.h" #include "broadcaststatus.h" @@ -80,9 +76,6 @@ using KMail::ImapAccountBase; #include "vacation.h" using KMail::Vacation; #include "favoritefolderview.h" - -#include - #include "subscriptiondialog.h" using KMail::SubscriptionDialog; #include "localsubscriptiondialog.h" @@ -106,6 +99,9 @@ using KMail::HeaderListQuickSearch; #include "kmheaders.h" #include "mailinglistpropertiesdialog.h" #include "templateparser.h" +#include "archivefolderdialog.h" +#include "folderutil.h" +#include "csshelper.h" #if !defined(NDEBUG) #include "sievedebugdialog.h" @@ -129,7 +125,6 @@ using KMime::Types::AddrSpecList; using KPIM::ProgressManager; #include "managesievescriptsdialog.h" -#include #include "customtemplates.h" #include "customtemplates_kfg.h" @@ -150,6 +145,7 @@ KMMainWidget::KMMainWidget(TQWidget *parent, const char *name, mFolderViewParent( 0 ), mFolderViewSplitter( 0 ), mQuickSearchLine( 0 ), + mArchiveFolderAction( 0 ), mShowBusySplashTimer( 0 ), mShowingOfflineScreen( false ), mMsgActions( 0 ), @@ -226,6 +222,8 @@ KMMainWidget::KMMainWidget(TQWidget *parent, const char *name, this, TQT_SLOT(slotChangeCaption(TQListViewItem*))); connect(mFolderTree, TQT_SIGNAL(selectionChanged()), TQT_SLOT(updateFolderMenu()) ); + connect( mFolderTree, TQT_SIGNAL(syncStateChanged()), + TQT_SLOT(updateFolderMenu()) ); connect(kmkernel->folderMgr(), TQT_SIGNAL(folderRemoved(KMFolder*)), this, TQT_SLOT(slotFolderRemoved(KMFolder*))); @@ -300,8 +298,6 @@ void KMMainWidget::readPreConfig(void) mHtmlPref = reader.readBoolEntry( "htmlMail", false ); mHtmlLoadExtPref = reader.readBoolEntry( "htmlLoadExternal", false ); mEnableFavoriteFolderView = GlobalSettings::self()->enableFavoriteFolderView(); - mEnableFolderQuickSearch = GlobalSettings::self()->enableFolderQuickSearch(); - mEnableQuickSearch = GlobalSettings::self()->quickSearchActive(); } @@ -344,8 +340,6 @@ void KMMainWidget::readConfig(void) bool oldReaderWindowActive = mReaderWindowActive; bool oldReaderWindowBelow = mReaderWindowBelow; bool oldFavoriteFolderView = mEnableFavoriteFolderView; - bool oldFolderQuickSearch = mEnableFolderQuickSearch; - bool oldQuickSearch = mEnableQuickSearch; TQString str; TQSize siz; @@ -360,9 +354,7 @@ void KMMainWidget::readConfig(void) bool layoutChanged = ( oldLongFolderList != mLongFolderList ) || ( oldReaderWindowActive != mReaderWindowActive ) || ( oldReaderWindowBelow != mReaderWindowBelow ) - || ( oldFavoriteFolderView != mEnableFavoriteFolderView ) - || ( oldFolderQuickSearch != mEnableFolderQuickSearch ) - || ( oldQuickSearch != mEnableQuickSearch ); + || ( oldFavoriteFolderView != mEnableFavoriteFolderView ); if( layoutChanged ) { @@ -450,7 +442,7 @@ void KMMainWidget::readConfig(void) mMsgView->readConfig(); mHeaders->readConfig(); - mHeaders->restoreLayout(KMKernel::config(), "Header-Geometry"); + mHeaders->restoreColumnLayout( KMKernel::config(), "Header-Geometry" ); if ( mFolderViewSplitter && !GlobalSettings::self()->folderViewSplitterPosition().isEmpty() ) { mFolderViewSplitter->setSizes( GlobalSettings::self()->folderViewSplitterPosition() ); @@ -493,9 +485,7 @@ void KMMainWidget::readConfig(void) bool layoutChanged = ( oldLongFolderList != mLongFolderList ) || ( oldReaderWindowActive != mReaderWindowActive ) || ( oldReaderWindowBelow != mReaderWindowBelow ) - || ( oldFavoriteFolderView != mEnableFavoriteFolderView ) - || ( oldFolderQuickSearch != mEnableFolderQuickSearch ) - || ( oldQuickSearch != mEnableQuickSearch ); + || ( oldFavoriteFolderView != mEnableFavoriteFolderView ); if ( layoutChanged ) { activatePanners(); } @@ -671,10 +661,10 @@ void KMMainWidget::createWidgets(void) KAction *action; - action = new KAction( i18n("Move Message to Folder"), Key_M, this, + mMoveMsgToFolderAction = new KAction( i18n("Move Message to Folder"), Key_M, this, TQT_SLOT(slotMoveMsg()), actionCollection(), "move_message_to_folder" ); - action->plugAccel( actionCollection()->kaccel() ); + mMoveMsgToFolderAction->plugAccel( actionCollection()->kaccel() ); action = new KAction( i18n("Copy Message to Folder"), Key_C, this, TQT_SLOT(slotCopyMsg()), actionCollection(), @@ -697,25 +687,9 @@ void KMMainWidget::createWidgets(void) folderTreeParent = mFolderViewSplitter; mFolderView = mFolderViewSplitter; } - - // the "folder tree" consists of a quicksearch input field and the tree itself - mSearchAndTree = new TQVBox(folderTreeParent); - mFolderQuickSearch = new TQHBox(mSearchAndTree); - TQPushButton *clear = new TQPushButton(TQApplication::reverseLayout() - ? SmallIcon("clear_left") - : SmallIcon("locationbar_erase"), "", mFolderQuickSearch); - clear->setFlat(true); - KListViewSearchLine *search = new KListViewSearchLine(mFolderQuickSearch); - mFolderTree = new KMFolderTree(this, mSearchAndTree, "folderTree"); - search->setListView(mFolderTree); - connect(clear, TQT_SIGNAL(clicked()), search, TQT_SLOT(clear())); - - if ( !GlobalSettings::enableFolderQuickSearch() ) { - mFolderQuickSearch->hide(); - } - + mFolderTree = new KMFolderTree(this, folderTreeParent, "folderTree"); if ( !GlobalSettings::enableFavoriteFolderView() ) { - mFolderView = mSearchAndTree; + mFolderView = mFolderTree; } connect( mFolderTree, TQT_SIGNAL(folderSelected(KMFolder*)), mFavoriteFolderView, TQT_SLOT(folderTreeSelectionChanged(KMFolder*)) ); @@ -1020,14 +994,12 @@ void KMMainWidget::slotCompose() if ( mFolder ) { msg->initHeader( mFolder->identity() ); - TemplateParser parser( msg, TemplateParser::NewMessage, - "", false, false, false, false ); + TemplateParser parser( msg, TemplateParser::NewMessage ); parser.process( NULL, mFolder ); win = KMail::makeComposer( msg, mFolder->identity() ); } else { msg->initHeader(); - TemplateParser parser( msg, TemplateParser::NewMessage, - "", false, false, false, false ); + TemplateParser parser( msg, TemplateParser::NewMessage ); parser.process( NULL, NULL ); win = KMail::makeComposer( msg ); } @@ -1128,6 +1100,9 @@ void KMMainWidget::modifyFolder( KMFolderTreeItem* folderItem ) i18n("Properties of Folder %1").arg( folder->label() ) ); props.exec(); updateFolderMenu(); + //Kolab issue 2152 + if ( mSystemTray ) + mSystemTray->foldersChanged(); } //----------------------------------------------------------------------------- @@ -1204,6 +1179,13 @@ void KMMainWidget::slotEmptyFolder() mEmptyFolderAction->setEnabled( false ); } +//----------------------------------------------------------------------------- +void KMMainWidget::slotArchiveFolder() +{ + KMail::ArchiveFolderDialog archiveDialog; + archiveDialog.setFolder( mFolder ); + archiveDialog.exec(); +} //----------------------------------------------------------------------------- void KMMainWidget::slotRemoveFolder() @@ -1214,6 +1196,13 @@ void KMMainWidget::slotRemoveFolder() if ( !mFolder ) return; if ( mFolder->isSystemFolder() ) return; if ( mFolder->isReadOnly() ) return; + if ( mFolder->mailCheckInProgress() ) { + KMessageBox::sorry( this, i18n( "It is not possible to delete this folder right now because it " + "is being syncronized. Please wait until the syncronization of " + "this folder is complete and then try again." ), + i18n( "Unable to delete folder" ) ); + return; + } TQString title; if ( mFolder->folderType() == KMFolderTypeSearch ) { @@ -1259,32 +1248,7 @@ void KMMainWidget::slotRemoveFolder() KGuiItem( i18n("&Delete"), "editdelete")) == KMessageBox::Continue) { - if ( mFolder->hasAccounts() ) { - // this folder has an account, so we need to change that to the inbox - for ( AccountList::Iterator it (mFolder->acctList()->begin() ), - end( mFolder->acctList()->end() ); it != end; ++it ) { - (*it)->setFolder( kmkernel->inboxFolder() ); - KMessageBox::information(this, - i18n("The folder you deleted was associated with the account " - "%1 which delivered mail into it. The folder the account " - "delivers new mail into was reset to the main Inbox folder.").arg( (*it)->name())); - } - } - if (mFolder->folderType() == KMFolderTypeImap) - kmkernel->imapFolderMgr()->remove(mFolder); - else if (mFolder->folderType() == KMFolderTypeCachedImap) { - // Deleted by user -> tell the account (see KMFolderCachedImap::listDirectory2) - KMFolderCachedImap* storage = static_cast( mFolder->storage() ); - KMAcctCachedImap* acct = storage->account(); - if ( acct ) - acct->addDeletedFolder( mFolder ); - - kmkernel->dimapFolderMgr()->remove(mFolder); - } - else if (mFolder->folderType() == KMFolderTypeSearch) - kmkernel->searchFolderMgr()->remove(mFolder); - else - kmkernel->folderMgr()->remove(mFolder); + KMail::FolderUtil::deleteFolder( mFolder, this ); } } @@ -1328,7 +1292,7 @@ void KMMainWidget::slotRefreshFolder() imap->getAndCheckFolder(); } else if ( mFolder->folderType() == KMFolderTypeCachedImap ) { KMFolderCachedImap* f = static_cast( mFolder->storage() ); - f->account()->processNewMailSingleFolder( mFolder ); + f->account()->processNewMailInFolder( mFolder ); } } } @@ -1443,6 +1407,18 @@ void KMMainWidget::slotToggleSubjectThreading() mHeaders->setSubjectThreading(mFolderThreadSubjPref); } +//----------------------------------------------------------------------------- +void KMMainWidget::slotToggleShowQuickSearch() +{ + GlobalSettings::self()->setQuickSearchActive( !GlobalSettings::self()->quickSearchActive() ); + if ( GlobalSettings::self()->quickSearchActive() ) + mSearchToolBar->show(); + else { + mQuickSearchLine->reset(); + mSearchToolBar->hide(); + } +} + //----------------------------------------------------------------------------- void KMMainWidget::slotMessageQueuedOrDrafted() { @@ -1670,6 +1646,7 @@ void KMMainWidget::slotUndo() { mHeaders->undo(); updateMessageActions(); + updateFolderMenu(); } //----------------------------------------------------------------------------- @@ -1827,22 +1804,69 @@ void KMMainWidget::slotCopyMsg() //----------------------------------------------------------------------------- void KMMainWidget::slotPrintMsg() { + KMMessage *msg = mHeaders->currentMsg(); + if ( !msg ) { + return; + } + bool htmlOverride = mMsgView ? mMsgView->htmlOverride() : false; bool htmlLoadExtOverride = mMsgView ? mMsgView->htmlLoadExtOverride() : false; KConfigGroup reader( KMKernel::config(), "Reader" ); bool useFixedFont = mMsgView ? mMsgView->isFixedFont() : reader.readBoolEntry( "useFixedFont", false ); - KMCommand *command = - new KMPrintCommand( this, mHeaders->currentMsg(), + + const HeaderStyle *style; + const HeaderStrategy *strategy; + if ( mMsgView ) { + style = mMsgView->headerStyle(); + strategy = mMsgView->headerStrategy(); + } else { + style = HeaderStyle::create( reader.readEntry( "header-style", "fancy" ) ); + strategy = HeaderStrategy::create( reader.readEntry( "header-set-displayed", "rich" ) ); + } + + KMPrintCommand *command = + new KMPrintCommand( this, msg, + style, strategy, htmlOverride, htmlLoadExtOverride, useFixedFont, overrideEncoding() ); + if ( mMsgView ) + command->setOverrideFont( mMsgView->cssHelper()->bodyFont( mMsgView->isFixedFont(), true /*printing*/ ) ); + command->start(); } +//----------------------------------------------------------------------------- +void KMMainWidget::setupForwardActions() +{ + disconnect( mForwardActionMenu, TQT_SIGNAL( activated() ), 0, 0 ); + mForwardActionMenu->remove( mForwardInlineAction ); + mForwardActionMenu->remove( mForwardAttachedAction ); + + if ( GlobalSettings::self()->forwardingInlineByDefault() ) { + mForwardActionMenu->insert( mForwardInlineAction, 0 ); + mForwardActionMenu->insert( mForwardAttachedAction, 1 ); + mForwardInlineAction->setShortcut( Key_F ); + mForwardAttachedAction->setShortcut( SHIFT+Key_F ); + connect( mForwardActionMenu, TQT_SIGNAL(activated()), this, + TQT_SLOT(slotForwardInlineMsg()) ); + + } else { + mForwardActionMenu->insert( mForwardAttachedAction, 0 ); + mForwardActionMenu->insert( mForwardInlineAction, 1 ); + mForwardInlineAction->setShortcut( SHIFT+Key_F ); + mForwardAttachedAction->setShortcut( Key_F ); + connect( mForwardActionMenu, TQT_SIGNAL(activated()), this, + TQT_SLOT(slotForwardAttachedMsg()) ); + } +} + //----------------------------------------------------------------------------- void KMMainWidget::slotConfigChanged() { readConfig(); + setupForwardActions(); + setupForwardingActionsList(); } //----------------------------------------------------------------------------- @@ -1888,6 +1912,7 @@ void KMMainWidget::slotOnlineStatus() kmkernel->stopNetworkJobs(); } else { kmkernel->resumeNetworkJobs(); + slotCheckVacation(); } } @@ -2396,9 +2421,7 @@ void KMMainWidget::slotMsgPopup(KMMessage&, const KURL &aUrl, const TQPoint& aPo if ( mFolder->isTemplates() ) { mUseAction->plug( menu ); } else { - - if ( !mFolder->isSent() ) - mMsgActions->replyMenu()->plug( menu ); + mMsgActions->replyMenu()->plug( menu ); mForwardActionMenu->plug( menu ); } editAction()->plug(menu); @@ -2790,6 +2813,10 @@ void KMMainWidget::setupActions() mRemoveFolderAction = new KAction( "foo" /*set in updateFolderMenu*/, "editdelete", 0, this, TQT_SLOT(slotRemoveFolder()), actionCollection(), "delete_folder" ); + mArchiveFolderAction = new KAction( i18n( "&Archive Folder..." ), "filesave", 0, this, + TQT_SLOT( slotArchiveFolder() ), actionCollection(), + "archive_folder" ); + mPreferHtmlAction = new KToggleAction( i18n("Prefer &HTML to Plain Text"), 0, this, TQT_SLOT(slotOverrideHtml()), actionCollection(), "prefer_html" ); @@ -2862,22 +2889,7 @@ void KMMainWidget::setupActions() "message_forward_redirect" ); - if ( GlobalSettings::self()->forwardingInlineByDefault() ) { - mForwardActionMenu->insert( mForwardInlineAction ); - mForwardActionMenu->insert( mForwardAttachedAction ); - mForwardInlineAction->setShortcut( Key_F ); - mForwardAttachedAction->setShortcut( SHIFT+Key_F ); - connect( mForwardActionMenu, TQT_SIGNAL(activated()), this, - TQT_SLOT(slotForwardInlineMsg()) ); - - } else { - mForwardActionMenu->insert( mForwardAttachedAction ); - mForwardActionMenu->insert( mForwardInlineAction ); - mForwardInlineAction->setShortcut( SHIFT+Key_F ); - mForwardAttachedAction->setShortcut( Key_F ); - connect( mForwardActionMenu, TQT_SIGNAL(activated()), this, - TQT_SLOT(slotForwardAttachedMsg()) ); - } + setupForwardActions(); mForwardActionMenu->insert( mForwardDigestAction ); mForwardActionMenu->insert( mRedirectAction ); @@ -3114,6 +3126,13 @@ void KMMainWidget::setupActions() actionCollection(), "go_next_unread_text" ); //----- Settings Menu + mToggleShowQuickSearchAction = new KToggleAction(i18n("Show Quick Search"), TQString::null, + 0, this, TQT_SLOT(slotToggleShowQuickSearch()), + actionCollection(), "show_quick_search"); + mToggleShowQuickSearchAction->setChecked( GlobalSettings::self()->quickSearchActive() ); + mToggleShowQuickSearchAction->setWhatsThis( + i18n( GlobalSettings::self()->quickSearchActiveItem()->whatsThis().utf8() ) ); + (void) new KAction( i18n("Configure &Filters..."), 0, this, TQT_SLOT(slotFilter()), actionCollection(), "filter" ); (void) new KAction( i18n("Configure &POP Filters..."), 0, this, @@ -3337,8 +3356,8 @@ void KMMainWidget::updateMessageActions() mMarkThreadAsUnreadAction->setEnabled( thread_actions ); mToggleThreadTodoAction->setEnabled( thread_actions && flags_available ); mToggleThreadFlagAction->setEnabled( thread_actions && flags_available ); - mTrashThreadAction->setEnabled( thread_actions && !mFolder->isReadOnly() ); - mDeleteThreadAction->setEnabled( thread_actions && !mFolder->isReadOnly() ); + mTrashThreadAction->setEnabled( thread_actions && mFolder->canDeleteMessages() ); + mDeleteThreadAction->setEnabled( thread_actions && mFolder->canDeleteMessages() ); if (mFolder && mHeaders && mHeaders->currentMsg()) { if (thread_actions) { @@ -3349,29 +3368,30 @@ void KMMainWidget::updateMessageActions() } } - mMoveActionMenu->setEnabled( mass_actions && !mFolder->isReadOnly() ); + mMoveActionMenu->setEnabled( mass_actions && mFolder->canDeleteMessages() ); + mMoveMsgToFolderAction->setEnabled( mass_actions && mFolder->canDeleteMessages() ); mCopyActionMenu->setEnabled( mass_actions ); - mTrashAction->setEnabled( mass_actions && !mFolder->isReadOnly() ); - mDeleteAction->setEnabled( mass_actions && !mFolder->isReadOnly() ); - mFindInMessageAction->setEnabled( mass_actions ); - mForwardInlineAction->setEnabled( mass_actions ); - mForwardAttachedAction->setEnabled( mass_actions ); - mForwardDigestAction->setEnabled( count > 1 || parent_thread ); + mTrashAction->setEnabled( mass_actions && mFolder->canDeleteMessages() ); + mDeleteAction->setEnabled( mass_actions && mFolder->canDeleteMessages() ); + mFindInMessageAction->setEnabled( mass_actions && !kmkernel->folderIsTemplates( mFolder ) ); + mForwardInlineAction->setEnabled( mass_actions && !kmkernel->folderIsTemplates( mFolder )); + mForwardAttachedAction->setEnabled( mass_actions && !kmkernel->folderIsTemplates( mFolder ) ); + mForwardDigestAction->setEnabled( ( count > 1 || parent_thread ) && !kmkernel->folderIsTemplates( mFolder ) ); - forwardMenu()->setEnabled( mass_actions ); + forwardMenu()->setEnabled( mass_actions && !kmkernel->folderIsTemplates( mFolder )); bool single_actions = count == 1; mUseAction->setEnabled( single_actions && kmkernel->folderIsTemplates( mFolder ) ); filterMenu()->setEnabled( single_actions ); - redirectAction()->setEnabled( single_actions ); + redirectAction()->setEnabled( single_actions && !kmkernel->folderIsTemplates( mFolder ) ); printAction()->setEnabled( single_actions ); viewSourceAction()->setEnabled( single_actions ); mSendAgainAction->setEnabled( single_actions - && ( mHeaders->currentMsg() && mHeaders->currentMsg()->isSent() ) - || ( mFolder && mHeaders->currentMsg() && - kmkernel->folderIsSentMailFolder( mFolder ) ) ); + && ( ( mHeaders->currentMsg() && mHeaders->currentMsg()->isSent() ) + || ( mFolder && mHeaders->currentMsg() && + kmkernel->folderIsSentMailFolder( mFolder ) ) ) ); mSaveAsAction->setEnabled( mass_actions ); bool mails = mFolder && mFolder->count(); bool enable_goto_unread = mails @@ -3426,13 +3446,27 @@ void KMMainWidget::updateFolderMenu() || ( cachedImap && knownImapPath ) ) && !multiFolder ); if ( mTroubleshootFolderAction ) mTroubleshootFolderAction->setEnabled( folderWithContent && ( cachedImap && knownImapPath ) && !multiFolder ); - mEmptyFolderAction->setEnabled( folderWithContent && ( mFolder->count() > 0 ) && !mFolder->isReadOnly() && !multiFolder ); - mEmptyFolderAction->setText( (mFolder && kmkernel->folderIsTrash(mFolder)) - ? i18n("E&mpty Trash") : i18n("&Move All Messages to Trash") ); - mRemoveFolderAction->setEnabled( mFolder && !mFolder->isSystemFolder() && !mFolder->isReadOnly() && !multiFolder); - mRemoveFolderAction->setText( mFolder && mFolder->folderType() == KMFolderTypeSearch - ? i18n("&Delete Search") : i18n("&Delete Folder") ); - mExpireFolderAction->setEnabled( mFolder && mFolder->isAutoExpire() && !multiFolder ); + + mEmptyFolderAction->setEnabled( folderWithContent && + ( mFolder->count() > 0 ) && mFolder->canDeleteMessages() && + !multiFolder ); + mEmptyFolderAction->setText( ( mFolder && kmkernel->folderIsTrash( mFolder ) ) ? + i18n( "E&mpty Trash" ) : + i18n( "&Move All Messages to Trash" ) ); + + mRemoveFolderAction->setEnabled( mFolder && + !mFolder->isSystemFolder() && + mFolder->canDeleteMessages() && + !multiFolder && !mFolder->noContent() && + !mFolder->mailCheckInProgress() ); + mRemoveFolderAction->setText( mFolder && + mFolder->folderType() == KMFolderTypeSearch ? + i18n( "&Delete Search" ) : + i18n( "&Delete Folder" ) ); + + if ( mArchiveFolderAction ) + mArchiveFolderAction->setEnabled( mFolder && !multiFolder ); + mExpireFolderAction->setEnabled( mFolder && mFolder->isAutoExpire() && !multiFolder && mFolder->canDeleteMessages() ); updateMarkAsReadAction(); // the visual ones only make sense if we are showing a message list mPreferHtmlAction->setEnabled( mHeaders->folder() ? true : false ); @@ -3447,8 +3481,8 @@ void KMMainWidget::updateFolderMenu() mHeaders->folder() ? ( mThreadMessagesAction->isChecked()) : false ); mThreadBySubjectAction->setChecked( mFolderThreadSubjPref ); - mNewFolderAction->setEnabled( !multiFolder ); - mRemoveDuplicatesAction->setEnabled( !multiFolder ); + mNewFolderAction->setEnabled( !multiFolder && ( mFolder && mFolder->folderType() != KMFolderTypeSearch )); + mRemoveDuplicatesAction->setEnabled( !multiFolder && mFolder && mFolder->canDeleteMessages() ); mFolderShortCutCommandAction->setEnabled( !multiFolder ); } @@ -3849,6 +3883,7 @@ void KMMainWidget::slotFolderTreeColumnsChanged() mTotalColumnToggle->setChecked( mFolderTree->isTotalActive() ); mUnreadColumnToggle->setChecked( mFolderTree->isUnreadActive() ); mSizeColumnToggle->setChecked( mFolderTree->isSizeActive() ); + mUnreadTextToggle->setChecked( !mFolderTree->isUnreadActive() ); } void KMMainWidget::toggleSystemTray() @@ -3896,6 +3931,7 @@ void KMMainWidget::updateFileMenu() actionCollection()->action("check_mail")->setEnabled( actList.size() > 0 ); actionCollection()->action("check_mail_in")->setEnabled( actList.size() > 0 ); + actionCollection()->action("favorite_check_mail")->setEnabled( actList.size() > 0 ); } @@ -3934,17 +3970,17 @@ void KMMainWidget::setupFolderView() { if ( GlobalSettings::self()->enableFavoriteFolderView() ) { mFolderView = mFolderViewSplitter; - mSearchAndTree->reparent( mFolderViewSplitter, 0, TQPoint( 0, 0 ) ); + mFolderTree->reparent( mFolderViewSplitter, 0, TQPoint( 0, 0 ) ); mFolderViewSplitter->show(); mFavoriteFolderView->show(); } else { - mFolderView = mSearchAndTree; + mFolderView = mFolderTree; mFolderViewSplitter->hide(); mFavoriteFolderView->hide(); } mFolderView->reparent( mFolderViewParent, 0, TQPoint( 0, 0 ) ); mFolderViewParent->moveToFirst( mFolderView ); - mSearchAndTree->show(); + mFolderTree->show(); } diff --git a/kmail/kmmainwidget.h b/kmail/kmmainwidget.h index 74baaf2a3..20e19a3c6 100644 --- a/kmail/kmmainwidget.h +++ b/kmail/kmmainwidget.h @@ -201,7 +201,12 @@ public slots: /** Select the folder and jump to the next unread msg */ void folderSelectedUnread( KMFolder* ); - void slotMsgSelected(KMMessage*); + void slotMsgSelected( KMMessage * ); + /** + Open a separate viewer window containing the specified message. + */ + void slotMsgActivated( KMMessage * ); + void slotMsgChanged(); /** Change the current folder, select a message in the current folder */ @@ -283,6 +288,7 @@ protected slots: void slotExpireAll(); void slotInvalidateIMAPFolders(); void slotMarkAllAsRead(); + void slotArchiveFolder(); void slotRemoveFolder(); void slotEmptyFolder(); void slotCompactFolder(); @@ -360,7 +366,6 @@ protected slots: /** etc. */ void slotDisplayCurrentMessage(); - void slotMsgActivated( KMMessage* ); void slotShowNewFromTemplate(); void slotNewFromTemplate( int ); @@ -378,6 +383,9 @@ protected slots: * often the the other updates and is therefor in its own method. */ void updateMarkAsReadAction(); + /** Settings menu */ + void slotToggleShowQuickSearch(); + /** XML-GUI stuff */ void slotEditNotifications(); void slotEditKeys(); @@ -427,6 +435,13 @@ private: */ TQString findCurrentImapPath(); + /** + * This function adds or updates the actions of the forward action menu, taking the + * preference whether to forward inline or as attachment by default into account. + * This has to be called when that preference config has been changed. + */ + void setupForwardActions(); + void setupFolderView(); private slots: @@ -439,7 +454,8 @@ private: *mDeleteThreadAction, *mSaveAsAction, *mUseAction, *mSendAgainAction, *mApplyAllFiltersAction, *mFindInMessageAction, *mSaveAttachmentsAction, *mOpenAction, *mViewSourceAction, - *mFavoritesCheckMailAction; + *mFavoritesCheckMailAction, + *mMoveMsgToFolderAction; // Composition actions KAction *mPrintAction, *mForwardInlineAction, *mForwardAttachedAction, *mForwardDigestAction, @@ -476,8 +492,8 @@ private: KToggleAction* mTotalColumnToggle; KToggleAction* mSizeColumnToggle; - TQVBox *mSearchAndTree; - TQHBox *mFolderQuickSearch; + KToggleAction *mToggleShowQuickSearchAction; + KMFolderTree *mFolderTree; KMail::FavoriteFolderView *mFavoriteFolderView; TQWidget *mFolderView; @@ -513,8 +529,6 @@ private: mFolderHtmlPref, mFolderHtmlLoadExtPref, mFolderThreadPref, mFolderThreadSubjPref, mReaderWindowActive, mReaderWindowBelow; bool mEnableFavoriteFolderView; - bool mEnableFolderQuickSearch; - bool mEnableQuickSearch; // TQPopupMenu *mMessageMenu; KMail::SearchWindow *mSearchWin; @@ -523,7 +537,7 @@ private: *mCompactFolderAction, *mRefreshFolderAction, *mEmptyFolderAction, *mMarkAllAsReadAction, *mFolderMailingListPropertiesAction, *mFolderShortCutCommandAction, *mTroubleshootFolderAction, - *mRemoveDuplicatesAction; + *mRemoveDuplicatesAction, *mArchiveFolderAction; KToggleAction *mPreferHtmlAction, *mPreferHtmlLoadExtAction, *mThreadMessagesAction; KToggleAction *mThreadBySubjectAction; KToggleAction *mFolderAction, *mHeaderAction, *mMimeAction; diff --git a/kmail/kmmainwin.cpp b/kmail/kmmainwin.cpp index 7d69e2ee2..994219b0f 100644 --- a/kmail/kmmainwin.cpp +++ b/kmail/kmmainwin.cpp @@ -40,7 +40,7 @@ KMMainWin::KMMainWin(TQWidget *) actionCollection(), "new_mail_client" ); mKMMainWidget = new KMMainWidget( this, "KMMainWidget", this, actionCollection() ); - mKMMainWidget->resize( 725, 700 ); + mKMMainWidget->resize( 450, 600 ); setCentralWidget(mKMMainWidget); setupStatusBar(); if (kmkernel->xmlGuiInstance()) diff --git a/kmail/kmmainwin.rc b/kmail/kmmainwin.rc index 07b9f6c5b..955dabad2 100644 --- a/kmail/kmmainwin.rc +++ b/kmail/kmmainwin.rc @@ -2,7 +2,7 @@ the same menu entries at the same place in KMail and Kontact --> - +

&File @@ -103,6 +103,7 @@ + diff --git a/kmail/kmmessage.cpp b/kmail/kmmessage.cpp index 5a70afdb1..6819d68d9 100644 --- a/kmail/kmmessage.cpp +++ b/kmail/kmmessage.cpp @@ -415,7 +415,9 @@ void KMMessage::fromDwString(const DwString& str, bool aSetStatus) setSignatureStateChar( headerField("X-KMail-SignatureState").at(0) ); setMDNSentState( static_cast( headerField("X-KMail-MDN-Sent").at(0).latin1() ) ); } - if (attachmentState() == KMMsgAttachmentUnknown && readyToShow()) + if ( invitationState() == KMMsgInvitationUnknown && readyToShow() ) + updateInvitationState(); + if ( attachmentState() == KMMsgAttachmentUnknown && readyToShow() ) updateAttachmentState(); mNeedsAssembly = false; @@ -720,11 +722,6 @@ void KMMessage::parseTextStringFromDwPart( partNode * root, if ( !root ) return; isHTML = false; - // initialy parse the complete message to decrypt any encrypted parts - { - ObjectTreeParser otp( 0, 0, true, false, true ); - otp.parseObjectTree( root ); - } partNode * curNode = root->findType( DwMime::kTypeText, DwMime::kSubtypeUnknown, true, @@ -743,15 +740,18 @@ void KMMessage::parseTextStringFromDwPart( partNode * root, //----------------------------------------------------------------------------- -TQString KMMessage::asPlainText( bool aStripSignature, bool allowDecryption ) const { +TQString KMMessage::asPlainTextFromObjectTree( partNode *root, bool aStripSignature, + bool allowDecryption ) const +{ + Q_ASSERT( root ); + Q_ASSERT( root->processed() ); + TQCString parsedString; bool isHTML = false; const TQTextCodec * codec = 0; - partNode * root = partNode::fromMessage( this ); if ( !root ) return TQString::null; parseTextStringFromDwPart( root, parsedString, codec, isHTML ); - delete root; if ( mOverrideCodec || !codec ) codec = this->codec(); @@ -767,27 +767,27 @@ TQString KMMessage::asPlainText( bool aStripSignature, bool allowDecryption ) co TQPtrList pgpBlocks; TQStrList nonPgpBlocks; if ( Kpgp::Module::prepareMessageForDecryption( parsedString, - pgpBlocks, - nonPgpBlocks ) ) { + pgpBlocks, + nonPgpBlocks ) ) { // Only decrypt/strip off the signature if there is only one OpenPGP // block in the message if ( pgpBlocks.count() == 1 ) { - Kpgp::Block * block = pgpBlocks.first(); - if ( block->type() == Kpgp::PgpMessageBlock || - block->type() == Kpgp::ClearsignedBlock ) { - if ( block->type() == Kpgp::PgpMessageBlock ) { - // try to decrypt this OpenPGP block - block->decrypt(); - } else { - // strip off the signature - block->verify(); - clearSigned = true; - } + Kpgp::Block * block = pgpBlocks.first(); + if ( block->type() == Kpgp::PgpMessageBlock || + block->type() == Kpgp::ClearsignedBlock ) { + if ( block->type() == Kpgp::PgpMessageBlock ) { + // try to decrypt this OpenPGP block + block->decrypt(); + } else { + // strip off the signature + block->verify(); + clearSigned = true; + } - result = codec->toUnicode( nonPgpBlocks.first() ) - + codec->toUnicode( block->text() ) - + codec->toUnicode( nonPgpBlocks.last() ); - } + result = codec->toUnicode( nonPgpBlocks.first() ) + + codec->toUnicode( block->text() ) + + codec->toUnicode( nonPgpBlocks.last() ); + } } } } @@ -820,6 +820,21 @@ TQString KMMessage::asPlainText( bool aStripSignature, bool allowDecryption ) co return result; } +//----------------------------------------------------------------------------- + +TQString KMMessage::asPlainText( bool aStripSignature, bool allowDecryption ) const +{ + partNode *root = partNode::fromMessage( this ); + if ( !root ) + return TQString::null; + + ObjectTreeParser otp; + otp.parseObjectTree( root ); + TQString result = asPlainTextFromObjectTree( root, aStripSignature, allowDecryption ); + delete root; + return result; +} + TQString KMMessage::asQuotedString( const TQString& aHeaderStr, const TQString& aIndentStr, const TQString& selection /* = TQString::null */, @@ -852,11 +867,10 @@ KMMessage* KMMessage::createReply( KMail::ReplyStrategy replyStrategy, TQString selection /* = TQString::null */, bool noQuote /* = false */, bool allowDecryption /* = true */, - bool selectionIsBody /* = false */, const TQString &tmpl /* = TQString::null */ ) { KMMessage* msg = new KMMessage; - TQString str, replyStr, mailingListStr, replyToStr, toStr; + TQString mailingListStr, replyToStr, toStr; TQStringList mailingListAddresses; TQCString refStr, headerName; bool replyAll = true; @@ -881,8 +895,6 @@ KMMessage* KMMessage::createReply( KMail::ReplyStrategy replyStrategy, } // use the "On ... Joe User wrote:" header by default - replyStr = sReplyAllStr; - switch( replyStrategy ) { case KMail::ReplySmart : { if ( !headerField( "Mail-Followup-To" ).isEmpty() ) { @@ -898,7 +910,7 @@ KMMessage* KMMessage::createReply( KMail::ReplyStrategy replyStrategy, else { // doesn't seem to be a mailing list, reply to From: address toStr = from(); - replyStr = sReplyStr; // reply to author, so use "On ... you wrote:" + //replyStr = sReplyStr; // reply to author, so use "On ... you wrote:" replyAll = false; } // strip all my addresses from the list of recipients @@ -1028,7 +1040,6 @@ KMMessage* KMMessage::createReply( KMail::ReplyStrategy replyStrategy, else if ( !from().isEmpty() ) { toStr = from(); } - replyStr = sReplyStr; // reply to author, so use "On ... you wrote:" replyAll = false; break; } @@ -1056,15 +1067,20 @@ KMMessage* KMMessage::createReply( KMail::ReplyStrategy replyStrategy, // } msg->setSubject( replySubject() ); - - TemplateParser parser( msg, (replyAll ? TemplateParser::ReplyAll : TemplateParser::Reply), - selection, sSmartQuote, noQuote, allowDecryption, selectionIsBody ); - if ( !tmpl.isEmpty() ) { - parser.process( tmpl, this ); - } else { - parser.process( this ); + msg->setHeaderField( "X-KMail-QuotePrefix", + formatString( GlobalSettings::self()->quoteString() ) ); + if( !noQuote ) { + TemplateParser parser( msg, ( replyAll ? TemplateParser::ReplyAll : TemplateParser::Reply ) ); + parser.setAllowDecryption( allowDecryption ); + if ( GlobalSettings::quoteSelectionOnly() ) { + parser.setSelection( selection ); + } + if ( !tmpl.isEmpty() ) { + parser.process( tmpl, this ); + } else { + parser.process( this ); + } } - // setStatus(KMMsgStatusReplied); msg->link(this, KMMsgStatusReplied); @@ -1127,12 +1143,12 @@ KMMessage* KMMessage::createRedirect( const TQString &toStr ) TQString strByWayOf = TQString("%1 (by way of %2 <%3>)") .arg( from() ) .arg( ident.fullName() ) - .arg( ident.emailAddr() ); + .arg( ident.primaryEmailAddress() ); // Resent-From: content TQString strFrom = TQString("%1 <%2>") .arg( ident.fullName() ) - .arg( ident.emailAddr() ); + .arg( ident.primaryEmailAddress() ); // format the current date to be used in Resent-Date: TQString origDate = msg->headerField( "Date" ); @@ -1211,7 +1227,6 @@ void KMMessage::sanitizeHeaders( const TQStringList& whiteList ) KMMessage* KMMessage::createForward( const TQString &tmpl /* = TQString::null */ ) { KMMessage* msg = new KMMessage(); - TQString id; // If this is a multipart mail or if the main part is only the text part, // Make an identical copy of the mail, minus headers, so attachments are @@ -1222,8 +1237,7 @@ KMMessage* KMMessage::createForward( const TQString &tmpl /* = TQString::null */ msg->fromDwString( this->asDwString() ); // remember the type and subtype, initFromMessage sets the contents type to // text/plain, via initHeader, for unclear reasons - const int type = msg->type(); - const int subtype = msg->subtype(); + DwMediaType oldContentType = msg->mMsg->Headers().ContentType(); msg->sanitizeHeaders(); @@ -1240,12 +1254,14 @@ KMMessage* KMMessage::createForward( const TQString &tmpl /* = TQString::null */ } } msg->mMsg->Assemble(); - msg->initFromMessage( this ); + //restore type - msg->setType( type ); - msg->setSubtype( subtype ); - } else if( type() == DwMime::kTypeText && subtype() == DwMime::kSubtypeHtml ) { + msg->mMsg->Headers().ContentType().FromString( oldContentType.AsString() ); + msg->mMsg->Headers().ContentType().Parse(); + msg->mMsg->Assemble(); + } + else if( type() == DwMime::kTypeText && subtype() == DwMime::kSubtypeHtml ) { // This is non-multipart html mail. Let`s make it text/plain and allow // template parser do the hard job. msg->initFromMessage( this ); @@ -1253,7 +1269,8 @@ KMMessage* KMMessage::createForward( const TQString &tmpl /* = TQString::null */ msg->setSubtype( DwMime::kSubtypeHtml ); msg->mNeedsAssembly = true; msg->cleanupHeader(); - } else { + } + else { // This is a non-multipart, non-text mail (e.g. text/calendar). Construct // a multipart/mixed mail and add the original body as an attachment. msg->initFromMessage( this ); @@ -1287,9 +1304,7 @@ KMMessage* KMMessage::createForward( const TQString &tmpl /* = TQString::null */ msg->setSubject( forwardSubject() ); - TemplateParser parser( msg, TemplateParser::Forward, - asPlainText( false, false ), - false, false, false, false); + TemplateParser parser( msg, TemplateParser::Forward ); if ( !tmpl.isEmpty() ) { parser.process( tmpl, this ); } else { @@ -1351,25 +1366,27 @@ static const int numMdnMessageBoxes static int requestAdviceOnMDN( const char * what ) { - for ( int i = 0 ; i < numMdnMessageBoxes ; ++i ) - if ( !qstrcmp( what, mdnMessageBoxes[i].dontAskAgainID ) ) + for ( int i = 0 ; i < numMdnMessageBoxes ; ++i ) { + if ( !qstrcmp( what, mdnMessageBoxes[i].dontAskAgainID ) ) { if ( mdnMessageBoxes[i].canDeny ) { - const KCursorSaver saver( TQCursor::ArrowCursor ); - int answer = TQMessageBox::information( 0, - i18n("Message Disposition Notification Request"), - i18n( mdnMessageBoxes[i].text ), - i18n("&Ignore"), i18n("Send \"&denied\""), i18n("&Send") ); - return answer ? answer + 1 : 0 ; // map to "mode" in createMDN + const KCursorSaver saver( TQCursor::ArrowCursor ); + int answer = TQMessageBox::information( 0, + i18n("Message Disposition Notification Request"), + i18n( mdnMessageBoxes[i].text ), + i18n("&Ignore"), i18n("Send \"&denied\""), i18n("&Send") ); + return answer ? answer + 1 : 0 ; // map to "mode" in createMDN } else { - const KCursorSaver saver( TQCursor::ArrowCursor ); - int answer = TQMessageBox::information( 0, - i18n("Message Disposition Notification Request"), - i18n( mdnMessageBoxes[i].text ), - i18n("&Ignore"), i18n("&Send") ); - return answer ? answer + 2 : 0 ; // map to "mode" in createMDN + const KCursorSaver saver( TQCursor::ArrowCursor ); + int answer = TQMessageBox::information( 0, + i18n("Message Disposition Notification Request"), + i18n( mdnMessageBoxes[i].text ), + i18n("&Ignore"), i18n("&Send") ); + return answer ? answer + 2 : 0 ; // map to "mode" in createMDN } + } + } kdWarning(5006) << "didn't find data for message box \"" - << what << "\"" << endl; + << what << "\"" << endl; return 0; } @@ -2487,28 +2504,38 @@ TQCString KMMessage::contentTransferEncodingStr() const //----------------------------------------------------------------------------- -int KMMessage::contentTransferEncoding() const +int KMMessage::contentTransferEncoding( DwEntity *entity ) const { - DwHeaders& header = mMsg->Headers(); - if (header.HasContentTransferEncoding()) + if ( !entity ) + entity = mMsg; + + DwHeaders& header = entity->Headers(); + if ( header.HasContentTransferEncoding() ) return header.ContentTransferEncoding().AsEnum(); else return DwMime::kCteNull; } //----------------------------------------------------------------------------- -void KMMessage::setContentTransferEncodingStr(const TQCString& aStr) +void KMMessage::setContentTransferEncodingStr( const TQCString& cteString, + DwEntity *entity ) { - mMsg->Headers().ContentTransferEncoding().FromString(aStr); - mMsg->Headers().ContentTransferEncoding().Parse(); + if ( !entity ) + entity = mMsg; + + entity->Headers().ContentTransferEncoding().FromString( cteString ); + entity->Headers().ContentTransferEncoding().Parse(); mNeedsAssembly = true; } //----------------------------------------------------------------------------- -void KMMessage::setContentTransferEncoding(int aCte) +void KMMessage::setContentTransferEncoding( int cte, DwEntity *entity ) { - mMsg->Headers().ContentTransferEncoding().FromEnum(aCte); + if ( !entity ) + entity = mMsg; + + entity->Headers().ContentTransferEncoding().FromEnum( cte ); mNeedsAssembly = true; } @@ -2526,6 +2553,16 @@ void KMMessage::setNeedsAssembly() mNeedsAssembly = true; } +//----------------------------------------------------------------------------- +void KMMessage::assembleIfNeeded() +{ + Q_ASSERT( mMsg ); + + if ( mNeedsAssembly ) { + mMsg->Assemble(); + mNeedsAssembly = false; + } +} //----------------------------------------------------------------------------- TQCString KMMessage::body() const @@ -2646,22 +2683,16 @@ TQValueList KMMessage::determineAllowedCtes( const CharFreq& cf, void KMMessage::setBodyAndGuessCte( const TQByteArray& aBuf, TQValueList & allowedCte, bool allow8Bit, - bool willBeSigned ) + bool willBeSigned, + DwEntity *entity ) { - CharFreq cf( aBuf ); // it's safe to pass null arrays + if ( !entity ) + entity = mMsg; + CharFreq cf( aBuf ); // it's safe to pass null arrays allowedCte = determineAllowedCtes( cf, allow8Bit, willBeSigned ); - -#ifndef NDEBUG - DwString dwCte; - DwCteEnumToStr(allowedCte[0], dwCte); - kdDebug(5006) << "CharFreq returned " << cf.type() << "/" - << cf.printableRatio() << " and I chose " - << dwCte.c_str() << endl; -#endif - - setCte( allowedCte[0] ); // choose best fitting - setBodyEncodedBinary( aBuf ); + setCte( allowedCte[0], entity ); // choose best fitting + setBodyEncodedBinary( aBuf, entity ); } @@ -2669,32 +2700,29 @@ void KMMessage::setBodyAndGuessCte( const TQByteArray& aBuf, void KMMessage::setBodyAndGuessCte( const TQCString& aBuf, TQValueList & allowedCte, bool allow8Bit, - bool willBeSigned ) + bool willBeSigned, + DwEntity *entity ) { - CharFreq cf( aBuf.data(), aBuf.size()-1 ); // it's safe to pass null strings + if ( !entity ) + entity = mMsg; + CharFreq cf( aBuf.data(), aBuf.size()-1 ); // it's safe to pass null strings allowedCte = determineAllowedCtes( cf, allow8Bit, willBeSigned ); - -#ifndef NDEBUG - DwString dwCte; - DwCteEnumToStr(allowedCte[0], dwCte); - kdDebug(5006) << "CharFreq returned " << cf.type() << "/" - << cf.printableRatio() << " and I chose " - << dwCte.c_str() << endl; -#endif - - setCte( allowedCte[0] ); // choose best fitting - setBodyEncoded( aBuf ); + setCte( allowedCte[0], entity ); // choose best fitting + setBodyEncoded( aBuf, entity ); } //----------------------------------------------------------------------------- -void KMMessage::setBodyEncoded(const TQCString& aStr) +void KMMessage::setBodyEncoded(const TQCString& aStr, DwEntity *entity ) { + if ( !entity ) + entity = mMsg; + DwString dwSrc(aStr.data(), aStr.size()-1 /* not the trailing NUL */); DwString dwResult; - switch (cte()) + switch (cte( entity )) { case DwMime::kCteBase64: DwEncodeBase64(dwSrc, dwResult); @@ -2707,30 +2735,35 @@ void KMMessage::setBodyEncoded(const TQCString& aStr) break; } - mMsg->Body().FromString(dwResult); + entity->Body().FromString(dwResult); mNeedsAssembly = true; } //----------------------------------------------------------------------------- -void KMMessage::setBodyEncodedBinary(const TQByteArray& aStr) +void KMMessage::setBodyEncodedBinary( const TQByteArray& aStr, DwEntity *entity ) { + if ( !entity ) + entity = mMsg; + DwString dwSrc(aStr.data(), aStr.size()); DwString dwResult; - switch (cte()) + switch ( cte( entity ) ) { case DwMime::kCteBase64: - DwEncodeBase64(dwSrc, dwResult); + DwEncodeBase64( dwSrc, dwResult ); break; case DwMime::kCteQuotedPrintable: - DwEncodeQuotedPrintable(dwSrc, dwResult); + DwEncodeQuotedPrintable( dwSrc, dwResult ); break; default: dwResult = dwSrc; break; } - mMsg->Body().FromString(dwResult); + entity->Body().FromString( dwResult ); + entity->Body().Parse(); + mNeedsAssembly = true; } @@ -2752,6 +2785,7 @@ void KMMessage::setBody(const char* aStr) mNeedsAssembly = true; } +//----------------------------------------------------------------------------- void KMMessage::setMultiPartBody( const TQCString & aStr ) { setBody( aStr ); mMsg->Body().Parse(); @@ -3053,7 +3087,8 @@ void applyHeadersToMessagePart( DwHeaders& headers, KMMessagePart* aPart ) // Content-description if (headers.HasContentDescription()) - aPart->setContentDescription(headers.ContentDescription().AsString().c_str()); + aPart->setContentDescription( KMMsgBase::decodeRFC2047String( + headers.ContentDescription().AsString().c_str() ) ); else aPart->setContentDescription(""); @@ -3136,6 +3171,44 @@ void KMMessage::deleteBodyParts() mMsg->Body().DeleteBodyParts(); } +//----------------------------------------------------------------------------- + +bool KMMessage::deleteBodyPart( int partIndex ) +{ + KMMessagePart part; + DwBodyPart *dwpart = findPart( partIndex ); + if ( !dwpart ) + return false; + KMMessage::bodyPart( dwpart, &part, true ); + if ( !part.isComplete() ) + return false; + + DwBody *parentNode = dynamic_cast( dwpart->Parent() ); + if ( !parentNode ) + return false; + parentNode->RemoveBodyPart( dwpart ); + + // add dummy part to show that a attachment has been deleted + KMMessagePart dummyPart; + dummyPart.duplicate( part ); + TQString comment = i18n("This attachment has been deleted."); + if ( !part.fileName().isEmpty() ) + comment = i18n( "The attachment '%1' has been deleted." ).arg( part.fileName() ); + dummyPart.setContentDescription( comment ); + dummyPart.setBodyEncodedBinary( TQByteArray() ); + TQCString cd = dummyPart.contentDisposition(); + if ( cd.find( "inline", 0, false ) == 0 ) { + cd.replace( 0, 10, "attachment" ); + dummyPart.setContentDisposition( cd ); + } else if ( cd.isEmpty() ) { + dummyPart.setContentDisposition( "attachment" ); + } + DwBodyPart* newDwPart = createDWBodyPart( &dummyPart ); + parentNode->AddBodyPart( newDwPart ); + getTopLevelPart()->Assemble(); + return true; +} + //----------------------------------------------------------------------------- DwBodyPart* KMMessage::createDWBodyPart(const KMMessagePart* aPart) { @@ -3150,9 +3223,7 @@ DwBodyPart* KMMessage::createDWBodyPart(const KMMessagePart* aPart) TQCString cte = aPart->cteStr(); TQCString contDesc = aPart->contentDescriptionEncoded(); TQCString contDisp = aPart->contentDisposition(); - TQCString encoding = autoDetectCharset(charset, sPrefCharsets, aPart->name()); - if (encoding.isEmpty()) encoding = "utf-8"; - TQCString name = KMMsgBase::encodeRFC2231String(aPart->name(), encoding); + TQCString name = KMMsgBase::encodeRFC2231StringAutoDetectCharset( aPart->name(), charset ); bool RFC2231encoded = aPart->name() != TQString(name); TQCString paramAttr = aPart->parameterAttribute(); @@ -3229,12 +3300,8 @@ DwBodyPart* KMMessage::createDWBodyPart(const KMMessagePart* aPart) if (!paramAttr.isEmpty()) { - TQCString encoding = autoDetectCharset(charset, sPrefCharsets, - aPart->parameterValue()); - if (encoding.isEmpty()) encoding = "utf-8"; TQCString paramValue; - paramValue = KMMsgBase::encodeRFC2231String(aPart->parameterValue(), - encoding); + paramValue = KMMsgBase::encodeRFC2231StringAutoDetectCharset( aPart->parameterValue(), charset ); DwParameter *param = new DwParameter; if (aPart->parameterValue() != TQString(paramValue)) { @@ -3771,24 +3838,41 @@ TQString KMMessage::emailAddrAsAnchor(const TQString& aEmail, bool stripped, con return aEmail; TQStringList addressList = KPIM::splitEmailAddrList( aEmail ); - TQString result; for( TQStringList::ConstIterator it = addressList.begin(); ( it != addressList.end() ); ++it ) { if( !(*it).isEmpty() ) { - TQString address = *it; + + // Extract the name, mail and some pretty versions out of the mail address + TQString name, mail; + KPIM::getNameAndMail( *it, name, mail ); + TQString pretty; + TQString prettyStripped; + if ( name.stripWhiteSpace().isEmpty() ) { + pretty = mail; + prettyStripped = mail; + } else { + pretty = KPIM::quoteNameIfNecessary( name ) + " <" + mail + ">"; + prettyStripped = name; + } + if(aLink) { - result += ""; + result += ""; + } + + if ( stripped ) { + result += KMMessage::quoteHtmlChars( prettyStripped, true ); + } + else { + result += KMMessage::quoteHtmlChars( pretty, true ); } - if( stripped ) - address = KMMessage::stripEmailAddr( address ); - result += KMMessage::quoteHtmlChars( address, true ); + if(aLink) - result += ", "; + result += ", "; } } // cut of the trailing ", " @@ -3800,7 +3884,6 @@ TQString KMMessage::emailAddrAsAnchor(const TQString& aEmail, bool stripped, con return result; } - //----------------------------------------------------------------------------- //static TQStringList KMMessage::stripAddressFromAddressList( const TQString& address, @@ -4023,7 +4106,7 @@ TQCString KMMessage::charset() const } //----------------------------------------------------------------------------- -void KMMessage::setCharset(const TQCString& bStr) +void KMMessage::setCharset( const TQCString &charset, DwEntity *entity ) { kdWarning( type() != DwMime::kTypeText ) << "KMMessage::setCharset(): trying to set a charset for a non-textual mimetype." << endl @@ -4031,23 +4114,32 @@ void KMMessage::setCharset(const TQCString& bStr) << "====================================================================" << endl << kdBacktrace( 5 ) << endl << "====================================================================" << endl; - TQCString aStr = bStr; - KPIM::kAsciiToLower( aStr.data() ); - DwMediaType &mType = dwContentType(); + + if ( !entity ) + entity = mMsg; + + DwMediaType &mType = entity->Headers().ContentType(); mType.Parse(); - DwParameter *param=mType.FirstParameter(); - while(param) + DwParameter *param = mType.FirstParameter(); + while( param ) { + // FIXME use the mimelib functions here for comparison. - if (!kasciistricmp(param->Attribute().c_str(), "charset")) break; - else param=param->Next(); - if (!param){ - param=new DwParameter; - param->SetAttribute("charset"); - mType.AddParameter(param); + if ( !kasciistricmp( param->Attribute().c_str(), "charset" ) ) + break; + + param = param->Next(); + } + if ( !param ) { + param = new DwParameter; + param->SetAttribute( "charset" ); + mType.AddParameter( param ); } else mType.SetModified(); - param->SetValue(DwString(aStr)); + + TQCString lowerCharset = charset; + KPIM::kAsciiToLower( lowerCharset.data() ); + param->SetValue( DwString( lowerCharset ) ); mType.Assemble(); } @@ -4078,7 +4170,8 @@ void KMMessage::setSignatureState(KMMsgSignatureState s, int idx) KMMsgBase::setSignatureState(s, idx); } -void KMMessage::setMDNSentState( KMMsgMDNSentState status, int idx ) { +void KMMessage::setMDNSentState( KMMsgMDNSentState status, int idx ) +{ if ( mMDNSentState == status ) return; if ( status == 0 ) @@ -4247,6 +4340,21 @@ void KMMessage::updateBodyPart(const TQString partSpecifier, const TQByteArray & } } +void KMMessage::updateInvitationState() +{ + if ( mMsg && mMsg->hasHeaders() && mMsg->Headers().HasContentType() ) { + TQString cntType = mMsg->Headers().ContentType().TypeStr().c_str(); + cntType += '/'; + cntType += mMsg->Headers().ContentType().SubtypeStr().c_str(); + if ( cntType.lower() == "text/calendar" ) { + setStatus( KMMsgStatusHasInvitation ); + return; + } + } + setStatus( KMMsgStatusHasNoInvitation ); + return; +} + //----------------------------------------------------------------------------- void KMMessage::updateAttachmentState( DwBodyPart* part ) { @@ -4270,6 +4378,18 @@ void KMMessage::updateAttachmentState( DwBodyPart* part ) filenameEmpty = KMMsgBase::decodeRFC2231String( KMMsgBase::extractRFC2231HeaderField( cd.AsString().c_str(), "filename" ) ).isEmpty(); } } + + // Filename still empty? Check if the content-type has a "name" parameter and try to use that as + // the attachment name + if ( filenameEmpty && part->Headers().HasContentType() ) { + DwMediaType contentType = part->Headers().ContentType(); + filenameEmpty = contentType.Name().empty(); + if ( filenameEmpty ) { + // let's try if it is rfc 2231 encoded which mimelib can't handle + filenameEmpty = KMMsgBase::decodeRFC2231String( KMMsgBase::extractRFC2231HeaderField( + contentType.AsString().c_str(), "name" ) ).isEmpty(); + } + } } if ( part->hasHeaders() && @@ -4312,15 +4432,17 @@ void KMMessage::updateAttachmentState( DwBodyPart* part ) setStatus( KMMsgStatusHasNoAttach ); } -void KMMessage::setBodyFromUnicode( const TQString & str ) { +void KMMessage::setBodyFromUnicode( const TQString &str, DwEntity *entity ) +{ TQCString encoding = KMMsgBase::autoDetectCharset( charset(), KMMessage::preferredCharsets(), str ); if ( encoding.isEmpty() ) encoding = "utf-8"; const TQTextCodec * codec = KMMsgBase::codecForName( encoding ); assert( codec ); TQValueList dummy; - setCharset( encoding ); - setBodyAndGuessCte( codec->fromUnicode( str ), dummy, false /* no 8bit */ ); + setCharset( encoding, entity ); + setBodyAndGuessCte( codec->fromUnicode( str ), dummy, false /* no 8bit */, + false, entity ); } const TQTextCodec * KMMessage::codec() const { @@ -4371,3 +4493,28 @@ void KMMessage::deleteWhenUnused() { sPendingDeletes << this; } + +DwBodyPart* KMMessage::findPart( int index ) +{ + int accu = 0; + return findPartInternal( getTopLevelPart(), index, accu ); +} + +DwBodyPart* KMMessage::findPartInternal(DwEntity * root, int index, int & accu) +{ + accu++; + if ( index < accu ) // should not happen + return 0; + DwBodyPart *current = dynamic_cast( root ); + if ( index == accu ) + return current; + DwBodyPart *rv = 0; + if ( root->Body().FirstBodyPart() ) + rv = findPartInternal( root->Body().FirstBodyPart(), index, accu ); + if ( !rv && current && current->Next() ) + rv = findPartInternal( current->Next(), index, accu ); + if ( !rv && root->Body().Message() ) + rv = findPartInternal( root->Body().Message(), index, accu ); + return rv; +} + diff --git a/kmail/kmmessage.h b/kmail/kmmessage.h index 53c1e4089..958d017e2 100644 --- a/kmail/kmmessage.h +++ b/kmail/kmmessage.h @@ -49,6 +49,7 @@ namespace KMail { class HeaderStrategy; } +class DwEntity; class DwBodyPart; class DwMediaType; class DwHeaders; @@ -161,8 +162,8 @@ public: required header fields with the proper values. The returned message is not stored in any folder. Marks this message as replied. */ KMMessage* createReply( KMail::ReplyStrategy replyStrategy = KMail::ReplySmart, - TQString selection=TQString::null, bool noQuote=false, - bool allowDecryption=true, bool selectionIsBody=false, + TQString selection=TQString::null, bool noQuote = false, + bool allowDecryption = true, const TQString &tmpl = TQString::null ); /** Create a new message that is a redirect to this message, filling all @@ -503,25 +504,46 @@ public: the header via headers() function) */ void setNeedsAssembly(); - /** Get or set the 'Content-Transfer-Encoding' header field - The member functions that involve enumerated types (ints) - will work only for well-known encodings. */ + /** + * Assemble the internal message. This is done automatically in most + * cases, but sometimes still necessary to call this manually. + */ + void assembleIfNeeded(); + + /** + * Get or set the 'Content-Transfer-Encoding' header field + * The member functions that involve enumerated types (ints) + * will work only for well-known encodings. + * Some functions take a DwEntity as second parameter, which + * specifies the body part or message of which the CTE will be changed or + * returned. If this is zero, the toplevel message will be taken. + */ TQCString contentTransferEncodingStr() const; - int contentTransferEncoding() const; - void setContentTransferEncodingStr(const TQCString& aStr); - void setContentTransferEncoding(int aCte); + int contentTransferEncoding( DwEntity *entity = 0 ) const; + void setContentTransferEncodingStr( const TQCString& cteString, DwEntity *entity = 0 ); + void setContentTransferEncoding( int cte, DwEntity *entity = 0 ); - /** Cte is short for ContentTransferEncoding. - These functions are an alternative to the ones with longer names. */ + /** + * Cte is short for ContentTransferEncoding. + * These functions are an alternative to the ones with longer names. + */ TQCString cteStr() const { return contentTransferEncodingStr(); } - int cte() const { return contentTransferEncoding(); } - void setCteStr(const TQCString& aStr) { setContentTransferEncodingStr(aStr); } - void setCte(int aCte) { setContentTransferEncoding(aCte); } + int cte( DwEntity *entity = 0 ) const { return contentTransferEncoding( entity ); } + void setCteStr( const TQCString& aStr, DwEntity *entity = 0 ) { + setContentTransferEncodingStr( aStr, entity ); + } + void setCte( int aCte, DwEntity *entity = 0 ) { + setContentTransferEncoding( aCte, entity ); + } - /** Sets this body part's content to @p str. @p str is subject to - automatic charset and CTE detection. - **/ - void setBodyFromUnicode( const TQString & str ); + /** + * Sets this body's content to @p str. @p str is subject to + * automatic charset and CTE detection. + * + * @param entity The body of this entity will be changed. If entity is 0, + * the body of the whole message will be changed. + */ + void setBodyFromUnicode( const TQString & str, DwEntity *entity = 0 ); /** Returns the body part decoded to unicode. **/ @@ -538,11 +560,17 @@ public: /** Hack to enable structured body parts to be set as flat text... */ void setMultiPartBody( const TQCString & aStr ); - /** Set the message body, encoding it according to the current content - transfer encoding. The first method for null terminated strings, - the second for binary data */ - void setBodyEncoded(const TQCString& aStr); - void setBodyEncodedBinary(const TQByteArray& aStr); + /** + * Set the message body, encoding it according to the current content + * transfer encoding. The first method for null terminated strings, + * the second for binary data. + * + * @param entity Specifies the body part or message of which the body will be + * set. If this is 0, the body of the toplevel message will be + * set. + */ + void setBodyEncoded( const TQCString& aStr, DwEntity *entity = 0 ); + void setBodyEncodedBinary( const TQByteArray& aStr, DwEntity *entity = 0 ); /** Returns a list of content-transfer-encodings that can be used with the given result of the character frequency analysis of a message or @@ -551,23 +579,29 @@ public: bool allow8Bit, bool willBeSigned ); - /** Sets body, encoded in the best fitting - content-transfer-encoding, which is determined by character - frequency count. + /** + * Sets body, encoded in the best fitting + * content-transfer-encoding, which is determined by character + * frequency count. + * + * @param aBuf input buffer + * @param allowedCte return: list of allowed cte's + * @param allow8Bit whether "8bit" is allowed as cte. + * @param willBeSigned whether "7bit"/"8bit" is allowed as cte according to RFC 3156 + * @param entity The body of this message or body part will get changed. + * If this is 0, the body of the toplevel message will be + * set. + */ + void setBodyAndGuessCte( const TQByteArray& aBuf, TQValueList& allowedCte, + bool allow8Bit = false, + bool willBeSigned = false, + DwEntity *entity = 0 ); - @param aBuf input buffer - @param allowedCte return: list of allowed cte's - @param allow8Bit whether "8bit" is allowed as cte. - @param willBeSigned whether "7bit"/"8bit" is allowed as cte according to RFC 3156 - */ - void setBodyAndGuessCte( const TQByteArray& aBuf, - TQValueList& allowedCte, - bool allow8Bit = false, - bool willBeSigned = false ); void setBodyAndGuessCte( const TQCString& aBuf, - TQValueList& allowedCte, - bool allow8Bit = false, - bool willBeSigned = false ); + TQValueList& allowedCte, + bool allow8Bit = false, + bool willBeSigned = false, + DwEntity *entity = 0 ); /** Returns a decoded version of the body from the current content transfer encoding. The first method returns a null terminated string, the second @@ -627,6 +661,12 @@ public: /** Delete all body parts. */ void deleteBodyParts(); + /** + * Delete a body part with the specified part index. + * A dummy body part with the text "the attachment foo was deleted" will replace the old part. + */ + bool deleteBodyPart( int partIndex ); + /** Set "Status" and "X-Status" fields of the message from the * internal message status. */ void setStatusFields(); @@ -725,8 +765,15 @@ public: /** Get the message charset.*/ TQCString charset() const; - /** Set the message charset. */ - void setCharset(const TQCString& aStr); + /** + * Sets the charset of the message or a subpart of the message. + * Only call this when the message or the subpart has a textual mimetype. + * + * @param aStr the MIME-compliant charset name, like 'ISO-88519-15'. + * @param entity the body part or message of which the charset should be changed. + * If this is 0, the charset of the toplevel message will be changed. + */ + void setCharset( const TQCString& charset, DwEntity *entity = 0 ); /** Get a TQTextCodec suitable for this message part */ const TQTextCodec * codec() const; @@ -822,7 +869,8 @@ public: /** Set if the message is ready to be shown */ void setReadyToShow( bool v ) { mReadyToShow = v; } - void updateAttachmentState(DwBodyPart * part = 0); + void updateAttachmentState( DwBodyPart *part = 0 ); + void updateInvitationState(); /** Return, if the message should not be deleted */ bool transferInProgress() const; @@ -860,6 +908,15 @@ public: converting HTML to plain text if necessary. */ TQString asPlainText( bool stripSignature, bool allowDecryption ) const; + /** + * Same as asPlainText(), only that this method expects an already parsed object tree as + * paramter. + * By passing an already parsed objecttree, this allows to share the objecttree and therefore + * reduce the amount of parsing (which can include decrypting, which can include a passphrase dialog) + */ + TQString asPlainTextFromObjectTree( partNode *root, bool stripSignature, + bool allowDecryption ) const; + /** Get stored cursor position */ int getCursorPos() { return mCursorPos; }; /** Set cursor position as offset from message start */ @@ -879,6 +936,8 @@ public: /** Delete this message as soon as it no longer in use. */ void deleteWhenUnused(); + DwBodyPart* findPart( int index ); + private: /** Initialization shared by the ctors. */ @@ -886,10 +945,12 @@ private: /** Assign the values of @param other to this message. Used in the copy c'tor. */ void assign( const KMMessage& other ); + DwBodyPart* findPartInternal( DwEntity* root, int index, int &accu ); + TQString mDrafts; TQString mTemplates; mutable DwMessage* mMsg; - mutable bool mNeedsAssembly :1; + mutable bool mNeedsAssembly :1; bool mDecodeHTML :1; bool mReadyToShow :1; bool mComplete :1; diff --git a/kmail/kmmimeparttree.cpp b/kmail/kmmimeparttree.cpp index cafef94ec..d7fb9541e 100644 --- a/kmail/kmmimeparttree.cpp +++ b/kmail/kmmimeparttree.cpp @@ -129,8 +129,6 @@ void KMMimePartTree::itemRightClicked( TQListViewItem* item, kdDebug(5006) << "Item was not a KMMimePartTreeItem!" << endl; } else { - kdDebug(5006) << "\n**\n** KMMimePartTree::itemRightClicked() **\n**" << endl; - TQPopupMenu* popup = new TQPopupMenu; if ( mCurrentContextMenuItem->node()->nodeId() > 2 && mCurrentContextMenuItem->node()->typeString() != "Multipart" ) { diff --git a/kmail/kmmsgbase.cpp b/kmail/kmmsgbase.cpp index df53e6e57..e3d3fec15 100644 --- a/kmail/kmmsgbase.cpp +++ b/kmail/kmmsgbase.cpp @@ -243,6 +243,14 @@ void KMMsgBase::setStatus(const KMMsgStatus aStatus, int idx) mStatus &= ~KMMsgStatusHasAttach; mStatus |= KMMsgStatusHasNoAttach; break; + case KMMsgStatusHasInvitation: + mStatus &= ~KMMsgStatusHasNoInvitation; + mStatus |= KMMsgStatusHasInvitation; + break; + case KMMsgStatusHasNoInvitation: + mStatus &= ~KMMsgStatusHasInvitation; + mStatus |= KMMsgStatusHasNoInvitation; + break; default: mStatus = aStatus; break; @@ -658,7 +666,8 @@ TQString KMMsgBase::decodeRFC2047String(const TQCString& aStr, TQCString prefCha return TQString::null; if ( str.find( "=?" ) < 0 ) { - if ( !prefCharset.isEmpty() ) { + if ( !prefCharset.isEmpty() && + kmkernel->isCodecAsciiCompatible( KMMsgBase::codecForName( prefCharset ) ) ) { if ( prefCharset == "us-ascii" ) { // isn`t this foolproof? return KMMsgBase::codecForName( "utf-8" )->toUnicode( str ); @@ -666,9 +675,15 @@ TQString KMMsgBase::decodeRFC2047String(const TQCString& aStr, TQCString prefCha return KMMsgBase::codecForName( prefCharset )->toUnicode( str ); } } else { - return KMMsgBase::codecForName( GlobalSettings::self()-> + if ( kmkernel->isCodecAsciiCompatible( KMMsgBase::codecForName( + GlobalSettings::self()->fallbackCharacterEncoding().latin1() ) ) ) { + return KMMsgBase::codecForName( GlobalSettings::self()-> fallbackCharacterEncoding().latin1() )->toUnicode( str ); + } } + + // Not RFC2047 encoded, and codec not ascii-compatible -> interpret as ascii + return TQString::fromAscii( str ); } TQString result; @@ -917,6 +932,16 @@ TQCString KMMsgBase::encodeRFC2231String( const TQString& _str, return result; } +//----------------------------------------------------------------------------- +TQCString KMMsgBase::encodeRFC2231StringAutoDetectCharset( const TQString &str, + const TQCString &defaultCharset ) +{ + TQCString encoding = KMMsgBase::autoDetectCharset( defaultCharset, + KMMessage::preferredCharsets(), str ); + if ( encoding.isEmpty() ) + encoding = "utf-8"; + return KMMsgBase::encodeRFC2231String( str, encoding ); +} //----------------------------------------------------------------------------- TQString KMMsgBase::decodeRFC2231String(const TQCString& _str) @@ -1077,6 +1102,18 @@ KMMsgAttachmentState KMMsgBase::attachmentState() const return KMMsgAttachmentUnknown; } + +KMMsgInvitationState KMMsgBase::invitationState() const +{ + KMMsgStatus st = status(); + if (st & KMMsgStatusHasInvitation) + return KMMsgHasInvitation; + else if (st & KMMsgStatusHasNoInvitation) + return KMMsgHasNoInvitation; + else + return KMMsgInvitationUnknown; +} + //----------------------------------------------------------------------------- static void swapEndian(TQString &str) { @@ -1340,11 +1377,11 @@ const uchar *KMMsgBase::asIndexString(int &length) const //these are completely arbitrary order tmp_str = fromStrip().stripWhiteSpace(); - STORE_DATA_LEN(MsgFromPart, tmp_str.unicode(), tmp_str.length() * 2, true); + STORE_DATA_LEN(MsgFromStripPart, tmp_str.unicode(), tmp_str.length() * 2, true); tmp_str = subject().stripWhiteSpace(); STORE_DATA_LEN(MsgSubjectPart, tmp_str.unicode(), tmp_str.length() * 2, true); tmp_str = toStrip().stripWhiteSpace(); - STORE_DATA_LEN(MsgToPart, tmp_str.unicode(), tmp_str.length() * 2, true); + STORE_DATA_LEN(MsgToStripPart, tmp_str.unicode(), tmp_str.length() * 2, true); tmp_str = replyToIdMD5().stripWhiteSpace(); STORE_DATA_LEN(MsgReplyToIdMD5Part, tmp_str.unicode(), tmp_str.length() * 2, true); tmp_str = xmark().stripWhiteSpace(); @@ -1376,6 +1413,12 @@ const uchar *KMMsgBase::asIndexString(int &length) const tmp = UID(); STORE_DATA(MsgUIDPart, tmp); + tmp_str = from(); + STORE_DATA_LEN( MsgFromPart, tmp_str.unicode(), tmp_str.length() * 2, true ); + + tmp_str = to(); + STORE_DATA_LEN( MsgToPart, tmp_str.unicode(), tmp_str.length() * 2, true ); + return ret; } #undef STORE_DATA_LEN diff --git a/kmail/kmmsgbase.h b/kmail/kmmsgbase.h index b2d867839..1137bb6e9 100644 --- a/kmail/kmmsgbase.h +++ b/kmail/kmmsgbase.h @@ -57,7 +57,9 @@ enum MsgStatus KMMsgStatusSpam = 0x00002000, KMMsgStatusHam = 0x00004000, KMMsgStatusHasAttach = 0x00008000, - KMMsgStatusHasNoAttach = 0x00010000 + KMMsgStatusHasNoAttach = 0x00010000, + KMMsgStatusHasInvitation = 0x00020000, + KMMsgStatusHasNoInvitation = 0x00040000 }; typedef uint KMMsgStatus; @@ -132,6 +134,13 @@ typedef enum KMMsgAttachmentUnknown } KMMsgAttachmentState; +/** Flags for invitation state */ +typedef enum +{ + KMMsgHasInvitation, + KMMsgHasNoInvitation, + KMMsgInvitationUnknown +} KMMsgInvitationState; class KMMsgBase { @@ -250,7 +259,9 @@ public: /** Important header fields of the message that are also kept in the index. */ virtual TQString subject(void) const = 0; virtual TQString fromStrip(void) const = 0; + virtual TQString from() const = 0; virtual TQString toStrip(void) const = 0; + virtual TQString to() const = 0; virtual TQString replyToIdMD5(void) const = 0; virtual TQString msgIdMD5(void) const = 0; virtual TQString replyToAuxIdMD5() const = 0; @@ -353,11 +364,18 @@ public: static TQCString encodeRFC2231String(const TQString& aStr, const TQCString& charset); + /** + * Just like encodeRFC2231String, only that the encoding is auto-detected. + * @param defaultCharset If given, this will be the prefered charset + */ + static TQCString encodeRFC2231StringAutoDetectCharset( const TQString &str, + const TQCString &defaultCharset = "" ); + /** Decode given string as described in RFC2231 */ static TQString decodeRFC2231String(const TQCString& aStr); /** Extract a given param from the RFC2231-encoded header field, in particular concatenate possibly multiple entries, which are given as paramname*0=..; - paramname*1=..; ... or paramname*0*=..; paramname*1*=..; ... and return + paramname*1=..; ... or paramname*0*=..; paramname*1*=..; ... and return their value as one string. That string will still be encoded */ static TQCString extractRFC2231HeaderField( const TQCString &aStr, const TQCString &field ); @@ -385,6 +403,9 @@ public: /** Return if the message has at least one attachment */ virtual KMMsgAttachmentState attachmentState() const; + /** Return if the message contains an invitation */ + virtual KMMsgInvitationState invitationState() const; + /** Check for prefixes @p prefixRegExps in @p str. If none is found, @p newPrefix + ' ' is prepended to @p str and the resulting string is returned. If @p replace is true, any @@ -439,9 +460,9 @@ public: { MsgNoPart = 0, //unicode strings - MsgFromPart = 1, + MsgFromStripPart = 1, MsgSubjectPart = 2, - MsgToPart = 3, + MsgToStripPart = 3, MsgReplyToIdMD5Part = 4, MsgIdMD5Part = 5, MsgXMarkPart = 6, @@ -459,7 +480,9 @@ public: // and another unsigned long MsgStatusPart = 16, MsgSizeServerPart = 17, - MsgUIDPart = 18 + MsgUIDPart = 18, + MsgToPart = 19, + MsgFromPart = 20 }; /** access to long msgparts */ off_t getLongPart(MsgPartType) const; diff --git a/kmail/kmmsgdict.cpp b/kmail/kmmsgdict.cpp index bedc3bb44..8f9a9551c 100644 --- a/kmail/kmmsgdict.cpp +++ b/kmail/kmmsgdict.cpp @@ -458,8 +458,15 @@ int KMMsgDict::readFolderIds( FolderStorage& storage ) return -1; } - //if (!msn) - //kdDebug(5006) << "Dict found zero serial number in folder " << folder->label() << endl; + // We found a serial number that is zero. This is not allowed, and would + // later cause problems like in bug 149715. + // Therefore, use a fresh serial number instead + if ( msn == 0 ) { + kdWarning(5006) << "readFolderIds(): Found serial number zero at index " << index + << " in folder " << filename << endl; + msn = getNextMsgSerNum(); + Q_ASSERT( msn != 0 ); + } // Insert into the dict. Don't use dict->replace() as we _know_ // there is no entry with the same msn, we just made sure. @@ -556,6 +563,10 @@ int KMMsgDict::writeFolderIds( const FolderStorage &storage ) Q_UINT32 msn = rentry->getMsn(index); if (!fwrite(&msn, sizeof(msn), 1, fp)) return -1; + if ( msn == 0 ) { + kdWarning(5006) << "writeFolderIds(): Serial number of message at index " + << index << " is zero in folder " << storage.label() << endl; + } } rentry->sync(); diff --git a/kmail/kmmsginfo.cpp b/kmail/kmmsginfo.cpp index 3b1112dc4..ece847a38 100644 --- a/kmail/kmmsginfo.cpp +++ b/kmail/kmmsginfo.cpp @@ -18,16 +18,17 @@ class KMMsgInfo::KMMsgInfoPrivate { public: enum { - SUBJECT_SET = 0x01, TO_SET = 0x02, REPLYTO_SET = 0x04, MSGID_SET=0x08, + SUBJECT_SET = 0x01, TOSTRIP_SET = 0x02, REPLYTO_SET = 0x04, MSGID_SET=0x08, DATE_SET = 0x10, OFFSET_SET = 0x20, SIZE_SET = 0x40, SIZESERVER_SET = 0x80, - XMARK_SET=0x100, FROM_SET=0x200, FILE_SET=0x400, ENCRYPTION_SET=0x800, + XMARK_SET=0x100, FROMSTRIP_SET=0x200, FILE_SET=0x400, ENCRYPTION_SET=0x800, SIGNATURE_SET=0x1000, MDN_SET=0x2000, REPLYTOAUX_SET = 0x4000, STRIPPEDSUBJECT_SET = 0x8000, UID_SET = 0x10000, + TO_SET = 0x20000, FROM_SET = 0x40000, ALL_SET = 0xFFFFFF, NONE_SET = 0x000000 }; uint modifiers; - TQString subject, from, to, replyToIdMD5, replyToAuxIdMD5, + TQString subject, fromStrip, toStrip, replyToIdMD5, replyToAuxIdMD5, strippedSubjectMD5, msgIdMD5, xmark, file; off_t folderOffset; size_t msgSize, msgSizeServer; @@ -36,6 +37,7 @@ public: KMMsgSignatureState signatureState; KMMsgMDNSentState mdnSentState; ulong UID; + TQString to, from; KMMsgInfoPrivate() : modifiers(NONE_SET) { } KMMsgInfoPrivate& operator=(const KMMsgInfoPrivate& other) { @@ -48,17 +50,17 @@ public: modifiers |= STRIPPEDSUBJECT_SET; strippedSubjectMD5 = other.strippedSubjectMD5; } - if (other.modifiers & FROM_SET) { - modifiers |= FROM_SET; - from = other.from; + if (other.modifiers & FROMSTRIP_SET) { + modifiers |= FROMSTRIP_SET; + fromStrip = other.fromStrip; } if (other.modifiers & FILE_SET) { modifiers |= FILE_SET; - file = other.from; + file = other.file; } - if (other.modifiers & TO_SET) { - modifiers |= TO_SET; - to = other.to; + if (other.modifiers & TOSTRIP_SET) { + modifiers |= TOSTRIP_SET; + toStrip = other.toStrip; } if (other.modifiers & REPLYTO_SET) { modifiers |= REPLYTO_SET; @@ -109,6 +111,14 @@ public: modifiers |= UID_SET; UID = other.UID; } + if (other.modifiers & TO_SET) { + modifiers |= TO_SET; + to = other.to; + } + if (other.modifiers & FROM_SET) { + modifiers |= FROM_SET; + from = other.from; + } return *this; } }; @@ -157,8 +167,8 @@ KMMsgInfo& KMMsgInfo::operator=(const KMMessage& msg) kd = new KMMsgInfoPrivate; kd->modifiers = KMMsgInfoPrivate::ALL_SET; kd->subject = msg.subject(); - kd->from = msg.fromStrip(); - kd->to = msg.toStrip(); + kd->fromStrip = msg.fromStrip(); + kd->toStrip = msg.toStrip(); kd->replyToIdMD5 = msg.replyToIdMD5(); kd->replyToAuxIdMD5 = msg.replyToAuxIdMD5(); kd->strippedSubjectMD5 = msg.strippedSubjectMD5(); @@ -174,6 +184,8 @@ KMMsgInfo& KMMsgInfo::operator=(const KMMessage& msg) kd->mdnSentState = msg.mdnSentState(); kd->msgSizeServer = msg.msgSizeServer(); kd->UID = msg.UID(); + kd->to = msg.to(); + kd->from = msg.from(); return *this; } @@ -187,8 +199,8 @@ void KMMsgInfo::init(const TQCString& aSubject, const TQCString& aFrom, KMMsgSignatureState signatureState, KMMsgMDNSentState mdnSentState, const TQCString& prefCharset, - off_t aFolderOffset, size_t aMsgSize, - size_t aMsgSizeServer, ulong aUID) + off_t aFolderOffset, size_t aMsgSize, + size_t aMsgSizeServer, ulong aUID) { mIndexOffset = 0; mIndexLength = 0; @@ -196,8 +208,8 @@ void KMMsgInfo::init(const TQCString& aSubject, const TQCString& aFrom, kd = new KMMsgInfoPrivate; kd->modifiers = KMMsgInfoPrivate::ALL_SET; kd->subject = decodeRFC2047String(aSubject, prefCharset); - kd->from = decodeRFC2047String( KMMessage::stripEmailAddr( aFrom ), prefCharset ); - kd->to = decodeRFC2047String( KMMessage::stripEmailAddr( aTo ), prefCharset ); + kd->fromStrip = decodeRFC2047String( KMMessage::stripEmailAddr( aFrom ), prefCharset ); + kd->toStrip = decodeRFC2047String( KMMessage::stripEmailAddr( aTo ), prefCharset ); kd->replyToIdMD5 = base64EncodedMD5( replyToId ); kd->replyToAuxIdMD5 = base64EncodedMD5( replyToAuxId ); kd->strippedSubjectMD5 = base64EncodedMD5( KMMessage::stripOffPrefixes( kd->subject ), true /*utf8*/ ); @@ -213,7 +225,9 @@ void KMMsgInfo::init(const TQCString& aSubject, const TQCString& aFrom, kd->mdnSentState = mdnSentState; kd->msgSizeServer = aMsgSizeServer; kd->UID = aUID; - mDirty = false; + kd->to = aTo; + kd->from = aFrom; + mDirty = false; } void KMMsgInfo::init(const TQCString& aSubject, const TQCString& aFrom, @@ -245,15 +259,23 @@ TQString KMMsgInfo::subject(void) const return getStringPart(MsgSubjectPart); } - //----------------------------------------------------------------------------- TQString KMMsgInfo::fromStrip(void) const +{ + if (kd && kd->modifiers & KMMsgInfoPrivate::FROMSTRIP_SET) + return kd->fromStrip; + return getStringPart(MsgFromStripPart); +} + +//----------------------------------------------------------------------------- +TQString KMMsgInfo::from() const { if (kd && kd->modifiers & KMMsgInfoPrivate::FROM_SET) return kd->from; - return getStringPart(MsgFromPart); + return getStringPart( MsgFromPart ); } + //----------------------------------------------------------------------------- TQString KMMsgInfo::fileName(void) const { @@ -265,10 +287,18 @@ TQString KMMsgInfo::fileName(void) const //----------------------------------------------------------------------------- TQString KMMsgInfo::toStrip(void) const +{ + if (kd && kd->modifiers & KMMsgInfoPrivate::TOSTRIP_SET) + return kd->toStrip; + return getStringPart(MsgToStripPart); +} + +//----------------------------------------------------------------------------- +TQString KMMsgInfo::to() const { if (kd && kd->modifiers & KMMsgInfoPrivate::TO_SET) return kd->to; - return getStringPart(MsgToPart); + return getStringPart( MsgToPart ); } //----------------------------------------------------------------------------- @@ -656,6 +686,24 @@ void KMMsgInfo::setDate(time_t aUnixTime) mDirty = true; } +void KMMsgInfo::setFrom( const TQString &from ) +{ + if ( !kd ) + kd = new KMMsgInfoPrivate; + kd->modifiers |= KMMsgInfoPrivate::FROM_SET; + kd->from = from; + mDirty = true; +} + +void KMMsgInfo::setTo( const TQString &to ) +{ + if ( !kd ) + kd = new KMMsgInfoPrivate; + kd->modifiers |= KMMsgInfoPrivate::TO_SET; + kd->to = to; + mDirty = true; +} + //--- For compatability with old index files void KMMsgInfo::compat_fromOldIndexString(const TQCString& str, bool toUtf8) { @@ -671,8 +719,8 @@ void KMMsgInfo::compat_fromOldIndexString(const TQCString& str, bool toUtf8) mStatus = (KMMsgStatus)str.at(0); if (toUtf8) { kd->subject = str.mid(37, 100).stripWhiteSpace(); - kd->from = str.mid(138, 50).stripWhiteSpace(); - kd->to = str.mid(189, 50).stripWhiteSpace(); + kd->fromStrip = str.mid(138, 50).stripWhiteSpace(); + kd->toStrip = str.mid(189, 50).stripWhiteSpace(); } else { start = offset = str.data() + 37; while (*start == ' ' && start - offset < 100) start++; @@ -680,11 +728,11 @@ void KMMsgInfo::compat_fromOldIndexString(const TQCString& str, bool toUtf8) 100 - (start - offset)), 100 - (start - offset)); start = offset = str.data() + 138; while (*start == ' ' && start - offset < 50) start++; - kd->from = TQString::fromUtf8(str.mid(start - str.data(), + kd->fromStrip = TQString::fromUtf8(str.mid(start - str.data(), 50 - (start - offset)), 50 - (start - offset)); start = offset = str.data() + 189; while (*start == ' ' && start - offset < 50) start++; - kd->to = TQString::fromUtf8(str.mid(start - str.data(), + kd->toStrip = TQString::fromUtf8(str.mid(start - str.data(), 50 - (start - offset)), 50 - (start - offset)); } kd->replyToIdMD5 = str.mid(240, 22).stripWhiteSpace(); diff --git a/kmail/kmmsginfo.h b/kmail/kmmsginfo.h index 043830200..8f557f25b 100644 --- a/kmail/kmmsginfo.h +++ b/kmail/kmmsginfo.h @@ -68,7 +68,9 @@ public: /** Inherited methods (see KMMsgBase for description): */ virtual TQString subject(void) const; virtual TQString fromStrip(void) const; + virtual TQString from() const; virtual TQString toStrip(void) const; + virtual TQString to() const; virtual TQString xmark(void) const; virtual TQString replyToIdMD5(void) const; virtual TQString replyToAuxIdMD5() const; @@ -101,6 +103,8 @@ public: virtual void setSignatureState( const KMMsgSignatureState, int idx = -1 ); virtual void setMDNSentState( const KMMsgMDNSentState, int idx = -1 ); virtual void setUID(ulong); + virtual void setFrom( const TQString &from ); + virtual void setTo( const TQString &to ); /** Grr.. c++! */ virtual void setStatus(const char* s1, const char* s2=0) { KMMsgBase::setStatus(s1, s2); } diff --git a/kmail/kmmsgpart.cpp b/kmail/kmmsgpart.cpp index 085e36c0f..112236c03 100644 --- a/kmail/kmmsgpart.cpp +++ b/kmail/kmmsgpart.cpp @@ -284,6 +284,9 @@ void KMMessagePart::setBodyEncodedBinary(const TQByteArray& aStr) assert( codec ); // Nice: We can use the convenience function :-) mBody = codec->encode( aStr ); + // QP encoding does CRLF -> LF conversion, which can change the size after decoding again + // and a size mismatch triggers an assert in various other methods + mBodyDecodedSize = -1; break; } default: @@ -585,7 +588,7 @@ TQString KMMessagePart::fileName(void) const const TQCString str = mContentDisposition.mid(startOfFilename, endOfFilename-startOfFilename+1) .stripWhiteSpace(); - return KMMsgBase::decodeRFC2047String(str, charset()); + return KMMsgBase::decodeRFC2047String(str); } return TQString::null; diff --git a/kmail/kmmsgpartdlg.cpp b/kmail/kmmsgpartdlg.cpp index 921c9d55b..4a897291f 100644 --- a/kmail/kmmsgpartdlg.cpp +++ b/kmail/kmmsgpartdlg.cpp @@ -390,10 +390,7 @@ void KMMsgPartDialogCompat::applyChanges() TQString name = fileName(); if ( !name.isEmpty() || !mMsgPart->name().isEmpty()) { mMsgPart->setName( name ); - TQCString encoding = KMMsgBase::autoDetectCharset( mMsgPart->charset(), - KMMessage::preferredCharsets(), name ); - if ( encoding.isEmpty() ) encoding = "utf-8"; - TQCString encName = KMMsgBase::encodeRFC2231String( name, encoding ); + TQCString encName = KMMsgBase::encodeRFC2231StringAutoDetectCharset( name, mMsgPart->charset() ); cDisp += "\n\tfilename"; if ( name != TQString( encName ) ) diff --git a/kmail/kmpopheaders.cpp b/kmail/kmpopheaders.cpp index 85445abd2..2828a49e0 100644 --- a/kmail/kmpopheaders.cpp +++ b/kmail/kmpopheaders.cpp @@ -22,8 +22,7 @@ KMPopHeaders::KMPopHeaders() } KMPopHeaders::~KMPopHeaders(){ - if (mHeader) - delete mHeader; + delete mHeader; } /** No descriptions */ diff --git a/kmail/kmreadermainwin.cpp b/kmail/kmreadermainwin.cpp index 8e77ad9d7..2f5227cde 100644 --- a/kmail/kmreadermainwin.cpp +++ b/kmail/kmreadermainwin.cpp @@ -140,10 +140,15 @@ void KMReaderMainWin::setUseFixedFont( bool useFixedFont ) } //----------------------------------------------------------------------------- -void KMReaderMainWin::showMsg( const TQString & encoding, KMMessage *msg ) +void KMReaderMainWin::showMsg( const TQString & encoding, KMMessage *msg, + unsigned long serNumOfOriginalMessage, int nodeIdOffset ) { mReaderWin->setOverrideEncoding( encoding ); mReaderWin->setMsg( msg, true ); + if ( serNumOfOriginalMessage != 0 ) { + Q_ASSERT( nodeIdOffset != -1 ); + mReaderWin->setOriginalMsg( serNumOfOriginalMessage, nodeIdOffset ); + } mReaderWin->slotTouchMessage(); setCaption( msg->subject() ); mMsg = msg; @@ -163,6 +168,13 @@ void KMReaderMainWin::slotFolderRemoved( TQObject* folderPtr ) mMsg->setParent( 0 ); } +void KMReaderMainWin::slotReplyOrForwardFinished() +{ + if ( GlobalSettings::self()->closeAfterReplyOrForward() ) { + close(); + } +} + //----------------------------------------------------------------------------- void KMReaderMainWin::slotTrashMsg() { @@ -212,6 +224,7 @@ void KMReaderMainWin::slotMarkAll() void KMReaderMainWin::slotPrintMsg() { KMPrintCommand *command = new KMPrintCommand( this, mReaderWin->message(), + mReaderWin->headerStyle(), mReaderWin->headerStrategy(), mReaderWin->htmlOverride(), mReaderWin->htmlLoadExtOverride(), mReaderWin->isFixedFont(), mReaderWin->overrideEncoding() ); command->setOverrideFont( mReaderWin->cssHelper()->bodyFont( mReaderWin->isFixedFont(), true /*printing*/ ) ); @@ -228,6 +241,8 @@ void KMReaderMainWin::slotForwardInlineMsg() } else { command = new KMForwardInlineCommand( this, mReaderWin->message() ); } + connect( command, TQT_SIGNAL( completed( KMCommand * ) ), + this, TQT_SLOT( slotReplyOrForwardFinished() ) ); command->start(); } @@ -241,6 +256,8 @@ void KMReaderMainWin::slotForwardAttachedMsg() } else { command = new KMForwardAttachedCommand( this, mReaderWin->message() ); } + connect( command, TQT_SIGNAL( completed( KMCommand * ) ), + this, TQT_SLOT( slotReplyOrForwardFinished() ) ); command->start(); } @@ -254,6 +271,8 @@ void KMReaderMainWin::slotForwardDigestMsg() } else { command = new KMForwardDigestCommand( this, mReaderWin->message() ); } + connect( command, TQT_SIGNAL( completed( KMCommand * ) ), + this, TQT_SLOT( slotReplyOrForwardFinished() ) ); command->start(); } @@ -261,6 +280,8 @@ void KMReaderMainWin::slotForwardDigestMsg() void KMReaderMainWin::slotRedirectMsg() { KMCommand *command = new KMRedirectCommand( this, mReaderWin->message() ); + connect( command, TQT_SIGNAL( completed( KMCommand * ) ), + this, TQT_SLOT( slotReplyOrForwardFinished() ) ); command->start(); } @@ -275,10 +296,37 @@ void KMReaderMainWin::slotShowMsgSrc() command->start(); } +//----------------------------------------------------------------------------- +void KMReaderMainWin::setupForwardActions() +{ + disconnect( mForwardActionMenu, TQT_SIGNAL( activated() ), 0, 0 ); + mForwardActionMenu->remove( mForwardInlineAction ); + mForwardActionMenu->remove( mForwardAttachedAction ); + + if ( GlobalSettings::self()->forwardingInlineByDefault() ) { + mForwardActionMenu->insert( mForwardInlineAction, 0 ); + mForwardActionMenu->insert( mForwardAttachedAction, 1 ); + mForwardInlineAction->setShortcut( Key_F ); + mForwardAttachedAction->setShortcut( SHIFT+Key_F ); + connect( mForwardActionMenu, TQT_SIGNAL(activated()), this, + TQT_SLOT(slotForwardInlineMsg()) ); + + } else { + mForwardActionMenu->insert( mForwardAttachedAction, 0 ); + mForwardActionMenu->insert( mForwardInlineAction, 1 ); + mForwardInlineAction->setShortcut( SHIFT+Key_F ); + mForwardAttachedAction->setShortcut( Key_F ); + connect( mForwardActionMenu, TQT_SIGNAL(activated()), this, + TQT_SLOT(slotForwardAttachedMsg()) ); + } +} + //----------------------------------------------------------------------------- void KMReaderMainWin::slotConfigChanged() { //readConfig(); + setupForwardActions(); + setupForwardingActionsList(); } void KMReaderMainWin::setupAccel() @@ -288,6 +336,9 @@ void KMReaderMainWin::setupAccel() mMsgActions = new KMail::MessageActions( actionCollection(), this ); mMsgActions->setMessageView( mReaderWin ); + connect( mMsgActions, TQT_SIGNAL( replyActionFinished() ), + this, TQT_SLOT( slotReplyOrForwardFinished() ) ); + //----- File Menu //mOpenAction = KStdAction::open( this, TQT_SLOT( slotOpenMsg() ), // actionCollection() ); @@ -351,21 +402,7 @@ void KMReaderMainWin::setupAccel() actionCollection(), "message_forward_redirect" ); - if ( GlobalSettings::self()->forwardingInlineByDefault() ) { - mForwardActionMenu->insert( mForwardInlineAction ); - mForwardActionMenu->insert( mForwardAttachedAction ); - mForwardInlineAction->setShortcut( Key_F ); - mForwardAttachedAction->setShortcut( SHIFT+Key_F ); - connect( mForwardActionMenu, TQT_SIGNAL(activated()), this, - TQT_SLOT(slotForwardInlineMsg()) ); - } else { - mForwardActionMenu->insert( mForwardAttachedAction ); - mForwardActionMenu->insert( mForwardInlineAction ); - mForwardInlineAction->setShortcut( SHIFT+Key_F ); - mForwardAttachedAction->setShortcut( Key_F ); - connect( mForwardActionMenu, TQT_SIGNAL(activated()), this, - TQT_SLOT(slotForwardAttachedMsg()) ); - } + setupForwardActions(); mForwardActionMenu->insert( mForwardDigestAction ); mForwardActionMenu->insert( mRedirectAction ); @@ -408,7 +445,7 @@ void KMReaderMainWin::slotMsgPopup(KMMessage &aMsg, const KURL &aUrl, const TQPo mUrl = aUrl; mMsg = &aMsg; bool urlMenuAdded=false; - + bool copyAdded = false; if (!aUrl.isEmpty()) { if (aUrl.protocol() == "mailto") { @@ -421,7 +458,8 @@ void KMReaderMainWin::slotMsgPopup(KMMessage &aMsg, const KURL &aUrl, const TQPo } mReaderWin->addAddrBookAction()->plug( menu ); mReaderWin->openAddrBookAction()->plug( menu ); - mReaderWin->copyAction()->plug( menu ); + mReaderWin->copyURLAction()->plug( menu ); + copyAdded = true; } else { // popup on a not-mailto URL mReaderWin->urlOpenAction()->plug( menu ); @@ -436,8 +474,8 @@ void KMReaderMainWin::slotMsgPopup(KMMessage &aMsg, const KURL &aUrl, const TQPo menu->insertSeparator(); mMsgActions->replyMenu()->plug( menu ); menu->insertSeparator(); - - mReaderWin->copyAction()->plug( menu ); + if( !copyAdded ) + mReaderWin->copyAction()->plug( menu ); mReaderWin->selectAllAction()->plug( menu ); } else if ( !urlMenuAdded ) { diff --git a/kmail/kmreadermainwin.h b/kmail/kmreadermainwin.h index ba458eb0a..a4d8e9725 100644 --- a/kmail/kmreadermainwin.h +++ b/kmail/kmreadermainwin.h @@ -36,8 +36,16 @@ public: void setUseFixedFont( bool useFixedFont ); - // take ownership of and show @param msg - void showMsg( const TQString & encoding, KMMessage *msg ); + /** + * take ownership of and show @param msg + * + * The last two paramters, serNumOfOriginalMessage and nodeIdOffset, are needed when @p msg + * is derived from another message, e.g. the user views an encapsulated message in this window. + * Then, the reader needs to know about that original message, so those to paramters are passed + * onto setOriginalMsg() of KMReaderWin. + */ + void showMsg( const TQString & encoding, KMMessage *msg, + unsigned long serNumOfOriginalMessage = 0, int nodeIdOffset = -1 ); /** * Sets up action list for forward menu. @@ -70,10 +78,19 @@ private slots: void slotFolderRemoved( TQObject* folderPtr ); + /// This closes the window if the setting to close the window after replying or + /// forwarding is set. + void slotReplyOrForwardFinished(); + private: void initKMReaderMainWin(); void setupAccel(); + /** + * @see the KMMainWidget function with the same name. + */ + void setupForwardActions(); + KMReaderWin *mReaderWin; KMMessage *mMsg; KURL mUrl; diff --git a/kmail/kmreaderwin.cpp b/kmail/kmreaderwin.cpp index b5ee41052..21be5a44a 100644 --- a/kmail/kmreaderwin.cpp +++ b/kmail/kmreaderwin.cpp @@ -52,6 +52,7 @@ using KMail::ISubject; using KMail::URLHandlerManager; #include "interfaces/observable.h" #include "util.h" +#include "kmheaders.h" #include "broadcaststatus.h" @@ -86,7 +87,7 @@ using KMail::TeeHtmlWriter; #include #include #include - +#include #include // for the click on attachment stuff (dnaber): @@ -208,81 +209,33 @@ void KMReaderWin::objectTreeToDecryptedMsg( partNode* node, kdDebug(5006) << TQString("-------------------------------------------------" ) << endl; kdDebug(5006) << TQString("KMReaderWin::objectTreeToDecryptedMsg( %1 ) START").arg( recCount ) << endl; if( node ) { + + kdDebug(5006) << node->typeString() << '/' << node->subTypeString() << endl; + partNode* curNode = node; partNode* dataNode = curNode; partNode * child = node->firstChild(); - bool bIsMultipart = false; + const bool bIsMultipart = node->type() == DwMime::kTypeMultipart ; + bool bKeepPartAsIs = false; switch( curNode->type() ){ - case DwMime::kTypeText: { -kdDebug(5006) << "* text *" << endl; - switch( curNode->subType() ){ - case DwMime::kSubtypeHtml: -kdDebug(5006) << "html" << endl; - break; - case DwMime::kSubtypeXVCard: -kdDebug(5006) << "v-card" << endl; - break; - case DwMime::kSubtypeRichtext: -kdDebug(5006) << "rich text" << endl; - break; - case DwMime::kSubtypeEnriched: -kdDebug(5006) << "enriched " << endl; - break; - case DwMime::kSubtypePlain: -kdDebug(5006) << "plain " << endl; - break; - default: -kdDebug(5006) << "default " << endl; - break; - } - } - break; case DwMime::kTypeMultipart: { -kdDebug(5006) << "* multipart *" << endl; - bIsMultipart = true; switch( curNode->subType() ){ - case DwMime::kSubtypeMixed: -kdDebug(5006) << "mixed" << endl; - break; - case DwMime::kSubtypeAlternative: -kdDebug(5006) << "alternative" << endl; - break; - case DwMime::kSubtypeDigest: -kdDebug(5006) << "digest" << endl; - break; - case DwMime::kSubtypeParallel: -kdDebug(5006) << "parallel" << endl; - break; - case DwMime::kSubtypeSigned: -kdDebug(5006) << "signed" << endl; + case DwMime::kSubtypeSigned: { + bKeepPartAsIs = true; + } break; case DwMime::kSubtypeEncrypted: { -kdDebug(5006) << "encrypted" << endl; - if ( child ) { - /* - ATTENTION: This code is to be replaced by the new 'auto-detect' feature. -------------------------------------- - */ - partNode* data = - child->findType( DwMime::kTypeApplication, DwMime::kSubtypeOctetStream, false, true ); - if ( !data ) - data = child->findType( DwMime::kTypeApplication, DwMime::kSubtypePkcs7Mime, false, true ); - if ( data && data->firstChild() ) - dataNode = data; - } + if ( child ) + dataNode = child; } break; - default : -kdDebug(5006) << "( unknown subtype )" << endl; - break; } } break; case DwMime::kTypeMessage: { -kdDebug(5006) << "* message *" << endl; switch( curNode->subType() ){ case DwMime::kSubtypeRfc822: { -kdDebug(5006) << "RfC 822" << endl; if ( child ) dataNode = child; } @@ -291,25 +244,19 @@ kdDebug(5006) << "RfC 822" << endl; } break; case DwMime::kTypeApplication: { -kdDebug(5006) << "* application *" << endl; switch( curNode->subType() ){ - case DwMime::kSubtypePostscript: -kdDebug(5006) << "postscript" << endl; - break; case DwMime::kSubtypeOctetStream: { -kdDebug(5006) << "octet stream" << endl; if ( child ) dataNode = child; } break; - case DwMime::kSubtypePgpEncrypted: -kdDebug(5006) << "pgp encrypted" << endl; - break; - case DwMime::kSubtypePgpSignature: -kdDebug(5006) << "pgp signed" << endl; + case DwMime::kSubtypePkcs7Signature: { + // note: subtype Pkcs7Signature specifies a signature part + // which we do NOT want to remove! + bKeepPartAsIs = true; + } break; case DwMime::kSubtypePkcs7Mime: { -kdDebug(5006) << "pkcs7 mime" << endl; // note: subtype Pkcs7Mime can also be signed // and we do NOT want to remove the signature! if ( child && curNode->encryptionState() != KMMsgNotEncrypted ) @@ -319,39 +266,6 @@ kdDebug(5006) << "pkcs7 mime" << endl; } } break; - case DwMime::kTypeImage: { -kdDebug(5006) << "* image *" << endl; - switch( curNode->subType() ){ - case DwMime::kSubtypeJpeg: -kdDebug(5006) << "JPEG" << endl; - break; - case DwMime::kSubtypeGif: -kdDebug(5006) << "GIF" << endl; - break; - } - } - break; - case DwMime::kTypeAudio: { -kdDebug(5006) << "* audio *" << endl; - switch( curNode->subType() ){ - case DwMime::kSubtypeBasic: -kdDebug(5006) << "basic" << endl; - break; - } - } - break; - case DwMime::kTypeVideo: { -kdDebug(5006) << "* video *" << endl; - switch( curNode->subType() ){ - case DwMime::kSubtypeMpeg: -kdDebug(5006) << "mpeg" << endl; - break; - } - } - break; - case DwMime::kTypeModel: -kdDebug(5006) << "* model *" << endl; - break; } @@ -389,6 +303,10 @@ kdDebug(5006) << " new Content-Type = " << headers->ContentType( } } + if ( bKeepPartAsIs ) { + resultingData += dataNode->encodedBody(); + } else { + // B) Store the body of this part. if( headers && bIsMultipart && dataNode->firstChild() ) { kdDebug(5006) << "is valid Multipart, processing children:" << endl; @@ -424,6 +342,7 @@ kdDebug(5006) << "Multipart processing children - DONE" << endl; kdDebug(5006) << "is Simple part or invalid Multipart, storing body data .. DONE" << endl; resultingData += part->Body().AsString().c_str(); } + } } else { kdDebug(5006) << "dataNode != curNode: Replace curNode by dataNode." << endl; bool rootNodeReplaceFlag = weAreReplacingTheRootNode || !curNode->parentNode(); @@ -488,6 +407,8 @@ KMReaderWin::KMReaderWin(TQWidget *aParent, const char *aName, int aFlags ) : TQWidget(aParent, aName, aFlags | Qt::WDestructiveClose), + mSerNumOfOriginalMessage( 0 ), + mNodeIdOffset( -1 ), mAttachmentStrategy( 0 ), mHeaderStrategy( 0 ), mHeaderStyle( 0 ), @@ -511,14 +432,18 @@ KMReaderWin::KMReaderWin(TQWidget *aParent, mAddBookmarksAction( 0 ), mStartIMChatAction( 0 ), mSelectAllAction( 0 ), + mHeaderOnlyAttachmentsAction( 0 ), mSelectEncodingAction( 0 ), mToggleFixFontAction( 0 ), + mCanStartDrag( false ), mHtmlWriter( 0 ), mSavedRelativePosition( 0 ), mDecrytMessageOverwrite( false ), mShowSignatureDetails( false ), - mShowAttachmentQuicklist( true ) + mShowAttachmentQuicklist( true ), + mShowRawToltecMail( false ) { + mExternalWindow = (aParent == mainWindow ); mSplitterSizes << 180 << 100; mMimeTreeMode = 1; mMimeTreeAtBottom = true; @@ -526,7 +451,6 @@ KMReaderWin::KMReaderWin(TQWidget *aParent, mLastSerNum = 0; mWaitingForSerNum = 0; mMessage = 0; - mLastStatus = KMMsgStatusUnknown; mMsgDisplay = true; mPrinting = false; mShowColorbar = false; @@ -642,6 +566,13 @@ void KMReaderWin::createActions( KActionCollection * ac ) { raction->setExclusiveGroup( "view_attachments_group" ); attachmentMenu->insert( raction ); + mHeaderOnlyAttachmentsAction = new KRadioAction( i18n( "View->attachments->", "In Header &Only" ), 0, + this, TQT_SLOT( slotHeaderOnlyAttachments() ), + ac, "view_attachments_headeronly" ); + mHeaderOnlyAttachmentsAction->setToolTip( i18n( "Show Attachments only in the header of the mail" ) ); + mHeaderOnlyAttachmentsAction->setExclusiveGroup( "view_attachments_group" ); + attachmentMenu->insert( mHeaderOnlyAttachmentsAction ); + // Set Encoding submenu mSelectEncodingAction = new KSelectAction( i18n( "&Set Encoding" ), "charset", 0, this, TQT_SLOT( slotSetEncoding() ), @@ -725,6 +656,8 @@ KRadioAction *KMReaderWin::actionForAttachmentStrategy( const AttachmentStrategy actionName = "view_attachments_inline"; else if ( as == AttachmentStrategy::hidden() ) actionName = "view_attachments_hide"; + else if ( as == AttachmentStrategy::headerOnly() ) + actionName = "view_attachments_headeronly"; if ( actionName ) return static_cast(mActionCollection->action(actionName)); @@ -735,37 +668,46 @@ KRadioAction *KMReaderWin::actionForAttachmentStrategy( const AttachmentStrategy void KMReaderWin::slotEnterpriseHeaders() { setHeaderStyleAndStrategy( HeaderStyle::enterprise(), HeaderStrategy::rich() ); + if( !mExternalWindow ) + writeConfig(); } void KMReaderWin::slotFancyHeaders() { setHeaderStyleAndStrategy( HeaderStyle::fancy(), HeaderStrategy::rich() ); + if( !mExternalWindow ) + writeConfig(); } void KMReaderWin::slotBriefHeaders() { setHeaderStyleAndStrategy( HeaderStyle::brief(), HeaderStrategy::brief() ); + if( !mExternalWindow ) + writeConfig(); } void KMReaderWin::slotStandardHeaders() { setHeaderStyleAndStrategy( HeaderStyle::plain(), HeaderStrategy::standard()); + writeConfig(); } void KMReaderWin::slotLongHeaders() { setHeaderStyleAndStrategy( HeaderStyle::plain(), HeaderStrategy::rich() ); + if( !mExternalWindow ) + writeConfig(); } void KMReaderWin::slotAllHeaders() { setHeaderStyleAndStrategy( HeaderStyle::plain(), HeaderStrategy::all() ); + if( !mExternalWindow ) + writeConfig(); } void KMReaderWin::slotLevelQuote( int l ) { - kdDebug( 5006 ) << "Old Level: " << mLevelQuote << " New Level: " << l << endl; - mLevelQuote = l; saveRelativePosition(); update(true); @@ -820,6 +762,10 @@ void KMReaderWin::slotHideAttachments() { setAttachmentStrategy( AttachmentStrategy::hidden() ); } +void KMReaderWin::slotHeaderOnlyAttachments() { + setAttachmentStrategy( AttachmentStrategy::headerOnly() ); +} + void KMReaderWin::slotCycleAttachmentStrategy() { setAttachmentStrategy( attachmentStrategy()->next() ); KRadioAction * action = actionForAttachmentStrategy( attachmentStrategy() ); @@ -831,6 +777,7 @@ void KMReaderWin::slotCycleAttachmentStrategy() { //----------------------------------------------------------------------------- KMReaderWin::~KMReaderWin() { + clearBodyPartMementos(); delete mHtmlWriter; mHtmlWriter = 0; delete mCSSHelper; if (mAutoDelete) delete message(); @@ -846,7 +793,7 @@ void KMReaderWin::slotMessageArrived( KMMessage *msg ) if ( msg->getMsgSerNum() == mWaitingForSerNum ) { setMsg( msg, true ); } else { - kdDebug( 5006 ) << "KMReaderWin::slotMessageArrived - ignoring update" << endl; + //kdDebug( 5006 ) << "KMReaderWin::slotMessageArrived - ignoring update" << endl; } } } @@ -856,7 +803,7 @@ void KMReaderWin::update( KMail::Interface::Observable * observable ) { if ( !mAtmUpdate ) { // reparse the msg - kdDebug(5006) << "KMReaderWin::update - message" << endl; + //kdDebug(5006) << "KMReaderWin::update - message" << endl; updateReaderWin(); return; } @@ -1067,8 +1014,6 @@ void KMReaderWin::initHtmlWidget(void) connect(mViewer->browserExtension(), TQT_SIGNAL(createNewWindow(const KURL &, const KParts::URLArgs &)),this, TQT_SLOT(slotUrlOpen(const KURL &))); - connect(mViewer,TQT_SIGNAL(onURL(const TQString &)),this, - TQT_SLOT(slotUrlOn(const TQString &))); connect(mViewer,TQT_SIGNAL(popupMenu(const TQString &, const TQPoint &)), TQT_SLOT(slotUrlPopup(const TQString &, const TQPoint &))); connect( kmkernel->imProxy(), TQT_SIGNAL( sigContactPresenceChanged( const TQString & ) ), @@ -1105,6 +1050,16 @@ void KMReaderWin::setHeaderStyleAndStrategy( const HeaderStyle * style, const HeaderStrategy * strategy ) { mHeaderStyle = style ? style : HeaderStyle::fancy(); mHeaderStrategy = strategy ? strategy : HeaderStrategy::rich(); + if ( mHeaderOnlyAttachmentsAction ) { + const bool styleHasAttachmentQuickList = mHeaderStyle == HeaderStyle::fancy() || + mHeaderStyle == HeaderStyle::enterprise(); + mHeaderOnlyAttachmentsAction->setEnabled( styleHasAttachmentQuickList ); + if ( !styleHasAttachmentQuickList && mAttachmentStrategy == AttachmentStrategy::headerOnly() ) { + // Style changed to something without an attachment quick list, need to change attachment + // strategy + setAttachmentStrategy( AttachmentStrategy::smart() ); + } + } update( true ); } @@ -1150,7 +1105,6 @@ void KMReaderWin::setPrintFont( const TQFont& font ) //----------------------------------------------------------------------------- const TQTextCodec * KMReaderWin::overrideCodec() const { - kdDebug(5006) << k_funcinfo << " mOverrideEncoding == '" << mOverrideEncoding << "'" << endl; if ( mOverrideEncoding.isEmpty() || mOverrideEncoding == "Auto" ) // Auto return 0; else @@ -1179,15 +1133,25 @@ void KMReaderWin::readGlobalOverrideCodec() } //----------------------------------------------------------------------------- -void KMReaderWin::setMsg(KMMessage* aMsg, bool force) +void KMReaderWin::setOriginalMsg( unsigned long serNumOfOriginalMessage, int nodeIdOffset ) { - if (aMsg) - kdDebug(5006) << "(" << aMsg->getMsgSerNum() << ", last " << mLastSerNum << ") " << aMsg->subject() << " " - << aMsg->fromStrip() << ", readyToShow " << (aMsg->readyToShow()) << endl; + mSerNumOfOriginalMessage = serNumOfOriginalMessage; + mNodeIdOffset = nodeIdOffset; +} - //Reset the level quote if the msg has changed. - if (aMsg && aMsg->getMsgSerNum() != mLastSerNum ){ +//----------------------------------------------------------------------------- +void KMReaderWin::setMsg( KMMessage* aMsg, bool force, bool updateOnly ) +{ + if ( aMsg ) { + kdDebug(5006) << "(" << aMsg->getMsgSerNum() << ", last " << mLastSerNum << ") " << aMsg->subject() << " " + << aMsg->fromStrip() << ", readyToShow " << (aMsg->readyToShow()) << endl; + } + + // Reset message-transient state + if ( aMsg && aMsg->getMsgSerNum() != mLastSerNum && !updateOnly ){ mLevelQuote = GlobalSettings::self()->collapseQuoteLevelSpin()-1; + mShowRawToltecMail = !GlobalSettings::self()->showToltecReplacementText(); + clearBodyPartMementos(); } if ( mPrinting ) mLevelQuote = -1; @@ -1234,14 +1198,11 @@ void KMReaderWin::setMsg(KMMessage* aMsg, bool force) if (aMsg) { aMsg->setOverrideCodec( overrideCodec() ); aMsg->setDecodeHTML( htmlMail() ); - mLastStatus = aMsg->status(); // FIXME: workaround to disable DND for IMAP load-on-demand if ( !aMsg->isComplete() ) mViewer->setDNDEnabled( false ); else mViewer->setDNDEnabled( true ); - } else { - mLastStatus = KMMsgStatusUnknown; } // only display the msg if it is complete @@ -1522,17 +1483,16 @@ void KMReaderWin::displayMessage() { TQTimer::singleShot( 1, this, TQT_SLOT(injectAttachments()) ); } +static bool message_was_saved_decrypted_before( const KMMessage * msg ) { + if ( !msg ) + return false; + //kdDebug(5006) << "msgId = " << msg->msgId() << endl; + return msg->msgId().stripWhiteSpace().startsWith( "typeString() + '/' + mRootNode->subTypeString(); TQString cntDesc = aMsg->subject(); @@ -1573,24 +1533,38 @@ void KMReaderWin::parseMsg(KMMessage* aMsg) if( vCardNode ) { // ### FIXME: We should only do this if the vCard belongs to the sender, // ### i.e. if the sender's email address is contained in the vCard. - const TQString vcard = vCardNode->msgPart().bodyToUnicode( overrideCodec() ); KABC::VCardConverter t; +#if defined(KABC_VCARD_ENCODING_FIX) + const TQByteArray vcard = vCardNode->msgPart().bodyDecodedBinary(); + if ( !t.parseVCardsRaw( vcard.data() ).empty() ) { +#else + const TQString vcard = vCardNode->msgPart().bodyToUnicode( overrideCodec() ); if ( !t.parseVCards( vcard ).empty() ) { +#endif hasVCard = true; - kdDebug(5006) << "FOUND A VALID VCARD" << endl; writeMessagePartToTempFile( &vCardNode->msgPart(), vCardNode->nodeId() ); } } - htmlWriter()->queue( writeMsgHeader(aMsg, hasVCard, true ) ); + + if ( !mRootNode || !mRootNode->isToltecMessage() || mShowRawToltecMail ) { + htmlWriter()->queue( writeMsgHeader(aMsg, hasVCard ? vCardNode : 0, true ) ); + } // show message content ObjectTreeParser otp( this ); + otp.setAllowAsync( true ); + otp.setShowRawToltecMail( mShowRawToltecMail ); otp.parseObjectTree( mRootNode ); // store encrypted/signed status information in the KMMessage // - this can only be done *after* calling parseObjectTree() KMMsgEncryptionState encryptionState = mRootNode->overallEncryptionState(); KMMsgSignatureState signatureState = mRootNode->overallSignatureState(); + // Don't crash when switching message while GPG passphrase entry dialog is shown #53185 + if (aMsg != message()) { + displayMessage(); + return; + } aMsg->setEncryptionState( encryptionState ); // Don't reset the signature state to "not signed" (e.g. if one canceled the // decryption of a signed messages which has already been decrypted before). @@ -1618,22 +1592,23 @@ void KMReaderWin::parseMsg(KMMessage* aMsg) kdDebug(5006) << "\n\n\nKMReaderWin::parseMsg() - special post-encryption handling:\n1." << endl; kdDebug(5006) << "(aMsg == msg) = " << (aMsg == message()) << endl; -kdDebug(5006) << " (KMMsgStatusUnknown == mLastStatus) = " << (KMMsgStatusUnknown == mLastStatus) << endl; -kdDebug(5006) << "|| (KMMsgStatusNew == mLastStatus) = " << (KMMsgStatusNew == mLastStatus) << endl; -kdDebug(5006) << "|| (KMMsgStatusUnread == mLastStatus) = " << (KMMsgStatusUnread == mLastStatus) << endl; -kdDebug(5006) << "(mIdOfLastViewedMessage != aMsg->msgId()) = " << (mIdOfLastViewedMessage != aMsg->msgId()) << endl; +kdDebug(5006) << "aMsg->parent() && aMsg->parent() != kmkernel->outboxFolder() = " << (aMsg->parent() && aMsg->parent() != kmkernel->outboxFolder()) << endl; +kdDebug(5006) << "message_was_saved_decrypted_before( aMsg ) = " << message_was_saved_decrypted_before( aMsg ) << endl; +kdDebug(5006) << "this->decryptMessage() = " << decryptMessage() << endl; +kdDebug(5006) << "otp.hasPendingAsyncJobs() = " << otp.hasPendingAsyncJobs() << endl; kdDebug(5006) << " (KMMsgFullyEncrypted == encryptionState) = " << (KMMsgFullyEncrypted == encryptionState) << endl; kdDebug(5006) << "|| (KMMsgPartiallyEncrypted == encryptionState) = " << (KMMsgPartiallyEncrypted == encryptionState) << endl; // only proceed if we were called the normal way - not by // double click on the message (==not running in a separate window) if( (aMsg == message()) + // don't remove encryption in the outbox folder :) + && ( aMsg->parent() && aMsg->parent() != kmkernel->outboxFolder() ) // only proceed if this message was not saved encryptedly before - // to make sure only *new* messages are saved in decrypted form - && ( (KMMsgStatusUnknown == mLastStatus) - || (KMMsgStatusNew == mLastStatus) - || (KMMsgStatusUnread == mLastStatus) ) - // avoid endless recursions - && (mIdOfLastViewedMessage != aMsg->msgId()) + && !message_was_saved_decrypted_before( aMsg ) + // only proceed if the message has actually been decrypted + && decryptMessage() + // only proceed if no pending async jobs are running: + && !otp.hasPendingAsyncJobs() // only proceed if this message is (at least partially) encrypted && ( (KMMsgFullyEncrypted == encryptionState) || (KMMsgPartiallyEncrypted == encryptionState) ) ) { @@ -1690,15 +1665,15 @@ kdDebug(5006) << "KMReaderWin - composing unencrypted message" << endl; //----------------------------------------------------------------------------- -TQString KMReaderWin::writeMsgHeader(KMMessage* aMsg, bool hasVCard, bool topLevel) +TQString KMReaderWin::writeMsgHeader( KMMessage* aMsg, partNode *vCardNode, bool topLevel ) { kdFatal( !headerStyle(), 5006 ) << "trying to writeMsgHeader() without a header style set!" << endl; kdFatal( !headerStrategy(), 5006 ) << "trying to writeMsgHeader() without a header strategy set!" << endl; TQString href; - if (hasVCard) - href = TQString("file:") + KURL::encode_string( mTempFiles.last() ); + if ( vCardNode ) + href = vCardNode->asHREF( "body" ); return headerStyle()->format( aMsg, headerStrategy(), href, mPrinting, topLevel ); } @@ -1763,10 +1738,14 @@ TQString KMReaderWin::createTempDir( const TQString ¶m ) } //----------------------------------------------------------------------------- -void KMReaderWin::showVCard( KMMessagePart * msgPart ) { +void KMReaderWin::showVCard( KMMessagePart *msgPart ) +{ +#if defined(KABC_VCARD_ENCODING_FIX) + const TQByteArray vCard = msgPart->bodyDecodedBinary(); +#else const TQString vCard = msgPart->bodyToUnicode( overrideCodec() ); - - VCardViewer *vcv = new VCardViewer(this, vCard, "vCardDialog"); +#endif + VCardViewer *vcv = new VCardViewer( this, vCard, "vCardDialog" ); vcv->show(); } @@ -1782,19 +1761,13 @@ void KMReaderWin::printMsg() int KMReaderWin::msgPartFromUrl(const KURL &aUrl) { if (aUrl.isEmpty()) return -1; - - bool ok; - if ( aUrl.url().startsWith( "#att" ) ) { - int res = aUrl.url().mid( 4 ).toInt( &ok ); - if ( ok ) return res; - } - if (!aUrl.isLocalFile()) return -1; TQString path = aUrl.path(); uint right = path.findRev('/'); uint left = path.findRev('.', right); + bool ok; int res = path.mid(left + 1, right - left - 1).toInt(&ok); return (ok) ? res : -1; } @@ -1910,7 +1883,8 @@ bool foundSMIMEData( const TQString aUrl, void KMReaderWin::slotUrlOn(const TQString &aUrl) { const KURL url(aUrl); - if ( url.protocol() == "kmail" || url.protocol() == "x-kmail" + + if ( url.protocol() == "kmail" || url.protocol() == "x-kmail" || url.protocol() == "attachment" || (url.protocol().isEmpty() && url.path().isEmpty()) ) { mViewer->setDNDEnabled( false ); } else { @@ -1919,10 +1893,12 @@ void KMReaderWin::slotUrlOn(const TQString &aUrl) if ( aUrl.stripWhiteSpace().isEmpty() ) { KPIM::BroadcastStatus::instance()->reset(); + mHoveredUrl = KURL(); + mLastClickImagePath = TQString(); return; } - mUrlClicked = url; + mHoveredUrl = url; const TQString msg = URLHandlerManager::instance()->statusBarMessage( url, this ); @@ -1934,7 +1910,7 @@ void KMReaderWin::slotUrlOn(const TQString &aUrl) //----------------------------------------------------------------------------- void KMReaderWin::slotUrlOpen(const KURL &aUrl, const KParts::URLArgs &) { - mUrlClicked = aUrl; + mClickedUrl = aUrl; if ( URLHandlerManager::instance()->handleClick( aUrl, this ) ) return; @@ -1947,17 +1923,43 @@ void KMReaderWin::slotUrlOpen(const KURL &aUrl, const KParts::URLArgs &) void KMReaderWin::slotUrlPopup(const TQString &aUrl, const TQPoint& aPos) { const KURL url( aUrl ); - mUrlClicked = url; + mClickedUrl = url; + + if ( url.protocol() == "mailto" ) { + mCopyURLAction->setText( i18n( "Copy Email Address" ) ); + } else { + mCopyURLAction->setText( i18n( "Copy Link Address" ) ); + } if ( URLHandlerManager::instance()->handleContextMenuRequest( url, aPos, this ) ) return; if ( message() ) { kdWarning( 5006 ) << "KMReaderWin::slotUrlPopup(): Unhandled URL right-click!" << endl; - emit popupMenu( *message(), url, aPos ); + emitPopupMenu( url, aPos ); } } +// Checks if the given node has a parent node that is a DIV which has an ID attribute +// with the value specified here +static bool hasParentDivWithId( const DOM::Node &start, const TQString &id ) +{ + if ( start.isNull() ) + return false; + + if ( start.nodeName().string() == "div" ) { + for ( unsigned int i = 0; i < start.attributes().length(); i++ ) { + if ( start.attributes().item( i ).nodeName().string() == "id" && + start.attributes().item( i ).nodeValue().string() == id ) + return true; + } + } + + if ( !start.parentNode().isNull() ) + return hasParentDivWithId( start.parentNode(), id ); + else return false; +} + //----------------------------------------------------------------------------- void KMReaderWin::showAttachmentPopup( int id, const TQString & name, const TQPoint & p ) { @@ -1969,14 +1971,22 @@ void KMReaderWin::showAttachmentPopup( int id, const TQString & name, const TQPo menu->insertItem(i18n("to view something", "View"), 3); menu->insertItem(SmallIcon("filesaveas"),i18n("Save As..."), 4); menu->insertItem(SmallIcon("editcopy"), i18n("Copy"), 9 ); - if ( GlobalSettings::self()->allowAttachmentEditing() ) + const bool canChange = message()->parent() ? !message()->parent()->isReadOnly() : false; + if ( GlobalSettings::self()->allowAttachmentEditing() && canChange ) menu->insertItem(SmallIcon("edit"), i18n("Edit Attachment"), 8 ); - if ( GlobalSettings::self()->allowAttachmentDeletion() ) + if ( GlobalSettings::self()->allowAttachmentDeletion() && canChange ) menu->insertItem(SmallIcon("editdelete"), i18n("Delete Attachment"), 7 ); if ( name.endsWith( ".xia", false ) && Kleo::CryptoBackendFactory::instance()->protocol( "Chiasmus" ) ) menu->insertItem( i18n( "Decrypt With Chiasmus..." ), 6 ); menu->insertItem(i18n("Properties"), 5); + + const bool attachmentInHeader = hasParentDivWithId( mViewer->nodeUnderMouse(), "attachmentInjectionPoint" ); + const bool hasScrollbar = mViewer->view()->verticalScrollBar()->isVisible(); + if ( attachmentInHeader && hasScrollbar ) { + menu->insertItem( i18n("Scroll To"), 10 ); + } + connect(menu, TQT_SIGNAL(activated(int)), this, TQT_SLOT(slotHandleAttachment(int))); menu->exec( p ,0 ); delete menu; @@ -2032,6 +2042,8 @@ void KMReaderWin::slotHandleAttachment( int choice ) urls.append( url ); KURLDrag* drag = new KURLDrag( urls, this ); TQApplication::clipboard()->setData( drag, QClipboard::Clipboard ); + } else if ( choice == 10 ) { // Scroll To + scrollToAttachment( node ); } } @@ -2064,7 +2076,7 @@ void KMReaderWin::slotCopySelectedText() //----------------------------------------------------------------------------- -void KMReaderWin::atmViewMsg(KMMessagePart* aMsgPart) +void KMReaderWin::atmViewMsg( KMMessagePart* aMsgPart, int nodeId ) { assert(aMsgPart!=0); KMMessage* msg = new KMMessage; @@ -2076,7 +2088,7 @@ void KMReaderWin::atmViewMsg(KMMessagePart* aMsgPart) msg->setUID(message()->UID()); msg->setReadyToShow(true); KMReaderMainWin *win = new KMReaderMainWin(); - win->showMsg( overrideEncoding(), msg ); + win->showMsg( overrideEncoding(), msg, message()->getMsgSerNum(), nodeId ); win->show(); } @@ -2168,6 +2180,7 @@ void KMReaderWin::setMsgPart( KMMessagePart* aMsgPart, bool aHTML, htmlWriter()->end(); setCaption( i18n("View Attachment: %1").arg( pname ) ); show(); + delete iio; } else { htmlWriter()->begin( mCSSHelper->cssDefinitions( isFixedFont() ) ); htmlWriter()->queue( mCSSHelper->htmlHead( isFixedFont() ) ); @@ -2208,7 +2221,7 @@ void KMReaderWin::slotAtmView( int id, const TQString& name ) if (pname.isEmpty()) pname="unnamed"; // image Attachment is saved already if (kasciistricmp(msgPart.typeStr(), "message")==0) { - atmViewMsg(&msgPart); + atmViewMsg( &msgPart,id ); } else if ((kasciistricmp(msgPart.typeStr(), "text")==0) && (kasciistricmp(msgPart.subtypeStr(), "x-vcard")==0)) { setMsgPart( &msgPart, htmlMail(), name, pname ); @@ -2239,7 +2252,7 @@ void KMReaderWin::openAttachment( int id, const TQString & name ) KMMessagePart& msgPart = node->msgPart(); if (kasciistricmp(msgPart.typeStr(), "message")==0) { - atmViewMsg(&msgPart); + atmViewMsg( &msgPart, id ); return; } @@ -2431,7 +2444,7 @@ void KMReaderWin::update( bool force ) { KMMessage* msg = message(); if ( msg ) - setMsg( msg, force ); + setMsg( msg, force, true /* updateOnly */ ); } @@ -2467,7 +2480,7 @@ void KMReaderWin::slotUrlClicked() identity = message()->parent()->identity(); } - KMCommand *command = new KMUrlClickedCommand( mUrlClicked, identity, this, + KMCommand *command = new KMUrlClickedCommand( mClickedUrl, identity, this, false, mainWidget ); command->start(); } @@ -2475,14 +2488,14 @@ void KMReaderWin::slotUrlClicked() //----------------------------------------------------------------------------- void KMReaderWin::slotMailtoCompose() { - KMCommand *command = new KMMailtoComposeCommand( mUrlClicked, message() ); + KMCommand *command = new KMMailtoComposeCommand( mClickedUrl, message() ); command->start(); } //----------------------------------------------------------------------------- void KMReaderWin::slotMailtoForward() { - KMCommand *command = new KMMailtoForwardCommand( mMainWindow, mUrlClicked, + KMCommand *command = new KMMailtoForwardCommand( mMainWindow, mClickedUrl, message() ); command->start(); } @@ -2490,7 +2503,7 @@ void KMReaderWin::slotMailtoForward() //----------------------------------------------------------------------------- void KMReaderWin::slotMailtoAddAddrBook() { - KMCommand *command = new KMMailtoAddAddrBookCommand( mUrlClicked, + KMCommand *command = new KMMailtoAddAddrBookCommand( mClickedUrl, mMainWindow); command->start(); } @@ -2498,7 +2511,7 @@ void KMReaderWin::slotMailtoAddAddrBook() //----------------------------------------------------------------------------- void KMReaderWin::slotMailtoOpenAddrBook() { - KMCommand *command = new KMMailtoOpenAddrBookCommand( mUrlClicked, + KMCommand *command = new KMMailtoOpenAddrBookCommand( mClickedUrl, mMainWindow ); command->start(); } @@ -2509,7 +2522,7 @@ void KMReaderWin::slotUrlCopy() // we don't necessarily need a mainWidget for KMUrlCopyCommand so // it doesn't matter if the dynamic_cast fails. KMCommand *command = - new KMUrlCopyCommand( mUrlClicked, + new KMUrlCopyCommand( mClickedUrl, dynamic_cast( mMainWindow ) ); command->start(); } @@ -2518,30 +2531,30 @@ void KMReaderWin::slotUrlCopy() void KMReaderWin::slotUrlOpen( const KURL &url ) { if ( !url.isEmpty() ) - mUrlClicked = url; - KMCommand *command = new KMUrlOpenCommand( mUrlClicked, this ); + mClickedUrl = url; + KMCommand *command = new KMUrlOpenCommand( mClickedUrl, this ); command->start(); } //----------------------------------------------------------------------------- void KMReaderWin::slotAddBookmarks() { - KMCommand *command = new KMAddBookmarksCommand( mUrlClicked, this ); + KMCommand *command = new KMAddBookmarksCommand( mClickedUrl, this ); command->start(); } //----------------------------------------------------------------------------- void KMReaderWin::slotUrlSave() { - KMCommand *command = new KMUrlSaveCommand( mUrlClicked, mMainWindow ); + KMCommand *command = new KMUrlSaveCommand( mClickedUrl, mMainWindow ); command->start(); } //----------------------------------------------------------------------------- void KMReaderWin::slotMailtoReply() { - KMCommand *command = new KMMailtoReplyCommand( mMainWindow, mUrlClicked, - message(), copyText() ); + KMCommand *command = new KMMailtoReplyCommand( mMainWindow, mClickedUrl, + message(), copyText() ); command->start(); } @@ -2584,6 +2597,14 @@ void KMReaderWin::slotSaveAttachments() saveCommand->start(); } +//----------------------------------------------------------------------------- +void KMReaderWin::saveAttachment( const KURL &tempFileName ) +{ + mAtmCurrent = msgPartFromUrl( tempFileName ); + mAtmCurrentName = mClickedUrl.path(); + slotHandleAttachment( KMHandleAttachmentCommand::Save ); // save +} + //----------------------------------------------------------------------------- void KMReaderWin::slotSaveMsg() { @@ -2597,10 +2618,35 @@ void KMReaderWin::slotSaveMsg() //----------------------------------------------------------------------------- void KMReaderWin::slotIMChat() { - KMCommand *command = new KMIMChatCommand( mUrlClicked, message() ); + KMCommand *command = new KMIMChatCommand( mClickedUrl, message() ); command->start(); } +//----------------------------------------------------------------------------- +static TQString linkForNode( const DOM::Node &node ) +{ + try { + if ( node.isNull() ) + return TQString(); + + const DOM::NamedNodeMap attributes = node.attributes(); + if ( !attributes.isNull() ) { + const DOM::Node href = attributes.getNamedItem( DOM::DOMString( "href" ) ); + if ( !href.isNull() ) { + return href.nodeValue().string(); + } + } + if ( !node.parentNode().isNull() ) { + return linkForNode( node.parentNode() ); + } else { + return TQString(); + } + } catch ( DOM::DOMException &e ) { + kdWarning(5006) << "Got an exception when trying to determine link under cursor!" << endl; + return TQString(); + } +} + //----------------------------------------------------------------------------- bool KMReaderWin::eventFilter( TQObject *, TQEvent *e ) { @@ -2608,17 +2654,87 @@ bool KMReaderWin::eventFilter( TQObject *, TQEvent *e ) TQMouseEvent* me = static_cast(e); if ( me->button() == LeftButton && ( me->state() & ShiftButton ) ) { // special processing for shift+click - mAtmCurrent = msgPartFromUrl( mUrlClicked ); - if ( mAtmCurrent < 0 ) return false; // not an attachment - mAtmCurrentName = mUrlClicked.path(); - slotHandleAttachment( KMHandleAttachmentCommand::Save ); // save - return true; // eat event + URLHandlerManager::instance()->handleShiftClick( mHoveredUrl, this ); + return true; + } + + if ( me->button() == LeftButton ) { + + TQString imagePath; + const DOM::Node nodeUnderMouse = mViewer->nodeUnderMouse(); + if ( !nodeUnderMouse.isNull() ) { + const DOM::NamedNodeMap attributes = nodeUnderMouse.attributes(); + if ( !attributes.isNull() ) { + const DOM::Node src = attributes.getNamedItem( DOM::DOMString( "src" ) ); + if ( !src.isNull() ) { + imagePath = src.nodeValue().string(); + } + } + } + + mCanStartDrag = URLHandlerManager::instance()->willHandleDrag( mHoveredUrl, imagePath, this ); + mLastClickPosition = me->pos(); + mLastClickImagePath = imagePath; } } + + if ( e->type() == TQEvent::MouseButtonRelease ) { + mCanStartDrag = false; + } + + if ( e->type() == TQEvent::MouseMove ) { + TQMouseEvent* me = static_cast( e ); + + // Handle this ourselves instead of connecting to mViewer::onURL(), since KHTML misses some + // notifications in case we started a drag ourselves + slotUrlOn( linkForNode( mViewer->nodeUnderMouse() ) ); + + if ( ( mLastClickPosition - me->pos() ).manhattanLength() > KGlobalSettings::dndEventDelay() ) { + if ( mCanStartDrag && ( !( mHoveredUrl.isEmpty() && mLastClickImagePath.isEmpty() ) ) ) { + if ( URLHandlerManager::instance()->handleDrag( mHoveredUrl, mLastClickImagePath, this ) ) { + mCanStartDrag = false; + slotUrlOn( TQString() ); + + // HACK: Send a mouse release event to the KHTMLView, as otherwise that will be missed in + // case we started a drag. If the event is missed, the HTML view gets into a wrong + // state, in which funny things like unsolicited drags start to happen. + TQMouseEvent mouseEvent( TQEvent::MouseButtonRelease, me->pos(), TQt::NoButton, TQt::NoButton ); + static_cast( mViewer->view() )->eventFilter( mViewer->view()->viewport(), + &mouseEvent ); + return true; + } + } + } + } + // standard event processing return false; } +void KMReaderWin::fillCommandInfo( partNode *node, KMMessage **msg, int *nodeId ) +{ + Q_ASSERT( msg && nodeId ); + + if ( mSerNumOfOriginalMessage != 0 ) { + KMFolder *folder = 0; + int index = -1; + KMMsgDict::instance()->getLocation( mSerNumOfOriginalMessage, &folder, &index ); + if ( folder && index != -1 ) + *msg = folder->getMsg( index ); + + if ( !( *msg ) ) { + kdWarning( 5006 ) << "Unable to find the original message, aborting attachment deletion!" << endl; + return; + } + + *nodeId = node->nodeId() + mNodeIdOffset; + } + else { + *nodeId = node->nodeId(); + *msg = message(); + } +} + void KMReaderWin::slotDeleteAttachment(partNode * node) { if ( KMessageBox::warningContinueCancel( this, @@ -2627,8 +2743,52 @@ void KMReaderWin::slotDeleteAttachment(partNode * node) != KMessageBox::Continue ) { return; } - KMDeleteAttachmentCommand* command = new KMDeleteAttachmentCommand( node, message(), this ); - command->start(); + + int nodeId = -1; + KMMessage *msg = 0; + fillCommandInfo( node, &msg, &nodeId ); + if ( msg && nodeId != -1 ) { + KMDeleteAttachmentCommand* command = new KMDeleteAttachmentCommand( nodeId, msg, this ); + command->start(); + connect( command, TQT_SIGNAL( completed( KMCommand * ) ), + this, TQT_SLOT( updateReaderWin() ) ); + connect( command, TQT_SIGNAL( completed( KMCommand * ) ), + this, TQT_SLOT( disconnectMsgAdded() ) ); + + // ### HACK: Since the command will do delete + add, a new message will arrive. However, we don't + // want the selection to change. Therefore, as soon as a new message arrives, select it, and then + // disconnect. + // Of course the are races, another message can arrive before ours, but we take the risk. + // And it won't work properly with multiple main windows + const KMHeaders * const headers = KMKernel::self()->getKMMainWidget()->headers(); + connect( headers, TQT_SIGNAL( msgAddedToListView( TQListViewItem* ) ), + this, TQT_SLOT( msgAdded( TQListViewItem* ) ) ); + } + + // If we are operating on a copy of parts of the message, make sure to update the copy as well. + if ( mSerNumOfOriginalMessage != 0 && message() ) { + message()->deleteBodyPart( node->nodeId() ); + update( true ); + } +} + +void KMReaderWin::msgAdded( TQListViewItem *item ) +{ + // A new message was added to the message list view. Select it. + // This is only connected right after we started a attachment delete command, so we expect a new + // message. Disconnect right afterwards, we only want this particular message to be selected. + disconnectMsgAdded(); + KMHeaders * const headers = KMKernel::self()->getKMMainWidget()->headers(); + headers->setCurrentItem( item ); + headers->clearSelection(); + headers->setSelected( item, true ); +} + +void KMReaderWin::disconnectMsgAdded() +{ + const KMHeaders *const headers = KMKernel::self()->getKMMainWidget()->headers(); + disconnect( headers, TQT_SIGNAL( msgAddedToListView( TQListViewItem* ) ), + this, TQT_SLOT( msgAdded( TQListViewItem* ) ) ); } void KMReaderWin::slotEditAttachment(partNode * node) @@ -2639,8 +2799,16 @@ void KMReaderWin::slotEditAttachment(partNode * node) != KMessageBox::Continue ) { return; } - KMEditAttachmentCommand* command = new KMEditAttachmentCommand( node, message(), this ); - command->start(); + + int nodeId = -1; + KMMessage *msg = 0; + fillCommandInfo( node, &msg, &nodeId ); + if ( msg && nodeId != -1 ) { + KMEditAttachmentCommand* command = new KMEditAttachmentCommand( nodeId, msg, this ); + command->start(); + } + + // FIXME: If we are operating on a copy of parts of the message, make sure to update the copy as well. } KMail::CSSHelper* KMReaderWin::cssHelper() @@ -2655,6 +2823,42 @@ bool KMReaderWin::decryptMessage() const return true; } +void KMReaderWin::scrollToAttachment( const partNode *node ) +{ + DOM::Document doc = mViewer->htmlDocument(); + + // The anchors for this are created in ObjectTreeParser::parseObjectTree() + mViewer->gotoAnchor( TQString::fromLatin1( "att%1" ).arg( node->nodeId() ) ); + + // Remove any old color markings which might be there + const partNode *root = node->topLevelParent(); + for ( int i = 0; i <= root->totalChildCount() + 1; i++ ) { + DOM::Element attachmentDiv = doc.getElementById( TQString( "attachmentDiv%1" ).arg( i + 1 ) ); + if ( !attachmentDiv.isNull() ) + attachmentDiv.removeAttribute( "style" ); + } + + // Don't mark hidden nodes, that would just produce a strange yellow line + if ( node->isDisplayedHidden() ) + return; + + // Now, color the div of the attachment in yellow, so that the user sees what happened. + // We created a special marked div for this in writeAttachmentMarkHeader() in ObjectTreeParser, + // find and modify that now. + DOM::Element attachmentDiv = doc.getElementById( TQString( "attachmentDiv%1" ).arg( node->nodeId() ) ); + if ( attachmentDiv.isNull() ) { + kdWarning( 5006 ) << "Could not find attachment div for attachment " << node->nodeId() << endl; + return; + } + + attachmentDiv.setAttribute( "style", TQString( "border:2px solid %1" ) + .arg( cssHelper()->pgpWarnColor().name() ) ); + + // Update rendering, otherwise the rendering is not updated when the user clicks on an attachment + // that causes scrolling and the open attachment dialog + doc.updateRendering(); +} + void KMReaderWin::injectAttachments() { // inject attachments in header view @@ -2668,30 +2872,33 @@ void KMReaderWin::injectAttachments() TQString visibility; TQString urlHandle; TQString imgSrc; - if( !showAttachmentQuicklist() ) - { - urlHandle.append( "kmail:showAttachmentQuicklist" ); - imgSrc.append( "attachmentQuicklistClosed.png" ); - } else { - urlHandle.append( "kmail:hideAttachmentQuicklist" ); - imgSrc.append( "attachmentQuicklistOpened.png" ); - } + if( !showAttachmentQuicklist() ) { + urlHandle.append( "kmail:showAttachmentQuicklist" ); + imgSrc.append( "attachmentQuicklistClosed.png" ); + } else { + urlHandle.append( "kmail:hideAttachmentQuicklist" ); + imgSrc.append( "attachmentQuicklistOpened.png" ); + } TQString html = renderAttachments( mRootNode, TQApplication::palette().active().background() ); if ( html.isEmpty() ) return; - if ( headerStyle() == HeaderStyle::fancy() ) - html.prepend( TQString::fromLatin1("
%1 
" ).arg(i18n("Attachments:")) ); - - if ( headerStyle() == HeaderStyle::enterprise() ) { - TQString link(""); - link += "
"; - html.prepend( link ); - } + TQString link(""); + if ( headerStyle() == HeaderStyle::fancy() ) { + link += "
"; + html.prepend( link ); + html.prepend( TQString::fromLatin1( "
%1 
" ). + arg( i18n( "Attachments:" ) ) ); + } else { + link += "
"; + html.prepend( link ); + } - assert( injectionPoint.tagName() == "div" ); - static_cast( injectionPoint ).setInnerHTML( html ); + assert( injectionPoint.tagName() == "div" ); + static_cast( injectionPoint ).setInnerHTML( html ); } static TQColor nextColor( const TQColor & c ) @@ -2712,48 +2919,45 @@ TQString KMReaderWin::renderAttachments(partNode * node, const TQColor &bgColor if ( !subHtml.isEmpty() ) { TQString visibility; - if( !showAttachmentQuicklist() ) - { - visibility.append( "display:none;" ); - } + if ( !showAttachmentQuicklist() ) { + visibility.append( "display:none;" ); + } TQString margin; if ( node != mRootNode || headerStyle() != HeaderStyle::enterprise() ) margin = "padding:2px; margin:2px; "; - if ( node->msgPart().typeStr() == "message" || node == mRootNode ) + TQString align = "left"; + if ( headerStyle() == HeaderStyle::enterprise() ) + align = "right"; + if ( node->msgPart().typeStr().lower() == "message" || node == mRootNode ) html += TQString::fromLatin1("
").arg( bgColor.name() ).arg( margin ).arg( visibility ); + "vertical-align:middle; float:%3; %4\">").arg( bgColor.name() ).arg( margin ) + .arg( align ).arg( visibility ); html += subHtml; - if ( node->msgPart().typeStr() == "message" || node == mRootNode ) + if ( node->msgPart().typeStr().lower() == "message" || node == mRootNode ) html += "
"; } } else { - TQString label, icon; - icon = node->msgPart().iconName( KIcon::Small ); - label = node->msgPart().contentDescription(); - if( label.isEmpty() ) - label = node->msgPart().name().stripWhiteSpace(); - if( label.isEmpty() ) - label = node->msgPart().fileName(); - bool typeBlacklisted = node->msgPart().typeStr() == "multipart"; - if ( !typeBlacklisted && node->msgPart().typeStr() == "application" ) { - typeBlacklisted = node->msgPart().subtypeStr() == "pgp-encrypted" - || node->msgPart().subtypeStr() == "pgp-signature" - || node->msgPart().subtypeStr() == "pkcs7-mime" - || node->msgPart().subtypeStr() == "pkcs7-signature"; - } - typeBlacklisted = typeBlacklisted || node == mRootNode; - if ( !label.isEmpty() && !icon.isEmpty() && !typeBlacklisted ) { + partNode::AttachmentDisplayInfo info = node->attachmentDisplayInfo(); + if ( info.displayInHeader ) { html += "
"; html += TQString::fromLatin1( "" ).arg( bgColor.name() ); - html += TQString::fromLatin1( "" ).arg( node->nodeId() ); - html += " "; + TQString fileName = writeMessagePartToTempFile( &node->msgPart(), node->nodeId() ); + TQString href = node->asHREF( "header" ); + html += TQString::fromLatin1( "" ); + html += " "; if ( headerStyle() == HeaderStyle::enterprise() ) { TQFont bodyFont = mCSSHelper->bodyFont( isFixedFont() ); TQFontMetrics fm( bodyFont ); - html += KStringHandler::rPixelSqueeze( label, fm, 140 ); - } else - html += label; + html += KStringHandler::rPixelSqueeze( info.label, fm, 140 ); + } else if ( headerStyle() == HeaderStyle::fancy() ) { + TQFont bodyFont = mCSSHelper->bodyFont( isFixedFont() ); + TQFontMetrics fm( bodyFont ); + html += KStringHandler::rPixelSqueeze( info.label, fm, 640 ); + } else { + html += info.label; + } html += "
"; } } @@ -2762,6 +2966,67 @@ TQString KMReaderWin::renderAttachments(partNode * node, const TQColor &bgColor return html; } +using namespace KMail::Interface; + +void KMReaderWin::setBodyPartMemento( const partNode * node, const TQCString & which, BodyPartMemento * memento ) +{ + const TQCString index = node->path() + ':' + which.lower(); + + const std::map::iterator it = mBodyPartMementoMap.lower_bound( index ); + if ( it != mBodyPartMementoMap.end() && it->first == index ) { + + if ( memento && memento == it->second ) + return; + + delete it->second; + + if ( memento ) { + it->second = memento; + } + else { + mBodyPartMementoMap.erase( it ); + } + + } else { + if ( memento ) { + mBodyPartMementoMap.insert( it, std::make_pair( index, memento ) ); + } + } + + if ( Observable * o = memento ? memento->asObservable() : 0 ) + o->attach( this ); +} + +BodyPartMemento * KMReaderWin::bodyPartMemento( const partNode * node, const TQCString & which ) const +{ + const TQCString index = node->path() + ':' + which.lower(); + const std::map::const_iterator it = mBodyPartMementoMap.find( index ); + if ( it == mBodyPartMementoMap.end() ) { + return 0; + } + else { + return it->second; + } +} + +static void detach_and_delete( BodyPartMemento * memento, KMReaderWin * obs ) { + if ( Observable * const o = memento ? memento->asObservable() : 0 ) + o->detach( obs ); + delete memento; +} + +void KMReaderWin::clearBodyPartMementos() +{ + for ( std::map::const_iterator it = mBodyPartMementoMap.begin(), end = mBodyPartMementoMap.end() ; it != end ; ++it ) + // Detach the memento from the reader. When cancelling it, it might trigger an update of the + // reader, which we are not interested in, and which is dangerous, since half the mementos are + // already deleted. + // https://issues.kolab.org/issue4187 + detach_and_delete( it->second, this ); + + mBodyPartMementoMap.clear(); +} + #include "kmreaderwin.moc" diff --git a/kmail/kmreaderwin.h b/kmail/kmreaderwin.h index da7029366..32c7abdf6 100644 --- a/kmail/kmreaderwin.h +++ b/kmail/kmreaderwin.h @@ -14,6 +14,8 @@ #include "kmmimeparttree.h" // Needed for friend declaration. #include "interfaces/observer.h" +#include + class TQFrame; class TQSplitter; class TQHBox; @@ -42,6 +44,7 @@ class KMMessagePart; namespace KMail { namespace Interface { class Observable; + class BodyPartMemento; } class PartMetaData; class ObjectTreeParser; @@ -139,7 +142,20 @@ public: /** Set the message that shall be shown. If msg is 0, an empty page is displayed. */ - virtual void setMsg(KMMessage* msg, bool force = false); + virtual void setMsg( KMMessage* msg, bool force = false, bool updateOnly = false ); + + /** + * This should be called when setting a message that was constructed from another message, which + * is the case when viewing encapsulated messages in the seperate reader window. + * We need to know the serial number of the original message, and at which part index the encapsulated + * message was at that original message, so that deleting and editing attachments can work on the + * original message. + * + * This is a HACK. There really shouldn't be a copy of the original mail. + * + * @see slotDeleteAttachment, slotEditAttachment, fillCommandInfo + */ + void setOriginalMsg( unsigned long serNumOfOriginalMessage, int nodeIdOffset ); /** Instead of settings a message to be shown sets a message part to be shown */ @@ -210,8 +226,12 @@ public: /** Enable the displaying of messages again after an URL was displayed */ void enableMsgDisplay(); - /** View message part of type message/RFC822 in extra viewer window. */ - void atmViewMsg(KMMessagePart* msgPart); + /** + * View message part of type message/RFC822 in extra viewer window. + * @param msgPart the part to display + * @param nodeId the part index of the message part that is displayed + */ + void atmViewMsg( KMMessagePart* msgPart, int nodeId ); bool atBottom() const; @@ -266,6 +286,7 @@ public: KMMessage* message(KMFolder** folder=0) const; void openAttachment( int id, const TQString & name ); + void saveAttachment( const KURL &tempFileName ); void emitUrlClicked( const KURL & url, int button ) { emit urlClicked( url, button ); @@ -302,6 +323,27 @@ public: /* show or hide the list that points to the attachments */ void setShowAttachmentQuicklist( bool showAttachmentQuicklist = true ) { mShowAttachmentQuicklist = showAttachmentQuicklist; } + // This controls whether a Toltec invitation is shown in its raw form or as a replacement text. + // This can be toggled with the "kmail:showRawToltecMail" link. + bool showRawToltecMail() const { return mShowRawToltecMail; } + void setShowRawToltecMail( bool showRawToltecMail ) { mShowRawToltecMail = showRawToltecMail; } + + /* retrieve BodyPartMemento of id \a which for partNode \a node */ + KMail::Interface::BodyPartMemento * bodyPartMemento( const partNode * node, const TQCString & which ) const; + + /* set/replace BodyPartMemento \a memento of id \a which for + partNode \a node. If there was a BodyPartMemento registered + already, replaces (deletes) that one. */ + void setBodyPartMemento( const partNode * node, const TQCString & which, KMail::Interface::BodyPartMemento * memento ); + + /// Scrolls to the given attachment and marks it with a yellow border + void scrollToAttachment( const partNode *node ); + +private: + /* deletes all BodyPartMementos. Use this when skipping to another + message (as opposed to re-loading the same one again). */ + void clearBodyPartMementos(); + signals: /** Emitted after parsing of a message to have it stored in unencrypted state in it's folder. */ @@ -383,6 +425,15 @@ public slots: void slotLevelQuote( int l ); void slotTouchMessage(); + /** + * Find the node ID and the message of the attachment that should be edited or deleted. + * This is used when setOriginalMsg() was called before, in that case we want to operate + * on the original message instead of our copy. + * + * @see setOriginalMsg + */ + void fillCommandInfo( partNode *node, KMMessage **msg, int *nodeId ); + void slotDeleteAttachment( partNode* node ); void slotEditAttachment( partNode* node ); @@ -402,12 +453,19 @@ protected slots: void slotSmartAttachments(); void slotInlineAttachments(); void slotHideAttachments(); + void slotHeaderOnlyAttachments(); /** Some attachment operations. */ void slotAtmView( int id, const TQString& name ); void slotDelayedResize(); void slotHandleAttachment( int ); + /** Helper functions used to change message selection in the message list after deleting + * an attachment, see slotDeleteAttachment() + */ + void disconnectMsgAdded(); + void msgAdded( TQListViewItem *item ); + protected: /** reimplemented in order to update the frame width in case of a changed GUI style */ @@ -432,7 +490,7 @@ protected: /** Creates a nice mail header depending on the current selected header style. */ - TQString writeMsgHeader(KMMessage* aMsg, bool hasVCard=false, bool topLevel=false); + TQString writeMsgHeader(KMMessage* aMsg, partNode *vCardNode = 0, bool topLevel=false ); /** Writes the given message part to a temporary file and returns the name of this file or TQString::null if writing failed. @@ -485,6 +543,11 @@ private: int mAtmCurrent; TQString mAtmCurrentName; KMMessage *mMessage; + + // See setOriginalMsg() for an explaination for those two. + unsigned long mSerNumOfOriginalMessage; + int mNodeIdOffset; + // widgets: TQSplitter * mSplitter; TQHBox *mBox; @@ -507,7 +570,6 @@ private: bool mMsgDisplay; bool mNoMDNsWhenEncrypted; unsigned long mLastSerNum; - KMMsgStatus mLastStatus; KMail::CSSHelper * mCSSHelper; bool mUseFixedFont; @@ -526,19 +588,29 @@ private: KAction *mMailToComposeAction, *mMailToReplyAction, *mMailToForwardAction, *mAddAddrBookAction, *mOpenAddrBookAction, *mCopyAction, *mCopyURLAction, *mUrlOpenAction, *mUrlSaveAsAction, *mAddBookmarksAction, *mStartIMChatAction, *mSelectAllAction; + KToggleAction *mHeaderOnlyAttachmentsAction; KSelectAction *mSelectEncodingAction; KToggleAction *mToggleFixFontAction; - KURL mUrlClicked; + + KURL mHoveredUrl; + KURL mClickedUrl; + TQPoint mLastClickPosition; + TQString mLastClickImagePath; + bool mCanStartDrag; + KMail::HtmlWriter * mHtmlWriter; + std::map mBodyPartMementoMap; // an attachment should be updated bool mAtmUpdate; int mChoice; unsigned long mWaitingForSerNum; float mSavedRelativePosition; - int mLevelQuote; + int mLevelQuote; bool mDecrytMessageOverwrite; bool mShowSignatureDetails; bool mShowAttachmentQuicklist; + bool mShowRawToltecMail; + bool mExternalWindow; }; diff --git a/kmail/kmsearchpattern.cpp b/kmail/kmsearchpattern.cpp index bec476ef6..3d97a2b31 100644 --- a/kmail/kmsearchpattern.cpp +++ b/kmail/kmsearchpattern.cpp @@ -27,6 +27,8 @@ using KMail::FilterLog; #include #include +#include +#include #include @@ -58,7 +60,8 @@ static struct _statusNames statusNames[] = { { "To Do", KMMsgStatusTodo }, { "Spam", KMMsgStatusSpam }, { "Ham", KMMsgStatusHam }, - { "Has Attachment", KMMsgStatusHasAttach } + { "Has Attachment", KMMsgStatusHasAttach }, + { "Invitation", KMMsgStatusHasInvitation } }; static const int numStatusNames = sizeof statusNames / sizeof ( struct _statusNames ); @@ -280,7 +283,7 @@ bool KMSearchRuleString::matches( const DwString & aStr, KMMessage & msg, start += headerLen; size_t stop = aStr.find( '\n', start ); char ch = '\0'; - while ( stop != DwString::npos && ( ch = aStr.at( stop + 1 ) ) == ' ' || ch == '\t' ) + while ( stop != DwString::npos && ( ( ch = aStr.at( stop + 1 ) ) == ' ' || ch == '\t' ) ) stop = aStr.find( '\n', stop + 1 ); const int len = stop == DwString::npos ? aStr.length() - start : stop - start ; const TQCString codedValue( aStr.data() + start, len + 1 ); @@ -333,7 +336,19 @@ bool KMSearchRuleString::matches( const KMMessage * msg ) const bool logContents = true; if( field() == "" ) { - msgContents = msg->asString(); + + // When searching in the complete message, we can't simply use msg->asString() here, + // as that wouldn't decode the body. Therefore we use the decoded body and all decoded + // header fields and add all to the one big search string. + msgContents += msg->bodyToUnicode(); + const DwHeaders& headers = msg->headers(); + const DwField * dwField = headers.FirstField(); + while( dwField != 0 ) { + const char * const fieldName = dwField->FieldNameStr().c_str(); + const TQString fieldValue = msg->headerFields( fieldName ).join( " " ); + msgContents += " " + fieldValue; + dwField = dwField->Next(); + } logContents = false; } else if ( field() == "" ) { msgContents = msg->bodyToUnicode(); diff --git a/kmail/kmsearchpattern.h b/kmail/kmsearchpattern.h index 00a26c7b4..45ffe6808 100644 --- a/kmail/kmsearchpattern.h +++ b/kmail/kmsearchpattern.h @@ -230,7 +230,8 @@ namespace KMail { { I18N_NOOP( "Spam" ), "kmmsgspam" }, { I18N_NOOP( "Ham" ), "kmmsgham" }, { I18N_NOOP( "To Do" ), "kmmsgtodo" }, - { I18N_NOOP( "Has Attachment"), "kmmsgattachment" } + { I18N_NOOP( "Invitation" ), "kmmsginvitation" }, + { I18N_NOOP( "Has Attachment"), "kmmsgattachment" } //must be last }; // If you change the ordering here; also do it in the array above enum StatusValueTypes { @@ -249,7 +250,8 @@ namespace KMail { StatusSpam = 12, StatusHam = 13, StatusToDo = 14, - StatusHasAttachment = 15 + StatusInvitation = 15, + StatusHasAttachment = 16 //must be last }; static const int StatusValueCount = @@ -291,7 +293,7 @@ public: KConfig group and there is a constructor, mainly used by KMFilter to initialize from a preset KConfig-Group. - From a class hierarchy point of view, it is a TQPtrList of + From a class hierarchy point of view, it is a TQPtrList of KMSearchRule's that adds the boolean operators (see Operator) 'and' and 'or' that connect the rules logically, and has a name under which it could be stored in the config file. diff --git a/kmail/kmsearchpatternedit.cpp b/kmail/kmsearchpatternedit.cpp index a928e57f3..399bc0e15 100644 --- a/kmail/kmsearchpatternedit.cpp +++ b/kmail/kmsearchpatternedit.cpp @@ -37,7 +37,13 @@ static const struct { { "", I18N_NOOP( "All Recipients" ) }, { "", I18N_NOOP( "Size in Bytes" ) }, { "", I18N_NOOP( "Age in Days" ) }, - { "", I18N_NOOP( "Message Status" ) } + { "", I18N_NOOP( "Message Status" ) }, + { "Subject", I18N_NOOP( "Subject" ) }, + { "From", I18N_NOOP( "From" ) }, + { "To", I18N_NOOP( "To" ) }, + { "CC", I18N_NOOP( "CC" ) }, + { "Reply-To", I18N_NOOP( "Reply To" ) }, + { "Organization", I18N_NOOP( "Organization" ) } }; static const int SpecialRuleFieldsCount = sizeof( SpecialRuleFields ) / sizeof( *SpecialRuleFields ); @@ -247,16 +253,16 @@ void KMSearchRuleWidget::initFieldList( bool headersOnly, bool absoluteDates ) mFilterFieldList.append( i18n( SpecialRuleFields[Size].displayName ) ); if ( !absoluteDates ) mFilterFieldList.append( i18n( SpecialRuleFields[AgeInDays].displayName ) ); - mFilterFieldList.append( i18n( SpecialRuleFields[Status].displayName ) ); + mFilterFieldList.append( i18n( SpecialRuleFields[Subject].displayName ) ); + mFilterFieldList.append( i18n( SpecialRuleFields[From].displayName ) ); + mFilterFieldList.append( i18n( SpecialRuleFields[To].displayName ) ); + mFilterFieldList.append( i18n( SpecialRuleFields[CC].displayName ) ); + mFilterFieldList.append( i18n( SpecialRuleFields[ReplyTo].displayName ) ); + mFilterFieldList.append( i18n( SpecialRuleFields[Organization].displayName ) ); + // these others only represent message headers and you can add to // them as you like - mFilterFieldList.append("Subject"); - mFilterFieldList.append("From"); - mFilterFieldList.append("To"); - mFilterFieldList.append("CC"); - mFilterFieldList.append("Reply-To"); mFilterFieldList.append("List-Id"); - mFilterFieldList.append("Organization"); mFilterFieldList.append("Resent-From"); mFilterFieldList.append("X-Loop"); mFilterFieldList.append("X-Mailing-List"); diff --git a/kmail/kmsearchpatternedit.h b/kmail/kmsearchpatternedit.h index a6ffd641f..3fee81e1b 100644 --- a/kmail/kmsearchpatternedit.h +++ b/kmail/kmsearchpatternedit.h @@ -45,7 +45,8 @@ public: be used to initialize the widget. */ KMSearchRuleWidget( TQWidget* parent=0, KMSearchRule* aRule=0, const char* name=0, bool headersOnly = false, bool absoluteDates = false ); - enum { Message, Body, AnyHeader, Recipients, Size, AgeInDays, Status }; + enum { Message, Body, AnyHeader, Recipients, Size, AgeInDays, Status, + Subject, From, To, CC, ReplyTo, Organization }; /** Set whether only header fields can be searched. If @p is true only header fields can be searched otherwise \ and \ searches diff --git a/kmail/kmsender.cpp b/kmail/kmsender.cpp index a79da268f..7f7730daf 100644 --- a/kmail/kmsender.cpp +++ b/kmail/kmsender.cpp @@ -317,6 +317,7 @@ void KMSender::doSendMsg() mCurrentMsg->setStatus(KMMsgStatusSent); mCurrentMsg->setStatus(KMMsgStatusRead); // otherwise it defaults to new on imap mCurrentMsg->updateAttachmentState(); + mCurrentMsg->updateInvitationState(); const KPIM::Identity & id = kmkernel->identityManager() ->identityForUoidOrDefault( mCurrentMsg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt() ); @@ -421,10 +422,10 @@ void KMSender::doSendMsg() // empty const KPIM::Identity & id = kmkernel->identityManager() ->identityForUoidOrDefault( mCurrentMsg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt() ); - if ( !id.emailAddr().isEmpty() ) { + if ( !id.primaryEmailAddress().isEmpty() ) { mCurrentMsg->setFrom( id.fullEmailAddr() ); } - else if ( !kmkernel->identityManager()->defaultIdentity().emailAddr().isEmpty() ) { + else if ( !kmkernel->identityManager()->defaultIdentity().primaryEmailAddress().isEmpty() ) { mCurrentMsg->setFrom( kmkernel->identityManager()->defaultIdentity().fullEmailAddr() ); } else { diff --git a/kmail/kmsystemtray.cpp b/kmail/kmsystemtray.cpp index e4d3a9879..8dc4eee94 100644 --- a/kmail/kmsystemtray.cpp +++ b/kmail/kmsystemtray.cpp @@ -110,7 +110,6 @@ void KMSystemTray::buildPopupMenu() { // Delete any previously created popup menu delete mPopupMenu; - mPopupMenu = 0; mPopupMenu = new KPopupMenu(); KMMainWidget * mainWidget = kmkernel->getKMMainWidget(); @@ -293,6 +292,9 @@ void KMSystemTray::foldersChanged() /** Check all new folders to see if we started with any new messages */ updateNewMessageNotification(currentFolder); } + else { + disconnect(currentFolder, TQT_SIGNAL(numUnreadMsgsChanged(KMFolder *)), this, TQT_SLOT(updateNewMessageNotification(KMFolder *)) ); + } } } @@ -588,4 +590,9 @@ void KMSystemTray::selectedAccount(int id) ft->selectCurrentFolder(); } +bool KMSystemTray::hasUnreadMail() const +{ + return ( mCount != 0 ); +} + #include "kmsystemtray.moc" diff --git a/kmail/kmsystemtray.h b/kmail/kmsystemtray.h index ab80f362a..62707464c 100644 --- a/kmail/kmsystemtray.h +++ b/kmail/kmsystemtray.h @@ -51,10 +51,13 @@ public: int mode() const; void hideKMail(); + bool hasUnreadMail() const; + +public slots: + void foldersChanged(); private slots: void updateNewMessageNotification(KMFolder * folder); - void foldersChanged(); void selectedAccount(int); void updateNewMessages(); void tray_quit(); diff --git a/kmail/kmversion.h b/kmail/kmversion.h index 8a7e68586..98706ae1b 100644 --- a/kmail/kmversion.h +++ b/kmail/kmversion.h @@ -3,6 +3,6 @@ #ifndef kmversion_h #define kmversion_h -#define KMAIL_VERSION "1.9.10" +#define KMAIL_VERSION "1.9.10 (enterprise35 0.20100827.1168748)" #endif /*kmversion_h*/ diff --git a/kmail/managesievescriptsdialog.cpp b/kmail/managesievescriptsdialog.cpp index a089c94a6..3bec71323 100644 --- a/kmail/managesievescriptsdialog.cpp +++ b/kmail/managesievescriptsdialog.cpp @@ -83,10 +83,15 @@ static KURL findUrlForAccount( const KMail::ImapAccountBase * a ) { u.setPass( a->passwd() ); u.setPort( sieve.port() ); // Translate IMAP LOGIN to PLAIN: - u.setQuery( "x-mech=" + ( a->auth() == "*" ? "PLAIN" : a->auth() ) ); + u.addQueryItem( "x-mech", a->auth() == "*" ? "PLAIN" : a->auth() ); + if ( !a->useSSL() && !a->useTLS() ) + u.addQueryItem( "x-allow-unencrypted", "true" ); return u; } else { - return sieve.alternateURL(); + KURL u = sieve.alternateURL(); + if ( u.protocol().lower() == "sieve" && !a->useSSL() && !a->useTLS() && u.queryItem("x-allow-unencrypted").isEmpty() ) + u.addQueryItem( "x-allow-unencrypted", "true" ); + return u; } } @@ -159,6 +164,7 @@ void KMail::ManageSieveScriptsDialog::slotContextMenuRequested( TQListViewItem * // script items: menu.insertItem( i18n( "Delete Script" ), this, TQT_SLOT(slotDeleteScript()) ); menu.insertItem( i18n( "Edit Script..." ), this, TQT_SLOT(slotEditScript()) ); + menu.insertItem( i18n( "Deactivate Script" ), this, TQT_SLOT(slotDeactivateScript()) ); } else { // top-levels: menu.insertItem( i18n( "New Script..." ), this, TQT_SLOT(slotNewScript()) ); @@ -167,6 +173,20 @@ void KMail::ManageSieveScriptsDialog::slotContextMenuRequested( TQListViewItem * mContextMenuItem = 0; } + +void KMail::ManageSieveScriptsDialog::slotDeactivateScript() { + if ( !mContextMenuItem ) + return; + + TQCheckListItem * parent = qcli_cast( mContextMenuItem->parent() ); + if ( !parent ) + return; + if ( mContextMenuItem->isOn()) { + mSelectedItems[parent] = mContextMenuItem; + changeActiveScript( parent,false ); + } +} + void KMail::ManageSieveScriptsDialog::slotSelectionChanged( TQListViewItem * i ) { TQCheckListItem * item = qcli_cast( i ); if ( !item ) @@ -176,11 +196,11 @@ void KMail::ManageSieveScriptsDialog::slotSelectionChanged( TQListViewItem * i ) return; if ( item->isOn() && mSelectedItems[parent] != item ) { mSelectedItems[parent] = item; - changeActiveScript( parent ); + changeActiveScript( parent,true ); } } -void KMail::ManageSieveScriptsDialog::changeActiveScript( TQCheckListItem * item ) { +void KMail::ManageSieveScriptsDialog::changeActiveScript( TQCheckListItem * item , bool activate) { if ( !item ) return; if ( !mUrls.count( item ) ) @@ -194,8 +214,11 @@ void KMail::ManageSieveScriptsDialog::changeActiveScript( TQCheckListItem * item if ( !selected ) return; u.setFileName( selected->text( 0 ) ); - - SieveJob * job = SieveJob::activate( u ); + SieveJob * job; + if ( activate ) + job = SieveJob::activate( u ); + else + job = SieveJob::desactivate( u ); connect( job, TQT_SIGNAL(result(KMail::SieveJob*,bool,const TQString&,bool)), this, TQT_SLOT(slotRefresh()) ); } @@ -235,7 +258,6 @@ void KMail::ManageSieveScriptsDialog::slotDeleteScript() { KStdGuiItem::del() ) != KMessageBox::Continue ) return; - SieveJob * job = SieveJob::del( u ); connect( job, TQT_SIGNAL(result(KMail::SieveJob*,bool,const TQString&,bool)), this, TQT_SLOT(slotRefresh()) ); @@ -295,15 +317,22 @@ KMail::SieveEditor::SieveEditor( TQWidget * parent, const char * name ) TQVBoxLayout * vlay = new TQVBoxLayout( plainPage(), 0, spacingHint() ); mTextEdit = new TQTextEdit( plainPage() ); vlay->addWidget( mTextEdit ); + mTextEdit->setFocus(); mTextEdit->setTextFormat( TQTextEdit::PlainText ); mTextEdit->setWordWrap( TQTextEdit::NoWrap ); mTextEdit->setFont( KGlobalSettings::fixedFont() ); - + connect( mTextEdit, TQT_SIGNAL( textChanged () ), TQT_SLOT( slotTextChanged() ) ); resize( 3 * sizeHint() ); } KMail::SieveEditor::~SieveEditor() {} + +void KMail::SieveEditor::slotTextChanged() +{ + enableButtonOK( !script().isEmpty() ); +} + void KMail::ManageSieveScriptsDialog::slotGetResult( KMail::SieveJob *, bool success, const TQString & script, bool isActive ) { if ( !success ) return; @@ -330,6 +359,7 @@ void KMail::ManageSieveScriptsDialog::slotSieveEditorOkClicked() { void KMail::ManageSieveScriptsDialog::slotSieveEditorCancelClicked() { mSieveEditor->deleteLater(); mSieveEditor = 0; mCurrentURL = KURL(); + slotRefresh(); } void KMail::ManageSieveScriptsDialog::slotPutResult( KMail::SieveJob *, bool success ) { diff --git a/kmail/managesievescriptsdialog.h b/kmail/managesievescriptsdialog.h index 2195e911e..70c4f6978 100644 --- a/kmail/managesievescriptsdialog.h +++ b/kmail/managesievescriptsdialog.h @@ -28,6 +28,7 @@ private slots: void slotSelectionChanged( TQListViewItem * ); void slotNewScript(); void slotEditScript(); + void slotDeactivateScript(); void slotDeleteScript(); void slotGetResult( KMail::SieveJob *, bool, const TQString &, bool ); void slotPutResult( KMail::SieveJob *, bool ); @@ -36,7 +37,7 @@ private slots: private: void killAllJobs(); - void changeActiveScript( TQCheckListItem * ); + void changeActiveScript( TQCheckListItem *, bool activate = true ); private: TQListView * mListView; diff --git a/kmail/managesievescriptsdialog_p.h b/kmail/managesievescriptsdialog_p.h index 83fde06b0..2ae12ff53 100644 --- a/kmail/managesievescriptsdialog_p.h +++ b/kmail/managesievescriptsdialog_p.h @@ -16,7 +16,8 @@ public: TQString script() const { return mTextEdit->text(); } void setScript( const TQString & script ) { mTextEdit->setText( script ); } - +private slots: + void slotTextChanged(); private: TQTextEdit * mTextEdit; }; diff --git a/kmail/messageactions.cpp b/kmail/messageactions.cpp index b900b7b59..cc5431954 100644 --- a/kmail/messageactions.cpp +++ b/kmail/messageactions.cpp @@ -138,7 +138,11 @@ void MessageActions::setSelectedVisibleSernums(const TQValueList< Q_UINT32 > & s void MessageActions::updateActions() { - const bool singleMsg = (mCurrentMessage != 0); + bool singleMsg = (mCurrentMessage != 0); + if ( mCurrentMessage && mCurrentMessage->parent() ) { + if ( mCurrentMessage->parent()->isTemplates() ) + singleMsg = false; + } const bool multiVisible = mVisibleSernums.count() > 0 || mCurrentMessage; const bool flagsAvailable = GlobalSettings::self()->allowLocalFlags() || !((mCurrentMessage && mCurrentMessage->parent()) ? mCurrentMessage->parent()->isReadOnly() : true); @@ -164,6 +168,18 @@ void MessageActions::updateActions() mEditAction->setEnabled( singleMsg ); } +template void MessageActions::replyCommand() +{ + if ( !mCurrentMessage ) + return; + const TQString text = mMessageView ? mMessageView->copyText() : ""; + KMCommand *command = new T( mParent, mCurrentMessage, text ); + connect( command, TQT_SIGNAL( completed( KMCommand * ) ), + this, TQT_SIGNAL( replyActionFinished() ) ); + command->start(); +} + + void MessageActions::slotCreateTodo() { if ( !mCurrentMessage ) diff --git a/kmail/messageactions.h b/kmail/messageactions.h index 39e9f937e..41279c552 100644 --- a/kmail/messageactions.h +++ b/kmail/messageactions.h @@ -55,19 +55,20 @@ class MessageActions : public QObject KAction* editAction() const { return mEditAction; } + signals: + + // This signal is emitted when a reply is triggered and the + // action has finished. + // This is useful for the stand-alone reader, it might want to close the window in + // that case. + void replyActionFinished(); + public slots: void editCurrentMessage(); private: void updateActions(); - template void replyCommand() - { - if ( !mCurrentMessage ) - return; - const TQString text = mMessageView ? mMessageView->copyText() : ""; - KMCommand *command = new T( mParent, mCurrentMessage, text ); - command->start(); - } + template void replyCommand(); void setMessageStatus( KMMsgStatus status, bool toggle = false ); private slots: diff --git a/kmail/messagecomposer.cpp b/kmail/messagecomposer.cpp index 8d9950738..8b3f1c5c4 100644 --- a/kmail/messagecomposer.cpp +++ b/kmail/messagecomposer.cpp @@ -86,6 +86,7 @@ #include #include +#include #include // ## keep default values in sync with configuredialog.cpp, Security::CryptoTab::setup() @@ -612,11 +613,8 @@ void MessageComposer::chiasmusEncryptAllAttachments() { part->setTypeStr( "application" ); part->setSubtypeStr( "vnd.de.bund.bsi.chiasmus" ); part->setName( filename + ".xia" ); - // this is taken from kmmsgpartdlg.cpp: - TQCString encoding = KMMsgBase::autoDetectCharset( part->charset(), KMMessage::preferredCharsets(), filename ); - if ( encoding.isEmpty() ) - encoding = "utf-8"; - const TQCString enc_name = KMMsgBase::encodeRFC2231String( filename + ".xia", encoding ); + const TQCString enc_name = KMMsgBase::encodeRFC2231StringAutoDetectCharset( + filename + ".xia", part->charset() ); const TQCString cDisp = "attachment;\n\tfilename" + ( TQString( enc_name ) != filename + ".xia" ? "*=" + enc_name @@ -2146,8 +2144,14 @@ void MessageComposer::pgpSignedMsg( const TQByteArray& cText, Kleo::CryptoMessag mSignature = TQByteArray(); const std::vector signingKeys = mKeyResolver->signingKeys( format ); - - assert( !signingKeys.empty() ); + if ( signingKeys.empty() ) { + KMessageBox::sorry( mComposeWin, + i18n("This message could not be signed, " + "since no valid signing keys have been found; " + "this should actually never happen, " + "please report this bug.") ); + return; + } // TODO: ASync call? Likely, yes :-) const Kleo::CryptoBackendFactory * cpf = Kleo::CryptoBackendFactory::instance(); @@ -2171,6 +2175,11 @@ void MessageComposer::pgpSignedMsg( const TQByteArray& cText, Kleo::CryptoMessag TQByteArray signature; const GpgME::SigningResult res = job->exec( signingKeys, cText, signingMode( format ), signature ); + { + std::stringstream ss; + ss << res; + kdDebug(5006) << ss.str().c_str() << endl; + } if ( res.error().isCanceled() ) { kdDebug() << "signing was canceled by user" << endl; return; @@ -2182,6 +2191,7 @@ void MessageComposer::pgpSignedMsg( const TQByteArray& cText, Kleo::CryptoMessag } if ( GlobalSettings::showGnuPGAuditLogAfterSuccessfulSignEncrypt() ) + if ( Kleo::MessageBox::showAuditLogButton( job.get() ) ) Kleo::MessageBox::auditLog( 0, job.get(), i18n("GnuPG Audit Log for Signing Operation") ); mSignature = signature; @@ -2219,6 +2229,11 @@ Kpgp::Result MessageComposer::pgpEncryptedMsg( TQByteArray & encryptedBody, const GpgME::EncryptionResult res = job->exec( encryptionKeys, cText, true /* we do ownertrust ourselves */, encryptedBody ); + { + std::stringstream ss; + ss << res; + kdDebug(5006) << ss.str().c_str() << endl; + } if ( res.error().isCanceled() ) { kdDebug() << "encryption was canceled by user" << endl; return Kpgp::Canceled; @@ -2230,6 +2245,7 @@ Kpgp::Result MessageComposer::pgpEncryptedMsg( TQByteArray & encryptedBody, } if ( GlobalSettings::showGnuPGAuditLogAfterSuccessfulSignEncrypt() ) + if ( Kleo::MessageBox::showAuditLogButton( job.get() ) ) Kleo::MessageBox::auditLog( 0, job.get(), i18n("GnuPG Audit Log for Encryption Operation") ); return Kpgp::Ok; @@ -2261,6 +2277,11 @@ Kpgp::Result MessageComposer::pgpSignedAndEncryptedMsg( TQByteArray & encryptedB const std::pair res = job->exec( signingKeys, encryptionKeys, cText, false, encryptedBody ); + { + std::stringstream ss; + ss << res.first << '\n' << res.second; + kdDebug(5006) << ss.str().c_str() << endl; + } if ( res.first.error().isCanceled() || res.second.error().isCanceled() ) { kdDebug() << "encrypt/sign was canceled by user" << endl; return Kpgp::Canceled; @@ -2275,6 +2296,7 @@ Kpgp::Result MessageComposer::pgpSignedAndEncryptedMsg( TQByteArray & encryptedB } if ( GlobalSettings::showGnuPGAuditLogAfterSuccessfulSignEncrypt() ) + if ( Kleo::MessageBox::showAuditLogButton( job.get() ) ) Kleo::MessageBox::auditLog( 0, job.get(), i18n("GnuPG Audit Log for Encryption Operation") ); return Kpgp::Ok; diff --git a/kmail/messageproperty.cpp b/kmail/messageproperty.cpp index 7de13eb35..b1e02161b 100644 --- a/kmail/messageproperty.cpp +++ b/kmail/messageproperty.cpp @@ -1,5 +1,5 @@ /* Message Property - + This file is part of KMail, the KDE mail client. Copyright (c) Don Sanders @@ -66,14 +66,13 @@ void MessageProperty::setFiltering( const KMMsgBase *msgBase, bool filter ) KMFolder* MessageProperty::filterFolder( Q_UINT32 serNum ) { - if (sFolders.contains(serNum)) - return sFolders[serNum].operator->(); - return 0; + TQMap >::const_iterator it = sFolders.find( serNum ); + return it == sFolders.constEnd() ? 0 : (*it).operator->(); } void MessageProperty::setFilterFolder( Q_UINT32 serNum, KMFolder* folder ) { - sFolders.replace(serNum, TQGuardedPtr(folder) ); + sFolders.insert(serNum, TQGuardedPtr(folder) ); } KMFolder* MessageProperty::filterFolder( const KMMsgBase *msgBase ) @@ -88,15 +87,14 @@ void MessageProperty::setFilterFolder( const KMMsgBase *msgBase, KMFolder* folde ActionScheduler* MessageProperty::filterHandler( Q_UINT32 serNum ) { - if ( sHandlers.contains( serNum )) - return sHandlers[serNum].operator->(); - return 0; + TQMap >::const_iterator it = sHandlers.find( serNum ); + return it == sHandlers.constEnd() ? 0 : (*it).operator->(); } void MessageProperty::setFilterHandler( Q_UINT32 serNum, ActionScheduler* handler ) { if (handler) - sHandlers.replace( serNum, TQGuardedPtr(handler) ); + sHandlers.insert( serNum, TQGuardedPtr(handler) ); else sHandlers.remove( serNum ); } @@ -113,16 +111,16 @@ void MessageProperty::setFilterHandler( const KMMsgBase *msgBase, ActionSchedule bool MessageProperty::transferInProgress( Q_UINT32 serNum ) { - if (sTransfers.contains(serNum)) - return sTransfers[serNum]; - return false; + TQMap::const_iterator it = sTransfers.find( serNum ); + return it == sTransfers.constEnd() ? false : *it; } void MessageProperty::setTransferInProgress( Q_UINT32 serNum, bool transfer, bool force ) { int transferInProgress = 0; - if (sTransfers.contains(serNum)) - transferInProgress = sTransfers[serNum]; + TQMap::const_iterator it = sTransfers.find( serNum ); + if (it != sTransfers.constEnd()) + transferInProgress = *it; if ( force && !transfer ) transferInProgress = 0; else @@ -130,7 +128,7 @@ void MessageProperty::setTransferInProgress( Q_UINT32 serNum, bool transfer, boo if ( transferInProgress < 0 ) transferInProgress = 0; if (transferInProgress) - sTransfers.replace( serNum, transferInProgress ); + sTransfers.insert( serNum, transferInProgress ); else sTransfers.remove( serNum ); } @@ -147,15 +145,14 @@ void MessageProperty::setTransferInProgress( const KMMsgBase *msgBase, bool tran Q_UINT32 MessageProperty::serialCache( const KMMsgBase *msgBase ) { - if (sSerialCache.contains( msgBase )) - return sSerialCache[msgBase]; - return 0; + TQMap::const_iterator it = sSerialCache.find( msgBase ); + return it == sSerialCache.constEnd() ? 0 : *it; } void MessageProperty::setSerialCache( const KMMsgBase *msgBase, Q_UINT32 serNum ) { if (serNum) - sSerialCache.replace( msgBase, serNum ); + sSerialCache.insert( msgBase, serNum ); else sSerialCache.remove( msgBase ); } diff --git a/kmail/messageproperty.h b/kmail/messageproperty.h index 4809cd7ad..91168e6f3 100644 --- a/kmail/messageproperty.h +++ b/kmail/messageproperty.h @@ -91,6 +91,7 @@ public: static bool transferInProgress( const KMMsgBase* ); static void setTransferInProgress( Q_UINT32, bool, bool = false ); static bool transferInProgress( Q_UINT32 ); + /** Some properties, namely complete, transferInProgress, and serialCache must be forgotten when a message class instance is destructed or assigned a new value */ @@ -99,10 +100,13 @@ public: private: // The folder a message is to be moved into once filtering is finished if any static TQMap > sFolders; + // The action scheduler currently processing a message if any static TQMap > sHandlers; + // The transferInProgres state of a message if any. static TQMap sTransfers; + // The cached serial number of a message if any. static TQMap sSerialCache; }; diff --git a/kmail/newfolderdialog.cpp b/kmail/newfolderdialog.cpp index 78294e5f9..99051222e 100644 --- a/kmail/newfolderdialog.cpp +++ b/kmail/newfolderdialog.cpp @@ -40,6 +40,7 @@ #include #include +#include "folderutil.h" #include "newfolderdialog.h" #include "kmfolder.h" #include "folderstorage.h" @@ -58,6 +59,9 @@ NewFolderDialog::NewFolderDialog( TQWidget* parent, KMFolder *folder ) : KDialogBase( parent, "new_folder_dialog", false, i18n( "New Folder" ), KDialogBase::Ok|KDialogBase::Cancel, KDialogBase::Ok, true ), + mFormatComboBox( 0 ), + mContentsComboBox( 0 ), + mNamespacesComboBox( 0 ), mFolder( folder ) { setWFlags( getWFlags() | WDestructiveClose ); @@ -112,7 +116,8 @@ NewFolderDialog::NewFolderDialog( TQWidget* parent, KMFolder *folder ) } // --- contents ----- - if ( kmkernel->iCalIface().isEnabled() ) { + if ( kmkernel->iCalIface().isEnabled() && + mFolder && mFolder->folderType() != KMFolderTypeImap ) { mContentsHBox = new TQHBoxLayout( 0, 0, 6, "mContentsHBox"); mContentsLabel = new TQLabel( privateLayoutWidget, "mContentsLabel" ); @@ -190,41 +195,12 @@ void NewFolderDialog::slotOk() return; } - // names of local folders must not contain a '/' - if ( fldName.find( '/' ) != -1 && - ( !mFolder || - ( mFolder->folderType() != KMFolderTypeImap && - mFolder->folderType() != KMFolderTypeCachedImap ) ) ) { - KMessageBox::error( this, i18n( "Folder names cannot contain the / (slash) character; please choose another folder name." ) ); + TQString msg; + if ( mFolder && !mFolder->isValidName( fldName, msg ) ) { + KMessageBox::error( this, msg ); return; } - // folder names must not start with a '.' - if ( fldName.startsWith( "." ) ) { - KMessageBox::error( this, i18n( "Folder names cannot start with a . (dot) character; please choose another folder name." ) ); - return; - } - - // names of IMAP folders must not contain the folder delimiter - if ( mFolder && - ( mFolder->folderType() == KMFolderTypeImap || - mFolder->folderType() == KMFolderTypeCachedImap ) ) { - TQString delimiter; - if ( mFolder->folderType() == KMFolderTypeImap ) { - KMAcctImap* ai = static_cast( mFolder->storage() )->account(); - if ( ai ) - delimiter = ai->delimiterForFolder( mFolder->storage() ); - } else { - KMAcctCachedImap* ai = static_cast( mFolder->storage() )->account(); - if ( ai ) - delimiter = ai->delimiterForFolder( mFolder->storage() ); - } - if ( !delimiter.isEmpty() && fldName.find( delimiter ) != -1 ) { - KMessageBox::error( this, i18n( "Your IMAP server does not allow the character '%1'; please choose another folder name." ).arg( delimiter ) ); - return; - } - } - // default parent is Top Level local folders KMFolderDir * selectedFolderDir = &(kmkernel->folderMgr()->dir()); // we got a parent, let's use that @@ -245,55 +221,21 @@ void NewFolderDialog::slotOk() /* Ok, obvious errors caught, let's try creating it for real. */ const TQString message = i18n( "Failed to create folder %1." " " ).arg(fldName); - bool success = false; - KMFolder *newFolder = 0; - if ( mFolder && mFolder->folderType() == KMFolderTypeImap ) { - KMFolderImap* selectedStorage = static_cast( mFolder->storage() ); - KMAcctImap *anAccount = selectedStorage->account(); - // check if a connection is available BEFORE creating the folder - if (anAccount->makeConnection() == ImapAccountBase::Connected) { - newFolder = kmkernel->imapFolderMgr()->createFolder( fldName, false, KMFolderTypeImap, selectedFolderDir ); - if ( newFolder ) { - TQString imapPath, parent; - if ( mNamespacesComboBox ) { - // create folder with namespace - parent = anAccount->addPathToNamespace( mNamespacesComboBox->currentText() ); - imapPath = anAccount->createImapPath( parent, fldName ); - } else { - imapPath = anAccount->createImapPath( selectedStorage->imapPath(), fldName ); - } - KMFolderImap* newStorage = static_cast( newFolder->storage() ); - selectedStorage->createFolder(fldName, parent); // create it on the server - newStorage->initializeFrom( selectedStorage, imapPath, TQString::null ); - static_cast(mFolder->storage())->setAccount( selectedStorage->account() ); - success = true; - } - } - } else if ( mFolder && mFolder->folderType() == KMFolderTypeCachedImap ) { - newFolder = kmkernel->dimapFolderMgr()->createFolder( fldName, false, KMFolderTypeCachedImap, selectedFolderDir ); - if ( newFolder ) { - KMFolderCachedImap* selectedStorage = static_cast( mFolder->storage() ); - KMFolderCachedImap* newStorage = static_cast( newFolder->storage() ); - newStorage->initializeFrom( selectedStorage ); - if ( mNamespacesComboBox ) { - // create folder with namespace - TQString path = selectedStorage->account()->createImapPath( - mNamespacesComboBox->currentText(), fldName ); - newStorage->setImapPathForCreation( path ); - } - success = true; - } - } else { - // local folder - if (mFormatComboBox->currentItem() == 1) - newFolder = kmkernel->folderMgr()->createFolder(fldName, false, KMFolderTypeMaildir, selectedFolderDir ); - else - newFolder = kmkernel->folderMgr()->createFolder(fldName, false, KMFolderTypeMbox, selectedFolderDir ); - if ( newFolder ) - success = true; + TQString namespaceName; + if ( mNamespacesComboBox ) { + namespaceName = mNamespacesComboBox->currentText(); } - if ( !success ) { + + KMFolderType folderType = KMFolderTypeUnknown; + if ( mFormatComboBox && mFormatComboBox->currentItem() == 1 ) + folderType = KMFolderTypeMaildir; + else if ( mFormatComboBox ) + folderType = KMFolderTypeMbox; + + KMFolder *newFolder = KMail::FolderUtil::createSubFolder( mFolder, selectedFolderDir, fldName, + namespaceName, folderType ); + if ( !newFolder ) { KMessageBox::error( this, message ); return; } diff --git a/kmail/newfolderdialog.h b/kmail/newfolderdialog.h index 96f4421eb..4744db3e2 100644 --- a/kmail/newfolderdialog.h +++ b/kmail/newfolderdialog.h @@ -69,7 +69,7 @@ class NewFolderDialog : public KDialogBase TQHBoxLayout* mNamespacesHBox; protected slots: void slotOk(); - void slotFolderNameChanged( const TQString & _text); + void slotFolderNameChanged( const TQString & _text); private: KMFolder* mFolder; diff --git a/kmail/objecttreeparser.cpp b/kmail/objecttreeparser.cpp index 4c56a7bd4..d8b98f24d 100644 --- a/kmail/objecttreeparser.cpp +++ b/kmail/objecttreeparser.cpp @@ -34,6 +34,7 @@ // my header file #include "objecttreeparser.h" +#include "objecttreeparser_p.h" // other KMail headers #include "kmkernel.h" @@ -52,6 +53,7 @@ #include "interfaces/bodypartformatter.h" #include "globalsettings.h" #include "util.h" +#include "callback.h" // other module headers #include @@ -108,9 +110,11 @@ // other headers #include +#include #include #include #include +#include #include "chiasmuskeyselector.h" namespace KMail { @@ -145,6 +149,9 @@ namespace KMail { mShowOnlyOneMimePart( showOnlyOneMimePart ), mKeepEncryptions( keepEncryptions ), mIncludeSignatures( includeSignatures ), + mHasPendingAsyncJobs( false ), + mAllowAsync( false ), + mShowRawToltecMail( false ), mAttachmentStrategy( strategy ), mHtmlWriter( htmlWriter ), mCSSHelper( cssHelper ) @@ -164,6 +171,8 @@ namespace KMail { mShowOnlyOneMimePart( other.showOnlyOneMimePart() ), mKeepEncryptions( other.keepEncryptions() ), mIncludeSignatures( other.includeSignatures() ), + mHasPendingAsyncJobs( other.hasPendingAsyncJobs() ), + mAllowAsync( other.allowAsync() ), mAttachmentStrategy( other.attachmentStrategy() ), mHtmlWriter( other.htmlWriter() ), mCSSHelper( other.cssHelper() ) @@ -176,7 +185,7 @@ namespace KMail { void ObjectTreeParser::insertAndParseNewChildNode( partNode& startNode, const char* content, const char* cntDesc, - bool append ) + bool append, bool addToTextualContent ) { DwBodyPart* myBody = new DwBodyPart( DwString( content ), 0 ); myBody->Parse(); @@ -201,6 +210,11 @@ namespace KMail { partNode* parentNode = &startNode; partNode* newNode = new partNode(false, myBody); + + // Build the object tree of the new node before setting the parent, as otherwise + // buildObjectTree() would erronously modify the parents as well + newNode->buildObjectTree( false ); + if ( append && parentNode->firstChild() ) { parentNode = parentNode->firstChild(); while( parentNode->nextSibling() ) @@ -209,40 +223,37 @@ namespace KMail { } else parentNode->setFirstChild( newNode ); - newNode->buildObjectTree( false ); - if ( startNode.mimePartTreeItem() ) { - kdDebug(5006) << "\n -----> Inserting items into MimePartTree\n" << endl; newNode->fillMimePartTree( startNode.mimePartTreeItem(), 0, TQString::null, TQString::null, TQString::null, 0, append ); - kdDebug(5006) << "\n <----- Finished inserting items into MimePartTree\n" << endl; } else { - kdDebug(5006) << "\n ------ Sorry, node.mimePartTreeItem() returns ZERO so" - << "\n we cannot insert new lines into MimePartTree. :-(\n" << endl; } - kdDebug(5006) << "\n -----> Now parsing the MimePartTree\n" << endl; ObjectTreeParser otp( mReader, cryptoProtocol() ); otp.parseObjectTree( newNode ); - mRawReplyString += otp.rawReplyString(); - mTextualContent += otp.textualContent(); - if ( !otp.textualContentCharset().isEmpty() ) - mTextualContentCharset = otp.textualContentCharset(); - kdDebug(5006) << "\n <----- Finished parsing the MimePartTree in insertAndParseNewChildNode()\n" << endl; + if ( addToTextualContent ) { + mRawReplyString += otp.rawReplyString(); + mTextualContent += otp.textualContent(); + if ( !otp.textualContentCharset().isEmpty() ) + mTextualContentCharset = otp.textualContentCharset(); + } } //----------------------------------------------------------------------------- void ObjectTreeParser::parseObjectTree( partNode * node ) { - kdDebug(5006) << "ObjectTreeParser::parseObjectTree( " - << (node ? "node OK, " : "no node, ") - << "showOnlyOneMimePart: " << (showOnlyOneMimePart() ? "TRUE" : "FALSE") - << " )" << endl; + //kdDebug(5006) << "ObjectTreeParser::parseObjectTree( " + // << (node ? "node OK, " : "no node, ") + // << "showOnlyOneMimePart: " << (showOnlyOneMimePart() ? "TRUE" : "FALSE") + // << " )" << endl; if ( !node ) return; + // reset pending async jobs state (we'll rediscover pending jobs as we go) + mHasPendingAsyncJobs = false; + // reset "processed" flags for... if ( showOnlyOneMimePart() ) { // ... this node and all descendants @@ -260,29 +271,38 @@ namespace KMail { ProcessResult processResult; - if ( mReader ) + if ( mReader ) { htmlWriter()->queue( TQString::fromLatin1("").arg( node->nodeId() ) ); + } + if ( const Interface::BodyPartFormatter * formatter = BodyPartFormatterFactory::instance()->createFor( node->typeString(), node->subTypeString() ) ) { - PartNodeBodyPart part( *node, codecFor( node ) ); - // Set the default display strategy for this body part relying on the - // identity of KMail::Interface::BodyPart::Display and AttachmentStrategy::Display - part.setDefaultDisplay( (KMail::Interface::BodyPart::Display) attachmentStrategy()->defaultDisplay( node ) ); - const Interface::BodyPartFormatter::Result result = formatter->format( &part, htmlWriter() ); - if ( mReader && node->bodyPartMemento() ) - if ( Interface::Observable * obs = node->bodyPartMemento()->asObservable() ) - obs->attach( mReader ); - switch ( result ) { - case Interface::BodyPartFormatter::AsIcon: - processResult.setNeverDisplayInline( true ); - // fall through: - case Interface::BodyPartFormatter::Failed: - defaultHandling( node, processResult ); - break; - case Interface::BodyPartFormatter::Ok: - case Interface::BodyPartFormatter::NeedContent: - // FIXME: incomplete content handling - ; + + // Only use the external plugin if we have a reader. Otherwise, just do nothing for this + // node. + if ( mReader ) { + PartNodeBodyPart part( *node, codecFor( node ) ); + // Set the default display strategy for this body part relying on the + // identity of KMail::Interface::BodyPart::Display and AttachmentStrategy::Display + part.setDefaultDisplay( (KMail::Interface::BodyPart::Display) attachmentStrategy()->defaultDisplay( node ) ); + + writeAttachmentMarkHeader( node ); + node->setDisplayedEmbedded( true ); + Callback callback( mReader->message(), mReader ); + const Interface::BodyPartFormatter::Result result = formatter->format( &part, htmlWriter(), callback ); + writeAttachmentMarkFooter(); + switch ( result ) { + case Interface::BodyPartFormatter::AsIcon: + processResult.setNeverDisplayInline( true ); + // fall through: + case Interface::BodyPartFormatter::Failed: + defaultHandling( node, processResult ); + break; + case Interface::BodyPartFormatter::Ok: + case Interface::BodyPartFormatter::NeedContent: + // FIXME: incomplete content handling + ; + } } } else { const BodyPartFormatter * bpf @@ -291,9 +311,13 @@ namespace KMail { << node->typeString() << '/' << node->subTypeString() << ')' << endl; - if ( bpf && !bpf->process( this, node, processResult ) ) + writeAttachmentMarkHeader( node ); + if ( bpf && !bpf->process( this, node, processResult ) ) { defaultHandling( node, processResult ); + } + writeAttachmentMarkFooter(); } + node->setProcessed( true, false ); // adjust signed/encrypted flags if inline PGP was found @@ -309,10 +333,15 @@ namespace KMail { // ### bodypartformatters. if ( !mReader ) return; - if ( attachmentStrategy() == AttachmentStrategy::hidden() && + + + const AttachmentStrategy * as = attachmentStrategy(); + if ( as && as->defaultDisplay( node ) == AttachmentStrategy::None && !showOnlyOneMimePart() && - node->parentNode() /* message is not an attachment */ ) + node->parentNode() /* message is not an attachment */ ) { + node->setDisplayedHidden( true ); return; + } bool asIcon = true; if ( showOnlyOneMimePart() ) @@ -321,25 +350,32 @@ namespace KMail { // window! asIcon = !node->hasContentDispositionInline(); else if ( !result.neverDisplayInline() ) - if ( const AttachmentStrategy * as = attachmentStrategy() ) + if ( as ) asIcon = as->defaultDisplay( node ) == AttachmentStrategy::AsIcon; // neither image nor text -> show as icon - if ( !result.isImage() - && node->type() != DwMime::kTypeText ) + if ( !result.isImage() && node->type() != DwMime::kTypeText ) asIcon = true; // if the image is not complete do not try to show it inline if ( result.isImage() && !node->msgPart().isComplete() ) asIcon = true; if ( asIcon ) { - if ( attachmentStrategy() != AttachmentStrategy::hidden() - || showOnlyOneMimePart() ) + if ( !( as && as->defaultDisplay( node ) == AttachmentStrategy::None ) || + showOnlyOneMimePart() ) { writePartIcon( &node->msgPart(), node->nodeId() ); - } else if ( result.isImage() ) + } + else { + node->setDisplayedHidden( true ); + } + } else if ( result.isImage() ) { + node->setDisplayedEmbedded( true ); writePartIcon( &node->msgPart(), node->nodeId(), true ); - else + } + else { + node->setDisplayedEmbedded( true ); writeBodyString( node->msgPart().bodyDecoded(), node->trueFromAddress(), codecFor( node ), result, false ); + } // end of ### } @@ -380,7 +416,7 @@ namespace KMail { const TQString& fromAddress, bool doCheck, TQCString* cleartextData, - std::vector paramSignatures, + const std::vector & paramSignatures, bool hideErrors ) { bool bIsOpaqueSigned = false; @@ -397,18 +433,22 @@ namespace KMail { } #ifndef NDEBUG - if ( !doCheck ) - kdDebug(5006) << "ObjectTreeParser::writeOpaqueOrMultipartSignedData: showing OpenPGP (Encrypted+Signed) data" << endl; - else - if ( data ) - kdDebug(5006) << "ObjectTreeParser::writeOpaqueOrMultipartSignedData: processing Multipart Signed data" << endl; - else - kdDebug(5006) << "ObjectTreeParser::writeOpaqueOrMultipartSignedData: processing Opaque Signed data" << endl; + if ( !doCheck ) { + //kdDebug(5006) << "ObjectTreeParser::writeOpaqueOrMultipartSignedData: showing OpenPGP (Encrypted+Signed) data" << endl; + } + else { + if ( data ) { + //kdDebug(5006) << "ObjectTreeParser::writeOpaqueOrMultipartSignedData: processing Multipart Signed data" << endl; + } + else { + //kdDebug(5006) << "ObjectTreeParser::writeOpaqueOrMultipartSignedData: processing Opaque Signed data" << endl; + } + } #endif if ( doCheck && cryptProto ) { - kdDebug(5006) << "ObjectTreeParser::writeOpaqueOrMultipartSignedData: going to call CRYPTPLUG " - << cryptPlugLibName << endl; + //kdDebug(5006) << "ObjectTreeParser::writeOpaqueOrMultipartSignedData: going to call CRYPTPLUG " + // << cryptPlugLibName << endl; } TQCString cleartext; @@ -423,9 +463,9 @@ namespace KMail { // replace simple LFs by CRLSs // according to RfC 2633, 3.1.1 Canonicalization - kdDebug(5006) << "Converting LF to CRLF (see RfC 2633, 3.1.1 Canonicalization)" << endl; + //kdDebug(5006) << "Converting LF to CRLF (see RfC 2633, 3.1.1 Canonicalization)" << endl; cleartext = Util::lf2crlf( cleartext ); - kdDebug(5006) << " done." << endl; + //kdDebug(5006) << " done." << endl; } dumpToFile( "dat_02_reader_signedtext_after_canonicalization", @@ -437,7 +477,7 @@ namespace KMail { } std::vector signatures; - if ( doCheck ) + if ( !doCheck ) signatures = paramSignatures; PartMetaData messagePart; @@ -450,36 +490,100 @@ namespace KMail { messagePart.status = i18n("Wrong Crypto Plug-In."); messagePart.status_code = GPGME_SIG_STAT_NONE; + GpgME::Key key; + if ( doCheck && cryptProto ) { GpgME::VerificationResult result; if ( data ) { // detached - if ( Kleo::VerifyDetachedJob * const job = cryptProto->verifyDetachedJob() ) { - TQByteArray plainData = cleartext; - plainData.resize( cleartext.size() - 1 ); - result = job->exec( signaturetext, plainData ); - messagePart.auditLog = job->auditLogAsHtml(); - } else { - cryptPlugError = CANT_VERIFY_SIGNATURES; + const VerifyDetachedBodyPartMemento * m + = dynamic_cast( sign.bodyPartMemento( "verifydetached" ) ); + if ( !m ) { + Kleo::VerifyDetachedJob * job = cryptProto->verifyDetachedJob(); + if ( !job ) { + cryptPlugError = CANT_VERIFY_SIGNATURES; + // PENDING(marc) cryptProto = 0 here? + } else { + TQByteArray plainData = cleartext; + plainData.resize( cleartext.size() - 1 ); + VerifyDetachedBodyPartMemento * newM + = new VerifyDetachedBodyPartMemento( job, cryptProto->keyListJob(), signaturetext, plainData ); + if ( allowAsync() ) { + if ( newM->start() ) { + messagePart.inProgress = true; + mHasPendingAsyncJobs = true; + } else { + m = newM; + } + } else { + newM->exec(); + m = newM; + } + sign.setBodyPartMemento( "verifydetached", newM ); + } + } else if ( m->isRunning() ) { + messagePart.inProgress = true; + mHasPendingAsyncJobs = true; + m = 0; + } + + if ( m ) { + result = m->verifyResult(); + messagePart.auditLogError = m->auditLogError(); + messagePart.auditLog = m->auditLogAsHtml(); + key = m->signingKey(); } } else { // opaque - if ( Kleo::VerifyOpaqueJob * const job = cryptProto->verifyOpaqueJob() ) { - TQByteArray plainData; - result = job->exec( signaturetext, plainData ); + const VerifyOpaqueBodyPartMemento * m + = dynamic_cast( sign.bodyPartMemento( "verifyopaque" ) ); + if ( !m ) { + Kleo::VerifyOpaqueJob * job = cryptProto->verifyOpaqueJob(); + if ( !job ) { + cryptPlugError = CANT_VERIFY_SIGNATURES; + // PENDING(marc) cryptProto = 0 here? + } else { + VerifyOpaqueBodyPartMemento * newM + = new VerifyOpaqueBodyPartMemento( job, cryptProto->keyListJob(), signaturetext ); + if ( allowAsync() ) { + if ( newM->start() ) { + messagePart.inProgress = true; + mHasPendingAsyncJobs = true; + } else { + m = newM; + } + } else { + newM->exec(); + m = newM; + } + sign.setBodyPartMemento( "verifyopaque", newM ); + } + } else if ( m->isRunning() ) { + messagePart.inProgress = true; + mHasPendingAsyncJobs = true; + m = 0; + } + + if ( m ) { + result = m->verifyResult(); + const TQByteArray & plainData = m->plainText(); cleartext = TQCString( plainData.data(), plainData.size() + 1 ); - messagePart.auditLog = job->auditLogAsHtml(); - } else { - cryptPlugError = CANT_VERIFY_SIGNATURES; + messagePart.auditLogError = m->auditLogError(); + messagePart.auditLog = m->auditLogAsHtml(); + key = m->signingKey(); } } + std::stringstream ss; + ss << result; + //kdDebug(5006) << ss.str().c_str() << endl; signatures = result.signatures(); } - if ( doCheck ) - kdDebug(5006) << "\nObjectTreeParser::writeOpaqueOrMultipartSignedData: returned from CRYPTPLUG" << endl; + if ( doCheck ) { + //kdDebug(5006) << "\nObjectTreeParser::writeOpaqueOrMultipartSignedData: returned from CRYPTPLUG" << endl; + } // ### only one signature supported if ( signatures.size() > 0 ) { - kdDebug(5006) << "\nObjectTreeParser::writeOpaqueOrMultipartSignedData: found signature" << endl; + //kdDebug(5006) << "\nObjectTreeParser::writeOpaqueOrMultipartSignedData: found signature" << endl; GpgME::Signature signature = signatures[0]; messagePart.status_code = signatureToStatus( signature ); @@ -493,16 +597,6 @@ namespace KMail { if ( messagePart.status_code & GPGME_SIG_STAT_GOOD ) messagePart.isGoodSignature = true; - // get key for this signature - Kleo::KeyListJob *job = cryptProto->keyListJob(); - std::vector keys; - GpgME::KeyListResult keyListRes = job->exec( TQString::fromLatin1( signature.fingerprint() ), false, keys ); - GpgME::Key key; - if ( keys.size() == 1 ) - key = keys[0]; - else if ( keys.size() > 1 ) - assert( false ); // ### wtf, what should we do in this case?? - // save extended signature status flags messagePart.sigSummary = signature.summary(); @@ -543,9 +637,9 @@ namespace KMail { } } - kdDebug(5006) << "\n key id: " << messagePart.keyId - << "\n key trust: " << messagePart.keyTrust - << "\n signer: " << messagePart.signer << endl; + //kdDebug(5006) << "\n key id: " << messagePart.keyId + // << "\n key trust: " << messagePart.keyTrust + // << "\n signer: " << messagePart.signer << endl; } else { messagePart.creationTime = TQDateTime(); @@ -631,11 +725,51 @@ namespace KMail { htmlWriter()->queue( writeSigstatFooter( messagePart ) ); } - kdDebug(5006) << "\nObjectTreeParser::writeOpaqueOrMultipartSignedData: done, returning " - << ( bIsOpaqueSigned ? "TRUE" : "FALSE" ) << endl; + //kdDebug(5006) << "\nObjectTreeParser::writeOpaqueOrMultipartSignedData: done, returning " + // << ( bIsOpaqueSigned ? "TRUE" : "FALSE" ) << endl; return bIsOpaqueSigned; } +void ObjectTreeParser::writeDecryptionInProgressBlock() { + assert( mReader ); + // PENDING(marc) find an animated icon here: + //const TQString iconName = KGlobal::instance()->iconLoader()->iconPath( "decrypted", KIcon::Small ); + const TQString decryptedData = i18n("Encrypted data not shown"); + PartMetaData messagePart; + messagePart.isDecryptable = true; + messagePart.isEncrypted = true; + messagePart.isSigned = false; + messagePart.inProgress = true; + htmlWriter()->queue( writeSigstatHeader( messagePart, + cryptoProtocol(), + TQString() ) ); + //htmlWriter()->queue( decryptedData ); + htmlWriter()->queue( writeSigstatFooter( messagePart ) ); +} + +void ObjectTreeParser::writeDeferredDecryptionBlock() { + assert( mReader ); + const TQString iconName = KGlobal::instance()->iconLoader()->iconPath( "decrypted", KIcon::Small ); + const TQString decryptedData = + "
" + + i18n("This message is encrypted.") + + "
" + "
"; + PartMetaData messagePart; + messagePart.isDecryptable = true; + messagePart.isEncrypted = true; + messagePart.isSigned = false; + mRawReplyString += decryptedData.utf8(); + htmlWriter()->queue( writeSigstatHeader( messagePart, + cryptoProtocol(), + TQString() ) ); + htmlWriter()->queue( decryptedData ); + htmlWriter()->queue( writeSigstatFooter( messagePart ) ); +} bool ObjectTreeParser::okDecryptMIME( partNode& data, TQCString& decryptedData, @@ -644,11 +778,15 @@ bool ObjectTreeParser::okDecryptMIME( partNode& data, bool showWarning, bool& passphraseError, bool& actuallyEncrypted, + bool& decryptionStarted, TQString& aErrorText, + GpgME::Error & auditLogError, TQString& auditLog ) { passphraseError = false; + decryptionStarted = false; aErrorText = TQString::null; + auditLogError = GpgME::Error(); auditLog = TQString::null; bool bDecryptionOk = false; enum { NO_PLUGIN, NOT_INITIALIZED, CANT_DECRYPT } @@ -660,19 +798,7 @@ bool ObjectTreeParser::okDecryptMIME( partNode& data, if ( cryptProto ) cryptPlugLibName = cryptProto->name(); - if ( mReader && !mReader->decryptMessage() ) { - TQString iconName = KGlobal::instance()->iconLoader()->iconPath( "decrypted", KIcon::Small ); - decryptedData = "
" - + i18n("This message is encrypted.").utf8() - + "
" - ""; - return false; - } + assert( !mReader || mReader->decryptMessage() ); if ( cryptProto && !kmkernel->contextMenuShown() ) { TQByteArray ciphertext( data.msgPart().bodyDecodedBinary() ); @@ -698,20 +824,48 @@ bool ObjectTreeParser::okDecryptMIME( partNode& data, #endif - kdDebug(5006) << "ObjectTreeParser::decryptMIME: going to call CRYPTPLUG " - << cryptPlugLibName << endl; + //kdDebug(5006) << "ObjectTreeParser::decryptMIME: going to call CRYPTPLUG " + // << cryptPlugLibName << endl; if ( mReader ) emit mReader->noDrag(); // in case pineentry pops up, don't let kmheaders start a drag afterwards - Kleo::DecryptVerifyJob* job = cryptProto->decryptVerifyJob(); - if ( !job ) { - cryptPlugError = CANT_DECRYPT; - cryptProto = 0; - } else { - TQByteArray plainText; - const std::pair res = job->exec( ciphertext, plainText ); - const GpgME::DecryptionResult & decryptResult = res.first; - const GpgME::VerificationResult & verifyResult = res.second; + // Check whether the memento contains a result from last time: + const DecryptVerifyBodyPartMemento * m + = dynamic_cast( data.bodyPartMemento( "decryptverify" ) ); + if ( !m ) { + Kleo::DecryptVerifyJob * job = cryptProto->decryptVerifyJob(); + if ( !job ) { + cryptPlugError = CANT_DECRYPT; + cryptProto = 0; + } else { + DecryptVerifyBodyPartMemento * newM + = new DecryptVerifyBodyPartMemento( job, ciphertext ); + if ( allowAsync() ) { + if ( newM->start() ) { + decryptionStarted = true; + mHasPendingAsyncJobs = true; + } else { + m = newM; + } + } else { + newM->exec(); + m = newM; + } + data.setBodyPartMemento( "decryptverify", newM ); + } + } else if ( m->isRunning() ) { + decryptionStarted = true; + mHasPendingAsyncJobs = true; + m = 0; + } + + if ( m ) { + const TQByteArray & plainText = m->plainText(); + const GpgME::DecryptionResult & decryptResult = m->decryptResult(); + const GpgME::VerificationResult & verifyResult = m->verifyResult(); + std::stringstream ss; + ss << decryptResult << '\n' << verifyResult; + //kdDebug(5006) << ss.str().c_str() << endl; signatureFound = verifyResult.signatures().size() > 0; signatures = verifyResult.signatures(); bDecryptionOk = !decryptResult.error(); @@ -719,10 +873,11 @@ bool ObjectTreeParser::okDecryptMIME( partNode& data, || decryptResult.error().code() == GPG_ERR_NO_SECKEY; actuallyEncrypted = decryptResult.error().code() != GPG_ERR_NO_DATA; aErrorText = TQString::fromLocal8Bit( decryptResult.error().asString() ); - auditLog = job->auditLogAsHtml(); + auditLogError = m->auditLogError(); + auditLog = m->auditLogAsHtml(); - kdDebug(5006) << "ObjectTreeParser::decryptMIME: returned from CRYPTPLUG" - << endl; + //kdDebug(5006) << "ObjectTreeParser::decryptMIME: returned from CRYPTPLUG" + // << endl; if ( bDecryptionOk ) decryptedData = TQCString( plainText.data(), plainText.size() + 1 ); else if ( mReader && showWarning ) { @@ -819,6 +974,7 @@ bool ObjectTreeParser::okDecryptMIME( partNode& data, showOnlyOneMimePart() ) { if ( mReader->htmlMail() ) { + curNode->setDisplayedEmbedded( true ); // ---Sven's strip and from end of attachment start- // We must fo this, or else we will see only 1st inlined html // attachment. It is IMHO enough to search only for and @@ -909,7 +1065,7 @@ namespace KMail { if ( nextDelim < 0) return false; - kdDebug(5006) << " processing old style Mailman digest" << endl; + //kdDebug(5006) << " processing old style Mailman digest" << endl; //if ( curNode->mRoot ) // curNode = curNode->mRoot; @@ -952,7 +1108,7 @@ namespace KMail { if ( -1 < thisEoL ) subject.truncate( thisEoL ); } - kdDebug(5006) << " embedded message found: \"" << subject << "\"" << endl; + //kdDebug(5006) << " embedded message found: \"" << subject << "\"" << endl; insertAndParseNewChildNode( *curNode, &*partStr, subject, true ); @@ -1029,8 +1185,7 @@ namespace KMail { TQString htmlStr = "" "
"; if ( !fileName.isEmpty() ) - htmlStr += "" + htmlStr += "asHREF( "body" ) + "\">" + label + ""; else htmlStr += label; @@ -1043,9 +1198,11 @@ namespace KMail { // process old style not-multipart Mailman messages to // enable verification of the embedded messages' signatures if ( !isMailmanMessage( curNode ) || - !processMailmanMessage( curNode ) ) + !processMailmanMessage( curNode ) ) { writeBodyString( mRawReplyString, curNode->trueFromAddress(), codecFor( curNode ), result, !bDrawFrame ); + curNode->setDisplayedEmbedded( true ); + } if ( bDrawFrame ) htmlWriter()->queue( "
" ); @@ -1065,7 +1222,30 @@ namespace KMail { mTextualContentCharset = otp.textualContentCharset(); } + TQString ObjectTreeParser::defaultToltecReplacementText() + { + return i18n( "This message is a Toltec Groupware object, it can only be viewed with " + "Microsoft Outlook in combination with the Toltec connector." ); + } + + bool ObjectTreeParser::processToltecMail( partNode *node ) + { + if ( !node || !mHtmlWriter || !GlobalSettings::self()->showToltecReplacementText() || + !node->isToltecMessage() || mShowRawToltecMail ) + return false; + + htmlWriter()->queue( GlobalSettings::self()->toltecReplacementText() ); + htmlWriter()->queue( "

" + + i18n( "Show Raw Message" ) + "" ); + return true; + } + bool ObjectTreeParser::processMultiPartMixedSubtype( partNode * node, ProcessResult & ) { + + if ( processToltecMail( node ) ) { + return true; + } + partNode * child = node->firstChild(); if ( !child ) return false; @@ -1203,20 +1383,28 @@ namespace KMail { CryptoProtocolSaver cpws( this, useThisCryptProto ); if ( partNode * dataChild = data->firstChild() ) { - kdDebug(5006) << "\n-----> Calling parseObjectTree( curNode->mChild )\n" << endl; + //kdDebug(5006) << "\n-----> Calling parseObjectTree( curNode->mChild )\n" << endl; stdChildHandling( dataChild ); - kdDebug(5006) << "\n-----> Returning from parseObjectTree( curNode->mChild )\n" << endl; + //kdDebug(5006) << "\n-----> Returning from parseObjectTree( curNode->mChild )\n" << endl; return true; } - kdDebug(5006) << "\n-----> Initially processing encrypted data\n" << endl; - PartMetaData messagePart; node->setEncryptionState( KMMsgFullyEncrypted ); + + if ( mReader && !mReader->decryptMessage() ) { + writeDeferredDecryptionBlock(); + data->setProcessed( true, false ); // Set the data node to done to prevent it from being processed + return true; + } + + //kdDebug(5006) << "\n-----> Initially processing encrypted data\n" << endl; + PartMetaData messagePart; TQCString decryptedData; bool signatureFound; std::vector signatures; bool passphraseError; bool actuallyEncrypted = true; + bool decryptionStarted; bool bOkDecrypt = okDecryptMIME( *data, decryptedData, @@ -1225,9 +1413,16 @@ namespace KMail { true, passphraseError, actuallyEncrypted, + decryptionStarted, messagePart.errorText, + messagePart.auditLogError, messagePart.auditLog ); + if ( decryptionStarted ) { + writeDecryptionInProgressBlock(); + return true; + } + // paint the frame if ( mReader ) { messagePart.isDecryptable = bOkDecrypt; @@ -1287,17 +1482,17 @@ namespace KMail { return false; if ( partNode * child = node->firstChild() ) { - kdDebug(5006) << "\n-----> Calling parseObjectTree( curNode->mChild )\n" << endl; + //kdDebug(5006) << "\n-----> Calling parseObjectTree( curNode->mChild )\n" << endl; ObjectTreeParser otp( mReader, cryptoProtocol() ); otp.parseObjectTree( child ); mRawReplyString += otp.rawReplyString(); mTextualContent += otp.textualContent(); if ( !otp.textualContentCharset().isEmpty() ) mTextualContentCharset = otp.textualContentCharset(); - kdDebug(5006) << "\n<----- Returning from parseObjectTree( curNode->mChild )\n" << endl; + //kdDebug(5006) << "\n<----- Returning from parseObjectTree( curNode->mChild )\n" << endl; return true; } - kdDebug(5006) << "\n-----> Initially processing data of embedded RfC 822 message\n" << endl; + //kdDebug(5006) << "\n-----> Initially processing data of embedded RfC 822 message\n" << endl; // paint the frame PartMetaData messagePart; if ( mReader ) { @@ -1310,7 +1505,7 @@ namespace KMail { htmlWriter()->queue( writeSigstatHeader( messagePart, cryptoProtocol(), node->trueFromAddress(), - filename ) ); + node ) ); } TQCString rfc822messageStr( node->msgPart().bodyDecoded() ); // display the headers of the encapsulated message @@ -1319,14 +1514,16 @@ namespace KMail { rfc822DwMessage->Parse(); KMMessage rfc822message( rfc822DwMessage ); node->setFromAddress( rfc822message.from() ); - kdDebug(5006) << "\n-----> Store RfC 822 message header \"From: " << rfc822message.from() << "\"\n" << endl; + //kdDebug(5006) << "\n-----> Store RfC 822 message header \"From: " << rfc822message.from() << "\"\n" << endl; if ( mReader ) htmlWriter()->queue( mReader->writeMsgHeader( &rfc822message ) ); //mReader->parseMsgHeader( &rfc822message ); // display the body of the encapsulated message insertAndParseNewChildNode( *node, &*rfc822messageStr, - "encapsulated message" ); + "encapsulated message", false /*append*/, + false /*add to textual content*/ ); + node->setDisplayedEmbedded( true ); if ( mReader ) htmlWriter()->queue( writeSigstatFooter( messagePart ) ); return true; @@ -1335,14 +1532,14 @@ namespace KMail { bool ObjectTreeParser::processApplicationOctetStreamSubtype( partNode * node, ProcessResult & result ) { if ( partNode * child = node->firstChild() ) { - kdDebug(5006) << "\n-----> Calling parseObjectTree( curNode->mChild )\n" << endl; + //kdDebug(5006) << "\n-----> Calling parseObjectTree( curNode->mChild )\n" << endl; ObjectTreeParser otp( mReader, cryptoProtocol() ); otp.parseObjectTree( child ); mRawReplyString += otp.rawReplyString(); mTextualContent += otp.textualContent(); if ( !otp.textualContentCharset().isEmpty() ) mTextualContentCharset = otp.textualContentCharset(); - kdDebug(5006) << "\n<----- Returning from parseObjectTree( curNode->mChild )\n" << endl; + //kdDebug(5006) << "\n<----- Returning from parseObjectTree( curNode->mChild )\n" << endl; return true; } @@ -1350,7 +1547,7 @@ namespace KMail { if ( node->parentNode() && DwMime::kTypeMultipart == node->parentNode()->type() && DwMime::kSubtypeEncrypted == node->parentNode()->subType() ) { - kdDebug(5006) << "\n-----> Initially processing encrypted data\n" << endl; + //kdDebug(5006) << "\n-----> Initially processing encrypted data\n" << endl; node->setEncryptionState( KMMsgFullyEncrypted ); if ( keepEncryptions() ) { const TQCString cstr = node->msgPart().bodyDecoded(); @@ -1358,6 +1555,8 @@ namespace KMail { writeBodyString( cstr, node->trueFromAddress(), codecFor( node ), result, false ); mRawReplyString += cstr; + } else if ( mReader && !mReader->decryptMessage() ) { + writeDeferredDecryptionBlock(); } else { /* ATTENTION: This code is to be replaced by the planned 'auto-detect' feature. @@ -1369,6 +1568,7 @@ namespace KMail { std::vector signatures; bool passphraseError; bool actuallyEncrypted = true; + bool decryptionStarted; bool bOkDecrypt = okDecryptMIME( *node, decryptedData, @@ -1377,9 +1577,16 @@ namespace KMail { true, passphraseError, actuallyEncrypted, + decryptionStarted, messagePart.errorText, + messagePart.auditLogError, messagePart.auditLog ); + if ( decryptionStarted ) { + writeDecryptionInProgressBlock(); + return true; + } + // paint the frame if ( mReader ) { messagePart.isDecryptable = bOkDecrypt; @@ -1415,18 +1622,18 @@ namespace KMail { bool ObjectTreeParser::processApplicationPkcs7MimeSubtype( partNode * node, ProcessResult & result ) { if ( partNode * child = node->firstChild() ) { - kdDebug(5006) << "\n-----> Calling parseObjectTree( curNode->mChild )\n" << endl; + //kdDebug(5006) << "\n-----> Calling parseObjectTree( curNode->mChild )\n" << endl; ObjectTreeParser otp( mReader, cryptoProtocol() ); otp.parseObjectTree( child ); mRawReplyString += otp.rawReplyString(); mTextualContent += otp.textualContent(); if ( !otp.textualContentCharset().isEmpty() ) mTextualContentCharset = otp.textualContentCharset(); - kdDebug(5006) << "\n<----- Returning from parseObjectTree( curNode->mChild )\n" << endl; + //kdDebug(5006) << "\n<----- Returning from parseObjectTree( curNode->mChild )\n" << endl; return true; } - kdDebug(5006) << "\n-----> Initially processing signed and/or encrypted data\n" << endl; + //kdDebug(5006) << "\n-----> Initially processing signed and/or encrypted data\n" << endl; if ( !node->dwPart() || !node->dwPart()->hasHeaders() ) return false; @@ -1445,7 +1652,7 @@ namespace KMail { const TQByteArray certData = node->msgPart().bodyDecodedBinary(); - Kleo::ImportJob *import = smimeCrypto->importJob(); + const STD_NAMESPACE_PREFIX auto_ptr import( smimeCrypto->importJob() ); const GpgME::ImportResult res = import->exec( certData ); if ( res.error() ) { htmlWriter()->queue( i18n( "Sorry, certificate could not be imported.
" @@ -1491,11 +1698,14 @@ namespace KMail { htmlWriter()->queue( i18n( "Failed: %1 (%2)" ) .arg( (*it).fingerprint(), TQString::fromLocal8Bit( (*it).error().asString() ) ) ); - else if ( (*it).status() & ~GpgME::Import::ContainedSecretKey ) - if ( (*it).status() & GpgME::Import::ContainedSecretKey ) + else if ( (*it).status() & ~GpgME::Import::ContainedSecretKey ) { + if ( (*it).status() & GpgME::Import::ContainedSecretKey ) { htmlWriter()->queue( i18n( "New or changed: %1 (secret key available)" ).arg( (*it).fingerprint() ) ); - else + } + else { htmlWriter()->queue( i18n( "New or changed: %1" ).arg( (*it).fingerprint() ) ); + } + } htmlWriter()->queue( "
" ); } @@ -1520,10 +1730,12 @@ namespace KMail { // if we either *know* that it is an encrypted message part // or there is neither signed nor encrypted parameter. if ( !isSigned ) { - if ( isEncrypted ) - kdDebug(5006) << "pkcs7 mime == S/MIME TYPE: enveloped (encrypted) data" << endl; - else - kdDebug(5006) << "pkcs7 mime - type unknown - enveloped (encrypted) data ?" << endl; + if ( isEncrypted ) { + //kdDebug(5006) << "pkcs7 mime == S/MIME TYPE: enveloped (encrypted) data" << endl; + } + else { + //kdDebug(5006) << "pkcs7 mime - type unknown - enveloped (encrypted) data ?" << endl; + } TQCString decryptedData; PartMetaData messagePart; messagePart.isEncrypted = true; @@ -1532,57 +1744,69 @@ namespace KMail { std::vector signatures; bool passphraseError; bool actuallyEncrypted = true; + bool decryptionStarted; - if ( okDecryptMIME( *node, + if ( mReader && !mReader->decryptMessage() ) { + writeDeferredDecryptionBlock(); + isEncrypted = true; + signTestNode = 0; // PENDING(marc) to be abs. sure, we'd need to have to look at the content + } else { + const bool bOkDecrypt = okDecryptMIME( *node, decryptedData, signatureFound, signatures, false, passphraseError, actuallyEncrypted, + decryptionStarted, messagePart.errorText, - messagePart.auditLog ) ) { - kdDebug(5006) << "pkcs7 mime - encryption found - enveloped (encrypted) data !" << endl; - isEncrypted = true; - node->setEncryptionState( KMMsgFullyEncrypted ); - signTestNode = 0; - // paint the frame - messagePart.isDecryptable = true; - if ( mReader ) - htmlWriter()->queue( writeSigstatHeader( messagePart, - cryptoProtocol(), - node->trueFromAddress() ) ); - insertAndParseNewChildNode( *node, - &*decryptedData, - "encrypted data" ); - if ( mReader ) - htmlWriter()->queue( writeSigstatFooter( messagePart ) ); - } else { - // decryption failed, which could be because the part was encrypted but - // decryption failed, or because we didn't know if it was encrypted, tried, - // and failed. If the message was not actually encrypted, we continue - // assuming it's signed - if ( passphraseError || ( smimeType.isEmpty() && actuallyEncrypted ) ) { + messagePart.auditLogError, + messagePart.auditLog ); + if ( decryptionStarted ) { + writeDecryptionInProgressBlock(); + return true; + } + if ( bOkDecrypt ) { + //kdDebug(5006) << "pkcs7 mime - encryption found - enveloped (encrypted) data !" << endl; isEncrypted = true; + node->setEncryptionState( KMMsgFullyEncrypted ); signTestNode = 0; - } - - if ( isEncrypted ) { - kdDebug(5006) << "pkcs7 mime - ERROR: COULD NOT DECRYPT enveloped data !" << endl; // paint the frame - messagePart.isDecryptable = false; - if ( mReader ) { + messagePart.isDecryptable = true; + if ( mReader ) htmlWriter()->queue( writeSigstatHeader( messagePart, cryptoProtocol(), node->trueFromAddress() ) ); - if ( mReader->decryptMessage() ) - writePartIcon( &node->msgPart(), node->nodeId() ); - else - htmlWriter()->queue( TQString::fromUtf8( decryptedData ) ); + insertAndParseNewChildNode( *node, + &*decryptedData, + "encrypted data" ); + if ( mReader ) htmlWriter()->queue( writeSigstatFooter( messagePart ) ); - } } else { - kdDebug(5006) << "pkcs7 mime - NO encryption found" << endl; + // decryption failed, which could be because the part was encrypted but + // decryption failed, or because we didn't know if it was encrypted, tried, + // and failed. If the message was not actually encrypted, we continue + // assuming it's signed + if ( passphraseError || ( smimeType.isEmpty() && actuallyEncrypted ) ) { + isEncrypted = true; + signTestNode = 0; + } + + if ( isEncrypted ) { + //kdDebug(5006) << "pkcs7 mime - ERROR: COULD NOT DECRYPT enveloped data !" << endl; + // paint the frame + messagePart.isDecryptable = false; + if ( mReader ) { + htmlWriter()->queue( writeSigstatHeader( messagePart, + cryptoProtocol(), + node->trueFromAddress() ) ); + assert( mReader->decryptMessage() ); // handled above + writePartIcon( &node->msgPart(), node->nodeId() ); + htmlWriter()->queue( writeSigstatFooter( messagePart ) ); + } + } else { + //kdDebug(5006) << "pkcs7 mime - NO encryption found" << endl; + } } } if ( isEncrypted ) @@ -1591,10 +1815,12 @@ namespace KMail { // We now try signature verification if necessarry. if ( signTestNode ) { - if ( isSigned ) - kdDebug(5006) << "pkcs7 mime == S/MIME TYPE: opaque signed data" << endl; - else - kdDebug(5006) << "pkcs7 mime - type unknown - opaque signed data ?" << endl; + if ( isSigned ) { + //kdDebug(5006) << "pkcs7 mime == S/MIME TYPE: opaque signed data" << endl; + } + else { + //kdDebug(5006) << "pkcs7 mime - type unknown - opaque signed data ?" << endl; + } bool sigFound = writeOpaqueOrMultipartSignedData( 0, *signTestNode, @@ -1605,14 +1831,14 @@ namespace KMail { isEncrypted ); if ( sigFound ) { if ( !isSigned ) { - kdDebug(5006) << "pkcs7 mime - signature found - opaque signed data !" << endl; + //kdDebug(5006) << "pkcs7 mime - signature found - opaque signed data !" << endl; isSigned = true; } signTestNode->setSignatureState( KMMsgFullySigned ); if ( signTestNode != node ) node->setSignatureState( KMMsgFullySigned ); } else { - kdDebug(5006) << "pkcs7 mime - NO signature found :-(" << endl; + //kdDebug(5006) << "pkcs7 mime - NO signature found :-(" << endl; } } @@ -1666,8 +1892,8 @@ bool ObjectTreeParser::decryptChiasmus( const TQByteArray& data, TQByteArray& bo GlobalSettings::setChiasmusDecryptionKey( selectorDlg.key() ); assert( !GlobalSettings::chiasmusDecryptionKey().isEmpty() ); - Kleo::SpecialJob * job = chiasmus->specialJob( "x-decrypt", TQMap() ); - if ( !job ) { + const STD_NAMESPACE_PREFIX auto_ptr job( chiasmus->specialJob( "x-decrypt", TQMap() ) ); + if ( !job.get() ) { errorText = i18n( "Chiasmus backend does not offer the " "\"x-decrypt\" function. Please report this bug." ); return false; @@ -1761,8 +1987,7 @@ bool ObjectTreeParser::processApplicationMsTnefSubtype( partNode *node, ProcessR TQString htmlStr = "" "
"; if ( !fileName.isEmpty() ) - htmlStr += "" + htmlStr += "asHREF( "body" ) + "\">" + label + ""; else htmlStr += label; @@ -1816,8 +2041,6 @@ bool ObjectTreeParser::processApplicationMsTnefSubtype( partNode *node, ProcessR if ( !mReader || !msgPart ) return; - kdDebug(5006) << "writePartIcon: PartNum: " << partNum << endl; - TQString label = msgPart->fileName(); if( label.isEmpty() ) label = msgPart->name(); @@ -1831,9 +2054,7 @@ bool ObjectTreeParser::processApplicationMsTnefSubtype( partNode *node, ProcessR TQString fileName = mReader->writeMessagePartToTempFile( msgPart, partNum ); - TQString href = fileName.isEmpty() ? - "part://" + TQString::number( partNum + 1 ) : - "file:" + KURL::encode_string( fileName ) ; + TQString href = TQString( "attachment:%1?place=body" ).arg( partNum ); TQString iconName; if( inlineImage ) @@ -1854,13 +2075,13 @@ bool ObjectTreeParser::processApplicationMsTnefSubtype( partNode *node, ProcessR if( inlineImage ) // show the filename of the image below the embedded image htmlWriter()->queue( "
" - "" + "" "
" "" "
" + comment + "

" ); else - // show the filename next to the image + // show the filename next to the icon htmlWriter()->queue( "" @@ -2077,16 +2298,29 @@ static TQString beginVerboseSigstatHeader() return ""; html += "
"; } -static TQString makeShowAuditLogLink( const TQString & auditLog ) { - if ( auditLog.isEmpty() ) - return i18n("No Audit Log available"); +static TQString makeShowAuditLogLink( const GpgME::Error & err, const TQString & auditLog ) { + if ( const unsigned int code = err.code() ) { + if ( code == GPG_ERR_NOT_IMPLEMENTED ) { + //kdDebug(5006) << "makeShowAuditLogLink: not showing link (not implemented)" << endl; + return TQString(); + } else if ( code == GPG_ERR_NO_DATA ) { + //kdDebug(5006) << "makeShowAuditLogLink: not showing link (not available)" << endl; + return i18n("No Audit Log available"); + } else { + return i18n("Error Retrieving Audit Log: %1").arg( TQString::fromLocal8Bit( err.asString() ) ); + } + } - KURL url; - url.setProtocol( "kmail" ); - url.setPath( "showAuditLog" ); - url.addQueryItem( "log", auditLog ); + if ( !auditLog.isEmpty() ) { + KURL url; + url.setProtocol( "kmail" ); + url.setPath( "showAuditLog" ); + url.addQueryItem( "log", auditLog ); - return "" + i18n("The Audit Log is a detailed error log from the gnupg backend", "Show Audit Log") + ""; + return "" + i18n("The Audit Log is a detailed error log from the gnupg backend", "Show Audit Log") + ""; + } + + return TQString::null; } static TQString endVerboseSigstatHeader( const PartMetaData & pmd ) @@ -2097,7 +2331,7 @@ static TQString endVerboseSigstatHeader( const PartMetaData & pmd ) html += i18n( "Hide Details" ); html += "
"; - html += makeShowAuditLogLink( pmd.auditLog ); + html += makeShowAuditLogLink( pmd.auditLogError, pmd.auditLog ); html += "
"; return html; } @@ -2105,7 +2339,7 @@ static TQString endVerboseSigstatHeader( const PartMetaData & pmd ) TQString ObjectTreeParser::writeSigstatHeader( PartMetaData & block, const Kleo::CryptoBackend::Protocol * cryptProto, const TQString & fromAddress, - const TQString & filename ) + partNode *node ) { const bool isSMIME = cryptProto && ( cryptProto == Kleo::CryptoBackendFactory::instance()->smime() ); TQString signer = block.signer; @@ -2118,12 +2352,11 @@ TQString ObjectTreeParser::writeSigstatHeader( PartMetaData & block, { htmlStr += "" "
"; - if( !filename.isEmpty() ) - htmlStr += "" + if ( node ) + htmlStr += "asHREF( "body" ) + "\">" + i18n("Encapsulated message") + ""; else - htmlStr += i18n("Encapsulated message"); + htmlStr += i18n("Encapsulated message"); htmlStr += "
"; } @@ -2131,7 +2364,9 @@ TQString ObjectTreeParser::writeSigstatHeader( PartMetaData & block, { htmlStr += "" "
"; - if( block.isDecryptable ) + if ( block.inProgress ) + htmlStr += i18n("Please wait while the message is being decrypted..."); + else if ( block.isDecryptable ) htmlStr += i18n("Encrypted message"); else { htmlStr += i18n("Encrypted message (decryption not possible)"); @@ -2140,9 +2375,18 @@ TQString ObjectTreeParser::writeSigstatHeader( PartMetaData & block, } htmlStr += "
"; } + + if ( block.isSigned && block.inProgress ) + { + block.signClass = "signInProgress"; + htmlStr += "" + "
"; + htmlStr += i18n("Please wait while the signature is being verified..."); + htmlStr += "
"; + } simpleHtmlStr = htmlStr; - if( block.isSigned ) { + if ( block.isSigned && !block.inProgress ) { TQStringList& blockAddrs( block.signerMailAddresses ); // note: At the moment frameColor and showKeyInfos are // used for CMS only but not for PGP signatures @@ -2232,7 +2476,7 @@ TQString ObjectTreeParser::writeSigstatHeader( PartMetaData & block, if( block.keyId.isEmpty() ) certificate = i18n("certificate"); else - certificate = startKeyHREF + i18n("certificate") + ""; + certificate = startKeyHREF + i18n("certificate") + ""; if( !blockAddrs.empty() ){ if( blockAddrs.grep( msgFrom, @@ -2512,6 +2756,26 @@ TQString ObjectTreeParser::writeSigstatFooter( PartMetaData& block ) return htmlStr; } +//----------------------------------------------------------------------------- + +void ObjectTreeParser::writeAttachmentMarkHeader( partNode *node ) +{ + if ( !mReader ) + return; + + htmlWriter()->queue( TQString( "
\n" ).arg( node->nodeId() ) ); +} + +//----------------------------------------------------------------------------- + +void ObjectTreeParser::writeAttachmentMarkFooter() +{ + if ( !mReader ) + return; + + htmlWriter()->queue( TQString( "
" ) ); +} + //----------------------------------------------------------------------------- void ObjectTreeParser::writeBodyStr( const TQCString& aStr, const TQTextCodec *aCodec, const TQString& fromAddress ) @@ -2562,8 +2826,8 @@ void ObjectTreeParser::writeBodyStr( const TQCString& aStr, const TQTextCodec *a TQCString str( *npbit ); if( !str.isEmpty() ) { htmlStr += quotedHTML( aCodec->toUnicode( str ), decorate ); - kdDebug( 5006 ) << "Non-empty Non-OpenPGP block found: '" << str - << "'" << endl; + //kdDebug( 5006 ) << "Non-empty Non-OpenPGP block found: '" << str + // << "'" << endl; // treat messages with empty lines before the first clearsigned // block as fully signed/encrypted if( firstNonPgpBlock ) { @@ -2706,7 +2970,7 @@ TQString ObjectTreeParser::quotedHTML( const TQString& s, bool decorate ) const unsigned int length = s.length(); // skip leading empty lines - for ( pos = 0; pos < length && s[pos] <= ' '; pos++ ); + for ( pos = 0; pos < length && s[pos] <= ' '; pos++ ) { ; } while (pos > 0 && (s[pos-1] == ' ' || s[pos-1] == '\t')) pos--; beg = pos; @@ -2836,10 +3100,6 @@ TQString ObjectTreeParser::quotedHTML( const TQString& s, bool decorate ) else htmlStr.append( quoteEnd ); - //kdDebug(5006) << "KMReaderWin::quotedHTML:\n" - // << "========================================\n" - // << htmlStr - // << "\n======================================\n"; return htmlStr; } diff --git a/kmail/objecttreeparser.h b/kmail/objecttreeparser.h index 2edc406f9..ad980edaf 100644 --- a/kmail/objecttreeparser.h +++ b/kmail/objecttreeparser.h @@ -40,12 +40,18 @@ #include #include +#include + class KMReaderWin; class KMMessagePart; class TQString; class TQWidget; class partNode; +namespace GpgME { + class Error; +} + namespace KMail { class AttachmentStrategy; @@ -110,6 +116,11 @@ namespace KMail { KMail::CSSHelper * cssHelper=0 ); virtual ~ObjectTreeParser(); + void setAllowAsync( bool allow ) { assert( !mHasPendingAsyncJobs ); mAllowAsync = allow; } + bool allowAsync() const { return mAllowAsync; } + + bool hasPendingAsyncJobs() const { return mHasPendingAsyncJobs; } + TQCString rawReplyString() const { return mRawReplyString; } /*! @return the text of the message, ie. what would appear in the @@ -140,6 +151,15 @@ namespace KMail { mIncludeSignatures = include; } + // Controls whether Toltec invitations are displayed in their raw form or as a replacement text, + // which is used in processToltecMail(). + void setShowRawToltecMail( bool showRawToltecMail ) { mShowRawToltecMail = showRawToltecMail; } + bool showRawToltecMail() const { return mShowRawToltecMail; } + + /// default text for processToltecMail(), which is used in kmail.kcfg, therefore it + /// needs to be static here. + static TQString defaultToltecReplacementText(); + const KMail::AttachmentStrategy * attachmentStrategy() const { return mAttachmentStrategy; } @@ -161,16 +181,24 @@ namespace KMail { void defaultHandling( partNode * node, ProcessResult & result ); - /** 1. Create a new partNode using 'content' data and Content-Description - found in 'cntDesc'. - 2. Make this node the child of 'node'. - 3. Insert the respective entries in the Mime Tree Viewer. - 3. Parse the 'node' to display the content. */ + /** + * 1. Create a new partNode using 'content' data and Content-Description + * found in 'cntDesc'. + * 2. Make this node the child of 'node'. + * 3. Insert the respective entries in the Mime Tree Viewer. + * 3. Parse the 'node' to display the content. + * + * @param addToTextualContent If true, this will add the textual content of the parsed node + * to the textual content of the current object tree parser. + * Setting this to false is useful for encapsulated messages, as we + * do not want the text in those to appear in the editor + */ // Function will be replaced once KMime is alive. void insertAndParseNewChildNode( partNode & node, const char * content, const char * cntDesc, - bool append=false ); + bool append=false, + bool addToTextualContent = true ); /** if data is 0: Feeds the HTML widget with the contents of the opaque signed data found in partNode 'sign'. @@ -186,9 +214,17 @@ namespace KMail { const TQString & fromAddress, bool doCheck=true, TQCString * cleartextData=0, - std::vector paramSignatures = std::vector(), + const std::vector & paramSignatures = std::vector(), bool hideErrors=false ); + /** Writes out the block that we use when the node is encrypted, + but we're deferring decryption for later. */ + void writeDeferredDecryptionBlock(); + + /** Writes out the block that we use when the node is encrypted, + but we've just kicked off async decryption. */ + void writeDecryptionInProgressBlock(); + /** Returns the contents of the given multipart/encrypted object. Data is decypted. May contain body parts. */ bool okDecryptMIME( partNode& data, @@ -198,11 +234,24 @@ namespace KMail { bool showWarning, bool& passphraseError, bool& actuallyEncrypted, + bool& decryptionStarted, TQString& aErrorText, + GpgME::Error & auditLogError, TQString& auditLog ); bool processMailmanMessage( partNode * node ); + /** + * This is called for all multipart/mixed nodes. It checks if that belongs to a Toltec mail, + * by checking various criteria. + * If it is a toltec mail, a special text, instead of the confusing toltec text, will be + * displayed. + * + * @return true if the mail was indeed a toltec mail, in which case the node should not be + * processed further + */ + bool processToltecMail( partNode * node ); + /** Checks whether @p str contains external references. To be precise, we only check whether @p str contains 'xxx="http[s]:' where xxx is not href. Obfuscated external references are ignored on purpose. @@ -245,9 +294,15 @@ namespace KMail { TQString writeSigstatHeader( KMail::PartMetaData & part, const Kleo::CryptoBackend::Protocol * cryptProto, const TQString & fromAddress, - const TQString & filename = TQString::null ); + partNode *node = 0 ); TQString writeSigstatFooter( KMail::PartMetaData & part ); + // The attachment mark is a div that is placed around the attchment. It is used for drawing + // a yellow border around the attachment when scrolling to it. When scrolling to it, the border + // color of the div is changed, see KMReaderWin::scrollToAttachment(). + void writeAttachmentMarkHeader( partNode *node ); + void writeAttachmentMarkFooter(); + void writeBodyStr( const TQCString & bodyString, const TQTextCodec * aCodec, const TQString & fromAddress, @@ -281,6 +336,9 @@ namespace KMail { bool mShowOnlyOneMimePart; bool mKeepEncryptions; bool mIncludeSignatures; + bool mHasPendingAsyncJobs; + bool mAllowAsync; + bool mShowRawToltecMail; const KMail::AttachmentStrategy * mAttachmentStrategy; KMail::HtmlWriter * mHtmlWriter; KMail::CSSHelper * mCSSHelper; diff --git a/kmail/objecttreeparser_p.cpp b/kmail/objecttreeparser_p.cpp new file mode 100644 index 000000000..a645b3989 --- /dev/null +++ b/kmail/objecttreeparser_p.cpp @@ -0,0 +1,350 @@ +/* -*- mode: C++; c-file-style: "gnu" -*- + objecttreeparser_p.cpp + + This file is part of KMail, the KDE mail client. + Copyright (c) 2009 Klarälvdalens Datakonsult AB + Authors: Marc Mutz + + KMail is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + KMail 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 TQt library by Trolltech AS, Norway (or with modified versions + of TQt that use the same license as TQt), 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 + TQt. 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 "objecttreeparser_p.h" + +#include +#include +#include +#include + +#include + +#include +#include + +#include + +using namespace KMail; +using namespace Kleo; +using namespace GpgME; + +CryptoBodyPartMemento::CryptoBodyPartMemento() + : TQObject( 0 ), + Interface::BodyPartMemento(), + ISubject(), + m_running( false ) +{ + +} + +CryptoBodyPartMemento::~CryptoBodyPartMemento() {} + +void CryptoBodyPartMemento::setAuditLog( const GpgME::Error & err, const TQString & log ) { + m_auditLogError = err; + m_auditLog = log; +} + +void CryptoBodyPartMemento::setRunning( bool running ) { + m_running = running; +} + +DecryptVerifyBodyPartMemento::DecryptVerifyBodyPartMemento( DecryptVerifyJob * job, const TQByteArray & cipherText ) + : CryptoBodyPartMemento(), + m_cipherText( cipherText ), + m_job( job ) +{ + assert( m_job ); +} + +DecryptVerifyBodyPartMemento::~DecryptVerifyBodyPartMemento() { + if ( m_job ) + m_job->slotCancel(); +} + +bool DecryptVerifyBodyPartMemento::start() { + assert( m_job ); + if ( const GpgME::Error err = m_job->start( m_cipherText ) ) { + m_dr = DecryptionResult( err ); + return false; + } + connect( m_job, TQT_SIGNAL(result(const GpgME::DecryptionResult&,const GpgME::VerificationResult&,const TQByteArray&)), + this, TQT_SLOT(slotResult(const GpgME::DecryptionResult&,const GpgME::VerificationResult&,const TQByteArray&)) ); + setRunning( true ); + return true; +} + +void DecryptVerifyBodyPartMemento::exec() { + assert( m_job ); + TQByteArray plainText; + setRunning( true ); + const std::pair p = m_job->exec( m_cipherText, plainText ); + saveResult( p.first, p.second, plainText ); + m_job->deleteLater(); // exec'ed jobs don't delete themselves + m_job = 0; +} + +void DecryptVerifyBodyPartMemento::saveResult( const DecryptionResult & dr, + const VerificationResult & vr, + const TQByteArray & plainText ) +{ + assert( m_job ); + setRunning( false ); + m_dr = dr; + m_vr = vr; + m_plainText = plainText; + setAuditLog( m_job->auditLogError(), m_job->auditLogAsHtml() ); +} + +void DecryptVerifyBodyPartMemento::slotResult( const DecryptionResult & dr, + const VerificationResult & vr, + const TQByteArray & plainText ) +{ + saveResult( dr, vr, plainText ); + m_job = 0; + notify(); +} + + + + +VerifyDetachedBodyPartMemento::VerifyDetachedBodyPartMemento( VerifyDetachedJob * job, + KeyListJob * klj, + const TQByteArray & signature, + const TQByteArray & plainText ) + : CryptoBodyPartMemento(), + m_signature( signature ), + m_plainText( plainText ), + m_job( job ), + m_keylistjob( klj ) +{ + assert( m_job ); +} + +VerifyDetachedBodyPartMemento::~VerifyDetachedBodyPartMemento() { + if ( m_job ) + m_job->slotCancel(); + if ( m_keylistjob ) + m_keylistjob->slotCancel(); +} + +bool VerifyDetachedBodyPartMemento::start() { + assert( m_job ); + if ( const GpgME::Error err = m_job->start( m_signature, m_plainText ) ) { + m_vr = VerificationResult( err ); + return false; + } + connect( m_job, TQT_SIGNAL(result(const GpgME::VerificationResult&)), + this, TQT_SLOT(slotResult(const GpgME::VerificationResult&)) ); + setRunning( true ); + return true; +} + +void VerifyDetachedBodyPartMemento::exec() { + assert( m_job ); + setRunning( true ); + saveResult( m_job->exec( m_signature, m_plainText ) ); + m_job->deleteLater(); // exec'ed jobs don't delete themselves + m_job = 0; + if ( canStartKeyListJob() ) { + std::vector keys; + m_keylistjob->exec( keyListPattern(), /*secretOnly=*/false, keys ); + if ( !keys.empty() ) + m_key = keys.back(); + } + if ( m_keylistjob ) + m_keylistjob->deleteLater(); // exec'ed jobs don't delete themselves + m_keylistjob = 0; + setRunning( false ); +} + +bool VerifyDetachedBodyPartMemento::canStartKeyListJob() const +{ + if ( !m_keylistjob ) + return false; + const char * const fpr = m_vr.signature( 0 ).fingerprint(); + return fpr && *fpr; +} + +TQStringList VerifyDetachedBodyPartMemento::keyListPattern() const +{ + assert( canStartKeyListJob() ); + return TQStringList( TQString::fromLatin1( m_vr.signature( 0 ).fingerprint() ) ); +} + +void VerifyDetachedBodyPartMemento::saveResult( const VerificationResult & vr ) +{ + assert( m_job ); + m_vr = vr; + setAuditLog( m_job->auditLogError(), m_job->auditLogAsHtml() ); +} + +void VerifyDetachedBodyPartMemento::slotResult( const VerificationResult & vr ) +{ + saveResult( vr ); + m_job = 0; + if ( canStartKeyListJob() && startKeyListJob() ) + return; + if ( m_keylistjob ) + m_keylistjob->deleteLater(); + m_keylistjob = 0; + setRunning( false ); + notify(); +} + +bool VerifyDetachedBodyPartMemento::startKeyListJob() +{ + assert( canStartKeyListJob() ); + if ( const GpgME::Error err = m_keylistjob->start( keyListPattern() ) ) + return false; + connect( m_keylistjob, TQT_SIGNAL(done()), this, TQT_SLOT(slotKeyListJobDone()) ); + connect( m_keylistjob, TQT_SIGNAL(nextKey(const GpgME::Key&)), + this, TQT_SLOT(slotNextKey(const GpgME::Key&)) ); + return true; +} + +void VerifyDetachedBodyPartMemento::slotNextKey( const GpgME::Key & key ) +{ + m_key = key; +} + +void VerifyDetachedBodyPartMemento::slotKeyListJobDone() +{ + m_keylistjob = 0; + setRunning( false ); + notify(); +} + + +VerifyOpaqueBodyPartMemento::VerifyOpaqueBodyPartMemento( VerifyOpaqueJob * job, + KeyListJob * klj, + const TQByteArray & signature ) + : CryptoBodyPartMemento(), + m_signature( signature ), + m_job( job ), + m_keylistjob( klj ) +{ + assert( m_job ); +} + +VerifyOpaqueBodyPartMemento::~VerifyOpaqueBodyPartMemento() { + if ( m_job ) + m_job->slotCancel(); + if ( m_keylistjob ) + m_keylistjob->slotCancel(); +} + +bool VerifyOpaqueBodyPartMemento::start() { + assert( m_job ); + if ( const GpgME::Error err = m_job->start( m_signature ) ) { + m_vr = VerificationResult( err ); + return false; + } + connect( m_job, TQT_SIGNAL(result(const GpgME::VerificationResult&,const TQByteArray&)), + this, TQT_SLOT(slotResult(const GpgME::VerificationResult&,const TQByteArray&)) ); + setRunning( true ); + return true; +} + +void VerifyOpaqueBodyPartMemento::exec() { + assert( m_job ); + setRunning( true ); + TQByteArray plainText; + saveResult( m_job->exec( m_signature, plainText ), plainText ); + m_job->deleteLater(); // exec'ed jobs don't delete themselves + m_job = 0; + if ( canStartKeyListJob() ) { + std::vector keys; + m_keylistjob->exec( keyListPattern(), /*secretOnly=*/false, keys ); + if ( !keys.empty() ) + m_key = keys.back(); + } + if ( m_keylistjob ) + m_keylistjob->deleteLater(); // exec'ed jobs don't delete themselves + m_keylistjob = 0; + setRunning( false ); +} + +bool VerifyOpaqueBodyPartMemento::canStartKeyListJob() const +{ + if ( !m_keylistjob ) + return false; + const char * const fpr = m_vr.signature( 0 ).fingerprint(); + return fpr && *fpr; +} + +TQStringList VerifyOpaqueBodyPartMemento::keyListPattern() const +{ + assert( canStartKeyListJob() ); + return TQStringList( TQString::fromLatin1( m_vr.signature( 0 ).fingerprint() ) ); +} + +void VerifyOpaqueBodyPartMemento::saveResult( const VerificationResult & vr, + const TQByteArray & plainText ) +{ + assert( m_job ); + m_vr = vr; + m_plainText = plainText; + setAuditLog( m_job->auditLogError(), m_job->auditLogAsHtml() ); +} + +void VerifyOpaqueBodyPartMemento::slotResult( const VerificationResult & vr, + const TQByteArray & plainText ) +{ + saveResult( vr, plainText ); + m_job = 0; + if ( canStartKeyListJob() && startKeyListJob() ) + return; + if ( m_keylistjob ) + m_keylistjob->deleteLater(); + m_keylistjob = 0; + setRunning( false ); + notify(); +} + +bool VerifyOpaqueBodyPartMemento::startKeyListJob() +{ + assert( canStartKeyListJob() ); + if ( const GpgME::Error err = m_keylistjob->start( keyListPattern() ) ) + return false; + connect( m_keylistjob, TQT_SIGNAL(done()), this, TQT_SLOT(slotKeyListJobDone()) ); + connect( m_keylistjob, TQT_SIGNAL(nextKey(const GpgME::Key&)), + this, TQT_SLOT(slotNextKey(const GpgME::Key&)) ); + return true; +} + +void VerifyOpaqueBodyPartMemento::slotNextKey( const GpgME::Key & key ) +{ + m_key = key; +} + +void VerifyOpaqueBodyPartMemento::slotKeyListJobDone() +{ + m_keylistjob = 0; + setRunning( false ); + notify(); +} + + +#include "objecttreeparser_p.moc" diff --git a/kmail/objecttreeparser_p.h b/kmail/objecttreeparser_p.h new file mode 100644 index 000000000..d70cd8c70 --- /dev/null +++ b/kmail/objecttreeparser_p.h @@ -0,0 +1,203 @@ +/* -*- mode: C++; c-file-style: "gnu" -*- + objecttreeparser_p.h + + This file is part of KMail, the KDE mail client. + Copyright (c) 2009 Klarälvdalens Datakonsult AB + + KMail is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License, version 2, as + published by the Free Software Foundation. + + KMail 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 TQt library by Trolltech AS, Norway (or with modified versions + of TQt that use the same license as TQt), 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 + TQt. 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. +*/ + +#ifndef _KMAIL_OBJECTTREEPARSER_P_H_ +#define _KMAIL_OBJECTTREEPARSER_P_H_ + +#include +#include +#include + +#include +#include +#include +#include + +#include "isubject.h" +#include "interfaces/bodypart.h" + +namespace Kleo { + class DecryptVerifyJob; + class VerifyDetachedJob; + class VerifyOpaqueJob; + class KeyListJob; +} + +class TQStringList; + +namespace KMail { + + class CryptoBodyPartMemento + : public TQObject, + public KMail::Interface::BodyPartMemento, + public KMail::ISubject + { + Q_OBJECT + public: + CryptoBodyPartMemento(); + ~CryptoBodyPartMemento(); + + /* reimp */ Interface::Observer * asObserver() { return 0; } + /* reimp */ Interface::Observable * asObservable() { return this; } + + bool isRunning() const { return m_running; } + + const TQString & auditLogAsHtml() const { return m_auditLog; } + GpgME::Error auditLogError() const { return m_auditLogError; } + + protected: + void setAuditLog( const GpgME::Error & err, const TQString & log ); + void setRunning( bool running ); + + private: + bool m_running; + TQString m_auditLog; + GpgME::Error m_auditLogError; + }; + + class DecryptVerifyBodyPartMemento + : public CryptoBodyPartMemento + { + Q_OBJECT + public: + DecryptVerifyBodyPartMemento( Kleo::DecryptVerifyJob * job, const TQByteArray & cipherText ); + ~DecryptVerifyBodyPartMemento(); + + bool start(); + void exec(); + + const TQByteArray & plainText() const { return m_plainText; } + const GpgME::DecryptionResult & decryptResult() const { return m_dr; } + const GpgME::VerificationResult & verifyResult() const { return m_vr; } + + private slots: + void slotResult( const GpgME::DecryptionResult & dr, + const GpgME::VerificationResult & vr, + const TQByteArray & plainText ); + + private: + void saveResult( const GpgME::DecryptionResult &, + const GpgME::VerificationResult &, + const TQByteArray & ); + private: + // input: + const TQByteArray m_cipherText; + TQGuardedPtr m_job; + // output: + GpgME::DecryptionResult m_dr; + GpgME::VerificationResult m_vr; + TQByteArray m_plainText; + }; + + + class VerifyDetachedBodyPartMemento + : public CryptoBodyPartMemento + { + Q_OBJECT + public: + VerifyDetachedBodyPartMemento( Kleo::VerifyDetachedJob * job, + Kleo::KeyListJob * klj, + const TQByteArray & signature, + const TQByteArray & plainText ); + ~VerifyDetachedBodyPartMemento(); + + bool start(); + void exec(); + + const GpgME::VerificationResult & verifyResult() const { return m_vr; } + const GpgME::Key & signingKey() const { return m_key; } + + private slots: + void slotResult( const GpgME::VerificationResult & vr ); + void slotKeyListJobDone(); + void slotNextKey( const GpgME::Key & ); + + private: + void saveResult( const GpgME::VerificationResult & ); + bool canStartKeyListJob() const; + TQStringList keyListPattern() const; + bool startKeyListJob(); + private: + // input: + const TQByteArray m_signature; + const TQByteArray m_plainText; + TQGuardedPtr m_job; + TQGuardedPtr m_keylistjob; + // output: + GpgME::VerificationResult m_vr; + GpgME::Key m_key; + }; + + + class VerifyOpaqueBodyPartMemento + : public CryptoBodyPartMemento + { + Q_OBJECT + public: + VerifyOpaqueBodyPartMemento( Kleo::VerifyOpaqueJob * job, + Kleo::KeyListJob * klj, + const TQByteArray & signature ); + ~VerifyOpaqueBodyPartMemento(); + + bool start(); + void exec(); + + const TQByteArray & plainText() const { return m_plainText; } + const GpgME::VerificationResult & verifyResult() const { return m_vr; } + const GpgME::Key & signingKey() const { return m_key; } + + private slots: + void slotResult( const GpgME::VerificationResult & vr, + const TQByteArray & plainText ); + void slotKeyListJobDone(); + void slotNextKey( const GpgME::Key & ); + + private: + void saveResult( const GpgME::VerificationResult &, + const TQByteArray & ); + bool canStartKeyListJob() const; + TQStringList keyListPattern() const; + bool startKeyListJob(); + private: + // input: + const TQByteArray m_signature; + TQGuardedPtr m_job; + TQGuardedPtr m_keylistjob; + // output: + GpgME::VerificationResult m_vr; + TQByteArray m_plainText; + GpgME::Key m_key; + }; + + +} // namespace KMail + +#endif // _KMAIL_OBJECTTREEPARSER_H_ diff --git a/kmail/partNode.cpp b/kmail/partNode.cpp index 80e0545bf..dbd5442ca 100644 --- a/kmail/partNode.cpp +++ b/kmail/partNode.cpp @@ -30,7 +30,10 @@ */ #include + #include "partNode.h" +#include "kmreaderwin.h" + #include #include #include "kmmimeparttree.h" @@ -64,12 +67,14 @@ partNode::partNode() mEncodedOk( false ), mDeleteDwBodyPart( false ), mMimePartTreeItem( 0 ), - mBodyPartMemento( 0 ) + mBodyPartMementoMap(), + mReader( 0 ), + mDisplayedEmbedded( false ) { adjustDefaultType( this ); } -partNode::partNode( DwBodyPart* dwPart, int explicitType, int explicitSubType, +partNode::partNode( KMReaderWin * win, DwBodyPart* dwPart, int explicitType, int explicitSubType, bool deleteDwBodyPart ) : mRoot( 0 ), mNext( 0 ), mChild( 0 ), mWasProcessed( false ), @@ -80,13 +85,15 @@ partNode::partNode( DwBodyPart* dwPart, int explicitType, int explicitSubType, mEncodedOk( false ), mDeleteDwBodyPart( deleteDwBodyPart ), mMimePartTreeItem( 0 ), - mBodyPartMemento( 0 ) + mBodyPartMementoMap(), + mReader( win ), + mDisplayedEmbedded( false ), + mDisplayedHidden( false ) { if ( explicitType != DwMime::kTypeUnknown ) { mType = explicitType; // this happens e.g. for the Root Node mSubType = explicitSubType; // representing the _whole_ message } else { -// kdDebug(5006) << "\n partNode::partNode() explicitType == DwMime::kTypeUnknown\n" << endl; if(dwPart && dwPart->hasHeaders() && dwPart->Headers().HasContentType()) { mType = (!dwPart->Headers().ContentType().Type())?DwMime::kTypeUnknown:dwPart->Headers().ContentType().Type(); mSubType = dwPart->Headers().ContentType().Subtype(); @@ -95,17 +102,9 @@ partNode::partNode( DwBodyPart* dwPart, int explicitType, int explicitSubType, mSubType = DwMime::kSubtypeUnknown; } } -#ifdef DEBUG - { - DwString type, subType; - DwTypeEnumToStr( mType, type ); - DwSubtypeEnumToStr( mSubType, subType ); - kdDebug(5006) << "\npartNode::partNode() " << type.c_str() << "/" << subType.c_str() << "\n" << endl; - } -#endif } -partNode * partNode::fromMessage( const KMMessage * msg ) { +partNode * partNode::fromMessage( const KMMessage * msg, KMReaderWin * win ) { if ( !msg ) return 0; @@ -124,11 +123,11 @@ partNode * partNode::fromMessage( const KMMessage * msg ) { // as just another DwBodyPart... DwBodyPart * mainBody = new DwBodyPart( *msg->getTopLevelPart() ); - partNode * root = new partNode( mainBody, mainType, mainSubType, true ); + partNode * root = new partNode( win, mainBody, mainType, mainSubType, true ); root->buildObjectTree(); root->setFromAddress( msg->from() ); - root->dump(); + //root->dump(); return root; } @@ -142,7 +141,9 @@ partNode::partNode( bool deleteDwBodyPart, DwBodyPart* dwPart ) mEncodedOk( false ), mDeleteDwBodyPart( deleteDwBodyPart ), mMimePartTreeItem( 0 ), - mBodyPartMemento( 0 ) + mBodyPartMementoMap(), + mReader( 0 ), + mDisplayedEmbedded( false ) { if ( dwPart && dwPart->hasHeaders() && dwPart->Headers().HasContentType() ) { mType = (!dwPart->Headers().ContentType().Type())?DwMime::kTypeUnknown:dwPart->Headers().ContentType().Type(); @@ -159,13 +160,16 @@ partNode::~partNode() { mDwPart = 0; delete mChild; mChild = 0; delete mNext; mNext = 0; - delete mBodyPartMemento; mBodyPartMemento = 0; + for ( std::map::const_iterator it = mBodyPartMementoMap.begin(), end = mBodyPartMementoMap.end() ; it != end ; ++it ) + delete it->second; + mBodyPartMementoMap.clear(); } #ifndef NDEBUG void partNode::dump( int chars ) const { - kdDebug(5006) << TQString().fill( ' ', chars ) << "+ " - << typeString() << '/' << subTypeString() << endl; + kdDebug(5006) << nodeId() << " " << TQString().fill( ' ', chars ) << "+ " + << typeString() << '/' << subTypeString() << " embedded:" << mDisplayedEmbedded + << " address:" << this << endl; if ( mChild ) mChild->dump( chars + 1 ); if ( mNext ) @@ -194,7 +198,7 @@ void partNode::buildObjectTree( bool processSiblings ) while( curNode && curNode->dwPart() ) { //dive into multipart messages while( DwMime::kTypeMultipart == curNode->type() ) { - partNode * newNode = new partNode( curNode->dwPart()->Body().FirstBodyPart() ); + partNode * newNode = new partNode( mReader, curNode->dwPart()->Body().FirstBodyPart() ); curNode->setFirstChild( newNode ); curNode = newNode; } @@ -210,7 +214,7 @@ void partNode::buildObjectTree( bool processSiblings ) return; // store next node if( curNode && curNode->dwPart() && curNode->dwPart()->Next() ) { - partNode* nextNode = new partNode( curNode->dwPart()->Next() ); + partNode* nextNode = new partNode( mReader, curNode->dwPart()->Next() ); curNode->setNext( nextNode ); curNode = nextNode; } else @@ -230,6 +234,13 @@ TQCString partNode::subTypeString() const { return s.c_str(); } +const partNode* partNode::topLevelParent() const { + const partNode *ret = this; + while ( ret->parentNode() ) + ret = ret->parentNode(); + return ret; +} + int partNode::childCount() const { int count = 0; for ( partNode * child = firstChild() ; child ; child = child->nextSibling() ) @@ -237,6 +248,15 @@ int partNode::childCount() const { return count; } +int partNode::totalChildCount() const { + int count = 0; + for ( partNode * child = firstChild() ; child ; child = child->nextSibling() ) { + ++count; + count += child->totalChildCount(); + } + return count; +} + TQString partNode::contentTypeParameter( const char * name ) const { if ( !mDwPart || !mDwPart->hasHeaders() ) return TQString::null; @@ -292,8 +312,6 @@ KMMsgEncryptionState partNode::overallEncryptionState() const } } -//kdDebug(5006) << "\n\n KMMsgEncryptionState: " << myState << endl; - return myState; } @@ -335,11 +353,24 @@ KMMsgSignatureState partNode::overallSignatureState() const } } -//kdDebug(5006) << "\n\n KMMsgSignatureState: " << myState << endl; - return myState; } +TQCString partNode::path() const +{ + if ( !parentNode() ) + return ':'; + const partNode * p = parentNode(); + + // count number of siblings with the same type as us: + int nth = 0; + for ( const partNode * c = p->firstChild() ; c != this ; c = c->nextSibling() ) + if ( c->type() == type() && c->subType() == subType() ) + ++nth; + + return p->path() + TQCString().sprintf( ":%X/%X[%X]", type(), subType(), nth ); +} + int partNode::nodeId() const { @@ -392,13 +423,6 @@ int partNode::calcNodeIdOrFindNode( int &curId, const partNode* findNode, int fi partNode* partNode::findType( int type, int subType, bool deep, bool wide ) { -#ifndef NDEBUG - DwString typeStr, subTypeStr; - DwTypeEnumToStr( mType, typeStr ); - DwSubtypeEnumToStr( mSubType, subTypeStr ); - kdDebug(5006) << "partNode::findType() is looking at " << typeStr.c_str() - << "/" << subTypeStr.c_str() << endl; -#endif if( (mType != DwMime::kTypeUnknown) && ( (type == DwMime::kTypeUnknown) || (type == mType) ) @@ -470,12 +494,12 @@ void partNode::fillMimePartTree( KMMimePartTreeItem* parentItem, } else cntType = "text/plain"; - if( cntDesc.isEmpty() ) - cntDesc = msgPart().contentDescription(); if( cntDesc.isEmpty() ) cntDesc = msgPart().name().stripWhiteSpace(); if( cntDesc.isEmpty() ) cntDesc = msgPart().fileName(); + if( cntDesc.isEmpty() ) + cntDesc = msgPart().contentDescription(); if( cntDesc.isEmpty() ) { if( mRoot && mRoot->mRoot ) cntDesc = i18n("internal part"); @@ -494,8 +518,6 @@ void partNode::fillMimePartTree( KMMimePartTreeItem* parentItem, // remove linebreak+whitespace from folded Content-Description cntDesc.replace( TQRegExp("\\n\\s*"), " " ); -kdDebug(5006) << " Inserting one item into MimePartTree" << endl; -kdDebug(5006) << " Content-Type: " << cntType << endl; if( parentItem ) mMimePartTreeItem = new KMMimePartTreeItem( parentItem, this, @@ -547,6 +569,13 @@ bool partNode::isAttachment() const if ( !dwPart()->hasHeaders() ) return false; DwHeaders& headers = dwPart()->Headers(); + if ( headers.HasContentType() && + headers.ContentType().Type() == DwMime::kTypeMessage && + headers.ContentType().Subtype() == DwMime::kSubtypeRfc822 ) { + // Messages are always attachments. Normally message attachments created from KMail have a content + // disposition, but some mail clients omit that. + return true; + } if( !headers.HasContentDisposition() ) return false; return ( headers.ContentDisposition().DispositionType() @@ -591,6 +620,47 @@ bool partNode::isFirstTextPart() const { return false; // make comiler happy } +bool partNode::isToltecMessage() const +{ + if ( type() != DwMime::kTypeMultipart || subType() != DwMime::kSubtypeMixed ) + return false; + + if ( childCount() != 3 ) + return false; + + const DwField* library = dwPart()->Headers().FindField( "X-Library" ); + if ( !library ) + return false; + + if ( !library->FieldBody() || + TQString( library->FieldBody()->AsString().c_str() ) != TQString( "Toltec" ) ) + return false; + + const DwField* kolabType = dwPart()->Headers().FindField( "X-Kolab-Type" ); + if ( !kolabType ) + return false; + + if ( !kolabType->FieldBody() || + !TQString( kolabType->FieldBody()->AsString().c_str() ).startsWith( "application/x-vnd.kolab" ) ) + return false; + + return true; +} + +bool partNode::isInEncapsulatedMessage() const +{ + const partNode * const topLevel = topLevelParent(); + const partNode *cur = this; + while ( cur && cur != topLevel ) { + const bool parentIsMessage = cur->parentNode() && + cur->parentNode()->msgPart().typeStr().lower() == "message"; + if ( parentIsMessage && cur->parentNode() != topLevel ) + return true; + cur = cur->parentNode(); + } + return false; +} + bool partNode::hasContentDispositionInline() const { if( !dwPart() ) @@ -610,3 +680,97 @@ const TQString& partNode::trueFromAddress() const node = node->mRoot; return node->mFromAddress; } + +KMail::Interface::BodyPartMemento * partNode::bodyPartMemento( const TQCString & which ) const +{ + if ( const KMReaderWin * r = reader() ) + return r->bodyPartMemento( this, which ); + else + return internalBodyPartMemento( which ); +} + +KMail::Interface::BodyPartMemento * partNode::internalBodyPartMemento( const TQCString & which ) const +{ + assert( !reader() ); + + const std::map::const_iterator it = mBodyPartMementoMap.find( which.lower() ); + return it != mBodyPartMementoMap.end() ? it->second : 0 ; +} + +void partNode::setBodyPartMemento( const TQCString & which, KMail::Interface::BodyPartMemento * memento ) +{ + if ( KMReaderWin * r = reader() ) + r->setBodyPartMemento( this, which, memento ); + else + internalSetBodyPartMemento( which, memento ); +} + +void partNode::internalSetBodyPartMemento( const TQCString & which, KMail::Interface::BodyPartMemento * memento ) +{ + assert( !reader() ); + + const std::map::iterator it = mBodyPartMementoMap.lower_bound( which.lower() ); + if ( it != mBodyPartMementoMap.end() && it->first == which.lower() ) { + delete it->second; + if ( memento ) { + it->second = memento; + } + else { + mBodyPartMementoMap.erase( it ); + } + } else { + mBodyPartMementoMap.insert( it, std::make_pair( which.lower(), memento ) ); + } +} + +bool partNode::isDisplayedEmbedded() const +{ + return mDisplayedEmbedded; +} + +void partNode::setDisplayedEmbedded( bool displayedEmbedded ) +{ + mDisplayedEmbedded = displayedEmbedded; +} + +bool partNode::isDisplayedHidden() const +{ + return mDisplayedHidden; +} + +void partNode::setDisplayedHidden( bool displayedHidden ) +{ + mDisplayedHidden = displayedHidden; +} + + +TQString partNode::asHREF( const TQString &place ) const +{ + return TQString( "attachment:%1?place=%2" ).arg( nodeId() ).arg( place ); +} + +partNode::AttachmentDisplayInfo partNode::attachmentDisplayInfo() const +{ + AttachmentDisplayInfo info; + info.icon = msgPart().iconName( KIcon::Small ); + info.label = msgPart().name().stripWhiteSpace(); + if ( info.label.isEmpty() ) + info.label = msgPart().fileName(); + if ( info.label.isEmpty() ) + info.label = msgPart().contentDescription(); + bool typeBlacklisted = msgPart().typeStr().lower() == "multipart"; + if ( !typeBlacklisted && msgPart().typeStr().lower() == "application" ) { + typeBlacklisted = msgPart().subtypeStr() == "pgp-encrypted" + || msgPart().subtypeStr().lower() == "pgp-signature" + || msgPart().subtypeStr().lower() == "pkcs7-mime" + || msgPart().subtypeStr().lower() == "pkcs7-signature"; + } + typeBlacklisted = typeBlacklisted || this == topLevelParent(); + bool firstTextChildOfEncapsulatedMsg = msgPart().typeStr().lower() == "text" && + msgPart().subtypeStr().lower() == "plain" && + parentNode() && + parentNode()->msgPart().typeStr().lower() == "message"; + typeBlacklisted = typeBlacklisted || firstTextChildOfEncapsulatedMsg; + info.displayInHeader = !info.label.isEmpty() && !info.icon.isEmpty() && !typeBlacklisted; + return info; +} diff --git a/kmail/partNode.h b/kmail/partNode.h index d53977035..ddc76aa62 100644 --- a/kmail/partNode.h +++ b/kmail/partNode.h @@ -45,9 +45,13 @@ #include #include +#include + class KMMimePartTreeItem; class KMMimePartTree; +class KMReaderWin; + /* =========================================================================== @@ -67,14 +71,22 @@ class partNode int calcNodeIdOrFindNode( int& curId, const partNode* calcNode, int findId, partNode** findNode ); -public: - static partNode * fromMessage( const KMMessage * msg ); - - partNode( DwBodyPart* dwPart, + partNode( KMReaderWin * win, DwBodyPart* dwPart, int explicitType = DwMime::kTypeUnknown, int explicitSubType = DwMime::kSubtypeUnknown, bool deleteDwBodyPart = false ); +public: + + struct AttachmentDisplayInfo + { + TQString label; + TQString icon; + bool displayInHeader; + }; + + static partNode * fromMessage( const KMMessage * msg, KMReaderWin * win=0 ); + partNode( bool deleteDwBodyPart, DwBodyPart* dwPart ); @@ -151,6 +163,11 @@ public: return mSignatureState; } + // path is a hierarchical path to this partNode. It is designed to + // be stable under decryption, where new child nodes are + // added. Treat it as an opaque string. + TQCString path() const; + int nodeId() const; // node ids start at 1 (this is the top level root node) partNode* findId( int id ); // returns the node which has the given id (or 0, resp.) @@ -202,7 +219,7 @@ public: mMimePartTreeItem = item; } - KMMimePartTreeItem* mimePartTreeItem() { + KMMimePartTreeItem* mimePartTreeItem() const { return mMimePartTreeItem; } @@ -217,23 +234,52 @@ public: */ bool isFirstTextPart() const; + bool isToltecMessage() const; + + /** + * @return true if this node is a child or an encapsulated message + */ + bool isInEncapsulatedMessage() const; + bool hasContentDispositionInline() const; TQString contentTypeParameter( const char * name ) const; const TQString& trueFromAddress() const; + const partNode * topLevelParent() const; partNode * parentNode() const { return mRoot; } partNode * nextSibling() const { return mNext; } partNode * firstChild() const { return mChild; } partNode * next( bool allowChildren=true ) const; int childCount() const; + int totalChildCount() const; bool processed() const { return mWasProcessed; } - KMail::Interface::BodyPartMemento * bodyPartMemento() const { return mBodyPartMemento; }; - void setBodyPartMemento( KMail::Interface::BodyPartMemento * memento ) { - mBodyPartMemento = memento; - }; + KMail::Interface::BodyPartMemento * bodyPartMemento( const TQCString & which ) const; + void setBodyPartMemento( const TQCString & which, KMail::Interface::BodyPartMemento * memento ); + + // A flag to remember if the node was embedded. This is useful for attachment nodes, the reader + // needs to know if they were displayed inline or not. + bool isDisplayedEmbedded() const; + void setDisplayedEmbedded( bool displayedEmbedded ); + + // Same as above, but this time determines if the node was hidden or not + bool isDisplayedHidden() const; + void setDisplayedHidden( bool displayedHidden ); + + // Get a href in the form attachment:?place=, used by ObjectTreeParser and + // UrlHandlerManager. + TQString asHREF( const TQString &place ) const; + + AttachmentDisplayInfo attachmentDisplayInfo() const; + +private: + KMReaderWin * reader() const { + return mReader ? mReader : mRoot ? mRoot->reader() : 0 ; + } + KMail::Interface::BodyPartMemento * internalBodyPartMemento( const TQCString & ) const; + void internalSetBodyPartMemento( const TQCString & which, KMail::Interface::BodyPartMemento * memento ); private: partNode* mRoot; @@ -253,7 +299,10 @@ private: bool mEncodedOk; bool mDeleteDwBodyPart; KMMimePartTreeItem* mMimePartTreeItem; - KMail::Interface::BodyPartMemento * mBodyPartMemento; + std::map mBodyPartMementoMap; + KMReaderWin * mReader; + bool mDisplayedEmbedded; + bool mDisplayedHidden; }; #endif diff --git a/kmail/partmetadata.h b/kmail/partmetadata.h index 1bc93d81e..ee6f51d34 100644 --- a/kmail/partmetadata.h +++ b/kmail/partmetadata.h @@ -18,6 +18,7 @@ #define _KMAIL_PARTMETADATA_H_ #include +#include #include #include @@ -34,6 +35,7 @@ namespace KMail { isGoodSignature( false ), isEncrypted( false ), isDecryptable( false ), + inProgress( false ), technicalProblem( false ), isEncapsulatedRfc822Message( false ) { @@ -50,10 +52,12 @@ namespace KMail { TQDateTime creationTime; TQString decryptionError; TQString auditLog; + GpgME::Error auditLogError; bool isSigned : 1; bool isGoodSignature : 1; bool isEncrypted : 1; bool isDecryptable : 1; + bool inProgress : 1; bool technicalProblem : 1; bool isEncapsulatedRfc822Message : 1; }; diff --git a/kmail/partnodebodypart.cpp b/kmail/partnodebodypart.cpp index 37e37353f..f9a56316b 100644 --- a/kmail/partnodebodypart.cpp +++ b/kmail/partnodebodypart.cpp @@ -82,11 +82,11 @@ bool KMail::PartNodeBodyPart::hasCompleteBody() const { } KMail::Interface::BodyPartMemento * KMail::PartNodeBodyPart::memento() const { - return mPartNode.bodyPartMemento(); + return mPartNode.bodyPartMemento( "__plugin__" ); } void KMail::PartNodeBodyPart::setBodyPartMemento( Interface::BodyPartMemento * memento ) { - mPartNode.setBodyPartMemento( memento ); + mPartNode.setBodyPartMemento( "__plugin__", memento ); } KMail::Interface::BodyPart::Display KMail::PartNodeBodyPart::defaultDisplay() const { diff --git a/kmail/pics/Makefile.am b/kmail/pics/Makefile.am index b1e1c368b..8c185ddb9 100644 --- a/kmail/pics/Makefile.am +++ b/kmail/pics/Makefile.am @@ -9,7 +9,7 @@ pics_DATA = kmmsgdel.png kmmsgnew.png kmmsgunseen.png kmmsgread.png \ kmmsgpartiallysigned.png kmmsgfullyencrypted.png \ kmmsgfullysigned.png kmmsgundefinedencrypted.png \ kmmsgundefinedsigned.png \ - kmmsgspam.png kmmsgham.png kmmsgattachment.png \ + kmmsgspam.png kmmsgham.png kmmsgattachment.png kmmsginvitation.png \ kmwizard.png \ quotecollapse.png quoteexpand.png \ enterprise_bottom_left.png \ diff --git a/kmail/pics/kmmsginvitation.png b/kmail/pics/kmmsginvitation.png new file mode 100644 index 000000000..82c9a67ba Binary files /dev/null and b/kmail/pics/kmmsginvitation.png differ diff --git a/kmail/profiles/profile-default-rc.desktop b/kmail/profiles/profile-default-rc.desktop index 7a75c8bb5..b8ba7e96f 100644 --- a/kmail/profiles/profile-default-rc.desktop +++ b/kmail/profiles/profile-default-rc.desktop @@ -31,7 +31,6 @@ Name[id]=Standar Name[is]=Sjálfgefið Name[it]=Predefinito Name[ja]=標準 -Name[ka]=ნაგულისხმევი Name[kk]=Әдетті Name[km]=លំនាំដើម Name[lt]=Numatytasis @@ -62,8 +61,7 @@ Name[tg]=Пешфарзӣ Name[th]=ค่าปริยาย Name[tr]=Öntanımlı Name[uk]=Типовий -Name[uz]=Andoza -Name[uz@cyrillic]=Андоза +Name[uz]=Андоза Name[ven]=Zwi si zwavhudi Name[vi]=Mặc định Name[xh]=Engagqibekanga @@ -99,7 +97,6 @@ Comment[hu]=Standard profil Comment[is]=Venjulegt snið Comment[it]=Profilo standard Comment[ja]=標準プロファイル -Comment[ka]=სტანდარტული პროფილი Comment[kk]=Стандартты профилі Comment[km]=ទម្រង់​ខ្នាត​គំរូ Comment[lt]=Standartinis profilis @@ -127,8 +124,7 @@ Comment[tg]=Профили оддӣ Comment[th]=โปรไฟล์มาตรฐาน Comment[tr]=Standart profil Comment[uk]=Типовий профіль -Comment[uz]=Andoza profili -Comment[uz@cyrillic]=Андоза профили +Comment[uz]=Андоза профили Comment[ven]=Zwithu zwo doweleaho Comment[vi]=Hồ sơ chuẩn Comment[xh]=Imboniselo yabucala esezantsi diff --git a/kmail/profiles/profile-high-contrast-rc.desktop b/kmail/profiles/profile-high-contrast-rc.desktop index 8c5b7cee0..a744ff1f4 100644 --- a/kmail/profiles/profile-high-contrast-rc.desktop +++ b/kmail/profiles/profile-high-contrast-rc.desktop @@ -27,7 +27,6 @@ Name[hu]=Kontrasztos Name[is]=Mikil birtuskil Name[it]=Alto contrasto Name[ja]=ハイコントラスト -Name[ka]=მაღალი კონტრასტი Name[kk]=Контрастығы жоғары Name[km]=កម្រិត​ពណ៌​ខ្ពស់ Name[lt]=Didelis kontrastas @@ -82,7 +81,6 @@ Comment[hu]=Nagyobb betűméretek látáscsökkent felhasználóknak Comment[is]=Stærra letur fyrir notendur með slæma sjón Comment[it]=Dimensioni più grandi dei caratteri per chi ha problemi di vista Comment[ja]=視力障害者のために大きなフォントを使用します -Comment[ka]=შრიფტის გაზრდილი ზომა მხედველობაშეზღუდული მომხმარებლებისთვის Comment[kk]=Көру қабілеті нашарларға арналған ірі қаріпті көрініс Comment[km]=បង្កើន​ទំហំពុម្ពអក្សរ ដើម្បី​បង្ក​លក្ខណៈ​ងាយស្រួល​ដល់​ជន​ពិការ​ភ្នែក Comment[lt]=Padidinti šriftų dydžiai blogai matantiems naudotojams diff --git a/kmail/profiles/profile-html-rc.desktop b/kmail/profiles/profile-html-rc.desktop index e609c50d7..f9655c91b 100644 --- a/kmail/profiles/profile-html-rc.desktop +++ b/kmail/profiles/profile-html-rc.desktop @@ -21,7 +21,7 @@ Comment[et]=Standardprofiil HTML-i eelvaatlusega - pole nii turvaline! Comment[eu]=HTML aurrebista gaituta duen profil estandarra - sekuritate gutxiago du Comment[fa]=profile استاندارد با پیش‌نمایش زنگام فعال‌شده - با ایمنی کمتر! Comment[fi]=Normaali profiili HTML-esikatselua käyttäville - vähemmän turvallinen. -Comment[fr]=Profil standard avec l'aperçu HTML activé - Moins sécurisé ! +Comment[fr]=Profil standard avec l'aperçu HTML activé - Moins sécurisé ! Comment[fy]=Standertprofyl mei HTML-foarbyld aktivearre - minder feilich! Comment[gl]=Perfil estándar con previsualización HTML activada - menos seguro! Comment[hi]=एचटीएमएल पूर्वावलोकन के साथ मानक प्रोफ़ाइल सक्षम है - कम सुरक्षित! @@ -30,7 +30,6 @@ Comment[hu]=Standard profil, HTML-előnézettel (kevésbé biztonságos) Comment[is]=Staðlað snið með HTML forsýn - minna öryggi! Comment[it]=Profilo standard con l'anteprima HTML abilitata - meno sicuro! Comment[ja]=HTML プレビューを有効にした標準プロファイル - 安全度は下がります! -Comment[ka]=სტანდარტული პროფილი HTML ესკიზით - ნაკლებად უსაფრთხო! Comment[kk]=Стандартты, HTML көрінісі бар профилі - қауіпсізігі төмен! Comment[km]=ទម្រង់​ខ្នាត​គំរូ​ដែល​អាច​មើល HTML ជាមុន - មិន​សូវ​មាន​សុវត្ថិភាព​ឡើយ ! Comment[lt]=Standartinis profilis su HTML peržiūra – mažiau saugus! diff --git a/kmail/profiles/profile-purist-rc.desktop b/kmail/profiles/profile-purist-rc.desktop index c77f2808c..fed413958 100644 --- a/kmail/profiles/profile-purist-rc.desktop +++ b/kmail/profiles/profile-purist-rc.desktop @@ -18,7 +18,6 @@ Name[hr]=Čistunski Name[hu]=Egyszerűsített Name[it]=Purista Name[ja]=純正 -Name[ka]=პურისტი Name[kk]=Пурист Name[km]=បរិសុទ្ធ Name[lt]=Itin paprastas @@ -72,7 +71,6 @@ Comment[hu]=A legtöbb extra kikapcsolva, a KDE alapértelmezéseinek felhaszná Comment[is]=Slökkt á flestum aukahlutum, notast við víðværar stillingar KDE Comment[it]=La maggior parte delle funzioni sono disabilitate, vengono usate le impostazioni globali di KDE Comment[ja]=ほとんどの機能を無効にし、KDE の全体設定を使用します -Comment[ka]=უმეტესობა შესაძლებლობებისა ამორთულია, გამოიყენება KDE-ს ზოგადი პარამეტრები Comment[kk]=Мүмкіндіктердің көбі, KDE жалпылары ғана қалдырып, өшірілген Comment[km]=លក្ខណៈ​ពិសេស​ភាគ​ច្រើន​ត្រូវ​បាន​បិទ ដោយ​ប្រើ​តែ​ការ​កំណត់​សកល​របស់ KDE ប៉ុណ្ណោះ Comment[lt]=Dauguma savybių išjungta, naudojami globalūs KDE nustatymai @@ -83,7 +81,7 @@ Comment[nb]=De fleste funksjoner slått av, KDEs globale innstillinger er i bruk Comment[nds]=Mehrste Funkschonen utmaakt, globaal KDE-Vörinstellen warrt bruukt Comment[ne]=धेरै विशेषता बन्द गरिएका छन, केडीई विश्वव्यापी सेटिङ प्रयोग गरिएका छन् Comment[nl]=Meeste mogelijkheden uitgezet, de globale KDE-instellingen worden gebruikt -Comment[nn]=Dei fleste funksjonar er slått av, globale KDE-innstillingar vert bruka +Comment[nn]=Dei fleste funksjonar er slått av, globale KDE-innstillingar vert brukt Comment[pl]=Większość opcji wyłączona, używane są domyślne ustawienia KDE Comment[pt]=A maioria das funcionalidades desligada, sendo usadas as opções globais do KDE Comment[pt_BR]=Maioria dos recursos desligados, são usadas configurações globais do KDE diff --git a/kmail/profiles/profile-secure-rc.desktop b/kmail/profiles/profile-secure-rc.desktop index fbc14b915..3cfc3cf2c 100644 --- a/kmail/profiles/profile-secure-rc.desktop +++ b/kmail/profiles/profile-secure-rc.desktop @@ -26,7 +26,6 @@ Name[hu]=Maximális biztonság Name[is]=Öruggast Name[it]=Massima sicurezza Name[ja]=最も安全 -Name[ka]=ყველაზე უსაფრთხო Name[kk]=Ең қауіпсіз Name[km]=សុវត្ថិភាព​បំផុត Name[lt]=Saugiausias @@ -52,8 +51,7 @@ Name[ta]=மிகவும் பாதுகாப்பான Name[tg]=Аз ҳама бехавфнокаш Name[tr]=En Güvenli Name[uk]=Найбільш безпечний -Name[uz]=Juda xavfsiz -Name[uz@cyrillic]=Жуда хавфсиз +Name[uz]=Жуда хавфсиз Name[zh_CN]=最安全 Name[zh_TW]=最安全 Comment=Sets all necessary options to achieve maximum security @@ -81,7 +79,6 @@ Comment[hu]=Az összes beállítás a legbiztonságosabb értékre állítva Comment[is]=Setur allar stillingar þannig að öryggið sé mest Comment[it]=Imposta tutte le opzioni necessario per ottenere la massima sicurezza Comment[ja]=最大のセキュリティ確保のために必要なすべてのオプションを設定します -Comment[ka]=აყენებს ყველა საჭირო ოპციას მაქსიმალური უსაფრთხოების მისაღწევად Comment[kk]=Қауіпсіздігі мейілінше арттырып бапталғаны Comment[km]=កំណត់​ជម្រើស​ចាំបាច់​ទាំងអស់ ដើម្បី​ទទួល​បាន​សុវត្ថិភាព​ខ្ពស់​បំផុត Comment[lt]=Nustato visas būtinas maksimaliam saugumui parinktis diff --git a/kmail/recipientseditor.cpp b/kmail/recipientseditor.cpp index 5ca2d7355..fe7b85ca9 100644 --- a/kmail/recipientseditor.cpp +++ b/kmail/recipientseditor.cpp @@ -159,6 +159,8 @@ RecipientLine::RecipientLine( TQWidget *parent ) TQToolTip::add( mCombo, i18n("Select type of recipient") ); mEdit = new RecipientLineEdit( this ); + TQToolTip::add( mEdit, + i18n( "Set the list of email addresses to receive this message" ) ); topLayout->addWidget( mEdit ); connect( mEdit, TQT_SIGNAL( returnPressed() ), TQT_SLOT( slotReturnPressed() ) ); connect( mEdit, TQT_SIGNAL( deleteMe() ), TQT_SLOT( slotPropagateDeletion() ) ); @@ -891,7 +893,8 @@ void RecipientsEditor::saveDistributionList() { DistributionListDialog *dlg = new DistributionListDialog( this ); dlg->setRecipients( mRecipientsView->recipients() ); - dlg->show(); + dlg->exec(); + delete dlg; } Recipient::List RecipientsEditor::recipients() const diff --git a/kmail/recipientspicker.cpp b/kmail/recipientspicker.cpp index 839898cbf..c1d2b3aed 100644 --- a/kmail/recipientspicker.cpp +++ b/kmail/recipientspicker.cpp @@ -772,7 +772,7 @@ void RecipientsPicker::pick( Recipient::Type type ) kdDebug() << "RecipientsPicker::pick " << int( type ) << endl; int count = 0; - TQListViewItemIterator it( mRecipientList , + TQListViewItemIterator it( mRecipientList , TQListViewItemIterator::Visible | TQListViewItemIterator::Selected ); for ( ; it.current(); ++it ) ++count; @@ -787,7 +787,7 @@ void RecipientsPicker::pick( Recipient::Type type ) return; } - it = TQListViewItemIterator( mRecipientList , + it = TQListViewItemIterator( mRecipientList , TQListViewItemIterator::Visible | TQListViewItemIterator::Selected ); for ( ; it.current(); ++it ) { RecipientViewItem *item = static_cast( it.current() ); @@ -855,7 +855,7 @@ void RecipientsPicker::slotSearchLDAP() void RecipientsPicker::ldapSearchResult() { - TQStringList emails = TQStringList::split(',', mLdapSearchDialog->selectedEMails() ); + TQStringList emails = KPIM::splitEmailAddrList( mLdapSearchDialog->selectedEMails() ); TQStringList::iterator it( emails.begin() ); TQStringList::iterator end( emails.end() ); for ( ; it != end; ++it ){ diff --git a/kmail/redirectdialog.cpp b/kmail/redirectdialog.cpp index 150e6a5d5..bf8fe51ce 100644 --- a/kmail/redirectdialog.cpp +++ b/kmail/redirectdialog.cpp @@ -76,14 +76,23 @@ RedirectDialog::RedirectDialog( TQWidget *parent, const char *name, connect( mBtnTo, TQT_SIGNAL(clicked()), TQT_SLOT(slotAddrBook()) ); + connect( mEditTo, TQT_SIGNAL( textChanged ( const TQString & ) ), TQT_SLOT( slotEmailChanged( const TQString & ) ) ); mLabelTo->setBuddy( mBtnTo ); mEditTo->setFocus(); setButtonGuiItem( User1, KGuiItem( i18n("&Send Now"), "mail_send" ) ); setButtonGuiItem( User2, KGuiItem( i18n("Send &Later"), "queue" ) ); + enableButton( User1, false ); + enableButton( User2, false ); } +void RedirectDialog::slotEmailChanged( const TQString & text ) +{ + enableButton( User1, !text.isEmpty() ); + enableButton( User2, !text.isEmpty() ); +} + //----------------------------------------------------------------------------- void RedirectDialog::slotUser1() { diff --git a/kmail/redirectdialog.h b/kmail/redirectdialog.h index a89714a04..9be9f272e 100644 --- a/kmail/redirectdialog.h +++ b/kmail/redirectdialog.h @@ -78,7 +78,7 @@ namespace KMail { void slotUser1(); void slotUser2(); - + void slotEmailChanged( const TQString & ); private: TQLabel *mLabelTo; KMLineEdit *mEditTo; diff --git a/kmail/searchwindow.cpp b/kmail/searchwindow.cpp index 81d64167f..b32dd96e0 100644 --- a/kmail/searchwindow.cpp +++ b/kmail/searchwindow.cpp @@ -181,7 +181,6 @@ SearchWindow::SearchWindow(KMMainWidget* w, const char* name, } else { mChkbxAllFolders->setChecked(true); } - mFolder = searchFolder; } mPatternEdit->setSearchPattern( mSearchPattern ); TQObjectList *list = mPatternEdit->queryList( 0, "mRuleField" ); @@ -237,13 +236,15 @@ SearchWindow::SearchWindow(KMMainWidget* w, const char* name, mLbxMatches->setDragEnabled( true ); - connect(mLbxMatches, TQT_SIGNAL(doubleClicked(TQListViewItem *)), - this, TQT_SLOT(slotShowMsg(TQListViewItem *))); - connect(mLbxMatches, TQT_SIGNAL(currentChanged(TQListViewItem *)), - this, TQT_SLOT(slotCurrentChanged(TQListViewItem *))); - connect( mLbxMatches, TQT_SIGNAL( contextMenuRequested( TQListViewItem*, const TQPoint &, int )), - this, TQT_SLOT( slotContextMenuRequested( TQListViewItem*, const TQPoint &, int ))); - vbl->addWidget(mLbxMatches); + connect( mLbxMatches, TQT_SIGNAL(clicked(TQListViewItem *)), + this, TQT_SLOT(slotShowMsg(TQListViewItem *)) ); + connect( mLbxMatches, TQT_SIGNAL(doubleClicked(TQListViewItem *)), + this, TQT_SLOT(slotViewMsg(TQListViewItem *)) ); + connect( mLbxMatches, TQT_SIGNAL(currentChanged(TQListViewItem *)), + this, TQT_SLOT(slotCurrentChanged(TQListViewItem *)) ); + connect( mLbxMatches, TQT_SIGNAL(contextMenuRequested(TQListViewItem *,const TQPoint &,int)), + this, TQT_SLOT(slotContextMenuRequested(TQListViewItem *,const TQPoint &,int)) ); + vbl->addWidget( mLbxMatches ); TQHBoxLayout *hbl2 = new TQHBoxLayout( vbl, spacingHint(), "kmfs_hbl2" ); mSearchFolderLbl = new TQLabel(i18n("Search folder &name:"), searchWidget); @@ -269,7 +270,7 @@ SearchWindow::SearchWindow(KMMainWidget* w, const char* name, mSearchResultOpenBtn->setEnabled(false); hbl2->addWidget(mSearchResultOpenBtn); connect( mSearchResultOpenBtn, TQT_SIGNAL( clicked() ), - this, TQT_SLOT( slotShowSelectedMsg() )); + this, TQT_SLOT( slotViewSelectedMsg() )); mStatusBar = new KStatusBar(searchWidget); mStatusBar->insertFixedItem(i18n("AMiddleLengthText..."), 0, true); mStatusBar->changeItem(i18n("Ready."), 0); @@ -472,6 +473,9 @@ void SearchWindow::slotSearch() mFetchingInProgress = 0; mSearchFolderOpenBtn->setEnabled(true); + if ( mSearchFolderEdt->text().isEmpty() ) { + mSearchFolderEdt->setText( i18n("Last Search") ); + } mBtnSearch->setEnabled(false); mBtnStop->setEnabled(true); @@ -486,8 +490,6 @@ void SearchWindow::slotSearch() // create one. if (!mFolder) { KMFolderMgr *mgr = kmkernel->searchFolderMgr(); - if (mSearchFolderEdt->text().isEmpty()) - mSearchFolderEdt->setText(i18n("Last Search")); TQString baseName = mSearchFolderEdt->text(); TQString fullName = baseName; int count = 0; @@ -630,12 +632,12 @@ void SearchWindow::closeEvent(TQCloseEvent *e) //----------------------------------------------------------------------------- void SearchWindow::scheduleRename( const TQString &s) { - if (!s.isEmpty() && s != i18n("Last Search")) { + if (!s.isEmpty() ) { mRenameTimer.start(250, true); mSearchFolderOpenBtn->setEnabled(false); } else { mRenameTimer.stop(); - mSearchFolderOpenBtn->setEnabled(true); + mSearchFolderOpenBtn->setEnabled(!s.isEmpty()); } } @@ -656,11 +658,13 @@ void SearchWindow::renameSearchFolder() ++i; } } - mSearchFolderOpenBtn->setEnabled(true); + if ( mFolder ) + mSearchFolderOpenBtn->setEnabled(true); } void SearchWindow::openSearchFolder() { + Q_ASSERT( mFolder ); renameSearchFolder(); mKMMainWidget->slotSelectFolder( mFolder->folder() ); slotClose(); @@ -680,32 +684,51 @@ void SearchWindow::folderInvalidated(KMFolder *folder) } //----------------------------------------------------------------------------- -bool SearchWindow::slotShowMsg(TQListViewItem *item) +KMMessage *SearchWindow::indexToMessage( TQListViewItem *item ) { - if(!item) - return false; + if( !item ) { + return 0; + } - KMFolder* folder; - int msgIndex; - KMMsgDict::instance()->getLocation(item->text(MSGID_COLUMN).toUInt(), - &folder, &msgIndex); + KMFolder *folder; + int msgIndex; + KMMsgDict::instance()->getLocation( item->text( MSGID_COLUMN ).toUInt(), + &folder, &msgIndex ); - if (!folder || msgIndex < 0) - return false; + if ( !folder || msgIndex < 0 ) { + return 0; + } - mKMMainWidget->slotSelectFolder(folder); - KMMessage* message = folder->getMsg(msgIndex); - if (!message) - return false; + mKMMainWidget->slotSelectFolder( folder ); + return folder->getMsg( msgIndex ); +} - mKMMainWidget->slotSelectMessage(message); +//----------------------------------------------------------------------------- +bool SearchWindow::slotShowMsg( TQListViewItem *item ) +{ + KMMessage *message = indexToMessage( item ); + if ( message ) { + mKMMainWidget->slotSelectMessage( message ); return true; + } + return false; } //----------------------------------------------------------------------------- -void SearchWindow::slotShowSelectedMsg() +void SearchWindow::slotViewSelectedMsg() { - slotShowMsg(mLbxMatches->currentItem()); + slotViewMsg( mLbxMatches->currentItem() ); +} + +//----------------------------------------------------------------------------- +bool SearchWindow::slotViewMsg( TQListViewItem *item ) +{ + KMMessage *message = indexToMessage( item ); + if ( message ) { + mKMMainWidget->slotMsgActivated( message ); + return true; + } + return false; } //----------------------------------------------------------------------------- @@ -720,8 +743,8 @@ void SearchWindow::enableGUI() KMSearch const *search = (mFolder) ? (mFolder->search()) : 0; bool searching = (search) ? (search->running()) : false; actionButton(KDialogBase::Close)->setEnabled(!searching); - mCbxFolders->setEnabled(!searching); - mChkSubFolders->setEnabled(!searching); + mCbxFolders->setEnabled(!searching && !mChkbxAllFolders->isChecked()); + mChkSubFolders->setEnabled(!searching && !mChkbxAllFolders->isChecked()); mChkbxAllFolders->setEnabled(!searching); mChkbxSpecificFolders->setEnabled(!searching); mPatternEdit->setEnabled(!searching); diff --git a/kmail/searchwindow.h b/kmail/searchwindow.h index c94b263e5..836553ae8 100644 --- a/kmail/searchwindow.h +++ b/kmail/searchwindow.h @@ -113,9 +113,10 @@ protected slots: void renameSearchFolder(); void openSearchFolder(); void folderInvalidated(KMFolder *); - virtual bool slotShowMsg(TQListViewItem *); - void slotShowSelectedMsg(); - void slotCurrentChanged(TQListViewItem *); + virtual bool slotShowMsg( TQListViewItem * ); + void slotViewSelectedMsg(); + virtual bool slotViewMsg( TQListViewItem * ); + void slotCurrentChanged( TQListViewItem * ); virtual void updateContextMenuActions(); virtual void slotContextMenuRequested( TQListViewItem*, const TQPoint &, int ); virtual void copySelectedToFolder( int menuId ); @@ -189,6 +190,9 @@ protected: KMSearchPattern *mSearchPattern; static const int MSGID_COLUMN; + +private: + KMMessage *indexToMessage( TQListViewItem *item ); }; } // namespace KMail diff --git a/kmail/sievedebugdialog.cpp b/kmail/sievedebugdialog.cpp index 5ab305e83..bc5d2cca0 100644 --- a/kmail/sievedebugdialog.cpp +++ b/kmail/sievedebugdialog.cpp @@ -219,6 +219,32 @@ SieveDebugDialog::~SieveDebugDialog() kdDebug( 5006 ) << k_funcinfo << endl; } +static KURL urlFromAccount( const KMail::ImapAccountBase * a ) { + const SieveConfig sieve = a->sieveConfig(); + if ( !sieve.managesieveSupported() ) + return KURL(); + + KURL u; + if ( sieve.reuseConfig() ) { + // assemble Sieve url from the settings of the account: + u.setProtocol( "sieve" ); + u.setHost( a->host() ); + u.setUser( a->login() ); + u.setPass( a->passwd() ); + u.setPort( sieve.port() ); + + // Translate IMAP LOGIN to PLAIN: + u.addQueryItem( "x-mech", a->auth() == "*" ? "PLAIN" : a->auth() ); + if ( !a->useSSL() && !a->useTLS() ) + u.addQueryItem( "x-allow-unencrypted", "true" ); + } else { + u = sieve.alternateURL(); + if ( u.protocol().lower() == "sieve" && !a->useSSL() && !a->useTLS() && u.queryItem("x-allow-unencrypted").isEmpty() ) + u.addQueryItem( "x-allow-unencrypted", "true" ); + } + return u; +} + void SieveDebugDialog::slotDiagNextAccount() { if ( mAccountList.isEmpty() ) @@ -233,26 +259,12 @@ void SieveDebugDialog::slotDiagNextAccount() if ( mAccountBase ) { // Detect URL for this IMAP account - SieveConfig sieve = mAccountBase->sieveConfig(); - if ( !sieve.managesieveSupported() ) + const KURL url = urlFromAccount( mAccountBase ); + if ( !url.isValid() ) { mEdit->append( i18n( "(Account does not support Sieve)\n\n" ) ); } else { - if ( sieve.reuseConfig() ) - { - // assemble Sieve url from the settings of the account: - mUrl.setProtocol( "sieve" ); - mUrl.setHost( mAccountBase->host() ); - mUrl.setUser( mAccountBase->login() ); - mUrl.setPass( mAccountBase->passwd() ); - mUrl.setPort( sieve.port() ); - - // Translate IMAP LOGIN to PLAIN: - mUrl.setQuery( "x-mech=" + ( mAccountBase->auth() == "*" ? "PLAIN" : mAccountBase->auth() ) ); - } else { - sieve.alternateURL(); - mUrl.setFileName( sieve.vacationFileName() ); - } + mUrl = url; mSieveJob = SieveJob::list( mUrl ); @@ -284,22 +296,9 @@ void SieveDebugDialog::slotDiagNextScript() mScriptList.pop_front(); mEdit->append( i18n( "Contents of script '%1':\n" ).arg( scriptFile ) ); - SieveConfig sieve = mAccountBase->sieveConfig(); - if ( sieve.reuseConfig() ) - { - // assemble Sieve url from the settings of the account: - mUrl.setProtocol( "sieve" ); - mUrl.setHost( mAccountBase->host() ); - mUrl.setUser( mAccountBase->login() ); - mUrl.setPass( mAccountBase->passwd() ); - mUrl.setPort( sieve.port() ); - // Translate IMAP LOGIN to PLAIN - mUrl.setQuery( "x-mech=" + ( mAccountBase->auth() == "*" ? "PLAIN" : mAccountBase->auth() ) ); - mUrl.setFileName( scriptFile ); - } else { - sieve.alternateURL(); - mUrl.setFileName( scriptFile ); - } + + mUrl = urlFromAccount( mAccountBase ); + mUrl.setFileName( scriptFile ); mSieveJob = SieveJob::get( mUrl ); diff --git a/kmail/sievejob.cpp b/kmail/sievejob.cpp index ade08b1da..0ce073d93 100644 --- a/kmail/sievejob.cpp +++ b/kmail/sievejob.cpp @@ -275,6 +275,12 @@ namespace KMail { return new SieveJob( url, TQString::null, commands ); } + SieveJob * SieveJob::desactivate( const KURL & url ) { + TQValueStack commands; + commands.push( Deactivate ); + return new SieveJob( url, TQString(), commands ); + } + SieveJob * SieveJob::activate( const KURL & url ) { TQValueStack commands; commands.push( Activate ); diff --git a/kmail/sievejob.h b/kmail/sievejob.h index ef9f4a504..ed8fb5d8c 100644 --- a/kmail/sievejob.h +++ b/kmail/sievejob.h @@ -68,6 +68,8 @@ namespace KMail { static SieveJob * activate( const KURL & url ); + static SieveJob * desactivate( const KURL & url ); + void kill( bool quiet=true ); const TQStringList & sieveCapabilities() const { diff --git a/kmail/simplefoldertree.h b/kmail/simplefoldertree.h new file mode 100644 index 000000000..6bfc3eb33 --- /dev/null +++ b/kmail/simplefoldertree.h @@ -0,0 +1,249 @@ +/* + Copyright (c) 2007 Volker Krause + Copyright (c) 2003 Andreas Gungl + Copyright (c) Stefan Taferner + + 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. +*/ + +#ifndef KMAIL_SIMPLEFOLDERTREE_H +#define KMAIL_SIMPLEFOLDERTREE_H + +#include "kmfolder.h" +#include "kmfoldertree.h" +#include "treebase.h" + +#include +#include +#include +#include + +class KMFolder; +class KMFolderTree; + +namespace KMail { + +static int recurseFilter( TQListViewItem * item, const TQString& filter, int column ) +{ + if ( item == 0 ) + return 0; + + TQListViewItem * child; + child = item->firstChild(); + + int enabled = 0; + while ( child ) { + enabled += recurseFilter( child, filter, column ); + child = child->nextSibling(); + } + + if ( filter.length() == 0 || + item->text( column ).find( filter, 0, false ) >= 0 ) { + item->setVisible( true ); + ++enabled; + } + else { + item->setVisible( !!enabled ); + item->setEnabled( false ); + } + + return enabled; +} + +class TreeItemBase +{ + public : + TreeItemBase() + : mFolder( 0 ) + { + kdDebug(5006) << k_funcinfo << endl; + } + virtual ~TreeItemBase() { } + + void setFolder( KMFolder * folder ) { mFolder = folder; }; + const KMFolder * folder() { return mFolder; }; + + // Set the flag which determines if this is an alternate row + void setAlternate ( bool alternate ) { + mAlternate = alternate; + } + + private: + KMFolder * mFolder; + bool mAlternate; + +}; + +template class SimpleFolderTreeItem : public T, public TreeItemBase +{ + public: + SimpleFolderTreeItem( TQListView * listView ) : + T( listView ), TreeItemBase() + { + kdDebug(5006) << k_funcinfo << endl; + } + SimpleFolderTreeItem( TQListView * listView, TQListViewItem * afterListViewItem ) : + T( listView, afterListViewItem ) , TreeItemBase() + { + kdDebug(5006) << k_funcinfo << endl; + } + SimpleFolderTreeItem( TQListViewItem * listViewItem ) : T( listViewItem ) , TreeItemBase() + { + kdDebug(5006) << k_funcinfo << endl; + } + + SimpleFolderTreeItem( TQListViewItem * listViewItem, TQListViewItem * afterListViewItem ) : + T( listViewItem, afterListViewItem ) , TreeItemBase() + { + kdDebug(5006) << k_funcinfo << endl; + } + +}; + +template <> class SimpleFolderTreeItem : public TQCheckListItem, public TreeItemBase +{ + public: + SimpleFolderTreeItem( TQListView * listView ) : + TQCheckListItem( listView, TQString(), CheckBox ), TreeItemBase() {} + SimpleFolderTreeItem( TQListView * listView, TQListViewItem * afterListViewItem ) : + TQCheckListItem( listView, afterListViewItem, TQString(), CheckBox ), TreeItemBase() {} + SimpleFolderTreeItem( TQListViewItem * listViewItem ) : + TQCheckListItem( listViewItem, TQString(), CheckBox ) {} + SimpleFolderTreeItem( TQListViewItem * listViewItem, TQListViewItem * afterListViewItem ) : + TQCheckListItem( listViewItem, afterListViewItem, TQString(), CheckBox ) {} + +}; + + +template class SimpleFolderTreeBase : public TreeBase +{ + + public: + + + inline SimpleFolderTreeBase( TQWidget * parent, KMFolderTree *folderTree, + const TQString &preSelection, bool mustBeReadWrite ) + : TreeBase( parent, folderTree, preSelection, mustBeReadWrite ) + { + assert( folderTree ); + setFolderColumn( addColumn( i18n( "Folder" ) ) ); + setPathColumn( addColumn( i18n( "Path" ) ) ); + + setRootIsDecorated( true ); + setSorting( -1 ); + + reload( mustBeReadWrite, true, true, preSelection ); + + } + + virtual SimpleFolderTreeItem* createItem( TQListView * parent ) + { + return new SimpleFolderTreeItem( parent ); + } + + virtual SimpleFolderTreeItem* createItem( TQListView * parent, TQListViewItem* afterListViewItem ) + { + return new SimpleFolderTreeItem( parent, afterListViewItem ); + } + + virtual SimpleFolderTreeItem* createItem( TQListViewItem * parent, TQListViewItem* afterListViewItem ) + { + return new SimpleFolderTreeItem( parent, afterListViewItem ); + } + + virtual SimpleFolderTreeItem* createItem( TQListViewItem * parent ) + { + return new SimpleFolderTreeItem( parent ); + } + + inline void keyPressEvent( TQKeyEvent *e ) + { + const char ascii = e->ascii(); + if ( ascii == 8 || ascii == 127 ) { + if ( mFilter.length() > 0 ) { + mFilter.truncate( mFilter.length()-1 ); + applyFilter( mFilter ); + } + } else if ( !e->text().isEmpty() && e->text().length() == 1 && e->text().at( 0 ).isPrint() ) { + applyFilter( mFilter + e->text() ); + } else { + KListView::keyPressEvent( e ); + } + } + + void applyFilter( const TQString& filter ) + { + kdDebug(5006) << k_funcinfo << filter << endl ; + // Reset all items to visible, enabled, and open + TQListViewItemIterator clean( this ); + while ( clean.current() ) { + TQListViewItem * item = clean.current(); + item->setEnabled( true ); + item->setVisible( true ); + item->setOpen( true ); + ++clean; + } + + mFilter = filter; + + if ( filter.isEmpty() ) { + setColumnText( pathColumn(), i18n("Path") ); + return; + } + + // Set the visibility and enabled status of each list item. + // The recursive algorithm is necessary because visiblity + // changes are automatically applied to child nodes by TQt. + TQListViewItemIterator it( this ); + while ( it.current() ) { + TQListViewItem * item = it.current(); + if ( item->depth() <= 0 ) + recurseFilter( item, filter, pathColumn() ); + ++it; + } + + // Recolor the rows appropriately + recolorRows(); + + // Iterate through the list to find the first selectable item + TQListViewItemIterator first ( this ); + while ( first.current() ) { + SimpleFolderTreeItem * item = static_cast< SimpleFolderTreeItem * >( first.current() ); + + if ( item->isVisible() && item->isSelectable() ) { + setSelected( item, true ); + ensureItemVisible( item ); + break; + } + + ++first; + } + + // Display and save the current filter + if ( filter.length() > 0 ) + setColumnText( pathColumn(), i18n("Path") + " ( " + filter + " )" ); + else + setColumnText( pathColumn(), i18n("Path") ); + + mFilter = filter; + } + +}; + +typedef SimpleFolderTreeBase SimpleFolderTree; + +} + +#endif diff --git a/kmail/simplestringlisteditor.cpp b/kmail/simplestringlisteditor.cpp index 8d71e2215..4be67e6e4 100644 --- a/kmail/simplestringlisteditor.cpp +++ b/kmail/simplestringlisteditor.cpp @@ -166,6 +166,15 @@ TQStringList SimpleStringListEditor::stringList() const { return result; } +bool SimpleStringListEditor::containsString( const TQString & str ) { + for ( TQListBoxItem * item = mListBox->firstItem() ; + item ; item = item->next() ) { + if ( item->text() == str ) + return true; + } + return false; +} + void SimpleStringListEditor::setButtonText( ButtonCode button, const TQString & text ) { switch ( button ) { @@ -207,9 +216,10 @@ void SimpleStringListEditor::slotAdd() { &ok, this ); // let the user verify the string before adding emit aboutToAdd( newEntry ); - if ( ok && !newEntry.isEmpty() ) - mListBox->insertItem( newEntry ); - emit changed(); + if ( ok && !newEntry.isEmpty() && !containsString( newEntry )) { + mListBox->insertItem( newEntry ); + emit changed(); + } } void SimpleStringListEditor::slotRemove() { diff --git a/kmail/simplestringlisteditor.h b/kmail/simplestringlisteditor.h index 47c6f6c9a..115a9a05e 100644 --- a/kmail/simplestringlisteditor.h +++ b/kmail/simplestringlisteditor.h @@ -91,6 +91,7 @@ protected slots: void slotSelectionChanged(); protected: + bool containsString( const TQString & str ); TQListBox *mListBox; TQPushButton *mAddButton; TQPushButton *mRemoveButton; diff --git a/kmail/snippetdlg.cpp b/kmail/snippetdlg.cpp index 61c75db88..93478d13f 100644 --- a/kmail/snippetdlg.cpp +++ b/kmail/snippetdlg.cpp @@ -1,6 +1,6 @@ /*************************************************************************** * snippet feature from kdevelop/plugins/snippet/ * - * * + * * * Copyright (C) 2007 by Robert Gruber * * rgruber@users.sourceforge.net * * * @@ -14,6 +14,7 @@ #include "snippetdlg.h" #include +#include #include #include @@ -42,6 +43,12 @@ SnippetDlg::SnippetDlg( KActionCollection* ac, TQWidget* parent, const char* nam connect( keyButton, TQT_SIGNAL( capturedShortcut( const KShortcut& ) ), this, TQT_SLOT( slotCapturedShortcut( const KShortcut& ) ) ); + btnAdd->setEnabled( false ); + connect( snippetName, TQT_SIGNAL(textChanged(const TQString &)), + this, TQT_SLOT(slotTextChanged(const TQString &)) ); + connect( snippetName, TQT_SIGNAL(returnPressed()), + this, TQT_SLOT(slotReturnPressed()) ); + layout3->addWidget( textLabel3, 7, 0 ); layout3->addWidget( keyButton, 7, 1 ); @@ -68,7 +75,7 @@ SnippetDlg::~SnippetDlg() */ void SnippetDlg::languageChange() { - textLabel3->setText( tr2i18n( "Sh&ortcut:" ) ); + textLabel3->setText( i18n( "Sh&ortcut:" ) ); } static bool shortcutIsValid( const KActionCollection* actionCollection, const KShortcut &sc ) @@ -105,4 +112,16 @@ void SnippetDlg::setShowShortcut( bool show ) keyButton->setShown( show ); } +void SnippetDlg::slotTextChanged( const TQString &text ) +{ + btnAdd->setEnabled( !text.isEmpty() ); +} + +void SnippetDlg::slotReturnPressed() +{ + if ( !snippetName->text().isEmpty() ) { + accept(); + } +} + #include "snippetdlg.moc" diff --git a/kmail/snippetdlg.h b/kmail/snippetdlg.h index fd990539f..efd7bc534 100644 --- a/kmail/snippetdlg.h +++ b/kmail/snippetdlg.h @@ -20,7 +20,7 @@ class SnippetDlg : public SnippetDlgBase { Q_OBJECT -public: + public: SnippetDlg( KActionCollection* ac, TQWidget* parent = 0, const char* name = 0, bool modal = FALSE, WFlags fl = 0 ); ~SnippetDlg(); @@ -31,12 +31,13 @@ public: KKeyButton* keyButton; KActionCollection* actionCollection; -private slots: - void slotCapturedShortcut( const KShortcut& ); - -protected slots: + protected slots: + void slotTextChanged( const TQString& ); + void slotReturnPressed(); virtual void languageChange(); + private slots: + void slotCapturedShortcut( const KShortcut& ); }; #endif // SNIPPETDLG_H diff --git a/kmail/snippetdlgbase.ui b/kmail/snippetdlgbase.ui index 5d87b564b..a5a28a72a 100644 --- a/kmail/snippetdlgbase.ui +++ b/kmail/snippetdlgbase.ui @@ -124,6 +124,9 @@ Group: + + cbGroup + diff --git a/kmail/snippetitem.cpp b/kmail/snippetitem.cpp index 206114ace..f9f3aa871 100644 --- a/kmail/snippetitem.cpp +++ b/kmail/snippetitem.cpp @@ -1,6 +1,6 @@ /*************************************************************************** * snippet feature from kdevelop/plugins/snippet/ * - * * + * * * Copyright (C) 2007 by Robert Gruber * * rgruber@users.sourceforge.net * * * @@ -23,6 +23,7 @@ SnippetItem::SnippetItem(TQListView * parent, TQString name, TQString text ) strName = name; strText = text; iParent = -1; + setOpen( true ); } SnippetItem::SnippetItem(TQListViewItem * parent, TQString name, TQString text) @@ -31,6 +32,7 @@ SnippetItem::SnippetItem(TQListViewItem * parent, TQString name, TQString text) strName = name; strText = text; iParent = ((SnippetGroup *)parent)->getId(); + setOpen( true ); } SnippetItem::~SnippetItem() @@ -86,7 +88,7 @@ void SnippetItem::resetParent() KAction* SnippetItem::getAction() -{ +{ return action; } @@ -128,7 +130,7 @@ Deklaration for class SnippetGroup int SnippetGroup::iMaxId = 1; SnippetGroup::SnippetGroup(TQListView * parent, TQString name, int id) - : SnippetItem(parent, name, "GROUP") + : SnippetItem(parent, name, i18n("GROUP")) { if (id > 0) { iId = id; @@ -146,7 +148,7 @@ SnippetGroup::~SnippetGroup() void SnippetGroup::setId(int id) { - iId = id; + iId = id; if (iId >= iMaxId) iMaxId = iId+1; } diff --git a/kmail/snippetwidget.cpp b/kmail/snippetwidget.cpp index 617021748..800a98e55 100644 --- a/kmail/snippetwidget.cpp +++ b/kmail/snippetwidget.cpp @@ -1,6 +1,6 @@ /*************************************************************************** * snippet feature from kdevelop/plugins/snippet/ * - * * + * * * Copyright (C) 2007 by Robert Gruber * * rgruber@users.sourceforge.net * * * @@ -168,7 +168,7 @@ void SnippetWidget::slotAddGroup() SnippetDlg dlg( mActionCollection, this, "SnippetDlg"); dlg.setShowShortcut( false ); dlg.snippetText->setEnabled(false); - dlg.snippetText->setText("GROUP"); + dlg.snippetText->setText(i18n("GROUP")); dlg.setCaption(i18n("Add Group")); dlg.cbGroup->insertItem(i18n("All")); dlg.cbGroup->setCurrentText(i18n("All")); diff --git a/kmail/stl_util.h b/kmail/stl_util.h index 69963df7b..5e3e5ce6f 100644 --- a/kmail/stl_util.h +++ b/kmail/stl_util.h @@ -32,6 +32,8 @@ #ifndef __KDEPIM__KMAIL__STL_UTIL_H__ #define __KDEPIM__KMAIL__STL_UTIL_H__ +#include + template struct DeleteAndSetToZero { void operator()( const T * & t ) { delete t; t = 0; } @@ -44,4 +46,19 @@ static inline void deleteAll( T & c ) { } } +namespace kdtools { + + template + bool any( Iterator first, Iterator last, UnaryPredicate p ) + { + while ( first != last ) + if ( p( *first ) ) + return true; + else + ++first; + return false; + } + +} // namespace kdtools + #endif // __KDEPIM__KMAIL__STL_UTIL_H__ diff --git a/kmail/stringutil.cpp b/kmail/stringutil.cpp new file mode 100644 index 000000000..907c5e1fb --- /dev/null +++ b/kmail/stringutil.cpp @@ -0,0 +1,49 @@ +/* Copyright 2009 Thomas McGuire + + 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) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 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, see . +*/ +#include "stringutil.h" +#include "kmmsgbase.h" +#include + +namespace KMail +{ + +namespace StringUtil +{ +#ifndef KMAIL_UNITTESTS +TQString encodeMailtoUrl( const TQString& str ) +{ + TQString result; + result = TQString::fromLatin1( KMMsgBase::encodeRFC2047String( str, + "utf-8" ) ); + result = KURL::encode_string( result ); + return result; +} + +TQString decodeMailtoUrl( const TQString& url ) +{ + TQString result; + result = KURL::decode_string( url.latin1() ); + result = KMMsgBase::decodeRFC2047String( result.latin1() ); + return result; +} +#endif + +} + +} diff --git a/kmail/stringutil.h b/kmail/stringutil.h new file mode 100644 index 000000000..2528bdced --- /dev/null +++ b/kmail/stringutil.h @@ -0,0 +1,43 @@ +/* Copyright 2009 Thomas McGuire + + 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) version 3 or any later version + accepted by the membership of KDE e.V. (or its successor approved + by the membership of KDE e.V.), which shall act as a proxy + defined in Section 14 of version 3 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, see . +*/ +#ifndef KMAIL_STRINGUTIL_H +#define KMAIL_STRINGUTIL_H + +#include + +namespace KMail +{ +/** + * This namespace contain helper functions for string manipulation + */ +namespace StringUtil +{ + /** Encodes an email address as mailto URL + */ + TQString encodeMailtoUrl( const TQString& str ); + + /** Decodes a mailto URL + */ + TQString decodeMailtoUrl( const TQString& url ); +} + +} +#endif + + diff --git a/kmail/subscriptiondialog.cpp b/kmail/subscriptiondialog.cpp index 725317437..58d00b24a 100644 --- a/kmail/subscriptiondialog.cpp +++ b/kmail/subscriptiondialog.cpp @@ -373,7 +373,7 @@ void SubscriptionDialog::doSave() KMail::ImapAccountBase *a = static_cast(mAcct); if( !a->onlySubscribedFolders() ) { int result = KMessageBox::questionYesNoCancel( this, - i18n("Currently subscriptions are not used for server %1\ndo you want to enable subscriptions?") + i18n("Currently subscriptions are not used for server. %1\nDo you want to enable subscriptions?") .arg( a->name() ), i18n("Enable Subscriptions?"), i18n("Enable"), i18n("Do Not Enable")); switch(result) { diff --git a/kmail/templateparser.cpp b/kmail/templateparser.cpp index 4112b58ed..79df85094 100644 --- a/kmail/templateparser.cpp +++ b/kmail/templateparser.cpp @@ -43,21 +43,46 @@ #include "kmkernel.h" #include #include +#include "partNode.h" +#include "attachmentcollector.h" +#include "objecttreeparser.h" +#include "util.h" #include "templateparser.h" +#include -TemplateParser::TemplateParser( KMMessage *amsg, const Mode amode, - const TQString aselection, - bool asmartQuote, bool anoQuote, - bool aallowDecryption, bool aselectionIsBody ) : - mMode( amode ), mFolder( 0 ), mIdentity( 0 ), mSelection( aselection ), - mSmartQuote( asmartQuote ), mNoQuote( anoQuote ), - mAllowDecryption( aallowDecryption ), mSelectionIsBody( aselectionIsBody ), - mDebug( false ), mQuoteString( "> " ), mAppend( false ) +using namespace KMail; + +TemplateParser::TemplateParser( KMMessage *amsg, const Mode amode ) : + mMode( amode ), mFolder( 0 ), mIdentity( 0 ), + mAllowDecryption( false ), + mDebug( false ), mQuoteString( "> " ), mAppend( false ), mOrigRoot( 0 ) { mMsg = amsg; } +void TemplateParser::setSelection( const TQString &selection ) +{ + mSelection = selection; +} + +void TemplateParser::setAllowDecryption( const bool allowDecryption ) +{ + mAllowDecryption = allowDecryption; +} + +bool TemplateParser::shouldStripSignature() const +{ + // Only strip the signature when replying, it should be preserved when forwarding + return ( mMode == Reply || mMode == ReplyAll) && GlobalSettings::stripSignature(); +} + +TemplateParser::~TemplateParser() +{ + delete mOrigRoot; + mOrigRoot = 0; +} + int TemplateParser::parseQuotes( const TQString &prefix, const TQString &str, TQString "e ) const { @@ -279,36 +304,36 @@ void TemplateParser::processWithTemplate( const TQString &tmpl ) int len = parseQuotes( "QUOTEPIPE=", cmd, q ); i += len; TQString pipe_cmd = q; - if ( mOrigMsg && !mNoQuote ) { - TQString str = pipe( pipe_cmd, mSelection ); + if ( mOrigMsg ) { + TQString str = pipe( pipe_cmd, messageText( false ) ); TQString quote = mOrigMsg->asQuotedString( "", mQuoteString, str, - mSmartQuote, mAllowDecryption ); + shouldStripSignature(), mAllowDecryption ); body.append( quote ); } } else if ( cmd.startsWith( "QUOTE" ) ) { kdDebug() << "Command: QUOTE" << endl; i += strlen( "QUOTE" ); - if ( mOrigMsg && !mNoQuote ) { - TQString quote = mOrigMsg->asQuotedString( "", mQuoteString, mSelection, - mSmartQuote, mAllowDecryption ); + if ( mOrigMsg ) { + TQString quote = mOrigMsg->asQuotedString( "", mQuoteString, messageText( true ), + shouldStripSignature(), mAllowDecryption ); body.append( quote ); } } else if ( cmd.startsWith( "QHEADERS" ) ) { kdDebug() << "Command: QHEADERS" << endl; i += strlen( "QHEADERS" ); - if ( mOrigMsg && !mNoQuote ) { + if ( mOrigMsg ) { TQString quote = mOrigMsg->asQuotedString( "", mQuoteString, mOrigMsg->headerAsSendableString(), - mSmartQuote, false ); + false, false ); body.append( quote ); } } else if ( cmd.startsWith( "HEADERS" ) ) { kdDebug() << "Command: HEADERS" << endl; i += strlen( "HEADERS" ); - if ( mOrigMsg && !mNoQuote ) { + if ( mOrigMsg ) { TQString str = mOrigMsg->headerAsSendableString(); body.append( str ); } @@ -321,7 +346,7 @@ void TemplateParser::processWithTemplate( const TQString &tmpl ) i += len; TQString pipe_cmd = q; if ( mOrigMsg ) { - TQString str = pipe(pipe_cmd, mSelection ); + TQString str = pipe(pipe_cmd, messageText( false ) ); body.append( str ); } @@ -363,7 +388,7 @@ void TemplateParser::processWithTemplate( const TQString &tmpl ) kdDebug() << "Command: TEXT" << endl; i += strlen( "TEXT" ); if ( mOrigMsg ) { - TQString quote = mOrigMsg->asPlainText( false, mAllowDecryption ); + TQString quote = messageText( false ); body.append( quote ); } @@ -379,10 +404,22 @@ void TemplateParser::processWithTemplate( const TQString &tmpl ) kdDebug() << "Command: OTEXT" << endl; i += strlen( "OTEXT" ); if ( mOrigMsg ) { - TQString quote = mOrigMsg->asPlainText( false, mAllowDecryption ); + TQString quote = messageText( false ); body.append( quote ); } + } else if ( cmd.startsWith( "OADDRESSEESADDR" ) ) { + kdDebug() << "Command: OADDRESSEESADDR" << endl; + i += strlen( "OADDRESSEESADDR" ); + const TQString to = mOrigMsg->to(); + const TQString cc = mOrigMsg->cc(); + if ( !to.isEmpty() ) + body.append( i18n( "To:" ) + ' ' + to ); + if ( !to.isEmpty() && !cc.isEmpty() ) + body.append( '\n' ); + if ( !cc.isEmpty() ) + body.append( i18n( "CC:" ) + ' ' + cc ); + } else if ( cmd.startsWith( "CCADDR" ) ) { kdDebug() << "Command: CCADDR" << endl; i += strlen( "CCADDR" ); @@ -829,20 +866,124 @@ void TemplateParser::processWithTemplate( const TQString &tmpl ) } } - // kdDebug() << "Message body: " << body << endl; + addProcessedBodyToMessage( body ); +} + +TQString TemplateParser::messageText( bool allowSelectionOnly ) +{ + if ( !mSelection.isEmpty() && allowSelectionOnly ) + return mSelection; + + // No selection text, therefore we need to parse the object tree ourselves to get + partNode *root = parsedObjectTree(); + return mOrigMsg->asPlainTextFromObjectTree( root, shouldStripSignature(), mAllowDecryption ); +} +partNode* TemplateParser::parsedObjectTree() +{ + if ( mOrigRoot ) + return mOrigRoot; + + mOrigRoot = partNode::fromMessage( mOrigMsg ); + ObjectTreeParser otp; // all defaults are ok + otp.parseObjectTree( mOrigRoot ); + return mOrigRoot; +} + +void TemplateParser::addProcessedBodyToMessage( const TQString &body ) +{ if ( mAppend ) { + + // ### What happens here if the body is multipart or in some way encoded? TQCString msg_body = mMsg->body(); msg_body.append( body.utf8() ); mMsg->setBody( msg_body ); - } else { - mMsg->setBodyFromUnicode( body ); + } + else { + + // Get the attachments of the original mail + partNode *root = parsedObjectTree(); + AttachmentCollector ac; + ac.collectAttachmentsFrom( root ); + + // Now, delete the old content and set the new content, which + // is either only the new text or the new text with some attachments. + mMsg->deleteBodyParts(); + + // Set To and CC from the template + if ( mMode == Forward ) { + if ( !mTo.isEmpty() ) { + mMsg->setTo( mMsg->to() + ',' + mTo ); + } + if ( !mCC.isEmpty() ) + mMsg->setCc( mMsg->cc() + ',' + mCC ); + } + + // If we have no attachment, simply create a text/plain part and + // set the processed template text as the body + if ( ac.attachments().empty() || mMode != Forward ) { + mMsg->headers().ContentType().FromString( DwString() ); // to get rid of old boundary + mMsg->headers().ContentType().Parse(); + mMsg->headers().ContentType().SetType( DwMime::kTypeText ); + mMsg->headers().ContentType().SetSubtype( DwMime::kSubtypePlain ); + mMsg->headers().Assemble(); + mMsg->setBodyFromUnicode( body ); + mMsg->assembleIfNeeded(); + } + + // If we have some attachments, create a multipart/mixed mail and + // add the normal body as well as the attachments + else + { + mMsg->headers().ContentType().SetType( DwMime::kTypeMultipart ); + mMsg->headers().ContentType().SetSubtype( DwMime::kSubtypeMixed ); + mMsg->headers().ContentType().CreateBoundary( 0 ); + + KMMessagePart textPart; + textPart.setBodyFromUnicode( body ); + mMsg->addDwBodyPart( mMsg->createDWBodyPart( &textPart ) ); + mMsg->assembleIfNeeded(); + + int attachmentNumber = 1; + for ( std::vector::const_iterator it = ac.attachments().begin(); + it != ac.attachments().end(); ++it, attachmentNumber++ ) { + + // When adding this body part, make sure to _not_ add the next bodypart + // as well, which mimelib would do, therefore creating a mail with many + // duplicate attachments (so many that KMail runs out of memory, in fact). + // Body::AddBodyPart is very misleading here... + ( *it )->dwPart()->SetNext( 0 ); + + DwBodyPart *cloned = static_cast( ( *it )->dwPart()->Clone() ); + + // If the content type has no name or filename parameter, add one, since otherwise the name + // would be empty in the attachment view of the composer, which looks confusing + if ( cloned->Headers().HasContentType() ) { + DwMediaType &ct = cloned->Headers().ContentType(); + + // Converting to a string here, since DwMediaType does not have a HasParameter() function + TQString ctStr = ct.AsString().c_str(); + if ( !ctStr.lower().contains( "name=" ) && !ctStr.lower().contains( "filename=" ) ) { + DwParameter *nameParameter = new DwParameter; + nameParameter->SetAttribute( "name" ); + nameParameter->SetValue( Util::dwString( KMMsgBase::encodeRFC2231StringAutoDetectCharset( + i18n( "Attachment %1" ).arg( attachmentNumber ) ) ) ); + ct.AddParameter( nameParameter ); + } + } + + mMsg->addDwBodyPart( cloned ); + mMsg->assembleIfNeeded(); + } + } } } TQString TemplateParser::findCustomTemplate( const TQString &tmplName ) { CTemplates t( tmplName ); + mTo = t.to(); + mCC = t.cC(); TQString content = t.content(); if ( !content.isEmpty() ) { return content; diff --git a/kmail/templateparser.h b/kmail/templateparser.h index 7e432ac31..3cb53ac11 100644 --- a/kmail/templateparser.h +++ b/kmail/templateparser.h @@ -29,7 +29,29 @@ class KMFolder; class TQObject; class KProcess; -class TemplateParser : public QObject +/** + * The TemplateParser transforms a message with a given template. + * + * A template contains text and commands, such as %QUOTE or %ODATE, which will be + * replaced with the real values in process(). + * + * The message given in the constructor is the message that is being transformed. + * The message text will be replaced by the processed text of the template, but other + * properties, such as the attachments or the subject, are preserved. + * + * There are two different kind of commands: Those that work on the message that is + * to be transformed and those that work on an 'original message'. + * Those that work on the message that is to be transformed have no special prefix, e.g. + * '%DATE'. Those that work on the original message have an 'O' prefix, for example + * '%ODATE'. + * This means that the %DATE command will take the date of the message passed in the + * constructor, the message which is to be transformed, whereas the %ODATE command will + * take the date of the message that is being passed in process(), the original message. + * + * TODO: What is the usecase of the commands that work on the message to be transformed? + * In general you only use the commands that work on the original message... + */ +class TemplateParser : public TQObject { Q_OBJECT @@ -44,16 +66,43 @@ class TemplateParser : public QObject static const int PipeTimeout = 15; public: - TemplateParser( KMMessage *amsg, const Mode amode, const TQString aselection, - bool aSmartQuote, bool anoQuote, bool aallowDecryption, - bool aselectionIsBody ); + TemplateParser( KMMessage *amsg, const Mode amode ); + ~TemplateParser(); + + /** + * Sets the selection. If this is set, only the selection will be added to commands such + * as %QUOTE. Otherwise, the whole message is quoted. + * If this is not called at all, the whole message is quoted as well. + * Call this before calling process(). + */ + void setSelection( const TQString &selection ); + + /** + * Sets whether the template parser is allowed to decrypt the original message when needing + * its message text, for example for the %QUOTE command. + * If true, it will tell the ObjectTreeParser it uses internally to decrypt the message, + * and that will possibly show a password request dialog to the user. + * + * The default is false. + */ + void setAllowDecryption( const bool allowDecryption ); - virtual void process( KMMessage *aorig_msg, KMFolder *afolder = NULL, bool append = false ); + virtual void process( KMMessage *aorig_msg, KMFolder *afolder = 0, bool append = false ); virtual void process( const TQString &tmplName, KMMessage *aorig_msg, - KMFolder *afolder = NULL, bool append = false ); + KMFolder *afolder = 0, bool append = false ); virtual void processWithTemplate( const TQString &tmpl ); + + /// This finds the template to use. Either the one from the folder, identity or + /// finally the global template. + /// This also reads the To and CC address of the template + /// @return the contents of the template virtual TQString findTemplate(); + + /// Finds the template with the given name. + /// This also reads the To and CC address of the template + /// @return the contents of the template virtual TQString findCustomTemplate( const TQString &tmpl ); + virtual TQString pipe( const TQString &cmd, const TQString &buf ); virtual TQString getFName( const TQString &str ); @@ -66,16 +115,48 @@ class TemplateParser : public QObject KMMessage *mMsg; KMMessage *mOrigMsg; TQString mSelection; - bool mSmartQuote; - bool mNoQuote; bool mAllowDecryption; - bool mSelectionIsBody; int mPipeRc; TQString mPipeOut; TQString mPipeErr; bool mDebug; TQString mQuoteString; bool mAppend; + TQString mTo, mCC; + partNode *mOrigRoot; + + /** + * If there was a text selection set in the constructor, that will be returned. + * Otherwise, returns the plain text of the original message, as in KMMessage::asPlainText(). + * The only difference is that this uses the cached object tree from parsedObjectTree() + * + * @param allowSelectionOnly if false, it will always return the complete mail text + */ + TQString messageText( bool allowSelectionOnly ); + + /** + * Returns the parsed object tree of the original message. + * The result is cached in mOrigRoot, therefore calling this multiple times will only parse + * the tree once. + */ + partNode* parsedObjectTree(); + + /** + * Called by processWithTemplate(). This adds the completely processed body to + * the message. + * + * In append mode, this will simply append the text to the body. + * + * Otherwise, the content of the old message is deleted and replaced with @p body. + * Attachments of the original message are also added back to the new message. + */ + void addProcessedBodyToMessage( const TQString &body ); + + /** + * Determines whether the signature should be stripped when getting the text of the original + * message, e.g. for commands such as %QUOTE + */ + bool shouldStripSignature() const; int parseQuotes( const TQString &prefix, const TQString &str, TQString "e ) const; diff --git a/kmail/templatesconfiguration.cpp b/kmail/templatesconfiguration.cpp index 1e2d74cbd..8b4f37e8f 100644 --- a/kmail/templatesconfiguration.cpp +++ b/kmail/templatesconfiguration.cpp @@ -394,9 +394,9 @@ void TemplatesConfiguration::importFromPhrases() "---------- %1 ----------\n" "\n" "Subject: %OFULLSUBJECT\n" - "Date: %ODATE\n" + "Date: %ODATE, %OTIMELONG\n" "From: %OFROMADDR\n" - "To: %OTOADDR\n" + "%OADDRESSEESADDR\n" "\n" "%TEXT\n" "-------------------------------------------------------\n" @@ -509,38 +509,43 @@ void TemplatesConfiguration::slotInsertCommand( TQString cmd, int adjustCursor ) TQString TemplatesConfiguration::defaultNewMessage() { return i18n( "%REM=\"Default new message template\"%-\n" - "%BLANK" + "%BLANK\n" + "%BLANK\n" + "%BLANK\n" ); } TQString TemplatesConfiguration::defaultReply() { return i18n( + "%CURSOR\n" + "%BLANK\n" "%REM=\"Default reply template\"%-\n" "On %ODATEEN %OTIMELONGEN you wrote:\n" "%QUOTE\n" - "%CURSOR\n" ); } TQString TemplatesConfiguration::defaultReplyAll() { return i18n( + "%CURSOR\n" + "%BLANK\n" "%REM=\"Default reply all template\"%-\n" "On %ODATEEN %OTIMELONGEN %OFROMNAME wrote:\n" "%QUOTE\n" - "%CURSOR\n" ); } -TQString TemplatesConfiguration::defaultForward() { +TQString TemplatesConfiguration::defaultForward() +{ return i18n( "%REM=\"Default forward template\"%-\n" "\n" "---------- Forwarded Message ----------\n" "\n" "Subject: %OFULLSUBJECT\n" - "Date: %ODATE\n" + "Date: %ODATE, %OTIMELONG\n" "From: %OFROMADDR\n" - "To: %OTOADDR\n" + "%OADDRESSEESADDR\n" "\n" "%TEXT\n" "-------------------------------------------------------\n" diff --git a/kmail/templatesinsertcommand.cpp b/kmail/templatesinsertcommand.cpp index ee07c375b..45bd81fca 100644 --- a/kmail/templatesinsertcommand.cpp +++ b/kmail/templatesinsertcommand.cpp @@ -139,6 +139,9 @@ TemplatesInsertCommand::TemplatesInsertCommand( TQWidget *parent, 0, mapper, TQT_SLOT( map() ), menu ); mapper->setMapping( action, COFromLName ); menu->insert( action ); + action = new KAction( i18n( "Addresses of all original recipients" ), + 0, mapper, TQT_SLOT( map() ), menu ); + mapper->setMapping( action, COAddresseesAddr ); action = new KAction( i18n( "Subject" ), 0, mapper, TQT_SLOT( map() ), menu ); mapper->setMapping( action, COFullSubject ); @@ -365,6 +368,7 @@ void TemplatesInsertCommand::slotMapped( int cmd ) case TemplatesInsertCommand::CTime: emit insertCommand("%TIME"); break; case TemplatesInsertCommand::CTimeLong: emit insertCommand("%TIMELONG"); break; case TemplatesInsertCommand::CTimeLongEn: emit insertCommand("%TIMELONGEN"); break; + case TemplatesInsertCommand::COAddresseesAddr: emit insertCommand("%OADDRESSEESADDR"); break; case TemplatesInsertCommand::CToAddr: emit insertCommand("%TOADDR"); break; case TemplatesInsertCommand::CToName: emit insertCommand("%TONAME"); break; case TemplatesInsertCommand::CToFName: emit insertCommand("%TOFNAME"); break; diff --git a/kmail/templatesinsertcommand.h b/kmail/templatesinsertcommand.h index 4a94a979b..04878136f 100644 --- a/kmail/templatesinsertcommand.h +++ b/kmail/templatesinsertcommand.h @@ -44,7 +44,8 @@ class TemplatesInsertCommand : public QPushButton CODateEn, CODateShort, CODate, CODow, COTimeLongEn, COTimeLong, COTime, CBlank, CNop, CClear, CDebug, CDebugOff, CToFName, CToLName, CFromFName, CFromLName, COToFName, COToLName, COFromFName, COFromLName, CCursor, - CCCAddr, CCCName, CCCFName, CCCLName, COCCAddr, COCCName, COCCFName, COCCLName }; + CCCAddr, CCCName, CCCFName, CCCLName, COCCAddr, COCCName, COCCFName, COCCLName, + COAddresseesAddr }; signals: void insertCommand( TemplatesInsertCommand::Command cmd ); diff --git a/kmail/treebase.cpp b/kmail/treebase.cpp new file mode 100644 index 000000000..19aff3155 --- /dev/null +++ b/kmail/treebase.cpp @@ -0,0 +1,235 @@ +/* + Copyright (c) 2008 Pradeepto K. Bhattacharya + ( adapted from kdepim/kmail/kmfolderseldlg.cpp and simplefoldertree.h ) + + 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. +*/ + +#include "treebase.h" +#include "kmfolder.h" +#include "kmfoldertree.h" +#include "simplefoldertree.h" + +#include +#include + +using namespace KMail; + +TreeBase::TreeBase( TQWidget *parent, KMFolderTree *folderTree, + const TQString &preSelection, bool mustBeReadWrite ) + : KListView( parent ), mFolderTree( folderTree ) +{ + Q_UNUSED( preSelection ); + Q_UNUSED( mustBeReadWrite ); + kdDebug(5006) << k_funcinfo << endl; + + connect(this, TQT_SIGNAL(collapsed(TQListViewItem*)), TQT_SLOT(recolorRows())); + connect(this, TQT_SIGNAL(expanded(TQListViewItem*)), TQT_SLOT(recolorRows())); + connect( this, TQT_SIGNAL( contextMenuRequested( TQListViewItem*, const TQPoint &, int ) ), + this, TQT_SLOT( slotContextMenuRequested( TQListViewItem*, const TQPoint & ) ) ); + +} + +const KMFolder * TreeBase::folder() const +{ + TQListViewItem * item = currentItem(); + if( item ) { + TreeItemBase *base = dynamic_cast( item ); + assert(base); + const KMFolder * folder = base->folder(); + return folder; + } + return 0; +} + +void TreeBase::setFolder( KMFolder *folder ) + { + for ( TQListViewItemIterator it( this ) ; it.current() ; ++it ) + { + const KMFolder *fld = dynamic_cast( it.current() )->folder(); + if ( fld == folder ) + { + setSelected( it.current(), true ); + ensureItemVisible( it.current() ); + } + } +} + +void TreeBase::addChildFolder() +{ + kdDebug(5006) << k_funcinfo << endl; + + const KMFolder *fld = folder(); + if ( fld ) { + mFolderTree->addChildFolder( (KMFolder *) fld, parentWidget() ); + reload( mLastMustBeReadWrite, mLastShowOutbox, mLastShowImapFolders ); + setFolder( (KMFolder *) fld ); + } +} + +void TreeBase::slotContextMenuRequested( TQListViewItem *lvi, const TQPoint &p ) +{ + kdDebug(5006) << k_funcinfo << endl; + + if (!lvi) + return; + setCurrentItem( lvi ); + setSelected( lvi, TRUE ); + + const KMFolder * folder = dynamic_cast( lvi )->folder(); + if ( !folder || folder->noContent() || folder->noChildren() ) + return; + + KPopupMenu *folderMenu = new KPopupMenu; + folderMenu->insertTitle( folder->label() ); + folderMenu->insertSeparator(); + folderMenu->insertItem(SmallIconSet("folder_new"), + i18n("&New Subfolder..."), this, + TQT_SLOT(addChildFolder())); + kmkernel->setContextMenuShown( true ); + folderMenu->exec (p, 0); + kmkernel->setContextMenuShown( false ); + delete folderMenu; + +} + +void TreeBase::recolorRows() +{ + kdDebug(5006) << k_funcinfo << endl; + + // Iterate through the list to set the alternate row flags. + int alt = 0; + TQListViewItemIterator it ( this ); + while ( it.current() ) { + TQListViewItem * item = it.current() ; + if ( item->isVisible() ) { + bool visible = true; + TQListViewItem * parent = item->parent(); + while ( parent ) { + if (!parent->isOpen()) { + visible = false; + break; + } + parent = parent->parent(); + } + + if ( visible ) { + TreeItemBase * treeItemBase = dynamic_cast( item ); + treeItemBase->setAlternate( alt ); + alt = !alt; + } + } + ++it; + } +} + +void TreeBase::reload( bool mustBeReadWrite, bool showOutbox, bool showImapFolders, + const TQString& preSelection ) +{ + clear(); + + mLastMustBeReadWrite = mustBeReadWrite; + mLastShowOutbox = showOutbox; + mLastShowImapFolders = showImapFolders; + + TQListViewItem * lastItem = 0; + TQListViewItem * lastTopItem = 0; + TQListViewItem * selectedItem = 0; + int lastDepth = 0; + + mFilter = ""; + TQString path; + + for ( TQListViewItemIterator it( mFolderTree ) ; it.current() ; ++it ) { + KMFolderTreeItem * fti = dynamic_cast( it.current() ); + + if ( !fti || fti->protocol() == KFolderTreeItem::Search ) + continue; + + int depth = fti->depth();// - 1; + //kdDebug( 5006 ) << "LastDepth=" << lastDepth << "\tdepth=" << depth + // << "\tname=" << fti->text( 0 ) << endl; + TQListViewItem * item = 0; + if ( depth <= 0 ) { + // top level - first top level item or after last existing top level item + if ( lastTopItem ) + item = createItem( this, lastTopItem ); + else + item = createItem( this ); + lastTopItem = item; + depth = 0; + path = ""; + } + else { + if ( depth > lastDepth ) { + // next lower level - parent node will get opened + item = createItem( lastItem ); + lastItem->setOpen( true ); + } + else { + + path = path.section( '/', 0, -2 - (lastDepth-depth) ); + if ( depth == lastDepth ) + // same level - behind previous item + item = createItem( lastItem->parent(), lastItem ); + else if ( depth < lastDepth ) { + // above previous level - might be more than one level difference + // but highest possibility is top level + while ( ( depth <= --lastDepth ) && lastItem->parent() ) { + lastItem = static_cast( lastItem->parent() ); + } + if ( lastItem->parent() ) + item = createItem( lastItem->parent(), lastItem ); + else { + // chain somehow broken - what does cause this ??? + kdDebug( 5006 ) << "You shouldn't get here: depth=" << depth + << "folder name=" << fti->text( 0 ) << endl; + item = createItem( this ); + lastTopItem = item; + } + } + } + } + + if ( depth > 0 ) + path += "/"; + path += fti->text( 0 ); + + + item->setText( mFolderColumn, fti->text( 0 ) ); + item->setText( mPathColumn, path ); + // Make items without folders and top level items unselectable + // (i.e. root item Local Folders and IMAP accounts) + if ( !fti->folder() || depth == 0 || ( mustBeReadWrite && fti->folder()->isReadOnly() ) ) { + item->setSelectable( false ); + } else { + TreeItemBase * treeItemBase = dynamic_cast( item ); + assert(treeItemBase); + treeItemBase->setFolder( fti->folder() ); + if ( preSelection == treeItemBase->folder()->idString() ) + selectedItem = item; + } + lastItem = item; + lastDepth = depth; + } + + if ( selectedItem ) { + setSelected( selectedItem, true ); + ensureItemVisible( selectedItem ); + } + +} + +#include "treebase.moc" diff --git a/kmail/treebase.h b/kmail/treebase.h new file mode 100644 index 000000000..3822add20 --- /dev/null +++ b/kmail/treebase.h @@ -0,0 +1,83 @@ +/* + Copyright (c) 2008 Pradeepto K. Bhattacharya + ( adapted from kdepim/kmail/kmfolderseldlg.cpp and simplefoldertree.h ) + + 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. +*/ + +#ifndef KMAIL_TREEBASE_H +#define KMAIL_TREEBASE_H + +#include "kmfolder.h" +#include "kmfoldertree.h" + +#include +#include + +namespace KMail { + +class TreeItemBase; + +class TreeBase : public KListView +{ + Q_OBJECT + public: + TreeBase( TQWidget * parent, KMFolderTree *folderTree, + const TQString &preSelection, bool mustBeReadWrite ); + + virtual ~TreeBase() {} + + const KMFolder * folder() const; + /** Set the current folder */ + void setFolder( KMFolder *folder ); + + inline void setFolder( const TQString& idString ) + { + setFolder( kmkernel->findFolderById( idString ) ); + } + + void reload( bool mustBeReadWrite, bool showOutbox, bool showImapFolders, + const TQString& preSelection = TQString::null ); + + int folderColumn() const { return mFolderColumn; } + void setFolderColumn( const int folderCol ) { mFolderColumn = folderCol; } + int pathColumn() const { return mPathColumn; } + void setPathColumn( const int pathCol ) { mPathColumn = pathCol; } + + public slots: + void addChildFolder(); + protected slots: + void slotContextMenuRequested( TQListViewItem *lvi, + const TQPoint &p ); + void recolorRows(); +protected: + virtual TQListViewItem* createItem( TQListView* ) = 0; + virtual TQListViewItem* createItem( TQListView*, TQListViewItem* ) = 0; + virtual TQListViewItem* createItem( TQListViewItem* ) = 0; + virtual TQListViewItem* createItem( TQListViewItem*, TQListViewItem* ) = 0; + + protected: + KMFolderTree* mFolderTree; + TQString mFilter; + bool mLastMustBeReadWrite; + bool mLastShowOutbox; + bool mLastShowImapFolders; + /** Folder and path column IDs. */ + int mFolderColumn; + int mPathColumn; + +}; +} +#endif diff --git a/kmail/urlhandlermanager.cpp b/kmail/urlhandlermanager.cpp index 100009faa..8ab2a25fc 100644 --- a/kmail/urlhandlermanager.cpp +++ b/kmail/urlhandlermanager.cpp @@ -2,7 +2,7 @@ urlhandlermanager.cpp This file is part of KMail, the KDE mail client. - Copyright (c) 2002-2003 Klar�vdalens Datakonsult AB + Copyright (c) 2002-2003 Klar�lvdalens Datakonsult AB Copyright (c) 2003 Marc Mutz KMail is free software; you can redistribute it and/or modify it @@ -43,9 +43,11 @@ #include "kmreaderwin.h" #include "kmkernel.h" #include "callback.h" +#include "stl_util.h" +#include +#include #include -#include "stl_util.h" #include #include @@ -123,8 +125,14 @@ namespace { ~AttachmentURLHandler() {} bool handleClick( const KURL &, KMReaderWin * ) const; + bool handleShiftClick( const KURL &url, KMReaderWin *window ) const; + bool handleDrag( const KURL &url, const TQString& imagePath, KMReaderWin *window ) const; + bool willHandleDrag( const KURL &url, const TQString& imagePath, KMReaderWin *window ) const; bool handleContextMenuRequest( const KURL &, const TQPoint &, KMReaderWin * ) const; TQString statusBarMessage( const KURL &, KMReaderWin * ) const; + private: + partNode* partNodeForUrl( const KURL &url, KMReaderWin *w ) const; + bool attachmentIsInHeader( const KURL &url ) const; }; class ShowAuditLogURLHandler : public KMail::URLHandler { @@ -137,6 +145,24 @@ namespace { TQString statusBarMessage( const KURL &, KMReaderWin * ) const; }; + // Handler that prevents dragging of internal images added by KMail, such as the envelope image + // in the enterprise header + class InternalImageURLHandler : public KMail::URLHandler { + public: + InternalImageURLHandler() : KMail::URLHandler() + {} + ~InternalImageURLHandler() + {} + bool handleDrag( const KURL &url, const TQString& imagePath, KMReaderWin *window ) const; + bool willHandleDrag( const KURL &url, const TQString& imagePath, KMReaderWin *window ) const; + bool handleClick( const KURL &, KMReaderWin * ) const + { return false; } + bool handleContextMenuRequest( const KURL &, const TQPoint &, KMReaderWin * ) const + { return false; } + TQString statusBarMessage( const KURL &, KMReaderWin * ) const + { return TQString(); } + }; + class FallBackURLHandler : public KMail::URLHandler { public: FallBackURLHandler() : KMail::URLHandler() {} @@ -212,7 +238,7 @@ static partNode * partNodeFromXKMailUrl( const KURL & url, KMReaderWin * w, TQSt const int part_id = urlParts[1].toInt( &ok ); if ( !ok ) return 0; - *path = KURL::decode_string( urlParts[2], 106 ); + *path = KURL::decode_string( urlParts[2] ); return w->partNodeForId( part_id ); } @@ -274,6 +300,7 @@ KMail::URLHandlerManager::URLHandlerManager() { registerHandler( new AttachmentURLHandler() ); registerHandler( mBodyPartURLHandlerManager = new BodyPartURLHandlerManager() ); registerHandler( new ShowAuditLogURLHandler() ); + registerHandler( new InternalImageURLHandler ); registerHandler( new FallBackURLHandler() ); } @@ -311,6 +338,32 @@ bool KMail::URLHandlerManager::handleClick( const KURL & url, KMReaderWin * w ) return false; } +bool KMail::URLHandlerManager::handleShiftClick( const KURL &url, KMReaderWin *window ) const +{ + for ( HandlerList::const_iterator it = mHandlers.begin() ; it != mHandlers.end() ; ++it ) + if ( (*it)->handleShiftClick( url, window ) ) + return true; + return false; +} + +bool KMail::URLHandlerManager::willHandleDrag( const KURL &url, const TQString& imagePath, + KMReaderWin *window ) const +{ + for ( HandlerList::const_iterator it = mHandlers.begin() ; it != mHandlers.end() ; ++it ) + if ( (*it)->willHandleDrag( url, imagePath, window ) ) + return true; + return false; +} + +bool KMail::URLHandlerManager::handleDrag( const KURL &url, const TQString& imagePath, + KMReaderWin *window ) const +{ + for ( HandlerList::const_iterator it = mHandlers.begin() ; it != mHandlers.end() ; ++it ) + if ( (*it)->handleDrag( url, imagePath, window ) ) + return true; + return false; +} + bool KMail::URLHandlerManager::handleContextMenuRequest( const KURL & url, const TQPoint & p, KMReaderWin * w ) const { for ( HandlerList::const_iterator it = mHandlers.begin() ; it != mHandlers.end() ; ++it ) if ( (*it)->handleContextMenuRequest( url, p, w ) ) @@ -405,6 +458,13 @@ namespace { return true; } + if ( url.path() == "showRawToltecMail" ) { + w->saveRelativePosition(); + w->setShowRawToltecMail( true ); + w->update( true ); + return true; + } + // if ( url.path() == "startIMApp" ) // { // kmkernel->imProxy()->startPreferredApp(); @@ -430,6 +490,10 @@ namespace { return i18n("Show signature details."); if ( url.path() == "hideSignatureDetails" ) return i18n("Hide signature details."); + if ( url.path() == "hideAttachmentQuicklist" ) + return i18n( "Hide attachment list" ); + if ( url.path() == "showAttachmentQuicklist" ) + return i18n( "Show attachment list" ); } return TQString::null ; } @@ -459,11 +523,14 @@ namespace { if ( url.protocol() == "kmail" && url.path() == "levelquote" ) { TQString query= url.query(); - if ( query.length()>=2 ) - if ( query[ 1 ] =='-' ) + if ( query.length()>=2 ) { + if ( query[ 1 ] =='-' ) { return i18n("Expand all quoted text."); - else + } + else { return i18n("Collapse quoted text."); + } + } } return TQString::null ; } @@ -517,32 +584,106 @@ namespace { } namespace { - bool AttachmentURLHandler::handleClick( const KURL & url, KMReaderWin * w ) const { + + partNode* AttachmentURLHandler::partNodeForUrl( const KURL &url, KMReaderWin *w ) const + { if ( !w || !w->message() ) + return 0; + if ( url.protocol() != "attachment" ) + return 0; + + bool ok; + int nodeId = url.path().toInt( &ok ); + if ( !ok ) + return 0; + + partNode * node = w->partNodeForId( nodeId ); + return node; + } + + bool AttachmentURLHandler::attachmentIsInHeader( const KURL &url ) const + { + bool inHeader = false; + const TQString place = url.queryItem( "place" ).lower(); + if ( place != TQString::null ) { + inHeader = ( place == "header" ); + } + return inHeader; + } + + bool AttachmentURLHandler::handleClick( const KURL & url, KMReaderWin * w ) const + { + partNode * node = partNodeForUrl( url, w ); + if ( !node ) + return false; + + const bool inHeader = attachmentIsInHeader( url ); + const bool shouldShowDialog = !node->isDisplayedEmbedded() || !inHeader; + if ( inHeader ) + w->scrollToAttachment( node ); + if ( shouldShowDialog ) + w->openAttachment( node->nodeId(), w->tempFileUrlFromPartNode( node ).path() ); + return true; + } + + bool AttachmentURLHandler::handleShiftClick( const KURL &url, KMReaderWin *window ) const + { + partNode * node = partNodeForUrl( url, window ); + if ( !node ) return false; - const int id = KMReaderWin::msgPartFromUrl( url ); - if ( id <= 0 ) + if ( !window ) return false; - w->openAttachment( id, url.path() ); + window->saveAttachment( window->tempFileUrlFromPartNode( node ) ); return true; } - bool AttachmentURLHandler::handleContextMenuRequest( const KURL & url, const TQPoint & p, KMReaderWin * w ) const { - if ( !w || !w->message() ) + bool AttachmentURLHandler::willHandleDrag( const KURL &url, const TQString& imagePath, + KMReaderWin *window ) const + { + Q_UNUSED( imagePath ); + return partNodeForUrl( url, window ) != 0; + } + + bool AttachmentURLHandler::handleDrag( const KURL &url, const TQString& imagePath, + KMReaderWin *window ) const + { + Q_UNUSED( imagePath ); + const partNode * node = partNodeForUrl( url, window ); + if ( !node ) return false; - const int id = KMReaderWin::msgPartFromUrl( url ); - if ( id <= 0 ) + + KURL file = window->tempFileUrlFromPartNode( node ).path(); + if ( !file.isEmpty() ) { + TQString icon = node->msgPart().iconName( KIcon::Small ); + KURLDrag* urlDrag = new KURLDrag( file, window ); + if ( !icon.isEmpty() ) { + TQPixmap iconMap( icon ); + urlDrag->setPixmap( iconMap ); + } + urlDrag->drag(); + return true; + } + else { + return false; + } + } + + bool AttachmentURLHandler::handleContextMenuRequest( const KURL & url, const TQPoint & p, KMReaderWin * w ) const + { + partNode * node = partNodeForUrl( url, w ); + if ( !node ) return false; - w->showAttachmentPopup( id, url.path(), p ); + + w->showAttachmentPopup( node->nodeId(), w->tempFileUrlFromPartNode( node ).path(), p ); return true; } - TQString AttachmentURLHandler::statusBarMessage( const KURL & url, KMReaderWin * w ) const { - if ( !w || !w->message() ) - return TQString::null; - const partNode * node = w->partNodeFromUrl( url ); + TQString AttachmentURLHandler::statusBarMessage( const KURL & url, KMReaderWin * w ) const + { + partNode * node = partNodeForUrl( url, w ); if ( !node ) return TQString::null; + const KMMessagePart & msgPart = node->msgPart(); TQString name = msgPart.fileName(); if ( name.isEmpty() ) @@ -569,7 +710,9 @@ namespace { return true; } - bool ShowAuditLogURLHandler::handleContextMenuRequest( const KURL & url, const TQPoint &, KMReaderWin * w ) const { + bool ShowAuditLogURLHandler::handleContextMenuRequest( const KURL & url, const TQPoint &, KMReaderWin * w ) const + { + Q_UNUSED( w ); // disable RMB for my own links: return !extractAuditLog( url ).isEmpty(); } @@ -582,6 +725,30 @@ namespace { } } +namespace { + bool InternalImageURLHandler::handleDrag( const KURL &url, const TQString& imagePath, + KMReaderWin *window ) const + { + Q_UNUSED( window ); + Q_UNUSED( url ); + const TQString kmailImagePath = locate( "data", "kmail/pics/" ); + if ( imagePath.contains( kmailImagePath ) ) { + // Do nothing, don't start a drag + return true; + } + return false; + } + + bool InternalImageURLHandler::willHandleDrag( const KURL &url, const TQString& imagePath, + KMReaderWin *window ) const + { + Q_UNUSED( window ); + Q_UNUSED( url ); + const TQString kmailImagePath = locate( "data", "kmail/pics/" ); + return imagePath.contains( kmailImagePath ); + } +} + namespace { bool FallBackURLHandler::handleClick( const KURL & url, KMReaderWin * w ) const { if ( w ) diff --git a/kmail/urlhandlermanager.h b/kmail/urlhandlermanager.h index acd9f2844..815952593 100644 --- a/kmail/urlhandlermanager.h +++ b/kmail/urlhandlermanager.h @@ -72,7 +72,10 @@ namespace KMail { void unregisterHandler( const Interface::BodyPartURLHandler * handler ); bool handleClick( const KURL & url, KMReaderWin * w=0 ) const; + bool handleShiftClick( const KURL &url, KMReaderWin *window = 0 ) const; bool handleContextMenuRequest( const KURL & url, const TQPoint & p, KMReaderWin * w=0 ) const; + bool willHandleDrag( const KURL &url, const TQString& imagePath, KMReaderWin *window = 0 ) const; + bool handleDrag( const KURL &url, const TQString& imagePath, KMReaderWin *window = 0 ) const; TQString statusBarMessage( const KURL & url, KMReaderWin * w=0 ) const; private: diff --git a/kmail/vacation.cpp b/kmail/vacation.cpp index d0cdedf85..0b9222f48 100644 --- a/kmail/vacation.cpp +++ b/kmail/vacation.cpp @@ -500,11 +500,15 @@ namespace KMail { u.setUser( a->login() ); u.setPass( a->passwd() ); u.setPort( sieve.port() ); - u.setQuery( "x-mech=" + (a->auth() == "*" ? "PLAIN" : a->auth()) ); //translate IMAP LOGIN to PLAIN + u.addQueryItem( "x-mech", a->auth() == "*" ? "PLAIN" : a->auth() ); //translate IMAP LOGIN to PLAIN + if ( !a->useSSL() && !a->useTLS() ) + u.addQueryItem( "x-allow-unencrypted", "true" ); u.setFileName( sieve.vacationFileName() ); return u; } else { KURL u = sieve.alternateURL(); + if ( u.protocol().lower() == "sieve" && !a->useSSL() && !a->useTLS() && u.queryItem("x-allow-unencrypted").isEmpty() ) + u.addQueryItem( "x-allow-unencrypted", "true" ); u.setFileName( sieve.vacationFileName() ); return u; } @@ -579,9 +583,11 @@ namespace KMail { TQStringList Vacation::defaultMailAliases() { TQStringList sl; for ( KPIM::IdentityManager::ConstIterator it = kmkernel->identityManager()->begin() ; - it != kmkernel->identityManager()->end() ; ++it ) - if ( !(*it).emailAddr().isEmpty() ) - sl.push_back( (*it).emailAddr() ); + it != kmkernel->identityManager()->end() ; ++it ) { + if ( !(*it).primaryEmailAddress().isEmpty() ) + sl.push_back( (*it).primaryEmailAddress() ); + sl += (*it).emailAliases(); + } return sl; } @@ -666,6 +672,7 @@ namespace KMail { mDialog->setMailAliases( defaultMailAliases().join(", ") ); mDialog->setSendForSpam( defaultSendForSpam() ); mDialog->setDomainName( defaultDomainName() ); + mDialog->setDomainCheck( false ); } void Vacation::slotDialogOk() { diff --git a/kmail/vacationdialog.cpp b/kmail/vacationdialog.cpp index a2dabe9b1..50dea24e2 100644 --- a/kmail/vacationdialog.cpp +++ b/kmail/vacationdialog.cpp @@ -71,7 +71,9 @@ namespace KMail { // "Resent only after" spinbox and label: ++row; - mIntervalSpin = new KIntSpinBox( 1, 356, 1, 7, 10, plainPage(), "mIntervalSpin" ); + int defDayInterval = 7; //default day interval + mIntervalSpin = new KIntSpinBox( 1, 356, 1, defDayInterval, 10, plainPage(), "mIntervalSpin" ); + mIntervalSpin->setSuffix( i18n(" day", " days", defDayInterval) ); connect(mIntervalSpin, TQT_SIGNAL( valueChanged( int )), TQT_SLOT( slotIntervalSpinChanged( int ) ) ); glay->addWidget( new TQLabel( mIntervalSpin, i18n("&Resend notification only after:"), plainPage() ), row, 0 ); glay->addWidget( mIntervalSpin, row, 1 ); @@ -168,28 +170,40 @@ namespace KMail { } void VacationDialog::setDomainName( const TQString & domain ) { - mDomainEdit->setText( domain ); - if ( !domain.isEmpty() ) + if ( !domain.isEmpty() ) { + mDomainEdit->setText( domain ); mDomainCheck->setChecked( true ); + } + } + + bool VacationDialog::domainCheck() const + { + return mDomainCheck->isChecked(); + } + + void VacationDialog::setDomainCheck( bool check ) + { + mDomainCheck->setChecked( check ); } - bool VacationDialog::sendForSpam() const { + bool VacationDialog::sendForSpam() const + { return !mSpamCheck->isChecked(); } - void VacationDialog::setSendForSpam( bool enable ) { + void VacationDialog::setSendForSpam( bool enable ) + { mSpamCheck->setChecked( !enable ); } - /* virtual*/ - void KMail::VacationDialog::enableDomainAndSendForSpam( bool enable ) { - mDomainCheck->setEnabled( enable ); - mDomainEdit->setEnabled( enable ); - mSpamCheck->setEnabled( enable ); + void KMail::VacationDialog::enableDomainAndSendForSpam( bool enable ) + { + mDomainCheck->setEnabled( enable ); + mDomainEdit->setEnabled( enable && mDomainCheck->isChecked() ); + mSpamCheck->setEnabled( enable ); } - } // namespace KMail #include "vacationdialog.moc" diff --git a/kmail/vacationdialog.h b/kmail/vacationdialog.h index 2ce076ff6..a971b1182 100644 --- a/kmail/vacationdialog.h +++ b/kmail/vacationdialog.h @@ -46,6 +46,9 @@ namespace KMail { bool activateVacation() const; virtual void setActivateVacation( bool activate ); + bool domainCheck() const; + virtual void setDomainCheck( bool check ); + TQString messageText() const; virtual void setMessageText( const TQString & text ); @@ -55,14 +58,14 @@ namespace KMail { KMime::Types::AddrSpecList mailAliases() const; virtual void setMailAliases( const KMime::Types::AddrSpecList & aliases ); virtual void setMailAliases( const TQString & aliases ); - + TQString domainName() const; virtual void setDomainName( const TQString & domain ); bool sendForSpam() const; virtual void setSendForSpam( bool enable ); - + private slots: void slotIntervalSpinChanged( int value ); diff --git a/kmail/vcardviewer.cpp b/kmail/vcardviewer.cpp index ff4a3b86e..0e34f72cc 100644 --- a/kmail/vcardviewer.cpp +++ b/kmail/vcardviewer.cpp @@ -37,7 +37,11 @@ using KABC::Addressee; #include -KMail::VCardViewer::VCardViewer(TQWidget *parent, const TQString& vCard, const char* name) +#if defined(KABC_VCARD_ENCODING_FIX) +KMail::VCardViewer::VCardViewer( TQWidget *parent, const TQByteArray &vCard, const char *name ) +#else +KMail::VCardViewer::VCardViewer( TQWidget *parent, const TQString &vCard, const char *name ) +#endif : KDialogBase( parent, name, false, i18n("VCard Viewer"), User1|User2|User3|Close, Close, true, i18n("&Import"), i18n("&Next Card"), i18n("&Previous Card") ) { @@ -47,7 +51,11 @@ KMail::VCardViewer::VCardViewer(TQWidget *parent, const TQString& vCard, const c setMainWidget(mAddresseeView); VCardConverter vcc; +#if defined(KABC_VCARD_ENCODING_FIX) + mAddresseeList = vcc.parseVCardsRaw( vCard.data() ); +#else mAddresseeList = vcc.parseVCards( vCard ); +#endif if ( !mAddresseeList.empty() ) { itAddresseeList = mAddresseeList.begin(); mAddresseeView->setAddressee( *itAddresseeList ); diff --git a/kmail/vcardviewer.h b/kmail/vcardviewer.h index 2fdcd090a..48da60ba3 100644 --- a/kmail/vcardviewer.h +++ b/kmail/vcardviewer.h @@ -22,6 +22,7 @@ #include #include +#include // for KABC_VCARD_ENCODING_FIX define #include @@ -33,24 +34,28 @@ namespace KPIM { namespace KMail { - class VCardViewer : public KDialogBase - { - Q_OBJECT - public: - VCardViewer(TQWidget *parent, const TQString& vCard, const char* name); - virtual ~VCardViewer(); - - protected: - virtual void slotUser1(); - virtual void slotUser2(); - virtual void slotUser3(); - - private: - KPIM::AddresseeView * mAddresseeView; - KABC::Addressee::List mAddresseeList; - - TQValueListIterator itAddresseeList; - }; +class VCardViewer : public KDialogBase +{ + Q_OBJECT + public: +#if defined(KABC_VCARD_ENCODING_FIX) + VCardViewer( TQWidget *parent, const TQByteArray &vCard, const char *name ); +#else + VCardViewer( TQWidget *parent, const TQString &vCard, const char *name ); +#endif + virtual ~VCardViewer(); + + protected: + virtual void slotUser1(); + virtual void slotUser2(); + virtual void slotUser3(); + + private: + KPIM::AddresseeView *mAddresseeView; + KABC::Addressee::List mAddresseeList; + + TQValueListIterator itAddresseeList; +}; } -- cgit v1.2.1