summaryrefslogtreecommitdiffstats
path: root/kmail
diff options
context:
space:
mode:
Diffstat (limited to 'kmail')
-rw-r--r--kmail/COMMITPOLICY13
-rw-r--r--kmail/ChangeLog806
-rw-r--r--kmail/DESIGN1
-rw-r--r--kmail/KMail.desktop93
-rw-r--r--kmail/Mainpage.dox871
-rw-r--r--kmail/Makefile.am237
-rw-r--r--kmail/TODO36
-rw-r--r--kmail/about/Makefile.am6
-rw-r--r--kmail/about/kmail.css26
-rw-r--r--kmail/about/main.html66
-rw-r--r--kmail/about/top-right-kmail.pngbin0 -> 17996 bytes
-rw-r--r--kmail/aboutdata.cpp229
-rw-r--r--kmail/aboutdata.h49
-rw-r--r--kmail/accountcombobox.cpp106
-rw-r--r--kmail/accountcombobox.h61
-rw-r--r--kmail/accountdialog.cpp2352
-rw-r--r--kmail/accountdialog.h340
-rw-r--r--kmail/accountmanager.cpp415
-rw-r--r--kmail/accountmanager.h141
-rw-r--r--kmail/accountwizard.cpp799
-rw-r--r--kmail/accountwizard.h138
-rw-r--r--kmail/acljobs.cpp261
-rw-r--r--kmail/acljobs.h177
-rw-r--r--kmail/actionscheduler.cpp836
-rw-r--r--kmail/actionscheduler.h173
-rw-r--r--kmail/annotationjobs.cpp253
-rw-r--r--kmail/annotationjobs.h199
-rw-r--r--kmail/antispamconfig.cpp98
-rw-r--r--kmail/antispamconfig.h120
-rw-r--r--kmail/antispamwizard.cpp1151
-rw-r--r--kmail/antispamwizard.h398
-rw-r--r--kmail/app_octetstream.cpp66
-rw-r--r--kmail/application_octetstream.desktop85
-rw-r--r--kmail/attachmentcollector.cpp85
-rw-r--r--kmail/attachmentcollector.h75
-rw-r--r--kmail/attachmentlistview.cpp146
-rw-r--r--kmail/attachmentlistview.h59
-rw-r--r--kmail/attachmentstrategy.cpp209
-rw-r--r--kmail/attachmentstrategy.h79
-rw-r--r--kmail/avscripts/Makefile.am2
-rwxr-xr-xkmail/avscripts/kmail_antivir.sh42
-rwxr-xr-xkmail/avscripts/kmail_clamav.sh53
-rwxr-xr-xkmail/avscripts/kmail_fprot.sh55
-rwxr-xr-xkmail/avscripts/kmail_sav.sh42
-rw-r--r--kmail/bodypartformatter.cpp334
-rw-r--r--kmail/bodypartformatter.h58
-rw-r--r--kmail/bodypartformatterfactory.cpp191
-rw-r--r--kmail/bodypartformatterfactory.h72
-rw-r--r--kmail/bodypartformatterfactory_p.h62
-rw-r--r--kmail/bodyvisitor.cpp211
-rw-r--r--kmail/bodyvisitor.h109
-rw-r--r--kmail/cachedimapjob.cpp841
-rw-r--r--kmail/cachedimapjob.h141
-rw-r--r--kmail/callback.cpp219
-rw-r--r--kmail/callback.h91
-rw-r--r--kmail/chiasmuskeyselector.cpp55
-rw-r--r--kmail/chiasmuskeyselector.h27
-rw-r--r--kmail/colorlistbox.cpp198
-rw-r--r--kmail/colorlistbox.h73
-rw-r--r--kmail/compactionjob.cpp292
-rw-r--r--kmail/compactionjob.h114
-rw-r--r--kmail/composer.cpp4
-rw-r--r--kmail/composer.h153
-rw-r--r--kmail/composercryptoconfiguration.ui158
-rw-r--r--kmail/configure.in.bot14
-rw-r--r--kmail/configure.in.in90
-rw-r--r--kmail/configuredialog.cpp5064
-rw-r--r--kmail/configuredialog.h90
-rw-r--r--kmail/configuredialog_p.cpp437
-rw-r--r--kmail/configuredialog_p.h1064
-rw-r--r--kmail/copyfolderjob.cpp283
-rw-r--r--kmail/copyfolderjob.h110
-rw-r--r--kmail/cr22-app-kmaillight.pngbin0 -> 1424 bytes
-rw-r--r--kmail/cr32-app-kmaillight.pngbin0 -> 1744 bytes
-rw-r--r--kmail/csshelper.cpp93
-rw-r--r--kmail/csshelper.h46
-rw-r--r--kmail/custommimeheader.kcfg15
-rw-r--r--kmail/custommimeheader.kcfgc5
-rw-r--r--kmail/customtemplates.cpp376
-rw-r--r--kmail/customtemplates.h92
-rw-r--r--kmail/customtemplates_base.ui303
-rw-r--r--kmail/customtemplates_kfg.kcfg28
-rw-r--r--kmail/customtemplates_kfg.kcfgc5
-rw-r--r--kmail/dcopimap.desktop55
-rw-r--r--kmail/dcopmail.desktop55
-rw-r--r--kmail/dcoptest.cpp40
-rw-r--r--kmail/dictionarycombobox.cpp149
-rw-r--r--kmail/dictionarycombobox.h83
-rw-r--r--kmail/distributionlistdialog.cpp261
-rw-r--r--kmail/distributionlistdialog.h46
-rw-r--r--kmail/editorwatcher.cpp181
-rw-r--r--kmail/editorwatcher.h77
-rw-r--r--kmail/encodingdetector.cpp1377
-rw-r--r--kmail/encodingdetector.h166
-rw-r--r--kmail/encodingdetector_ja.cpp376
-rw-r--r--kmail/encodingdetector_ja_p.h126
-rw-r--r--kmail/eventsrc126
-rw-r--r--kmail/expirejob.cpp252
-rw-r--r--kmail/expirejob.h81
-rw-r--r--kmail/expirypropertiesdialog.cpp197
-rw-r--r--kmail/expirypropertiesdialog.h68
-rw-r--r--kmail/favoritefolderview.cpp517
-rw-r--r--kmail/favoritefolderview.h95
-rw-r--r--kmail/filehtmlwriter.cpp108
-rw-r--r--kmail/filehtmlwriter.h67
-rw-r--r--kmail/filterimporterexporter.cpp216
-rw-r--r--kmail/filterimporterexporter.h70
-rw-r--r--kmail/filterlog.cpp165
-rw-r--r--kmail/filterlog.h161
-rw-r--r--kmail/filterlogdlg.cpp268
-rw-r--r--kmail/filterlogdlg.h80
-rw-r--r--kmail/folderIface.cpp150
-rw-r--r--kmail/folderIface.h71
-rw-r--r--kmail/folderdiaacltab.cpp817
-rw-r--r--kmail/folderdiaacltab.h157
-rw-r--r--kmail/folderdiaquotatab.cpp215
-rw-r--r--kmail/folderdiaquotatab.h91
-rw-r--r--kmail/folderdiaquotatab_p.cpp88
-rw-r--r--kmail/folderdiaquotatab_p.h63
-rw-r--r--kmail/folderjob.cpp130
-rw-r--r--kmail/folderjob.h188
-rw-r--r--kmail/folderpropertiesdialog.ui638
-rw-r--r--kmail/folderpropertiesdialog.ui.h15
-rw-r--r--kmail/folderrequester.cpp127
-rw-r--r--kmail/folderrequester.h112
-rw-r--r--kmail/foldershortcutdialog.cpp108
-rw-r--r--kmail/foldershortcutdialog.h74
-rw-r--r--kmail/folderstorage.cpp1176
-rw-r--r--kmail/folderstorage.h645
-rw-r--r--kmail/foldertreebase.cpp243
-rw-r--r--kmail/foldertreebase.h98
-rw-r--r--kmail/folderviewtooltip.h55
-rw-r--r--kmail/globalsettings.cpp62
-rw-r--r--kmail/globalsettings.h59
-rw-r--r--kmail/globalsettings_base.kcfgc7
-rw-r--r--kmail/headeritem.cpp585
-rw-r--r--kmail/headeritem.h241
-rw-r--r--kmail/headerlistquicksearch.cpp193
-rw-r--r--kmail/headerlistquicksearch.h102
-rw-r--r--kmail/headerstrategy.cpp312
-rw-r--r--kmail/headerstrategy.h80
-rw-r--r--kmail/headerstyle.cpp983
-rw-r--r--kmail/headerstyle.h88
-rw-r--r--kmail/hi128-app-kmail.pngbin0 -> 15605 bytes
-rw-r--r--kmail/hi16-app-kmail.pngbin0 -> 1198 bytes
-rw-r--r--kmail/hi22-app-kmail.pngbin0 -> 1521 bytes
-rw-r--r--kmail/hi32-app-kmail.pngbin0 -> 2946 bytes
-rw-r--r--kmail/hi48-app-kmail.pngbin0 -> 4799 bytes
-rw-r--r--kmail/hi64-app-kmail.pngbin0 -> 6140 bytes
-rw-r--r--kmail/hisc-app-kmail.svgzbin0 -> 2953 bytes
-rw-r--r--kmail/htmlstatusbar.cpp140
-rw-r--r--kmail/htmlstatusbar.h100
-rw-r--r--kmail/identitydialog.cpp700
-rw-r--r--kmail/identitydialog.h118
-rw-r--r--kmail/identitydrag.cpp86
-rw-r--r--kmail/identitydrag.h65
-rw-r--r--kmail/identitylistview.cpp146
-rw-r--r--kmail/identitylistview.h86
-rw-r--r--kmail/imapaccountbase.cpp1446
-rw-r--r--kmail/imapaccountbase.h635
-rw-r--r--kmail/imapjob.cpp711
-rw-r--r--kmail/imapjob.h97
-rw-r--r--kmail/index.cpp594
-rw-r--r--kmail/index.h218
-rw-r--r--kmail/interfaces/Makefile.am8
-rw-r--r--kmail/interfaces/bodypart.h163
-rw-r--r--kmail/interfaces/bodypartformatter.h95
-rw-r--r--kmail/interfaces/bodyparturlhandler.h105
-rw-r--r--kmail/interfaces/htmlwriter.h120
-rw-r--r--kmail/interfaces/observable.h57
-rw-r--r--kmail/interfaces/observer.h55
-rw-r--r--kmail/interfaces/rulewidgethandler.h81
-rw-r--r--kmail/interfaces/urlhandler.h79
-rw-r--r--kmail/isubject.cpp39
-rw-r--r--kmail/isubject.h63
-rw-r--r--kmail/jobscheduler.cpp256
-rw-r--r--kmail/jobscheduler.h156
-rw-r--r--kmail/kcm_kmail.cpp85
-rw-r--r--kmail/kcursorsaver.h61
-rw-r--r--kmail/keyresolver.cpp1615
-rw-r--r--kmail/keyresolver.h319
-rw-r--r--kmail/khtmlparthtmlwriter.cpp154
-rw-r--r--kmail/khtmlparthtmlwriter.h83
-rw-r--r--kmail/kleo_util.h81
-rw-r--r--kmail/klistboxdialog.cpp83
-rw-r--r--kmail/klistboxdialog.h36
-rw-r--r--kmail/klistviewindexedsearchline.cpp74
-rw-r--r--kmail/klistviewindexedsearchline.h76
-rw-r--r--kmail/kmaccount.cpp500
-rw-r--r--kmail/kmaccount.h336
-rw-r--r--kmail/kmacctcachedimap.cpp479
-rw-r--r--kmail/kmacctcachedimap.h230
-rw-r--r--kmail/kmacctfolder.cpp46
-rw-r--r--kmail/kmacctfolder.h55
-rw-r--r--kmail/kmacctimap.cpp621
-rw-r--r--kmail/kmacctimap.h152
-rw-r--r--kmail/kmacctlocal.cpp322
-rw-r--r--kmail/kmacctlocal.h57
-rw-r--r--kmail/kmacctmaildir.cpp234
-rw-r--r--kmail/kmacctmaildir.h37
-rw-r--r--kmail/kmacctseldlg.cpp77
-rw-r--r--kmail/kmacctseldlg.h51
-rw-r--r--kmail/kmaddrbook.cpp135
-rw-r--r--kmail/kmaddrbook.h43
-rwxr-xr-xkmail/kmail-3.1-update-new-mail-notification-settings.pl38
-rwxr-xr-xkmail/kmail-3.1-use-UOID-for-identities.pl93
-rw-r--r--kmail/kmail-3.1.4-dont-use-UOID-0-for-any-identity.pl88
-rwxr-xr-xkmail/kmail-3.2-misc.sh78
-rwxr-xr-xkmail/kmail-3.2-update-loop-on-goto-unread-settings.sh21
-rw-r--r--kmail/kmail-3.3-aegypten.pl28
-rw-r--r--kmail/kmail-3.3-misc.pl27
-rw-r--r--kmail/kmail-3.3-move-identities.pl31
-rwxr-xr-xkmail/kmail-3.3-split-sign-encr-keys.sh31
-rwxr-xr-xkmail/kmail-3.3-use-ID-for-accounts.pl236
-rw-r--r--kmail/kmail-3.3b1-misc.pl38
-rw-r--r--kmail/kmail-3.4-misc.pl27
-rw-r--r--kmail/kmail-3.4.1-update-status-filters.pl42
-rw-r--r--kmail/kmail-3.5-trigger-flag-migration.pl39
-rwxr-xr-xkmail/kmail-pgpidentity.pl34
-rwxr-xr-xkmail/kmail-upd-identities.pl54
-rw-r--r--kmail/kmail.antispamrc237
-rw-r--r--kmail/kmail.antispamrc-HOWTO154
-rw-r--r--kmail/kmail.antivirusrc50
-rw-r--r--kmail/kmail.kcfg670
-rw-r--r--kmail/kmail.upd179
-rw-r--r--kmail/kmailIface.h219
-rw-r--r--kmail/kmail_config_accounts.desktop162
-rw-r--r--kmail/kmail_config_appearance.desktop169
-rw-r--r--kmail/kmail_config_composer.desktop150
-rw-r--r--kmail/kmail_config_identity.desktop170
-rw-r--r--kmail/kmail_config_misc.desktop166
-rw-r--r--kmail/kmail_config_security.desktop172
-rw-r--r--kmail/kmail_options.h31
-rw-r--r--kmail/kmail_part.cpp251
-rw-r--r--kmail/kmail_part.h100
-rw-r--r--kmail/kmail_part.rc199
-rw-r--r--kmail/kmail_view.desktop22
-rw-r--r--kmail/kmailicalIface.h175
-rw-r--r--kmail/kmailicalifaceimpl.cpp2297
-rw-r--r--kmail/kmailicalifaceimpl.h347
-rw-r--r--kmail/kmailpartIface.h35
-rw-r--r--kmail/kmatmlistview.cpp175
-rw-r--r--kmail/kmatmlistview.h71
-rw-r--r--kmail/kmcommands.cpp3559
-rw-r--r--kmail/kmcommands.h1101
-rw-r--r--kmail/kmcomposerui.rc115
-rw-r--r--kmail/kmcomposewin.cpp5233
-rw-r--r--kmail/kmcomposewin.h911
-rw-r--r--kmail/kmdebug.h62
-rw-r--r--kmail/kmdict.cpp117
-rw-r--r--kmail/kmdict.h68
-rw-r--r--kmail/kmedit.cpp766
-rw-r--r--kmail/kmedit.h131
-rw-r--r--kmail/kmfawidgets.cpp157
-rw-r--r--kmail/kmfawidgets.h57
-rw-r--r--kmail/kmfilter.cpp430
-rw-r--r--kmail/kmfilter.h312
-rw-r--r--kmail/kmfilteraction.cpp1930
-rw-r--r--kmail/kmfilteraction.h700
-rw-r--r--kmail/kmfilterdlg.cpp1354
-rw-r--r--kmail/kmfilterdlg.h422
-rw-r--r--kmail/kmfiltermgr.cpp509
-rw-r--r--kmail/kmfiltermgr.h198
-rw-r--r--kmail/kmfolder.cpp881
-rw-r--r--kmail/kmfolder.h694
-rw-r--r--kmail/kmfoldercachedimap.cpp2992
-rw-r--r--kmail/kmfoldercachedimap.h567
-rw-r--r--kmail/kmfoldercombobox.cpp188
-rw-r--r--kmail/kmfoldercombobox.h51
-rw-r--r--kmail/kmfolderdia.cpp792
-rw-r--r--kmail/kmfolderdia.h241
-rw-r--r--kmail/kmfolderdir.cpp313
-rw-r--r--kmail/kmfolderdir.h92
-rw-r--r--kmail/kmfolderimap.cpp2434
-rw-r--r--kmail/kmfolderimap.h548
-rw-r--r--kmail/kmfolderindex.cpp499
-rw-r--r--kmail/kmfolderindex.h130
-rw-r--r--kmail/kmfoldermaildir.cpp1172
-rw-r--r--kmail/kmfoldermaildir.h169
-rw-r--r--kmail/kmfoldermbox.cpp1278
-rw-r--r--kmail/kmfoldermbox.h157
-rw-r--r--kmail/kmfoldermgr.cpp604
-rw-r--r--kmail/kmfoldermgr.h186
-rw-r--r--kmail/kmfoldernode.cpp65
-rw-r--r--kmail/kmfoldernode.h81
-rw-r--r--kmail/kmfoldersearch.cpp1141
-rw-r--r--kmail/kmfoldersearch.h216
-rw-r--r--kmail/kmfolderseldlg.cpp574
-rw-r--r--kmail/kmfolderseldlg.h119
-rw-r--r--kmail/kmfoldertree.cpp2126
-rw-r--r--kmail/kmfoldertree.h350
-rw-r--r--kmail/kmfoldertype.h37
-rw-r--r--kmail/kmglobal.h92
-rw-r--r--kmail/kmgroupware.cpp88
-rw-r--r--kmail/kmgroupware.h46
-rw-r--r--kmail/kmheaders.cpp3569
-rw-r--r--kmail/kmheaders.h465
-rw-r--r--kmail/kmkernel.cpp2401
-rw-r--r--kmail/kmkernel.h504
-rw-r--r--kmail/kmlineeditspell.cpp216
-rw-r--r--kmail/kmlineeditspell.h55
-rw-r--r--kmail/kmmainwidget.cpp3979
-rw-r--r--kmail/kmmainwidget.h564
-rw-r--r--kmail/kmmainwin.cpp222
-rw-r--r--kmail/kmmainwin.h80
-rw-r--r--kmail/kmmainwin.rc205
-rw-r--r--kmail/kmmessage.cpp4373
-rw-r--r--kmail/kmmessage.h916
-rw-r--r--kmail/kmmimeparttree.cpp395
-rw-r--r--kmail/kmmimeparttree.h125
-rw-r--r--kmail/kmmsgbase.cpp1483
-rw-r--r--kmail/kmmsgbase.h472
-rw-r--r--kmail/kmmsgdict.cpp651
-rw-r--r--kmail/kmmsgdict.h168
-rw-r--r--kmail/kmmsginfo.cpp700
-rw-r--r--kmail/kmmsginfo.h126
-rw-r--r--kmail/kmmsglist.cpp186
-rw-r--r--kmail/kmmsglist.h96
-rw-r--r--kmail/kmmsgpart.cpp602
-rw-r--r--kmail/kmmsgpart.h255
-rw-r--r--kmail/kmmsgpartdlg.cpp452
-rw-r--r--kmail/kmmsgpartdlg.h163
-rw-r--r--kmail/kmpopfiltercnfrmdlg.cpp470
-rw-r--r--kmail/kmpopfiltercnfrmdlg.h111
-rw-r--r--kmail/kmpopheaders.cpp75
-rw-r--r--kmail/kmpopheaders.h63
-rw-r--r--kmail/kmreadermainwin.cpp535
-rw-r--r--kmail/kmreadermainwin.h92
-rw-r--r--kmail/kmreadermainwin.rc75
-rw-r--r--kmail/kmreaderwin.cpp2767
-rw-r--r--kmail/kmreaderwin.h546
-rw-r--r--kmail/kmsearchpattern.cpp929
-rw-r--r--kmail/kmsearchpattern.h407
-rw-r--r--kmail/kmsearchpatternedit.cpp483
-rw-r--r--kmail/kmsearchpatternedit.h211
-rw-r--r--kmail/kmsender.cpp1231
-rw-r--r--kmail/kmsender.h174
-rw-r--r--kmail/kmsender_p.h151
-rw-r--r--kmail/kmservertest.cpp189
-rw-r--r--kmail/kmservertest.h88
-rw-r--r--kmail/kmstartup.cpp268
-rw-r--r--kmail/kmstartup.h44
-rw-r--r--kmail/kmsystemtray.cpp585
-rw-r--r--kmail/kmsystemtray.h91
-rw-r--r--kmail/kmtransport.cpp807
-rw-r--r--kmail/kmtransport.h169
-rw-r--r--kmail/kmversion.h8
-rw-r--r--kmail/korghelper.cpp53
-rw-r--r--kmail/korghelper.h32
-rw-r--r--kmail/kwindowpositioner.cpp60
-rw-r--r--kmail/kwindowpositioner.h47
-rw-r--r--kmail/listjob.cpp255
-rw-r--r--kmail/listjob.h146
-rw-r--r--kmail/localsubscriptiondialog.cpp148
-rw-r--r--kmail/localsubscriptiondialog.h67
-rw-r--r--kmail/mailcomposerIface.h53
-rw-r--r--kmail/maildirjob.cpp120
-rw-r--r--kmail/maildirjob.h61
-rw-r--r--kmail/mailinglist-magic.cpp414
-rw-r--r--kmail/mailinglist-magic.h85
-rw-r--r--kmail/mailinglistpropertiesdialog.cpp344
-rw-r--r--kmail/mailinglistpropertiesdialog.h87
-rw-r--r--kmail/mailserviceimpl.cpp135
-rw-r--r--kmail/mailserviceimpl.h74
-rw-r--r--kmail/mailsourceviewer.cpp84
-rw-r--r--kmail/mailsourceviewer.h71
-rw-r--r--kmail/main.cpp116
-rw-r--r--kmail/managesievescriptsdialog.cpp347
-rw-r--r--kmail/managesievescriptsdialog.h55
-rw-r--r--kmail/managesievescriptsdialog_p.h27
-rw-r--r--kmail/mboxjob.cpp124
-rw-r--r--kmail/mboxjob.h61
-rw-r--r--kmail/messageactions.cpp260
-rw-r--r--kmail/messageactions.h106
-rw-r--r--kmail/messagecomposer.cpp2284
-rw-r--r--kmail/messagecomposer.h279
-rw-r--r--kmail/messagecopyhelper.cpp115
-rw-r--r--kmail/messagecopyhelper.h79
-rw-r--r--kmail/messageproperty.cpp173
-rw-r--r--kmail/messageproperty.h112
-rw-r--r--kmail/messagesender.h97
-rwxr-xr-xkmail/mh2kmail27
-rwxr-xr-xkmail/mh2kmailr29
-rw-r--r--kmail/networkaccount.cpp367
-rw-r--r--kmail/networkaccount.h142
-rw-r--r--kmail/newfolderdialog.cpp311
-rw-r--r--kmail/newfolderdialog.h79
-rw-r--r--kmail/objecttreeparser.cpp2872
-rw-r--r--kmail/objecttreeparser.h295
-rw-r--r--kmail/partNode.cpp612
-rw-r--r--kmail/partNode.h259
-rw-r--r--kmail/partmetadata.h64
-rw-r--r--kmail/partnodebodypart.cpp98
-rw-r--r--kmail/partnodebodypart.h74
-rw-r--r--kmail/pics/Makefile.am37
-rw-r--r--kmail/pics/attachmentQuicklistClosed.pngbin0 -> 727 bytes
-rw-r--r--kmail/pics/attachmentQuicklistOpened.pngbin0 -> 719 bytes
-rw-r--r--kmail/pics/enterprise_bottom.pngbin0 -> 182 bytes
-rw-r--r--kmail/pics/enterprise_bottom_left.pngbin0 -> 264 bytes
-rw-r--r--kmail/pics/enterprise_bottom_right.pngbin0 -> 275 bytes
-rw-r--r--kmail/pics/enterprise_icon.pngbin0 -> 10638 bytes
-rw-r--r--kmail/pics/enterprise_left.pngbin0 -> 190 bytes
-rw-r--r--kmail/pics/enterprise_right.pngbin0 -> 184 bytes
-rw-r--r--kmail/pics/enterprise_s_left.pngbin0 -> 359 bytes
-rw-r--r--kmail/pics/enterprise_s_right.pngbin0 -> 362 bytes
-rw-r--r--kmail/pics/enterprise_sbar.pngbin0 -> 218 bytes
-rw-r--r--kmail/pics/enterprise_sp_right.pngbin0 -> 1101 bytes
-rw-r--r--kmail/pics/enterprise_sw.pngbin0 -> 2385 bytes
-rw-r--r--kmail/pics/enterprise_top.pngbin0 -> 187 bytes
-rw-r--r--kmail/pics/enterprise_top_left.pngbin0 -> 263 bytes
-rw-r--r--kmail/pics/enterprise_top_right.pngbin0 -> 266 bytes
-rw-r--r--kmail/pics/enterprise_w.pngbin0 -> 7223 bytes
-rw-r--r--kmail/pics/icons/Makefile.am2
-rw-r--r--kmail/pics/icons/cr128-action-online_status.pngbin0 -> 14284 bytes
-rw-r--r--kmail/pics/icons/cr16-action-kmgroupware_folder_calendar.pngbin0 -> 904 bytes
-rw-r--r--kmail/pics/icons/cr16-action-kmgroupware_folder_contacts.pngbin0 -> 1059 bytes
-rw-r--r--kmail/pics/icons/cr16-action-kmgroupware_folder_journals.pngbin0 -> 963 bytes
-rw-r--r--kmail/pics/icons/cr16-action-kmgroupware_folder_notes.pngbin0 -> 866 bytes
-rw-r--r--kmail/pics/icons/cr16-action-kmgroupware_folder_tasks.pngbin0 -> 1025 bytes
-rw-r--r--kmail/pics/icons/cr16-action-mail_flag.pngbin0 -> 745 bytes
-rw-r--r--kmail/pics/icons/cr16-action-mail_ham.pngbin0 -> 661 bytes
-rw-r--r--kmail/pics/icons/cr16-action-mail_ignore.pngbin0 -> 903 bytes
-rw-r--r--kmail/pics/icons/cr16-action-mail_spam.pngbin0 -> 930 bytes
-rw-r--r--kmail/pics/icons/cr16-action-mail_todo.pngbin0 -> 910 bytes
-rw-r--r--kmail/pics/icons/cr16-action-online_status.pngbin0 -> 894 bytes
-rw-r--r--kmail/pics/icons/cr22-action-kmgroupware_folder_calendar.pngbin0 -> 1215 bytes
-rw-r--r--kmail/pics/icons/cr22-action-kmgroupware_folder_journals.pngbin0 -> 1564 bytes
-rw-r--r--kmail/pics/icons/cr22-action-kmgroupware_folder_tasks.pngbin0 -> 947 bytes
-rw-r--r--kmail/pics/icons/cr22-action-mail_ham.pngbin0 -> 769 bytes
-rw-r--r--kmail/pics/icons/cr22-action-mail_ignore.pngbin0 -> 1364 bytes
-rw-r--r--kmail/pics/icons/cr22-action-mail_spam.pngbin0 -> 1502 bytes
-rw-r--r--kmail/pics/icons/cr22-action-online_status.pngbin0 -> 1396 bytes
-rw-r--r--kmail/pics/icons/cr32-action-kmgroupware_folder_calendar.pngbin0 -> 1155 bytes
-rw-r--r--kmail/pics/icons/cr32-action-kmgroupware_folder_tasks.pngbin0 -> 1358 bytes
-rw-r--r--kmail/pics/icons/cr32-action-mail_ham.pngbin0 -> 1393 bytes
-rw-r--r--kmail/pics/icons/cr32-action-mail_ignore.pngbin0 -> 2202 bytes
-rw-r--r--kmail/pics/icons/cr32-action-mail_spam.pngbin0 -> 2512 bytes
-rw-r--r--kmail/pics/icons/cr32-action-online_status.pngbin0 -> 2350 bytes
-rw-r--r--kmail/pics/icons/cr48-action-online_status.pngbin0 -> 4204 bytes
-rw-r--r--kmail/pics/icons/crsc-action-mail_ignore.svgzbin0 -> 2309 bytes
-rw-r--r--kmail/pics/icons/crsc-action-online_status.svgzbin0 -> 6038 bytes
-rw-r--r--kmail/pics/kmmsgattachment.pngbin0 -> 529 bytes
-rw-r--r--kmail/pics/kmmsgdel.pngbin0 -> 545 bytes
-rw-r--r--kmail/pics/kmmsgencryptionproblematic.pngbin0 -> 442 bytes
-rw-r--r--kmail/pics/kmmsgflag.pngbin0 -> 568 bytes
-rw-r--r--kmail/pics/kmmsgforwarded.pngbin0 -> 277 bytes
-rw-r--r--kmail/pics/kmmsgfullyencrypted.pngbin0 -> 430 bytes
-rw-r--r--kmail/pics/kmmsgfullysigned.pngbin0 -> 456 bytes
-rw-r--r--kmail/pics/kmmsgham.pngbin0 -> 438 bytes
-rw-r--r--kmail/pics/kmmsgignored.pngbin0 -> 589 bytes
-rw-r--r--kmail/pics/kmmsgnew.pngbin0 -> 481 bytes
-rw-r--r--kmail/pics/kmmsgpartiallyencrypted.pngbin0 -> 312 bytes
-rw-r--r--kmail/pics/kmmsgpartiallysigned.pngbin0 -> 515 bytes
-rw-r--r--kmail/pics/kmmsgqueued.pngbin0 -> 282 bytes
-rw-r--r--kmail/pics/kmmsgread.pngbin0 -> 517 bytes
-rw-r--r--kmail/pics/kmmsgread_fwd.pngbin0 -> 432 bytes
-rw-r--r--kmail/pics/kmmsgread_fwd_replied.pngbin0 -> 406 bytes
-rw-r--r--kmail/pics/kmmsgread_replied.pngbin0 -> 418 bytes
-rw-r--r--kmail/pics/kmmsgreplied.pngbin0 -> 291 bytes
-rw-r--r--kmail/pics/kmmsgsent.pngbin0 -> 291 bytes
-rw-r--r--kmail/pics/kmmsgsignatureproblematic.pngbin0 -> 446 bytes
-rw-r--r--kmail/pics/kmmsgspam.pngbin0 -> 660 bytes
-rw-r--r--kmail/pics/kmmsgtodo.pngbin0 -> 910 bytes
-rw-r--r--kmail/pics/kmmsgundefinedencrypted.pngbin0 -> 415 bytes
-rw-r--r--kmail/pics/kmmsgundefinedsigned.pngbin0 -> 430 bytes
-rw-r--r--kmail/pics/kmmsgunseen.pngbin0 -> 396 bytes
-rw-r--r--kmail/pics/kmmsgwatched.pngbin0 -> 616 bytes
-rw-r--r--kmail/pics/kmwizard.pngbin0 -> 24546 bytes
-rw-r--r--kmail/pics/kmwizard.svg1875
-rw-r--r--kmail/pics/pgp-keys.pngbin0 -> 4165 bytes
-rw-r--r--kmail/pics/quotecollapse.pngbin0 -> 1142 bytes
-rw-r--r--kmail/pics/quoteexpand.pngbin0 -> 1149 bytes
-rw-r--r--kmail/popaccount.cpp1086
-rw-r--r--kmail/popaccount.h242
-rw-r--r--kmail/profiles/Makefile.am14
-rw-r--r--kmail/profiles/profile-default-rc.desktop180
-rw-r--r--kmail/profiles/profile-high-contrast-rc.desktop164
-rw-r--r--kmail/profiles/profile-html-rc.desktop109
-rw-r--r--kmail/profiles/profile-purist-rc.desktop129
-rw-r--r--kmail/profiles/profile-secure-rc.desktop136
-rw-r--r--kmail/protocols.h45
-rw-r--r--kmail/quotajobs.cpp147
-rw-r--r--kmail/quotajobs.h194
-rw-r--r--kmail/recipientseditor.cpp998
-rw-r--r--kmail/recipientseditor.h366
-rw-r--r--kmail/recipientseditortest.cpp112
-rw-r--r--kmail/recipientseditortest.h44
-rw-r--r--kmail/recipientspicker.cpp878
-rw-r--r--kmail/recipientspicker.h248
-rw-r--r--kmail/redirectdialog.cpp140
-rw-r--r--kmail/redirectdialog.h92
-rw-r--r--kmail/regexplineedit.cpp136
-rw-r--r--kmail/regexplineedit.h79
-rw-r--r--kmail/renamejob.cpp253
-rw-r--r--kmail/renamejob.h95
-rw-r--r--kmail/replyphrases.kcfg35
-rw-r--r--kmail/replyphrases.kcfgc5
-rw-r--r--kmail/rulewidgethandlermanager.cpp1520
-rw-r--r--kmail/rulewidgethandlermanager.h100
-rw-r--r--kmail/scalix.cpp104
-rw-r--r--kmail/scalix.h83
-rw-r--r--kmail/searchjob.cpp454
-rw-r--r--kmail/searchjob.h136
-rw-r--r--kmail/searchwindow.cpp946
-rw-r--r--kmail/searchwindow.h195
-rw-r--r--kmail/secondarywindow.cpp81
-rw-r--r--kmail/secondarywindow.h63
-rw-r--r--kmail/sieveconfig.cpp170
-rw-r--r--kmail/sieveconfig.h128
-rw-r--r--kmail/sievedebugdialog.cpp410
-rw-r--r--kmail/sievedebugdialog.h94
-rw-r--r--kmail/sievejob.cpp289
-rw-r--r--kmail/sievejob.h129
-rw-r--r--kmail/signatureconfigurator.cpp268
-rw-r--r--kmail/signatureconfigurator.h82
-rw-r--r--kmail/simplestringlisteditor.cpp307
-rw-r--r--kmail/simplestringlisteditor.h106
-rw-r--r--kmail/smimeconfiguration.ui413
-rw-r--r--kmail/snippetconfig.cpp25
-rw-r--r--kmail/snippetconfig.h61
-rw-r--r--kmail/snippetdlg.cpp108
-rw-r--r--kmail/snippetdlg.h42
-rw-r--r--kmail/snippetdlgbase.ui177
-rw-r--r--kmail/snippetitem.cpp154
-rw-r--r--kmail/snippetitem.h85
-rw-r--r--kmail/snippetsettings.cpp53
-rw-r--r--kmail/snippetsettings.h46
-rw-r--r--kmail/snippetsettingsbase.ui184
-rw-r--r--kmail/snippetwidget.cpp957
-rw-r--r--kmail/snippetwidget.h95
-rw-r--r--kmail/spamheaderanalyzer.cpp157
-rw-r--r--kmail/spamheaderanalyzer.h90
-rw-r--r--kmail/stl_util.h47
-rw-r--r--kmail/subscriptiondialog.cpp444
-rw-r--r--kmail/subscriptiondialog.h156
-rw-r--r--kmail/teehtmlwriter.cpp98
-rw-r--r--kmail/teehtmlwriter.h71
-rw-r--r--kmail/templateparser.cpp1093
-rw-r--r--kmail/templateparser.h90
-rw-r--r--kmail/templatesconfiguration.cpp561
-rw-r--r--kmail/templatesconfiguration.h75
-rw-r--r--kmail/templatesconfiguration_base.ui332
-rw-r--r--kmail/templatesconfiguration_kfg.kcfg43
-rw-r--r--kmail/templatesconfiguration_kfg.kcfgc5
-rw-r--r--kmail/templatesinsertcommand.cpp402
-rw-r--r--kmail/templatesinsertcommand.h61
-rw-r--r--kmail/tests/Makefile.am23
-rw-r--r--kmail/tests/messagedicttests.cpp79
-rw-r--r--kmail/tests/messagedicttests.h30
-rw-r--r--kmail/tests/mimelibtests.cpp117
-rw-r--r--kmail/tests/mimelibtests.h44
-rw-r--r--kmail/tests/multipartmixed.mbox10
-rw-r--r--kmail/tests/signedmail.mbox111
-rw-r--r--kmail/tests/storagelayermodule.cpp16
-rw-r--r--kmail/tests/utiltests.cpp207
-rw-r--r--kmail/tests/utiltests.h30
-rw-r--r--kmail/textsource.cpp65
-rw-r--r--kmail/textsource.h46
-rw-r--r--kmail/tips78
-rw-r--r--kmail/transportmanager.cpp68
-rw-r--r--kmail/transportmanager.h42
-rw-r--r--kmail/undostack.cpp157
-rw-r--r--kmail/undostack.h72
-rwxr-xr-xkmail/upgrade-signature.pl63
-rwxr-xr-xkmail/upgrade-transport.pl36
-rw-r--r--kmail/urlhandlermanager.cpp597
-rw-r--r--kmail/urlhandlermanager.h88
-rw-r--r--kmail/util.cpp194
-rw-r--r--kmail/util.h222
-rw-r--r--kmail/vacation.cpp729
-rw-r--r--kmail/vacation.h89
-rw-r--r--kmail/vacationdialog.cpp195
-rw-r--r--kmail/vacationdialog.h82
-rw-r--r--kmail/vcardviewer.cpp96
-rw-r--r--kmail/vcardviewer.h58
-rw-r--r--kmail/warningconfiguration.ui408
-rw-r--r--kmail/xfaceconfigurator.cpp285
-rw-r--r--kmail/xfaceconfigurator.h64
578 files changed, 162849 insertions, 0 deletions
diff --git a/kmail/COMMITPOLICY b/kmail/COMMITPOLICY
new file mode 100644
index 00000000..5ebb56a0
--- /dev/null
+++ b/kmail/COMMITPOLICY
@@ -0,0 +1,13 @@
+KMail policy is decided by the KMail decision making group according
+to the KMail decision making process, as was decided on the kde-core
+development list and is reflected by the state of the KMail about
+box.
+
+The KMail decision making group consists of all KMail core developers,
+including maintainers. The decision making process is as follows:
+
+In the result of a conflict over a commit to the KMail section of KDE
+CVS a resolution to this conflict, that is the decision to keep or
+revert the commit can be decided by a unanimous agreement of the
+maintainers, or failing that a resolution to the conflict will be
+decided by a vote of KMail core developers.
diff --git a/kmail/ChangeLog b/kmail/ChangeLog
new file mode 100644
index 00000000..e9c2eaa1
--- /dev/null
+++ b/kmail/ChangeLog
@@ -0,0 +1,806 @@
+
+NOTE: This file is out of date. For a detailed list of changes
+please visit http://webcvs.kde.org
+
+2000-05-17 George Staikos <staikos@kde.org>
+ * Updated the authors list
+ * Added Stefan's external filter code and modified it somewhat
+ * bugfixes on size parameter again.
+
+2000-05-14 George Staikos <staikos@0wned.org>
+
+ * Forward now forwards ALL headers if ALL headers are set
+
+2000-05-12 George Staikos <staikos@0wned.org>
+
+ * Added an optional column for message size
+ * Created a context menu item for "Save As"
+ * Fixed various typos
+ * Created welcome message on inbox creation
+
+2000-03-25 Sven Radej <radej@kde.org>
+
+ * Removed old signal-driven "IPC" and introduced DCOP interface
+ kmailIface.h gets installed in include directory
+ * new class KMKernel, holds all previously static objects
+ (to access them, instead of "yourObject->something()" use
+ "kmkernel->yourObject()->something()"
+
+2000-02-28 Mario Weilguni <mario@weilguni.net>
+
+ * Corrected wrong shortcut in menu "File/&Filter"
+ * Added mini-icon to several subwindows of kmail
+ * Removed those ugly password dialogs with some
+ more modern and style one.
+
+2000-02-02 George Staikos <staikos@0wned.org> (KMail-1.1.35)
+ Mail Sending
+ * External editor support
+ * Confirm before send
+ * Attach custom mime headers
+
+ POP3
+ * Settings entry for the POP3 timer
+
+ Security
+ * Changed tpmnam to mkstemp
+
+ General
+ * New mail received notification
+ * Sigfile editor
+
+2000-02-02 Jacek Stolarczyk <jacek@mer.chemia.polsl.gliwice.pl>
+ Settings
+ * Ported createPushButton, createLabeledEntry and addLabeledWidget
+ from char* to QString&. It was causing problems for passing
+ already internationalized strings. In cPB i18n() was unnecessarily
+ called for the second time.
+
+1999-12-15 Don Sanders <sanders@kde.org> (KMail-1.1.32)
+
+ General
+ * Cache number of unread messages
+ * Added auto synchronization logic for correcting count of unread
+ messages.
+ * Added support for customizing colors in all three panes. (Currently
+ have to restart kmail for this to take effect due to HTML widget
+ renovations).
+ * Added support for customizing fonts and font sizes in all three
+ panes. (Currently have to restart kmail for this to take effect due
+ to HTML widget renovations). Fixed width fonts starting working too.
+ * Ported all the changes made to KMail 1.0.x.
+ * Ported KMail to QT 2.1 and KDE 2 kdelibs (that is it runs and works
+ now rather than just compiling)
+
+ Privacy
+ * Committed patch by "J. Nick Koston" <bdraco@darkorb.net> to add
+ support for GNU Privacy Guard.
+
+ Filter Dialog
+ * Various buttons are enabled/disabled depending on whether they can
+ be used.
+ * Filter title is updated as the user enters filter details
+ * Fix a bug where changes could be lost when changing priority of
+ new filter.
+ * List of folders supports nested folders by indentation.
+
+ Folder Tree Window
+ * Nested folder support.
+ * Improved DnD support (hover opening, auto scrolling, highlight
+ destination folder while dragging).
+ * Show count of unread messages in different color after the folder
+ name.
+
+ Headers Window
+ * Ported kmheaders to QListView.
+ * Sorting is now done by QListView, this means the index file on
+ doesn't have to be updated, all sorting is done in main memory
+ (much faster). This also seems to have eliminated the lingering
+ problem of missorted mail.
+ * Implemented support for Shift and Ctrl selection and DnD.
+ * Show DnD cursor while dragging even in headers window.
+ * Eliminated flicker when changing folder.
+ * Speed up folder changing by reusing QListViewItems.
+ * When sorting always use the date as the second priority sorting key
+ * Added indicator in header for current sort column and direction.
+ * Eliminated needless gui updating when changing status, copying or
+ moving messages. This speeds up these operations a lot.
+ * Fixed problem with different date format being shown for current
+ message.
+
+ Reader Window
+ * Committed patched by Daniel Naber <dnaber@mini.gt.owl.de> that
+ fixes problem with messges with attachments and no text part.
+ * Lars Knoll ported the Reader Window to the new html widget. This
+ widget is still under construction (somethings haven't been
+ reimplemented yet)
+ * Show messages of type text/html as html (For Wired news etc).
+ * Added slight delay to updating reader window and so that holding
+ down the next/prev message key wouldn't cause the header window to
+ become out of sync with the reader window.
+
+ Folder Settings Dialog
+ * Simplified by removing account related stuff.
+ * Added support for reparenting via combobox.
+
+ Pixmaps
+ * Converted the pixmaps to png (expect for kdelogo which wasn't
+ looking right).
+ * New folder pixmaps contributed by
+ Greg Newton <gregnewton@netscape.net>
+
+ Composer Window
+ * Added undo/redo menu items. (Sent patch to support undo/redo in
+ QMultiLineEdit to Trolls)
+ * Updated KMail to use the new improved word wrapping in QMultiLineEdit
+ (no more signature munching).
+ * Set date of sent messages to current sent time rather than creation
+ time.
+
+ Message class
+ * Worked with Jacek Stolarczyk <jacek@mer.chemia.polsl.gliwice.pl> to
+ fix bug that was preventing KMail from working on Alpha Architecture.
+
+ Folder class
+ * Added extra protection so mail won't be lost if the disk was full.
+ * Fixed problem with unfiltered mail being lost with the help of
+ Bob Bernstein <ruptured-duck@home.com> and dep <dep@snet.net>
+
+1999-12-04 Stefan Taferner <taferner@kde.org> (KMail-1.1.31)
+
+ * Implemented bouncing of mails. Hopefully this will help
+ to fool spammers ;-)
+
+1999-10-10 Stefan Taferner <taferner@kde.org> (KMail-1.1.25)
+
+ * Switched folder-tree widget to QListView. Currently
+ drag&drop of messages is therefore broken.
+ * Added support for subdirectories in ~/Mail -> hierarchical
+ folders!
+ * Changed ancient folder edit/create dialog to something
+ more useful.
+
+1999-02-21 Stefan Taferner <taferner@kde.org>
+
+ * Added extra ungrabbing of pointer and keyboard to avoid
+ locking problems when many message windows pop up.
+
+1999-02-10 Stefan Taferner <taferner@kde.org> (KMail-1.1.1)
+
+ * Bugfix: new/unread messages that are displayed upon selecting
+ of a folder are now properly displayed as read.
+
+ * Bugfix: filter-dialog: up/down moving (esp. of last entry)
+ now works properly
+
+1999-02-06 Stefan Taferner <taferner@kde.org> (KMail-1.1.0)
+
+ * Added missing parts of documentation for proper install.
+
+1999-02-06 Sven Radej <radej@kde.org> (KMail-1.0.17)
+
+ * Server-client method - no more locking problems, lost instead
+ of sent mail, ghost messages...
+ Do the right thing when sending to "me@there (John Doe)"
+ Waldo's folderless-acount-segv-fix
+ More against destroyed last char in message
+ Better long/normal folder-list geometry bugfix
+ Don't quit while sending, and show a label while you send.
+
+1999-02-03 Sven Radej <radej@kde.org> (KMail-1.0.16)
+
+ * Bugfix: possible segv-on-create-folder. Reciever is properly
+ shown (index design & version updated) in sent&queued mail.
+ Fix agains overwriting last char in last message when appending
+ new one to folder.
+
+1999-01-30 Stefan Taferner <taferner@kde.org> (KMail-1.0.16)
+
+ * Removed obsolete / commented-out code.
+
+ * Bugfix: KMail now properly handles the 'filename' attribute
+ of message parts in the reader (Bug #445).
+
+1999-01-29 Stefan Taferner <taferner@kde.org> (KMail-1.0.15)
+
+ * Bugfix: disabled deleting of system folders.
+
+ * Removed save button in composer which is still not
+ implemented ;->
+
+ * Bugfix: disabled renaming of system folders (inbox, outbox,
+ sent-mails, trash).
+
+ * Fix: improved performance when displaying messages (Bug #148).
+
+ * Bugfix: fixed crash when dropping message on directory
+ entry in folder list.
+
+ * Improved: email completion of composer (Ctrl-t) now
+ searches for appearance of given characters in addressbook's
+ list. Up to now only used beginning of string.
+
+ * Added missing i18n() around "Spellcheck complete" in
+ composer.
+
+ * Bugfix: Added %_ in composer settings, e.g. for indentation
+ template, to add a space at the end. Added %f which expands
+ to the two first characters of the name in the email address.
+
+ * Bugfix: Indentation template (see composer settings) now
+ supports the same wildcards as the other fields, e.g. "%f>%_"
+
+1999-01-28 Stefan Taferner <taferner@kde.org>
+
+ * Bugfix: kmail hangs when sending large messages via
+ SMTP. Actually was a performance issue with QRegExp.
+
+ * Bugfix: main window was hidden upon press of Ok button
+ in settings dialog.
+
+1999-01-25 Stefan Taferner <taferner@kde.org>
+
+ * Bugfix: 'From' column did not update new text at
+ first.
+
+1999-01-23 Stefan Taferner <taferner@kde.org> (KMail-1.0.14)
+
+ * New feature: layout can now be switched between
+ "long folder list" and "short folder list". Option
+ setable in settings->appearance.
+
+ * New feature: the "From" column now contains the
+ receiver for the folders outbox and sent-mail. Column
+ "Sender" automatically switches it's title to "Receiver"
+ for these folders.
+
+ * Incorporated new documentation.
+
+1999-01-21 Stefan Taferner <taferner@kde.org> (KMail-1.0.13)
+
+ * Fixed broken placement of contents in settings dialog.
+
+ * Filter Dialog: changed combo box style to new style.
+ Now it is possible to have more folders than the screen
+ is high and still select them in the filter dialog.
+ * Filter Dialog: changed folder combo box to (semi) auto
+ resize. Changed layout of dialog to better display all
+ elements. Also enabled vertical resizing of dialog.
+
+1999-01-17 Stefan Taferner <taferner@kde.org> (KMail-1.0.12)
+
+ Patches from Michael Teske <mteske@c-s-k.de>:
+ * Fixed broken retrieve-all for pop accounts.
+ * Fixed broken marking of new mails as New.
+
+1999-01-01 Stefan Taferner <taferner@kde.org> (KMail-1.0.11)
+
+ * Hopefully pgp zombies are fixed now.
+ * Applied patch for fixing problems with some inline
+ encoding switchings.
+
+1998-12-21 Stefan Taferner <taferner@kde.org>
+
+ Applied several patches from Lars Heete <hel@admin.de>:
+ * missing deletion of drop zone in composer destructor
+ * handling of NULL status field in message base
+ * reading process status of finished pgp process to avoid
+ zombies ;-)
+ * workaround for "Could not load..." warnings, that should
+ rather be debug messages, in message handler
+
+1998-12-20 Stefan Taferner <taferner@kde.org> (KMail-1.0.10)
+
+ * Fixed possible crash when subject is empty.
+
+1998-12-17 Juraj Bednar <bednar@isternet.sk>
+
+ * Added the capatability to insert arbitary public keys from public
+ keyring. (PGP)
+
+1998-12-16 Stefan Taferner <taferner@kde.org> (KMail-1.0.9)
+
+ * Switched busy pointer (if not animated) and hand cursor
+ to new KCursor class.
+
+ * Headers: status of message is now set to read also for
+ messages that are displayed when the folder is opened.
+
+1998-12-07 Stefan Taferner <taferner@kde.org>
+
+ * Updated documentation
+
+1998-12-06 Stefan Taferner <taferner@kde.org>
+
+ * Added i18n() around label in folder list.
+
+1998-12-03 Stefan Taferner <taferner@kde.org> (KMail-1.0.8)
+
+ * New messages: added organization to message header
+ if given.
+
+ * Headers: position of top message in folder list is
+ stored and reused upon next opening. Exception: if there
+ is a new or unread message in the folder, then the
+ first new/unread message is shown.
+
+ * Composer: fixed missing last character in replies.
+
+ * Composer: attachments are now included in forwards.
+
+1998-11-24 Stefan Taferner <taferner@kde.org>
+
+ * Pop: does not stop pop retrieval now when pop server does
+ not understand LAST command.
+
+1998-11-15 Stefan Taferner <taferner@kde.org> (KMail-1.0.7)
+
+ * Headers: now current message is not reset when reading mails
+ and checking for new messages meanwhile.
+
+ * Pop: added configuration option for leave-mail-on-server.
+
+ * Pop: Fixed problems in pop authentication code.
+
+ * Pop: Enabled POP3 LAST command which was committed to
+ kdesupport some time ago.
+
+1998-11-15 Alex Zepeda <garbanzo@hooked.net>
+
+ * kfontutils.cpp (kfontToStr): Cast two const char *'s to char *'s as
+ egcs seems to barf without the casts.
+
+1998-11-10 Harri Porten <porten@kde.org>
+
+ * Removed hardcoded localkdedir()
+
+ * Let KApplication() do the job of creating ~/.kde/share/config
+
+1998-10-31 Stefan Taferner <taferner@kde.org> (KMail-1.0.6)
+
+ * Fixed: crash when fetching new mail and inbox folder
+ is not accessible.
+
+1998-10-21 Lars Knoll <knoll@mpi-hd.mpg.de>
+ * fixed a bug in the support for pgp-2.6
+
+1998-10-08 Stefan Taferner <taferner@kde.org> (KMail-1.0.5)
+
+ * Sorting messages: replaced sorting algorithm, which caused
+ random hangs, with stock qsort -- hopefully working now ;-)
+
+ * Fixed: folder list went out of scope (current item pointing to
+ nowhere) when folder was deleted.
+
+ * Composer: fixed broken confirm-close dialog.
+
+ * Increased version to better match upcoming Kde-1.1 release.
+
+ * Added call to kapp->quit() when last window is closed.
+
+1998-10-05 Lars Knoll <knoll@mpi-hd.mpg.de>
+ * Added new pgp classes. They should fix a lot of problems
+ with the current support of pgp5.0 and pgp2.6, and fix all
+ security problems with the old versions of kpgp, since all
+ comunication with pgp (including the passphrase) is done via
+ pipes.
+
+1998-09-26 Stefan Taferner <taferner@kde.org> (KMail-0.8.1)
+
+ * Fixed crash upon close of a main- or composer window.
+ Bug seems to be introduced by changed behaviour of KTMainWindow.
+ Still KMail crashes when you click on any message afterwards.
+
+ * Addressbook: finally fixed bug in not reading last line.
+ Thanks to all who reported!
+
+ * Replaced occurrences of KTopLevelWidget with KTMainWindow.
+
+1998-09-21 Markus Wuebben <markus@office.DInet.de>
+
+ * added a short cut for mail checking
+
+1998-09-12 Markus Wuebben <markus@kde.org>
+
+ * some nice little fixes
+
+1998-09-11 Juraj Bednar <bednar@isternet.sk>
+
+ * finally wrote PGP replying and forwarding decryption
+ * PGP 5.0i should work now with no problems (should ;-)
+
+Sun Sep 6 19:20:34 1998 Markus Wuebben <mason@HoneyBunny.uni-dortmund.de>
+
+ * kmacctpop.cpp (setPasswd): Fixed password problem.
+
+Sun Sep 6 18:28:45 1998 Markus Wuebben <markus@kde.org>
+
+ * kmreaderwin.cpp : Finally fixed attachment problem
+
+1998-07-31 Markus Wuebben <markus@kde.org>
+
+ * kmsettings.cpp / kmmainwin.cpp Implemented send mail on check.
+ * kmacctlocal.cpp (processNewMail): Added message box
+ warning if mail folder was not found.
+
+ * kmmainwin.cpp Added editing of message in outbox folder.
+ * kmfolderdlg.cpp Removed out of index warning
+
+
+1998-07-23 Stefan Taferner <taferner@kde.org>
+
+ * Added message status "Read". This status is set to
+ messages which are loaded with check-mail that have
+ status set to "R".
+
+ These messages appear whith blue text, but without
+ the green ball to the left, in the message list.
+
+1998-07-22 Markus Wuebben <markus@kde.org>
+
+ * main.cpp Applied session management patch.
+ * kmfolder.cpp Applied fseek patch for solaris
+
+1998-07-03 Stefan Taferner <taferner@kde.org>
+
+ * PGP: fixed crash when no ~/.pgp/pubring.pgp is installed
+ and signed message is selected.
+
+1998-06-30 Stefan Taferner <taferner@kde.org> (KMail-0.7.9)
+
+ * PGP: removed dialog asking for pass phrase when only
+ signed message was given, added missing asking for pass
+ phrase when encrypted/signed message is sent.
+
+ * Composer: fixed bug that showed busy pointer all the
+ time with auto-append-signature set and no signature file
+ given.
+
+ * Composer: removed mSendImmediate which seems unused
+ in the composer nowadays. Simplified code of slotSend,
+ slotSendNow, and slotSendLater (to be easier consistent).
+
+1998-06-29 Stefan Taferner <taferner@kde.org> (KMail-0.7.8)
+
+ * Config: writeConfig() was not called when windows
+ got closed. Now it is.
+
+1998-06-27 Markus Wuebben <markus@kde.org>
+
+ * kmcomposewin.cpp (applyChanges): add false to applyChanges to
+ avoid segfault on ALPHAs.
+
+1998-06-24 Stefan Taferner <taferner@kde.org>
+
+ * Pgp detection simplified and bugfixed.
+
+ * Folder status messages for index creation and
+ compacting folder reduced from every 100th to every 10th.
+
+1998-06-23 Markus Wuebben <markus@kde.org> (KMail-0.7.7)
+
+ * removed various unnecessary assert()s. and replaced them
+ with if()s. Especially in functions that return void this
+ is very helpfull and makes kmail more stable.
+
+ * Checked return values with if()s for some functions to
+ make sure that we run into to trouble with those problems.
+
+1998-06-23 Stefan Taferner <taferner@kde.org> (KMail-0.7.7)
+
+ * Removed lots of old debug messages.
+
+ * Composer: answering the confirmation is no longer
+ ignored.
+
+ * Finetuning: added missing pixmaps to makefile and
+ repainted pixmap for queued and sent messages.
+
+ * Composer: changed behaviour of email completion a bit.
+ Now it is possible to add multiple recipients with comma
+ separated and complete also those after the first one.
+ Drawback: email addresses with comma in them are not
+ handled properly currently.
+
+ * Pop leave-on-server: due to the fact that we need another
+ field in the pop settings dialog "download-all-msgs"
+ the leave-on-server checkbox currently also toggles
+ the download-all-msgs feature.
+ So: download-all-msgs = !leave-on-server
+
+ * Folders are now marked red after download if they
+ contain new or unread messages. As a feature, folders
+ that receive old messages are not marked.
+
+ * Fixed problems in pop code downloading old messages
+ also. The "download" dialog is a bit misleading because
+ it should say now "checking message:" instead of
+ "downloading message:" because old messages are counted
+ here also.
+
+1998-06-22 Markus Wuebben <markus@kde.org> (KMail-0.7.6)
+
+ * fixed a whole bunch of stuff that made kmail segfault.
+
+1998-06-21 Mario Weilguni <mweilguni@kde.org>
+
+ * small fix for quicker response in kmnewiostatus.cpp
+
+ * "Delete folder" dialog now defaults to "No" (compliant to
+ KDE standards)
+
+1998-06-20 Markus Wuebben <markus@kde.org>
+
+ * kmsender.cpp (cleanup): Fixed the sendQueued problem.
+ If only one message from the outbox couldnt get sent all
+ following messages got deleted from the outbox. This is fixed now.
+
+
+1998-06-19 Stefan Taferner <taferner@kde.org> (KMail-0.7.6)
+
+ * Folder: fixed broken locking on systems without
+ flock(). Thanks to Werner Ertle <wer@christl.hl.siemens.de>
+ who sent me the patch.
+
+1998-06-18 Stefan Taferner <taferner@kde.org> (KMail-0.7.5)
+
+ * Deleting folders crashed kmail. Fixed.
+
+ * Folder-compacting: added busy pointer and progress
+ output in the status bar (idea from Mario).
+
+ * Reader: removed ':' from the characters that mark
+ quoted text to catch smileys at the beginning of the
+ line ;-)
+
+ * Main window configuration (geometry, etc.) is now
+ stored again.
+
+ * Composer: changed completion key from Ctrl-. to Ctrl-T
+ as kfile has.
+
+1998-06-18 Mario Weilguni <mweilguni@kde.org>
+
+ * Dialog for folder selection: Escape now closes dialog,
+ remembers last folder, has a default pushbutton
+
+1998-06-17 Stefan Taferner <taferner@kde.org> (KMail-0.7.4)
+
+ * Message-list: now the first unread/new message is
+ displayed when opening a folder.
+
+ * Message-status: now messages change status from
+ "new" to "unread" when the user changes folder (and
+ not when the folder is closed somewhere within KMail).
+
+ * Reader: worked around problem of current HTML widget
+ with long lines (without '\n')
+
+1998-06-16 Stefan Taferner <taferner@kde.org> (KMail-0.7.3)
+
+ * Headers: Improved switching between folders to do
+ less paints to avoid flickering where possible.
+
+ * Warnings: changed warning messages of QPixmap and
+ QPainter to debug messages to avoid lots of message
+ boxes from broken KToolbar code when changing color
+ scheme.
+
+ * Composer (email-address-completion)
+ Changed email-address completion to be case insensitive.
+ I think this is more useful in the general case.
+
+ * Composer: fixed broken Tab handling. Replaced
+ Tab-completion in the email-address header lines
+ (To, From, Cc, etc.) with Ctrl-. completion.
+
+ * Filters: removed some debug statements that slowed
+ down filtering a lot.
+
+ * kmmsgbase.cpp: added isUnread() method.
+
+ * Message-list: improved next/prev_unread_message methods.
+ Improved code that ensures that current item is visible
+ and added it to several places in the headers code.
+
+1998-06-15 Stefan Taferner <taferner@kde.org>
+
+ * kmfilteraction.cpp (process): Removed patch for kmfilter
+ action "Move" of 1998-06-14.
+ Fixed bug in kmfilteraction "Move" action.
+
+ * Fonts: reader- and composer-window now change
+ their font when the settings are changed.
+
+ * Shutdown: improved handling of window-close and
+ KMail shutdown- and crash-handling.
+
+
+1998-06-14 Markus Wuebben <markus@kde.org>
+
+ * kmfilteraction.cpp (process): Applied patch for kmfilter
+ actionmove.
+
+1998-06-13 Mario Weilguni <mweilguni@kde.org> (KMail-0.7.2)
+
+ * key for next_unread_message and prev_unread_message added
+
+1998-06-12 Stefan Taferner <taferner@kde.org>
+
+ * Folder loading: moved status message in index
+ creation to a more suitable place. Should be lots
+ faster now.
+
+ * Composer: Now path of last filedialog for attachments
+ is stored.
+
+ * Fonts: improved font settings. The HTML widget
+ unfortunately only honors the font family. Added new
+ functions (kfontutils.h) for easy font to/from string
+ conversion.
+
+1998-06-12 Mario Weilguni <mweilguni@kde.org> (KMail-0.7.1)
+ * new sort algorithm - quicksort instead of bubble-sort
+
+1998-06-11 Stefan Taferner <taferner@kde.org> (KMail-0.7.0)
+
+ * Initial start now opens the settings dialog. Don't
+ be alerted, this will happen once for everybody now ;-)
+
+ * Fonts: implemented font setting for HTML widget,
+ list of messages, and composer.
+
+ * Drag&Drop: implemented drag&drop of messages to
+ folders. Dropping into HTML widget crashes KMail.
+ Could be a bug of the HTML widget, however.
+
+ * Move-message: improved folder selection dialog.
+
+ * Message-list: now cleared properly when the current
+ folder is deleted.
+
+1998-06-11 Markus Wuebben <markus@kde.org> (KMail-0.6.9)
+
+ * KMail now uses KFileDialog only!
+
+1998-06-11 Mario Weilguni <mweilguni@kde.org> (KMail-0.6.9)
+
+ * "P" and "N" (next and previous mail) will cause the header
+ list to be scrolled if the new item is not visible
+
+ * fixed some code to prevent compiler warning
+
+ * panner position is now (again) restored
+
+1998-06-07 Stefan Taferner <taferner@kde.org> (KMail-0.6.8)
+
+ * Folders: changed message about index-recreation
+ to debug from warning.
+
+ * Message/Headers: Email addresses like <joe@home.org>
+ (with the <> around the address) no longer lead to
+ an empty field in the message list.
+
+ * Headers: Removed extra space in header line(s).
+
+ * Headers: Empty Subject moved From and Date one column
+ left. Fixed (hopefully).
+
+ * Composer: Send again did not decode quoted-printable
+ sent messages. Fixed.
+
+ * PGP: KMail did not ask for mantra upon first reading of
+ an encrypted message. Applied patch, from Michael Vogel
+ <icarus@hades.pop-celle.de>, which hopefully works :-)
+
+1998-05-13 Stefan Taferner <taferner@kde.org> (KMail-0.6.7)
+
+ * Filters: Bugfix: when deleting a folder that was the target
+ of a filter the next get-mails crashed.
+
+ * Filter Dialog: Deleting filter rules did not work. Fixed.
+
+1998-05-09 Markus Wuebben <markus@kde.org>
+
+ * kmsettings.cpp (KMAccountSettings):
+ Fixed "Delete mail from server". The row was too low.
+ * kmmainwin.cpp
+ Added a single account mail check option.
+
+1998-04-29 Markus Wuebben <markus@kde.org>
+
+ * Reader: Fixed the url parsing which made kmail
+ loop.
+
+1998-04-28 Stefan Taferner <taferner@kde.org> (KMail-0.6.6)
+
+ * Folders: Emptying of the trash folder resulted in an infinite
+ loop. Fixed now.
+
+1998-04-04 Stefan Taferner <taferner@kde.org> (Kde Beta-4)
+
+ * Filters: Removed warning dialog. Filters are working ok for
+ me for several weeks now.
+
+ * Folders: After I finally also deleted lots of important messages
+ by mistake I now changed the behaviour of Folder->Empty to
+ move the messages into trash instead of deleting them ;-)
+ This made the "Are you sure" dialog now obsolete, IMO.
+
+1998-03-31 Stefan Taferner <taferner@kde.org> (KMail-0.6.5)
+
+ * Folders: fixed bug introduced on 1998-03-26: open failed on
+ folders where lock() returned an error.
+
+1998-03-28 Stefan Taferner <taferner@kde.org>
+
+ * Headers: when opening a folder the contents was not automatically
+ sorted, now is.
+
+ * Pop: new messages retrieved from pop accounts were not
+ marked new. Now fixed.
+
+1998-03-26 Stefan Taferner <taferner@kde.org>
+
+ * Folders: when opening and locking a folder there were
+ cases (in fact most of the time) when the folder seemed
+ to be not locked but was. Now this is handled better.
+
+1998-03-24 Stefan Taferner <taferner@kde.org> (KMail-0.6.4)
+
+ * Message: decoding of quoted-printable RFC1522 strings
+ was broken for strings that started with a decoded character.
+
+1998-03-18 Stefan Taferner <taferner@kde.org>
+
+ * Reader: improved detection of urls and email addresses
+ to ignore special characters at the end, e.g. "." or ")".
+
+ * Messagelist: finally fixed sorting of messages. Also
+ implemented ascending/descending/none sorting (use multiple
+ clicks on the column headers to switch). Sorting order "none"
+ is what was IMO missing for the trash folder :-)
+
+1998-03-17 Stefan Taferner <taferner@kde.org> (KMail-0.6.3)
+
+ * Composer: in menu view, when 'all headers' view is
+ activated, then the individual header line menu entries are
+ disabled now.
+
+ * Folders: changed creation of folders to -rw-------
+ instead of the default -rw-r--r--. Same for the initial
+ creation of ~/Mail.
+
+ * Attachments: temporary files (when viewing attachments)
+ are now also created with permissions -rw-------.
+
+1998-03-15 Stefan Taferner <taferner@kde.org> (KMail-0.6.2)
+
+ * Startup: added option -check which does mail checking
+ upon startup. Also options that start with a '-' but which
+ are unknown are now skipped.
+
+1998-03-09 Stefan Taferner <taferner@kde.org> (KMail-0.6.1)
+
+ * Reader: '_' was not considered part of a smart-detected email
+ address (auto detection of @).
+
+ * Reader: attachments of type message/rfc822 are now shown
+ in an external reader window if open or view from the attachment
+ popup menu is chosen.
+
+ * Composer: finally got insertion of tabs into message
+ working.
+
+(many changes are missing here, sorry)
+
+1998-02-14 Stefan Taferner <taferner@kde.org>
+
+ * Startup: when recovering dead letters the auto signature was
+ appended twice. Fixed now.
+
+ * Composer: fixed broken inserting of files.
+
+ * kFileToString(): fixed handling of files with zero length.
+
+Initial version: 0.5.7
diff --git a/kmail/DESIGN b/kmail/DESIGN
new file mode 100644
index 00000000..0d2c93c4
--- /dev/null
+++ b/kmail/DESIGN
@@ -0,0 +1 @@
+The contents of this file are now to be found and added to in Mainpage.dox.
diff --git a/kmail/KMail.desktop b/kmail/KMail.desktop
new file mode 100644
index 00000000..52e31f0e
--- /dev/null
+++ b/kmail/KMail.desktop
@@ -0,0 +1,93 @@
+[Desktop Entry]
+Name=KMail
+Name[eo]=Retpoŝto
+Name[hi]=के-मेल
+Name[lv]=KPasts
+Name[mk]=КПошта
+Name[ne]=केडीई मेल
+Name[pa]=ਕੇ-ਪੱਤਰ
+Name[sv]=Kmail
+Name[ta]=Kஅஞ்சல்
+Name[th]=จัดการจดหมาย - K
+Name[zh_TW]=KMail 郵件軟體
+Type=Application
+Exec=kmail -caption "%c" %i %m
+Icon=kmail
+DocPath=kmail/index.html
+GenericName=Mail Client
+GenericName[af]=E-pos kliënt
+GenericName[ar]=زبون البريد
+GenericName[az]=Poçt Alıcısı
+GenericName[be]=Паштовы кліент
+GenericName[bg]=Пощенски клиент
+GenericName[br]=Kliant postel
+GenericName[bs]=Program za čitanje elektronske pošte
+GenericName[ca]=Client de correu
+GenericName[cs]=Klient pro čtení elektronické pošty
+GenericName[cy]=Dibynnydd Ebost
+GenericName[da]=E-mail-klient
+GenericName[de]=Mail-Programm
+GenericName[el]=Πελάτης mail
+GenericName[eo]=Legi kaj sendi retpoŝton
+GenericName[es]=Cliente de correo
+GenericName[et]=E-posti klient
+GenericName[eu]=Posta bezeroa
+GenericName[fa]=کارخواه‌نامه
+GenericName[fi]=Sähköpostiohjelma
+GenericName[fr]=Logiciel de messagerie électronique
+GenericName[fy]=E-portclient
+GenericName[ga]=Cliant Ríomhphoist
+GenericName[gl]=Cliente de correo
+GenericName[he]=לקוח דוא"ל
+GenericName[hi]=डाकिया
+GenericName[hr]=Program za čitanje elektronske pošte
+GenericName[hu]=Levelezőprogram
+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
+GenericName[lv]=Pasta Klients
+GenericName[mk]=Клиент за електронска пошта
+GenericName[ms]=Pelanggan Mel
+GenericName[mt]=Klijent tal-imejl
+GenericName[nb]=E-postklient
+GenericName[nds]=Nettpostprogramm
+GenericName[ne]=मेल क्लाइन्ट
+GenericName[nl]=E-mailclient
+GenericName[nn]=E-postklient
+GenericName[pa]=ਪੱਤਰ ਕਲਾਂਇਟ
+GenericName[pl]=Program pocztowy
+GenericName[pt]=Cliente de E-mail
+GenericName[pt_BR]=Cliente de E-mail
+GenericName[ro]=Program de poştă electronică
+GenericName[ru]=Почтовый клиент
+GenericName[rw]=Umukiriya w'Ubutumwa
+GenericName[se]=E-boastaprográmma
+GenericName[sk]=Poštový klient
+GenericName[sl]=Poštni odjemalec
+GenericName[sr]=Поштански клијент
+GenericName[sr@Latn]=Poštanski klijent
+GenericName[sv]=E-postklient
+GenericName[ta]=அஞ்சல் பயனர்
+GenericName[tg]=Клиенти почтавӣ
+GenericName[th]=ไคลเอนต์จดหมายอิเล็กทรอนิกส์
+GenericName[tr]=E-posta İstemcisi
+GenericName[uk]=Поштовий клієнт
+GenericName[uz]=Xat-xabar klienti
+GenericName[uz@cyrillic]=Хат-хабар клиенти
+GenericName[ven]=Mushumisani na poso
+GenericName[xh]=Umxhasi Weposi
+GenericName[zh_CN]=邮件客户程序
+GenericName[zh_TW]=收發信軟體
+GenericName[zu]=Imeyili Yomthengi
+Terminal=false
+
+X-KDE-StartupNotify=true
+X-DCOP-ServiceType=Unique
+X-DCOP-ServiceName=kmail
+ServiceTypes=DCOP/ResourceBackend/IMAP,DCOP/Mailer
+Categories=Qt;KDE;Network;Office;Email;
diff --git a/kmail/Mainpage.dox b/kmail/Mainpage.dox
new file mode 100644
index 00000000..0b15ebfb
--- /dev/null
+++ b/kmail/Mainpage.dox
@@ -0,0 +1,871 @@
+/** \mainpage KMail architectural overview
+
+\section KMail design principles
+
+This file is intended to guide the reader's way through the KMail
+codebase. It should esp. be handy for people not hacking full-time on
+KMail as well as people that want to trace bugs in parts of KMail
+which they don't know well.
+
+Contents:
+- Kernel
+- Identity
+- Filters
+- ConfigureDialog
+- MDNs
+- Folders
+- Index
+- Headers
+- Display
+
+TODO: reader, composer, messages, accounts, ...
+
+\section kernel KERNEL
+
+Files: kmkernel.h, kmkernel.cpp
+
+Contact Zack Rusin <zack@kde.org> with questions...
+
+The first thing you'll notice about KMail is the extensive use of
+kmkernel->xxx() constructs. The "kmkernel" is a define in kmkernel.h
+declared as :
+#define kmkernel KMKernel::self()
+KMKernel is the central object in KMail. It's always created before
+any other class, therefore you are _guaranteed_ that KMKernel::self()
+(and therefore "kmkernel" construct) won't return 0 (null).
+
+KMKernel implements the KMailIface (our DCOP interface) and gives
+access to all the core KMail functionality.
+
+
+\section identity IDENTITY
+
+FIXME this has moved to libkpimidentities, right?
+
+Files: identity*, kmidentity.{h,cpp}, configuredialog.cpp,
+ signatureconfigurator.{h,cpp}
+
+Contact Marc Mutz <mutz@kde.org> on questions...
+
+Identities consists of various fields represented by
+QStrings. Currently, those fields are hardcoded, but feel free to
+implement KPIM::Identity as a map from strings to QVariants or somesuch.
+
+One part of identities are signatures. They can represent four modes
+(Signature::Type) of operation (disabled, text from file or command
+and inline text), which correspond to the combo box in the
+identitydialog.
+
+Identities are designed to be used through the KPIM::IdentityManager:
+const KPIM::Identity & ident =
+ kmkernel->identityManager()->identityForUoidOrDefault(...)
+Make sure you assign to a _const_ reference, since the identityForFoo
+methods are overloaded with non-const methods that access a different
+list of identities in the manager that is used while configuring. That
+is known source of errors when you use identityForFoo() as a parameter
+to a method taking const KPIM::Identity &.
+
+WARNING: Don't hold the reference longer than the current functions
+scope or next return to the event loop. That's b/c the config dialog
+is not modal and the user may hit apply/ok anytime between calls to
+function that want to use the identity reference. Store the UOID
+instead if you need to keep track of the identity. You may also want
+to connect to one of the KPIM::IdentityManager::changed() or ::deleted()
+signals, if you want to do special processing in case the identity
+changes.
+
+Thus, in the ConfigureDialog, you will see non-const KPIM::Identity
+references being used, while everywhere else (KMMessage,
+IdentityCombo) const references are used.
+
+The KPIM::IdentityCombo is what you see in the composer. It's a
+self-updating combo box of KPIM::Identity's. Use this if you want the user
+to choose an identity, e.g. in the folder dialog.
+
+Ihe IdentityListView is what you see in the config dialog's identity
+management page. It's not meant to be used elsewhere, but is DnD
+enabled (well, at the time of this writing, only drag-enabled). This
+is going to be used to dnd identities around between KNode and KMail,
+e.g.
+
+The SignatureConfigurator is the third tab in the identity
+dialog. It's separate since it is used by the identity manager to
+request a new file/command if the current value somehow fails.
+
+
+
+\section filter FILTER
+
+Contact Marc Mutz <mutz@kde.org> on questions...
+
+Filters consist of a search pattern and a list of actions plus a few
+flags to indicate when they are to be applied (kmfilter.h).
+ They are managed in a QPtrList<KMFilter>, called KMFilterMgr. This
+filter magnager is responsible for loading and storing filters
+(read/writeConfig) and for executing them (process). The unique
+instance of the filter manager is held by the kernel
+(KMKernel::filterMgr()).
+
+The search pattern is a QPtrList of search rules (kmsearchpattern.h) and a
+boolean operator that defines their relation (and/or).
+
+A search rule consists of a field-QString, a "function"-enum and a
+"contents" or "value" QString. The first gives the header (or
+pseudoheader) to match against, the second says how to match (equals,
+consists, is less than,...) and the third holds the pattern to match
+against.
+ Currently, there are two types of search rules, which are mixed
+together into a single class: String-valued and int-valued. The latter
+is a hack to enable \verbatim<size>\endverbatim and
+\verbatim<age in days>\endverbatim pseudo-header matching.
+ KMSearchRules should better be organized like KMFilterActions are.
+
+A filter action (kmfilteraction.h) inherits from KMFilterAction or one
+of it's convenience sub-classes. They have three sub-interfaces: (1)
+argument handling, (2) processing and (3) parameter widget handling.
+ Interface (1) consists of args{From,As}String(), name() and
+isEmpty() and is used to read and write the arguments (if any) from/to
+the config.
+ Interface (2) is used by the filter manager to execute the action
+(process() / ReturnCode).
+ Interface (3) is used by the filter dialog to allow editing of
+actions and consists of name(), label() and the
+*ParamWidget*(). Complex parameter widgets are collected in
+kmfawidget.{h,cpp}.
+
+A typical call for applying filters is
+
+KMKernel::filterMgr()
+foreach message {
+ KMFilterMgr::process():
+}
+
+
+\section configuration CONFIGURE DIALOG
+
+Files: configuredialog*.{h,cpp} ( identitylistview.{h,cpp} )
+
+Contact Marc Mutz <mutz@kde.org> on questions...
+
+The configuredialog is made up of pages that in turn may consist of a
+number of tabs. The genral rule of thumb is that each page and tab is
+responsible for reading and writing the config options presented on
+it, although in the future, that may be reduced to interacting with
+the corresponding config manager instead. But that won't change the
+basic principle.
+
+Thus, there is an abstract base class ConfigurePage (defined in
+configuredialog_p.h), which derives from QWidget. It has four methods
+of which you have to reimplement at least the first two:
+
+- void setup()
+ Re-read the config (from the config file or the manager) and update
+ the widgets correspondingly. That is, you should only create the
+ widgets in the ctor, not set the options yet. The reason for that is
+ that the config dialog, once created, is simply hidden and shown
+ subsequently, so we need a reset-like method anyway.
+
+- void apply()
+ Read the config from the widgets and write it into the config file
+ or the corresponding config manager.
+
+- void installProfile()
+ This is called when the user selected a profile and hit apply. A
+ profile is just another KConfig object. Therefore, this method
+ should be the same as setup(), except that you should only alter
+ widgets for configs that really exist in the profile.
+
+For tabbed config pages, there exists a convenience class called
+TabbedConfigurationPage, which (as of this writing only offers the
+addTab() convenience method. It is planned to also provide
+reimplementations of setup, dismiss, apply and installProfile that just
+call the same functions for each tab.
+
+\section mdn MDNs
+
+Files: libkdenetwork/kmime_mdn.{h,cpp} and kmmessage.{h,cpp}, mostly
+
+Contact Marc Mutz <mutz@kde.org> on questions...
+
+MDNs (Message Disposition Notifications; RFC 2298) are a way to send
+back information regarding received messages back to their
+sender. Examples include "message read/deleted/forwarded/processed".
+
+The code in kmime_mdn.{h,cpp} is responsible for creating the
+message/disposition-notification body part (2nd child of
+multipart/report that makes the MDN) and for providing the template
+for human-readable text that goes into the text/plain part (1st child
+of the multipart/report).
+
+The code in KMMessage::createMDN() actually constructs a message
+containing a MDN for this message, using the kmime_mdn helper
+functions. It starts by checking the index for an already sent MDN,
+since the RFC demands that MDNs be sent only once for every
+message. If that test succeeds, it goes on to check various other
+constraints as per RFC and if all goes well the message containing the
+multipart/report is created.
+
+If you need to use this functionality, see KMReaderWin::touchMsg() and
+KMFilterAction::sendMDN() for examples. The touchMsg() code is invoked
+on display of a message and sends a "displayed" MDN back (if so
+configured), whereas the KMFilterAction method is a convenience helper
+for the various filter actions that can provoke a MDN (move to trash,
+redirect, forward, ...).
+
+
+\section folders Folders
+
+Files: kmfolder*.{h,cpp}, folderstorage.{h,cpp} and *job.{h,cpp}
+
+Contact Zack Rusin <zack@kde.org> with questions...
+
+The collaboration among KMail folder classes looks
+as follows :
+
+ KMFolderNode
+ / \
+ / \
+ KMFolderDir \
+ KMFolder
+ .
+ .
+ v
+ FolderStorage
+ |
+ |
+ KMFolderIndex
+ |
+ |
+ ---< actual folder types: KMFolderImap, KMFolderMbox... >--
+
+At the base KMail's folder design starts with KMFolderNode which
+inherits QObject. KMFolderNode is the base class encapsulating
+common folder properties such as the name and a boolean signifying whether
+the folder is a folder holding mail directly or a KMFolderDir.
+KMFolderNode's often do not have an on-disk representation, they are
+entities existing only within KMail's design.
+
+KMFolder acts as the runtime representation of a folder with the physical
+storage part being represented by a member of type FolderStorage.
+KMFolder and FolderStorage have many functions with the same names and
+signatures, but there is no inheritance.
+KMFolderIndex contains some common indexing functionality for physical folders.
+Subclasses of KMFolderIndex finally interact directly with physical storage
+or with storage providers over the network.
+
+KMFolderDir is a directory abstraction which holds KMFolderNode's.
+It inherits KMFolderNode and KMFolderNodeList which is a QPtrList<KMFolderNode>.
+A special case of a KMFolderDir is KMFolderRootDir; it represents
+the toplevel KMFolderDir in KMail's folder hierarchy.
+
+KMFolderDir's contents are managed by KMFolderMgr's.
+KMail contains three main KMFolderMgr's. They can be
+accessed via KMKernel ( the "kmkernel" construct ). Those methods are :
+1) KMFolderMgr *folderMgr() - which returns the folder manager for
+ the folders stored locally.
+2) KMFolderMgr *imapFolderMgr() - which returns the folder manager
+ for all imap folders. They're handled a little differently because
+ for all imap messages only headers are cached locally while the
+ main contents of all messages is kept on the server.
+3) KMFolderMgr *dimapFolderMgr() - which returns disconnected IMAP (dimap)
+ folder manager. In dimap, both the headers and a copy of the full message
+ are cached locally.
+4) KMFolderMgr *searchFolderMgr() - which returns the folder manager
+ for search folders (folders created by using the "find
+ messages" tool). Other email clients call this type of folder
+ "virtual folders".
+
+FolderJob classes - These classes allow asynchronous operations on
+KMFolder's. You create a Job on the heap, connect to one of its
+signals and wait for the job to finish. Folders serve as FolderJob
+factories. For example, to retrieve the full message from a folder
+you do :
+
+FolderJob *job = folderParent->createJob( aMsg, tGetMessage );
+connect( job, SIGNAL(messageRetrieved(KMMessage*)),
+ SLOT(msgWasRetrieved(KMMessage*)) );
+job->start();
+
+
+\section index Index (old)
+
+Files: kmfolderindex.{h,cpp} and kmmsg{base,info}.{h,cpp}
+
+Contact Marc Mutz <mutz@kde.org> or
+ Till Adam <adam@kde.org> or
+ Don Sanders <sanders@kde.org>
+with questions...
+
+ index := header *entry
+
+
+ header := magic LF NUL header-length byte-order-marker sizeof-long
+
+ magic := "# KMail-Index V" 1*DIGITS
+
+ header-length := Q_UINT32
+
+ byte-order-marker := Q_UINT32( 0x12345678 )
+
+ sizeof-long := Q_UINT32( 4 / 8 )
+
+
+ entry := tag length value
+
+ tag := Q_UINT32 ; little endian (native?)
+
+ length := Q_UINT16 ; little endian (native?)
+
+ value := unicode-string-value / ulong-value
+
+ unicode-string-value := 0*256QChar ; network-byte-order
+
+ ulong-value := unsigned_long ; little endian
+
+Currently defined tag values are:
+
+ Msg*Part num. val type obtained by:
+
+ No 0 u -
+ From 1 u fromStrip().stripWhitespace()
+ Subject 2 u subject().stripWhitespace()
+ To 3 u toStrip().stripWhiteSpace()
+ ReplyToIdMD5 4 u replyToIdMD5().stripWhiteSpace()
+ IdMD5 5 u msgIdMD5().stripWhiteSpace()
+ XMark 6 u xmark().stripWhiteSpace()
+ Offset 7 l folderOffset() (not only mbox!)
+ LegacyStatus 8 l mLegacyStatus
+ Size 9 l msgSize()
+ Date 10 l date()
+ File 11 u fileName() (not only maildir!)
+ CryptoState 12 l (signatureState() << 16) | encryptionState())
+ MDNSent 13 l mdnSentState()
+ ReplyToAuxIdMD5 14 u replyToAuxIdMD5()
+ StrippedSubject 15 u strippedSubjectMD5().stripWhiteSpace()
+ Status 16 l status()
+
+ u: unicode-string-value; l: ulong-value
+
+Proposed new (KDE 3.2 / KMail 1.6) entry format:
+
+ index := header *entry
+
+ entry := sync 1*( tag type content ) crc-tag crc-value
+
+ sync := Q_UINT16 (32?) ; resync mark, some magic bit pattern
+ ; (together with preceding crc-tag provides
+ ; 24(40)bits to resync on in case of index
+ ; corruption)
+
+ tag := Q_UINT8
+
+ type := Q_UINT8
+
+ content := variable-length-content / fixed-length-content
+
+ crc-tag := tag type ; tag=CRC16, type=CRC16
+
+ crc-value := Q_UINT16 ; the CRC16 sum is calculated over all of
+ ; 1*( tag type content )
+
+ variable-length-content := length *512byte padding
+
+ padding := *3NUL ; make the string a multiple of 4 octets in length
+
+ fixed-length-content := 1*byte
+
+ length := Q_UINT16 (Q_UINT8?)
+
+ byte := Q_UINT8
+
+The type field is pseudo-structured:
+
+bit: 7 6 5 4 3 2 1 0
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+ | uniq | chunk | len |
+ +-----+-----+-----+-----+-----+-----+-----+-----+
+
+uniq: 3 bits = max. 8 different types with same chunk size:
+
+ for chunk = (0)00 (LSB(base)=0: octets):
+ 00(0) Utf8String
+ 01(0) BitField
+ 10(0) reserved
+ 11(0) Extend
+
+ for chunk = (1)00 (LSB(base)=1: 16-octet blocks):
+ 00(1) MD5(Hash/List)
+ 01(1) reserved
+ 10(1) reserved
+ 11(1) Extend
+
+ for chunk = 01 (shorts):
+ 000 Utf16String
+ 001-110 reserved
+ 111 Extend
+
+ for chunk = 10 (int32):
+ 000 Utf32String (4; not to be used yet)
+ 001 Size32
+ 010 Offset32
+ 011 SerNum/UOID
+ 100 DateTime
+ 101 Color (QRgb: (a,r,g,b))
+ 110 reserved
+ 111 Extend
+
+ for chunk = 11 (int64):
+ 000 reserved
+ 001 Size64
+ 010 Offset64
+ 011-110 reserved
+ 111 Extend
+
+len: length in chunks
+ 000 (variable width -> Q_UINT16 with the real width follows)
+ 001..111: fixed-width data of length 2^len (2^1=2..2^6=128)
+
+You find all defined values for the type field in indexentrybase.cpp
+
+Currently defined tags are:
+
+ tag type content
+
+ DateSent DateTime Date:
+ DateReceived DateTime last Received:'s date-time
+ FromDisplayName String decoded display-name of first From: addr
+ ToDisplayName String dto. for To:
+ CcDisplayName String dto. for Cc:
+ FromAddrSpecs String possibly IMAA-encoded, comma-separated addr-spec's of From:
+ ToAddrSpecs String dto. for To:
+ CcAddrSpecs String dto. for Cc:
+ Subject String decoded Subject:
+ BaseSubjectMD5 String md5(utf8(stripOffPrefixes(subject())))
+ BodyPeek String body preview
+ MaildirFile String Filename of maildir file for this messagea
+ MBoxOffset Offset(64) Offset in mbox file (pointing to From_)
+ MBoxLength Size(64) Length of message in mbox file (incl. From_)
+ Size Size(64) rfc2822-size of message (in mbox: excl. From_)
+ Status BitField (see below)
+ MessageIdMD5 MD5Hash MD5Hash of _normalized_ Message-Id:
+ MDNLink SerialNumber SerNum of MDN received for this message
+ DNSLink SerialNumber SerNUm of DSN received for this message
+ ThreadHeads SerialNumberList MD5Hash's of all (so far discovered)
+ _top-level thread parents_
+ ThreadParents SerialNumberList MD5Hash's of all (so far discovered)
+ thread parents
+
+
+ "String" is either Utf8String or (Utf16String or Latin1String),
+ depending on content
+
+Currently allocated bits for the Status BitField are:
+
+ Bit Value: on(/off) (\\imapflag)
+
+ # "std stati":
+ 0 New (\\Recent)
+ 1 Read (\\Seen)
+ 2 Answered (\\Answered)
+ 3 Deleted (\\Deleted)
+ 4 Flagged (\\Flagged)
+ 5 Draft (\\Draft)
+ 6 Forwarded
+ 7 reserved
+
+ # message properties:
+ 8 HasAttachments
+ 9 MDNSent ($MDNSent)
+ 10..11 00: unspecified, 01: Low, 10: Normal, 11: High Priority
+ 12..15 0001: Queued, 0010: Sent, 0011: DeliveryDelayed,
+ 0100: Delivered, 0101: DeliveryFailed,
+ 0110: DisplayedToReceiver, 0111: DeletedByReceiver,
+ 1001: ProcessedByReceiver, 1010: ForwardedByReceiver,
+ 1011-1110: reserved
+ 1111: AnswerReceived
+
+ # signature / encryption state:
+ 16..19 0001: FullyEncrypted, 0010: PartiallyEncrypted, 1xxx: Problem
+ 20..23 0001: FullySigned, 0010: PartiallySigned, 1xxx: Problem
+
+ # "workflow stati":
+ 24..25 01: Important, 10: ToDo, 11: Later (from Moz/Evo)
+ 26..27 01: Work, 10: Personal, 11: reserved (dto.)
+ 28..29 01: ThreadWatched, 10: ThreadIgnored, 11: reserved
+ 30..31 reserved
+
+All bits and combinations marked as reserved MUST NOT be altered if
+set and MUST be set to zero (0) when creating the bitfield.
+
+
+\section headers Headers (Threading and Sorting)
+
+Contact Till Adam <adam@kde.org> or
+ Don Sanders <sanders@kde.org>
+with questions...
+
+Threading and sorting is implemented in kmheaders.[cpp|h] and headeritem.[cpp|h]
+and involves a handfull of players, namely:
+
+class KMHeaders:
+ this is the listview that contains the subject, date etc of each mail.
+ It's a singleton, which means there is only one, per mainwidget headers
+ list. It is actually a member of KMMainwidget and accessible there.
+
+class HeaderItem:
+ these are the [Q|K]ListViewItem descendend items the KMHeaders listview
+ consists of. There's one for each message.
+
+class SortCacheItem:
+ these are what the threading and sorting as well as the caching of those
+ operate on. Each is paired with a HeaderItem, such that each holds a
+ pointer to one, and vice versa, each header item holds a pointer to it's
+ associated sort cache item.
+
+.sorted file:
+ The order of the sorted and threaded (if threading is turned on for this
+ folder) messages is cached on disk in a file named .$FOLDER.index.sorted
+ so if, for example the name of a folder is foo, the associated sorting
+ cache file would be called ".foo.index.sorted".
+ For each message, its serial number, that of its parent, the length of its
+ sorting key, and the key itself are written to this file. At the start of
+ the file several per folder counts and flags are cached additionally,
+ immediately after a short file headers. The entries from the start of the
+ file are:
+ - "## KMail Sort V%04d\n\t" (magic header and version string)
+ - byteOrder flag containing 0x12345678 for byte order testing
+ - column, the sort column used for the folder
+ - ascending, the sort direction
+ - threaded, is the view threaded or is it not?
+ - appended, have there been items appended to the file (obsolete?)
+ - discovered_count, number of new mail discovered since the last sort file
+ generation
+ - sorted_count, number of sorted messages in the header list
+
+What is used for figuring out threading?
+ - messages can have an In-Reply-To header that contains the message id of
+ another message. This is called a perfect parent.
+ - additionally there is the References header which, if present, holds a
+ list of message ids that the current message is a follow up to. We
+ currently use the second to last entry in that list only. See further
+ down for the reasoning behind that.
+ - If the above two fail and the message is prefixed (Re: foo, Fwd: foo etc.)
+ an attempt is made to find a parent by looking for messages with the same
+ subject. How that is done is explained below as well.
+
+ For all these comparisons of header contents, the md5 hashes of the headers
+ are used for speed reasons. They are stored in the index entry for each
+ message. All data structures described below use md5 hash strings unless
+ stated otherwise.
+
+Strategy:
+ When a folder is opened, updateMessageList is called, which in turn calls
+ readSortOrder where all the fun happens. If there is a .sorted file at the
+ expected location, it is openend and parsed. The header flags are read in
+ order to figure out the state in which this .sorted file was written. This
+ means the sort direction, whether the folder is threaded or not etc.
+ FIXME: is there currently sanity checking going on?
+ Now the file is parsed and for each message in the file a SortCacheItem is
+ created, which holds the offset in the .sorted file for this message as well
+ as it's sort key as read from the file. That sort cache item is entered into
+ the global mSortCache structure (member of the KMHeaders instance), which is
+ a QMemArray<SortCacheItem *> of the size mFolder->count(). Note that this
+ is the size reported by the folder, not as read from the .sorted file. The
+ mSortCache (along with some other structures) is updated when messages are
+ added or removed. More on that further down.
+ As soon as the serial number is read from the file, that number is looked up
+ in the message dict, to ensure it is still in the current folder. If not, it
+ has been moved away in the meantime (possibly by outside forces such as
+ other clients) and a deleted counter is incremented and all further
+ processing stopped for this message.
+ The messages parent serial number, as read from the sorted file is then
+ used to look up the parent and reset it to -1 should it not be in the
+ current folder anymore. -1 and -2 are special values that can show up
+ as parent serial numbers and are used to encode the following:
+ -1 means this message has no perfect parent, a parent for it needs to
+ be found from among the other messages, if there is a suitable one
+ -2 means this message is top level and will forever stay so, no need
+ to even try to find a parent. This is also used for the non-threaded
+ case. These are messages that have neither an In-Reply-To header nor
+ a References header and have a subject that is not prefixed.
+ In case there is a perfect parent, the current sort cache item is
+ appended to the parents list of unsorted children, or to that of
+ root, if there is not. A sort cache item is created in the mSortCache
+ for the parent, if it is not already there. Messages with a parent of
+ -1 are appended to the "unparented" list, which is later traversed and
+ its elements threaded. Messages with -2 as the parent are children of
+ root as well, as noted above, and will remain so.
+
+ Once the end of the file is reached, we should have a nicely filled
+ mSortCache, containing a sort cache item for each message that was in the
+ sorted file. Messages with perfect parents know about them, top level
+ messages know about that as well, all others are on a list and will be
+ threaded later.
+
+ Now, what happens when messages have been added to the store since the last
+ update of the .sorted file? Right, they don't have a sort cache item yet,
+ and would be overlooked. Consequently all message ids in the folder from 0
+ to mFolder->count() are looked at and a SortCacheItem is created for the
+ ones that do not have one yet. This is where all sort cache items are created
+ if there was no sorted file. The items created here are by definition un-
+ sorted as well as unparented. On creation their sort key is figured out as
+ well.
+
+ The next step is finding parents for those messages that are either new, or
+ had a parent of -1 in the .sorted file. To that end, a dict of all sort
+ cache items indexed by the md5 hash of their messsage id headers is created,
+ that will be used for looking up sort cache items by message id. The list of
+ yet unparented messages is then traversed and findParent() called for each
+ element wihch checks In-Reply-To and References headers and looks up the
+ sort cache item of those parents in the above mentioned dict. Should none be
+ found, the item is added to a second list the items of which will be subject
+ threaded.
+
+ How does findParent() work, well, it tries to find the message pointed to by
+ the In-Reply-To header and if that fails looks for the one pointed to by the
+ second to last entry of the References header list. Why the second to last?
+ Imagine the following situation in Bob's kmail:
+ - Bob get's mail from Alice
+ - Bob replies to Alice's mail, but his mail is stored in the Outbox, not the
+ Inbox.
+ - Alice replies again, so Bob now has two mails from Alice which are part of
+ the same thread. The mail in the middle is somewhere else. We want that to
+ thread as follows:
+ Bob <- In-Reply-To: Alice1
+ ============
+ Alice1
+ |_Alice <- In-Reply-To: Bob (not here), References: Alice, Bob
+
+ - since the above is a common special case, it is worth trying. I think. ;)
+
+ If the parent found is perfect (In-Reply-To), the sort cache items is marked
+ as such. mIsImperfectlyThreaded is used for that purposer, we will soon see
+ why that flag is needed.
+
+ On this first pass, no subject threading is attempted yet. Once it is done,
+ the messages that are now top-level, the current thread heads, so to speak,
+ are collected into a second dict ( QDict< QPtrList< SortCacheItem > > )
+ that contains for each different subject an entry holding a list of (so far
+ top level) messages with that subject, that are potential parents for
+ threading by subjects. These lists are sorted by date, so the parent closest
+ by date can be chosen. Sorting of these lists happens via insertion sort
+ while they are built because not only are they expected to be short (apart
+ from hard corner cases such as cvs commit lists, for which subject threading
+ makes little sense anyhow and where it should be turned off), but also since
+ the messages should be roughly pre sorted by date in the store already.
+ Some cursory benchmarking supports that assumption.
+ If now a parent is needed for a message with a certain subject, the list of
+ top level messages with that subject is traversed and the first one that is
+ older than our message is chosen as it's parent. Parents more than six weeks
+ older than the message are not accepted. The reasoning being that if a new
+ message with the same subject turns up after such a long time, the chances
+ that it is still part of the same thread are slim. The value of six weeks
+ is chosen as a result of a poll conducted on #kde-devel, so it's probably
+ bogus. :) All of this happens in the aptly named: findParentBySubject().
+
+ Everthing that still has no parent after this ends up at toplevel, no further
+ attemp is made at finding one. If you are reading this because you want to
+ implement threading by body search or somesuch, please go away, I don't like
+ you. :)
+
+ Ok, so far we have only operated on sort cache items, nothing ui wise has
+ happened yet. Now that we have established the parent/child relations of all
+ messages, it's time to create HeaderItems for them for use in the header
+ list. But wait, you say, what about sorting? Wouldn't it make sense to do
+ that first? Exactly, you're a clever bugger, ey? Here, have a cookie. ;)
+ Both creation of header items and sorting of the as of yet unsorted sort
+ cache items happen at the same time.
+
+ As previously mentioned (or not) each sort cache item holds a list of its
+ sorted and one of its unsorted children. Starting with the root node the
+ unsorted list is first qsorted, and then merged with the list of already
+ sorted children. To achieve that, the heads of both lists are compared and
+ the one with the "better" key is added to the list view next by creating a
+ KMHeaderListItem for it. That header item receives both its sort key as well
+ as its id from the sort cache item. Should the current sort cache item have
+ children, it is added to the end of a queue of nodes to repeat these steps
+ on after the current level is sorted. This way, a breadth first merge sort
+ is performed on the sort cache items and header items are created at each
+ node.
+
+ What follows is another iteration over all message ids in the folder, to
+ make sure that there are HeaderItems for each now. Should that not be the
+ case, top level emergency items are created. This loop is also used to add
+ all header items that are imperfectly threaded to a list so they can be
+ reevalutated when a new message arrives. Here the reverse mapping from
+ header items to sort cache items are made as well. Those are also necessary
+ so the sort cache item based data structures can be updated when a message
+ is removed.
+
+ The rest of readSortOrder should make sense on itself, I guess, if not, drop
+ me an email.
+
+What happens when a message arrives in the folder?
+ Among other things, the msgAdded slot is called, which creates the necessary
+ sort cache item and header item for the new message and makes sure the data
+ structures described above are updated accordingly. If threading is enabled,
+ the new message is threaded using the same findParent and findParentBySubject
+ methods used on folder open. If the message ends up in a watched or ignored
+ thread, those status bits are inherited from the parent. The message is also
+ added to the dict of header items, the index of messages by message id and,
+ if applicable and if the message is threaded at top level, to the list of
+ potential parents for subject threading.
+
+ After those house keeping tasks are performed, the list of as of yet imper-
+ fectly threaded messages is traversed and our newly arrived message is
+ considered as a new parent for each item on it. This is especially important
+ to ensure that parents arriving out of order after their children still end
+ up as parents. If necessary, the entries in the .sorted file of rethreaded
+ messages are updated. An entry for the new message itself is appended to the
+ .sorted file as well.
+
+ Note that as an optimization newly arriving mail as part of a mailcheck in
+ an imap folder is not added via msgAdded, but rather via complete reload of
+ the folder via readSortOrder(). That's because only the headers are gotten
+ for each mail on imap and that is so fast, that adding them individually to
+ the list view is very slow by comparison. Thus we need to make sure that
+ writeSortOrder() is called whenever something related to sorting changes,
+ otherwise we read stale info from the .sorted file. The reload is triggered
+ by the folderComplete() signal of imap folders.
+
+What happens when a message is removed from the folder?
+ In this case the msgRemoved slot kicks in and updates the headers list. First
+ the sort cache item and header item representing our message are removed from
+ the data structures and the ids of all items after it in the store decre-
+ mented. Then a list of children of the message is assembled containing those
+ children that have to be reparented now that our message has gone away. If
+ one of those children has been marked as toBeDeleted, it is simply added to
+ root at top level, because there is no need to find a parent for it if it is
+ to go away as well. This is an optimization to avoid rethreading all
+ messages in a thread when deleting several messages in a thread, or even the
+ whole thread. The KMMoveCommand marks all messages that will be moved out of
+ the folder as such so that can be detected here and the useless reparenting
+ can be avoided. Note that that does not work when moving messages via filter
+ action.
+
+ That list of children is then traversed and a new parent found for each one
+ using, again, findParent and findParentBySubject. When a message becomes
+ imperfectly threaded in the process, it is added to the corresponding list.
+
+ The message itself is removed from the list of imperfectly threaded messages
+ and its header item is finally deleted. The HeaderItem destructor destroys
+ the SortCacheItem as well, which is hopefully no longer referenced anywhere
+ at this point.
+
+
+
+\section display DISPLAY (reader window - new)
+
+ Contact Marc Mutz <mutz@kde.org> with questions...
+
+What happens when a message is to displayed in the reader window?
+ First, since KMMessagePart and KMMessage don't work with nested body
+ parts, a hierarchical tree of MIME body parts is constructed with
+ partNode's (being wrappers around KMMessagePart and
+ DwBodyPart). This is done in KMReaderWin::parseMsg(KMMessage*).
+ After some legacy handling, an ObjectTreeParser is instantiated and
+ it's parseObjectTree() method called on the root
+ partNode. ObjectTreeParser is the result of an ongoing refactoring
+ to enhance the design of the message display code. It's an
+ implementation of the Method Object pattern, used to break down a
+ huuuge method into many smaller ones without the need to pass around
+ a whole lot of paramters (those are made members
+ instead). parseObjectTree() recurses into the partNode tree and
+ generates an HTML representation of each node in the tree (although
+ some can be hidden by setting them to processed beforehand or - the
+ new way - by using AttachmentStrategy::Hidden). The HTML generation
+ is delegated to BodyPartFormatters, while the HTML is written to
+ HTMLWriters, which abstract away HTML sinks. One of those is
+ KHTMLPartHTMLWriter, which writes to the KHTMLPart in the
+ readerwindow. This is the current state of the code. The goal of the
+ ongoing refactoring is to make the HTML generation blissfully
+ ignorant of the readerwindow and to allow display plugins that can
+ operate against stable interfaces even though the internal KMail
+ classes change dramatically.
+
+ To this end, we designed a new set of interfaces that allows plugins
+ to be written that need or want to asynchronously update their HTML
+ representation. This set of interfaces consists of the following:
+
+ - @em BodyPartFormatterPlugin
+ the plugin base class. Allows the BodyPartFormatterFactory to
+ query it for an arbitray number of BodyPartFormatters and
+ their associated metadata and url handlers.
+
+ - @em BodyPartFormatter
+ the formatter interface. Contains a single method format()
+ that takes a BodyPart to process and an HTMLWriter to write the
+ generated HTML to and returns a result code with which it can
+ request more information or delegate the formatting back to
+ some default processor. BodyPartFormatters are meant to be
+ Flyweights, implying that the format() method is not allowed
+ to maintain state between calls, but see Mememto below.
+
+ - @em BodyPart
+ body part interface. Contains methods to retrieve the content
+ of a message part and some of the more interesting header
+ information. This interface decouples the bodypart formatters
+ from the implementation used in KMail (or KNode, for that
+ matter). Also contains methods to set and retrieve a Memento,
+ which is used by BodyPartFormatters to maintain state between
+ calls of format().
+
+ - @em BodyPartMemento
+ interface for opaque state storage and event handling.
+ Contains only a virtual destructor to enable the backend
+ processing code to destroy the object without the help of the
+ bodypart formatter.
+
+
+During the design phase we identified a need for BodyPartFormatters to
+request their being called on some form of events, e.g. a dcop
+signal. Thus, the Memento interface also includes the IObserver and
+ISubject interfaces. If a BodyPartFormatter needs to react to a signal
+(Qt or DCOP), it implements the Memento interface using a QObject,
+connects the signal to a slot on the Memento and (as an ISubject)
+notifies it's IObservers when the slot is called. If a Memento is
+created, the reader window registers itself as an observer of the
+Memento and will magically invoke the corresponding BodyPartFormatter,
+passing along the Memento to be retrieved from the BodyPart interface.
+
+An example should make this clearer. Suppose, we want to update our
+display after 10 seconds. Initially, we just write out an icon, and
+after 10 seconds, we want to replace the icon by a "Hello world!"
+line. The following code does exactly this:
+
+@code
+class DelayedHelloWorldBodyPartFormatter
+ : public KMail::BodyPartFormatter {
+public:
+ Result format( KMail::BodyPart * bodyPart,
+ KMail::HTMLWriter * htmlWriter ) {
+ if ( !bodyPart->memento() ) {
+ bodyPart->registerMemento( new DelayedHelloWorldBodyPartMemento() );
+ return AsIcon;
+ } else {
+ htmlWriter->write( "Hello, world!" );
+ return Ok;
+ }
+ }
+};
+
+class DelayedHelloWorldBodyPartMemento
+ : public QObject, public KMail::BodyPartMemento {
+public:
+ DelayedHelloWorldBodyPartMemento()
+ : QObject( 0, "DelayedHelloWorldBodyPartMemento" ),
+ KMail::BodyPartMemento()
+ {
+ QTimer::singleShot( 10*1000, this, SLOT(slotTimeout()) );
+ }
+
+private slots:
+ void slotTimeout() { notify(): }
+
+private:
+ // need to reimplement this abstract method...
+ bool update() { return true; }
+};
+@endcode
+
+*/
diff --git a/kmail/Makefile.am b/kmail/Makefile.am
new file mode 100644
index 00000000..ea566c45
--- /dev/null
+++ b/kmail/Makefile.am
@@ -0,0 +1,237 @@
+#KDE_OPTIONS = nofinal
+KDE_CXXFLAGS = $(USE_RTTI)
+
+SUBDIRS = interfaces . about pics profiles avscripts tests
+
+INCLUDES = -I$(top_srcdir)/libkmime \
+ -I$(top_srcdir)/libkpgp \
+ -I$(top_srcdir)/libkdenetwork \
+ -I$(top_srcdir)/libkdepim \
+ -I$(top_srcdir)/libkpimidentities \
+ -I$(top_srcdir)/libemailfunctions \
+ -I$(top_srcdir)/libksieve \
+ -I$(top_srcdir)/mimelib \
+ -I$(top_srcdir)/certmanager/lib \
+ -I$(top_srcdir)/certmanager/lib/ui \
+ -I$(top_srcdir)/indexlib \
+ -I$(top_srcdir)/ktnef \
+ -I$(top_srcdir)/korganizer \
+ -I$(top_srcdir) \
+ $(GPGME_CFLAGS) \
+ $(all_includes)
+
+if add_indexlib
+INDEXLIB=../indexlib/libindex.la
+endif
+
+lib_LTLIBRARIES = libkmailprivate.la
+libkmailprivate_la_LDFLAGS = $(all_libraries) -avoid-version -no-undefined
+libkmailprivate_la_LIBADD = \
+ ../libkmime/libkmime.la ../libkpgp/libkpgp.la ../libkdepim/libkdepim.la \
+ ../libkpimidentities/libkpimidentities.la ../mimelib/libmimelib.la \
+ ../libksieve/libksieve.la ../libemailfunctions/libemailfunctions.la \
+ ../certmanager/lib/libkleopatra.la $(INDEXLIB) \
+ ../libkcal/libkcal.la \
+ $(LIB_KHTML) $(LIB_KSPELL) $(LIB_KABC)
+
+kde_module_LTLIBRARIES = kcm_kmail.la libkmailpart.la libkmail_bodypartformatter_application_octetstream.la
+libkmailpart_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) -module -avoid-version -no-undefined
+libkmailpart_la_LIBADD = libkmailprivate.la
+
+kcm_kmail_la_SOURCES = kcm_kmail.cpp
+kcm_kmail_la_LDFLAGS = $(all_libraries) -module -avoid-version -no-undefined
+kcm_kmail_la_LIBADD = libkmailprivate.la $(LIB_KDECORE)
+
+libkmail_bodypartformatter_application_octetstream_la_SOURCES = app_octetstream.cpp
+libkmail_bodypartformatter_application_octetstream_la_LDFLAGS = $(all_libraries) -module $(KDE_PLUGIN)
+
+linkdir = $(kde_datadir)/kmail/plugins/bodypartformatter
+link_DATA = application_octetstream.desktop
+
+bin_PROGRAMS = kmail
+kmail_LDFLAGS = $(all_libraries) $(KDE_RPATH)
+kmail_LDADD = libkmailprivate.la
+
+libkmailprivate_la_SOURCES = kmmessage.cpp kmmainwin.cpp configuredialog.cpp \
+ configuredialog_p.cpp klistviewindexedsearchline.cpp \
+ simplestringlisteditor.cpp index.cpp \
+ identitydrag.cpp identitylistview.cpp identitydialog.cpp \
+ kmfolderdia.cpp kmfoldertree.cpp kmtransport.cpp \
+ kmfoldercombobox.cpp kmaccount.cpp kmheaders.cpp \
+ headeritem.cpp listjob.cpp \
+ kmcomposewin.cpp kmfolder.cpp kmmsgpartdlg.cpp \
+ kmreaderwin.cpp htmlstatusbar.cpp kmmsgdict.cpp \
+ kmgroupware.cpp folderstorage.cpp \
+ csshelper.cpp klistboxdialog.cpp \
+ actionscheduler.cpp messageproperty.cpp \
+ kmmsgpart.cpp kmmsginfo.cpp \
+ accountmanager.cpp kmacctfolder.cpp kmdict.cpp \
+ kmsystemtray.cpp kmacctlocal.cpp kmfolderdir.cpp \
+ kmfoldermgr.cpp kmfoldernode.cpp kmsender.cpp \
+ kmacctseldlg.cpp kmfiltermgr.cpp kmsearchpatternedit.cpp \
+ filterimporterexporter.cpp \
+ encodingdetector.cpp encodingdetector_ja.cpp \
+ kmfilteraction.cpp kmsearchpattern.cpp \
+ kmfolderseldlg.cpp kmfilter.cpp kmfilterdlg.cpp \
+ kmmsgbase.cpp kmmsglist.cpp kmaddrbook.cpp \
+ signatureconfigurator.cpp xfaceconfigurator.cpp \
+ networkaccount.cpp imapaccountbase.cpp \
+ kmservertest.cpp kmacctimap.cpp kmacctcachedimap.cpp \
+ kmfawidgets.cpp kmfoldermbox.cpp kmfolderimap.cpp \
+ undostack.cpp kmfoldercachedimap.cpp \
+ kmfoldermaildir.cpp popaccount.cpp colorlistbox.cpp \
+ kmkernel.cpp kmailIface.skel kmailicalIface.skel \
+ accountdialog.cpp searchwindow.cpp vcardviewer.cpp \
+ vacationdialog.cpp vacation.cpp sievedebugdialog.cpp \
+ sieveconfig.cpp sievejob.cpp \
+ kmpopheaders.cpp kmpopfiltercnfrmdlg.cpp \
+ kmmimeparttree.cpp \
+ mailinglist-magic.cpp kmacctmaildir.cpp \
+ attachmentstrategy.cpp \
+ headerstrategy.cpp headerstyle.cpp khtmlparthtmlwriter.cpp \
+ filehtmlwriter.cpp teehtmlwriter.cpp \
+ mailcomposerIface.skel objecttreeparser.cpp \
+ attachmentcollector.cpp \
+ bodypartformatter.cpp bodypartformatterfactory.cpp \
+ partNode.cpp \
+ mailsourceviewer.cpp \
+ kmcommands.cpp kmreadermainwin.cpp \
+ kmstartup.cpp kmmainwidget.cpp \
+ folderpropertiesdialog.ui kmfolderindex.cpp \
+ kmfoldersearch.cpp transportmanager.cpp \
+ folderjob.cpp cachedimapjob.cpp \
+ maildirjob.cpp mboxjob.cpp imapjob.cpp \
+ subscriptiondialog.cpp kmailicalifaceimpl.cpp aboutdata.cpp \
+ folderIface.cpp folderIface.skel mailserviceimpl.cpp \
+ attachmentlistview.cpp kmedit.cpp kmlineeditspell.cpp \
+ kmatmlistview.cpp composer.cpp \
+ isubject.cpp bodyvisitor.cpp antispamwizard.cpp \
+ urlhandlermanager.cpp dictionarycombobox.cpp \
+ secondarywindow.cpp filterlog.cpp filterlogdlg.cpp \
+ korganizeriface.stub messagecomposer.cpp \
+ keyresolver.cpp globalsettings.cpp globalsettings_base.kcfgc \
+ regexplineedit.cpp rulewidgethandlermanager.cpp \
+ headerlistquicksearch.cpp acljobs.cpp folderdiaacltab.cpp \
+ quotajobs.cpp folderdiaquotatab.cpp folderdiaquotatab_p.cpp \
+ partnodebodypart.cpp \
+ expirejob.cpp compactionjob.cpp jobscheduler.cpp callback.cpp \
+ searchjob.cpp renamejob.cpp \
+ composercryptoconfiguration.ui \
+ warningconfiguration.ui smimeconfiguration.ui annotationjobs.cpp \
+ accountcombobox.cpp redirectdialog.cpp foldershortcutdialog.cpp \
+ folderrequester.cpp \
+ spamheaderanalyzer.cpp antispamconfig.cpp \
+ replyphrases.kcfgc custommimeheader.kcfgc \
+ recipientseditor.cpp \
+ recipientspicker.cpp kwindowpositioner.cpp \
+ distributionlistdialog.cpp expirypropertiesdialog.cpp \
+ mailinglistpropertiesdialog.cpp newfolderdialog.cpp \
+ accountwizard.cpp textsource.cpp \
+ managesievescriptsdialog.cpp chiasmuskeyselector.cpp \
+ util.cpp templatesinsertcommand.cpp \
+ customtemplates_base.ui customtemplates.cpp \
+ customtemplates_kfg.kcfgc \
+ templatesconfiguration_base.ui templatesconfiguration.cpp \
+ templatesconfiguration_kfg.kcfgc \
+ templateparser.cpp \
+ copyfolderjob.cpp \
+ messagecopyhelper.cpp \
+ localsubscriptiondialog.cpp \
+ editorwatcher.cpp \
+ kcalendariface.stub \
+ favoritefolderview.cpp \
+ foldertreebase.cpp \
+ snippetdlgbase.ui \
+ snippetwidget.cpp \
+ snippetconfig.cpp \
+ snippetdlg.cpp \
+ snippetitem.cpp \
+ snippetsettings.cpp \
+ snippetsettingsbase.ui \
+ scalix.cpp \
+ messageactions.cpp \
+ korghelper.cpp
+
+libkmailprivate_la_COMPILE_FIRST = globalsettings_base.h customtemplates_base.h templatesconfiguration_base.h
+
+kmail_SOURCES = main.cpp
+
+kmail_COMPILE_FIRST = globalsettings_base.h customtemplates_base.h templatesconfiguration_base.h
+
+libkmailpart_la_SOURCES = kmailpartIface.skel kmail_part.cpp
+
+libkmailpart_la_COMPILE_FIRST = globalsettings_base.h customtemplates_base.h templatesconfiguration_base.h
+
+check_PROGRAMS = dcoptest recipienteditortest
+
+METASOURCES = AUTO
+
+dcoptest_SOURCES = dcoptest.cpp kmailIface.skel kmailIface.stub mailcomposerIface.skel mailcomposerIface.stub
+dcoptest_LDADD = $(LIB_KIO)
+dcoptest_LDFLAGS = $(all_libraries)
+
+recipienteditortest_SOURCES = recipientseditortest.cpp
+recipienteditortest_LDADD = libkmailprivate.la ../libkdepim/libkdepim.la $(LIB_KIO) $(LIB_KABC)
+recipienteditortest_LDFLAGS = $(all_libraries)
+
+kmailIface_DCOPIDLNG = true
+kmailicalIface_DCOPIDLNG = true
+
+korganizeriface_DIR = $(top_srcdir)/korganizer
+kcalendariface_DIR = $(top_srcdir)/korganizer
+
+include_HEADERS = kmailIface.h kmailpartIface.h kmailicalIface.h
+
+xdg_apps_DATA = KMail.desktop kmail_view.desktop
+
+# why?
+EXTRA_DIST = KMail.desktop $(link_DATA)
+
+KDE_ICON = AUTO
+
+rcdir = $(kde_datadir)/kmail
+rc_DATA = kmcomposerui.rc kmmainwin.rc kmreadermainwin.rc eventsrc kmail_part.rc
+
+updatedir = $(kde_datadir)/kconf_update
+update_DATA = kmail.upd
+update_SCRIPTS = upgrade-transport.pl kmail-pgpidentity.pl \
+ upgrade-signature.pl kmail-upd-identities.pl \
+ kmail-3.1-use-UOID-for-identities.pl \
+ kmail-3.1-update-new-mail-notification-settings.pl \
+ kmail-3.1.4-dont-use-UOID-0-for-any-identity.pl \
+ kmail-3.2-update-loop-on-goto-unread-settings.sh \
+ kmail-3.2-misc.sh \
+ kmail-3.3-use-ID-for-accounts.pl \
+ kmail-3.3-move-identities.pl \
+ kmail-3.3-aegypten.pl \
+ kmail-3.3-split-sign-encr-keys.sh \
+ kmail-3.3-misc.pl \
+ kmail-3.3b1-misc.pl \
+ kmail-3.4-misc.pl \
+ kmail-3.4.1-update-status-filters.pl \
+ kmail-3.5-trigger-flag-migration.pl
+
+confdir = $(kde_confdir)
+conf_DATA = kmail.antispamrc kmail.antivirusrc
+
+tipdir = $(kde_datadir)/kmail
+tip_DATA = tips
+
+servicetypedir = $(kde_servicetypesdir)
+servicetype_DATA = dcopmail.desktop dcopimap.desktop
+
+kde_services_DATA = kmail_config_misc.desktop kmail_config_appearance.desktop \
+ kmail_config_identity.desktop kmail_config_accounts.desktop kmail_config_composer.desktop \
+ kmail_config_security.desktop
+
+messages: rc.cpp
+ 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
+
+DOXYGEN_REFERENCES = kdeui
+include $(top_srcdir)/admin/Doxyfile.am
diff --git a/kmail/TODO b/kmail/TODO
new file mode 100644
index 00000000..cf5792a1
--- /dev/null
+++ b/kmail/TODO
@@ -0,0 +1,36 @@
+Around the 3.4 release
+TA: remove the disabled expiry tab code from kmfolderdia.cpp once it is clear
+ that it won't be used anymore
+
+Before the first 3.2 Beta
+MM: Write config upgrade script for
+ - AttachmentStyle -> AttachmentStrategy change
+ - HeaderStyle -> { HeaderStyle, HeaderStrategy } split
+DS: Full Text Indexing
+DS: Client side imap filtering
+DS: Support for multipart/related mails
+DS: Support for html composing
+DS: Better for replying to html mail
+??: Disconnected IMAP
+
+Needs more planning:
+DS: BBDB like functionality
+DS: Fuller DCOP interface (requires hidden dcop interfaces thanks Alex!)
+DS: Console (plain text) client using dcop interface
+DS: XFace with support for Allison reconstruction.
+DS: writeindex incrementally update for deleted items.
+DS: (improve status showing, for concurrent account checking)
+DS: Use takeitem to hide read messages, with special behavior
+ in threaded mode
+DS: Propoganda: Analysis Articles, head-to-head comparisons
+DS: Single message class
+DS: Zero-copy display of message 15465
+DS: Zero-copy downloading from pop server
+DS: Set color action
+DS: XML GUI context menus to support KMail plugins
+DS: IMAP body searching
+??: Dock Windows
+
+Groupware:
+KHZ: Make groupware use a real vCal parser
+MM: Implement generic viewer plugin
diff --git a/kmail/about/Makefile.am b/kmail/about/Makefile.am
new file mode 100644
index 00000000..4ac57fc2
--- /dev/null
+++ b/kmail/about/Makefile.am
@@ -0,0 +1,6 @@
+about_DATA = \
+ top-right-kmail.png \
+ main.html \
+ kmail.css
+
+aboutdir = $(kde_datadir)/kmail/about
diff --git a/kmail/about/kmail.css b/kmail/about/kmail.css
new file mode 100644
index 00000000..3fb2ce90
--- /dev/null
+++ b/kmail/about/kmail.css
@@ -0,0 +1,26 @@
+
+#headerR {
+ position: absolute;
+ right: 0px;
+ width: 430px;
+ height: 131px;
+ background-image: url(top-right-kmail.png);
+}
+
+#title {
+ right: 125px;
+}
+
+#tagline {
+ right: 125px;
+}
+
+#boxCenter {
+ background-image: url(box-center-kmail.png);
+ background-repeat: no-repeat;
+ background-color: #dfe7f3;
+ background-position: bottom right;
+}
+
+/* vim:set sw=2 et nocindent smartindent: */
+
diff --git a/kmail/about/main.html b/kmail/about/main.html
new file mode 100644
index 00000000..bc367933
--- /dev/null
+++ b/kmail/about/main.html
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta name="generator" content=
+ "HTML Tidy for Linux/x86 (vers 1st August 2004), see www.w3.org" />
+
+ <style type="text/css">
+ /*<![CDATA[*/
+ @import "%1"; /* kde_infopage.css */
+ %1 /* maybe @import "kde_infopage_rtl.css"; */
+ @import "kmail.css";
+ body {font-size: %1px;}
+ /*]]>*/
+ </style>
+
+ <title>KMail</title>
+</head>
+
+<body>
+ <div id="header">
+ <div id="headerL"/>
+ <div id="headerR"/>
+
+ <div id="title">
+ %2 <!-- KMail -->
+ </div>
+
+ <div id="tagline">
+ %3 <!-- Catchphrase -->
+ </div>
+ </div>
+
+ <!-- the bar -->
+ <div id="bar">
+ <div id="barT"><div id="barTL"/><div id="barTR"/><div id="barTC"/></div>
+ <div id="barL">
+ <div id="barR">
+ <div id="barCenter" class="bar_text">
+ %4<!-- KMail is ... -->
+ </div>
+ </div>
+ </div>
+ <div id="barB"><div id="barBL"/><div id="barBR"/><div id="barBC"/></div>
+ </div>
+
+ <!-- the main text box -->
+ <div id="box">
+ <div id="boxT"><div id="boxTL"/><div id="boxTR"/><div id="boxTC"/></div>
+ <div id="boxL">
+ <div id="boxR">
+ <div id="boxCenter">
+ <!--Welcome to KMail-->
+ %5
+ </div>
+ </div>
+ </div>
+ <div id="boxB"><div id="boxBL"/><div id="boxBR"/><div id="boxBC"/></div>
+ </div>
+
+ <div id="footer"><div id="footerL"/><div id="footerR"/></div>
+</body>
+</html>
+<!-- vim:set sw=2 et nocindent smartindent: -->
diff --git a/kmail/about/top-right-kmail.png b/kmail/about/top-right-kmail.png
new file mode 100644
index 00000000..bf15f38b
--- /dev/null
+++ b/kmail/about/top-right-kmail.png
Binary files differ
diff --git a/kmail/aboutdata.cpp b/kmail/aboutdata.cpp
new file mode 100644
index 00000000..e525fae4
--- /dev/null
+++ b/kmail/aboutdata.cpp
@@ -0,0 +1,229 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ aboutdata.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "aboutdata.h"
+
+#include "kmversion.h"
+
+namespace KMail {
+
+ struct about_data {
+ const char * name;
+ const char * desc;
+ const char * email;
+ const char * web;
+ };
+
+ // This file should not be changed by anybody other than the maintainer
+ // or the co-maintainer.
+
+ static const about_data authors[] = {
+ { "Ingo Kl\303\266cker", I18N_NOOP("Maintainer"),
+ "kloecker@kde.org", 0 },
+ { "Don Sanders", I18N_NOOP("Adopter and co-maintainer"),
+ "sanders@kde.org", 0 },
+ { "Stefan Taferner", I18N_NOOP("Original author"),
+ "taferner@kde.org", 0 },
+ { "Michael H\303\244ckel", I18N_NOOP("Former maintainer"),
+ "haeckel@kde.org", 0 },
+
+ { "Till Adam", I18N_NOOP("Core developer"),
+ "adam@kde.org", 0 },
+ { "Carsten Burghardt", I18N_NOOP("Core developer"),
+ "burghardt@kde.org", 0 },
+ { "Marc Mutz", I18N_NOOP("Core developer"),
+ "mutz@kde.org", 0 },
+ { "Daniel Naber", I18N_NOOP("Documentation"),
+ "daniel.naber@t-online.de", 0 },
+ { "Zack Rusin", I18N_NOOP("Core developer"),
+ "zack@kde.org", 0 },
+
+ { "Toyohiro Asukai", 0,
+ "toyohiro@ksmplus.com", 0 },
+ { "Waldo Bastian", 0,
+ "bastian@kde.org", 0 },
+ { "Ryan Breen", I18N_NOOP("system tray notification"),
+ "ryan@ryanbreen.com", 0 },
+ { "Steven Brown", 0,
+ "swbrown@ucsd.edu", 0 },
+ { "Matthias Kalle Dalheimer", 0,
+ "kalle@kde.org", 0 },
+ { "Matt Douhan", 0,
+ "matt@fruitsalad.org", 0 },
+ { "Cristi Dumitrescu", 0,
+ "cristid@chip.ro", 0 },
+ { "David Faure", 0,
+ "faure@kde.org", 0 },
+ { "Philippe Fremy", 0,
+ "pfremy@chez.com", 0 },
+ { "Kurt Granroth", 0,
+ "granroth@kde.org", 0 },
+ { "Andreas Gungl", I18N_NOOP("PGP 6 support and further enhancements of the encryption support"),
+ "a.gungl@gmx.de", 0 },
+ { "Steffen Hansen", 0,
+ "hansen@kde.org", 0 },
+ { "Igor Janssen", 0,
+ "rm@linux.ru.net", 0 },
+ { "Matt Johnston", 0,
+ "matt@caifex.org", 0 },
+ { "Christer Kaivo-oja", 0,
+ "whizkid@telia.com", 0 },
+ { "Lars Knoll", I18N_NOOP("Original encryption support\n"
+ "PGP 2 and PGP 5 support"),
+ "knoll@kde.org", 0 },
+ { "J. Nick Koston", I18N_NOOP("GnuPG support"),
+ "bdraco@darkorb.net", 0 },
+ { "Stephan Kulow", 0,
+ "coolo@kde.org", 0 },
+ { "Guillaume Laurent", 0,
+ "glaurent@telegraph-road.org", 0 },
+ { "Sam Magnuson", 0,
+ "sam@trolltech.com", 0 },
+ { "Laurent Montel", 0,
+ "lmontel@mandrakesoft.com", 0 },
+ { "Matt Newell", 0,
+ "newellm@proaxis.com", 0 },
+ { "Denis Perchine", 0,
+ "dyp@perchine.com", 0 },
+ { "Samuel Penn", 0,
+ "sam@bifrost.demon.co.uk", 0 },
+ { "Carsten Pfeiffer", 0,
+ "pfeiffer@kde.org", 0 },
+ { "Sven Radej", 0,
+ "radej@kde.org", 0 },
+ { "Mark Roberts", 0,
+ "mark@taurine.demon.co.uk", 0 },
+ { "Wolfgang Rohdewald", 0,
+ "wrohdewald@dplanet.ch", 0 },
+ { "Espen Sand", 0,
+ "espen@kde.org", 0 },
+ { "Aaron J. Seigo", 0,
+ "aseigo@olympusproject.org", 0 },
+ { "George Staikos", 0,
+ "staikos@kde.org", 0 },
+ { "Jason Stephenson", 0,
+ "panda@mis.net", 0 },
+ { "Jacek Stolarczyk", 0,
+ "jacek@mer.chemia.polsl.gliwice.pl", 0 },
+ { "Roberto S. Teixeira", 0,
+ "maragato@kde.org", 0 },
+ { "Bo Thorsen", 0,
+ "bo@sonofthor.dk", 0 },
+ { "Ronen Tzur", 0,
+ "rtzur@shani.net", 0 },
+ { "Mario Weilguni", 0,
+ "mweilguni@sime.com", 0 },
+ { "Wynn Wilkes", 0,
+ "wynnw@calderasystems.com", 0 },
+ { "Robert D. Williams", 0,
+ "rwilliams@kde.org", 0 },
+ { "Markus W\303\274bben", 0,
+ "markus.wuebben@kde.org", 0 },
+ { "Karl-Heinz Zimmer", 0,
+ "khz@kde.org", 0 }
+ };
+
+ static const about_data credits[] = {
+ { "Sam Abed", 0, 0, 0 }, // KConfigXT porting, smileys->emoticons replacement
+ { "Joern Ahrens", 0, 0, 0 }, // implement wish 77182 (Add some separators to "Mark Message as" popup menu)
+ { "Tom Albers", 0, 0, 0 }, // small fixes, bugzilla maintenance
+ { "Albert Cervera Areny", 0, 0, 0 }, // implemented wish 88309 (optional compression of attachments)
+ { "Patrick Audley", 0, 0, 0 }, // add optional graphical spam status to fancy headers
+ { "Benjamin Azan", 0, 0, 0 }, // implemented todo status handling
+ { "Albert Astals Cid", 0, 0, 0 }, // fix for bug:95441 (folder tree context menu doesn't show shortcuts assigned to the actions)
+ { "Cornelius Schumacher", 0, "schumacher@kde.org", 0 }, // implemented the new recipients editor and picker
+ { "Frederick Emmott", I18N_NOOP("Anti-virus support"),
+ "fred87@users.sf.net", 0 },
+ { "Sandro Giessl", 0, 0, 0 }, // frame width fixes for widget styles
+ { "Severin Greimel", 0, 0, 0 }, // several patches
+ { "Shaheed Haque", 0, 0, 0 }, // fix for bug:69744 (Resource folders: "Journals" should be "Journal")
+ { "Ingo Heeskens", 0, 0, 0 }, // implemented wish 34857 (per folder option for loading external references)
+ { "Kurt Hindenburg", 0, 0, 0 }, // implemented wish 89003 (delete whole thread)
+ { "Heiko Hund", I18N_NOOP("POP filters"),
+ "heiko@ist.eigentlich.net", 0 },
+ { "Torsten Kasch", 0, 0, 0 }, // crash fix for Solaris (cf. bug:68801)
+ { "Jason 'vanRijn' Kasper", 0, 0, 0 }, // implemented wish 79938 (configurable font for new/unread/important messages)
+ { "Martijn Klingens", 0, 0, 0 }, // fix keyboard navigation in the Status combo of the quick search
+ { "Christoph Kl\303\274nter", 0, 0, 0 }, // fix for bug:88216 (drag&drop from KAddressBook to the To: field)
+ { "Martin Koller", 0, 0, 0 }, // optional columns in the message list
+ { "Tobias K\303\266nig", 0, 0, 0 }, // edit recent addresses, store email<->OpenPGP key association in address book
+ { "Volker Krause", 0, 0, 0 }, // implemented KWallet support, fixed multiple bugs
+ { "Francois Kritzinger", 0, 0, 0 }, // fix bug in configuration dialog
+ { "Danny Kukawka", 0, 0, 0 }, // DCOP enhancements for better message importing
+ { "Roger Larsson", 0, 0, 0 }, // add name of checked account to status bar message
+ { "Jeffrey McGee", 0, 0, 0 }, // fix for bug:64251
+ { "Dirk M\303\274ller", 0, 0, 0 }, // KURL() fixes and qt_cast optimizations
+ { "OpenUsability", I18N_NOOP("Usability tests and improvements"), 0, "http://www.openusability.org" },
+ { "Mario Teijeiro Otero", 0, 0, 0 }, // various vendor annotations fixes
+ { "Simon Perreault", 0, 0, 0 }, // make the composer remember its "Use Fixed Font" setting (bug 49481)
+ { "Bernhard Reiter", I18N_NOOP("\xC3\x84gypten and Kroupware project management"),
+ "bernhard@intevation.de", 0 },
+ { "Edwin Schepers", 0, "yez@home.nl", 0 }, // composition of HTML messages
+ { "Jakob Schr\303\266ter", 0, 0, 0 }, // implemented wish 28319 (X-Face support)
+ { "Jan Simonson", I18N_NOOP("beta testing of PGP 6 support"),
+ "jan@simonson.pp.se", 0 },
+ { "Paul Sprakes", 0, 0, 0 }, // fix for bug:63619 (filter button in toolbar doesn't work), context menu clean up
+ { "Will Stephenson", 0, 0, 0 }, // added IM status indicator
+ { "Hasso Tepper", 0, 0, 0 }, // improve layout of recipients editor
+ { "Patrick S. Vogt", I18N_NOOP("timestamp for 'Transmission completed' status messages"),
+ "patrick.vogt@unibas.ch", 0 },
+ { "Jan-Oliver Wagner", I18N_NOOP("\xC3\x84gypten and Kroupware project management"),
+ "jan@intevation.de", 0 },
+ { "Wolfgang Westphal", I18N_NOOP("multiple encryption keys per address"),
+ "wolfgang.westphal@gmx.de", 0 },
+ { "Thorsten Zachmann", I18N_NOOP("POP filters"),
+ "t.zachmann@zagge.de", 0 },
+ { "Thomas Zander", 0, 0, 0 }
+ };
+
+ AboutData::AboutData()
+ : KAboutData( "kmail", I18N_NOOP("KMail"),KMAIL_VERSION,
+ I18N_NOOP("KDE Email Client"), License_GPL,
+ I18N_NOOP("(c) 1997-2008, The KMail developers"), 0,
+ "http://kontact.kde.org/kmail/" )
+ {
+ using KMail::authors;
+ using KMail::credits;
+ for ( unsigned int i = 0 ; i < sizeof authors / sizeof *authors ; ++i )
+ addAuthor( authors[i].name, authors[i].desc, authors[i].email, authors[i].web );
+ for ( unsigned int i = 0 ; i < sizeof credits / sizeof *credits ; ++i )
+ addCredit( credits[i].name, credits[i].desc, credits[i].email, credits[i].web );
+ }
+
+ AboutData::~AboutData() {
+
+ }
+
+} // namespace KMail
diff --git a/kmail/aboutdata.h b/kmail/aboutdata.h
new file mode 100644
index 00000000..b7c82f11
--- /dev/null
+++ b/kmail/aboutdata.h
@@ -0,0 +1,49 @@
+/* -*- c++ -*-
+ aboutdata.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_ABOUTDATA_H__
+#define __KMAIL_ABOUTDATA_H__
+
+#include <kaboutdata.h>
+#include <kdepimmacros.h>
+
+namespace KMail {
+
+ class KDE_EXPORT AboutData : public KAboutData {
+ public:
+ AboutData();
+ ~AboutData();
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_ABOUTDATA_H__
+
diff --git a/kmail/accountcombobox.cpp b/kmail/accountcombobox.cpp
new file mode 100644
index 00000000..afa8398e
--- /dev/null
+++ b/kmail/accountcombobox.cpp
@@ -0,0 +1,106 @@
+/** -*- mode: C++ -*-
+ * Copyright (c) 2004 David Faure <faure@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#include "accountcombobox.h"
+#include "kmfolder.h"
+#include "kmfolderdir.h"
+#include "accountmanager.h"
+#include <kdebug.h>
+
+using namespace KMail;
+
+AccountComboBox::AccountComboBox( QWidget* parent, const char* name )
+ : QComboBox( parent, name )
+{
+ connect( kmkernel->acctMgr(), SIGNAL( accountAdded( KMAccount* ) ),
+ this, SLOT( slotRefreshAccounts() ) );
+ connect( kmkernel->acctMgr(), SIGNAL( accountRemoved( KMAccount* ) ),
+ this, SLOT( slotRefreshAccounts() ) );
+ slotRefreshAccounts();
+}
+
+void AccountComboBox::slotRefreshAccounts()
+{
+ KMAccount* curr = currentAccount();
+ clear();
+ // Note that this won't take into account newly-created-in-configuredialog accounts
+ // until clicking OK or Apply. This would make this class much more complex
+ // (this would have to be different depending on whether this combo is in the
+ // configuration dialog or not...)
+ QStringList accountNames;
+ QValueList<KMAccount *> lst = applicableAccounts();
+ QValueList<KMAccount *>::ConstIterator it = lst.begin();
+ for ( ; it != lst.end() ; ++it )
+ accountNames.append( (*it)->name() );
+ kdDebug() << k_funcinfo << accountNames << endl;
+ insertStringList( accountNames );
+ if ( curr )
+ setCurrentAccount( curr );
+}
+
+
+void AccountComboBox::setCurrentAccount( KMAccount* account )
+{
+ int i = 0;
+ QValueList<KMAccount *> lst = applicableAccounts();
+ QValueList<KMAccount *>::ConstIterator it = lst.begin();
+ for ( ; it != lst.end() ; ++it, ++i ) {
+ if ( (*it) == account ) {
+ setCurrentItem( i );
+ return;
+ }
+ }
+}
+
+KMAccount* AccountComboBox::currentAccount() const
+{
+ int i = 0;
+ QValueList<KMAccount *> lst = applicableAccounts();
+ QValueList<KMAccount *>::ConstIterator it = lst.begin();
+ while ( it != lst.end() && i < currentItem() ) {
+ ++it;
+ ++i;
+ }
+ if ( it != lst.end() )
+ return *it;
+ return 0;
+}
+
+QValueList<KMAccount *> KMail::AccountComboBox::applicableAccounts() const
+{
+ QValueList<KMAccount *> lst;
+ for( KMAccount *a = kmkernel->acctMgr()->first(); a;
+ a = kmkernel->acctMgr()->next() ) {
+ if ( a && a->type() == "cachedimap" ) { //// ## proko2 hack. Need a list of allowed account types as ctor param
+ lst.append( a );
+ }
+ }
+ return lst;
+}
+
+#include "accountcombobox.moc"
diff --git a/kmail/accountcombobox.h b/kmail/accountcombobox.h
new file mode 100644
index 00000000..5c8d1bbd
--- /dev/null
+++ b/kmail/accountcombobox.h
@@ -0,0 +1,61 @@
+/* -*- mode: C++ -*-
+ * Copyright (c) 2004 David Faure <faure@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+#ifndef KMAIL_ACCOUNTCOMBOBOX_H
+#define KMAIL_ACCOUNTCOMBOBOX_H
+
+#include <qcombobox.h>
+
+class KMAccount;
+
+namespace KMail {
+
+/**
+ * A readonly combobox showing the accounts, to select one.
+ * WARNING: this widget is hardcoded to only display disconnected imap
+ * accounts, in this branch.
+ */
+class AccountComboBox : public QComboBox
+{
+ Q_OBJECT
+
+public:
+ AccountComboBox( QWidget* parent, const char* name = 0 );
+
+ void setCurrentAccount( KMAccount* account );
+ KMAccount* currentAccount() const;
+
+private slots:
+ void slotRefreshAccounts();
+private:
+ QValueList<KMAccount *> applicableAccounts() const;
+};
+
+
+} // namespace
+
+#endif
diff --git a/kmail/accountdialog.cpp b/kmail/accountdialog.cpp
new file mode 100644
index 00000000..b517925a
--- /dev/null
+++ b/kmail/accountdialog.cpp
@@ -0,0 +1,2352 @@
+/*
+ * kmail: KDE mail client
+ * This file: Copyright (C) 2000 Espen Sand, espen@kde.org
+ *
+ * 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 <config.h>
+
+#include "accountdialog.h"
+
+#include <qbuttongroup.h>
+#include <qcheckbox.h>
+#include <qlayout.h>
+#include <qtabwidget.h>
+#include <qradiobutton.h>
+#include <qvalidator.h>
+#include <qlabel.h>
+#include <qpushbutton.h>
+#include <qwhatsthis.h>
+#include <qhbox.h>
+#include <qcombobox.h>
+#include <qheader.h>
+#include <qtoolbutton.h>
+#include <qgrid.h>
+
+#include <kfiledialog.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kmessagebox.h>
+#include <knuminput.h>
+#include <kseparator.h>
+#include <kapplication.h>
+#include <kmessagebox.h>
+#include <kprotocolinfo.h>
+#include <kiconloader.h>
+#include <kpopupmenu.h>
+
+#include <netdb.h>
+#include <netinet/in.h>
+
+#include "sieveconfig.h"
+#include "kmacctmaildir.h"
+#include "kmacctlocal.h"
+#include "accountmanager.h"
+#include "popaccount.h"
+#include "kmacctimap.h"
+#include "kmacctcachedimap.h"
+#include "kmfoldermgr.h"
+#include "kmservertest.h"
+#include "protocols.h"
+#include "folderrequester.h"
+#include "kmmainwidget.h"
+#include "kmfolder.h"
+#include <libkpimidentities/identitymanager.h>
+#include <libkpimidentities/identitycombo.h>
+#include <libkpimidentities/identity.h>
+#include "globalsettings.h"
+
+#include <cassert>
+#include <stdlib.h>
+
+#ifdef HAVE_PATHS_H
+#include <paths.h> /* defines _PATH_MAILDIR */
+#endif
+
+#ifndef _PATH_MAILDIR
+#define _PATH_MAILDIR "/var/spool/mail"
+#endif
+
+namespace KMail {
+
+class ProcmailRCParser
+{
+public:
+ ProcmailRCParser(QString fileName = QString::null);
+ ~ProcmailRCParser();
+
+ QStringList getLockFilesList() const { return mLockFiles; }
+ QStringList getSpoolFilesList() const { return mSpoolFiles; }
+
+protected:
+ void processGlobalLock(const QString&);
+ void processLocalLock(const QString&);
+ void processVariableSetting(const QString&, int);
+ QString expandVars(const QString&);
+
+ QFile mProcmailrc;
+ QTextStream *mStream;
+ QStringList mLockFiles;
+ QStringList mSpoolFiles;
+ QAsciiDict<QString> mVars;
+};
+
+ProcmailRCParser::ProcmailRCParser(QString fname)
+ : mProcmailrc(fname),
+ mStream(new QTextStream(&mProcmailrc))
+{
+ mVars.setAutoDelete(true);
+
+ // predefined
+ mVars.insert( "HOME", new QString( QDir::homeDirPath() ) );
+
+ if( !fname || fname.isEmpty() ) {
+ fname = QDir::homeDirPath() + "/.procmailrc";
+ mProcmailrc.setName(fname);
+ }
+
+ QRegExp lockFileGlobal("^LOCKFILE=", true);
+ QRegExp lockFileLocal("^:0", true);
+
+ if( mProcmailrc.open(IO_ReadOnly) ) {
+
+ QString s;
+
+ while( !mStream->eof() ) {
+
+ s = mStream->readLine().stripWhiteSpace();
+
+ if( s[0] == '#' ) continue; // skip comments
+
+ int commentPos = -1;
+
+ if( (commentPos = s.find('#')) > -1 ) {
+ // get rid of trailing comment
+ s.truncate(commentPos);
+ s = s.stripWhiteSpace();
+ }
+
+ if( lockFileGlobal.search(s) != -1 ) {
+ processGlobalLock(s);
+ } else if( lockFileLocal.search(s) != -1 ) {
+ processLocalLock(s);
+ } else if( int i = s.find('=') ) {
+ processVariableSetting(s,i);
+ }
+ }
+
+ }
+ QString default_Location = getenv("MAIL");
+
+ if (default_Location.isNull()) {
+ default_Location = _PATH_MAILDIR;
+ default_Location += '/';
+ default_Location += getenv("USER");
+ }
+ if ( !mSpoolFiles.contains(default_Location) )
+ mSpoolFiles << default_Location;
+
+ default_Location = default_Location + ".lock";
+ if ( !mLockFiles.contains(default_Location) )
+ mLockFiles << default_Location;
+}
+
+ProcmailRCParser::~ProcmailRCParser()
+{
+ delete mStream;
+}
+
+void
+ProcmailRCParser::processGlobalLock(const QString &s)
+{
+ QString val = expandVars(s.mid(s.find('=') + 1).stripWhiteSpace());
+ if ( !mLockFiles.contains(val) )
+ mLockFiles << val;
+}
+
+void
+ProcmailRCParser::processLocalLock(const QString &s)
+{
+ QString val;
+ int colonPos = s.findRev(':');
+
+ if (colonPos > 0) { // we don't care about the leading one
+ val = s.mid(colonPos + 1).stripWhiteSpace();
+
+ if ( val.length() ) {
+ // user specified a lockfile, so process it
+ //
+ val = expandVars(val);
+ if( val[0] != '/' && mVars.find("MAILDIR") )
+ val.insert(0, *(mVars["MAILDIR"]) + '/');
+ } // else we'll deduce the lockfile name one we
+ // get the spoolfile name
+ }
+
+ // parse until we find the spoolfile
+ QString line, prevLine;
+ do {
+ prevLine = line;
+ line = mStream->readLine().stripWhiteSpace();
+ } while ( !mStream->eof() && (line[0] == '*' ||
+ prevLine[prevLine.length() - 1] == '\\' ));
+
+ if( line[0] != '!' && line[0] != '|' && line[0] != '{' ) {
+ // this is a filename, expand it
+ //
+ line = line.stripWhiteSpace();
+ line = expandVars(line);
+
+ // prepend default MAILDIR if needed
+ if( line[0] != '/' && mVars.find("MAILDIR") )
+ line.insert(0, *(mVars["MAILDIR"]) + '/');
+
+ // now we have the spoolfile name
+ if ( !mSpoolFiles.contains(line) )
+ mSpoolFiles << line;
+
+ if( colonPos > 0 && (!val || val.isEmpty()) ) {
+ // there is a local lockfile, but the user didn't
+ // specify the name so compute it from the spoolfile's name
+ val = line;
+
+ // append lock extension
+ if( mVars.find("LOCKEXT") )
+ val += *(mVars["LOCKEXT"]);
+ else
+ val += ".lock";
+ }
+
+ if ( !val.isNull() && !mLockFiles.contains(val) ) {
+ mLockFiles << val;
+ }
+ }
+
+}
+
+void
+ProcmailRCParser::processVariableSetting(const QString &s, int eqPos)
+{
+ if( eqPos == -1) return;
+
+ QString varName = s.left(eqPos),
+ varValue = expandVars(s.mid(eqPos + 1).stripWhiteSpace());
+
+ mVars.insert(varName.latin1(), new QString(varValue));
+}
+
+QString
+ProcmailRCParser::expandVars(const QString &s)
+{
+ if( s.isEmpty()) return s;
+
+ QString expS = s;
+
+ QAsciiDictIterator<QString> it( mVars ); // iterator for dict
+
+ while ( it.current() ) {
+ expS.replace(QString::fromLatin1("$") + it.currentKey(), *it.current());
+ ++it;
+ }
+
+ return expS;
+}
+
+
+
+AccountDialog::AccountDialog( const QString & caption, KMAccount *account,
+ QWidget *parent, const char *name, bool modal )
+ : KDialogBase( parent, name, modal, caption, Ok|Cancel|Help, Ok, true ),
+ mAccount( account ),
+ mServerTest( 0 ),
+ mCurCapa( AllCapa ),
+ mCapaNormal( AllCapa ),
+ mCapaSSL( AllCapa ),
+ mCapaTLS( AllCapa ),
+ mSieveConfigEditor( 0 )
+{
+ mValidator = new QRegExpValidator( QRegExp( "[A-Za-z0-9-_:.]*" ), 0 );
+ setHelp("receiving-mail");
+
+ QString accountType = mAccount->type();
+
+ if( accountType == "local" )
+ {
+ makeLocalAccountPage();
+ }
+ else if( accountType == "maildir" )
+ {
+ makeMaildirAccountPage();
+ }
+ else if( accountType == "pop" )
+ {
+ makePopAccountPage();
+ }
+ else if( accountType == "imap" )
+ {
+ makeImapAccountPage();
+ }
+ else if( accountType == "cachedimap" )
+ {
+ makeImapAccountPage(true);
+ }
+ else
+ {
+ QString msg = i18n( "Account type is not supported." );
+ KMessageBox::information( topLevelWidget(),msg,i18n("Configure Account") );
+ return;
+ }
+
+ setupSettings();
+}
+
+AccountDialog::~AccountDialog()
+{
+ delete mValidator;
+ mValidator = 0;
+ delete mServerTest;
+ mServerTest = 0;
+}
+
+void AccountDialog::makeLocalAccountPage()
+{
+ ProcmailRCParser procmailrcParser;
+ QFrame *page = makeMainWidget();
+ QGridLayout *topLayout = new QGridLayout( page, 12, 3, 0, spacingHint() );
+ topLayout->addColSpacing( 1, fontMetrics().maxWidth()*15 );
+ topLayout->setRowStretch( 11, 10 );
+ topLayout->setColStretch( 1, 10 );
+
+ mLocal.titleLabel = new QLabel( i18n("Account Type: Local Account"), page );
+ topLayout->addMultiCellWidget( mLocal.titleLabel, 0, 0, 0, 2 );
+ QFont titleFont( mLocal.titleLabel->font() );
+ titleFont.setBold( true );
+ mLocal.titleLabel->setFont( titleFont );
+ KSeparator *hline = new KSeparator( KSeparator::HLine, page);
+ topLayout->addMultiCellWidget( hline, 1, 1, 0, 2 );
+
+ QLabel *label = new QLabel( i18n("Account &name:"), page );
+ topLayout->addWidget( label, 2, 0 );
+ mLocal.nameEdit = new KLineEdit( page );
+ label->setBuddy( mLocal.nameEdit );
+ topLayout->addWidget( mLocal.nameEdit, 2, 1 );
+
+ label = new QLabel( i18n("File &location:"), page );
+ topLayout->addWidget( label, 3, 0 );
+ mLocal.locationEdit = new QComboBox( true, page );
+ label->setBuddy( mLocal.locationEdit );
+ topLayout->addWidget( mLocal.locationEdit, 3, 1 );
+ mLocal.locationEdit->insertStringList(procmailrcParser.getSpoolFilesList());
+
+ QPushButton *choose = new QPushButton( i18n("Choo&se..."), page );
+ choose->setAutoDefault( false );
+ connect( choose, SIGNAL(clicked()), this, SLOT(slotLocationChooser()) );
+ topLayout->addWidget( choose, 3, 2 );
+
+ QButtonGroup *group = new QButtonGroup(i18n("Locking Method"), page );
+ group->setColumnLayout(0, Qt::Horizontal);
+ group->layout()->setSpacing( 0 );
+ group->layout()->setMargin( 0 );
+ QGridLayout *groupLayout = new QGridLayout( group->layout() );
+ groupLayout->setAlignment( Qt::AlignTop );
+ groupLayout->setSpacing( 6 );
+ groupLayout->setMargin( 11 );
+
+ mLocal.lockProcmail = new QRadioButton( i18n("Procmail loc&kfile:"), group);
+ groupLayout->addWidget(mLocal.lockProcmail, 0, 0);
+
+ mLocal.procmailLockFileName = new QComboBox( true, group );
+ groupLayout->addWidget(mLocal.procmailLockFileName, 0, 1);
+ mLocal.procmailLockFileName->insertStringList(procmailrcParser.getLockFilesList());
+ mLocal.procmailLockFileName->setEnabled(false);
+
+ QObject::connect(mLocal.lockProcmail, SIGNAL(toggled(bool)),
+ mLocal.procmailLockFileName, SLOT(setEnabled(bool)));
+
+ mLocal.lockMutt = new QRadioButton(
+ i18n("&Mutt dotlock"), group);
+ groupLayout->addWidget(mLocal.lockMutt, 1, 0);
+
+ mLocal.lockMuttPriv = new QRadioButton(
+ i18n("M&utt dotlock privileged"), group);
+ groupLayout->addWidget(mLocal.lockMuttPriv, 2, 0);
+
+ mLocal.lockFcntl = new QRadioButton(
+ i18n("&FCNTL"), group);
+ groupLayout->addWidget(mLocal.lockFcntl, 3, 0);
+
+ mLocal.lockNone = new QRadioButton(
+ i18n("Non&e (use with care)"), group);
+ groupLayout->addWidget(mLocal.lockNone, 4, 0);
+
+ topLayout->addMultiCellWidget( group, 4, 4, 0, 2 );
+
+#if 0
+ QHBox* resourceHB = new QHBox( page );
+ resourceHB->setSpacing( 11 );
+ mLocal.resourceCheck =
+ new QCheckBox( i18n( "Account for semiautomatic resource handling" ), resourceHB );
+ mLocal.resourceClearButton =
+ new QPushButton( i18n( "Clear" ), resourceHB );
+ QWhatsThis::add( mLocal.resourceClearButton,
+ i18n( "Delete all allocations for the resource represented by this account." ) );
+ mLocal.resourceClearButton->setEnabled( false );
+ connect( mLocal.resourceCheck, SIGNAL( toggled(bool) ),
+ mLocal.resourceClearButton, SLOT( setEnabled(bool) ) );
+ connect( mLocal.resourceClearButton, SIGNAL( clicked() ),
+ this, SLOT( slotClearResourceAllocations() ) );
+ mLocal.resourceClearPastButton =
+ new QPushButton( i18n( "Clear Past" ), resourceHB );
+ mLocal.resourceClearPastButton->setEnabled( false );
+ connect( mLocal.resourceCheck, SIGNAL( toggled(bool) ),
+ mLocal.resourceClearPastButton, SLOT( setEnabled(bool) ) );
+ QWhatsThis::add( mLocal.resourceClearPastButton,
+ i18n( "Delete all outdated allocations for the resource represented by this account." ) );
+ connect( mLocal.resourceClearPastButton, SIGNAL( clicked() ),
+ this, SLOT( slotClearPastResourceAllocations() ) );
+ topLayout->addMultiCellWidget( resourceHB, 5, 5, 0, 2 );
+#endif
+
+ mLocal.includeInCheck =
+ new QCheckBox( i18n("Include in m&anual mail check"),
+ page );
+ topLayout->addMultiCellWidget( mLocal.includeInCheck, 5, 5, 0, 2 );
+
+ mLocal.intervalCheck =
+ new QCheckBox( i18n("Enable &interval mail checking"), page );
+ topLayout->addMultiCellWidget( mLocal.intervalCheck, 6, 6, 0, 2 );
+ connect( mLocal.intervalCheck, SIGNAL(toggled(bool)),
+ this, SLOT(slotEnableLocalInterval(bool)) );
+ mLocal.intervalLabel = new QLabel( i18n("Check inter&val:"), page );
+ topLayout->addWidget( mLocal.intervalLabel, 7, 0 );
+ mLocal.intervalSpin = new KIntNumInput( page );
+ mLocal.intervalLabel->setBuddy( mLocal.intervalSpin );
+ mLocal.intervalSpin->setRange( GlobalSettings::self()->minimumCheckInterval(), 10000, 1, false );
+ mLocal.intervalSpin->setSuffix( i18n(" min") );
+ mLocal.intervalSpin->setValue( defaultmailcheckintervalmin );
+ topLayout->addWidget( mLocal.intervalSpin, 7, 1 );
+
+ label = new QLabel( i18n("&Destination folder:"), page );
+ topLayout->addWidget( label, 8, 0 );
+ mLocal.folderCombo = new QComboBox( false, page );
+ label->setBuddy( mLocal.folderCombo );
+ topLayout->addWidget( mLocal.folderCombo, 8, 1 );
+
+ label = new QLabel( i18n("&Pre-command:"), page );
+ topLayout->addWidget( label, 9, 0 );
+ mLocal.precommand = new KLineEdit( page );
+ label->setBuddy( mLocal.precommand );
+ topLayout->addWidget( mLocal.precommand, 9, 1 );
+
+ mLocal.identityLabel = new QLabel( i18n("Identity:"), page );
+ topLayout->addWidget( mLocal.identityLabel, 10, 0 );
+ mLocal.identityCombo = new KPIM::IdentityCombo(kmkernel->identityManager(), page );
+ mLocal.identityLabel->setBuddy( mLocal.identityCombo );
+ topLayout->addWidget( mLocal.identityCombo, 10, 1 );
+
+ connect(kapp,SIGNAL(kdisplayFontChanged()),SLOT(slotFontChanged()));
+}
+
+void AccountDialog::makeMaildirAccountPage()
+{
+ ProcmailRCParser procmailrcParser;
+
+ QFrame *page = makeMainWidget();
+ QGridLayout *topLayout = new QGridLayout( page, 11, 3, 0, spacingHint() );
+ topLayout->addColSpacing( 1, fontMetrics().maxWidth()*15 );
+ topLayout->setRowStretch( 11, 10 );
+ topLayout->setColStretch( 1, 10 );
+
+ mMaildir.titleLabel = new QLabel( i18n("Account Type: Maildir Account"), page );
+ topLayout->addMultiCellWidget( mMaildir.titleLabel, 0, 0, 0, 2 );
+ QFont titleFont( mMaildir.titleLabel->font() );
+ titleFont.setBold( true );
+ mMaildir.titleLabel->setFont( titleFont );
+ QFrame *hline = new QFrame( page );
+ hline->setFrameStyle( QFrame::Sunken | QFrame::HLine );
+ topLayout->addMultiCellWidget( hline, 1, 1, 0, 2 );
+
+ mMaildir.nameEdit = new KLineEdit( page );
+ topLayout->addWidget( mMaildir.nameEdit, 2, 1 );
+ QLabel *label = new QLabel( mMaildir.nameEdit, i18n("Account &name:"), page );
+ topLayout->addWidget( label, 2, 0 );
+
+ mMaildir.locationEdit = new QComboBox( true, page );
+ topLayout->addWidget( mMaildir.locationEdit, 3, 1 );
+ mMaildir.locationEdit->insertStringList(procmailrcParser.getSpoolFilesList());
+ label = new QLabel( mMaildir.locationEdit, i18n("Folder &location:"), page );
+ topLayout->addWidget( label, 3, 0 );
+
+ QPushButton *choose = new QPushButton( i18n("Choo&se..."), page );
+ choose->setAutoDefault( false );
+ connect( choose, SIGNAL(clicked()), this, SLOT(slotMaildirChooser()) );
+ topLayout->addWidget( choose, 3, 2 );
+
+#if 0
+ QHBox* resourceHB = new QHBox( page );
+ resourceHB->setSpacing( 11 );
+ mMaildir.resourceCheck =
+ new QCheckBox( i18n( "Account for semiautomatic resource handling" ), resourceHB );
+ mMaildir.resourceClearButton =
+ new QPushButton( i18n( "Clear" ), resourceHB );
+ mMaildir.resourceClearButton->setEnabled( false );
+ connect( mMaildir.resourceCheck, SIGNAL( toggled(bool) ),
+ mMaildir.resourceClearButton, SLOT( setEnabled(bool) ) );
+ QWhatsThis::add( mMaildir.resourceClearButton,
+ i18n( "Delete all allocations for the resource represented by this account." ) );
+ connect( mMaildir.resourceClearButton, SIGNAL( clicked() ),
+ this, SLOT( slotClearResourceAllocations() ) );
+ mMaildir.resourceClearPastButton =
+ new QPushButton( i18n( "Clear Past" ), resourceHB );
+ mMaildir.resourceClearPastButton->setEnabled( false );
+ connect( mMaildir.resourceCheck, SIGNAL( toggled(bool) ),
+ mMaildir.resourceClearPastButton, SLOT( setEnabled(bool) ) );
+ QWhatsThis::add( mMaildir.resourceClearPastButton,
+ i18n( "Delete all outdated allocations for the resource represented by this account." ) );
+ connect( mMaildir.resourceClearPastButton, SIGNAL( clicked() ),
+ this, SLOT( slotClearPastResourceAllocations() ) );
+ topLayout->addMultiCellWidget( resourceHB, 4, 4, 0, 2 );
+#endif
+
+ mMaildir.includeInCheck =
+ new QCheckBox( i18n("Include in &manual mail check"), page );
+ topLayout->addMultiCellWidget( mMaildir.includeInCheck, 4, 4, 0, 2 );
+
+ mMaildir.intervalCheck =
+ new QCheckBox( i18n("Enable &interval mail checking"), page );
+ topLayout->addMultiCellWidget( mMaildir.intervalCheck, 5, 5, 0, 2 );
+ connect( mMaildir.intervalCheck, SIGNAL(toggled(bool)),
+ this, SLOT(slotEnableMaildirInterval(bool)) );
+ mMaildir.intervalLabel = new QLabel( i18n("Check inter&val:"), page );
+ topLayout->addWidget( mMaildir.intervalLabel, 6, 0 );
+ mMaildir.intervalSpin = new KIntNumInput( page );
+ mMaildir.intervalSpin->setRange( GlobalSettings::self()->minimumCheckInterval(), 10000, 1, false );
+ mMaildir.intervalSpin->setSuffix( i18n(" min") );
+ mMaildir.intervalSpin->setValue( defaultmailcheckintervalmin );
+ mMaildir.intervalLabel->setBuddy( mMaildir.intervalSpin );
+ topLayout->addWidget( mMaildir.intervalSpin, 6, 1 );
+
+ mMaildir.folderCombo = new QComboBox( false, page );
+ topLayout->addWidget( mMaildir.folderCombo, 7, 1 );
+ label = new QLabel( mMaildir.folderCombo,
+ i18n("&Destination folder:"), page );
+ topLayout->addWidget( label, 7, 0 );
+
+ mMaildir.precommand = new KLineEdit( page );
+ topLayout->addWidget( mMaildir.precommand, 8, 1 );
+ label = new QLabel( mMaildir.precommand, i18n("&Pre-command:"), page );
+ topLayout->addWidget( label, 8, 0 );
+
+
+ mMaildir.identityLabel = new QLabel( i18n("Identity:"), page );
+ topLayout->addWidget( mMaildir.identityLabel, 9, 0 );
+ mMaildir.identityCombo = new KPIM::IdentityCombo(kmkernel->identityManager(), page );
+ mMaildir.identityLabel->setBuddy( mMaildir.identityCombo );
+ topLayout->addWidget( mMaildir.identityCombo, 9, 1 );
+
+ connect(kapp,SIGNAL(kdisplayFontChanged()),SLOT(slotFontChanged()));
+}
+
+
+void AccountDialog::makePopAccountPage()
+{
+ QFrame *page = makeMainWidget();
+ QVBoxLayout *topLayout = new QVBoxLayout( page, 0, spacingHint() );
+
+ mPop.titleLabel = new QLabel( page );
+ mPop.titleLabel->setText( i18n("Account Type: POP Account") );
+ QFont titleFont( mPop.titleLabel->font() );
+ titleFont.setBold( true );
+ mPop.titleLabel->setFont( titleFont );
+ topLayout->addWidget( mPop.titleLabel );
+ KSeparator *hline = new KSeparator( KSeparator::HLine, page);
+ topLayout->addWidget( hline );
+
+ QTabWidget *tabWidget = new QTabWidget(page);
+ topLayout->addWidget( tabWidget );
+
+ QWidget *page1 = new QWidget( tabWidget );
+ tabWidget->addTab( page1, i18n("&General") );
+
+ QGridLayout *grid = new QGridLayout( page1, 16, 2, marginHint(), spacingHint() );
+ grid->addColSpacing( 1, fontMetrics().maxWidth()*15 );
+ grid->setRowStretch( 15, 10 );
+ grid->setColStretch( 1, 10 );
+
+ QLabel *label = new QLabel( i18n("Account &name:"), page1 );
+ grid->addWidget( label, 0, 0 );
+ mPop.nameEdit = new KLineEdit( page1 );
+ label->setBuddy( mPop.nameEdit );
+ grid->addWidget( mPop.nameEdit, 0, 1 );
+
+ label = new QLabel( i18n("&Login:"), page1 );
+ QWhatsThis::add( label, i18n("Your Internet Service Provider gave you a <em>user name</em> which is used to authenticate you with their servers. It usually is the first part of your email address (the part before <em>@</em>).") );
+ grid->addWidget( label, 1, 0 );
+ mPop.loginEdit = new KLineEdit( page1 );
+ label->setBuddy( mPop.loginEdit );
+ grid->addWidget( mPop.loginEdit, 1, 1 );
+
+ label = new QLabel( i18n("P&assword:"), page1 );
+ grid->addWidget( label, 2, 0 );
+ mPop.passwordEdit = new KLineEdit( page1 );
+ mPop.passwordEdit->setEchoMode( QLineEdit::Password );
+ label->setBuddy( mPop.passwordEdit );
+ grid->addWidget( mPop.passwordEdit, 2, 1 );
+
+ label = new QLabel( i18n("Ho&st:"), page1 );
+ grid->addWidget( label, 3, 0 );
+ mPop.hostEdit = new KLineEdit( page1 );
+ // only letters, digits, '-', '.', ':' (IPv6) and '_' (for Windows
+ // compatibility) are allowed
+ mPop.hostEdit->setValidator(mValidator);
+ label->setBuddy( mPop.hostEdit );
+ grid->addWidget( mPop.hostEdit, 3, 1 );
+
+ label = new QLabel( i18n("&Port:"), page1 );
+ grid->addWidget( label, 4, 0 );
+ mPop.portEdit = new KLineEdit( page1 );
+ mPop.portEdit->setValidator( new QIntValidator(this) );
+ label->setBuddy( mPop.portEdit );
+ grid->addWidget( mPop.portEdit, 4, 1 );
+
+ mPop.storePasswordCheck =
+ new QCheckBox( i18n("Sto&re POP password"), page1 );
+ QWhatsThis::add( mPop.storePasswordCheck,
+ i18n("Check this option to have KMail store "
+ "the password.\nIf KWallet is available "
+ "the password will be stored there which is considered "
+ "safe.\nHowever, if KWallet is not available, "
+ "the password will be stored in KMail's configuration "
+ "file. The password is stored in an "
+ "obfuscated format, but should not be "
+ "considered secure from decryption efforts "
+ "if access to the configuration file is obtained.") );
+ grid->addMultiCellWidget( mPop.storePasswordCheck, 5, 5, 0, 1 );
+
+ mPop.leaveOnServerCheck =
+ new QCheckBox( i18n("Lea&ve fetched messages on the server"), page1 );
+ connect( mPop.leaveOnServerCheck, SIGNAL( clicked() ),
+ this, SLOT( slotLeaveOnServerClicked() ) );
+ grid->addMultiCellWidget( mPop.leaveOnServerCheck, 6, 6, 0, 1 );
+ QHBox *afterDaysBox = new QHBox( page1 );
+ afterDaysBox->setSpacing( KDialog::spacingHint() );
+ mPop.leaveOnServerDaysCheck =
+ new QCheckBox( i18n("Leave messages on the server for"), afterDaysBox );
+ connect( mPop.leaveOnServerDaysCheck, SIGNAL( toggled(bool) ),
+ this, SLOT( slotEnableLeaveOnServerDays(bool)) );
+ mPop.leaveOnServerDaysSpin = new KIntNumInput( afterDaysBox );
+ mPop.leaveOnServerDaysSpin->setRange( 1, 365, 1, false );
+ connect( mPop.leaveOnServerDaysSpin, SIGNAL(valueChanged(int)),
+ SLOT(slotLeaveOnServerDaysChanged(int)));
+ mPop.leaveOnServerDaysSpin->setValue( 1 );
+ afterDaysBox->setStretchFactor( mPop.leaveOnServerDaysSpin, 1 );
+ grid->addMultiCellWidget( afterDaysBox, 7, 7, 0, 1 );
+ QHBox *leaveOnServerCountBox = new QHBox( page1 );
+ leaveOnServerCountBox->setSpacing( KDialog::spacingHint() );
+ mPop.leaveOnServerCountCheck =
+ new QCheckBox( i18n("Keep only the last"), leaveOnServerCountBox );
+ connect( mPop.leaveOnServerCountCheck, SIGNAL( toggled(bool) ),
+ this, SLOT( slotEnableLeaveOnServerCount(bool)) );
+ mPop.leaveOnServerCountSpin = new KIntNumInput( leaveOnServerCountBox );
+ mPop.leaveOnServerCountSpin->setRange( 1, 999999, 1, false );
+ connect( mPop.leaveOnServerCountSpin, SIGNAL(valueChanged(int)),
+ SLOT(slotLeaveOnServerCountChanged(int)));
+ mPop.leaveOnServerCountSpin->setValue( 100 );
+ grid->addMultiCellWidget( leaveOnServerCountBox, 8, 8, 0, 1 );
+ QHBox *leaveOnServerSizeBox = new QHBox( page1 );
+ leaveOnServerSizeBox->setSpacing( KDialog::spacingHint() );
+ mPop.leaveOnServerSizeCheck =
+ new QCheckBox( i18n("Keep only the last"), leaveOnServerSizeBox );
+ connect( mPop.leaveOnServerSizeCheck, SIGNAL( toggled(bool) ),
+ this, SLOT( slotEnableLeaveOnServerSize(bool)) );
+ mPop.leaveOnServerSizeSpin = new KIntNumInput( leaveOnServerSizeBox );
+ mPop.leaveOnServerSizeSpin->setRange( 1, 999999, 1, false );
+ mPop.leaveOnServerSizeSpin->setSuffix( i18n(" MB") );
+ mPop.leaveOnServerSizeSpin->setValue( 10 );
+ grid->addMultiCellWidget( leaveOnServerSizeBox, 9, 9, 0, 1 );
+#if 0
+ QHBox *resourceHB = new QHBox( page1 );
+ resourceHB->setSpacing( 11 );
+ mPop.resourceCheck =
+ new QCheckBox( i18n( "Account for semiautomatic resource handling" ), resourceHB );
+ mPop.resourceClearButton =
+ new QPushButton( i18n( "Clear" ), resourceHB );
+ mPop.resourceClearButton->setEnabled( false );
+ connect( mPop.resourceCheck, SIGNAL( toggled(bool) ),
+ mPop.resourceClearButton, SLOT( setEnabled(bool) ) );
+ QWhatsThis::add( mPop.resourceClearButton,
+ i18n( "Delete all allocations for the resource represented by this account." ) );
+ connect( mPop.resourceClearButton, SIGNAL( clicked() ),
+ this, SLOT( slotClearResourceAllocations() ) );
+ mPop.resourceClearPastButton =
+ new QPushButton( i18n( "Clear Past" ), resourceHB );
+ mPop.resourceClearPastButton->setEnabled( false );
+ connect( mPop.resourceCheck, SIGNAL( toggled(bool) ),
+ mPop.resourceClearPastButton, SLOT( setEnabled(bool) ) );
+ QWhatsThis::add( mPop.resourceClearPastButton,
+ i18n( "Delete all outdated allocations for the resource represented by this account." ) );
+ connect( mPop.resourceClearPastButton, SIGNAL( clicked() ),
+ this, SLOT( slotClearPastResourceAllocations() ) );
+ grid->addMultiCellWidget( resourceHB, 10, 10, 0, 2 );
+#endif
+
+ mPop.includeInCheck =
+ new QCheckBox( i18n("Include in man&ual mail check"), page1 );
+ grid->addMultiCellWidget( mPop.includeInCheck, 10, 10, 0, 1 );
+
+ QHBox * hbox = new QHBox( page1 );
+ hbox->setSpacing( KDialog::spacingHint() );
+ mPop.filterOnServerCheck =
+ new QCheckBox( i18n("&Filter messages if they are greater than"), hbox );
+ mPop.filterOnServerSizeSpin = new KIntNumInput ( hbox );
+ mPop.filterOnServerSizeSpin->setEnabled( false );
+ hbox->setStretchFactor( mPop.filterOnServerSizeSpin, 1 );
+ mPop.filterOnServerSizeSpin->setRange( 1, 10000000, 100, false );
+ connect(mPop.filterOnServerSizeSpin, SIGNAL(valueChanged(int)),
+ SLOT(slotFilterOnServerSizeChanged(int)));
+ mPop.filterOnServerSizeSpin->setValue( 50000 );
+ grid->addMultiCellWidget( hbox, 11, 11, 0, 1 );
+ connect( mPop.filterOnServerCheck, SIGNAL(toggled(bool)),
+ mPop.filterOnServerSizeSpin, SLOT(setEnabled(bool)) );
+ connect( mPop.filterOnServerCheck, SIGNAL( clicked() ),
+ this, SLOT( slotFilterOnServerClicked() ) );
+ QString msg = i18n("If you select this option, POP Filters will be used to "
+ "decide what to do with messages. You can then select "
+ "to download, delete or keep them on the server." );
+ QWhatsThis::add( mPop.filterOnServerCheck, msg );
+ QWhatsThis::add( mPop.filterOnServerSizeSpin, msg );
+
+ mPop.intervalCheck =
+ new QCheckBox( i18n("Enable &interval mail checking"), page1 );
+ grid->addMultiCellWidget( mPop.intervalCheck, 12, 12, 0, 1 );
+ connect( mPop.intervalCheck, SIGNAL(toggled(bool)),
+ this, SLOT(slotEnablePopInterval(bool)) );
+ mPop.intervalLabel = new QLabel( i18n("Chec&k interval:"), page1 );
+ grid->addWidget( mPop.intervalLabel, 13, 0 );
+ mPop.intervalSpin = new KIntNumInput( page1 );
+ mPop.intervalSpin->setRange( GlobalSettings::self()->minimumCheckInterval(), 10000, 1, false );
+ mPop.intervalSpin->setSuffix( i18n(" min") );
+ mPop.intervalSpin->setValue( defaultmailcheckintervalmin );
+ mPop.intervalLabel->setBuddy( mPop.intervalSpin );
+ grid->addWidget( mPop.intervalSpin, 13, 1 );
+
+ label = new QLabel( i18n("Des&tination folder:"), page1 );
+ grid->addWidget( label, 14, 0 );
+ mPop.folderCombo = new QComboBox( false, page1 );
+ label->setBuddy( mPop.folderCombo );
+ grid->addWidget( mPop.folderCombo, 14, 1 );
+
+ label = new QLabel( i18n("Pre-com&mand:"), page1 );
+ grid->addWidget( label, 15, 0 );
+ mPop.precommand = new KLineEdit( page1 );
+ label->setBuddy(mPop.precommand);
+ grid->addWidget( mPop.precommand, 15, 1 );
+
+ mPop.identityLabel = new QLabel( i18n("Identity:"), page1 );
+ grid->addWidget( mPop.identityLabel, 16, 0 );
+ mPop.identityCombo = new KPIM::IdentityCombo(kmkernel->identityManager(), page1 );
+ mPop.identityLabel->setBuddy( mPop.identityCombo );
+ grid->addWidget( mPop.identityCombo, 16, 1 );
+
+ QWidget *page2 = new QWidget( tabWidget );
+ tabWidget->addTab( page2, i18n("&Extras") );
+ QVBoxLayout *vlay = new QVBoxLayout( page2, marginHint(), spacingHint() );
+
+ vlay->addSpacing( KDialog::spacingHint() );
+
+ QHBoxLayout *buttonLay = new QHBoxLayout( vlay );
+ mPop.checkCapabilities =
+ new QPushButton( i18n("Check &What the Server Supports"), page2 );
+ connect(mPop.checkCapabilities, SIGNAL(clicked()),
+ SLOT(slotCheckPopCapabilities()));
+ buttonLay->addStretch();
+ buttonLay->addWidget( mPop.checkCapabilities );
+ buttonLay->addStretch();
+
+ vlay->addSpacing( KDialog::spacingHint() );
+
+ mPop.encryptionGroup = new QButtonGroup( 1, Qt::Horizontal,
+ i18n("Encryption"), page2 );
+ mPop.encryptionNone =
+ new QRadioButton( i18n("&None"), mPop.encryptionGroup );
+ mPop.encryptionSSL =
+ new QRadioButton( i18n("Use &SSL for secure mail download"),
+ mPop.encryptionGroup );
+ mPop.encryptionTLS =
+ new QRadioButton( i18n("Use &TLS for secure mail download"),
+ mPop.encryptionGroup );
+ connect(mPop.encryptionGroup, SIGNAL(clicked(int)),
+ SLOT(slotPopEncryptionChanged(int)));
+ vlay->addWidget( mPop.encryptionGroup );
+
+ mPop.authGroup = new QButtonGroup( 1, Qt::Horizontal,
+ i18n("Authentication Method"), page2 );
+ mPop.authUser = new QRadioButton( i18n("Clear te&xt") , mPop.authGroup,
+ "auth clear text" );
+ mPop.authLogin = new QRadioButton( i18n("Please translate this "
+ "authentication method only if you have a good reason", "&LOGIN"),
+ mPop.authGroup, "auth login" );
+ mPop.authPlain = new QRadioButton( i18n("Please translate this "
+ "authentication method only if you have a good reason", "&PLAIN"),
+ mPop.authGroup, "auth plain" );
+ mPop.authCRAM_MD5 = new QRadioButton( i18n("CRAM-MD&5"), mPop.authGroup, "auth cram-md5" );
+ mPop.authDigestMd5 = new QRadioButton( i18n("&DIGEST-MD5"), mPop.authGroup, "auth digest-md5" );
+ mPop.authNTLM = new QRadioButton( i18n("&NTLM"), mPop.authGroup, "auth ntlm" );
+ mPop.authGSSAPI = new QRadioButton( i18n("&GSSAPI"), mPop.authGroup, "auth gssapi" );
+ if ( KProtocolInfo::capabilities("pop3").contains("SASL") == 0 )
+ {
+ mPop.authNTLM->hide();
+ mPop.authGSSAPI->hide();
+ }
+ mPop.authAPOP = new QRadioButton( i18n("&APOP"), mPop.authGroup, "auth apop" );
+
+ vlay->addWidget( mPop.authGroup );
+
+ mPop.usePipeliningCheck =
+ new QCheckBox( i18n("&Use pipelining for faster mail download"), page2 );
+ connect(mPop.usePipeliningCheck, SIGNAL(clicked()),
+ SLOT(slotPipeliningClicked()));
+ vlay->addWidget( mPop.usePipeliningCheck );
+
+ vlay->addStretch();
+
+ connect(kapp,SIGNAL(kdisplayFontChanged()),SLOT(slotFontChanged()));
+}
+
+
+void AccountDialog::makeImapAccountPage( bool connected )
+{
+ QFrame *page = makeMainWidget();
+ QVBoxLayout *topLayout = new QVBoxLayout( page, 0, spacingHint() );
+
+ mImap.titleLabel = new QLabel( page );
+ if( connected )
+ mImap.titleLabel->setText( i18n("Account Type: Disconnected IMAP Account") );
+ else
+ mImap.titleLabel->setText( i18n("Account Type: IMAP Account") );
+ QFont titleFont( mImap.titleLabel->font() );
+ titleFont.setBold( true );
+ mImap.titleLabel->setFont( titleFont );
+ topLayout->addWidget( mImap.titleLabel );
+ KSeparator *hline = new KSeparator( KSeparator::HLine, page);
+ topLayout->addWidget( hline );
+
+ QTabWidget *tabWidget = new QTabWidget(page);
+ topLayout->addWidget( tabWidget );
+
+ QWidget *page1 = new QWidget( tabWidget );
+ tabWidget->addTab( page1, i18n("&General") );
+
+ int row = -1;
+ QGridLayout *grid = new QGridLayout( page1, 16, 2, marginHint(), spacingHint() );
+ grid->addColSpacing( 1, fontMetrics().maxWidth()*16 );
+
+ ++row;
+ QLabel *label = new QLabel( i18n("Account &name:"), page1 );
+ grid->addWidget( label, row, 0 );
+ mImap.nameEdit = new KLineEdit( page1 );
+ label->setBuddy( mImap.nameEdit );
+ grid->addWidget( mImap.nameEdit, row, 1 );
+
+ ++row;
+ label = new QLabel( i18n("&Login:"), page1 );
+ QWhatsThis::add( label, i18n("Your Internet Service Provider gave you a <em>user name</em> which is used to authenticate you with their servers. It usually is the first part of your email address (the part before <em>@</em>).") );
+ grid->addWidget( label, row, 0 );
+ mImap.loginEdit = new KLineEdit( page1 );
+ label->setBuddy( mImap.loginEdit );
+ grid->addWidget( mImap.loginEdit, row, 1 );
+
+ ++row;
+ label = new QLabel( i18n("P&assword:"), page1 );
+ grid->addWidget( label, row, 0 );
+ mImap.passwordEdit = new KLineEdit( page1 );
+ mImap.passwordEdit->setEchoMode( QLineEdit::Password );
+ label->setBuddy( mImap.passwordEdit );
+ grid->addWidget( mImap.passwordEdit, row, 1 );
+
+ ++row;
+ label = new QLabel( i18n("Ho&st:"), page1 );
+ grid->addWidget( label, row, 0 );
+ mImap.hostEdit = new KLineEdit( page1 );
+ // only letters, digits, '-', '.', ':' (IPv6) and '_' (for Windows
+ // compatibility) are allowed
+ mImap.hostEdit->setValidator(mValidator);
+ label->setBuddy( mImap.hostEdit );
+ grid->addWidget( mImap.hostEdit, row, 1 );
+
+ ++row;
+ label = new QLabel( i18n("&Port:"), page1 );
+ grid->addWidget( label, row, 0 );
+ mImap.portEdit = new KLineEdit( page1 );
+ mImap.portEdit->setValidator( new QIntValidator(this) );
+ label->setBuddy( mImap.portEdit );
+ grid->addWidget( mImap.portEdit, row, 1 );
+
+ // namespace list
+ ++row;
+ QHBox* box = new QHBox( page1 );
+ label = new QLabel( i18n("Namespaces:"), box );
+ QWhatsThis::add( label, i18n( "Here you see the different namespaces that your IMAP server supports."
+ "Each namespace represents a prefix that separates groups of folders."
+ "Namespaces allow KMail for example to display your personal folders and shared folders in one account." ) );
+ // button to reload
+ QToolButton* button = new QToolButton( box );
+ button->setAutoRaise(true);
+ button->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
+ button->setFixedSize( 22, 22 );
+ button->setIconSet(
+ KGlobal::iconLoader()->loadIconSet( "reload", KIcon::Small, 0 ) );
+ connect( button, SIGNAL(clicked()), this, SLOT(slotReloadNamespaces()) );
+ QWhatsThis::add( button,
+ i18n("Reload the namespaces from the server. This overwrites any changes.") );
+ grid->addWidget( box, row, 0 );
+
+ // grid with label, namespace list and edit button
+ QGrid* listbox = new QGrid( 3, page1 );
+ label = new QLabel( i18n("Personal"), listbox );
+ QWhatsThis::add( label, i18n( "Personal namespaces include your personal folders." ) );
+ mImap.personalNS = new KLineEdit( listbox );
+ mImap.personalNS->setReadOnly( true );
+ mImap.editPNS = new QToolButton( listbox );
+ mImap.editPNS->setIconSet(
+ KGlobal::iconLoader()->loadIconSet( "edit", KIcon::Small, 0 ) );
+ mImap.editPNS->setAutoRaise( true );
+ mImap.editPNS->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
+ mImap.editPNS->setFixedSize( 22, 22 );
+ connect( mImap.editPNS, SIGNAL(clicked()), this, SLOT(slotEditPersonalNamespace()) );
+
+ label = new QLabel( i18n("Other Users"), listbox );
+ QWhatsThis::add( label, i18n( "These namespaces include the folders of other users." ) );
+ mImap.otherUsersNS = new KLineEdit( listbox );
+ mImap.otherUsersNS->setReadOnly( true );
+ mImap.editONS = new QToolButton( listbox );
+ mImap.editONS->setIconSet(
+ KGlobal::iconLoader()->loadIconSet( "edit", KIcon::Small, 0 ) );
+ mImap.editONS->setAutoRaise( true );
+ mImap.editONS->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
+ mImap.editONS->setFixedSize( 22, 22 );
+ connect( mImap.editONS, SIGNAL(clicked()), this, SLOT(slotEditOtherUsersNamespace()) );
+
+ label = new QLabel( i18n("Shared"), listbox );
+ QWhatsThis::add( label, i18n( "These namespaces include the shared folders." ) );
+ mImap.sharedNS = new KLineEdit( listbox );
+ mImap.sharedNS->setReadOnly( true );
+ mImap.editSNS = new QToolButton( listbox );
+ mImap.editSNS->setIconSet(
+ KGlobal::iconLoader()->loadIconSet( "edit", KIcon::Small, 0 ) );
+ mImap.editSNS->setAutoRaise( true );
+ mImap.editSNS->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
+ mImap.editSNS->setFixedSize( 22, 22 );
+ connect( mImap.editSNS, SIGNAL(clicked()), this, SLOT(slotEditSharedNamespace()) );
+
+ label->setBuddy( listbox );
+ grid->addWidget( listbox, row, 1 );
+
+ ++row;
+ mImap.storePasswordCheck =
+ new QCheckBox( i18n("Sto&re IMAP password"), page1 );
+ QWhatsThis::add( mImap.storePasswordCheck,
+ i18n("Check this option to have KMail store "
+ "the password.\nIf KWallet is available "
+ "the password will be stored there which is considered "
+ "safe.\nHowever, if KWallet is not available, "
+ "the password will be stored in KMail's configuration "
+ "file. The password is stored in an "
+ "obfuscated format, but should not be "
+ "considered secure from decryption efforts "
+ "if access to the configuration file is obtained.") );
+ grid->addMultiCellWidget( mImap.storePasswordCheck, row, row, 0, 1 );
+
+ if( !connected ) {
+ ++row;
+ mImap.autoExpungeCheck =
+ new QCheckBox( i18n("Automaticall&y compact folders (expunges deleted messages)"), page1);
+ grid->addMultiCellWidget( mImap.autoExpungeCheck, row, row, 0, 1 );
+ }
+
+ ++row;
+ mImap.hiddenFoldersCheck = new QCheckBox( i18n("Sho&w hidden folders"), page1);
+ grid->addMultiCellWidget( mImap.hiddenFoldersCheck, row, row, 0, 1 );
+
+
+ ++row;
+ mImap.subscribedFoldersCheck = new QCheckBox(
+ i18n("Show only s&ubscribed folders"), page1);
+ grid->addMultiCellWidget( mImap.subscribedFoldersCheck, row, row, 0, 1 );
+
+ ++row;
+ mImap.locallySubscribedFoldersCheck = new QCheckBox(
+ i18n("Show only &locally subscribed folders"), page1);
+ grid->addMultiCellWidget( mImap.locallySubscribedFoldersCheck, row, row, 0, 1 );
+
+ if ( !connected ) {
+ // not implemented for disconnected yet
+ ++row;
+ mImap.loadOnDemandCheck = new QCheckBox(
+ i18n("Load attach&ments on demand"), page1);
+ QWhatsThis::add( mImap.loadOnDemandCheck,
+ i18n("Activate this to load attachments not automatically when you select the email but only when you click on the attachment. This way also big emails are shown instantly.") );
+ grid->addMultiCellWidget( mImap.loadOnDemandCheck, row, row, 0, 1 );
+ }
+
+ if ( !connected ) {
+ // not implemented for disconnected yet
+ ++row;
+ mImap.listOnlyOpenCheck = new QCheckBox(
+ i18n("List only open folders"), page1);
+ QWhatsThis::add( mImap.listOnlyOpenCheck,
+ i18n("Only folders that are open (expanded) in the folder tree are checked for subfolders. Use this if there are many folders on the server.") );
+ grid->addMultiCellWidget( mImap.listOnlyOpenCheck, row, row, 0, 1 );
+ }
+
+#if 0
+ ++row;
+ QHBox* resourceHB = new QHBox( page1 );
+ resourceHB->setSpacing( 11 );
+ mImap.resourceCheck =
+ new QCheckBox( i18n( "Account for semiautomatic resource handling" ), resourceHB );
+ mImap.resourceClearButton =
+ new QPushButton( i18n( "Clear" ), resourceHB );
+ mImap.resourceClearButton->setEnabled( false );
+ connect( mImap.resourceCheck, SIGNAL( toggled(bool) ),
+ mImap.resourceClearButton, SLOT( setEnabled(bool) ) );
+ QWhatsThis::add( mImap.resourceClearButton,
+ i18n( "Delete all allocations for the resource represented by this account." ) );
+ connect( mImap.resourceClearButton, SIGNAL( clicked() ),
+ this, SLOT( slotClearResourceAllocations() ) );
+ mImap.resourceClearPastButton =
+ new QPushButton( i18n( "Clear Past" ), resourceHB );
+ mImap.resourceClearPastButton->setEnabled( false );
+ connect( mImap.resourceCheck, SIGNAL( toggled(bool) ),
+ mImap.resourceClearPastButton, SLOT( setEnabled(bool) ) );
+ QWhatsThis::add( mImap.resourceClearPastButton,
+ i18n( "Delete all outdated allocations for the resource represented by this account." ) );
+ connect( mImap.resourceClearPastButton, SIGNAL( clicked() ),
+ this, SLOT( slotClearPastResourceAllocations() ) );
+ grid->addMultiCellWidget( resourceHB, row, row, 0, 2 );
+#endif
+
+ ++row;
+ mImap.includeInCheck =
+ new QCheckBox( i18n("Include in manual mail chec&k"), page1 );
+ grid->addMultiCellWidget( mImap.includeInCheck, row, row, 0, 1 );
+
+ ++row;
+ mImap.intervalCheck =
+ new QCheckBox( i18n("Enable &interval mail checking"), page1 );
+ grid->addMultiCellWidget( mImap.intervalCheck, row, row, 0, 2 );
+ connect( mImap.intervalCheck, SIGNAL(toggled(bool)),
+ this, SLOT(slotEnableImapInterval(bool)) );
+ ++row;
+ mImap.intervalLabel = new QLabel( i18n("Check inter&val:"), page1 );
+ grid->addWidget( mImap.intervalLabel, row, 0 );
+ mImap.intervalSpin = new KIntNumInput( page1 );
+ mImap.intervalSpin->setRange( GlobalSettings::minimumCheckInterval(), 60, 1, false );
+ mImap.intervalSpin->setValue( defaultmailcheckintervalmin );
+ mImap.intervalSpin->setSuffix( i18n( " min" ) );
+ mImap.intervalLabel->setBuddy( mImap.intervalSpin );
+ grid->addWidget( mImap.intervalSpin, row, 1 );
+
+ ++row;
+ label = new QLabel( i18n("&Trash folder:"), page1 );
+ grid->addWidget( label, row, 0 );
+ mImap.trashCombo = new FolderRequester( page1,
+ kmkernel->getKMMainWidget()->folderTree() );
+ mImap.trashCombo->setShowOutbox( false );
+ label->setBuddy( mImap.trashCombo );
+ grid->addWidget( mImap.trashCombo, row, 1 );
+
+ ++row;
+ mImap.identityLabel = new QLabel( i18n("Identity:"), page1 );
+ grid->addWidget( mImap.identityLabel, row, 0 );
+ mImap.identityCombo = new KPIM::IdentityCombo(kmkernel->identityManager(), page1 );
+ mImap.identityLabel->setBuddy( mImap.identityCombo );
+ grid->addWidget( mImap.identityCombo, row, 1 );
+
+ QWidget *page2 = new QWidget( tabWidget );
+ tabWidget->addTab( page2, i18n("S&ecurity") );
+ QVBoxLayout *vlay = new QVBoxLayout( page2, marginHint(), spacingHint() );
+
+ vlay->addSpacing( KDialog::spacingHint() );
+
+ QHBoxLayout *buttonLay = new QHBoxLayout( vlay );
+ mImap.checkCapabilities =
+ new QPushButton( i18n("Check &What the Server Supports"), page2 );
+ connect(mImap.checkCapabilities, SIGNAL(clicked()),
+ SLOT(slotCheckImapCapabilities()));
+ buttonLay->addStretch();
+ buttonLay->addWidget( mImap.checkCapabilities );
+ buttonLay->addStretch();
+
+ vlay->addSpacing( KDialog::spacingHint() );
+
+ mImap.encryptionGroup = new QButtonGroup( 1, Qt::Horizontal,
+ i18n("Encryption"), page2 );
+ mImap.encryptionNone =
+ new QRadioButton( i18n("&None"), mImap.encryptionGroup );
+ mImap.encryptionSSL =
+ new QRadioButton( i18n("Use &SSL for secure mail download"),
+ mImap.encryptionGroup );
+ mImap.encryptionTLS =
+ new QRadioButton( i18n("Use &TLS for secure mail download"),
+ mImap.encryptionGroup );
+ connect(mImap.encryptionGroup, SIGNAL(clicked(int)),
+ SLOT(slotImapEncryptionChanged(int)));
+ vlay->addWidget( mImap.encryptionGroup );
+
+ mImap.authGroup = new QButtonGroup( 1, Qt::Horizontal,
+ i18n("Authentication Method"), page2 );
+ mImap.authUser = new QRadioButton( i18n("Clear te&xt"), mImap.authGroup );
+ mImap.authLogin = new QRadioButton( i18n("Please translate this "
+ "authentication method only if you have a good reason", "&LOGIN"),
+ mImap.authGroup );
+ mImap.authPlain = new QRadioButton( i18n("Please translate this "
+ "authentication method only if you have a good reason", "&PLAIN"),
+ mImap.authGroup );
+ mImap.authCramMd5 = new QRadioButton( i18n("CRAM-MD&5"), mImap.authGroup );
+ mImap.authDigestMd5 = new QRadioButton( i18n("&DIGEST-MD5"), mImap.authGroup );
+ mImap.authNTLM = new QRadioButton( i18n("&NTLM"), mImap.authGroup );
+ mImap.authGSSAPI = new QRadioButton( i18n("&GSSAPI"), mImap.authGroup );
+ mImap.authAnonymous = new QRadioButton( i18n("&Anonymous"), mImap.authGroup );
+ vlay->addWidget( mImap.authGroup );
+
+ vlay->addStretch();
+
+ // TODO (marc/bo): Test this
+ mSieveConfigEditor = new SieveConfigEditor( tabWidget );
+ mSieveConfigEditor->layout()->setMargin( KDialog::marginHint() );
+ tabWidget->addTab( mSieveConfigEditor, i18n("&Filtering") );
+
+ connect(kapp,SIGNAL(kdisplayFontChanged()),SLOT(slotFontChanged()));
+}
+
+
+void AccountDialog::setupSettings()
+{
+ QComboBox *folderCombo = 0;
+ int interval = mAccount->checkInterval();
+
+ QString accountType = mAccount->type();
+ if( accountType == "local" )
+ {
+ ProcmailRCParser procmailrcParser;
+ KMAcctLocal *acctLocal = dynamic_cast<KMAcctLocal*>(mAccount);
+
+ if ( acctLocal->location().isEmpty() )
+ acctLocal->setLocation( procmailrcParser.getSpoolFilesList().first() );
+ else
+ mLocal.locationEdit->insertItem( acctLocal->location() );
+
+ if ( acctLocal->procmailLockFileName().isEmpty() )
+ acctLocal->setProcmailLockFileName( procmailrcParser.getLockFilesList().first() );
+ else
+ mLocal.procmailLockFileName->insertItem( acctLocal->procmailLockFileName() );
+
+ mLocal.nameEdit->setText( mAccount->name() );
+ mLocal.nameEdit->setFocus();
+ mLocal.locationEdit->setEditText( acctLocal->location() );
+ if (acctLocal->lockType() == mutt_dotlock)
+ mLocal.lockMutt->setChecked(true);
+ else if (acctLocal->lockType() == mutt_dotlock_privileged)
+ mLocal.lockMuttPriv->setChecked(true);
+ else if (acctLocal->lockType() == procmail_lockfile) {
+ mLocal.lockProcmail->setChecked(true);
+ mLocal.procmailLockFileName->setEditText(acctLocal->procmailLockFileName());
+ } else if (acctLocal->lockType() == FCNTL)
+ mLocal.lockFcntl->setChecked(true);
+ else if (acctLocal->lockType() == lock_none)
+ mLocal.lockNone->setChecked(true);
+
+ if ( interval <= 0 ) mLocal.intervalSpin->setValue( defaultmailcheckintervalmin );
+ else mLocal.intervalSpin->setValue( interval );
+ mLocal.intervalCheck->setChecked( interval >= 1 );
+#if 0
+ mLocal.resourceCheck->setChecked( mAccount->resource() );
+#endif
+ mLocal.includeInCheck->setChecked( !mAccount->checkExclude() );
+ mLocal.precommand->setText( mAccount->precommand() );
+
+ slotEnableLocalInterval( interval >= 1 );
+ folderCombo = mLocal.folderCombo;
+ mLocal.identityCombo-> setCurrentIdentity( mAccount->identityId() );
+ }
+ else if( accountType == "pop" )
+ {
+ PopAccount &ap = *(PopAccount*)mAccount;
+ mPop.nameEdit->setText( mAccount->name() );
+ mPop.nameEdit->setFocus();
+ mPop.loginEdit->setText( ap.login() );
+ mPop.passwordEdit->setText( ap.passwd());
+ mPop.hostEdit->setText( ap.host() );
+ mPop.portEdit->setText( QString("%1").arg( ap.port() ) );
+ mPop.usePipeliningCheck->setChecked( ap.usePipelining() );
+ mPop.storePasswordCheck->setChecked( ap.storePasswd() );
+ mPop.leaveOnServerCheck->setChecked( ap.leaveOnServer() );
+ mPop.leaveOnServerDaysCheck->setEnabled( ap.leaveOnServer() );
+ mPop.leaveOnServerDaysCheck->setChecked( ap.leaveOnServerDays() >= 1 );
+ mPop.leaveOnServerDaysSpin->setValue( ap.leaveOnServerDays() >= 1 ?
+ ap.leaveOnServerDays() : 7 );
+ mPop.leaveOnServerCountCheck->setEnabled( ap.leaveOnServer() );
+ mPop.leaveOnServerCountCheck->setChecked( ap.leaveOnServerCount() >= 1 );
+ mPop.leaveOnServerCountSpin->setValue( ap.leaveOnServerCount() >= 1 ?
+ ap.leaveOnServerCount() : 100 );
+ mPop.leaveOnServerSizeCheck->setEnabled( ap.leaveOnServer() );
+ mPop.leaveOnServerSizeCheck->setChecked( ap.leaveOnServerSize() >= 1 );
+ mPop.leaveOnServerSizeSpin->setValue( ap.leaveOnServerSize() >= 1 ?
+ ap.leaveOnServerSize() : 10 );
+ mPop.filterOnServerCheck->setChecked( ap.filterOnServer() );
+ mPop.filterOnServerSizeSpin->setValue( ap.filterOnServerCheckSize() );
+ mPop.intervalCheck->setChecked( interval >= 1 );
+ if ( interval <= 0 ) mPop.intervalSpin->setValue( defaultmailcheckintervalmin );
+ else mPop.intervalSpin->setValue( interval );
+#if 0
+ mPop.resourceCheck->setChecked( mAccount->resource() );
+#endif
+ mPop.includeInCheck->setChecked( !mAccount->checkExclude() );
+ mPop.precommand->setText( ap.precommand() );
+ mPop.identityCombo-> setCurrentIdentity( mAccount->identityId() );
+ if (ap.useSSL())
+ mPop.encryptionSSL->setChecked( true );
+ else if (ap.useTLS())
+ mPop.encryptionTLS->setChecked( true );
+ else mPop.encryptionNone->setChecked( true );
+ if (ap.auth() == "LOGIN")
+ mPop.authLogin->setChecked( true );
+ else if (ap.auth() == "PLAIN")
+ mPop.authPlain->setChecked( true );
+ else if (ap.auth() == "CRAM-MD5")
+ mPop.authCRAM_MD5->setChecked( true );
+ else if (ap.auth() == "DIGEST-MD5")
+ mPop.authDigestMd5->setChecked( true );
+ else if (ap.auth() == "NTLM")
+ mPop.authNTLM->setChecked( true );
+ else if (ap.auth() == "GSSAPI")
+ mPop.authGSSAPI->setChecked( true );
+ else if (ap.auth() == "APOP")
+ mPop.authAPOP->setChecked( true );
+ else mPop.authUser->setChecked( true );
+
+ slotEnableLeaveOnServerDays( mPop.leaveOnServerDaysCheck->isEnabled() ?
+ ap.leaveOnServerDays() >= 1 : 0);
+ slotEnableLeaveOnServerCount( mPop.leaveOnServerCountCheck->isEnabled() ?
+ ap.leaveOnServerCount() >= 1 : 0);
+ slotEnableLeaveOnServerSize( mPop.leaveOnServerSizeCheck->isEnabled() ?
+ ap.leaveOnServerSize() >= 1 : 0);
+ slotEnablePopInterval( interval >= 1 );
+ folderCombo = mPop.folderCombo;
+ }
+ else if( accountType == "imap" )
+ {
+ KMAcctImap &ai = *(KMAcctImap*)mAccount;
+ mImap.nameEdit->setText( mAccount->name() );
+ mImap.nameEdit->setFocus();
+ mImap.loginEdit->setText( ai.login() );
+ mImap.passwordEdit->setText( ai.passwd());
+ mImap.hostEdit->setText( ai.host() );
+ mImap.portEdit->setText( QString("%1").arg( ai.port() ) );
+ mImap.autoExpungeCheck->setChecked( ai.autoExpunge() );
+ mImap.hiddenFoldersCheck->setChecked( ai.hiddenFolders() );
+ mImap.subscribedFoldersCheck->setChecked( ai.onlySubscribedFolders() );
+ mImap.locallySubscribedFoldersCheck->setChecked( ai.onlyLocallySubscribedFolders() );
+ mImap.loadOnDemandCheck->setChecked( ai.loadOnDemand() );
+ mImap.listOnlyOpenCheck->setChecked( ai.listOnlyOpenFolders() );
+ mImap.storePasswordCheck->setChecked( ai.storePasswd() );
+#if 0
+ mImap.resourceCheck->setChecked( ai.resource() );
+#endif
+ mImap.includeInCheck->setChecked( !ai.checkExclude() );
+ mImap.intervalCheck->setChecked( interval >= 1 );
+ if ( interval <= 0 ) mImap.intervalSpin->setValue( defaultmailcheckintervalmin );
+ else mImap.intervalSpin->setValue( interval );
+ QString trashfolder = ai.trash();
+ if (trashfolder.isEmpty())
+ trashfolder = kmkernel->trashFolder()->idString();
+ mImap.trashCombo->setFolder( trashfolder );
+ slotEnableImapInterval( interval >= 1 );
+ mImap.identityCombo-> setCurrentIdentity( mAccount->identityId() );
+ //mImap.identityCombo->insertStringList( kmkernel->identityManager()->shadowIdentities() );
+ if (ai.useSSL())
+ mImap.encryptionSSL->setChecked( true );
+ else if (ai.useTLS())
+ mImap.encryptionTLS->setChecked( true );
+ else mImap.encryptionNone->setChecked( true );
+ if (ai.auth() == "CRAM-MD5")
+ mImap.authCramMd5->setChecked( true );
+ else if (ai.auth() == "DIGEST-MD5")
+ mImap.authDigestMd5->setChecked( true );
+ else if (ai.auth() == "NTLM")
+ mImap.authNTLM->setChecked( true );
+ else if (ai.auth() == "GSSAPI")
+ mImap.authGSSAPI->setChecked( true );
+ else if (ai.auth() == "ANONYMOUS")
+ mImap.authAnonymous->setChecked( true );
+ else if (ai.auth() == "PLAIN")
+ mImap.authPlain->setChecked( true );
+ else if (ai.auth() == "LOGIN")
+ mImap.authLogin->setChecked( true );
+ else mImap.authUser->setChecked( true );
+ if ( mSieveConfigEditor )
+ mSieveConfigEditor->setConfig( ai.sieveConfig() );
+ }
+ else if( accountType == "cachedimap" )
+ {
+ KMAcctCachedImap &ai = *(KMAcctCachedImap*)mAccount;
+ mImap.nameEdit->setText( mAccount->name() );
+ mImap.nameEdit->setFocus();
+ mImap.loginEdit->setText( ai.login() );
+ mImap.passwordEdit->setText( ai.passwd());
+ mImap.hostEdit->setText( ai.host() );
+ mImap.portEdit->setText( QString("%1").arg( ai.port() ) );
+#if 0
+ mImap.resourceCheck->setChecked( ai.resource() );
+#endif
+ mImap.hiddenFoldersCheck->setChecked( ai.hiddenFolders() );
+ mImap.subscribedFoldersCheck->setChecked( ai.onlySubscribedFolders() );
+ mImap.locallySubscribedFoldersCheck->setChecked( ai.onlyLocallySubscribedFolders() );
+ mImap.storePasswordCheck->setChecked( ai.storePasswd() );
+ mImap.intervalCheck->setChecked( interval >= 1 );
+ if ( interval <= 0 ) mImap.intervalSpin->setValue( defaultmailcheckintervalmin );
+ else mImap.intervalSpin->setValue( interval );
+ mImap.includeInCheck->setChecked( !ai.checkExclude() );
+ QString trashfolder = ai.trash();
+ if (trashfolder.isEmpty())
+ trashfolder = kmkernel->trashFolder()->idString();
+ mImap.trashCombo->setFolder( trashfolder );
+ slotEnableImapInterval( interval >= 1 );
+ mImap.identityCombo-> setCurrentIdentity( mAccount->identityId() );
+ //mImap.identityCombo->insertStringList( kmkernel->identityManager()->shadowIdentities() );
+ if (ai.useSSL())
+ mImap.encryptionSSL->setChecked( true );
+ else if (ai.useTLS())
+ mImap.encryptionTLS->setChecked( true );
+ else mImap.encryptionNone->setChecked( true );
+ if (ai.auth() == "CRAM-MD5")
+ mImap.authCramMd5->setChecked( true );
+ else if (ai.auth() == "DIGEST-MD5")
+ mImap.authDigestMd5->setChecked( true );
+ else if (ai.auth() == "GSSAPI")
+ mImap.authGSSAPI->setChecked( true );
+ else if (ai.auth() == "NTLM")
+ mImap.authNTLM->setChecked( true );
+ else if (ai.auth() == "ANONYMOUS")
+ mImap.authAnonymous->setChecked( true );
+ else if (ai.auth() == "PLAIN")
+ mImap.authPlain->setChecked( true );
+ else if (ai.auth() == "LOGIN")
+ mImap.authLogin->setChecked( true );
+ else mImap.authUser->setChecked( true );
+ if ( mSieveConfigEditor )
+ mSieveConfigEditor->setConfig( ai.sieveConfig() );
+ }
+ else if( accountType == "maildir" )
+ {
+ KMAcctMaildir *acctMaildir = dynamic_cast<KMAcctMaildir*>(mAccount);
+
+ mMaildir.nameEdit->setText( mAccount->name() );
+ mMaildir.nameEdit->setFocus();
+ mMaildir.locationEdit->setEditText( acctMaildir->location() );
+
+ if ( interval <= 0 ) mMaildir.intervalSpin->setValue( defaultmailcheckintervalmin );
+ else mMaildir.intervalSpin->setValue( interval );
+ mMaildir.intervalCheck->setChecked( interval >= 1 );
+#if 0
+ mMaildir.resourceCheck->setChecked( mAccount->resource() );
+#endif
+ mMaildir.includeInCheck->setChecked( !mAccount->checkExclude() );
+ mMaildir.precommand->setText( mAccount->precommand() );
+ mMaildir.identityCombo-> setCurrentIdentity( mAccount->identityId() );
+ slotEnableMaildirInterval( interval >= 1 );
+ folderCombo = mMaildir.folderCombo;
+ }
+ else // Unknown account type
+ return;
+
+ if ( accountType == "imap" || accountType == "cachedimap" )
+ {
+ // settings for imap in general
+ ImapAccountBase &ai = *(ImapAccountBase*)mAccount;
+ // namespaces
+ if ( ( ai.namespaces().isEmpty() || ai.namespaceToDelimiter().isEmpty() ) &&
+ !ai.login().isEmpty() && !ai.passwd().isEmpty() && !ai.host().isEmpty() )
+ {
+ slotReloadNamespaces();
+ } else {
+ slotSetupNamespaces( ai.namespacesWithDelimiter() );
+ }
+ }
+
+ if (!folderCombo) return;
+
+ KMFolderDir *fdir = (KMFolderDir*)&kmkernel->folderMgr()->dir();
+ KMFolder *acctFolder = mAccount->folder();
+ if( acctFolder == 0 )
+ {
+ acctFolder = (KMFolder*)fdir->first();
+ }
+ if( acctFolder == 0 )
+ {
+ folderCombo->insertItem( i18n("<none>") );
+ }
+ else
+ {
+ uint i = 0;
+ int curIndex = -1;
+ kmkernel->folderMgr()->createI18nFolderList(&mFolderNames, &mFolderList);
+ while (i < mFolderNames.count())
+ {
+ QValueList<QGuardedPtr<KMFolder> >::Iterator it = mFolderList.at(i);
+ KMFolder *folder = *it;
+ if (folder->isSystemFolder())
+ {
+ mFolderList.remove(it);
+ mFolderNames.remove(mFolderNames.at(i));
+ } else {
+ if (folder == acctFolder) curIndex = i;
+ i++;
+ }
+ }
+ mFolderNames.prepend(i18n("inbox"));
+ mFolderList.prepend(kmkernel->inboxFolder());
+ folderCombo->insertStringList(mFolderNames);
+ folderCombo->setCurrentItem(curIndex + 1);
+
+ // -sanders hack for startup users. Must investigate this properly
+ if (folderCombo->count() == 0)
+ folderCombo->insertItem( i18n("inbox") );
+ }
+}
+
+void AccountDialog::slotLeaveOnServerClicked()
+{
+ bool state = mPop.leaveOnServerCheck->isChecked();
+ mPop.leaveOnServerDaysCheck->setEnabled( state );
+ mPop.leaveOnServerCountCheck->setEnabled( state );
+ mPop.leaveOnServerSizeCheck->setEnabled( state );
+ if ( state ) {
+ if ( mPop.leaveOnServerDaysCheck->isChecked() ) {
+ slotEnableLeaveOnServerDays( state );
+ }
+ if ( mPop.leaveOnServerCountCheck->isChecked() ) {
+ slotEnableLeaveOnServerCount( state );
+ }
+ if ( mPop.leaveOnServerSizeCheck->isChecked() ) {
+ slotEnableLeaveOnServerSize( state );
+ }
+ } else {
+ slotEnableLeaveOnServerDays( state );
+ slotEnableLeaveOnServerCount( state );
+ slotEnableLeaveOnServerSize( state );
+ }
+ if ( !( mCurCapa & UIDL ) && mPop.leaveOnServerCheck->isChecked() ) {
+ KMessageBox::information( topLevelWidget(),
+ i18n("The server does not seem to support unique "
+ "message numbers, but this is a "
+ "requirement for leaving messages on the "
+ "server.\n"
+ "Since some servers do not correctly "
+ "announce their capabilities you still "
+ "have the possibility to turn leaving "
+ "fetched messages on the server on.") );
+ }
+}
+
+void AccountDialog::slotFilterOnServerClicked()
+{
+ if ( !( mCurCapa & TOP ) && mPop.filterOnServerCheck->isChecked() ) {
+ KMessageBox::information( topLevelWidget(),
+ i18n("The server does not seem to support "
+ "fetching message headers, but this is a "
+ "requirement for filtering messages on the "
+ "server.\n"
+ "Since some servers do not correctly "
+ "announce their capabilities you still "
+ "have the possibility to turn filtering "
+ "messages on the server on.") );
+ }
+}
+
+void AccountDialog::slotPipeliningClicked()
+{
+ if (mPop.usePipeliningCheck->isChecked())
+ KMessageBox::information( topLevelWidget(),
+ i18n("Please note that this feature can cause some POP3 servers "
+ "that do not support pipelining to send corrupted mail;\n"
+ "this is configurable, though, because some servers support pipelining "
+ "but do not announce their capabilities. To check whether your POP3 server "
+ "announces pipelining support use the \"Check What the Server "
+ "Supports\" button at the bottom of the dialog;\n"
+ "if your server does not announce it, but you want more speed, then "
+ "you should do some testing first by sending yourself a batch "
+ "of mail and downloading it."), QString::null,
+ "pipelining");
+}
+
+
+void AccountDialog::slotPopEncryptionChanged(int id)
+{
+ kdDebug(5006) << "slotPopEncryptionChanged( " << id << " )" << endl;
+ // adjust port
+ if ( id == SSL || mPop.portEdit->text() == "995" )
+ mPop.portEdit->setText( ( id == SSL ) ? "995" : "110" );
+
+ // switch supported auth methods
+ mCurCapa = ( id == TLS ) ? mCapaTLS
+ : ( id == SSL ) ? mCapaSSL
+ : mCapaNormal;
+ enablePopFeatures( mCurCapa );
+ const QButton *old = mPop.authGroup->selected();
+ if ( !old->isEnabled() )
+ checkHighest( mPop.authGroup );
+}
+
+
+void AccountDialog::slotImapEncryptionChanged(int id)
+{
+ kdDebug(5006) << "slotImapEncryptionChanged( " << id << " )" << endl;
+ // adjust port
+ if ( id == SSL || mImap.portEdit->text() == "993" )
+ mImap.portEdit->setText( ( id == SSL ) ? "993" : "143" );
+
+ // switch supported auth methods
+ int authMethods = ( id == TLS ) ? mCapaTLS
+ : ( id == SSL ) ? mCapaSSL
+ : mCapaNormal;
+ enableImapAuthMethods( authMethods );
+ QButton *old = mImap.authGroup->selected();
+ if ( !old->isEnabled() )
+ checkHighest( mImap.authGroup );
+}
+
+
+void AccountDialog::slotCheckPopCapabilities()
+{
+ if ( mPop.hostEdit->text().isEmpty() || mPop.portEdit->text().isEmpty() )
+ {
+ KMessageBox::sorry( this, i18n( "Please specify a server and port on "
+ "the General tab first." ) );
+ return;
+ }
+ delete mServerTest;
+ mServerTest = new KMServerTest(POP_PROTOCOL, mPop.hostEdit->text(),
+ mPop.portEdit->text().toInt());
+ connect( mServerTest, SIGNAL( capabilities( const QStringList &,
+ const QStringList & ) ),
+ this, SLOT( slotPopCapabilities( const QStringList &,
+ const QStringList & ) ) );
+ mPop.checkCapabilities->setEnabled(false);
+}
+
+
+void AccountDialog::slotCheckImapCapabilities()
+{
+ if ( mImap.hostEdit->text().isEmpty() || mImap.portEdit->text().isEmpty() )
+ {
+ KMessageBox::sorry( this, i18n( "Please specify a server and port on "
+ "the General tab first." ) );
+ return;
+ }
+ delete mServerTest;
+ mServerTest = new KMServerTest(IMAP_PROTOCOL, mImap.hostEdit->text(),
+ mImap.portEdit->text().toInt());
+ connect( mServerTest, SIGNAL( capabilities( const QStringList &,
+ const QStringList & ) ),
+ this, SLOT( slotImapCapabilities( const QStringList &,
+ const QStringList & ) ) );
+ mImap.checkCapabilities->setEnabled(false);
+}
+
+
+unsigned int AccountDialog::popCapabilitiesFromStringList( const QStringList & l )
+{
+ unsigned int capa = 0;
+ kdDebug( 5006 ) << k_funcinfo << l << endl;
+ for ( QStringList::const_iterator it = l.begin() ; it != l.end() ; ++it ) {
+ QString cur = (*it).upper();
+ if ( cur == "PLAIN" )
+ capa |= Plain;
+ else if ( cur == "LOGIN" )
+ capa |= Login;
+ else if ( cur == "CRAM-MD5" )
+ capa |= CRAM_MD5;
+ else if ( cur == "DIGEST-MD5" )
+ capa |= Digest_MD5;
+ else if ( cur == "NTLM" )
+ capa |= NTLM;
+ else if ( cur == "GSSAPI" )
+ capa |= GSSAPI;
+ else if ( cur == "APOP" )
+ capa |= APOP;
+ else if ( cur == "PIPELINING" )
+ capa |= Pipelining;
+ else if ( cur == "TOP" )
+ capa |= TOP;
+ else if ( cur == "UIDL" )
+ capa |= UIDL;
+ else if ( cur == "STLS" )
+ capa |= STLS;
+ }
+ return capa;
+}
+
+
+void AccountDialog::slotPopCapabilities( const QStringList & capaNormal,
+ const QStringList & capaSSL )
+{
+ mPop.checkCapabilities->setEnabled( true );
+ mCapaNormal = popCapabilitiesFromStringList( capaNormal );
+ if ( mCapaNormal & STLS )
+ mCapaTLS = mCapaNormal;
+ else
+ mCapaTLS = 0;
+ mCapaSSL = popCapabilitiesFromStringList( capaSSL );
+ kdDebug(5006) << "mCapaNormal = " << mCapaNormal
+ << "; mCapaSSL = " << mCapaSSL
+ << "; mCapaTLS = " << mCapaTLS << endl;
+ mPop.encryptionNone->setEnabled( !capaNormal.isEmpty() );
+ mPop.encryptionSSL->setEnabled( !capaSSL.isEmpty() );
+ mPop.encryptionTLS->setEnabled( mCapaTLS != 0 );
+ checkHighest( mPop.encryptionGroup );
+ delete mServerTest;
+ mServerTest = 0;
+}
+
+
+void AccountDialog::enablePopFeatures( unsigned int capa )
+{
+ kdDebug(5006) << "enablePopFeatures( " << capa << " )" << endl;
+ mPop.authPlain->setEnabled( capa & Plain );
+ mPop.authLogin->setEnabled( capa & Login );
+ mPop.authCRAM_MD5->setEnabled( capa & CRAM_MD5 );
+ mPop.authDigestMd5->setEnabled( capa & Digest_MD5 );
+ mPop.authNTLM->setEnabled( capa & NTLM );
+ mPop.authGSSAPI->setEnabled( capa & GSSAPI );
+ mPop.authAPOP->setEnabled( capa & APOP );
+ if ( !( capa & Pipelining ) && mPop.usePipeliningCheck->isChecked() ) {
+ mPop.usePipeliningCheck->setChecked( false );
+ KMessageBox::information( topLevelWidget(),
+ i18n("The server does not seem to support "
+ "pipelining; therefore, this option has "
+ "been disabled.\n"
+ "Since some servers do not correctly "
+ "announce their capabilities you still "
+ "have the possibility to turn pipelining "
+ "on. But please note that this feature can "
+ "cause some POP servers that do not "
+ "support pipelining to send corrupt "
+ "messages. So before using this feature "
+ "with important mail you should first "
+ "test it by sending yourself a larger "
+ "number of test messages which you all "
+ "download in one go from the POP "
+ "server.") );
+ }
+ if ( !( capa & UIDL ) && mPop.leaveOnServerCheck->isChecked() ) {
+ mPop.leaveOnServerCheck->setChecked( false );
+ KMessageBox::information( topLevelWidget(),
+ i18n("The server does not seem to support unique "
+ "message numbers, but this is a "
+ "requirement for leaving messages on the "
+ "server; therefore, this option has been "
+ "disabled.\n"
+ "Since some servers do not correctly "
+ "announce their capabilities you still "
+ "have the possibility to turn leaving "
+ "fetched messages on the server on.") );
+ }
+ if ( !( capa & TOP ) && mPop.filterOnServerCheck->isChecked() ) {
+ mPop.filterOnServerCheck->setChecked( false );
+ KMessageBox::information( topLevelWidget(),
+ i18n("The server does not seem to support "
+ "fetching message headers, but this is a "
+ "requirement for filtering messages on the "
+ "server; therefore, this option has been "
+ "disabled.\n"
+ "Since some servers do not correctly "
+ "announce their capabilities you still "
+ "have the possibility to turn filtering "
+ "messages on the server on.") );
+ }
+}
+
+
+unsigned int AccountDialog::imapCapabilitiesFromStringList( const QStringList & l )
+{
+ unsigned int capa = 0;
+ for ( QStringList::const_iterator it = l.begin() ; it != l.end() ; ++it ) {
+ QString cur = (*it).upper();
+ if ( cur == "AUTH=PLAIN" )
+ capa |= Plain;
+ else if ( cur == "AUTH=LOGIN" )
+ capa |= Login;
+ else if ( cur == "AUTH=CRAM-MD5" )
+ capa |= CRAM_MD5;
+ else if ( cur == "AUTH=DIGEST-MD5" )
+ capa |= Digest_MD5;
+ else if ( cur == "AUTH=NTLM" )
+ capa |= NTLM;
+ else if ( cur == "AUTH=GSSAPI" )
+ capa |= GSSAPI;
+ else if ( cur == "AUTH=ANONYMOUS" )
+ capa |= Anonymous;
+ else if ( cur == "STARTTLS" )
+ capa |= STARTTLS;
+ }
+ return capa;
+}
+
+
+void AccountDialog::slotImapCapabilities( const QStringList & capaNormal,
+ const QStringList & capaSSL )
+{
+ mImap.checkCapabilities->setEnabled( true );
+ mCapaNormal = imapCapabilitiesFromStringList( capaNormal );
+ if ( mCapaNormal & STARTTLS )
+ mCapaTLS = mCapaNormal;
+ else
+ mCapaTLS = 0;
+ mCapaSSL = imapCapabilitiesFromStringList( capaSSL );
+ kdDebug(5006) << "mCapaNormal = " << mCapaNormal
+ << "; mCapaSSL = " << mCapaSSL
+ << "; mCapaTLS = " << mCapaTLS << endl;
+ mImap.encryptionNone->setEnabled( !capaNormal.isEmpty() );
+ mImap.encryptionSSL->setEnabled( !capaSSL.isEmpty() );
+ mImap.encryptionTLS->setEnabled( mCapaTLS != 0 );
+ checkHighest( mImap.encryptionGroup );
+ delete mServerTest;
+ mServerTest = 0;
+}
+
+void AccountDialog::slotLeaveOnServerDaysChanged ( int value )
+{
+ mPop.leaveOnServerDaysSpin->setSuffix( i18n(" day", " days", value) );
+}
+
+
+void AccountDialog::slotLeaveOnServerCountChanged ( int value )
+{
+ mPop.leaveOnServerCountSpin->setSuffix( i18n(" message", " messages", value) );
+}
+
+
+void AccountDialog::slotFilterOnServerSizeChanged ( int value )
+{
+ mPop.filterOnServerSizeSpin->setSuffix( i18n(" byte", " bytes", value) );
+}
+
+
+void AccountDialog::enableImapAuthMethods( unsigned int capa )
+{
+ kdDebug(5006) << "enableImapAuthMethods( " << capa << " )" << endl;
+ mImap.authPlain->setEnabled( capa & Plain );
+ mImap.authLogin->setEnabled( capa & Login );
+ mImap.authCramMd5->setEnabled( capa & CRAM_MD5 );
+ mImap.authDigestMd5->setEnabled( capa & Digest_MD5 );
+ mImap.authNTLM->setEnabled( capa & NTLM );
+ mImap.authGSSAPI->setEnabled( capa & GSSAPI );
+ mImap.authAnonymous->setEnabled( capa & Anonymous );
+}
+
+
+void AccountDialog::checkHighest( QButtonGroup *btnGroup )
+{
+ kdDebug(5006) << "checkHighest( " << btnGroup << " )" << endl;
+ for ( int i = btnGroup->count() - 1; i >= 0 ; --i ) {
+ QButton * btn = btnGroup->find( i );
+ if ( btn && btn->isEnabled() ) {
+ btn->animateClick();
+ return;
+ }
+ }
+}
+
+
+void AccountDialog::slotOk()
+{
+ saveSettings();
+ accept();
+}
+
+
+void AccountDialog::saveSettings()
+{
+ QString accountType = mAccount->type();
+ if( accountType == "local" )
+ {
+ KMAcctLocal *acctLocal = dynamic_cast<KMAcctLocal*>(mAccount);
+
+ if (acctLocal) {
+ mAccount->setName( mLocal.nameEdit->text() );
+ acctLocal->setLocation( mLocal.locationEdit->currentText() );
+ if (mLocal.lockMutt->isChecked())
+ acctLocal->setLockType(mutt_dotlock);
+ else if (mLocal.lockMuttPriv->isChecked())
+ acctLocal->setLockType(mutt_dotlock_privileged);
+ else if (mLocal.lockProcmail->isChecked()) {
+ acctLocal->setLockType(procmail_lockfile);
+ acctLocal->setProcmailLockFileName(mLocal.procmailLockFileName->currentText());
+ }
+ else if (mLocal.lockNone->isChecked())
+ acctLocal->setLockType(lock_none);
+ else acctLocal->setLockType(FCNTL);
+ }
+
+ mAccount->setCheckInterval( mLocal.intervalCheck->isChecked() ?
+ mLocal.intervalSpin->value() : 0 );
+#if 0
+ mAccount->setResource( mLocal.resourceCheck->isChecked() );
+#endif
+ mAccount->setCheckExclude( !mLocal.includeInCheck->isChecked() );
+
+ mAccount->setPrecommand( mLocal.precommand->text() );
+
+ mAccount->setFolder( *mFolderList.at(mLocal.folderCombo->currentItem()) );
+
+ mAccount->setIdentityId( mLocal.identityCombo->currentIdentity() );
+
+ }
+ else if( accountType == "pop" )
+ {
+ mAccount->setName( mPop.nameEdit->text() );
+ mAccount->setCheckInterval( mPop.intervalCheck->isChecked() ?
+ mPop.intervalSpin->value() : 0 );
+#if 0
+ mAccount->setResource( mPop.resourceCheck->isChecked() );
+#endif
+ mAccount->setCheckExclude( !mPop.includeInCheck->isChecked() );
+
+ mAccount->setFolder( *mFolderList.at(mPop.folderCombo->currentItem()) );
+
+ mAccount->setIdentityId( mPop.identityCombo->currentIdentity() );
+
+ initAccountForConnect();
+ PopAccount &epa = *(PopAccount*)mAccount;
+ epa.setUsePipelining( mPop.usePipeliningCheck->isChecked() );
+ epa.setLeaveOnServer( mPop.leaveOnServerCheck->isChecked() );
+ epa.setLeaveOnServerDays( mPop.leaveOnServerCheck->isChecked() ?
+ ( mPop.leaveOnServerDaysCheck->isChecked() ?
+ mPop.leaveOnServerDaysSpin->value() : -1 ) : 0);
+ epa.setLeaveOnServerCount( mPop.leaveOnServerCheck->isChecked() ?
+ ( mPop.leaveOnServerCountCheck->isChecked() ?
+ mPop.leaveOnServerCountSpin->value() : -1 ) : 0 );
+ epa.setLeaveOnServerSize( mPop.leaveOnServerCheck->isChecked() ?
+ ( mPop.leaveOnServerSizeCheck->isChecked() ?
+ mPop.leaveOnServerSizeSpin->value() : -1 ) : 0 );
+ epa.setFilterOnServer( mPop.filterOnServerCheck->isChecked() );
+ epa.setFilterOnServerCheckSize (mPop.filterOnServerSizeSpin->value() );
+ epa.setPrecommand( mPop.precommand->text() );
+
+ }
+ else if( accountType == "imap" )
+ {
+ mAccount->setName( mImap.nameEdit->text() );
+ mAccount->setCheckInterval( mImap.intervalCheck->isChecked() ?
+ mImap.intervalSpin->value() : 0 );
+ mAccount->setIdentityId( mImap.identityCombo->currentIdentity() );
+
+#if 0
+ mAccount->setResource( mImap.resourceCheck->isChecked() );
+#endif
+ mAccount->setCheckExclude( !mImap.includeInCheck->isChecked() );
+ mAccount->setFolder( kmkernel->imapFolderMgr()->findById(mAccount->id()) );
+
+ initAccountForConnect();
+ KMAcctImap &epa = *(KMAcctImap*)mAccount;
+ epa.setAutoExpunge( mImap.autoExpungeCheck->isChecked() );
+ epa.setHiddenFolders( mImap.hiddenFoldersCheck->isChecked() );
+ epa.setOnlySubscribedFolders( mImap.subscribedFoldersCheck->isChecked() );
+ epa.setOnlyLocallySubscribedFolders( mImap.locallySubscribedFoldersCheck->isChecked() );
+ epa.setLoadOnDemand( mImap.loadOnDemandCheck->isChecked() );
+ epa.setListOnlyOpenFolders( mImap.listOnlyOpenCheck->isChecked() );
+ KMFolder *t = mImap.trashCombo->folder();
+ if ( t )
+ epa.setTrash( mImap.trashCombo->folder()->idString() );
+ else
+ epa.setTrash( kmkernel->trashFolder()->idString() );
+#if 0
+ epa.setResource( mImap.resourceCheck->isChecked() );
+#endif
+ epa.setCheckExclude( !mImap.includeInCheck->isChecked() );
+ if ( mSieveConfigEditor )
+ epa.setSieveConfig( mSieveConfigEditor->config() );
+ }
+ else if( accountType == "cachedimap" )
+ {
+ mAccount->setName( mImap.nameEdit->text() );
+ mAccount->setCheckInterval( mImap.intervalCheck->isChecked() ?
+ mImap.intervalSpin->value() : 0 );
+ mAccount->setIdentityId( mImap.identityCombo->currentIdentity() );
+
+#if 0
+ mAccount->setResource( mImap.resourceCheck->isChecked() );
+#endif
+ mAccount->setCheckExclude( !mImap.includeInCheck->isChecked() );
+ //mAccount->setFolder( NULL );
+ mAccount->setFolder( kmkernel->dimapFolderMgr()->findById(mAccount->id()) );
+ //kdDebug(5006) << "account for folder " << mAccount->folder()->name() << endl;
+
+ initAccountForConnect();
+ KMAcctCachedImap &epa = *(KMAcctCachedImap*)mAccount;
+ epa.setHiddenFolders( mImap.hiddenFoldersCheck->isChecked() );
+ epa.setOnlySubscribedFolders( mImap.subscribedFoldersCheck->isChecked() );
+ epa.setOnlyLocallySubscribedFolders( mImap.locallySubscribedFoldersCheck->isChecked() );
+ epa.setStorePasswd( mImap.storePasswordCheck->isChecked() );
+ epa.setPasswd( mImap.passwordEdit->text(), epa.storePasswd() );
+ KMFolder *t = mImap.trashCombo->folder();
+ if ( t )
+ epa.setTrash( mImap.trashCombo->folder()->idString() );
+ else
+ epa.setTrash( kmkernel->trashFolder()->idString() );
+#if 0
+ epa.setResource( mImap.resourceCheck->isChecked() );
+#endif
+ epa.setCheckExclude( !mImap.includeInCheck->isChecked() );
+ if ( mSieveConfigEditor )
+ epa.setSieveConfig( mSieveConfigEditor->config() );
+ }
+ else if( accountType == "maildir" )
+ {
+ KMAcctMaildir *acctMaildir = dynamic_cast<KMAcctMaildir*>(mAccount);
+
+ if (acctMaildir) {
+ mAccount->setName( mMaildir.nameEdit->text() );
+ acctMaildir->setLocation( mMaildir.locationEdit->currentText() );
+
+ KMFolder *targetFolder = *mFolderList.at(mMaildir.folderCombo->currentItem());
+ if ( targetFolder->location() == acctMaildir->location() ) {
+ /*
+ Prevent data loss if the user sets the destination folder to be the same as the
+ source account maildir folder by setting the target folder to the inbox.
+ ### FIXME post 3.2: show dialog and let the user chose another target folder
+ */
+ targetFolder = kmkernel->inboxFolder();
+ }
+ mAccount->setFolder( targetFolder );
+ }
+ mAccount->setCheckInterval( mMaildir.intervalCheck->isChecked() ?
+ mMaildir.intervalSpin->value() : 0 );
+#if 0
+ mAccount->setResource( mMaildir.resourceCheck->isChecked() );
+#endif
+ mAccount->setCheckExclude( !mMaildir.includeInCheck->isChecked() );
+
+ mAccount->setPrecommand( mMaildir.precommand->text() );
+
+ mAccount->setIdentityId( mMaildir.identityCombo->currentIdentity() );
+ }
+
+ if ( accountType == "imap" || accountType == "cachedimap" )
+ {
+ // settings for imap in general
+ ImapAccountBase &ai = *(ImapAccountBase*)mAccount;
+ // namespace
+ ImapAccountBase::nsMap map;
+ ImapAccountBase::namespaceDelim delimMap;
+ ImapAccountBase::nsDelimMap::Iterator it;
+ ImapAccountBase::namespaceDelim::Iterator it2;
+ for ( it = mImap.nsMap.begin(); it != mImap.nsMap.end(); ++it ) {
+ QStringList list;
+ for ( it2 = it.data().begin(); it2 != it.data().end(); ++it2 ) {
+ list << it2.key();
+ delimMap[it2.key()] = it2.data();
+ }
+ map[it.key()] = list;
+ }
+ ai.setNamespaces( map );
+ ai.setNamespaceToDelimiter( delimMap );
+ }
+
+ kmkernel->acctMgr()->writeConfig( true );
+ // get the new account and register the new destination folder
+ // this is the target folder for local or pop accounts and the root folder
+ // of the account for (d)imap
+ KMAccount* newAcct = kmkernel->acctMgr()->find(mAccount->id());
+ if (newAcct)
+ {
+ if( accountType == "local" ) {
+ newAcct->setFolder( *mFolderList.at(mLocal.folderCombo->currentItem()), true );
+ } else if ( accountType == "pop" ) {
+ newAcct->setFolder( *mFolderList.at(mPop.folderCombo->currentItem()), true );
+ } else if ( accountType == "maildir" ) {
+ newAcct->setFolder( *mFolderList.at(mMaildir.folderCombo->currentItem()), true );
+ } else if ( accountType == "imap" ) {
+ newAcct->setFolder( kmkernel->imapFolderMgr()->findById(mAccount->id()), true );
+ } else if ( accountType == "cachedimap" ) {
+ newAcct->setFolder( kmkernel->dimapFolderMgr()->findById(mAccount->id()), true );
+ }
+ }
+}
+
+
+void AccountDialog::slotLocationChooser()
+{
+ static QString directory( "/" );
+
+ KFileDialog dialog( directory, QString::null, this, 0, true );
+ dialog.setCaption( i18n("Choose Location") );
+
+ bool result = dialog.exec();
+ if( result == false )
+ {
+ return;
+ }
+
+ KURL url = dialog.selectedURL();
+ if( url.isEmpty() )
+ {
+ return;
+ }
+ if( url.isLocalFile() == false )
+ {
+ KMessageBox::sorry( 0, i18n( "Only local files are currently supported." ) );
+ return;
+ }
+
+ mLocal.locationEdit->setEditText( url.path() );
+ directory = url.directory();
+}
+
+void AccountDialog::slotMaildirChooser()
+{
+ static QString directory( "/" );
+
+ QString dir = KFileDialog::getExistingDirectory(directory, this, i18n("Choose Location"));
+
+ if( dir.isEmpty() )
+ return;
+
+ mMaildir.locationEdit->setEditText( dir );
+ directory = dir;
+}
+
+void AccountDialog::slotEnableLeaveOnServerDays( bool state )
+{
+ if ( state && !mPop.leaveOnServerDaysCheck->isEnabled()) return;
+ mPop.leaveOnServerDaysSpin->setEnabled( state );
+}
+
+void AccountDialog::slotEnableLeaveOnServerCount( bool state )
+{
+ if ( state && !mPop.leaveOnServerCountCheck->isEnabled()) return;
+ mPop.leaveOnServerCountSpin->setEnabled( state );
+ return;
+}
+
+void AccountDialog::slotEnableLeaveOnServerSize( bool state )
+{
+ if ( state && !mPop.leaveOnServerSizeCheck->isEnabled()) return;
+ mPop.leaveOnServerSizeSpin->setEnabled( state );
+ return;
+}
+
+void AccountDialog::slotEnablePopInterval( bool state )
+{
+ mPop.intervalSpin->setEnabled( state );
+ mPop.intervalLabel->setEnabled( state );
+}
+
+void AccountDialog::slotEnableImapInterval( bool state )
+{
+ mImap.intervalSpin->setEnabled( state );
+ mImap.intervalLabel->setEnabled( state );
+}
+
+void AccountDialog::slotEnableLocalInterval( bool state )
+{
+ mLocal.intervalSpin->setEnabled( state );
+ mLocal.intervalLabel->setEnabled( state );
+}
+
+void AccountDialog::slotEnableMaildirInterval( bool state )
+{
+ mMaildir.intervalSpin->setEnabled( state );
+ mMaildir.intervalLabel->setEnabled( state );
+}
+
+void AccountDialog::slotFontChanged( void )
+{
+ QString accountType = mAccount->type();
+ if( accountType == "local" )
+ {
+ QFont titleFont( mLocal.titleLabel->font() );
+ titleFont.setBold( true );
+ mLocal.titleLabel->setFont(titleFont);
+ }
+ else if( accountType == "pop" )
+ {
+ QFont titleFont( mPop.titleLabel->font() );
+ titleFont.setBold( true );
+ mPop.titleLabel->setFont(titleFont);
+ }
+ else if( accountType == "imap" )
+ {
+ QFont titleFont( mImap.titleLabel->font() );
+ titleFont.setBold( true );
+ mImap.titleLabel->setFont(titleFont);
+ }
+}
+
+#if 0
+void AccountDialog::slotClearResourceAllocations()
+{
+ mAccount->clearIntervals();
+}
+
+
+void AccountDialog::slotClearPastResourceAllocations()
+{
+ mAccount->clearOldIntervals();
+}
+#endif
+
+void AccountDialog::slotReloadNamespaces()
+{
+ if ( mAccount->type() == "imap" || mAccount->type() == "cachedimap" )
+ {
+ initAccountForConnect();
+ mImap.personalNS->setText( i18n("Fetching Namespaces...") );
+ mImap.otherUsersNS->setText( QString::null );
+ mImap.sharedNS->setText( QString::null );
+ ImapAccountBase* ai = static_cast<ImapAccountBase*>( mAccount );
+ connect( ai, SIGNAL( namespacesFetched( const ImapAccountBase::nsDelimMap& ) ),
+ this, SLOT( slotSetupNamespaces( const ImapAccountBase::nsDelimMap& ) ) );
+ connect( ai, SIGNAL( connectionResult(int, const QString&) ),
+ this, SLOT( slotConnectionResult(int, const QString&) ) );
+ ai->getNamespaces();
+ }
+}
+
+void AccountDialog::slotConnectionResult( int errorCode, const QString& )
+{
+ if ( errorCode > 0 ) {
+ ImapAccountBase* ai = static_cast<ImapAccountBase*>( mAccount );
+ disconnect( ai, SIGNAL( namespacesFetched( const ImapAccountBase::nsDelimMap& ) ),
+ this, SLOT( slotSetupNamespaces( const ImapAccountBase::nsDelimMap& ) ) );
+ disconnect( ai, SIGNAL( connectionResult(int, const QString&) ),
+ this, SLOT( slotConnectionResult(int, const QString&) ) );
+ mImap.personalNS->setText( QString::null );
+ }
+}
+
+void AccountDialog::slotSetupNamespaces( const ImapAccountBase::nsDelimMap& map )
+{
+ disconnect( this, SLOT( slotSetupNamespaces( const ImapAccountBase::nsDelimMap& ) ) );
+ mImap.personalNS->setText( QString::null );
+ mImap.otherUsersNS->setText( QString::null );
+ mImap.sharedNS->setText( QString::null );
+ mImap.nsMap = map;
+
+ ImapAccountBase::namespaceDelim ns = map[ImapAccountBase::PersonalNS];
+ ImapAccountBase::namespaceDelim::ConstIterator it;
+ if ( !ns.isEmpty() ) {
+ mImap.personalNS->setText( namespaceListToString( ns.keys() ) );
+ mImap.editPNS->setEnabled( true );
+ } else {
+ mImap.editPNS->setEnabled( false );
+ }
+ ns = map[ImapAccountBase::OtherUsersNS];
+ if ( !ns.isEmpty() ) {
+ mImap.otherUsersNS->setText( namespaceListToString( ns.keys() ) );
+ mImap.editONS->setEnabled( true );
+ } else {
+ mImap.editONS->setEnabled( false );
+ }
+ ns = map[ImapAccountBase::SharedNS];
+ if ( !ns.isEmpty() ) {
+ mImap.sharedNS->setText( namespaceListToString( ns.keys() ) );
+ mImap.editSNS->setEnabled( true );
+ } else {
+ mImap.editSNS->setEnabled( false );
+ }
+}
+
+const QString AccountDialog::namespaceListToString( const QStringList& list )
+{
+ QStringList myList = list;
+ for ( QStringList::Iterator it = myList.begin(); it != myList.end(); ++it ) {
+ if ( (*it).isEmpty() ) {
+ (*it) = "<" + i18n("Empty") + ">";
+ }
+ }
+ return myList.join(",");
+}
+
+void AccountDialog::initAccountForConnect()
+{
+ QString type = mAccount->type();
+ if ( type == "local" )
+ return;
+
+ NetworkAccount &na = *(NetworkAccount*)mAccount;
+
+ if ( type == "pop" ) {
+ na.setHost( mPop.hostEdit->text().stripWhiteSpace() );
+ na.setPort( mPop.portEdit->text().toInt() );
+ na.setLogin( mPop.loginEdit->text().stripWhiteSpace() );
+ na.setStorePasswd( mPop.storePasswordCheck->isChecked() );
+ na.setPasswd( mPop.passwordEdit->text(), na.storePasswd() );
+ na.setUseSSL( mPop.encryptionSSL->isChecked() );
+ na.setUseTLS( mPop.encryptionTLS->isChecked() );
+ if (mPop.authUser->isChecked())
+ na.setAuth("USER");
+ else if (mPop.authLogin->isChecked())
+ na.setAuth("LOGIN");
+ else if (mPop.authPlain->isChecked())
+ na.setAuth("PLAIN");
+ else if (mPop.authCRAM_MD5->isChecked())
+ na.setAuth("CRAM-MD5");
+ else if (mPop.authDigestMd5->isChecked())
+ na.setAuth("DIGEST-MD5");
+ else if (mPop.authNTLM->isChecked())
+ na.setAuth("NTLM");
+ else if (mPop.authGSSAPI->isChecked())
+ na.setAuth("GSSAPI");
+ else if (mPop.authAPOP->isChecked())
+ na.setAuth("APOP");
+ else na.setAuth("AUTO");
+ }
+ else if ( type == "imap" || type == "cachedimap" ) {
+ na.setHost( mImap.hostEdit->text().stripWhiteSpace() );
+ na.setPort( mImap.portEdit->text().toInt() );
+ na.setLogin( mImap.loginEdit->text().stripWhiteSpace() );
+ na.setStorePasswd( mImap.storePasswordCheck->isChecked() );
+ na.setPasswd( mImap.passwordEdit->text(), na.storePasswd() );
+ na.setUseSSL( mImap.encryptionSSL->isChecked() );
+ na.setUseTLS( mImap.encryptionTLS->isChecked() );
+ if (mImap.authCramMd5->isChecked())
+ na.setAuth("CRAM-MD5");
+ else if (mImap.authDigestMd5->isChecked())
+ na.setAuth("DIGEST-MD5");
+ else if (mImap.authNTLM->isChecked())
+ na.setAuth("NTLM");
+ else if (mImap.authGSSAPI->isChecked())
+ na.setAuth("GSSAPI");
+ else if (mImap.authAnonymous->isChecked())
+ na.setAuth("ANONYMOUS");
+ else if (mImap.authLogin->isChecked())
+ na.setAuth("LOGIN");
+ else if (mImap.authPlain->isChecked())
+ na.setAuth("PLAIN");
+ else na.setAuth("*");
+ }
+}
+
+void AccountDialog::slotEditPersonalNamespace()
+{
+ NamespaceEditDialog dialog( this, ImapAccountBase::PersonalNS, &mImap.nsMap );
+ if ( dialog.exec() == QDialog::Accepted ) {
+ slotSetupNamespaces( mImap.nsMap );
+ }
+}
+
+void AccountDialog::slotEditOtherUsersNamespace()
+{
+ NamespaceEditDialog dialog( this, ImapAccountBase::OtherUsersNS, &mImap.nsMap );
+ if ( dialog.exec() == QDialog::Accepted ) {
+ slotSetupNamespaces( mImap.nsMap );
+ }
+}
+
+void AccountDialog::slotEditSharedNamespace()
+{
+ NamespaceEditDialog dialog( this, ImapAccountBase::SharedNS, &mImap.nsMap );
+ if ( dialog.exec() == QDialog::Accepted ) {
+ slotSetupNamespaces( mImap.nsMap );
+ }
+}
+
+NamespaceLineEdit::NamespaceLineEdit( QWidget* parent )
+ : KLineEdit( parent )
+{
+}
+
+void NamespaceLineEdit::setText( const QString& text )
+{
+ mLastText = text;
+ KLineEdit::setText( text );
+}
+
+NamespaceEditDialog::NamespaceEditDialog( QWidget *parent,
+ ImapAccountBase::imapNamespace type, ImapAccountBase::nsDelimMap* map )
+ : KDialogBase( parent, "edit_namespace", false, QString::null,
+ Ok|Cancel, Ok, true ), mType( type ), mNamespaceMap( map )
+{
+ QVBox *page = makeVBoxMainWidget();
+
+ QString ns;
+ if ( mType == ImapAccountBase::PersonalNS ) {
+ ns = i18n("Personal");
+ } else if ( mType == ImapAccountBase::OtherUsersNS ) {
+ ns = i18n("Other Users");
+ } else {
+ ns = i18n("Shared");
+ }
+ setCaption( i18n("Edit Namespace '%1'").arg(ns) );
+ QGrid* grid = new QGrid( 2, page );
+
+ mBg = new QButtonGroup( 0 );
+ connect( mBg, SIGNAL( clicked(int) ), this, SLOT( slotRemoveEntry(int) ) );
+ mDelimMap = mNamespaceMap->find( mType ).data();
+ ImapAccountBase::namespaceDelim::Iterator it;
+ for ( it = mDelimMap.begin(); it != mDelimMap.end(); ++it ) {
+ NamespaceLineEdit* edit = new NamespaceLineEdit( grid );
+ edit->setText( it.key() );
+ QToolButton* button = new QToolButton( grid );
+ button->setIconSet(
+ KGlobal::iconLoader()->loadIconSet( "editdelete", KIcon::Small, 0 ) );
+ button->setAutoRaise( true );
+ button->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
+ button->setFixedSize( 22, 22 );
+ mLineEditMap[ mBg->insert( button ) ] = edit;
+ }
+}
+
+void NamespaceEditDialog::slotRemoveEntry( int id )
+{
+ if ( mLineEditMap.contains( id ) ) {
+ // delete the lineedit and remove namespace from map
+ NamespaceLineEdit* edit = mLineEditMap[id];
+ mDelimMap.remove( edit->text() );
+ if ( edit->isModified() ) {
+ mDelimMap.remove( edit->lastText() );
+ }
+ mLineEditMap.remove( id );
+ delete edit;
+ }
+ if ( mBg->find( id ) ) {
+ // delete the button
+ delete mBg->find( id );
+ }
+ adjustSize();
+}
+
+void NamespaceEditDialog::slotOk()
+{
+ QMap<int, NamespaceLineEdit*>::Iterator it;
+ for ( it = mLineEditMap.begin(); it != mLineEditMap.end(); ++it ) {
+ NamespaceLineEdit* edit = it.data();
+ if ( edit->isModified() ) {
+ // register delimiter for new namespace
+ mDelimMap[edit->text()] = mDelimMap[edit->lastText()];
+ mDelimMap.remove( edit->lastText() );
+ }
+ }
+ mNamespaceMap->replace( mType, mDelimMap );
+ KDialogBase::slotOk();
+}
+
+} // namespace KMail
+
+#include "accountdialog.moc"
diff --git a/kmail/accountdialog.h b/kmail/accountdialog.h
new file mode 100644
index 00000000..784cee00
--- /dev/null
+++ b/kmail/accountdialog.h
@@ -0,0 +1,340 @@
+/* -*- c++ -*-
+ * accountdialog.h
+ *
+ * kmail: KDE mail client
+ * This file: Copyright (C) 2000 Espen Sand, espen@kde.org
+ *
+ * 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 _ACCOUNT_DIALOG_H_
+#define _ACCOUNT_DIALOG_H_
+
+#include <kdialogbase.h>
+#include <klistview.h>
+#include <klineedit.h>
+#include <qguardedptr.h>
+#include "imapaccountbase.h"
+
+class QRegExpValidator;
+class QCheckBox;
+class QComboBox;
+class QPushButton;
+class QLabel;
+class QLineEdit;
+class QRadioButton;
+class QToolButton;
+class KIntNumInput;
+class KMAccount;
+class KMFolder;
+class KMServerTest;
+class QButtonGroup;
+
+namespace KPIM {
+class IdentityCombo;
+}
+
+namespace KMail {
+
+class SieveConfigEditor;
+class FolderRequester;
+
+class AccountDialog : public KDialogBase
+{
+ Q_OBJECT
+
+ public:
+ AccountDialog( const QString & caption, KMAccount *account,
+ QWidget *parent=0, const char *name=0, bool modal=true );
+ virtual ~AccountDialog();
+ private:
+ struct LocalWidgets
+ {
+ QLabel *titleLabel;
+ QLineEdit *nameEdit;
+ QComboBox *locationEdit;
+ QRadioButton *lockMutt;
+ QRadioButton *lockMuttPriv;
+ QRadioButton *lockProcmail;
+ QComboBox *procmailLockFileName;
+ QRadioButton *lockFcntl;
+ QRadioButton *lockNone;
+ QLineEdit *precommand;
+#if 0
+ QCheckBox *resourceCheck;
+ QPushButton *resourceClearButton;
+ QPushButton *resourceClearPastButton;
+#endif
+ QCheckBox *includeInCheck;
+ QCheckBox *intervalCheck;
+ QLabel *intervalLabel;
+ KIntNumInput *intervalSpin;
+ QComboBox *folderCombo;
+ //QComboBox *identityCombo;
+ KPIM::IdentityCombo *identityCombo;
+ QLabel *identityLabel;
+ };
+
+ struct MaildirWidgets
+ {
+ QLabel *titleLabel;
+ QLineEdit *nameEdit;
+ QComboBox *locationEdit;
+ QLineEdit *precommand;
+#if 0
+ QCheckBox *resourceCheck;
+ QPushButton *resourceClearButton;
+ QPushButton *resourceClearPastButton;
+#endif
+ QCheckBox *includeInCheck;
+ QCheckBox *intervalCheck;
+ QLabel *intervalLabel;
+ KIntNumInput *intervalSpin;
+ QComboBox *folderCombo;
+ //QComboBox *identityCombo;
+ KPIM::IdentityCombo *identityCombo;
+ QLabel *identityLabel;
+ };
+
+ struct PopWidgets
+ {
+ QLabel *titleLabel;
+ QLineEdit *nameEdit;
+ QLineEdit *loginEdit;
+ QLineEdit *passwordEdit;
+ QLineEdit *hostEdit;
+ QLineEdit *portEdit;
+ QLineEdit *precommand;
+ QButtonGroup *encryptionGroup;
+ QRadioButton *encryptionNone;
+ QRadioButton *encryptionSSL;
+ QRadioButton *encryptionTLS;
+ QButtonGroup *authGroup;
+ QRadioButton *authUser;
+ QRadioButton *authPlain;
+ QRadioButton *authLogin;
+ QRadioButton *authCRAM_MD5;
+ QRadioButton *authDigestMd5;
+ QRadioButton *authNTLM;
+ QRadioButton *authGSSAPI;
+ QRadioButton *authAPOP;
+
+ QPushButton *checkCapabilities;
+ QCheckBox *usePipeliningCheck;
+ QCheckBox *storePasswordCheck;
+ QCheckBox *leaveOnServerCheck;
+ QCheckBox *leaveOnServerDaysCheck;
+ KIntNumInput *leaveOnServerDaysSpin;
+ QCheckBox *leaveOnServerCountCheck;
+ KIntNumInput *leaveOnServerCountSpin;
+ QCheckBox *leaveOnServerSizeCheck;
+ KIntNumInput *leaveOnServerSizeSpin;
+#if 0
+ QCheckBox *resourceCheck;
+ QPushButton *resourceClearButton;
+ QPushButton *resourceClearPastButton;
+#endif
+ QCheckBox *includeInCheck;
+ QCheckBox *intervalCheck;
+ QCheckBox *filterOnServerCheck;
+ QLabel *intervalLabel;
+ KIntNumInput *intervalSpin;
+ KIntNumInput *filterOnServerSizeSpin;
+ QComboBox *folderCombo;
+ //QComboBox *identityCombo;
+ KPIM::IdentityCombo *identityCombo;
+ QLabel *identityLabel;
+ };
+
+ struct ImapWidgets
+ {
+ QLabel *titleLabel;
+ QLineEdit *nameEdit;
+ QLineEdit *loginEdit;
+ QLineEdit *passwordEdit;
+ QLineEdit *hostEdit;
+ QLineEdit *portEdit;
+#if 0
+ QCheckBox *resourceCheck;
+ QPushButton *resourceClearButton;
+ QPushButton *resourceClearPastButton;
+#endif
+ QCheckBox *autoExpungeCheck; // only used by normal (online) IMAP
+ QCheckBox *hiddenFoldersCheck;
+ QCheckBox *subscribedFoldersCheck;
+ QCheckBox *locallySubscribedFoldersCheck;
+ QCheckBox *loadOnDemandCheck;
+ QCheckBox *storePasswordCheck;
+ QCheckBox *progressDialogCheck; // only used by Disconnected IMAP
+ QCheckBox *includeInCheck;
+ QCheckBox *intervalCheck;
+ QCheckBox *listOnlyOpenCheck;
+ QLabel *intervalLabel;
+ KIntNumInput *intervalSpin;
+ QButtonGroup *encryptionGroup;
+ QRadioButton *encryptionNone;
+ QRadioButton *encryptionSSL;
+ QRadioButton *encryptionTLS;
+ QButtonGroup *authGroup;
+ QRadioButton *authUser;
+ QRadioButton *authPlain;
+ QRadioButton *authLogin;
+ QRadioButton *authCramMd5;
+ QRadioButton *authDigestMd5;
+ QRadioButton *authGSSAPI;
+ QRadioButton *authNTLM;
+ QRadioButton *authAnonymous;
+ QPushButton *checkCapabilities;
+ FolderRequester *trashCombo;
+ KLineEdit *personalNS;
+ KLineEdit *otherUsersNS;
+ KLineEdit *sharedNS;
+ QToolButton *editPNS;
+ QToolButton *editONS;
+ QToolButton *editSNS;
+ ImapAccountBase::nsDelimMap nsMap;
+ KPIM::IdentityCombo *identityCombo;
+ QLabel *identityLabel;
+ };
+
+ private slots:
+ virtual void slotOk();
+ void slotLocationChooser();
+ void slotMaildirChooser();
+ void slotEnablePopInterval( bool state );
+ void slotEnableImapInterval( bool state );
+ void slotEnableLocalInterval( bool state );
+ void slotEnableMaildirInterval( bool state );
+ void slotFontChanged();
+ void slotLeaveOnServerClicked();
+ void slotEnableLeaveOnServerDays( bool state );
+ void slotEnableLeaveOnServerCount( bool state );
+ void slotEnableLeaveOnServerSize( bool state );
+ void slotFilterOnServerClicked();
+ void slotPipeliningClicked();
+ void slotPopEncryptionChanged(int);
+ void slotImapEncryptionChanged(int);
+ void slotCheckPopCapabilities();
+ void slotCheckImapCapabilities();
+ void slotPopCapabilities( const QStringList &, const QStringList & );
+ void slotImapCapabilities( const QStringList &, const QStringList & );
+ void slotReloadNamespaces();
+ void slotSetupNamespaces( const ImapAccountBase::nsDelimMap& map );
+ void slotEditPersonalNamespace();
+ void slotEditOtherUsersNamespace();
+ void slotEditSharedNamespace();
+ void slotConnectionResult( int errorCode, const QString& );
+ void slotLeaveOnServerDaysChanged( int value );
+ void slotLeaveOnServerCountChanged( int value );
+ void slotFilterOnServerSizeChanged( int value );
+#if 0
+ // Moc doesn't understand #if 0, so they are also commented out
+ // void slotClearResourceAllocations();
+ // void slotClearPastResourceAllocations();
+#endif
+
+ private:
+ void makeLocalAccountPage();
+ void makeMaildirAccountPage();
+ void makePopAccountPage();
+ void makeImapAccountPage( bool disconnected = false );
+ void setupSettings();
+ void saveSettings();
+ void checkHighest( QButtonGroup * );
+ static unsigned int popCapabilitiesFromStringList( const QStringList & );
+ static unsigned int imapCapabilitiesFromStringList( const QStringList & );
+ void enablePopFeatures( unsigned int );
+ void enableImapAuthMethods( unsigned int );
+ void initAccountForConnect();
+ const QString namespaceListToString( const QStringList& list );
+
+ private:
+ LocalWidgets mLocal;
+ MaildirWidgets mMaildir;
+ PopWidgets mPop;
+ ImapWidgets mImap;
+ KMAccount *mAccount;
+ QValueList<QGuardedPtr<KMFolder> > mFolderList;
+ QStringList mFolderNames;
+ KMServerTest *mServerTest;
+ enum EncryptionMethods {
+ NoEncryption = 0,
+ SSL = 1,
+ TLS = 2
+ };
+ enum Capabilities {
+ Plain = 1,
+ Login = 2,
+ CRAM_MD5 = 4,
+ Digest_MD5 = 8,
+ Anonymous = 16,
+ APOP = 32,
+ Pipelining = 64,
+ TOP = 128,
+ UIDL = 256,
+ STLS = 512, // TLS for POP
+ STARTTLS = 512, // TLS for IMAP
+ GSSAPI = 1024,
+ NTLM = 2048,
+ AllCapa = 0xffffffff
+ };
+ unsigned int mCurCapa;
+ unsigned int mCapaNormal;
+ unsigned int mCapaSSL;
+ unsigned int mCapaTLS;
+ KMail::SieveConfigEditor *mSieveConfigEditor;
+ QRegExpValidator *mValidator;
+};
+
+class NamespaceLineEdit: public KLineEdit
+{
+ Q_OBJECT
+
+ public:
+ NamespaceLineEdit( QWidget* parent );
+
+ const QString& lastText() { return mLastText; }
+
+ public slots:
+ virtual void setText ( const QString & );
+
+ private:
+ QString mLastText;
+};
+
+class NamespaceEditDialog: public KDialogBase
+{
+ Q_OBJECT
+
+ public:
+ NamespaceEditDialog( QWidget* parent, ImapAccountBase::imapNamespace type,
+ ImapAccountBase::nsDelimMap* map );
+
+ protected slots:
+ void slotOk();
+ void slotRemoveEntry( int );
+
+ private:
+ ImapAccountBase::imapNamespace mType;
+ ImapAccountBase::nsDelimMap* mNamespaceMap;
+ ImapAccountBase::namespaceDelim mDelimMap;
+ QMap<int, NamespaceLineEdit*> mLineEditMap;
+ QButtonGroup* mBg;
+};
+
+} // namespace KMail
+
+#endif
diff --git a/kmail/accountmanager.cpp b/kmail/accountmanager.cpp
new file mode 100644
index 00000000..c27432ae
--- /dev/null
+++ b/kmail/accountmanager.cpp
@@ -0,0 +1,415 @@
+// KMail Account Manager
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "accountmanager.h"
+
+#include "kmaccount.h"
+#include "kmacctfolder.h"
+#include "kmacctmaildir.h"
+#include "kmacctlocal.h"
+#include "popaccount.h"
+#include "kmacctimap.h"
+#include "networkaccount.h"
+#include "kmacctcachedimap.h"
+#include "broadcaststatus.h"
+#include "kmfiltermgr.h"
+#include "globalsettings.h"
+
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+#include <kconfig.h>
+#include <kapplication.h>
+
+#include <qregexp.h>
+#include <qvaluelist.h>
+
+using namespace KMail;
+
+//-----------------------------------------------------------------------------
+AccountManager::AccountManager()
+ :QObject(), mNewMailArrived( false ), mInteractive( false ),
+ mTotalNewMailsArrived( 0 ), mDisplaySummary( false )
+{
+ mAcctChecking.clear();
+ mAcctTodo.clear();
+}
+
+//-----------------------------------------------------------------------------
+AccountManager::~AccountManager()
+{
+ writeConfig( false );
+}
+
+
+//-----------------------------------------------------------------------------
+void AccountManager::writeConfig( bool withSync )
+{
+ KConfig* config = KMKernel::config();
+ QString groupName;
+
+ KConfigGroupSaver saver(config, "General");
+ config->writeEntry("accounts", mAcctList.count());
+
+ // first delete all account groups in the config file:
+ QStringList accountGroups =
+ config->groupList().grep( QRegExp( "Account \\d+" ) );
+ for ( QStringList::Iterator it = accountGroups.begin() ;
+ it != accountGroups.end() ; ++it )
+ config->deleteGroup( *it );
+
+ // now write new account groups:
+ int i = 1;
+ for ( AccountList::ConstIterator it( mAcctList.begin() ), end( mAcctList.end() ); it != end; ++it, ++i ) {
+ groupName.sprintf("Account %d", i);
+ KConfigGroupSaver saver(config, groupName);
+ (*it)->writeConfig(*config);
+ }
+ if (withSync) config->sync();
+}
+
+
+//-----------------------------------------------------------------------------
+void AccountManager::readConfig(void)
+{
+ KConfig* config = KMKernel::config();
+ KMAccount* acct;
+ QString acctType, acctName;
+ QCString groupName;
+ int i, num;
+ uint id;
+
+ for ( AccountList::Iterator it( mAcctList.begin() ), end( mAcctList.end() ); it != end; ++it )
+ delete *it;
+ mAcctList.clear();
+
+ KConfigGroup general(config, "General");
+ num = general.readNumEntry("accounts", 0);
+
+ for (i=1; i<=num; i++)
+ {
+ groupName.sprintf("Account %d", i);
+ KConfigGroupSaver saver(config, groupName);
+ acctType = config->readEntry("Type");
+ // Provide backwards compatibility
+ if (acctType == "advanced pop" || acctType == "experimental pop")
+ acctType = "pop";
+ acctName = config->readEntry("Name");
+ id = config->readUnsignedNumEntry("Id", 0);
+ if (acctName.isEmpty()) acctName = i18n("Account %1").arg(i);
+ acct = create(acctType, acctName, id);
+ if (!acct) continue;
+ add(acct);
+ acct->readConfig(*config);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void AccountManager::singleCheckMail(KMAccount *account, bool interactive)
+{
+ mNewMailArrived = false;
+ mInteractive = interactive;
+
+ // if sync has been requested by the user then check if check-interval was disabled by user, if yes, then
+ // de-install the timer
+ // Safe guard against an infinite sync loop (kolab/issue2607)
+ if ( mInteractive )
+ account->readTimerConfig();
+
+ // queue the account
+ mAcctTodo.append(account);
+
+ if (account->checkingMail())
+ {
+ kdDebug(5006) << "account " << account->name() << " busy, queuing" << endl;
+ return;
+ }
+
+ processNextCheck(false);
+}
+
+//-----------------------------------------------------------------------------
+void AccountManager::processNextCheck( bool _newMail )
+{
+ kdDebug(5006) << "processNextCheck, remaining " << mAcctTodo.count() << endl;
+ if ( _newMail )
+ mNewMailArrived = true;
+
+ for ( AccountList::Iterator it( mAcctChecking.begin() ), end( mAcctChecking.end() ); it != end; ) {
+ KMAccount* acct = *it;
+ ++it;
+ if ( acct->checkingMail() )
+ continue;
+ // check done
+ kdDebug(5006) << "account " << acct->name() << " finished check" << endl;
+ mAcctChecking.remove( acct );
+ kmkernel->filterMgr()->deref();
+ disconnect( acct, SIGNAL( finishedCheck( bool, CheckStatus ) ),
+ this, SLOT( processNextCheck( bool ) ) );
+ }
+ if ( mAcctChecking.isEmpty() ) {
+ // all checks finished, display summary
+ if ( mDisplaySummary )
+ KPIM::BroadcastStatus::instance()->setStatusMsgTransmissionCompleted(
+ mTotalNewMailsArrived );
+ emit checkedMail( mNewMailArrived, mInteractive, mTotalNewInFolder );
+ mTotalNewMailsArrived = 0;
+ mTotalNewInFolder.clear();
+ mDisplaySummary = false;
+ }
+ if ( mAcctTodo.isEmpty() ) return;
+
+ QString accountHostName;
+
+ KMAccount *curAccount = 0;
+ for ( AccountList::Iterator it ( mAcctTodo.begin() ), last ( mAcctTodo.end() ); it != last; ) {
+ KMAccount *acct = *it;
+ ++it;
+ if ( !acct->checkingMail() && acct->mailCheckCanProceed() ) {
+ curAccount = acct;
+ mAcctTodo.remove( acct );
+ break;
+ }
+ }
+ if ( !curAccount ) return; // no account or all of them are already checking
+
+ if ( curAccount->type() != "imap" && curAccount->type() != "cachedimap" &&
+ curAccount->folder() == 0 ) {
+ QString tmp = i18n("Account %1 has no mailbox defined:\n"
+ "mail checking aborted;\n"
+ "check your account settings.")
+ .arg(curAccount->name());
+ KMessageBox::information(0,tmp);
+ emit checkedMail( false, mInteractive, mTotalNewInFolder );
+ mTotalNewMailsArrived = 0;
+ mTotalNewInFolder.clear();
+ return;
+ }
+
+ connect( curAccount, SIGNAL( finishedCheck( bool, CheckStatus ) ),
+ this, SLOT( processNextCheck( bool ) ) );
+
+ KPIM::BroadcastStatus::instance()->setStatusMsg(
+ i18n("Checking account %1 for new mail").arg(curAccount->name()));
+
+ kdDebug(5006) << "processing next mail check for " << curAccount->name() << endl;
+
+ curAccount->setCheckingMail( true );
+ mAcctChecking.append( curAccount );
+ kmkernel->filterMgr()->ref();
+ curAccount->processNewMail( mInteractive );
+}
+
+//-----------------------------------------------------------------------------
+KMAccount* AccountManager::create( const QString &aType, const QString &aName, uint id )
+{
+ KMAccount* act = 0;
+ if ( id == 0 )
+ id = createId();
+
+ if ( aType == "local" ) {
+ act = new KMAcctLocal(this, aName.isEmpty() ? i18n("Local Account") : aName, id);
+ act->setFolder( kmkernel->inboxFolder() );
+ } else if ( aType == "maildir" ) {
+ act = new KMAcctMaildir(this, aName.isEmpty() ? i18n("Local Account") : aName, id);
+ act->setFolder( kmkernel->inboxFolder() );
+ } else if ( aType == "pop" ) {
+ act = new KMail::PopAccount(this, aName.isEmpty() ? i18n("POP Account") : aName, id);
+ act->setFolder( kmkernel->inboxFolder() );
+ } else if ( aType == "imap" ) {
+ act = new KMAcctImap(this, aName.isEmpty() ? i18n("IMAP Account") : aName, id);
+ } else if (aType == "cachedimap") {
+ act = new KMAcctCachedImap(this, aName.isEmpty() ? i18n("IMAP Account") : aName, id);
+ }
+ if ( !act ) {
+ kdWarning(5006) << "Attempt to instantiate a non-existing account type!" << endl;
+ return 0;
+ }
+ connect( act, SIGNAL( newMailsProcessed( const QMap<QString, int> & ) ),
+ this, SLOT( addToTotalNewMailCount( const QMap<QString, int> & ) ) );
+ return act;
+}
+
+
+//-----------------------------------------------------------------------------
+void AccountManager::add( KMAccount *account )
+{
+ if ( account ) {
+ mAcctList.append( account );
+ // init folder's account list
+ KMAcctFolder *folder = static_cast<KMAcctFolder*>( account->folder() );
+ if ( folder && !folder->hasAccounts() ) {
+ folder->addAccount( account );
+ }
+ emit accountAdded( account );
+ account->installTimer();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+KMAccount* AccountManager::findByName(const QString &aName) const
+{
+ if ( aName.isEmpty() ) return 0;
+
+ for ( AccountList::ConstIterator it( mAcctList.begin() ), end( mAcctList.end() ); it != end; ++it ) {
+ if ( (*it)->name() == aName ) return (*it);
+ }
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+KMAccount* AccountManager::find( const uint id ) const
+{
+ if (id == 0) return 0;
+ for ( AccountList::ConstIterator it( mAcctList.begin() ), end( mAcctList.end() ); it != end; ++it ) {
+ if ( (*it)->id() == id ) return (*it);
+ }
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+KMAccount* AccountManager::first()
+{
+ if ( !mAcctList.empty() ) {
+ mPtrListInterfaceProxyIterator = mAcctList.begin();
+ return *mPtrListInterfaceProxyIterator;
+ } else {
+ return 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+KMAccount* AccountManager::next()
+{
+ ++mPtrListInterfaceProxyIterator;
+ if ( mPtrListInterfaceProxyIterator == mAcctList.end() )
+ return 0;
+ else
+ return *mPtrListInterfaceProxyIterator;
+}
+
+//-----------------------------------------------------------------------------
+bool AccountManager::remove( KMAccount* acct )
+{
+ if( !acct )
+ return false;
+ mAcctList.remove( acct );
+ emit accountRemoved( acct );
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+void AccountManager::checkMail( bool _interactive )
+{
+ mNewMailArrived = false;
+
+ if ( mAcctList.isEmpty() ) {
+ KMessageBox::information( 0,i18n("You need to add an account in the network "
+ "section of the settings in order to receive mail.") );
+ return;
+ }
+ mDisplaySummary = true;
+
+ mTotalNewMailsArrived=0;
+ mTotalNewInFolder.clear();
+
+ for ( AccountList::Iterator it( mAcctList.begin() ), end( mAcctList.end() ); it != end; ++it ) {
+ if ( !(*it)->checkExclude() )
+ singleCheckMail( (*it), _interactive);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void AccountManager::singleInvalidateIMAPFolders(KMAccount *account) {
+ account->invalidateIMAPFolders();
+}
+
+
+void AccountManager::invalidateIMAPFolders()
+{
+ for ( AccountList::ConstIterator it( mAcctList.begin() ), end( mAcctList.end() ); it != end; ++it )
+ singleInvalidateIMAPFolders( *it );
+}
+
+
+//-----------------------------------------------------------------------------
+QStringList AccountManager::getAccounts() const
+{
+ QStringList strList;
+ for ( AccountList::ConstIterator it( mAcctList.begin() ), end( mAcctList.end() ); it != end; ++it ) {
+ strList.append( (*it)->name() );
+ }
+ return strList;
+}
+
+//-----------------------------------------------------------------------------
+void AccountManager::intCheckMail(int item, bool _interactive)
+{
+ mNewMailArrived = false;
+ mTotalNewMailsArrived = 0;
+ mTotalNewInFolder.clear();
+ if ( KMAccount *acct = mAcctList[ item ] )
+ singleCheckMail( acct, _interactive );
+ mDisplaySummary = false;
+}
+
+
+//-----------------------------------------------------------------------------
+void AccountManager::addToTotalNewMailCount( const QMap<QString, int> & newInFolder )
+{
+ for ( QMap<QString, int>::const_iterator it = newInFolder.begin();
+ it != newInFolder.end(); ++it ) {
+ mTotalNewMailsArrived += it.data();
+ if ( mTotalNewInFolder.find( it.key() ) == mTotalNewInFolder.end() )
+ mTotalNewInFolder[it.key()] = it.data();
+ else
+ mTotalNewInFolder[it.key()] += it.data();
+ }
+}
+
+//-----------------------------------------------------------------------------
+uint AccountManager::createId()
+{
+ QValueList<uint> usedIds;
+ for ( AccountList::ConstIterator it( mAcctList.begin() ), end( mAcctList.end() ); it != end; ++it ) {
+ usedIds << (*it)->id();
+ }
+
+ usedIds << 0; // 0 is default for unknown
+ int newId;
+ do
+ {
+ newId = kapp->random();
+ } while ( usedIds.find(newId) != usedIds.end() );
+
+ return newId;
+}
+
+//-----------------------------------------------------------------------------
+void AccountManager::cancelMailCheck()
+{
+ for ( AccountList::ConstIterator it( mAcctList.begin() ), end( mAcctList.end() ); it != end; ++it ) {
+ (*it)->cancelMailCheck();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void AccountManager::readPasswords()
+{
+ for ( AccountList::ConstIterator it( mAcctList.begin() ), end( mAcctList.end() ); it != end; ++it ) {
+ NetworkAccount *acct = dynamic_cast<NetworkAccount*>( (*it) );
+ if ( acct )
+ acct->readPassword();
+ }
+}
+
+#include "accountmanager.moc"
diff --git a/kmail/accountmanager.h b/kmail/accountmanager.h
new file mode 100644
index 00000000..fda0591f
--- /dev/null
+++ b/kmail/accountmanager.h
@@ -0,0 +1,141 @@
+/* -*- mode: C++ -*-
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ *
+ * 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 accountmanager_h
+#define accountmanager_h
+
+#include <qobject.h>
+#include "kmaccount.h"
+#include <kdepimmacros.h>
+
+class QString;
+class QStringList;
+
+namespace KMail {
+/**
+ * The account manager is responsible for creating accounts of various types
+ * via the factory method create() and for keeping track of them.
+ */
+class KDE_EXPORT AccountManager: public QObject
+{
+ Q_OBJECT
+ friend class ::KMAccount;
+
+public:
+ /** Initializes the account manager. readConfig() needs to be called in
+ * order to fill it with persisted account information from the config file. */
+ AccountManager();
+ ~AccountManager();
+
+ /** Completely reload accounts from config. */
+ void readConfig(void);
+
+ /** Write accounts to config. */
+ void writeConfig( bool withSync=true );
+
+ /** Create a new account of given type with given name. Currently
+ the types "local" for local mail folders and "pop" are supported. */
+ KMAccount* create( const QString& type,
+ const QString& name = QString::null,
+ uint id = 0);
+
+ /** Adds an account to the list of accounts */
+ void add( KMAccount *account );
+
+ /** Find account by name. Returns 0 if account does not exist.
+ Search is done case sensitive. */
+ KMAccount* findByName( const QString& name ) const;
+
+ /** Find account by id. Returns 0 if account does not exist.
+ */
+ KMAccount* find( const uint id ) const;
+
+ /** Physically remove account. Also deletes the given account object !
+ Returns FALSE and does nothing if the account cannot be removed. */
+ bool remove( KMAccount* );
+
+ /** First account of the list */
+ const KMAccount* first() const { return first(); }
+ KMAccount* first();
+
+ /** Next account of the list */
+ const KMAccount* next() const { return next(); }
+ KMAccount* next();
+
+ /** Processes all accounts looking for new mail */
+ void checkMail( bool interactive = true );
+
+ /** Delete all IMAP folders and resync them */
+ void invalidateIMAPFolders();
+
+ QStringList getAccounts() const;
+
+ /// Called on exit (KMMainWin::queryExit)
+ void cancelMailCheck();
+
+ /** Read passwords of all accounts from the wallet */
+ void readPasswords();
+
+public slots:
+ void singleCheckMail( KMAccount *, bool interactive = true );
+ void singleInvalidateIMAPFolders( KMAccount * );
+
+ void intCheckMail( int, bool interactive = true );
+ void processNextCheck( bool newMail );
+
+ /** this slot increases the count of new mails to show a total number
+ after checking in multiple accounts. */
+ void addToTotalNewMailCount( const QMap<QString, int> & newInFolder );
+
+
+signals:
+ /**
+ * Emitted if new mail has been collected.
+ * @param newMail true if there was new mail
+ * @param interactive true if the mail check was initiated by the user
+ * @param newInFolder number of new messages for each folder
+ **/
+ void checkedMail( bool newMail, bool interactive,
+ const QMap<QString, int> & newInFolder );
+ /** emitted when an account is removed */
+ void accountRemoved( KMAccount* account );
+ /** emitted when an account is added */
+ void accountAdded( KMAccount* account );
+
+private:
+ /** Create a new unique ID */
+ uint createId();
+
+ AccountList mAcctList;
+ AccountList::Iterator mPtrListInterfaceProxyIterator;
+ AccountList mAcctChecking;
+ AccountList mAcctTodo;
+ bool mNewMailArrived;
+ bool mInteractive;
+ int mTotalNewMailsArrived;
+
+ // for detailed (per folder) new mail notification
+ QMap<QString, int> mTotalNewInFolder;
+
+ // if a summary should be displayed
+ bool mDisplaySummary;
+};
+
+} // namespace KMail
+#endif /*accountmanager_h*/
diff --git a/kmail/accountwizard.cpp b/kmail/accountwizard.cpp
new file mode 100644
index 00000000..cc831790
--- /dev/null
+++ b/kmail/accountwizard.cpp
@@ -0,0 +1,799 @@
+/*******************************************************************************
+**
+** Filename : accountwizard.cpp
+** Created on : 07 February, 2005
+** Copyright : (c) 2005 Tobias Koenig
+** Email : tokoe@kde.org
+**
+*******************************************************************************/
+
+/*******************************************************************************
+**
+** 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.
+**
+** In addition, as a special exception, the copyright holders give
+** permission to link the code of this program with any edition of
+** the Qt library by Trolltech AS, Norway (or with modified versions
+** of Qt that use the same license as Qt), and distribute linked
+** combinations including the two. You must obey the GNU General
+** Public License in all respects for all of the code used other than
+** Qt. If you modify this file, you may extend this exception to
+** your version of the file, but you are not obligated to do so. If
+** you do not wish to do so, delete this exception statement from
+** your version.
+*******************************************************************************/
+
+#include <kdialog.h>
+#include <kfiledialog.h>
+#include <klineedit.h>
+#include <klistbox.h>
+#include <klocale.h>
+
+#include <qcheckbox.h>
+#include <qdir.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qpushbutton.h>
+#include <qvbox.h>
+
+#include "kmacctlocal.h"
+#include "kmkernel.h"
+#include "popaccount.h"
+#include "kmacctimap.h"
+#include "kmacctcachedimap.h"
+#include "kmacctmaildir.h"
+#include "accountmanager.h"
+using KMail::AccountManager;
+
+#include "globalsettings.h"
+#include "kmservertest.h"
+#include "kmtransport.h"
+#include "libkpimidentities/identity.h"
+#include "libkpimidentities/identitymanager.h"
+#include "protocols.h"
+
+#include "accountwizard.h"
+
+enum Capabilities
+{
+ Plain = 1,
+ Login = 2,
+ CRAM_MD5 = 4,
+ Digest_MD5 = 8,
+ Anonymous = 16,
+ APOP = 32,
+ Pipelining = 64,
+ TOP = 128,
+ UIDL = 256,
+ STLS = 512, // TLS for POP
+ STARTTLS = 512, // TLS for IMAP
+ GSSAPI = 1024,
+ NTLM = 2048,
+ AllCapa = 0xffffffff
+};
+
+class AccountTypeBox : public KListBox
+{
+ public:
+ enum Type { Local, POP3, IMAP, dIMAP, Maildir };
+
+ AccountTypeBox( QWidget *parent )
+ : KListBox( parent, "AccountTypeBox" )
+ {
+ mTypeList << i18n( "Local mailbox" );
+ mTypeList << i18n( "POP3" );
+ mTypeList << i18n( "IMAP" );
+ mTypeList << i18n( "Disconnected IMAP" );
+ mTypeList << i18n( "Maildir mailbox" );
+
+ insertStringList( mTypeList );
+ }
+
+ void setType( Type type )
+ {
+ setCurrentItem( (int)type );
+ }
+
+ Type type() const
+ {
+ return (Type)currentItem();
+ }
+
+ private:
+ QStringList mTypeList;
+};
+
+AccountWizard::AccountWizard( KMKernel *kernel, QWidget *parent )
+ : KWizard( parent, "KWizard" ), mKernel( kernel ),
+ mAccount( 0 ), mTransportInfo( 0 ), mServerTest( 0 )
+{
+ setupWelcomePage();
+ setupAccountTypePage();
+ setupAccountInformationPage();
+ setupLoginInformationPage();
+ setupServerInformationPage();
+}
+
+void AccountWizard::start( KMKernel *kernel, QWidget *parent )
+{
+ KConfigGroup wizardConfig( KMKernel::config(), "AccountWizard" );
+
+ if ( wizardConfig.readBoolEntry( "ShowOnStartup", true ) ) {
+ AccountWizard wizard( kernel, parent );
+ int result = wizard.exec();
+ if ( result == QDialog::Accepted ) {
+ wizardConfig.writeEntry( "ShowOnStartup", false );
+ kernel->slotConfigChanged();
+ }
+ }
+}
+
+void AccountWizard::showPage( QWidget *page )
+{
+ if ( page == mWelcomePage ) {
+ // do nothing
+ } else if ( page == mAccountTypePage ) {
+ if ( mTypeBox->currentItem() == -1 )
+ mTypeBox->setType( AccountTypeBox::POP3 );
+ } else if ( page == mAccountInformationPage ) {
+ if ( mRealName->text().isEmpty() && mEMailAddress->text().isEmpty() &&
+ mOrganization->text().isEmpty() ) {
+ KPIM::IdentityManager *manager = mKernel->identityManager();
+ const KPIM::Identity &identity = manager->defaultIdentity();
+
+ mRealName->setText( identity.fullName() );
+ mEMailAddress->setText( identity.emailAddr() );
+ mOrganization->setText( identity.organization() );
+ }
+ } else if ( page == mLoginInformationPage ) {
+ if ( mLoginName->text().isEmpty() ) {
+ // try to extract login from email address
+ QString email = mEMailAddress->text();
+ int pos = email.find( '@' );
+ if ( pos != -1 )
+ mLoginName->setText( email.left( pos ) );
+
+ // take the whole email as login otherwise?!?
+ }
+ } else if ( page == mServerInformationPage ) {
+ if ( mTypeBox->type() == AccountTypeBox::Local ||
+ mTypeBox->type() == AccountTypeBox::Maildir ) {
+ mIncomingServerWdg->hide();
+ mIncomingLocationWdg->show();
+ mIncomingLabel->setText( i18n( "Location:" ) );
+
+ if ( mTypeBox->type() == AccountTypeBox::Local )
+ mIncomingLocation->setText( QDir::homeDirPath() + "/inbox" );
+ else
+ mIncomingLocation->setText( QDir::homeDirPath() + "/Mail/" );
+ } else {
+ mIncomingLocationWdg->hide();
+ mIncomingServerWdg->show();
+ mIncomingLabel->setText( i18n( "Incoming server:" ) );
+ }
+
+ setFinishEnabled( mServerInformationPage, true );
+ }
+
+ QWizard::showPage( page );
+}
+
+void AccountWizard::setupWelcomePage()
+{
+ mWelcomePage = new QVBox( this );
+ ((QVBox*)mWelcomePage)->setSpacing( KDialog::spacingHint() );
+
+ QLabel *label = new QLabel( i18n( "Welcome to KMail" ), mWelcomePage );
+ QFont font = label->font();
+ font.setBold( true );
+ label->setFont( font );
+
+ new QLabel( i18n( "<qt>It seems you have started KMail for the first time. "
+ "You can use this wizard to setup your mail accounts. Just "
+ "enter the connection data that you received from your email provider "
+ "into the following pages.</qt>" ), mWelcomePage );
+
+ addPage( mWelcomePage, i18n( "Welcome" ) );
+}
+
+void AccountWizard::setupAccountTypePage()
+{
+ mAccountTypePage = new QVBox( this );
+ ((QVBox*)mAccountTypePage)->setSpacing( KDialog::spacingHint() );
+
+ new QLabel( i18n( "Select what kind of account you would like to create" ), mAccountTypePage );
+
+ mTypeBox = new AccountTypeBox( mAccountTypePage );
+
+ addPage( mAccountTypePage, i18n( "Account Type" ) );
+}
+
+void AccountWizard::setupAccountInformationPage()
+{
+ mAccountInformationPage = new QWidget( this );
+ QGridLayout *layout = new QGridLayout( mAccountInformationPage, 3, 2,
+ KDialog::marginHint(), KDialog::spacingHint() );
+
+ QLabel *label = new QLabel( i18n( "Real name:" ), mAccountInformationPage );
+ mRealName = new KLineEdit( mAccountInformationPage );
+ label->setBuddy( mRealName );
+
+ layout->addWidget( label, 0, 0 );
+ layout->addWidget( mRealName, 0, 1 );
+
+ label = new QLabel( i18n( "E-mail address:" ), mAccountInformationPage );
+ mEMailAddress = new KLineEdit( mAccountInformationPage );
+ label->setBuddy( mEMailAddress );
+
+ layout->addWidget( label, 1, 0 );
+ layout->addWidget( mEMailAddress, 1, 1 );
+
+ label = new QLabel( i18n( "Organization:" ), mAccountInformationPage );
+ mOrganization = new KLineEdit( mAccountInformationPage );
+ label->setBuddy( mOrganization );
+
+ layout->addWidget( label, 2, 0 );
+ layout->addWidget( mOrganization, 2, 1 );
+
+ addPage( mAccountInformationPage, i18n( "Account Information" ) );
+}
+
+void AccountWizard::setupLoginInformationPage()
+{
+ mLoginInformationPage = new QWidget( this );
+ QGridLayout *layout = new QGridLayout( mLoginInformationPage, 2, 2,
+ KDialog::marginHint(), KDialog::spacingHint() );
+
+ QLabel *label = new QLabel( i18n( "Login name:" ), mLoginInformationPage );
+ mLoginName = new KLineEdit( mLoginInformationPage );
+ label->setBuddy( mLoginName );
+
+ layout->addWidget( label, 0, 0 );
+ layout->addWidget( mLoginName, 0, 1 );
+
+ label = new QLabel( i18n( "Password:" ), mLoginInformationPage );
+ mPassword = new KLineEdit( mLoginInformationPage );
+ mPassword->setEchoMode( QLineEdit::Password );
+ label->setBuddy( mPassword );
+
+ layout->addWidget( label, 1, 0 );
+ layout->addWidget( mPassword, 1, 1 );
+
+ addPage( mLoginInformationPage, i18n( "Login Information" ) );
+}
+
+void AccountWizard::setupServerInformationPage()
+{
+ mServerInformationPage = new QWidget( this );
+ QGridLayout *layout = new QGridLayout( mServerInformationPage, 3, 2,
+ KDialog::marginHint(), KDialog::spacingHint() );
+
+ mIncomingLabel = new QLabel( mServerInformationPage );
+
+ mIncomingServerWdg = new QVBox( mServerInformationPage );
+ mIncomingServer = new KLineEdit( mIncomingServerWdg );
+ mIncomingUseSSL = new QCheckBox( i18n( "Use secure connection (SSL)" ), mIncomingServerWdg );
+
+ mIncomingLocationWdg = new QHBox( mServerInformationPage );
+ mIncomingLocation = new KLineEdit( mIncomingLocationWdg );
+ mChooseLocation = new QPushButton( i18n( "Choose..." ), mIncomingLocationWdg );
+
+ connect( mChooseLocation, SIGNAL( clicked() ),
+ this, SLOT( chooseLocation() ) );
+
+ layout->addWidget( mIncomingLabel, 0, 0, AlignTop );
+ layout->addWidget( mIncomingLocationWdg, 0, 1 );
+ layout->addWidget( mIncomingServerWdg, 0, 1 );
+
+ QLabel *label = new QLabel( i18n( "Outgoing server:" ), mServerInformationPage );
+ mOutgoingServer = new KLineEdit( mServerInformationPage );
+ label->setBuddy( mOutgoingServer );
+
+ layout->addWidget( label, 1, 0 );
+ layout->addWidget( mOutgoingServer, 1, 1 );
+
+ mOutgoingUseSSL = new QCheckBox( i18n( "Use secure connection (SSL)" ), mServerInformationPage );
+ layout->addWidget( mOutgoingUseSSL, 2, 1 );
+
+ mLocalDelivery = new QCheckBox( i18n( "Use local delivery" ),
+ mServerInformationPage );
+ layout->addWidget( mLocalDelivery, 3, 0 );
+
+ connect( mLocalDelivery, SIGNAL( toggled( bool ) ),
+ mOutgoingServer, SLOT( setDisabled( bool ) ) );
+
+ addPage( mServerInformationPage, i18n( "Server Information" ) );
+}
+
+void AccountWizard::chooseLocation()
+{
+ QString location;
+
+ if ( mTypeBox->type() == AccountTypeBox::Local ) {
+ location = KFileDialog::getSaveFileName( QString(), QString(), this );
+ } else if ( mTypeBox->type() == AccountTypeBox::Maildir ) {
+ location = KFileDialog::getExistingDirectory( QString(), this );
+ }
+
+ if ( !location.isEmpty() )
+ mIncomingLocation->setText( location );
+}
+
+QString AccountWizard::accountName() const
+{
+ // create account name
+ QString name( i18n( "None" ) );
+
+ QString email = mEMailAddress->text();
+ int pos = email.find( '@' );
+ if ( pos != -1 ) {
+ name = email.mid( pos + 1 );
+ name[ 0 ] = name[ 0 ].upper();
+ }
+
+ return name;
+}
+
+QLabel *AccountWizard::createInfoLabel( const QString &msg )
+{
+ QLabel *label = new QLabel( msg, this );
+ label->setFrameStyle( QFrame::Panel | QFrame::Raised );
+ label->resize( fontMetrics().width( msg ) + 20, label->height() * 2 );
+ label->move( width() / 2 - label->width() / 2, height() / 2 - label->height() / 2 );
+ label->show();
+
+ return label;
+}
+
+void AccountWizard::accept()
+{
+ // store identity information
+ KPIM::IdentityManager *manager = mKernel->identityManager();
+ KPIM::Identity &identity = manager->modifyIdentityForUoid( manager->defaultIdentity().uoid() );
+
+ identity.setFullName( mRealName->text() );
+ identity.setEmailAddr( mEMailAddress->text() );
+ identity.setOrganization( mOrganization->text() );
+
+ manager->commit();
+
+ QTimer::singleShot( 0, this, SLOT( createTransport() ) );
+}
+
+void AccountWizard::createTransport()
+{
+ // create outgoing account
+ KConfigGroup general( KMKernel::config(), "General" );
+
+ uint numTransports = general.readNumEntry( "transports", 0 );
+
+ for ( uint i = 1 ; i <= numTransports ; i++ ) {
+ KMTransportInfo *info = new KMTransportInfo();
+ info->readConfig( i );
+ mTransportInfoList.append( info );
+ }
+
+ mTransportInfo = new KMTransportInfo();
+
+ if ( mLocalDelivery->isChecked() ) { // local delivery
+ mTransportInfo->type = "sendmail";
+ mTransportInfo->name = i18n( "Sendmail" );
+ mTransportInfo->host = "/usr/sbin/sendmail"; // TODO: search for sendmail in PATH
+ mTransportInfo->auth = false;
+ mTransportInfo->setStorePasswd( false );
+
+ QTimer::singleShot( 0, this, SLOT( transportCreated() ) );
+ } else { // delivery via SMTP
+ mTransportInfo->type = "smtp";
+ mTransportInfo->name = accountName();
+ mTransportInfo->host = mOutgoingServer->text();
+ mTransportInfo->user = mLoginName->text();
+ mTransportInfo->setPasswd( mPassword->text() );
+
+ int port = (mOutgoingUseSSL->isChecked() ? 465 : 25);
+ checkSmtpCapabilities( mTransportInfo->host, port );
+ }
+}
+
+void AccountWizard::transportCreated()
+{
+ mTransportInfoList.append( mTransportInfo );
+
+ KConfigGroup general( KMKernel::config(), "General" );
+ general.writeEntry( "transports", mTransportInfoList.count() );
+
+ for ( uint i = 0 ; i < mTransportInfoList.count() ; i++ )
+ mTransportInfo->writeConfig( i + 1 );
+
+ // No default transport? => set the first transport as the default
+ if ( GlobalSettings::self()->defaultTransport().isEmpty() ) {
+ KConfigGroup general( KMKernel::config(), "General" );
+
+ if ( mTransportInfoList.count() > 0 ) {
+ KMTransportInfo info;
+ info.readConfig( 1 );
+ KConfigGroup composer( KMKernel::config(), "Composer" );
+ GlobalSettings::self()->setDefaultTransport( info.name );
+ GlobalSettings::self()->setCurrentTransport( info.name );
+ }
+ }
+
+ mTransportInfoList.setAutoDelete( true );
+ mTransportInfoList.clear();
+
+ QTimer::singleShot( 0, this, SLOT( createAccount() ) );
+}
+
+void AccountWizard::createAccount()
+{
+ // create incoming account
+ AccountManager *acctManager = mKernel->acctMgr();
+
+ int port = 0;
+
+ switch ( mTypeBox->type() ) {
+ case AccountTypeBox::Local:
+ {
+ mAccount = acctManager->create( "local", i18n( "Local Account" ) );
+ static_cast<KMAcctLocal*>( mAccount )->setLocation( mIncomingLocation->text() );
+ break;
+ }
+ case AccountTypeBox::POP3:
+ {
+ mAccount = acctManager->create( "pop", accountName() );
+ KMail::PopAccount *acct = static_cast<KMail::PopAccount*>( mAccount );
+ acct->setLogin( mLoginName->text() );
+ acct->setPasswd( mPassword->text() );
+ acct->setHost( mIncomingServer->text() );
+ port = mIncomingUseSSL->isChecked() ? 995 : 110;
+ break;
+ }
+ case AccountTypeBox::IMAP:
+ {
+ mAccount = acctManager->create( "imap", accountName() );
+ KMAcctImap *acct = static_cast<KMAcctImap*>( mAccount );
+ acct->setLogin( mLoginName->text() );
+ acct->setPasswd( mPassword->text() );
+ acct->setHost( mIncomingServer->text() );
+ port = mIncomingUseSSL->isChecked() ? 993 : 143;
+ break;
+ }
+ case AccountTypeBox::dIMAP:
+ {
+ mAccount = acctManager->create( "cachedimap", accountName() );
+ KMAcctCachedImap *acct = static_cast<KMAcctCachedImap*>( mAccount );
+ acct->setLogin( mLoginName->text() );
+ acct->setPasswd( mPassword->text() );
+ acct->setHost( mIncomingServer->text() );
+ port = mIncomingUseSSL->isChecked() ? 993 : 143;
+ break;
+ }
+ case AccountTypeBox::Maildir:
+ {
+ mAccount = acctManager->create( "maildir", i18n( "Local Account" ) );
+ static_cast<KMAcctMaildir*>( mAccount )->setLocation( mIncomingLocation->text() );
+ break;
+ }
+ }
+
+ if ( mTypeBox->type() == AccountTypeBox::POP3 )
+ checkPopCapabilities( mIncomingServer->text(), port );
+ else if ( mTypeBox->type() == AccountTypeBox::IMAP || mTypeBox->type() == AccountTypeBox::dIMAP )
+ checkImapCapabilities( mIncomingServer->text(), port );
+ else
+ QTimer::singleShot( 0, this, SLOT( accountCreated() ) );
+}
+
+void AccountWizard::accountCreated()
+{
+ if ( mAccount )
+ {
+ mKernel->acctMgr()->add( mAccount );
+ mKernel->cleanupImapFolders();
+ }
+
+ finished();
+}
+
+void AccountWizard::finished()
+{
+ GlobalSettings::self()->writeConfig();
+
+ QWizard::accept();
+}
+
+// ----- Security Checks --------------
+
+void AccountWizard::checkPopCapabilities( const QString &server, int port )
+{
+ delete mServerTest;
+ mServerTest = new KMServerTest( POP_PROTOCOL, server, port );
+
+ connect( mServerTest, SIGNAL( capabilities( const QStringList&, const QStringList& ) ),
+ this, SLOT( popCapabilities( const QStringList&, const QStringList& ) ) );
+
+ mAuthInfoLabel = createInfoLabel( i18n( "Check for supported security capabilities of %1..." ).arg( server ) );
+}
+
+void AccountWizard::checkImapCapabilities( const QString &server, int port )
+{
+ delete mServerTest;
+ mServerTest = new KMServerTest( IMAP_PROTOCOL, server, port );
+
+ connect( mServerTest, SIGNAL( capabilities( const QStringList&, const QStringList& ) ),
+ this, SLOT( imapCapabilities( const QStringList&, const QStringList& ) ) );
+
+ mAuthInfoLabel = createInfoLabel( i18n( "Check for supported security capabilities of %1..." ).arg( server ) );
+}
+
+void AccountWizard::checkSmtpCapabilities( const QString &server, int port )
+{
+ delete mServerTest;
+ mServerTest = new KMServerTest( SMTP_PROTOCOL, server, port );
+
+ connect( mServerTest, SIGNAL( capabilities( const QStringList&, const QStringList&,
+ const QString&, const QString&, const QString& ) ),
+ this, SLOT( smtpCapabilities( const QStringList&, const QStringList&,
+ const QString&, const QString&, const QString& ) ) );
+
+ mAuthInfoLabel = createInfoLabel( i18n( "Check for supported security capabilities of %1..." ).arg( server ) );
+}
+
+void AccountWizard::popCapabilities( const QStringList &capaNormalList,
+ const QStringList &capaSSLList )
+{
+ uint capaNormal = popCapabilitiesFromStringList( capaNormalList );
+ uint capaTLS = 0;
+
+ if ( capaNormal & STLS )
+ capaTLS = capaNormal;
+
+ uint capaSSL = popCapabilitiesFromStringList( capaSSLList );
+
+ KMail::NetworkAccount *account = static_cast<KMail::NetworkAccount*>( mAccount );
+
+ bool useSSL = !capaSSLList.isEmpty();
+ bool useTLS = capaTLS != 0;
+
+ account->setUseSSL( useSSL );
+ account->setUseTLS( useTLS );
+
+ uint capa = (useSSL ? capaSSL : (useTLS ? capaTLS : capaNormal));
+
+ if ( capa & Plain )
+ account->setAuth( "PLAIN" );
+ else if ( capa & Login )
+ account->setAuth( "LOGIN" );
+ else if ( capa & CRAM_MD5 )
+ account->setAuth( "CRAM-MD5" );
+ else if ( capa & Digest_MD5 )
+ account->setAuth( "DIGEST-MD5" );
+ else if ( capa & NTLM )
+ account->setAuth( "NTLM" );
+ else if ( capa & GSSAPI )
+ account->setAuth( "GSSAPI" );
+ else if ( capa & APOP )
+ account->setAuth( "APOP" );
+ else
+ account->setAuth( "USER" );
+
+ account->setPort( useSSL ? 995 : 110 );
+
+ mServerTest->deleteLater();
+ mServerTest = 0;
+
+ delete mAuthInfoLabel;
+ mAuthInfoLabel = 0;
+
+ accountCreated();
+}
+
+
+void AccountWizard::imapCapabilities( const QStringList &capaNormalList,
+ const QStringList &capaSSLList )
+{
+ uint capaNormal = imapCapabilitiesFromStringList( capaNormalList );
+ uint capaTLS = 0;
+ if ( capaNormal & STARTTLS )
+ capaTLS = capaNormal;
+
+ uint capaSSL = imapCapabilitiesFromStringList( capaSSLList );
+
+ KMail::NetworkAccount *account = static_cast<KMail::NetworkAccount*>( mAccount );
+
+ bool useSSL = !capaSSLList.isEmpty();
+ bool useTLS = (capaTLS != 0);
+
+ account->setUseSSL( useSSL );
+ account->setUseTLS( useTLS );
+
+ uint capa = (useSSL ? capaSSL : (useTLS ? capaTLS : capaNormal));
+
+ if ( capa & CRAM_MD5 )
+ account->setAuth( "CRAM-MD5" );
+ else if ( capa & Digest_MD5 )
+ account->setAuth( "DIGEST-MD5" );
+ else if ( capa & NTLM )
+ account->setAuth( "NTLM" );
+ else if ( capa & GSSAPI )
+ account->setAuth( "GSSAPI" );
+ else if ( capa & Anonymous )
+ account->setAuth( "ANONYMOUS" );
+ else if ( capa & Login )
+ account->setAuth( "LOGIN" );
+ else if ( capa & Plain )
+ account->setAuth( "PLAIN" );
+ else
+ account->setAuth( "*" );
+
+ account->setPort( useSSL ? 993 : 143 );
+
+ mServerTest->deleteLater();
+ mServerTest = 0;
+
+ delete mAuthInfoLabel;
+ mAuthInfoLabel = 0;
+
+ accountCreated();
+}
+
+void AccountWizard::smtpCapabilities( const QStringList &capaNormal,
+ const QStringList &capaSSL,
+ const QString &authNone,
+ const QString &authSSL,
+ const QString &authTLS )
+{
+ uint authBitsNone, authBitsSSL, authBitsTLS;
+
+ if ( authNone.isEmpty() && authSSL.isEmpty() && authTLS.isEmpty() ) {
+ // slave doesn't seem to support "* AUTH METHODS" metadata (or server can't do AUTH)
+ authBitsNone = authMethodsFromStringList( capaNormal );
+ if ( capaNormal.findIndex( "STARTTLS" ) != -1 )
+ authBitsTLS = authBitsNone;
+ else
+ authBitsTLS = 0;
+ authBitsSSL = authMethodsFromStringList( capaSSL );
+ } else {
+ authBitsNone = authMethodsFromString( authNone );
+ authBitsSSL = authMethodsFromString( authSSL );
+ authBitsTLS = authMethodsFromString( authTLS );
+ }
+
+ uint authBits = 0;
+ if ( capaNormal.findIndex( "STARTTLS" ) != -1 ) {
+ mTransportInfo->encryption = "TLS";
+ authBits = authBitsTLS;
+ } else if ( !capaSSL.isEmpty() ) {
+ mTransportInfo->encryption = "SSL";
+ authBits = authBitsSSL;
+ } else {
+ mTransportInfo->encryption = "NONE";
+ authBits = authBitsNone;
+ }
+
+ if ( authBits & Login )
+ mTransportInfo->authType = "LOGIN";
+ else if ( authBits & CRAM_MD5 )
+ mTransportInfo->authType = "CRAM-MD5";
+ else if ( authBits & Digest_MD5 )
+ mTransportInfo->authType = "DIGEST-MD5";
+ else if ( authBits & NTLM )
+ mTransportInfo->authType = "NTLM";
+ else if ( authBits & GSSAPI )
+ mTransportInfo->authType = "GSSAPI";
+ else
+ mTransportInfo->authType = "PLAIN";
+
+ mTransportInfo->port = ( !capaSSL.isEmpty() ? "465" : "25" );
+
+ mServerTest->deleteLater();
+ mServerTest = 0;
+
+ delete mAuthInfoLabel;
+ mAuthInfoLabel = 0;
+
+ transportCreated();
+}
+
+uint AccountWizard::popCapabilitiesFromStringList( const QStringList & l )
+{
+ unsigned int capa = 0;
+
+ for ( QStringList::const_iterator it = l.begin() ; it != l.end() ; ++it ) {
+ QString cur = (*it).upper();
+ if ( cur == "PLAIN" )
+ capa |= Plain;
+ else if ( cur == "LOGIN" )
+ capa |= Login;
+ else if ( cur == "CRAM-MD5" )
+ capa |= CRAM_MD5;
+ else if ( cur == "DIGEST-MD5" )
+ capa |= Digest_MD5;
+ else if ( cur == "NTLM" )
+ capa |= NTLM;
+ else if ( cur == "GSSAPI" )
+ capa |= GSSAPI;
+ else if ( cur == "APOP" )
+ capa |= APOP;
+ else if ( cur == "STLS" )
+ capa |= STLS;
+ }
+
+ return capa;
+}
+
+uint AccountWizard::imapCapabilitiesFromStringList( const QStringList & l )
+{
+ unsigned int capa = 0;
+
+ for ( QStringList::const_iterator it = l.begin() ; it != l.end() ; ++it ) {
+ QString cur = (*it).upper();
+ if ( cur == "AUTH=PLAIN" )
+ capa |= Plain;
+ else if ( cur == "AUTH=LOGIN" )
+ capa |= Login;
+ else if ( cur == "AUTH=CRAM-MD5" )
+ capa |= CRAM_MD5;
+ else if ( cur == "AUTH=DIGEST-MD5" )
+ capa |= Digest_MD5;
+ else if ( cur == "AUTH=NTLM" )
+ capa |= NTLM;
+ else if ( cur == "AUTH=GSSAPI" )
+ capa |= GSSAPI;
+ else if ( cur == "AUTH=ANONYMOUS" )
+ capa |= Anonymous;
+ else if ( cur == "STARTTLS" )
+ capa |= STARTTLS;
+ }
+
+ return capa;
+}
+
+uint AccountWizard::authMethodsFromString( const QString & s )
+{
+ unsigned int result = 0;
+
+ QStringList sl = QStringList::split( '\n', s.upper() );
+ for ( QStringList::const_iterator it = sl.begin() ; it != sl.end() ; ++it )
+ if ( *it == "SASL/LOGIN" )
+ result |= Login;
+ else if ( *it == "SASL/PLAIN" )
+ result |= Plain;
+ else if ( *it == "SASL/CRAM-MD5" )
+ result |= CRAM_MD5;
+ else if ( *it == "SASL/DIGEST-MD5" )
+ result |= Digest_MD5;
+ else if ( *it == "SASL/NTLM" )
+ result |= NTLM;
+ else if ( *it == "SASL/GSSAPI" )
+ result |= GSSAPI;
+
+ return result;
+}
+
+uint AccountWizard::authMethodsFromStringList( const QStringList & sl )
+{
+ unsigned int result = 0;
+
+ for ( QStringList::const_iterator it = sl.begin() ; it != sl.end() ; ++it )
+ if ( *it == "LOGIN" )
+ result |= Login;
+ else if ( *it == "PLAIN" )
+ result |= Plain;
+ else if ( *it == "CRAM-MD5" )
+ result |= CRAM_MD5;
+ else if ( *it == "DIGEST-MD5" )
+ result |= Digest_MD5;
+ else if ( *it == "NTLM" )
+ result |= NTLM;
+ else if ( *it == "GSSAPI" )
+ result |= GSSAPI;
+
+ return result;
+}
+
+#include "accountwizard.moc"
diff --git a/kmail/accountwizard.h b/kmail/accountwizard.h
new file mode 100644
index 00000000..0dea0e44
--- /dev/null
+++ b/kmail/accountwizard.h
@@ -0,0 +1,138 @@
+/*******************************************************************************
+**
+** Filename : accountwizard.h
+** Created on : 07 February, 2005
+** Copyright : (c) 2005 Tobias Koenig
+** Email : tokoe@kde.org
+**
+*******************************************************************************/
+
+/*******************************************************************************
+**
+** 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.
+**
+** In addition, as a special exception, the copyright holders give
+** permission to link the code of this program with any edition of
+** the Qt library by Trolltech AS, Norway (or with modified versions
+** of Qt that use the same license as Qt), and distribute linked
+** combinations including the two. You must obey the GNU General
+** Public License in all respects for all of the code used other than
+** Qt. If you modify this file, you may extend this exception to
+** your version of the file, but you are not obligated to do so. If
+** you do not wish to do so, delete this exception statement from
+** your version.
+*******************************************************************************/
+
+#ifndef KMWIZARD_H
+#define KMWIZARD_H
+
+#include <kwizard.h>
+
+class KLineEdit;
+class QCheckBox;
+class QLabel;
+class QPushButton;
+
+class KMAccount;
+class KMKernel;
+class KMServerTest;
+class AccountTypeBox;
+class KMTransportInfo;
+
+class AccountWizard : public KWizard
+{
+ Q_OBJECT
+
+ public:
+ /**
+ Starts the wizard. The wizard is only shown when it has not be
+ run successfully before.
+
+ @param kernel The mail kernel the wizard should work on.
+ @param parent The parent widget of the dialog.
+ */
+ static void start( KMKernel *kernel, QWidget *parent = 0 );
+
+ /**
+ Reimplemented
+ */
+ void showPage( QWidget *page );
+
+ protected:
+ AccountWizard( KMKernel *kernel, QWidget *parent );
+ ~AccountWizard() {};
+
+ void setupWelcomePage();
+ void setupAccountTypePage();
+ void setupAccountInformationPage();
+ void setupLoginInformationPage();
+ void setupServerInformationPage();
+
+ protected slots:
+ void chooseLocation();
+ virtual void accept();
+ void createTransport();
+ void transportCreated();
+ void createAccount();
+ void accountCreated();
+ void finished();
+
+ private slots:
+ void popCapabilities( const QStringList&, const QStringList& );
+ void imapCapabilities( const QStringList&, const QStringList& );
+ void smtpCapabilities( const QStringList&, const QStringList&,
+ const QString&, const QString&, const QString& );
+
+ private:
+ QString accountName() const;
+ QLabel *createInfoLabel( const QString &msg );
+
+ void checkPopCapabilities( const QString&, int );
+ void checkImapCapabilities( const QString&, int );
+ void checkSmtpCapabilities( const QString&, int );
+ uint popCapabilitiesFromStringList( const QStringList& );
+ uint imapCapabilitiesFromStringList( const QStringList& );
+ uint authMethodsFromString( const QString& );
+ uint authMethodsFromStringList( const QStringList& );
+
+ QWidget *mWelcomePage;
+
+ QWidget *mAccountTypePage;
+ AccountTypeBox *mTypeBox;
+
+ QWidget *mAccountInformationPage;
+ KLineEdit *mRealName;
+ KLineEdit *mEMailAddress;
+ KLineEdit *mOrganization;
+
+ QWidget *mLoginInformationPage;
+ KLineEdit *mLoginName;
+ KLineEdit *mPassword;
+
+ QWidget *mServerInformationPage;
+ QLabel *mIncomingLabel;
+ KLineEdit *mIncomingServer;
+ QCheckBox *mIncomingUseSSL;
+ KLineEdit *mIncomingLocation;
+
+ QPushButton *mChooseLocation;
+ KLineEdit *mOutgoingServer;
+ QCheckBox *mOutgoingUseSSL;
+ QCheckBox *mLocalDelivery;
+
+ QWidget *mIncomingServerWdg;
+ QWidget *mIncomingLocationWdg;
+
+ QLabel *mAuthInfoLabel;
+
+ KMKernel *mKernel;
+ KMAccount *mAccount;
+ KMTransportInfo *mTransportInfo;
+ QPtrList<KMTransportInfo> mTransportInfoList;
+ KMServerTest *mServerTest;
+};
+
+#endif
diff --git a/kmail/acljobs.cpp b/kmail/acljobs.cpp
new file mode 100644
index 00000000..568a6f4b
--- /dev/null
+++ b/kmail/acljobs.cpp
@@ -0,0 +1,261 @@
+/**
+ * acljobs.cpp
+ *
+ * Copyright (c) 2004 David Faure <faure@kde.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+#include "acljobs.h"
+#include <kio/scheduler.h>
+#include <kdebug.h>
+
+using namespace KMail;
+
+// Convert str to an ACLPermissions value.
+// url and user are there only for the error message
+static unsigned int IMAPRightsToPermission( const QString& str, const KURL& url, const QString& user ) {
+ unsigned int perm = 0;
+ uint len = str.length();
+ for (uint i = 0; i < len; ++i) {
+ QChar ch = str[i];
+ switch ( ch.latin1() ) {
+ case 'l': perm |= ACLJobs::List; break;
+ case 'r': perm |= ACLJobs::Read; break;
+ case 's': perm |= ACLJobs::WriteSeenFlag; break;
+ case 'w': perm |= ACLJobs::WriteFlags; break;
+ case 'i': perm |= ACLJobs::Insert; break;
+ case 'p': perm |= ACLJobs::Post; break;
+ case 'c': perm |= ACLJobs::Create; break;
+ case 'd': perm |= ACLJobs::Delete; break;
+ case 'a': perm |= ACLJobs::Administer; break;
+ default: break;
+ }
+ }
+ if ( ( perm & ACLJobs::Read ) && !( perm & ACLJobs::WriteSeenFlag ) ) {
+ // Reading without 'seen' is, well, annoying. Unusable, even.
+ // So we treat 'rs' as a single one.
+ // But if the permissions were set out of kmail, better check that both are set
+ kdWarning(5006) << "IMAPRightsToPermission: found read (r) but not seen (s). Things will not work well for folder " << url << " and user " << ( user.isEmpty() ? "myself" : user ) << endl;
+ if ( perm & ACLJobs::Administer )
+ kdWarning(5006) << "You can change this yourself in the ACL dialog" << endl;
+ else
+ kdWarning(5006) << "Ask your admin for 's' permissions." << endl;
+ // Is the above correct enough to be turned into a KMessageBox?
+ }
+
+ return perm;
+}
+
+static QCString permissionsToIMAPRights( unsigned int permissions ) {
+ QCString str = "";
+ if ( permissions & ACLJobs::List )
+ str += 'l';
+ if ( permissions & ACLJobs::Read )
+ str += 'r';
+ if ( permissions & ACLJobs::WriteSeenFlag )
+ str += 's';
+ if ( permissions & ACLJobs::WriteFlags )
+ str += 'w';
+ if ( permissions & ACLJobs::Insert )
+ str += 'i';
+ if ( permissions & ACLJobs::Post )
+ str += 'p';
+ if ( permissions & ACLJobs::Create )
+ str += 'c';
+ if ( permissions & ACLJobs::Delete )
+ str += 'd';
+ if ( permissions & ACLJobs::Administer )
+ str += 'a';
+ return str;
+}
+
+#ifndef NDEBUG
+QString ACLJobs::permissionsToString( unsigned int permissions )
+{
+ QString str;
+ if ( permissions & ACLJobs::List )
+ str += "List ";
+ if ( permissions & ACLJobs::Read )
+ str += "Read ";
+ if ( permissions & ACLJobs::WriteFlags )
+ str += "Write ";
+ if ( permissions & ACLJobs::Insert )
+ str += "Insert ";
+ if ( permissions & ACLJobs::Post )
+ str += "Post ";
+ if ( permissions & ACLJobs::Create )
+ str += "Create ";
+ if ( permissions & ACLJobs::Delete )
+ str += "Delete ";
+ if ( permissions & ACLJobs::Administer )
+ str += "Administer ";
+ if ( !str.isEmpty() )
+ str.truncate( str.length() - 1 );
+ return str;
+}
+#endif
+
+KIO::SimpleJob* ACLJobs::setACL( KIO::Slave* slave, const KURL& url, const QString& user, unsigned int permissions )
+{
+ QString perm = QString::fromLatin1( permissionsToIMAPRights( permissions ) );
+
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+ stream << (int)'A' << (int)'S' << url << user << perm;
+
+ KIO::SimpleJob* job = KIO::special( url, packedArgs, false );
+ KIO::Scheduler::assignJobToSlave( slave, job );
+ return job;
+}
+
+ACLJobs::DeleteACLJob* ACLJobs::deleteACL( KIO::Slave* slave, const KURL& url, const QString& user )
+{
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+ stream << (int)'A' << (int)'D' << url << user;
+
+ ACLJobs::DeleteACLJob* job = new ACLJobs::DeleteACLJob( url, user, packedArgs, false );
+ KIO::Scheduler::assignJobToSlave( slave, job );
+ return job;
+}
+
+ACLJobs::GetACLJob* ACLJobs::getACL( KIO::Slave* slave, const KURL& url )
+{
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+ stream << (int)'A' << (int)'G' << url;
+
+ ACLJobs::GetACLJob* job = new ACLJobs::GetACLJob( url, packedArgs, false );
+ KIO::Scheduler::assignJobToSlave( slave, job );
+ return job;
+}
+
+ACLJobs::GetUserRightsJob* ACLJobs::getUserRights( KIO::Slave* slave, const KURL& url )
+{
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+ stream << (int)'A' << (int)'M' << url;
+
+ ACLJobs::GetUserRightsJob* job = new ACLJobs::GetUserRightsJob( url, packedArgs, false );
+ KIO::Scheduler::assignJobToSlave( slave, job );
+ return job;
+}
+
+ACLJobs::GetACLJob::GetACLJob( const KURL& url, const QByteArray &packedArgs,
+ bool showProgressInfo )
+ : KIO::SimpleJob( url, KIO::CMD_SPECIAL, packedArgs, showProgressInfo )
+{
+ connect( this, SIGNAL(infoMessage(KIO::Job*,const QString&)),
+ SLOT(slotInfoMessage(KIO::Job*,const QString&)) );
+}
+
+void ACLJobs::GetACLJob::slotInfoMessage( KIO::Job*, const QString& str )
+{
+ // Parse the result
+ QStringList lst = QStringList::split( "\"", str, true );
+ while ( lst.count() >= 2 ) // we take items 2 by 2
+ {
+ QString user = lst.front(); lst.pop_front();
+ QString imapRights = lst.front(); lst.pop_front();
+ unsigned int perm = IMAPRightsToPermission( imapRights, url(), user );
+ m_entries.append( ACLListEntry( user, imapRights, perm ) );
+ }
+}
+
+ACLJobs::GetUserRightsJob::GetUserRightsJob( const KURL& url, const QByteArray &packedArgs,
+ bool showProgressInfo )
+ : KIO::SimpleJob( url, KIO::CMD_SPECIAL, packedArgs, showProgressInfo )
+{
+ connect( this, SIGNAL(infoMessage(KIO::Job*,const QString&)),
+ SLOT(slotInfoMessage(KIO::Job*,const QString&)) );
+}
+
+void ACLJobs::GetUserRightsJob::slotInfoMessage( KIO::Job*, const QString& str )
+{
+ // Parse the result
+ m_permissions = IMAPRightsToPermission( str, url(), QString::null );
+}
+
+ACLJobs::DeleteACLJob::DeleteACLJob( const KURL& url, const QString& userId,
+ const QByteArray &packedArgs,
+ bool showProgressInfo )
+ : KIO::SimpleJob( url, KIO::CMD_SPECIAL, packedArgs, showProgressInfo ),
+ mUserId( userId )
+{
+}
+
+////
+
+ACLJobs::MultiSetACLJob::MultiSetACLJob( KIO::Slave* slave, const KURL& url, const ACLList& acl, bool showProgressInfo )
+ : KIO::Job( showProgressInfo ),
+ mSlave( slave ),
+ mUrl( url ), mACLList( acl ), mACLListIterator( mACLList.begin() )
+{
+ QTimer::singleShot(0, this, SLOT(slotStart()));
+}
+
+void ACLJobs::MultiSetACLJob::slotStart()
+{
+ // Skip over unchanged entries
+ while ( mACLListIterator != mACLList.end() && !(*mACLListIterator).changed )
+ ++mACLListIterator;
+
+ if ( mACLListIterator != mACLList.end() )
+ {
+ const ACLListEntry& entry = *mACLListIterator;
+ KIO::Job* job = 0;
+ if ( entry.permissions > -1 )
+ job = setACL( mSlave, mUrl, entry.userId, entry.permissions );
+ else
+ job = deleteACL( mSlave, mUrl, entry.userId );
+
+ addSubjob( job );
+ } else { // done!
+ emitResult();
+ }
+}
+
+void ACLJobs::MultiSetACLJob::slotResult( KIO::Job *job )
+{
+ if ( job->error() ) {
+ KIO::Job::slotResult( job ); // will set the error and emit result(this)
+ return;
+ }
+ subjobs.remove(job);
+ const ACLListEntry& entry = *mACLListIterator;
+ emit aclChanged( entry.userId, entry.permissions );
+
+ // Move on to next one
+ ++mACLListIterator;
+ slotStart();
+}
+
+ACLJobs::MultiSetACLJob* ACLJobs::multiSetACL( KIO::Slave* slave, const KURL& url, const ACLList& acl )
+{
+ return new MultiSetACLJob( slave, url, acl, false /*showProgressInfo*/ );
+}
+
+#include "acljobs.moc"
diff --git a/kmail/acljobs.h b/kmail/acljobs.h
new file mode 100644
index 00000000..0d4a731d
--- /dev/null
+++ b/kmail/acljobs.h
@@ -0,0 +1,177 @@
+/*
+ * acljobs.h
+ *
+ * Copyright (c) 2004 David Faure <faure@kde.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#ifndef KMACLJOBS_H
+#define KMACLJOBS_H
+
+#include <kio/job.h>
+#include <qvaluevector.h>
+
+namespace KMail {
+
+ /// One entry in the ACL list: user and permissions
+ struct ACLListEntry {
+ ACLListEntry() {} // for QValueVector
+ ACLListEntry( const QString& u, const QString& irl, int p )
+ : userId( u ), internalRightsList( irl ), permissions( p ), changed( false ) {}
+ QString userId;
+ QString internalRightsList; ///< protocol-dependent string (e.g. IMAP rights list)
+ int permissions; ///< based on the ACLPermissions enum
+ bool changed; ///< special flag for KMFolderCachedImap
+ };
+
+ typedef QValueVector<ACLListEntry> ACLList;
+
+/**
+ * This namespace contains functions that return jobs for ACL operations.
+ *
+ * The current implementation is tied to IMAP.
+ * If someone wants to extend this to other protocols, turn the class into a namespace
+ * and use virtual methods.
+ */
+namespace ACLJobs {
+
+ /// 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.
+ enum ACLPermissions {
+ List = 1,
+ Read = 2,
+ WriteFlags = 4,
+ Insert = 8,
+ Create = 16,
+ Delete = 32,
+ Administer = 64,
+ Post = 128,
+ WriteSeenFlag = 256,
+ // alias for "all read/write permissions except admin"
+ AllWrite = List | Read | WriteFlags | Insert | Post | Create | Delete | WriteSeenFlag,
+ // alias for "all permissions"
+ All = List | Read | WriteFlags | Insert | Post | Create | Delete | Administer | WriteSeenFlag
+ };
+ /// Set the permissions for a given user on a given url
+ KIO::SimpleJob* setACL( KIO::Slave* slave, const KURL& url, const QString& user, unsigned int permissions );
+
+ class DeleteACLJob;
+ /// Delete the permissions for a given user on a given url
+ DeleteACLJob* deleteACL( KIO::Slave* slave, const KURL& url, const QString& user );
+
+ class GetACLJob;
+ /// List all ACLs for a given url
+ GetACLJob* getACL( KIO::Slave* slave, const KURL& url );
+
+ class GetUserRightsJob;
+ /// Get the users' rights for a given url
+ GetUserRightsJob* getUserRights( KIO::Slave* slave, const KURL& url );
+
+ class MultiSetACLJob;
+ /// Set and delete a list of permissions for different users on a given url
+ MultiSetACLJob* multiSetACL( KIO::Slave* slave, const KURL& url, const ACLList& acl );
+
+ /// List all ACLs for a given url
+ class GetACLJob : public KIO::SimpleJob
+ {
+ Q_OBJECT
+ public:
+ GetACLJob( const KURL& url, const QByteArray &packedArgs,
+ bool showProgressInfo );
+
+ const ACLList& entries() const { return m_entries; }
+
+ protected slots:
+ void slotInfoMessage( KIO::Job*, const QString& );
+ private:
+ ACLList m_entries;
+ };
+
+ /// Get the users' rights for a given url
+ class GetUserRightsJob : public KIO::SimpleJob
+ {
+ Q_OBJECT
+ public:
+ GetUserRightsJob( const KURL& url, const QByteArray &packedArgs,
+ bool showProgressInfo );
+ unsigned int permissions() const { return m_permissions; }
+
+ protected slots:
+ void slotInfoMessage( KIO::Job*, const QString& );
+ private:
+ unsigned int m_permissions;
+ };
+
+ /// Delete the permissions for a given user on a given url
+ /// This class only exists to store the userid in the job
+ class DeleteACLJob : public KIO::SimpleJob
+ {
+ Q_OBJECT
+ public:
+ DeleteACLJob( const KURL& url, const QString& userId,
+ const QByteArray &packedArgs,
+ bool showProgressInfo );
+
+ QString userId() const { return mUserId; }
+
+ private:
+ QString mUserId;
+ };
+
+ /// Set and delete a list of permissions for different users on a given url
+ class MultiSetACLJob : public KIO::Job {
+ Q_OBJECT
+
+ public:
+ MultiSetACLJob( KIO::Slave* slave, const KURL& url, const ACLList& acl, bool showProgressInfo );
+
+ signals:
+ // Emitted when a given user's permissions were successfully changed.
+ // This allows the caller to keep track of what exactly was done (and handle errors better)
+ void aclChanged( const QString& userId, int permissions );
+
+ protected slots:
+ virtual void slotStart();
+ virtual void slotResult( KIO::Job *job );
+
+ private:
+ KIO::Slave* mSlave;
+ const KURL mUrl;
+ const ACLList mACLList;
+ ACLList::const_iterator mACLListIterator;
+ };
+
+
+#ifndef NDEBUG
+ QString permissionsToString( unsigned int permissions );
+#endif
+}
+
+} // namespace
+
+#endif /* KMACLJOBS_H */
diff --git a/kmail/actionscheduler.cpp b/kmail/actionscheduler.cpp
new file mode 100644
index 00000000..3d92cd7a
--- /dev/null
+++ b/kmail/actionscheduler.cpp
@@ -0,0 +1,836 @@
+/* Action Scheduler
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) Don Sanders <sanders@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+#include <kdebug.h> // FIXME
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "actionscheduler.h"
+
+#include "filterlog.h"
+#include "messageproperty.h"
+#include "kmfilter.h"
+#include "kmfolderindex.h"
+#include "kmfoldermgr.h"
+#include "kmmsgdict.h"
+#include "kmcommands.h"
+#include "kmheaders.h"
+#include "accountmanager.h"
+using KMail::AccountManager;
+
+#include <qtimer.h>
+#include <kconfig.h>
+#include <kstandarddirs.h>
+
+using namespace KMail;
+typedef QPtrList<KMMsgBase> KMMessageList;
+
+
+KMFolderMgr* ActionScheduler::tempFolderMgr = 0;
+int ActionScheduler::refCount = 0;
+int ActionScheduler::count = 0;
+QValueList<ActionScheduler*> *ActionScheduler::schedulerList = 0;
+bool ActionScheduler::sEnabled = false;
+bool ActionScheduler::sEnabledChecked = false;
+
+ActionScheduler::ActionScheduler(KMFilterMgr::FilterSet set,
+ QValueList<KMFilter*> filters,
+ KMHeaders *headers,
+ KMFolder *srcFolder)
+ :mSet( set ), mHeaders( headers )
+{
+ ++count;
+ ++refCount;
+ mExecuting = false;
+ mExecutingLock = false;
+ mFetchExecuting = false;
+ mFiltersAreQueued = false;
+ mResult = ResultOk;
+ mIgnore = false;
+ mAutoDestruct = false;
+ mAlwaysMatch = false;
+ mAccountId = 0;
+ mAccount = false;
+ lastCommand = 0;
+ lastJob = 0;
+ finishTimer = new QTimer( this, "finishTimer" );
+ connect( finishTimer, SIGNAL(timeout()), this, SLOT(finish()));
+ fetchMessageTimer = new QTimer( this, "fetchMessageTimer" );
+ connect( fetchMessageTimer, SIGNAL(timeout()), this, SLOT(fetchMessage()));
+ tempCloseFoldersTimer = new QTimer( this, "tempCloseFoldersTimer" );
+ connect( tempCloseFoldersTimer, SIGNAL(timeout()), this, SLOT(tempCloseFolders()));
+ processMessageTimer = new QTimer( this, "processMessageTimer" );
+ connect( processMessageTimer, SIGNAL(timeout()), this, SLOT(processMessage()));
+ filterMessageTimer = new QTimer( this, "filterMessageTimer" );
+ connect( filterMessageTimer, SIGNAL(timeout()), this, SLOT(filterMessage()));
+ timeOutTimer = new QTimer( this, "timeOutTimer" );
+ connect( timeOutTimer, SIGNAL(timeout()), this, SLOT(timeOut()));
+ fetchTimeOutTimer = new QTimer( this, "fetchTimeOutTimer" );
+ connect( fetchTimeOutTimer, SIGNAL(timeout()), this, SLOT(fetchTimeOut()));
+
+ QValueList<KMFilter*>::Iterator it = filters.begin();
+ for (; it != filters.end(); ++it)
+ mFilters.append( **it );
+ mDestFolder = 0;
+ if (srcFolder) {
+ mDeleteSrcFolder = false;
+ setSourceFolder( srcFolder );
+ } else {
+ QString tmpName;
+ tmpName.setNum( count );
+ if (!tempFolderMgr)
+ tempFolderMgr = new KMFolderMgr(locateLocal("data","kmail/filter"));
+ KMFolder *tempFolder = tempFolderMgr->findOrCreate( tmpName );
+ tempFolder->expunge();
+ mDeleteSrcFolder = true;
+ setSourceFolder( tempFolder );
+ }
+ if (!schedulerList)
+ schedulerList = new QValueList<ActionScheduler*>;
+ schedulerList->append( this );
+}
+
+ActionScheduler::~ActionScheduler()
+{
+ schedulerList->remove( this );
+ tempCloseFolders();
+ disconnect( mSrcFolder, SIGNAL(closed()),
+ this, SLOT(folderClosedOrExpunged()) );
+ disconnect( mSrcFolder, SIGNAL(expunged(KMFolder*)),
+ this, SLOT(folderClosedOrExpunged()) );
+ mSrcFolder->close("actionschedsrc");
+
+ if (mDeleteSrcFolder)
+ tempFolderMgr->remove(mSrcFolder);
+
+ --refCount;
+ if (refCount == 0) {
+ delete tempFolderMgr;
+ tempFolderMgr = 0;
+ }
+}
+
+void ActionScheduler::setAutoDestruct( bool autoDestruct )
+{
+ mAutoDestruct = autoDestruct;
+}
+
+void ActionScheduler::setAlwaysMatch( bool alwaysMatch )
+{
+ mAlwaysMatch = alwaysMatch;
+}
+
+void ActionScheduler::setDefaultDestinationFolder( KMFolder *destFolder )
+{
+ mDestFolder = destFolder;
+}
+
+void ActionScheduler::setSourceFolder( KMFolder *srcFolder )
+{
+ srcFolder->open("actionschedsrc");
+ if (mSrcFolder) {
+ disconnect( mSrcFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
+ this, SLOT(msgAdded(KMFolder*, Q_UINT32)) );
+ disconnect( mSrcFolder, SIGNAL(closed()),
+ this, SLOT(folderClosedOrExpunged()) );
+ disconnect( mSrcFolder, SIGNAL(expunged(KMFolder*)),
+ this, SLOT(folderClosedOrExpunged()) );
+ mSrcFolder->close("actionschedsrc");
+ }
+ mSrcFolder = srcFolder;
+ int i = 0;
+ for (i = 0; i < mSrcFolder->count(); ++i)
+ enqueue( mSrcFolder->getMsgBase( i )->getMsgSerNum() );
+ if (mSrcFolder) {
+ connect( mSrcFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
+ this, SLOT(msgAdded(KMFolder*, Q_UINT32)) );
+ connect( mSrcFolder, SIGNAL(closed()),
+ this, SLOT(folderClosedOrExpunged()) );
+ connect( mSrcFolder, SIGNAL(expunged(KMFolder*)),
+ this, SLOT(folderClosedOrExpunged()) );
+ }
+}
+
+void ActionScheduler::setFilterList( QValueList<KMFilter*> filters )
+{
+ mFiltersAreQueued = true;
+ mQueuedFilters.clear();
+
+ QValueList<KMFilter*>::Iterator it = filters.begin();
+ for (; it != filters.end(); ++it)
+ mQueuedFilters.append( **it );
+ if (!mExecuting) {
+ mFilters = mQueuedFilters;
+ mFiltersAreQueued = false;
+ mQueuedFilters.clear();
+ }
+}
+
+void ActionScheduler::folderClosedOrExpunged()
+{
+ // mSrcFolder has been closed. reopen it.
+ if ( mSrcFolder )
+ {
+ mSrcFolder->open( "actionsched" );
+ }
+}
+
+int ActionScheduler::tempOpenFolder( KMFolder* aFolder )
+{
+ assert( aFolder );
+ tempCloseFoldersTimer->stop();
+ if ( aFolder == mSrcFolder.operator->() )
+ return 0;
+
+ int rc = aFolder->open("actionsched");
+ if (rc)
+ return rc;
+
+ mOpenFolders.append( aFolder );
+ return 0;
+}
+
+void ActionScheduler::tempCloseFolders()
+{
+ // close temp opened folders
+ QValueListConstIterator<QGuardedPtr<KMFolder> > it;
+ for (it = mOpenFolders.begin(); it != mOpenFolders.end(); ++it) {
+ KMFolder *folder = *it;
+ if (folder)
+ folder->close("actionsched");
+ }
+ mOpenFolders.clear();
+}
+
+void ActionScheduler::execFilters(const QValueList<Q_UINT32> serNums)
+{
+ QValueListConstIterator<Q_UINT32> it;
+ for (it = serNums.begin(); it != serNums.end(); ++it)
+ execFilters( *it );
+}
+
+void ActionScheduler::execFilters(const QPtrList<KMMsgBase> msgList)
+{
+ KMMsgBase *msgBase;
+ QPtrList<KMMsgBase> list = msgList;
+ for (msgBase = list.first(); msgBase; msgBase = list.next())
+ execFilters( msgBase->getMsgSerNum() );
+}
+
+void ActionScheduler::execFilters(KMMsgBase* msgBase)
+{
+ execFilters( msgBase->getMsgSerNum() );
+}
+
+void ActionScheduler::execFilters(Q_UINT32 serNum)
+{
+ if (mResult != ResultOk) {
+ if ((mResult != ResultCriticalError) &&
+ !mExecuting && !mExecutingLock && !mFetchExecuting) {
+ mResult = ResultOk; // Recoverable error
+ if (!mFetchSerNums.isEmpty()) {
+ mFetchSerNums.push_back( mFetchSerNums.first() );
+ mFetchSerNums.pop_front();
+ }
+ } else
+ return; // An error has already occurred don't even try to process this msg
+ }
+ if (MessageProperty::filtering( serNum )) {
+ // Not good someone else is already filtering this msg
+ mResult = ResultError;
+ if (!mExecuting && !mFetchExecuting)
+ finishTimer->start( 0, true );
+ } else {
+ // Everything is ok async fetch this message
+ mFetchSerNums.append( serNum );
+ if (!mFetchExecuting) {
+ //Need to (re)start incomplete msg fetching chain
+ mFetchExecuting = true;
+ fetchMessageTimer->start( 0, true );
+ }
+ }
+}
+
+KMMsgBase *ActionScheduler::messageBase(Q_UINT32 serNum)
+{
+ int idx = -1;
+ KMFolder *folder = 0;
+ KMMsgBase *msg = 0;
+ KMMsgDict::instance()->getLocation( serNum, &folder, &idx );
+ // It's possible that the message has been deleted or moved into a
+ // different folder
+ if (folder && (idx != -1)) {
+ // everything is ok
+ tempOpenFolder( folder ); // just in case msg has moved
+ msg = folder->getMsgBase( idx );
+ } else {
+ // the message is gone!
+ mResult = ResultError;
+ finishTimer->start( 0, true );
+ }
+ return msg;
+}
+
+KMMessage *ActionScheduler::message(Q_UINT32 serNum)
+{
+ int idx = -1;
+ KMFolder *folder = 0;
+ KMMessage *msg = 0;
+ KMMsgDict::instance()->getLocation( serNum, &folder, &idx );
+ // It's possible that the message has been deleted or moved into a
+ // different folder
+ if (folder && (idx != -1)) {
+ // everything is ok
+ msg = folder->getMsg( idx );
+ tempOpenFolder( folder ); // just in case msg has moved
+ } else {
+ // the message is gone!
+ mResult = ResultError;
+ finishTimer->start( 0, true );
+ }
+ return msg;
+}
+
+void ActionScheduler::finish()
+{
+ if (mResult != ResultOk) {
+ // Must handle errors immediately
+ emit result( mResult );
+ return;
+ }
+
+ if (!mExecuting) {
+
+ if (!mFetchSerNums.isEmpty()) {
+ // Possibly if (mResult == ResultOk) should cancel job and start again.
+ // Believe smarter logic to bail out if an error has occurred is required.
+ // Perhaps should be testing for mFetchExecuting or at least set it to true
+ fetchMessageTimer->start( 0, true ); // give it a bit of time at a test
+ return;
+ } else {
+ mFetchExecuting = false;
+ }
+
+ if (mSerNums.begin() != mSerNums.end()) {
+ mExecuting = true;
+ processMessageTimer->start( 0, true );
+ return;
+ }
+
+ // If an error has occurred and a permanent source folder has
+ // been set then move all the messages left in the source folder
+ // to the inbox. If no permanent source folder has been set
+ // then abandon filtering of queued messages.
+ if (!mDeleteSrcFolder && !mDestFolder.isNull() ) {
+ while ( mSrcFolder->count() > 0 ) {
+ KMMessage *msg = mSrcFolder->getMsg( 0 );
+ mDestFolder->moveMsg( msg );
+ }
+
+ // Wait a little while before closing temp folders, just in case
+ // new messages arrive for filtering.
+ tempCloseFoldersTimer->start( 60*1000, true );
+ }
+ mSerNums.clear(); //abandon
+ mFetchSerNums.clear(); //abandon
+
+ if (mFiltersAreQueued)
+ mFilters = mQueuedFilters;
+ mQueuedFilters.clear();
+ mFiltersAreQueued = false;
+ ReturnCode aResult = mResult;
+ mResult = ResultOk;
+ mExecutingLock = false;
+ emit result( aResult );
+ if (mAutoDestruct)
+ delete this;
+ }
+ // else a message may be in the process of being fetched or filtered
+ // wait until both of these commitments are finished then this
+ // method should be called again.
+}
+
+void ActionScheduler::fetchMessage()
+{
+ QValueListIterator<Q_UINT32> mFetchMessageIt = mFetchSerNums.begin();
+ while (mFetchMessageIt != mFetchSerNums.end()) {
+ if (!MessageProperty::transferInProgress(*mFetchMessageIt))
+ break;
+ ++mFetchMessageIt;
+ }
+
+ // Note: Perhaps this could be improved. We shouldn't give up straight away
+ // if !mFetchSerNums.isEmpty (becausing transferInProgress is true
+ // for some messages). Instead we should delay for a minute or so and
+ // again.
+ if (mFetchMessageIt == mFetchSerNums.end() && !mFetchSerNums.isEmpty()) {
+ mResult = ResultError;
+ }
+ if ((mFetchMessageIt == mFetchSerNums.end()) || (mResult != ResultOk)) {
+ mFetchExecuting = false;
+ if (!mSrcFolder->count())
+ mSrcFolder->expunge();
+ finishTimer->start( 0, true );
+ return;
+ }
+
+ //If we got this far then there's a valid message to work with
+ KMMsgBase *msgBase = messageBase( *mFetchMessageIt );
+
+ if ((mResult != ResultOk) || (!msgBase)) {
+ mFetchExecuting = false;
+ return;
+ }
+ mFetchUnget = msgBase->isMessage();
+ KMMessage *msg = message( *mFetchMessageIt );
+ if (mResult != ResultOk) {
+ mFetchExecuting = false;
+ return;
+ }
+
+ if (msg && msg->isComplete()) {
+ messageFetched( msg );
+ } else if (msg) {
+ fetchTimeOutTime = QTime::currentTime();
+ fetchTimeOutTimer->start( 60 * 1000, true );
+ FolderJob *job = msg->parent()->createJob( msg );
+ connect( job, SIGNAL(messageRetrieved( KMMessage* )),
+ SLOT(messageFetched( KMMessage* )) );
+ lastJob = job;
+ job->start();
+ } else {
+ mFetchExecuting = false;
+ mResult = ResultError;
+ finishTimer->start( 0, true );
+ return;
+ }
+}
+
+void ActionScheduler::messageFetched( KMMessage *msg )
+{
+ fetchTimeOutTimer->stop();
+ if (!msg) {
+ // Should never happen, but sometimes does;
+ fetchMessageTimer->start( 0, true );
+ return;
+ }
+
+ mFetchSerNums.remove( msg->getMsgSerNum() );
+
+ // Note: This may not be necessary. What about when it's time to
+ // delete the original message?
+ // Is the new serial number being set correctly then?
+ if ((mSet & KMFilterMgr::Explicit) ||
+ (msg->headerField( "X-KMail-Filtered" ).isEmpty())) {
+ QString serNumS;
+ serNumS.setNum( msg->getMsgSerNum() );
+ KMMessage *newMsg = new KMMessage;
+ newMsg->fromString(msg->asString());
+ newMsg->setStatus(msg->status());
+ newMsg->setComplete(msg->isComplete());
+ newMsg->setHeaderField( "X-KMail-Filtered", serNumS );
+ mSrcFolder->addMsg( newMsg );
+ } else {
+ fetchMessageTimer->start( 0, true );
+ }
+ if (mFetchUnget && msg->parent())
+ msg->parent()->unGetMsg( msg->parent()->find( msg ));
+ return;
+}
+
+void ActionScheduler::msgAdded( KMFolder*, Q_UINT32 serNum )
+{
+ if (!mIgnore)
+ enqueue( serNum );
+}
+
+void ActionScheduler::enqueue(Q_UINT32 serNum)
+{
+ if (mResult != ResultOk)
+ return; // An error has already occurred don't even try to process this msg
+
+ if (MessageProperty::filtering( serNum )) {
+ // Not good someone else is already filtering this msg
+ mResult = ResultError;
+ if (!mExecuting && !mFetchExecuting)
+ finishTimer->start( 0, true );
+ } else {
+ // Everything is ok async filter this message
+ mSerNums.append( serNum );
+
+ if (!mExecuting) {
+ // Note: Need to (re)start incomplete msg filtering chain
+ // The state of mFetchExecuting is of some concern.
+ mExecuting = true;
+ mMessageIt = mSerNums.begin();
+ processMessageTimer->start( 0, true );
+ }
+ }
+}
+
+void ActionScheduler::processMessage()
+{
+ if (mExecutingLock)
+ return;
+ mExecutingLock = true;
+ mMessageIt = mSerNums.begin();
+ while (mMessageIt != mSerNums.end()) {
+ if (!MessageProperty::transferInProgress(*mMessageIt))
+ break;
+ ++mMessageIt;
+ }
+
+ if (mMessageIt == mSerNums.end() && !mSerNums.isEmpty()) {
+ mExecuting = false;
+ processMessageTimer->start( 600, true );
+ }
+
+ if ((mMessageIt == mSerNums.end()) || (mResult != ResultOk)) {
+ mExecutingLock = false;
+ mExecuting = false;
+ finishTimer->start( 0, true );
+ return;
+ }
+
+ //If we got this far then there's a valid message to work with
+ KMMsgBase *msgBase = messageBase( *mMessageIt );
+ if (!msgBase || mResult != ResultOk) {
+ mExecuting = false;
+ return;
+ }
+
+ MessageProperty::setFiltering( *mMessageIt, true );
+ MessageProperty::setFilterHandler( *mMessageIt, this );
+ MessageProperty::setFilterFolder( *mMessageIt, mDestFolder );
+ if ( FilterLog::instance()->isLogging() ) {
+ FilterLog::instance()->addSeparator();
+ }
+ mFilterIt = mFilters.begin();
+
+ mUnget = msgBase->isMessage();
+ KMMessage *msg = message( *mMessageIt );
+ if (mResult != ResultOk) {
+ mExecuting = false;
+ return;
+ }
+
+ bool mdnEnabled = true;
+ {
+ KConfigGroup mdnConfig( kmkernel->config(), "MDN" );
+ int mode = mdnConfig.readNumEntry( "default-policy", 0 );
+ if (!mode || mode < 0 || mode > 3)
+ mdnEnabled = false;
+ }
+ mdnEnabled = true; // For 3.2 force all mails to be complete
+
+ if ((msg && msg->isComplete()) ||
+ (msg && !(*mFilterIt).requiresBody(msg) && !mdnEnabled))
+ {
+ // We have a complete message or
+ // we can work with an incomplete message
+ // Get a write lock on the message while it's being filtered
+ msg->setTransferInProgress( true );
+ filterMessageTimer->start( 0, true );
+ return;
+ }
+ if (msg) {
+ FolderJob *job = msg->parent()->createJob( msg );
+ connect( job, SIGNAL(messageRetrieved( KMMessage* )),
+ SLOT(messageRetrieved( KMMessage* )) );
+ job->start();
+ } else {
+ mExecuting = false;
+ mResult = ResultError;
+ finishTimer->start( 0, true );
+ return;
+ }
+}
+
+void ActionScheduler::messageRetrieved(KMMessage* msg)
+{
+ // Get a write lock on the message while it's being filtered
+ msg->setTransferInProgress( true );
+ filterMessageTimer->start( 0, true );
+}
+
+void ActionScheduler::filterMessage()
+{
+ if (mFilterIt == mFilters.end()) {
+ moveMessage();
+ return;
+ }
+ if (((mSet & KMFilterMgr::Outbound) && (*mFilterIt).applyOnOutbound()) ||
+ ((mSet & KMFilterMgr::Inbound) && (*mFilterIt).applyOnInbound() &&
+ (!mAccount ||
+ (mAccount && (*mFilterIt).applyOnAccount(mAccountId)))) ||
+ ((mSet & KMFilterMgr::Explicit) && (*mFilterIt).applyOnExplicit())) {
+
+ // filter is applicable
+ if ( FilterLog::instance()->isLogging() ) {
+ QString logText( i18n( "<b>Evaluating filter rules:</b> " ) );
+ logText.append( (*mFilterIt).pattern()->asString() );
+ FilterLog::instance()->add( logText, FilterLog::patternDesc );
+ }
+ if (mAlwaysMatch ||
+ (*mFilterIt).pattern()->matches( *mMessageIt )) {
+ if ( FilterLog::instance()->isLogging() ) {
+ FilterLog::instance()->add( i18n( "<b>Filter rules have matched.</b>" ),
+ FilterLog::patternResult );
+ }
+ mFilterAction = (*mFilterIt).actions()->first();
+ actionMessage();
+ return;
+ }
+ }
+ ++mFilterIt;
+ filterMessageTimer->start( 0, true );
+}
+
+void ActionScheduler::actionMessage(KMFilterAction::ReturnCode res)
+{
+ if (res == KMFilterAction::CriticalError) {
+ mResult = ResultCriticalError;
+ finish(); //must handle critical errors immediately
+ }
+ if (mFilterAction) {
+ KMMessage *msg = message( *mMessageIt );
+ if (msg) {
+ if ( FilterLog::instance()->isLogging() ) {
+ QString logText( i18n( "<b>Applying filter action:</b> %1" )
+ .arg( mFilterAction->displayString() ) );
+ FilterLog::instance()->add( logText, FilterLog::appliedAction );
+ }
+ KMFilterAction *action = mFilterAction;
+ mFilterAction = (*mFilterIt).actions()->next();
+ action->processAsync( msg );
+ }
+ } else {
+ // there are no more actions
+ if ((*mFilterIt).stopProcessingHere())
+ mFilterIt = mFilters.end();
+ else
+ ++mFilterIt;
+ filterMessageTimer->start( 0, true );
+ }
+}
+
+void ActionScheduler::moveMessage()
+{
+ KMMsgBase *msgBase = messageBase( *mMessageIt );
+ if (!msgBase)
+ return;
+
+ MessageProperty::setTransferInProgress( *mMessageIt, false, true );
+ KMMessage *msg = message( *mMessageIt );
+ KMFolder *folder = MessageProperty::filterFolder( *mMessageIt );
+ QString serNumS = msg->headerField( "X-KMail-Filtered" );
+ if (!serNumS.isEmpty())
+ mOriginalSerNum = serNumS.toUInt();
+ else
+ mOriginalSerNum = 0;
+ MessageProperty::setFilterHandler( *mMessageIt, 0 );
+ MessageProperty::setFiltering( *mMessageIt, false );
+ mSerNums.remove( *mMessageIt );
+
+ KMMessage *orgMsg = 0;
+ ReturnCode mOldReturnCode = mResult;
+ if (mOriginalSerNum)
+ orgMsg = message( mOriginalSerNum );
+ mResult = mOldReturnCode; // ignore errors in deleting original message
+ if (!orgMsg || !orgMsg->parent()) {
+ // Original message is gone, no point filtering it anymore
+ mSrcFolder->removeMsg( mSrcFolder->find( msg ) );
+ kdDebug(5006) << "The original serial number is missing. "
+ << "Cannot complete the filtering." << endl;
+ mExecutingLock = false;
+ processMessageTimer->start( 0, true );
+ return;
+ } else {
+ if (!folder) // no filter folder specified leave in current place
+ folder = orgMsg->parent();
+ }
+
+ mIgnore = true;
+ assert( msg->parent() == mSrcFolder.operator->() );
+ mSrcFolder->take( mSrcFolder->find( msg ) );
+ mSrcFolder->addMsg( msg );
+ mIgnore = false;
+
+ if (msg && folder && kmkernel->folderIsTrash( folder ))
+ KMFilterAction::sendMDN( msg, KMime::MDN::Deleted );
+
+ timeOutTime = QTime::currentTime();
+ KMCommand *cmd = new KMMoveCommand( folder, msg );
+ connect( cmd, SIGNAL( completed( KMCommand * ) ),
+ this, SLOT( moveMessageFinished( KMCommand * ) ) );
+ cmd->start();
+ // sometimes the move command doesn't complete so time out after a minute
+ // and move onto the next message
+ lastCommand = cmd;
+ timeOutTimer->start( 60 * 1000, true );
+}
+
+void ActionScheduler::moveMessageFinished( KMCommand *command )
+{
+ timeOutTimer->stop();
+ if ( command->result() != KMCommand::OK )
+ mResult = ResultError;
+
+ if (!mSrcFolder->count())
+ mSrcFolder->expunge();
+
+ // in case the message stayed in the current folder TODO optimize
+ if ( mHeaders )
+ mHeaders->clearSelectableAndAboutToBeDeleted( mOriginalSerNum );
+ KMMessage *msg = 0;
+ ReturnCode mOldReturnCode = mResult;
+ if (mOriginalSerNum) {
+ msg = message( mOriginalSerNum );
+ emit filtered( mOriginalSerNum );
+ }
+
+ mResult = mOldReturnCode; // ignore errors in deleting original message
+ KMCommand *cmd = 0;
+ if (msg && msg->parent()) {
+ cmd = new KMMoveCommand( 0, msg );
+// cmd->start(); // Note: sensitive logic here.
+ }
+
+ if (mResult == ResultOk) {
+ mExecutingLock = false;
+ if (cmd)
+ connect( cmd, SIGNAL( completed( KMCommand * ) ),
+ this, SLOT( processMessage() ) );
+ else
+ processMessageTimer->start( 0, true );
+ } else {
+ // Note: An alternative to consider is just calling
+ // finishTimer->start and returning
+ if (cmd)
+ connect( cmd, SIGNAL( completed( KMCommand * ) ),
+ this, SLOT( finish() ) );
+ else
+ finishTimer->start( 0, true );
+ }
+ if (cmd)
+ cmd->start();
+ // else moveMessageFinished should call finish
+}
+
+void ActionScheduler::copyMessageFinished( KMCommand *command )
+{
+ if ( command->result() != KMCommand::OK )
+ actionMessage( KMFilterAction::ErrorButGoOn );
+ else
+ actionMessage();
+}
+
+void ActionScheduler::timeOut()
+{
+ // Note: This is a good place for a debug statement
+ assert( lastCommand );
+ // sometimes imap jobs seem to just stall so give up and move on
+ disconnect( lastCommand, SIGNAL( completed( KMCommand * ) ),
+ this, SLOT( moveMessageFinished( KMCommand * ) ) );
+ lastCommand = 0;
+ mExecutingLock = false;
+ mExecuting = false;
+ finishTimer->start( 0, true );
+ if (mOriginalSerNum) // Try again
+ execFilters( mOriginalSerNum );
+}
+
+void ActionScheduler::fetchTimeOut()
+{
+ // Note: This is a good place for a debug statement
+ assert( lastJob );
+ // sometimes imap jobs seem to just stall so give up and move on
+ disconnect( lastJob, SIGNAL(messageRetrieved( KMMessage* )),
+ this, SLOT(messageFetched( KMMessage* )) );
+ lastJob->kill();
+ lastJob = 0;
+ fetchMessageTimer->start( 0, true );
+}
+
+QString ActionScheduler::debug()
+{
+ QString res;
+ QValueList<ActionScheduler*>::iterator it;
+ int i = 1;
+ for ( it = schedulerList->begin(); it != schedulerList->end(); ++it ) {
+ res.append( QString( "ActionScheduler #%1.\n" ).arg( i ) );
+ if ((*it)->mAccount && kmkernel->find( (*it)->mAccountId )) {
+ res.append( QString( "Account %1, Name %2.\n" )
+ .arg( (*it)->mAccountId )
+ .arg( kmkernel->acctMgr()->find( (*it)->mAccountId )->name() ) );
+ }
+ res.append( QString( "mExecuting %1, " ).arg( (*it)->mExecuting ? "true" : "false" ) );
+ res.append( QString( "mExecutingLock %1, " ).arg( (*it)->mExecutingLock ? "true" : "false" ) );
+ res.append( QString( "mFetchExecuting %1.\n" ).arg( (*it)->mFetchExecuting ? "true" : "false" ) );
+ res.append( QString( "mOriginalSerNum %1.\n" ).arg( (*it)->mOriginalSerNum ) );
+ res.append( QString( "mMessageIt %1.\n" ).arg( ((*it)->mMessageIt != 0) ? *(*it)->mMessageIt : 0 ) );
+ res.append( QString( "mSerNums count %1, " ).arg( (*it)->mSerNums.count() ) );
+ res.append( QString( "mFetchSerNums count %1.\n" ).arg( (*it)->mFetchSerNums.count() ) );
+ res.append( QString( "mResult " ) );
+ if ((*it)->mResult == ResultOk)
+ res.append( QString( "ResultOk.\n" ) );
+ else if ((*it)->mResult == ResultError)
+ res.append( QString( "ResultError.\n" ) );
+ else if ((*it)->mResult == ResultCriticalError)
+ res.append( QString( "ResultCriticalError.\n" ) );
+ else
+ res.append( QString( "Unknown.\n" ) );
+
+ ++i;
+ }
+ return res;
+}
+
+bool ActionScheduler::isEnabled()
+{
+ if (sEnabledChecked)
+ return sEnabled;
+
+ sEnabledChecked = true;
+ KConfig* config = KMKernel::config();
+ KConfigGroupSaver saver(config, "General");
+ sEnabled = config->readBoolEntry("action-scheduler", false);
+ return sEnabled;
+}
+
+bool ActionScheduler::ignoreChanges( bool ignore )
+{
+ bool oldValue = mIgnore;
+ mIgnore = ignore;
+ return oldValue;
+}
+
+#include "actionscheduler.moc"
diff --git a/kmail/actionscheduler.h b/kmail/actionscheduler.h
new file mode 100644
index 00000000..027bce21
--- /dev/null
+++ b/kmail/actionscheduler.h
@@ -0,0 +1,173 @@
+/* -*- mode: C++ -*-
+ Action Scheduler
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) Don Sanders <sanders@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef actionscheduler_h
+#define actionscheduler_h
+
+#include "kmfilteraction.h" // for KMFilterAction::ReturnCode
+#include "kmfilter.h"
+#include "kmfiltermgr.h" // KMFilterMgr::FilterSet
+#include "kmcommands.h"
+
+#include <qobject.h>
+#include <qguardedptr.h>
+#include <qtimer.h>
+
+class KMHeaders;
+
+namespace KMail {
+
+/* A class for asynchronous filtering of messages */
+class ActionScheduler : public QObject
+{
+ Q_OBJECT
+
+public:
+ enum ReturnCode { ResultOk, ResultError, ResultCriticalError };
+
+ ActionScheduler(KMFilterMgr::FilterSet set,
+ QValueList<KMFilter*> filters,
+ KMHeaders *headers = 0,
+ KMFolder *srcFolder = 0);
+ ~ActionScheduler();
+
+ /** The action scheduler will be deleted after the finish signal is emitted
+ if this property is set to true */
+ void setAutoDestruct( bool );
+
+ /** Apply all filters even if they don't match */
+ void setAlwaysMatch( bool );
+
+ /** Set a default folder to move messages into */
+ void setDefaultDestinationFolder( KMFolder* );
+
+ /** Set a folder to monitor for new messages to filter */
+ void setSourceFolder( KMFolder* );
+
+ /** Set a list of filters to work with
+ The current list will not be updated until the queue
+ of messages left to process is empty */
+ void setFilterList( QValueList<KMFilter*> filters );
+
+ /** Set the id of the account associated with this scheduler */
+ void setAccountId( uint id ) { mAccountId = id; mAccount = true; }
+
+ /** Clear the id of the account associated with this scheduler */
+ void clearAccountId() { mAccountId = 0; mAccount = false; }
+
+ /** Queue a message for filtering */
+ void execFilters(const QValueList<Q_UINT32> serNums);
+ void execFilters(const QPtrList<KMMsgBase> msgList);
+ void execFilters(KMMsgBase* msgBase);
+ void execFilters(Q_UINT32 serNum);
+
+ static QString debug();
+ static bool isEnabled();
+
+ /** Allow or deny manipulations on the message to be filtered.
+ This is needed when using pipe-through filters, because the
+ changes made by the filter have to be written back.
+ The old value before applying the new value is returned. */
+ bool ignoreChanges( bool ignore );
+
+signals:
+ /** Emitted when filtering is completed */
+ void result(ReturnCode);
+ void filtered(Q_UINT32);
+
+public slots:
+ /** Called back by asynchronous actions when they have completed */
+ void actionMessage(KMFilterAction::ReturnCode = KMFilterAction::GoOn);
+
+ /** Called back by asynchronous copy actions when they have completed */
+ void copyMessageFinished( KMCommand *command );
+
+private slots:
+ KMMsgBase* messageBase(Q_UINT32 serNum);
+ KMMessage* message(Q_UINT32 serNum);
+ void finish();
+
+ void folderClosedOrExpunged();
+
+ int tempOpenFolder(KMFolder* aFolder);
+ void tempCloseFolders();
+
+ //Fetching slots
+ void fetchMessage();
+ void messageFetched( KMMessage *msg );
+ void msgAdded( KMFolder*, Q_UINT32 );
+ void enqueue(Q_UINT32 serNum);
+
+ //Filtering slots
+ void processMessage();
+ void messageRetrieved(KMMessage*);
+ void filterMessage();
+ void moveMessage();
+ void moveMessageFinished( KMCommand *command );
+ void timeOut();
+ void fetchTimeOut();
+
+private:
+ static QValueList<ActionScheduler*> *schedulerList; // for debugging
+ static KMFolderMgr *tempFolderMgr;
+ static int refCount, count;
+ static bool sEnabled, sEnabledChecked;
+ QValueListIterator<Q_UINT32> mMessageIt;
+ QValueListIterator<KMFilter> mFilterIt;
+ QValueList<Q_UINT32> mSerNums, mFetchSerNums;
+ QValueList<QGuardedPtr<KMFolder> > mOpenFolders;
+ QValueList<KMFilter> mFilters, mQueuedFilters;
+ KMFilterAction* mFilterAction;
+ KMFilterMgr::FilterSet mSet;
+ KMHeaders *mHeaders;
+ QGuardedPtr<KMFolder> mSrcFolder, mDestFolder;
+ bool mExecuting, mExecutingLock, mFetchExecuting;
+ bool mUnget, mFetchUnget;
+ bool mIgnore;
+ bool mFiltersAreQueued;
+ bool mAutoDestruct;
+ bool mAlwaysMatch;
+ bool mAccount;
+ uint mAccountId;
+ Q_UINT32 mOriginalSerNum;
+ bool mDeleteSrcFolder;
+ ReturnCode mResult;
+ QTimer *finishTimer, *fetchMessageTimer, *tempCloseFoldersTimer;
+ QTimer *processMessageTimer, *filterMessageTimer;
+ QTimer *timeOutTimer, *fetchTimeOutTimer;
+ QTime timeOutTime, fetchTimeOutTime;
+ KMCommand *lastCommand;
+ FolderJob *lastJob;
+};
+
+}
+
+#endif /*actionscheduler_h*/
diff --git a/kmail/annotationjobs.cpp b/kmail/annotationjobs.cpp
new file mode 100644
index 00000000..314f01fa
--- /dev/null
+++ b/kmail/annotationjobs.cpp
@@ -0,0 +1,253 @@
+/**
+ * annotationjobs.cpp
+ *
+ * Copyright (c) 2004 David Faure <faure@kde.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+#include "annotationjobs.h"
+#include <kio/scheduler.h>
+#include <kdebug.h>
+
+using namespace KMail;
+
+KIO::SimpleJob* AnnotationJobs::setAnnotation(
+ KIO::Slave* slave, const KURL& url, const QString& entry,
+ const QMap<QString,QString>& attributes )
+{
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+ stream << (int)'M' << (int)'S' << url << entry << attributes;
+
+ KIO::SimpleJob* job = KIO::special( url, packedArgs, false );
+ KIO::Scheduler::assignJobToSlave( slave, job );
+ return job;
+}
+
+AnnotationJobs::GetAnnotationJob* AnnotationJobs::getAnnotation(
+ KIO::Slave* slave, const KURL& url, const QString& entry,
+ const QStringList& attributes )
+{
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+ stream << (int)'M' << (int)'G' << url << entry << attributes;
+
+ GetAnnotationJob* job = new GetAnnotationJob( url, entry, packedArgs, false );
+ KIO::Scheduler::assignJobToSlave( slave, job );
+ return job;
+}
+
+AnnotationJobs::GetAnnotationJob::GetAnnotationJob( const KURL& url, const QString& entry,
+ const QByteArray &packedArgs,
+ bool showProgressInfo )
+ : KIO::SimpleJob( url, KIO::CMD_SPECIAL, packedArgs, showProgressInfo ),
+ mEntry( entry )
+{
+ connect( this, SIGNAL(infoMessage(KIO::Job*,const QString&)),
+ SLOT(slotInfoMessage(KIO::Job*,const QString&)) );
+}
+
+void AnnotationJobs::GetAnnotationJob::slotInfoMessage( KIO::Job*, const QString& str )
+{
+ // Parse the result
+ QStringList lst = QStringList::split( "\r", str );
+ while ( lst.count() >= 2 ) // we take items 2 by 2
+ {
+ QString name = lst.front(); lst.pop_front();
+ QString value = lst.front(); lst.pop_front();
+ mAnnotations.append( AnnotationAttribute( mEntry, name, value ) );
+ }
+}
+
+AnnotationJobs::MultiGetAnnotationJob::MultiGetAnnotationJob(
+ KIO::Slave* slave, const KURL& url, const QStringList& entries, bool showProgressInfo )
+ : KIO::Job( showProgressInfo ),
+ mSlave( slave ),
+ mUrl( url ), mEntryList( entries ), mEntryListIterator( mEntryList.begin() )
+{
+ QTimer::singleShot(0, this, SLOT(slotStart()));
+}
+
+
+void AnnotationJobs::MultiGetAnnotationJob::slotStart()
+{
+ if ( mEntryListIterator != mEntryList.end() ) {
+ QStringList attributes;
+ attributes << "value";
+ KIO::Job* job = getAnnotation( mSlave, mUrl, *mEntryListIterator, attributes );
+ addSubjob( job );
+ } else { // done!
+ emitResult();
+ }
+}
+
+void AnnotationJobs::MultiGetAnnotationJob::slotResult( KIO::Job *job )
+{
+ if ( job->error() ) {
+ KIO::Job::slotResult( job ); // will set the error and emit result(this)
+ return;
+ }
+ subjobs.remove( job );
+ const QString& entry = *mEntryListIterator;
+ QString value;
+ bool found = false;
+ GetAnnotationJob* getJob = static_cast<GetAnnotationJob *>( 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;
+ if ( lst[i].name.startsWith( "value." ) ) { // value.priv or value.shared
+ found = true;
+ value = lst[i].value;
+ break;
+ }
+ }
+ emit annotationResult( entry, value, found );
+ // Move on to next one
+ ++mEntryListIterator;
+ slotStart();
+}
+
+AnnotationJobs::MultiGetAnnotationJob* AnnotationJobs::multiGetAnnotation( KIO::Slave* slave, const KURL& url, const QStringList& entries )
+{
+ return new MultiGetAnnotationJob( slave, url, entries, false /*showProgressInfo*/ );
+}
+
+////
+
+AnnotationJobs::MultiSetAnnotationJob::MultiSetAnnotationJob(
+ KIO::Slave* slave, const KURL& url, const AnnotationList& annotations, bool showProgressInfo )
+ : KIO::Job( showProgressInfo ),
+ mSlave( slave ),
+ mUrl( url ), mAnnotationList( annotations ), mAnnotationListIterator( mAnnotationList.begin() )
+{
+ QTimer::singleShot(0, this, SLOT(slotStart()));
+}
+
+
+void AnnotationJobs::MultiSetAnnotationJob::slotStart()
+{
+ if ( mAnnotationListIterator != mAnnotationList.end() ) {
+ const AnnotationAttribute& attr = *mAnnotationListIterator;
+ // setAnnotation can set multiple attributes for a given entry.
+ // So in theory we could group entries coming from our list. Bah.
+ QMap<QString, QString> attributes;
+ attributes.insert( attr.name, attr.value );
+ kdDebug() << k_funcinfo << " setting annotation " << attr.entry << " " << attr.name << " " << attr.value << endl;
+ KIO::Job* job = setAnnotation( mSlave, mUrl, attr.entry, attributes );
+ addSubjob( job );
+ } else { // done!
+ emitResult();
+ }
+}
+
+void AnnotationJobs::MultiSetAnnotationJob::slotResult( KIO::Job *job )
+{
+ if ( job->error() ) {
+ KIO::Job::slotResult( job ); // will set the error and emit result(this)
+ return;
+ }
+ subjobs.remove( job );
+ const AnnotationAttribute& attr = *mAnnotationListIterator;
+ emit annotationChanged( attr.entry, attr.name, attr.value );
+
+ // Move on to next one
+ ++mAnnotationListIterator;
+ slotStart();
+}
+
+AnnotationJobs::MultiSetAnnotationJob* AnnotationJobs::multiSetAnnotation(
+ KIO::Slave* slave, const KURL& url, const AnnotationList& annotations )
+{
+ return new MultiSetAnnotationJob( slave, url, annotations, false /*showProgressInfo*/ );
+}
+
+
+AnnotationJobs::MultiUrlGetAnnotationJob::MultiUrlGetAnnotationJob( KIO::Slave* slave,
+ const KURL& baseUrl,
+ const QStringList& paths,
+ const QString& annotation )
+ : KIO::Job( false ),
+ mSlave( slave ),
+ mUrl( baseUrl ),
+ mPathList( paths ),
+ mPathListIterator( mPathList.begin() ),
+ mAnnotation( annotation )
+{
+ QTimer::singleShot(0, this, SLOT(slotStart()));
+}
+
+
+void AnnotationJobs::MultiUrlGetAnnotationJob::slotStart()
+{
+ if ( mPathListIterator != mPathList.end() ) {
+ QStringList attributes;
+ attributes << "value";
+ KURL url(mUrl);
+ url.setPath( *mPathListIterator );
+ KIO::Job* job = getAnnotation( mSlave, url, mAnnotation, attributes );
+ addSubjob( job );
+ } else { // done!
+ emitResult();
+ }
+}
+
+void AnnotationJobs::MultiUrlGetAnnotationJob::slotResult( KIO::Job *job )
+{
+ if ( job->error() ) {
+ KIO::Job::slotResult( job ); // will set the error and emit result(this)
+ return;
+ }
+ subjobs.remove( job );
+ const QString& path = *mPathListIterator;
+ GetAnnotationJob* getJob = static_cast<GetAnnotationJob *>( job );
+ const AnnotationList& lst = getJob->annotations();
+ for ( unsigned int i = 0 ; i < lst.size() ; ++ i ) {
+ kdDebug(5006) << "MultiURL: found annotation " << lst[i].name << " = " << lst[i].value << " for path: " << path << endl;
+ if ( lst[i].name.startsWith( "value." ) ) { // value.priv or value.shared
+ mAnnotations.insert( path, lst[i].value );
+ break;
+ }
+ }
+ // Move on to next one
+ ++mPathListIterator;
+ slotStart();
+}
+
+QMap<QString, QString> AnnotationJobs::MultiUrlGetAnnotationJob::annotations() const
+{
+ return mAnnotations;
+}
+
+AnnotationJobs::MultiUrlGetAnnotationJob* AnnotationJobs::multiUrlGetAnnotation( KIO::Slave* slave,
+ const KURL& baseUrl,
+ const QStringList& paths,
+ const QString& annotation )
+{
+ return new MultiUrlGetAnnotationJob( slave, baseUrl, paths, annotation );
+}
+
+
+#include "annotationjobs.moc"
diff --git a/kmail/annotationjobs.h b/kmail/annotationjobs.h
new file mode 100644
index 00000000..5375eeed
--- /dev/null
+++ b/kmail/annotationjobs.h
@@ -0,0 +1,199 @@
+/*
+ * annotationjobs.h
+ *
+ * Copyright (c) 2004 David Faure <faure@kde.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#ifndef ANNOTATIONJOBS_H
+#define ANNOTATIONJOBS_H
+
+#include <kio/job.h>
+#include <qvaluevector.h>
+
+namespace KMail {
+
+/// One entry in the annotation list: attribute name and attribute value
+struct AnnotationAttribute {
+ AnnotationAttribute() {} // for QValueVector
+ AnnotationAttribute( const QString& e, const QString& n, const QString& v )
+ : entry( e ), name( n ), value( v ) {}
+ QString entry; // e.g. /comment
+ QString name; // e.g. value.shared
+ QString value;
+};
+
+typedef QValueVector<AnnotationAttribute> AnnotationList;
+
+/**
+ * This namespace contains functions that return jobs for annotation operations.
+ *
+ * The current implementation is tied to IMAP.
+ * If someone wants to extend this to other protocols, turn the namespace into a class
+ * and use virtual methods.
+ */
+namespace AnnotationJobs {
+
+/**
+ * Set an annotation entry (note that it can have multiple attributes)
+ * @param slave Slave object the job should be assigned to
+ * @param url URL for the annotation
+ * @param entry the name of the annotation entry
+ * @param attributes attribute name+value pairs
+ */
+KIO::SimpleJob* setAnnotation( KIO::Slave* slave, const KURL& url, const QString& entry,
+ const QMap<QString,QString>& attributes );
+
+class MultiSetAnnotationJob;
+/**
+ * Set multiple annotation entries
+ */
+MultiSetAnnotationJob* multiSetAnnotation( KIO::Slave* slave, const KURL& url, const AnnotationList& annotations );
+
+class GetAnnotationJob;
+/**
+ * Get an annotation entry
+ * @param slave Slave object the job should be assigned to
+ * @param url URL for the annotation
+ * @param entry the name of the annotation entry
+ * @param attributes attribute names
+ */
+GetAnnotationJob* getAnnotation( KIO::Slave* slave, const KURL& url, const QString& entry,
+ const QStringList& attributes );
+
+class MultiGetAnnotationJob;
+/**
+ * Get multiple annotation entries
+ * Currently we assume we want to get the "value" for each, to simplify the data structure.
+ */
+MultiGetAnnotationJob* multiGetAnnotation( KIO::Slave* slave, const KURL& url, const QStringList& entries );
+
+class MultiUrlGetAnnotationJob;
+/**
+ * Get annotation entries for multiple folders.
+ * @param paths The paths to get the annotation for
+ * @param annotation The annotation to get
+ */
+MultiUrlGetAnnotationJob* multiUrlGetAnnotation( KIO::Slave* slave,
+ const KURL& baseUrl,
+ const QStringList& paths,
+ const QString& annotation );
+
+
+/// for getAnnotation()
+class GetAnnotationJob : public KIO::SimpleJob
+{
+ Q_OBJECT
+public:
+ GetAnnotationJob( const KURL& url, const QString& entry, const QByteArray &packedArgs,
+ bool showProgressInfo );
+
+ const AnnotationList& annotations() const { return mAnnotations; }
+
+protected slots:
+ void slotInfoMessage( KIO::Job*, const QString& );
+private:
+ AnnotationList mAnnotations;
+ QString mEntry;
+};
+
+/// for multiGetAnnotation
+class MultiGetAnnotationJob : public KIO::Job
+{
+ Q_OBJECT
+
+public:
+ MultiGetAnnotationJob( KIO::Slave* slave, const KURL& url, const QStringList& entries, bool showProgressInfo );
+
+signals:
+ // Emitted when a given annotation was found - or not found
+ void annotationResult( const QString& entry, const QString& value, bool found );
+
+protected slots:
+ virtual void slotStart();
+ virtual void slotResult( KIO::Job *job );
+
+private:
+ KIO::Slave* mSlave;
+ const KURL mUrl;
+ const QStringList mEntryList;
+ QStringList::const_iterator mEntryListIterator;
+};
+
+/// for multiUrlGetAnnotation
+class MultiUrlGetAnnotationJob : public KIO::Job
+{
+ Q_OBJECT
+
+public:
+ MultiUrlGetAnnotationJob( KIO::Slave* slave, const KURL& baseUrl,
+ const QStringList& paths, const QString& annotation );
+
+ QMap<QString, QString> annotations() const;
+
+protected slots:
+ virtual void slotStart();
+ virtual void slotResult( KIO::Job *job );
+
+private:
+ KIO::Slave* mSlave;
+ const KURL mUrl;
+ const QStringList mPathList;
+ QStringList::const_iterator mPathListIterator;
+ QString mAnnotation;
+ QMap<QString, QString> mAnnotations;
+};
+
+/// for multiSetAnnotation
+class MultiSetAnnotationJob : public KIO::Job
+{
+ Q_OBJECT
+
+public:
+ MultiSetAnnotationJob( KIO::Slave* slave, const KURL& url, const AnnotationList& annotations, bool showProgressInfo );
+
+signals:
+ // Emitted when a given annotation was successfully changed
+ void annotationChanged( const QString& entry, const QString& attribute, const QString& value );
+
+protected slots:
+ virtual void slotStart();
+ virtual void slotResult( KIO::Job *job );
+
+private:
+ KIO::Slave* mSlave;
+ const KURL mUrl;
+ const AnnotationList mAnnotationList;
+ AnnotationList::const_iterator mAnnotationListIterator;
+};
+
+} // AnnotationJobs namespace
+
+} // KMail namespace
+
+#endif /* ANNOTATIONJOBS_H */
+
diff --git a/kmail/antispamconfig.cpp b/kmail/antispamconfig.cpp
new file mode 100644
index 00000000..448bd9ac
--- /dev/null
+++ b/kmail/antispamconfig.cpp
@@ -0,0 +1,98 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ antispamconfig.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2004 Patrick Audley <paudley@blackcat.ca>
+ Copyright (c) 2004 Ingo Kloecker <kloecker@kde.org>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "antispamconfig.h"
+
+#include <kasciistricmp.h>
+
+#include <kstaticdeleter.h>
+#include <kconfig.h>
+
+using namespace KMail;
+
+AntiSpamConfig * AntiSpamConfig::sSelf = 0;
+static KStaticDeleter<AntiSpamConfig> antispamconfig_sd;
+
+AntiSpamConfig * AntiSpamConfig::instance() {
+ if ( !sSelf ) {
+ antispamconfig_sd.setObject( sSelf, new AntiSpamConfig() );
+ sSelf->readConfig();
+ }
+ return sSelf;
+}
+
+void AntiSpamConfig::readConfig()
+{
+ mAgents.clear();
+ KConfig config( "kmail.antispamrc", true );
+ config.setReadDefaults( true );
+ KConfigGroup general( &config, "General" );
+ unsigned int totalTools = general.readUnsignedNumEntry( "tools", 0 );
+ for ( unsigned int i = 1; i <= totalTools; ++i ) {
+ KConfigGroup tool( &config, QString("Spamtool #%1").arg( i ) );
+ if ( tool.hasKey( "ScoreHeader" ) ) {
+ QString name = tool.readEntry( "ScoreName" );
+ QCString header = tool.readEntry( "ScoreHeader" ).latin1();
+ QCString type = tool.readEntry( "ScoreType" ).latin1();
+ QString score = tool.readEntryUntranslated( "ScoreValueRegexp" );
+ QString threshold = tool.readEntryUntranslated( "ScoreThresholdRegexp" );
+ SpamAgentTypes typeE = SpamAgentNone;
+ if ( kasciistricmp( type.data(), "bool" ) == 0 )
+ typeE = SpamAgentBool;
+ else if ( kasciistricmp( type.data(), "decimal" ) == 0 )
+ typeE = SpamAgentFloat;
+ else if ( kasciistricmp( type.data(), "percentage" ) == 0 )
+ typeE = SpamAgentFloatLarge;
+ else if ( kasciistricmp( type.data(), "adjusted" ) == 0 )
+ typeE = SpamAgentAdjustedFloat;
+ mAgents.append( SpamAgent( name, typeE, header, QRegExp( score ),
+ QRegExp( threshold ) ) );
+ }
+ }
+}
+
+const SpamAgents AntiSpamConfig::uniqueAgents() const
+{
+ QStringList seenAgents;
+ SpamAgents agents;
+ SpamAgents::ConstIterator it( mAgents.begin() );
+ SpamAgents::ConstIterator end( mAgents.end() );
+ for ( ; it != end ; ++it ) {
+ const QString agent( ( *it ).name() );
+ if ( seenAgents.find( agent ) == seenAgents.end() ) {
+ agents.append( *it );
+ seenAgents.append( agent );
+ }
+ }
+ return agents;
+}
diff --git a/kmail/antispamconfig.h b/kmail/antispamconfig.h
new file mode 100644
index 00000000..64394fe9
--- /dev/null
+++ b/kmail/antispamconfig.h
@@ -0,0 +1,120 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ antispamconfig.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2004 Patrick Audley <paudley@blackcat.ca>
+ Copyright (c) 2004 Ingo Kloecker <kloecker@kde.org>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_ANTISPAMCONFIG_H__
+#define __KMAIL_ANTISPAMCONFIG_H__
+
+#include <qvaluelist.h>
+#include <qregexp.h>
+
+class QString;
+class QCString;
+
+namespace KMail {
+
+ /// Valid types of SpamAgent
+ typedef enum {
+ SpamAgentNone, //!< Invalid SpamAgent, skip this agent
+ SpamAgentBool, //!< Simple Yes or No (Razor)
+ SpamAgentFloat, //!< For straight percentages between 0.0 and 1.0 (BogoFilter)
+ SpamAgentFloatLarge, //!< For straight percentages between 0.0 and 100.0
+ SpamAgentAdjustedFloat //!< Use this when we need to compare against a threshold (SpamAssasssin)
+ } SpamAgentTypes;
+
+ class SpamAgent {
+ public:
+ SpamAgent() : mType( SpamAgentNone ) {}
+ SpamAgent( const QString & name, SpamAgentTypes type, const QCString & field,
+ const QRegExp & score, const QRegExp & threshold )
+ : mName( name ), mType( type ), mField( field ),
+ mScore( score ), mThreshold( threshold ) {}
+
+ QString name() const { return mName; }
+ SpamAgentTypes scoreType() const { return mType; }
+ QCString header() const { return mField; }
+ QRegExp scorePattern() const { return mScore; }
+ QRegExp thresholdPattern() const { return mThreshold; }
+
+ private:
+ QString mName;
+ SpamAgentTypes mType;
+ QCString mField;
+ QRegExp mScore;
+ QRegExp mThreshold;
+ };
+ typedef QValueList<SpamAgent> SpamAgents;
+ typedef QValueListIterator<SpamAgent> SpamAgentsIterator;
+
+ /**
+ @short Singleton to manage loading the kmail.antispamrc file.
+ @author Patrick Audley <paudley@blackcat.ca>
+
+ Use of this config-manager class is straight forward. Since it
+ is a singleton object, all you have to do is obtain an instance
+ by calling @p SpamConfig::instance() and use any of the
+ public member functions.
+ */
+ class AntiSpamConfig {
+ private:
+ static AntiSpamConfig * sSelf;
+
+ AntiSpamConfig() {}
+
+ public:
+ ~AntiSpamConfig() {}
+
+ static AntiSpamConfig * instance();
+
+ /**
+ * Returns a list of all agents found on the system. This
+ * might list SA twice, if both the C and the Perl version are present.
+ */
+ const SpamAgents agents() const { return mAgents; }
+ SpamAgents agents() { return mAgents; }
+
+ /**
+ * Returns a list of unique agents, found on the system. SpamAssassin will
+ * only be listed once, even if both the C and the Perl version are
+ * installed.
+ */
+ const SpamAgents uniqueAgents() const;
+
+ private:
+ SpamAgents mAgents;
+
+ void readConfig();
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_ANTISPAMCONFIG_H__
diff --git a/kmail/antispamwizard.cpp b/kmail/antispamwizard.cpp
new file mode 100644
index 00000000..bb8d74bb
--- /dev/null
+++ b/kmail/antispamwizard.cpp
@@ -0,0 +1,1151 @@
+/*
+ This file is part of KMail.
+ Copyright (c) 2003 Andreas Gungl <a.gungl@gmx.de>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "antispamwizard.h"
+#include "kcursorsaver.h"
+#include "accountmanager.h"
+#include "kmfilter.h"
+#include "kmfilteraction.h"
+#include "kmfiltermgr.h"
+#include "kmkernel.h"
+#include "kmfolderseldlg.h"
+#include "kmfoldertree.h"
+#include "kmmainwin.h"
+#include "networkaccount.h"
+#include "folderrequester.h"
+
+#include <kaction.h>
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kdialog.h>
+#include <kiconloader.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kprocess.h>
+
+#include <qdom.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qtooltip.h>
+#include <qwhatsthis.h>
+
+using namespace KMail;
+
+AntiSpamWizard::AntiSpamWizard( WizardMode mode,
+ QWidget* parent, KMFolderTree * mainFolderTree )
+ : KWizard( parent ),
+ mInfoPage( 0 ),
+ mSpamRulesPage( 0 ),
+ mVirusRulesPage( 0 ),
+ mSummaryPage( 0 ),
+ mMode( mode )
+{
+ // read the configuration for the anti-spam tools
+ ConfigReader reader( mMode, mToolList );
+ reader.readAndMergeConfig();
+ mToolList = reader.getToolList();
+
+#ifndef NDEBUG
+ if ( mMode == AntiSpam )
+ kdDebug(5006) << endl << "Considered anti-spam tools: " << endl;
+ else
+ kdDebug(5006) << endl << "Considered anti-virus tools: " << endl;
+#endif
+ for ( QValueListIterator<SpamToolConfig> it = mToolList.begin();
+ it != mToolList.end(); ++it ) {
+#ifndef NDEBUG
+ kdDebug(5006) << "Predefined tool: " << (*it).getId() << endl;
+ kdDebug(5006) << "Config version: " << (*it).getVersion() << endl;
+ kdDebug(5006) << "Selection priority: " << (*it).getPrio() << endl;
+ kdDebug(5006) << "Displayed name: " << (*it).getVisibleName() << endl;
+ kdDebug(5006) << "Executable: " << (*it).getExecutable() << endl;
+ kdDebug(5006) << "WhatsThis URL: " << (*it).getWhatsThisText() << endl;
+ kdDebug(5006) << "Filter name: " << (*it).getFilterName() << endl;
+ kdDebug(5006) << "Detection command: " << (*it).getDetectCmd() << endl;
+ kdDebug(5006) << "Learn spam command: " << (*it).getSpamCmd() << endl;
+ kdDebug(5006) << "Learn ham command: " << (*it).getHamCmd() << endl;
+ kdDebug(5006) << "Detection header: " << (*it).getDetectionHeader() << endl;
+ kdDebug(5006) << "Detection pattern: " << (*it).getDetectionPattern() << endl;
+ kdDebug(5006) << "Use as RegExp: " << (*it).isUseRegExp() << endl;
+ kdDebug(5006) << "Supports Bayes Filter: " << (*it).useBayesFilter() << endl;
+ kdDebug(5006) << "Type: " << (*it).getType() << endl << endl;
+#endif
+ }
+
+ setCaption( ( mMode == AntiSpam ) ? i18n( "Anti-Spam Wizard" )
+ : i18n( "Anti-Virus Wizard" ) );
+ mInfoPage = new ASWizInfoPage( mMode, 0, "" );
+ addPage( mInfoPage,
+ ( mMode == AntiSpam )
+ ? i18n( "Welcome to the KMail Anti-Spam Wizard" )
+ : i18n( "Welcome to the KMail Anti-Virus Wizard" ) );
+ connect( mInfoPage, SIGNAL( selectionChanged( void ) ),
+ this, SLOT( checkProgramsSelections( void ) ) );
+
+ if ( mMode == AntiSpam ) {
+ mSpamRulesPage = new ASWizSpamRulesPage( 0, "", mainFolderTree );
+ addPage( mSpamRulesPage, i18n( "Options to fine-tune the handling of spam messages" ));
+ connect( mSpamRulesPage, SIGNAL( selectionChanged( void ) ),
+ this, SLOT( slotBuildSummary( void ) ) );
+ }
+ else {
+ mVirusRulesPage = new ASWizVirusRulesPage( 0, "", mainFolderTree );
+ addPage( mVirusRulesPage, i18n( "Options to fine-tune the handling of virus messages" ));
+ connect( mVirusRulesPage, SIGNAL( selectionChanged( void ) ),
+ this, SLOT( checkVirusRulesSelections( void ) ) );
+ }
+
+ connect( this, SIGNAL( helpClicked( void) ),
+ this, SLOT( slotHelpClicked( void ) ) );
+
+ setNextEnabled( mInfoPage, false );
+
+ if ( mMode == AntiSpam ) {
+ mSummaryPage = new ASWizSummaryPage( 0, "" );
+ addPage( mSummaryPage, i18n( "Summary of changes to be made by this wizard" ) );
+ setNextEnabled( mSpamRulesPage, true );
+ setFinishEnabled( mSummaryPage, true );
+ }
+
+ QTimer::singleShot( 0, this, SLOT( checkToolAvailability( void ) ) );
+}
+
+
+void AntiSpamWizard::accept()
+{
+ if ( mSpamRulesPage ) {
+ kdDebug( 5006 ) << "Folder name for messages classified as spam is "
+ << mSpamRulesPage->selectedSpamFolderName() << endl;
+ kdDebug( 5006 ) << "Folder name for messages classified as unsure is "
+ << mSpamRulesPage->selectedUnsureFolderName() << endl;
+ }
+ if ( mVirusRulesPage )
+ kdDebug( 5006 ) << "Folder name for viruses is "
+ << mVirusRulesPage->selectedFolderName() << endl;
+
+ KMFilterActionDict dict;
+ QValueList<KMFilter*> filterList;
+ bool replaceExistingFilters = false;
+
+ // Let's start with virus detection and handling,
+ // so we can avoid spam checks for viral messages
+ if ( mMode == AntiVirus ) {
+ for ( QValueListIterator<SpamToolConfig> it = mToolList.begin();
+ it != mToolList.end(); ++it ) {
+ if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ) &&
+ ( mVirusRulesPage->pipeRulesSelected() && (*it).isVirusTool() ) )
+ {
+ // pipe messages through the anti-virus tools,
+ // one single filter for each tool
+ // (could get combined but so it's easier to understand for the user)
+ KMFilter* pipeFilter = new KMFilter();
+ QPtrList<KMFilterAction>* pipeFilterActions = pipeFilter->actions();
+ KMFilterAction* pipeFilterAction = dict["filter app"]->create();
+ pipeFilterAction->argsFromString( (*it).getDetectCmd() );
+ pipeFilterActions->append( pipeFilterAction );
+ KMSearchPattern* pipeFilterPattern = pipeFilter->pattern();
+ pipeFilterPattern->setName( uniqueNameFor( (*it).getFilterName() ) );
+ pipeFilterPattern->append( KMSearchRule::createInstance( "<size>",
+ KMSearchRule::FuncIsGreaterOrEqual, "0" ) );
+ pipeFilter->setApplyOnOutbound( false);
+ pipeFilter->setApplyOnInbound();
+ pipeFilter->setApplyOnExplicit();
+ pipeFilter->setStopProcessingHere( false );
+ pipeFilter->setConfigureShortcut( false );
+
+ filterList.append( pipeFilter );
+ }
+ }
+
+ if ( mVirusRulesPage->moveRulesSelected() )
+ {
+ // Sort out viruses depending on header fields set by the tools
+ KMFilter* virusFilter = new KMFilter();
+ QPtrList<KMFilterAction>* virusFilterActions = virusFilter->actions();
+ KMFilterAction* virusFilterAction1 = dict["transfer"]->create();
+ virusFilterAction1->argsFromString( mVirusRulesPage->selectedFolderName() );
+ virusFilterActions->append( virusFilterAction1 );
+ if ( mVirusRulesPage->markReadRulesSelected() ) {
+ KMFilterAction* virusFilterAction2 = dict["set status"]->create();
+ virusFilterAction2->argsFromString( "R" ); // Read
+ virusFilterActions->append( virusFilterAction2 );
+ }
+ KMSearchPattern* virusFilterPattern = virusFilter->pattern();
+ virusFilterPattern->setName( uniqueNameFor( i18n( "Virus handling" ) ) );
+ virusFilterPattern->setOp( KMSearchPattern::OpOr );
+ for ( QValueListIterator<SpamToolConfig> it = mToolList.begin();
+ it != mToolList.end(); ++it ) {
+ if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ))
+ {
+ if ( (*it).isVirusTool() )
+ {
+ const QCString header = (*it).getDetectionHeader().ascii();
+ const QString & pattern = (*it).getDetectionPattern();
+ if ( (*it).isUseRegExp() )
+ virusFilterPattern->append(
+ KMSearchRule::createInstance( header,
+ KMSearchRule::FuncRegExp, pattern ) );
+ else
+ virusFilterPattern->append(
+ KMSearchRule::createInstance( header,
+ KMSearchRule::FuncContains, pattern ) );
+ }
+ }
+ }
+ virusFilter->setApplyOnOutbound( false);
+ virusFilter->setApplyOnInbound();
+ virusFilter->setApplyOnExplicit();
+ virusFilter->setStopProcessingHere( true );
+ virusFilter->setConfigureShortcut( false );
+
+ filterList.append( virusFilter );
+ }
+ }
+ else { // AntiSpam mode
+ // TODO Existing filters with same name are replaced. This is hardcoded
+ // ATM and needs to be replaced with a value from a (still missing)
+ // checkbox in the GUI. At least, the replacement is announced in the GUI.
+ replaceExistingFilters = true;
+ for ( QValueListIterator<SpamToolConfig> it = mToolList.begin();
+ it != mToolList.end(); ++it ) {
+ if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ) &&
+ (*it).isSpamTool() && !(*it).isDetectionOnly() )
+ {
+ // pipe messages through the anti-spam tools,
+ // one single filter for each tool
+ // (could get combined but so it's easier to understand for the user)
+ KMFilter* pipeFilter = new KMFilter();
+ QPtrList<KMFilterAction>* pipeFilterActions = pipeFilter->actions();
+ KMFilterAction* pipeFilterAction = dict["filter app"]->create();
+ pipeFilterAction->argsFromString( (*it).getDetectCmd() );
+ pipeFilterActions->append( pipeFilterAction );
+ KMSearchPattern* pipeFilterPattern = pipeFilter->pattern();
+ if ( replaceExistingFilters )
+ pipeFilterPattern->setName( (*it).getFilterName() );
+ else
+ pipeFilterPattern->setName( uniqueNameFor( (*it).getFilterName() ) );
+ pipeFilterPattern->append( KMSearchRule::createInstance( "<size>",
+ KMSearchRule::FuncIsLessOrEqual, "256000" ) );
+ pipeFilter->setApplyOnOutbound( false);
+ pipeFilter->setApplyOnInbound();
+ pipeFilter->setApplyOnExplicit();
+ pipeFilter->setStopProcessingHere( false );
+ pipeFilter->setConfigureShortcut( false );
+
+ filterList.append( pipeFilter );
+ }
+ }
+
+ // Sort out spam depending on header fields set by the tools
+ KMFilter* spamFilter = new KMFilter();
+ QPtrList<KMFilterAction>* spamFilterActions = spamFilter->actions();
+ if ( mSpamRulesPage->moveSpamSelected() )
+ {
+ KMFilterAction* spamFilterAction1 = dict["transfer"]->create();
+ spamFilterAction1->argsFromString( mSpamRulesPage->selectedSpamFolderName() );
+ spamFilterActions->append( spamFilterAction1 );
+ }
+ KMFilterAction* spamFilterAction2 = dict["set status"]->create();
+ spamFilterAction2->argsFromString( "P" ); // Spam
+ spamFilterActions->append( spamFilterAction2 );
+ if ( mSpamRulesPage->markAsReadSelected() ) {
+ KMFilterAction* spamFilterAction3 = dict["set status"]->create();
+ spamFilterAction3->argsFromString( "R" ); // Read
+ spamFilterActions->append( spamFilterAction3 );
+ }
+ KMSearchPattern* spamFilterPattern = spamFilter->pattern();
+ if ( replaceExistingFilters )
+ spamFilterPattern->setName( i18n( "Spam handling" ) );
+ else
+ spamFilterPattern->setName( uniqueNameFor( i18n( "Spam handling" ) ) );
+ spamFilterPattern->setOp( KMSearchPattern::OpOr );
+ for ( QValueListIterator<SpamToolConfig> it = mToolList.begin();
+ it != mToolList.end(); ++it ) {
+ if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ) )
+ {
+ if ( (*it).isSpamTool() )
+ {
+ const QCString header = (*it).getDetectionHeader().ascii();
+ const QString & pattern = (*it).getDetectionPattern();
+ if ( (*it).isUseRegExp() )
+ spamFilterPattern->append(
+ KMSearchRule::createInstance( header,
+ KMSearchRule::FuncRegExp, pattern ) );
+ else
+ spamFilterPattern->append(
+ KMSearchRule::createInstance( header,
+ KMSearchRule::FuncContains, pattern ) );
+ }
+ }
+ }
+ spamFilter->setApplyOnOutbound( false);
+ spamFilter->setApplyOnInbound();
+ spamFilter->setApplyOnExplicit();
+ spamFilter->setStopProcessingHere( true );
+ spamFilter->setConfigureShortcut( false );
+ filterList.append( spamFilter );
+
+ if ( mSpamRulesPage->moveUnsureSelected() )
+ {
+ // Sort out messages classified as unsure
+ bool atLeastOneUnsurePattern = false;
+ KMFilter* unsureFilter = new KMFilter();
+ QPtrList<KMFilterAction>* unsureFilterActions = unsureFilter->actions();
+ KMFilterAction* unsureFilterAction1 = dict["transfer"]->create();
+ unsureFilterAction1->argsFromString( mSpamRulesPage->selectedUnsureFolderName() );
+ unsureFilterActions->append( unsureFilterAction1 );
+ KMSearchPattern* unsureFilterPattern = unsureFilter->pattern();
+ if ( replaceExistingFilters )
+ unsureFilterPattern->setName( i18n( "Semi spam (unsure) handling" ) );
+ else
+ unsureFilterPattern->setName( uniqueNameFor( i18n( "Semi spam (unsure) handling" ) ) );
+ unsureFilterPattern->setOp( KMSearchPattern::OpOr );
+ for ( QValueListIterator<SpamToolConfig> it = mToolList.begin();
+ it != mToolList.end(); ++it ) {
+ if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ) )
+ {
+ if ( (*it).isSpamTool() && (*it).hasTristateDetection())
+ {
+ atLeastOneUnsurePattern = true;
+ const QCString header = (*it).getDetectionHeader().ascii();
+ const QString & pattern = (*it).getDetectionPattern2();
+ if ( (*it).isUseRegExp() )
+ unsureFilterPattern->append(
+ KMSearchRule::createInstance( header,
+ KMSearchRule::FuncRegExp, pattern ) );
+ else
+ unsureFilterPattern->append(
+ KMSearchRule::createInstance( header,
+ KMSearchRule::FuncContains, pattern ) );
+ }
+ }
+ }
+ unsureFilter->setApplyOnOutbound( false);
+ unsureFilter->setApplyOnInbound();
+ unsureFilter->setApplyOnExplicit();
+ unsureFilter->setStopProcessingHere( true );
+ unsureFilter->setConfigureShortcut( false );
+
+ if ( atLeastOneUnsurePattern )
+ filterList.append( unsureFilter );
+ else
+ delete unsureFilter;
+ }
+
+ // Classify messages manually as Spam
+ KMFilter* classSpamFilter = new KMFilter();
+ classSpamFilter->setIcon( "mail_spam" );
+ QPtrList<KMFilterAction>* classSpamFilterActions = classSpamFilter->actions();
+ KMFilterAction* classSpamFilterActionFirst = dict["set status"]->create();
+ classSpamFilterActionFirst->argsFromString( "P" );
+ classSpamFilterActions->append( classSpamFilterActionFirst );
+ for ( QValueListIterator<SpamToolConfig> it = mToolList.begin();
+ it != mToolList.end(); ++it ) {
+ if ( mInfoPage->isProgramSelected( (*it).getVisibleName() )
+ && (*it).useBayesFilter() && !(*it).isDetectionOnly() )
+ {
+ KMFilterAction* classSpamFilterAction = dict["execute"]->create();
+ classSpamFilterAction->argsFromString( (*it).getSpamCmd() );
+ classSpamFilterActions->append( classSpamFilterAction );
+ }
+ }
+ if ( mSpamRulesPage->moveSpamSelected() )
+ {
+ KMFilterAction* classSpamFilterActionLast = dict["transfer"]->create();
+ classSpamFilterActionLast->argsFromString( mSpamRulesPage->selectedSpamFolderName() );
+ classSpamFilterActions->append( classSpamFilterActionLast );
+ }
+
+ KMSearchPattern* classSpamFilterPattern = classSpamFilter->pattern();
+ if ( replaceExistingFilters )
+ classSpamFilterPattern->setName( i18n( "Classify as spam" ) );
+ else
+ classSpamFilterPattern->setName( uniqueNameFor( i18n( "Classify as spam" ) ) );
+ classSpamFilterPattern->append( KMSearchRule::createInstance( "<size>",
+ KMSearchRule::FuncIsGreaterOrEqual, "0" ) );
+ classSpamFilter->setApplyOnOutbound( false);
+ classSpamFilter->setApplyOnInbound( false );
+ classSpamFilter->setApplyOnExplicit( false );
+ classSpamFilter->setStopProcessingHere( true );
+ classSpamFilter->setConfigureShortcut( true );
+ classSpamFilter->setConfigureToolbar( true );
+ filterList.append( classSpamFilter );
+
+ // Classify messages manually as not Spam / as Ham
+ KMFilter* classHamFilter = new KMFilter();
+ classHamFilter->setIcon( "mail_ham" );
+ QPtrList<KMFilterAction>* classHamFilterActions = classHamFilter->actions();
+ KMFilterAction* classHamFilterActionFirst = dict["set status"]->create();
+ classHamFilterActionFirst->argsFromString( "H" );
+ classHamFilterActions->append( classHamFilterActionFirst );
+ for ( QValueListIterator<SpamToolConfig> it = mToolList.begin();
+ it != mToolList.end(); ++it ) {
+ if ( mInfoPage->isProgramSelected( (*it).getVisibleName() )
+ && (*it).useBayesFilter() && !(*it).isDetectionOnly() )
+ {
+ KMFilterAction* classHamFilterAction = dict["execute"]->create();
+ classHamFilterAction->argsFromString( (*it).getHamCmd() );
+ classHamFilterActions->append( classHamFilterAction );
+ }
+ }
+ KMSearchPattern* classHamFilterPattern = classHamFilter->pattern();
+ if ( replaceExistingFilters )
+ classHamFilterPattern->setName( i18n( "Classify as NOT spam" ) );
+ else
+ classHamFilterPattern->setName( uniqueNameFor( i18n( "Classify as NOT spam" ) ) );
+ classHamFilterPattern->append( KMSearchRule::createInstance( "<size>",
+ KMSearchRule::FuncIsGreaterOrEqual, "0" ) );
+ classHamFilter->setApplyOnOutbound( false);
+ classHamFilter->setApplyOnInbound( false );
+ classHamFilter->setApplyOnExplicit( false );
+ classHamFilter->setStopProcessingHere( true );
+ 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. */
+ if ( !filterList.isEmpty() )
+ KMKernel::self()->filterMgr()->appendFilters(
+ filterList, replaceExistingFilters );
+
+ QDialog::accept();
+}
+
+
+void AntiSpamWizard::checkProgramsSelections()
+{
+ bool status = false;
+ bool supportUnsure = false;
+
+ mSpamToolsUsed = false;
+ mVirusToolsUsed = false;
+ for ( QValueListIterator<SpamToolConfig> it = mToolList.begin();
+ it != mToolList.end(); ++it ) {
+ if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ) )
+ {
+ status = true;
+ if ( (*it).isSpamTool() ) {
+ mSpamToolsUsed = true;
+ if ( (*it).hasTristateDetection() )
+ supportUnsure = true;
+ }
+ if ( (*it).isVirusTool() )
+ mVirusToolsUsed = true;
+ }
+ }
+
+ if ( mMode == AntiSpam ) {
+ mSpamRulesPage->allowUnsureFolderSelection( supportUnsure );
+ slotBuildSummary();
+ }
+
+ if ( ( mMode == AntiVirus ) && mVirusToolsUsed )
+ checkVirusRulesSelections();
+
+ setNextEnabled( mInfoPage, status );
+}
+
+
+void AntiSpamWizard::checkVirusRulesSelections()
+{
+ setFinishEnabled( mVirusRulesPage, anyVirusOptionChecked() );
+}
+
+
+void AntiSpamWizard::checkToolAvailability()
+{
+ // this can take some time to find the tools
+ KCursorSaver busy( KBusyPtr::busy() );
+
+ bool found = false;
+ for ( QValueListIterator<SpamToolConfig> it = mToolList.begin();
+ it != mToolList.end(); ++it ) {
+ QString text( i18n("Scanning for %1...").arg( (*it).getId() ) );
+ mInfoPage->setScanProgressText( text );
+ if ( (*it).isSpamTool() && (*it).isServerBased() ) {
+ // check the configured account for pattern in <server>
+ QString pattern = (*it).getServerPattern();
+ kdDebug(5006) << "Testing for server pattern:" << pattern << endl;
+
+ AccountManager* mgr = kmkernel->acctMgr();
+ KMAccount* account = mgr->first();
+ while ( account ) {
+ if ( account->type() == "pop" || account->type().contains( "imap" ) ) {
+ const NetworkAccount * n = dynamic_cast<const NetworkAccount*>( account );
+ if ( n && n->host().lower().contains( pattern.lower() ) ) {
+ mInfoPage->addAvailableTool( (*it).getVisibleName() );
+ found = true;
+ }
+ }
+ account = mgr->next();
+ }
+ }
+ else {
+ // check the availability of the application
+ KApplication::kApplication()->processEvents( 200 );
+ if ( !checkForProgram( (*it).getExecutable() ) ) {
+ mInfoPage->addAvailableTool( (*it).getVisibleName() );
+ found = true;
+ }
+ }
+ }
+ if ( found )
+ mInfoPage->setScanProgressText( ( mMode == AntiSpam )
+ ? i18n("Scanning for anti-spam tools finished.")
+ : i18n("Scanning for anti-virus tools finished.") );
+ else
+ mInfoPage->setScanProgressText( ( mMode == AntiSpam )
+ ? i18n("<p>No spam detection tools have been found. "
+ "Install your spam detection software and "
+ "re-run this wizard.</p>")
+ : i18n("Scanning complete. No anti-virus tools found.") );
+}
+
+
+void AntiSpamWizard::slotHelpClicked()
+{
+ if ( mMode == AntiSpam )
+ kapp->invokeHelp( "the-anti-spam-wizard", "kmail" );
+ else
+ kapp->invokeHelp( "the-anti-virus-wizard", "kmail" );
+}
+
+
+void AntiSpamWizard::slotBuildSummary()
+{
+ QString text;
+ QString newFilters;
+ QString replaceFilters;
+
+ if ( mMode == AntiVirus ) {
+ text = ""; // TODO add summary for the virus part
+ }
+ else { // AntiSpam mode
+ if ( mSpamRulesPage->markAsReadSelected() )
+ text = i18n( "<p>Messages classified as spam are marked as read." );
+ else
+ text = i18n( "<p>Messages classified as spam are not marked as read." );
+
+ if ( mSpamRulesPage->moveSpamSelected() )
+ text += i18n( "<br>Spam messages are moved into the folder named <i>" )
+ + mSpamRulesPage->selectedSpamFolderName() + "</i>.</p>";
+ else
+ text += i18n( "<br>Spam messages are not moved into a certain folder.</p>" );
+
+ for ( QValueListIterator<SpamToolConfig> it = mToolList.begin();
+ it != mToolList.end(); ++it ) {
+ if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ) &&
+ (*it).isSpamTool() && !(*it).isDetectionOnly() ) {
+ sortFilterOnExistance( (*it).getFilterName(), newFilters, replaceFilters );
+ }
+ }
+ sortFilterOnExistance( i18n( "Spam handling" ), newFilters, replaceFilters );
+
+ // The need for a andling of status "probably spam" depends on the tools chosen
+ if ( mSpamRulesPage->moveUnsureSelected() ) {
+ bool atLeastOneUnsurePattern = false;
+ for ( QValueListIterator<SpamToolConfig> it = mToolList.begin();
+ it != mToolList.end(); ++it ) {
+ if ( mInfoPage->isProgramSelected( (*it).getVisibleName() ) ) {
+ if ( (*it).isSpamTool() && (*it).hasTristateDetection())
+ atLeastOneUnsurePattern = true;
+ }
+ }
+ if ( atLeastOneUnsurePattern ) {
+ sortFilterOnExistance( i18n( "Semi spam (unsure) handling" ),
+ newFilters, replaceFilters );
+ text += i18n( "<p>The folder for messages classified as unsure (probably spam) is <i>" )
+ + mSpamRulesPage->selectedUnsureFolderName() + "</i>.</p>";
+ }
+ }
+
+ // Manual classification via toolbar icon / manually applied filter action
+ sortFilterOnExistance( i18n( "Classify as spam" ),
+ newFilters, replaceFilters );
+ sortFilterOnExistance( i18n( "Classify as NOT spam" ),
+ newFilters, replaceFilters );
+
+ // Show the filters in the summary
+ if ( !newFilters.isEmpty() )
+ text += i18n( "<p>The wizard will create the following filters:<ul>" )
+ + newFilters + "</ul></p>";
+ if ( !replaceFilters.isEmpty() )
+ text += i18n( "<p>The wizard will replace the following filters:<ul>" )
+ + replaceFilters + "</ul></p>";
+ }
+
+ mSummaryPage->setSummaryText( text );
+}
+
+
+int AntiSpamWizard::checkForProgram( const QString &executable )
+{
+ kdDebug(5006) << "Testing for executable:" << executable << endl;
+ KProcess process;
+ process << executable;
+ process.setUseShell( true );
+ process.start( KProcess::Block );
+ return process.exitStatus();
+}
+
+
+bool AntiSpamWizard::anyVirusOptionChecked()
+{
+ return ( mVirusRulesPage->moveRulesSelected()
+ || mVirusRulesPage->pipeRulesSelected() );
+}
+
+
+const QString AntiSpamWizard::uniqueNameFor( const QString & name )
+{
+ return KMKernel::self()->filterMgr()->createUniqueName( name );
+}
+
+
+void AntiSpamWizard::sortFilterOnExistance(
+ const QString & intendedFilterName,
+ QString & newFilters, QString & replaceFilters )
+{
+ if ( uniqueNameFor( intendedFilterName ) == intendedFilterName )
+ newFilters += "<li>" + intendedFilterName + "</li>";
+ else
+ replaceFilters += "<li>" + intendedFilterName + "</li>";
+}
+
+
+//---------------------------------------------------------------------------
+AntiSpamWizard::SpamToolConfig::SpamToolConfig( QString toolId,
+ int configVersion, int prio, QString name, QString exec,
+ QString url, QString filter, QString detection, QString spam, QString ham,
+ QString header, QString pattern, QString pattern2, QString serverPattern,
+ bool detectionOnly, bool regExp, bool bayesFilter, bool tristateDetection,
+ WizardMode type )
+ : mId( toolId ), mVersion( configVersion ), mPrio( prio ),
+ mVisibleName( name ), mExecutable( exec ), mWhatsThisText( url ),
+ mFilterName( filter ), mDetectCmd( detection ), mSpamCmd( spam ),
+ mHamCmd( ham ), mDetectionHeader( header ), mDetectionPattern( pattern ),
+ mDetectionPattern2( pattern2 ), mServerPattern( serverPattern ),
+ mDetectionOnly( detectionOnly ),
+ mUseRegExp( regExp ), mSupportsBayesFilter( bayesFilter ),
+ mSupportsUnsure( tristateDetection ), mType( type )
+{
+}
+
+
+bool AntiSpamWizard::SpamToolConfig::isServerBased() const
+{
+ return !mServerPattern.isEmpty();
+}
+
+
+//---------------------------------------------------------------------------
+AntiSpamWizard::ConfigReader::ConfigReader( WizardMode mode,
+ QValueList<SpamToolConfig> & configList )
+ : mToolList( configList ),
+ mMode( mode )
+{
+ if ( mMode == AntiSpam )
+ mConfig = new KConfig( "kmail.antispamrc", true );
+ else
+ mConfig = new KConfig( "kmail.antivirusrc", true );
+}
+
+AntiSpamWizard::ConfigReader::~ConfigReader( )
+{
+ delete mConfig;
+}
+
+
+void AntiSpamWizard::ConfigReader::readAndMergeConfig()
+{
+ QString groupName = ( mMode == AntiSpam )
+ ? QString("Spamtool #%1")
+ : QString("Virustool #%1");
+ // read the configuration from the global config file
+ mConfig->setReadDefaults( true );
+ KConfigGroup general( mConfig, "General" );
+ int registeredTools = general.readNumEntry( "tools", 0 );
+ for (int i = 1; i <= registeredTools; i++)
+ {
+ KConfigGroup toolConfig( mConfig, groupName.arg( i ) );
+ if( !toolConfig.readBoolEntry( "HeadersOnly", false ) )
+ mToolList.append( readToolConfig( toolConfig ) );
+ }
+
+ // read the configuration from the user config file
+ // and merge newer config data
+ mConfig->setReadDefaults( false );
+ KConfigGroup user_general( mConfig, "General" );
+ int user_registeredTools = user_general.readNumEntry( "tools", 0 );
+ for (int i = 1; i <= user_registeredTools; i++)
+ {
+ KConfigGroup toolConfig( mConfig, groupName.arg( i ) );
+ if( !toolConfig.readBoolEntry( "HeadersOnly", false ) )
+ mergeToolConfig( readToolConfig( toolConfig ) );
+ }
+ // Make sure to have add least one tool listed even when the
+ // config file was not found or whatever went wrong
+ // Currently only works for spam tools
+ if ( mMode == AntiSpam ) {
+ if ( registeredTools < 1 && user_registeredTools < 1 )
+ mToolList.append( createDummyConfig() );
+ sortToolList();
+ }
+}
+
+
+AntiSpamWizard::SpamToolConfig
+ AntiSpamWizard::ConfigReader::readToolConfig( KConfigGroup & configGroup )
+{
+ QString id = configGroup.readEntry( "Ident" );
+ int version = configGroup.readNumEntry( "Version" );
+#ifndef NDEBUG
+ kdDebug(5006) << "Found predefined tool: " << id << endl;
+ kdDebug(5006) << "With config version : " << version << endl;
+#endif
+ int prio = configGroup.readNumEntry( "Priority", 1 );
+ QString name = configGroup.readEntry( "VisibleName" );
+ QString executable = configGroup.readEntry( "Executable" );
+ QString url = configGroup.readEntry( "URL" );
+ QString filterName = configGroup.readEntry( "PipeFilterName" );
+ QString detectCmd = configGroup.readEntry( "PipeCmdDetect" );
+ QString spamCmd = configGroup.readEntry( "ExecCmdSpam" );
+ QString hamCmd = configGroup.readEntry( "ExecCmdHam" );
+ QString header = configGroup.readEntry( "DetectionHeader" );
+ QString pattern = configGroup.readEntry( "DetectionPattern" );
+ QString pattern2 = configGroup.readEntry( "DetectionPattern2" );
+ QString serverPattern = configGroup.readEntry( "ServerPattern" );
+ bool detectionOnly = configGroup.readBoolEntry( "DetectionOnly", false );
+ bool useRegExp = configGroup.readBoolEntry( "UseRegExp" );
+ bool supportsBayes = configGroup.readBoolEntry( "SupportsBayes", false );
+ bool supportsUnsure = configGroup.readBoolEntry( "SupportsUnsure", false );
+ return SpamToolConfig( id, version, prio, name, executable, url,
+ filterName, detectCmd, spamCmd, hamCmd,
+ header, pattern, pattern2, serverPattern,
+ detectionOnly, useRegExp,
+ supportsBayes, supportsUnsure, mMode );
+}
+
+
+AntiSpamWizard::SpamToolConfig AntiSpamWizard::ConfigReader::createDummyConfig()
+{
+ return SpamToolConfig( "spamassassin", 0, 1,
+ "SpamAssassin", "spamassassin -V",
+ "http://spamassassin.org", "SpamAssassin Check",
+ "spamassassin -L",
+ "sa-learn -L --spam --no-rebuild --single",
+ "sa-learn -L --ham --no-rebuild --single",
+ "X-Spam-Flag", "yes", "", "",
+ false, false, true, false, AntiSpam );
+}
+
+
+void AntiSpamWizard::ConfigReader::mergeToolConfig( AntiSpamWizard::SpamToolConfig config )
+{
+ bool found = false;
+ for ( QValueListIterator<SpamToolConfig> it = mToolList.begin();
+ it != mToolList.end(); ++it ) {
+#ifndef NDEBUG
+ kdDebug(5006) << "Check against tool: " << (*it).getId() << endl;
+ kdDebug(5006) << "Against version : " << (*it).getVersion() << endl;
+#endif
+ if ( (*it).getId() == config.getId() )
+ {
+ found = true;
+ if ( (*it).getVersion() < config.getVersion() )
+ {
+#ifndef NDEBUG
+ kdDebug(5006) << "Replacing config ..." << endl;
+#endif
+ mToolList.remove( it );
+ mToolList.append( config );
+ }
+ break;
+ }
+ }
+ if ( !found )
+ mToolList.append( config );
+}
+
+
+void AntiSpamWizard::ConfigReader::sortToolList()
+{
+ QValueList<SpamToolConfig> tmpList;
+ SpamToolConfig config;
+
+ while ( !mToolList.isEmpty() ) {
+ QValueListIterator<SpamToolConfig> highest;
+ int priority = 0; // ascending
+ for ( QValueListIterator<SpamToolConfig> it = mToolList.begin();
+ it != mToolList.end(); ++it ) {
+ if ( (*it).getPrio() > priority ) {
+ priority = (*it).getPrio();
+ highest = it;
+ }
+ }
+ config = (*highest);
+ tmpList.append( config );
+ mToolList.remove( highest );
+ }
+ for ( QValueListIterator<SpamToolConfig> it = tmpList.begin();
+ it != tmpList.end(); ++it ) {
+ mToolList.append( (*it) );
+ }
+}
+
+
+//---------------------------------------------------------------------------
+ASWizPage::ASWizPage( QWidget * parent, const char * name,
+ const QString *bannerName )
+ : QWidget( parent, name )
+{
+ QString banner = "kmwizard.png";
+ if ( bannerName && !bannerName->isEmpty() )
+ banner = *bannerName;
+
+ mLayout = new QHBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
+ mPixmap = new QPixmap( UserIcon(banner) );
+ mBannerLabel = new QLabel( this );
+ mBannerLabel->setPixmap( *mPixmap );
+ mBannerLabel->setScaledContents( false );
+ mBannerLabel->setFrameShape( QFrame::StyledPanel );
+ mBannerLabel->setFrameShadow( QFrame::Sunken );
+
+ mLayout->addWidget( mBannerLabel );
+ mLayout->addItem( new QSpacerItem( 5, 5, QSizePolicy::Minimum, QSizePolicy::Expanding ) );
+}
+
+
+//---------------------------------------------------------------------------
+ASWizInfoPage::ASWizInfoPage( AntiSpamWizard::WizardMode mode,
+ QWidget * parent, const char * name )
+ : ASWizPage( parent, name )
+{
+ QBoxLayout * layout = new QVBoxLayout( mLayout );
+
+ mIntroText = new QLabel( this );
+ mIntroText->setText(
+ ( mode == AntiSpamWizard::AntiSpam )
+ ? i18n(
+ "The wizard will search for any tools to do spam detection\n"
+ "and setup KMail to work with them."
+ )
+ : i18n(
+ "<p>Here you can get some assistance in setting up KMail's filter "
+ "rules to use some commonly-known anti-virus tools.</p>"
+ "<p>The wizard can detect those tools on your computer as "
+ "well as create filter rules to classify messages using these "
+ "tools and to separate messages containing viruses. "
+ "The wizard will not take any existing filter "
+ "rules into consideration: it will always append the new rules.</p>"
+ "<p><b>Warning:</b> As KMail appears to be frozen during the scan of the "
+ "messages for viruses, you may encounter problems with "
+ "the responsiveness of KMail because anti-virus tool "
+ "operations are usually time consuming; please consider "
+ "deleting the filter rules created by the wizard to get "
+ "back to the former behavior."
+ ) );
+ layout->addWidget( mIntroText );
+
+ mScanProgressText = new QLabel( this );
+ mScanProgressText->setText( "" ) ;
+ layout->addWidget( mScanProgressText );
+
+ mToolsList = new KListBox( this );
+ mToolsList->hide();
+ mToolsList->setSelectionMode( QListBox::Multi );
+ mToolsList->setRowMode( QListBox::FixedNumber );
+ mToolsList->setRowMode( 10 );
+ layout->addWidget( mToolsList );
+ connect( mToolsList, SIGNAL(selectionChanged()),
+ this, SLOT(processSelectionChange(void)) );
+
+ mSelectionHint = new QLabel( this );
+ mSelectionHint->setText( "" );
+ layout->addWidget( mSelectionHint );
+
+ layout->addStretch();
+}
+
+
+void ASWizInfoPage::setScanProgressText( const QString &toolName )
+{
+ mScanProgressText->setText( toolName );
+}
+
+
+void ASWizInfoPage::addAvailableTool( const QString &visibleName )
+{
+ QString listName = visibleName;
+ mToolsList->insertItem( listName );
+ if ( !mToolsList->isVisible() )
+ {
+ mToolsList->show();
+ mToolsList->setSelected( 0, true );
+ mSelectionHint->setText( i18n("<p>Please select the tools to be used "
+ "for the detection and go "
+ "to the next page.</p>") );
+ }
+}
+
+bool ASWizInfoPage::isProgramSelected( const QString &visibleName )
+{
+ QString listName = visibleName;
+ return mToolsList->isSelected( mToolsList->findItem( listName ) );
+}
+
+
+void ASWizInfoPage::processSelectionChange()
+{
+ emit selectionChanged();
+}
+
+
+//---------------------------------------------------------------------------
+ASWizSpamRulesPage::ASWizSpamRulesPage( QWidget * parent, const char * name,
+ KMFolderTree * mainFolderTree )
+ : ASWizPage( parent, name )
+{
+ QVBoxLayout *layout = new QVBoxLayout( mLayout );
+
+ mMarkRules = new QCheckBox( i18n("&Mark detected spam messages as read"), this );
+ QWhatsThis::add( mMarkRules,
+ i18n( "Mark messages which have been classified as spam as read.") );
+ layout->addWidget( mMarkRules);
+
+ mMoveSpamRules = new QCheckBox( i18n("Move &known spam to:"), this );
+ QWhatsThis::add( mMoveSpamRules,
+ i18n( "The default folder for spam messages is the trash folder, "
+ "but you may change that in the folder view below.") );
+ layout->addWidget( mMoveSpamRules );
+
+ mFolderReqForSpamFolder = new FolderRequester( this, mainFolderTree );
+ mFolderReqForSpamFolder->setFolder( "trash" );
+ mFolderReqForSpamFolder->setMustBeReadWrite( true );
+ mFolderReqForSpamFolder->setShowOutbox( false );
+ mFolderReqForSpamFolder->setShowImapFolders( false );
+
+ QHBoxLayout *hLayout1 = new QHBoxLayout( layout );
+ hLayout1->addSpacing( KDialog::spacingHint() * 3 );
+ hLayout1->addWidget( mFolderReqForSpamFolder );
+
+ mMoveUnsureRules = new QCheckBox( i18n("Move &probable spam to:"), this );
+ QWhatsThis::add( mMoveUnsureRules,
+ i18n( "The default folder is the inbox folder, but you may change that "
+ "in the folder view below.<p>"
+ "Not all tools support a classification as unsure. If you haven't "
+ "selected a capable tool, you can't select a folder as well.") );
+ layout->addWidget( mMoveUnsureRules );
+
+ mFolderReqForUnsureFolder = new FolderRequester( this, mainFolderTree );
+ mFolderReqForUnsureFolder->setFolder( "inbox" );
+ mFolderReqForUnsureFolder->setMustBeReadWrite( true );
+ mFolderReqForUnsureFolder->setShowOutbox( false );
+ mFolderReqForUnsureFolder->setShowImapFolders( false );
+
+ QHBoxLayout *hLayout2 = new QHBoxLayout( layout );
+ hLayout2->addSpacing( KDialog::spacingHint() * 3 );
+ hLayout2->addWidget( mFolderReqForUnsureFolder );
+
+ layout->addStretch();
+
+ connect( mMarkRules, SIGNAL(clicked()),
+ this, SLOT(processSelectionChange(void)) );
+ connect( mMoveSpamRules, SIGNAL(clicked()),
+ this, SLOT(processSelectionChange(void)) );
+ connect( mMoveUnsureRules, SIGNAL(clicked()),
+ this, SLOT(processSelectionChange(void)) );
+ connect( mFolderReqForSpamFolder, SIGNAL(folderChanged(KMFolder*)),
+ this, SLOT(processSelectionChange(KMFolder*)) );
+ connect( mFolderReqForUnsureFolder, SIGNAL(folderChanged(KMFolder*)),
+ this, SLOT(processSelectionChange(KMFolder*)) );
+
+ mMarkRules->setChecked( true );
+ mMoveSpamRules->setChecked( true );
+}
+
+
+bool ASWizSpamRulesPage::markAsReadSelected() const
+{
+ return mMarkRules->isChecked();
+}
+
+
+bool ASWizSpamRulesPage::moveSpamSelected() const
+{
+ return mMoveSpamRules->isChecked();
+}
+
+
+bool ASWizSpamRulesPage::moveUnsureSelected() const
+{
+ return mMoveUnsureRules->isChecked();
+}
+
+
+QString ASWizSpamRulesPage::selectedSpamFolderName() const
+{
+ QString name = "trash";
+ if ( mFolderReqForSpamFolder->folder() )
+ name = mFolderReqForSpamFolder->folder()->idString();
+ return name;
+}
+
+
+QString ASWizSpamRulesPage::selectedUnsureFolderName() const
+{
+ QString name = "inbox";
+ if ( mFolderReqForUnsureFolder->folder() )
+ name = mFolderReqForUnsureFolder->folder()->idString();
+ return name;
+}
+
+
+void ASWizSpamRulesPage::processSelectionChange()
+{
+ mFolderReqForSpamFolder->setEnabled( mMoveSpamRules->isChecked() );
+ mFolderReqForUnsureFolder->setEnabled( mMoveUnsureRules->isChecked() );
+ emit selectionChanged();
+}
+
+
+void ASWizSpamRulesPage::processSelectionChange( KMFolder* )
+{
+ processSelectionChange();
+}
+
+
+void ASWizSpamRulesPage::allowUnsureFolderSelection( bool enabled )
+{
+ mMoveUnsureRules->setEnabled( enabled );
+ mMoveUnsureRules->setShown( enabled );
+ mFolderReqForUnsureFolder->setEnabled( enabled );
+ mFolderReqForUnsureFolder->setShown( enabled );
+}
+
+
+//---------------------------------------------------------------------------
+ASWizVirusRulesPage::ASWizVirusRulesPage( QWidget * parent, const char * name,
+ KMFolderTree * mainFolderTree )
+ : ASWizPage( parent, name )
+{
+ QGridLayout *grid = new QGridLayout( mLayout, 5, 1, KDialog::spacingHint() );
+
+ mPipeRules = new QCheckBox( i18n("Check messages using the anti-virus tools"), this );
+ QWhatsThis::add( mPipeRules,
+ i18n( "Let the anti-virus tools check your messages. The wizard "
+ "will create appropriate filters. The messages are usually "
+ "marked by the tools so that following filters can react "
+ "on this and, for example, move virus messages to a special folder.") );
+ grid->addWidget( mPipeRules, 0, 0 );
+
+ mMoveRules = new QCheckBox( i18n("Move detected viral messages to the selected folder"), this );
+ QWhatsThis::add( mMoveRules,
+ i18n( "A filter to detect messages classified as virus-infected and to move "
+ "those messages into a predefined folder is created. The "
+ "default folder is the trash folder, but you may change that "
+ "in the folder view.") );
+ grid->addWidget( mMoveRules, 1, 0 );
+
+ mMarkRules = new QCheckBox( i18n("Additionally, mark detected viral messages as read"), this );
+ mMarkRules->setEnabled( false );
+ QWhatsThis::add( mMarkRules,
+ i18n( "Mark messages which have been classified as "
+ "virus-infected as read, as well as moving them "
+ "to the selected folder.") );
+ grid->addWidget( mMarkRules, 2, 0 );
+
+ QString s = "trash";
+ mFolderTree = new SimpleFolderTree( this, mainFolderTree, s, true );
+ grid->addWidget( mFolderTree, 3, 0 );
+
+ connect( mPipeRules, SIGNAL(clicked()),
+ this, SLOT(processSelectionChange(void)) );
+ connect( mMoveRules, SIGNAL(clicked()),
+ this, SLOT(processSelectionChange(void)) );
+ connect( mMarkRules, SIGNAL(clicked()),
+ this, SLOT(processSelectionChange(void)) );
+ connect( mMoveRules, SIGNAL( toggled( bool ) ),
+ mMarkRules, SLOT( setEnabled( bool ) ) );
+}
+
+bool ASWizVirusRulesPage::pipeRulesSelected() const
+{
+ return mPipeRules->isChecked();
+}
+
+
+bool ASWizVirusRulesPage::moveRulesSelected() const
+{
+ return mMoveRules->isChecked();
+}
+
+bool ASWizVirusRulesPage::markReadRulesSelected() const
+{
+ return mMarkRules->isChecked();
+}
+
+
+QString ASWizVirusRulesPage::selectedFolderName() const
+{
+ QString name = "trash";
+ if ( mFolderTree->folder() )
+ name = mFolderTree->folder()->idString();
+ return name;
+}
+
+void ASWizVirusRulesPage::processSelectionChange()
+{
+ emit selectionChanged();
+}
+
+
+//---------------------------------------------------------------------------
+ASWizSummaryPage::ASWizSummaryPage( QWidget * parent, const char * name )
+ : ASWizPage( parent, name )
+{
+ QBoxLayout * layout = new QVBoxLayout( mLayout );
+
+ mSummaryText = new QLabel( this );
+ layout->addWidget( mSummaryText );
+ layout->addStretch();
+}
+
+
+void ASWizSummaryPage::setSummaryText( const QString & text )
+{
+ mSummaryText->setText( text );
+}
+
+
+#include "antispamwizard.moc"
diff --git a/kmail/antispamwizard.h b/kmail/antispamwizard.h
new file mode 100644
index 00000000..745edae6
--- /dev/null
+++ b/kmail/antispamwizard.h
@@ -0,0 +1,398 @@
+/* -*- mode: C++ -*-
+ This file is part of KMail.
+ Copyright (c) 2003 Andreas Gungl <a.gungl@gmx.de>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+#ifndef KMAIL_ANTISPAMWIZARD_H
+#define KMAIL_ANTISPAMWIZARD_H
+
+#include <kconfig.h>
+#include <klistbox.h>
+#include <kwizard.h>
+
+#include <qcheckbox.h>
+#include <qdict.h>
+#include <qlayout.h>
+
+class KActionCollection;
+class KMFolder;
+class KMFolderTree;
+class QLabel;
+
+namespace KMail {
+
+ class SimpleFolderTree;
+ class FolderRequester;
+
+ class ASWizInfoPage;
+ class ASWizSpamRulesPage;
+ class ASWizVirusRulesPage;
+ class ASWizSummaryPage;
+
+ //---------------------------------------------------------------------------
+ /**
+ @short KMail anti-spam wizard.
+ @author Andreas Gungl <a.gungl@gmx.de>
+
+ The wizard helps to create filter rules to let KMail operate
+ with external anti-spam tools. The wizard tries to detect the
+ tools, but the user can overide the preselections.
+ Then the user can decide what funtionality shall be supported
+ by the created filter rules.
+ The wizard will append the created filter rules after the
+ last existing rule to keep possible conflicts with existing
+ filter configurations minimal.
+
+ Anti-virus support was added by Fred Emmott <fred87@users.sf.net>
+
+ The configuration for the tools to get checked and set up
+ is read from a config file. The structure of the file is as
+ following:
+ <pre>
+ [General]
+ tools=1
+
+ [Spamtool #1]
+ Ident=spamassassin
+ Version=0
+ Priority=1
+ VisibleName=&Spamassassin
+ Executable=spamassassin -V
+ URL=http://spamassassin.org
+ PipeFilterName=SpamAssassin Check
+ PipeCmdDetect=spamassassin -L
+ ExecCmdSpam=sa-learn --spam --no-rebuild --single
+ ExecCmdHam=sa-learn --ham --no-rebuild --single
+ DetectionHeader=X-Spam-Flag
+ DetectionPattern=yes
+ DetectionPattern2=
+ DetectionOnly=0
+ UseRegExp=0
+ SupportsBayes=1
+ SupportsUnsure=0
+ ServerSided=0
+ type=spam
+ </pre>
+ The name of the config file is kmail.antispamrc
+ and it's expected in the config dir of KDE.
+
+ */
+ class AntiSpamWizard : public KWizard
+ {
+ Q_OBJECT
+
+ public:
+ /** The wizard can be used for setting up anti-spam tools and for
+ setting up anti-virus tools.
+ */
+ enum WizardMode { AntiSpam, AntiVirus };
+
+ /** Constructor that needs to initialize from the main folder tree
+ of KMail.
+ @param mode The mode the wizard should run in.
+ @param parent The parent widget for the wizard.
+ @param mainFolderTree The main folder tree from which the folders
+ are copied to allow the selection of a spam folder in a tree
+ within one of the wizard pages.
+ */
+ AntiSpamWizard( WizardMode mode,
+ QWidget * parent, KMFolderTree * mainFolderTree );
+
+ protected:
+ /**
+ Instances of this class store the settings for one tool as read from
+ the config file. Visible name and What's this text can not get
+ translated!
+ */
+ class SpamToolConfig
+ {
+ public:
+ SpamToolConfig() {}
+ SpamToolConfig( QString toolId, int configVersion, int prio,
+ QString name, QString exec, QString url, QString filter,
+ QString detection, QString spam, QString ham,
+ QString header, QString pattern, QString pattern2,
+ QString serverPattern,
+ bool detectionOnly, bool regExp, bool bayesFilter,
+ bool tristateDetection, WizardMode type );
+
+ int getVersion() const { return mVersion; }
+ int getPrio() const { return mPrio; }
+ QString getId() const { return mId; }
+ QString getVisibleName() const { return mVisibleName; }
+ QString getExecutable() const { return mExecutable; }
+ QString getWhatsThisText() const { return mWhatsThisText; }
+ QString getFilterName() const { return mFilterName; }
+ QString getDetectCmd() const { return mDetectCmd; }
+ QString getSpamCmd() const { return mSpamCmd; }
+ QString getHamCmd() const { return mHamCmd; }
+ QString getDetectionHeader() const { return mDetectionHeader; }
+ QString getDetectionPattern() const { return mDetectionPattern; }
+ QString getDetectionPattern2() const { return mDetectionPattern2; }
+ QString getServerPattern() const { return mServerPattern; }
+ bool isServerBased() const;
+ bool isDetectionOnly() const { return mDetectionOnly; }
+ bool isUseRegExp() const { return mUseRegExp; }
+ bool useBayesFilter() const { return mSupportsBayesFilter; }
+ bool hasTristateDetection() const { return mSupportsUnsure; }
+ WizardMode getType() const { return mType; }
+ // convinience methods for types
+ bool isSpamTool() const { return ( mType == AntiSpam ); }
+ bool isVirusTool() const { return ( mType == AntiVirus ); }
+
+ private:
+ // used to identifiy configs for the same tool
+ QString mId;
+ // The version of the config data, used for merging and
+ // detecting newer configs
+ int mVersion;
+ // the priority of the tool in the list presented to the user
+ int mPrio;
+ // the name as shown by the checkbox in the dialog page
+ QString mVisibleName;
+ // the command to check the existance of the tool
+ QString mExecutable;
+ // the What's This help text (e.g. url for the tool)
+ QString mWhatsThisText;
+ // name for the created filter in the filter list
+ QString mFilterName;
+ // pipe through cmd used to detect spam messages
+ QString mDetectCmd;
+ // pipe through cmd to let the tool learn a spam message
+ QString mSpamCmd;
+ // pipe through cmd to let the tool learn a ham message
+ QString mHamCmd;
+ // by which header are messages marked as spam
+ QString mDetectionHeader;
+ // what header pattern is used to mark spam messages
+ QString mDetectionPattern;
+ // what header pattern is used to mark unsure messages
+ QString mDetectionPattern2;
+ // what header pattern is used in the account to check for a certain server
+ QString mServerPattern;
+ // filter cannot search actively but relies on pattern by regExp or contain rule
+ bool mDetectionOnly;
+ // filter searches for the pattern by regExp or contain rule
+ bool mUseRegExp;
+ // can the tool learn spam and ham, has it a bayesian algorithm
+ bool mSupportsBayesFilter;
+ // differentiate between ham, spam and a third "unsure" state
+ bool mSupportsUnsure;
+ // Is the tool AntiSpam or AntiVirus
+ WizardMode mType;
+ };
+
+ /**
+ Instances of this class control reading the configuration of the
+ anti-spam tools from global and user config files as well as the
+ merging of different config versions.
+ */
+ class ConfigReader
+ {
+ public:
+ ConfigReader( WizardMode mode,
+ QValueList<SpamToolConfig> & configList );
+ ~ConfigReader( );
+
+ QValueList<SpamToolConfig> & getToolList() { return mToolList; }
+
+ void readAndMergeConfig();
+
+ private:
+ QValueList<SpamToolConfig> & mToolList;
+ KConfig *mConfig;
+ WizardMode mMode;
+
+ SpamToolConfig readToolConfig( KConfigGroup & configGroup );
+ SpamToolConfig createDummyConfig();
+
+ void mergeToolConfig( SpamToolConfig config );
+ void sortToolList();
+ };
+
+ /** Evaluate the settings made and create the appropriate filter rules. */
+ void accept();
+
+ protected slots:
+ /** Modify the status of the wizard to reflect the selection of spam tools. */
+ void checkProgramsSelections();
+ /** Modify the status of the wizard to reflect the selected functionality. */
+ void checkVirusRulesSelections();
+ /** Check if the spam tools are available via the PATH */
+ void checkToolAvailability();
+ /** Show a help topic */
+ void slotHelpClicked();
+ /** Create the summary text based on the current settings */
+ void slotBuildSummary();
+
+ private:
+ /* Check for the availability of an executible along the PATH */
+ int checkForProgram( const QString &executable );
+ /* generic checks if any option in a page is checked */
+ bool anyVirusOptionChecked();
+ /* convenience method calling the appropriate filter manager method */
+ const QString uniqueNameFor( const QString & name );
+ /* convenience method to sort out new and existing filters */
+ void sortFilterOnExistance( const QString & intendedFilterName,
+ QString & newFilters,
+ QString & replaceFilters );
+
+ /* The pages in the wizard */
+ ASWizInfoPage * mInfoPage;
+ ASWizSpamRulesPage * mSpamRulesPage;
+ ASWizVirusRulesPage * mVirusRulesPage;
+ ASWizSummaryPage * mSummaryPage;
+
+ /* The configured tools and it's settings to be used in the wizard. */
+ QValueList<SpamToolConfig> mToolList;
+
+ /* Are any spam tools selected? */
+ bool mSpamToolsUsed;
+ /* Are any virus tools selected? */
+ bool mVirusToolsUsed;
+
+ WizardMode mMode;
+ };
+
+
+ //---------------------------------------------------------------------------
+ class ASWizPage : public QWidget
+ {
+ public:
+ ASWizPage( QWidget *parent, const char *name,
+ const QString *bannerName = 0);
+
+ protected:
+ QBoxLayout *mLayout;
+
+ private:
+ QPixmap *mPixmap;
+ QLabel *mBannerLabel;
+ };
+
+
+ //---------------------------------------------------------------------------
+ class ASWizInfoPage : public ASWizPage
+ {
+ Q_OBJECT
+
+ public:
+ ASWizInfoPage( AntiSpamWizard::WizardMode mode,
+ QWidget *parent, const char *name );
+
+ void setScanProgressText( const QString &toolName );
+ void addAvailableTool( const QString &visibleName );
+ bool isProgramSelected( const QString &visibleName );
+
+ private slots:
+ void processSelectionChange();
+
+ signals:
+ void selectionChanged();
+
+ private:
+ QLabel *mIntroText;
+ QLabel *mScanProgressText;
+ QLabel *mSelectionHint;
+ KListBox *mToolsList;
+ };
+
+ //---------------------------------------------------------------------------
+ class ASWizSpamRulesPage : public ASWizPage
+ {
+ Q_OBJECT
+
+ public:
+ ASWizSpamRulesPage( QWidget * parent, const char * name, KMFolderTree * mainFolderTree );
+
+ bool markAsReadSelected() const;
+ bool moveSpamSelected() const;
+ bool moveUnsureSelected() const;
+
+ QString selectedSpamFolderName() const;
+ QString selectedUnsureFolderName() const;
+
+ void allowUnsureFolderSelection( bool enabled );
+
+ private slots:
+ void processSelectionChange();
+ void processSelectionChange( KMFolder* );
+
+ signals:
+ void selectionChanged();
+
+ private:
+ QCheckBox * mMarkRules;
+ QCheckBox * mMoveSpamRules;
+ QCheckBox * mMoveUnsureRules;
+ FolderRequester *mFolderReqForSpamFolder;
+ FolderRequester *mFolderReqForUnsureFolder;
+ };
+
+ //-------------------------------------------------------------------------
+ class ASWizVirusRulesPage : public ASWizPage
+ {
+ Q_OBJECT
+
+ public:
+ ASWizVirusRulesPage( QWidget * parent, const char * name, KMFolderTree * mainFolderTree );
+
+ bool pipeRulesSelected() const;
+ bool moveRulesSelected() const;
+ bool markReadRulesSelected() const;
+
+ QString selectedFolderName() const;
+
+ private slots:
+ void processSelectionChange();
+ signals:
+ void selectionChanged();
+
+ private:
+ QCheckBox * mPipeRules;
+ QCheckBox * mMoveRules;
+ SimpleFolderTree *mFolderTree;
+ QCheckBox * mMarkRules;
+ };
+
+ //---------------------------------------------------------------------------
+ class ASWizSummaryPage : public ASWizPage
+ {
+ Q_OBJECT
+
+ public:
+ ASWizSummaryPage( QWidget * parent, const char * name );
+
+ void setSummaryText( const QString & text );
+
+ private:
+ QLabel * mSummaryText;
+ };
+
+
+} // namespace KMail
+
+#endif // KMAIL_ANTISPAMWIZARD_H
diff --git a/kmail/app_octetstream.cpp b/kmail/app_octetstream.cpp
new file mode 100644
index 00000000..73594645
--- /dev/null
+++ b/kmail/app_octetstream.cpp
@@ -0,0 +1,66 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ app_octetstream.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2004 Marc Mutz <mutz@kde.org>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "interfaces/bodypartformatter.h"
+
+#include <kdepimmacros.h>
+
+namespace {
+
+ class Formatter : public KMail::Interface::BodyPartFormatter {
+ public:
+ Result format( KMail::Interface::BodyPart *, KMail::HtmlWriter * ) const { return AsIcon; }
+ };
+
+ class Plugin : public KMail::Interface::BodyPartFormatterPlugin {
+ public:
+ const KMail::Interface::BodyPartFormatter * bodyPartFormatter( int idx ) const {
+ return idx == 0 ? new Formatter() : 0 ;
+ }
+ const char * type( int idx ) const {
+ return idx == 0 ? "application" : 0 ;
+ }
+ const char * subtype( int idx ) const {
+ return idx == 0 ? "octet-stream" : 0 ;
+ }
+
+ const KMail::Interface::BodyPartURLHandler * urlHandler( int ) const { return 0; }
+ };
+
+}
+
+extern "C"
+KDE_EXPORT KMail::Interface::BodyPartFormatterPlugin *
+libkmail_bodypartformatter_application_octetstream_create_bodypart_formatter_plugin() {
+ return new Plugin();
+}
+
diff --git a/kmail/application_octetstream.desktop b/kmail/application_octetstream.desktop
new file mode 100644
index 00000000..a0272b6b
--- /dev/null
+++ b/kmail/application_octetstream.desktop
@@ -0,0 +1,85 @@
+[Misc]
+Name=Application Octetstream
+Name[af]=Application/Octet-stream
+Name[ca]=Aplicació Octetstream
+Name[cs]=Aplikace octetstream
+Name[da]=Program oktetstrøm
+Name[eo]=Aplikaĵa Bitokfluo
+Name[es]=Aplicación en flujo de octetos
+Name[eu]=Aplikazioa/zortzikote-jarioa
+Name[fa]=Octetstream کاربرد
+Name[fi]=Octetstream-sovellus
+Name[fr]=Application (flux d'octets)
+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
+Name[nb]=Program Octetstrøm
+Name[ne]=अक्टेस्ट्रिम अनुप्रयोग
+Name[nl]=Applicatie octetstroom
+Name[nn]=Oktettstraum frå program
+Name[pl]=Dane binarne
+Name[pt]=Aplicação Sequência Binária
+Name[pt_BR]=Aplicativo Octetstream
+Name[ru]=Бинарный поток приложения
+Name[sl]=Programski Octetstream
+Name[sr]=Апликација Octetstream
+Name[sr@Latn]=Aplikacija Octetstream
+Name[sv]=Program-oktettström
+Name[ta]=பயன்பாட்டு எண்மம்
+Name[tg]=Миқдори зиёди бинарии барномот
+Name[tr]=Uygulama Sekizli Akışı
+Name[zh_CN]=应用程序 Octetstream
+Comment=A bodypart formatter plugin for application/octet-stream
+Comment[af]='n Inprop module wat die lyf gedeelte vir 'application/octet-stream' formateer
+Comment[bg]=Приставка за форматиране на двоични данни
+Comment[bs]=Dodatak za formatiranje tijela poruke za application/octet-stream
+Comment[ca]=Un endollable formatador del cos per application/octet-stream
+Comment[cs]=Modul formátovače těla pro application/octet-stream
+Comment[da]=Et bodypart formateringsplugin for application/octet-stream
+Comment[de]=Ein Bodypart-Formatierungsmodul für application/octet-stream
+Comment[el]=Ένας μορφοποιητής για application/octet-stream
+Comment[es]=Un accesorio de formato para el cuerpo de application/octet-stream
+Comment[et]=Põhiosa vormindamisplugin (MIME tüübile application/octet-stream)
+Comment[eu]=Gorputz-zati formateatzaile plugin bat aplikazio/zortikote-jarioentzat
+Comment[fa]=وصلۀ قالب‌دهندۀ بخش بدنه برای کاربرد/octet-stream
+Comment[fi]=Muokkainliitännäinen application/octet-stream-muodolle
+Comment[fr]=Un module formateur de corps pour application (flux d'octets)
+Comment[fy]=In opmaakplugin foar application/octet-stream
+Comment[gl]=Unha extensión formateadora do corpo para aplicacións/octet-stream
+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
+Comment[ms]=Plugin pemformat bahagian isi untuk alpikasi/aliran oktet
+Comment[nb]=Et programtillegg for meldingstekst-formatering for application/octet-stream
+Comment[nds]=En Hööftdeel-Formateermoduul för application/octet-stream
+Comment[ne]=अनुप्रयोग/अक्टेट-स्ट्रिमका लागि एउटा मूख्यभाग ढाँचाकार
+Comment[nl]=Een opmaakplugin voor application/octet-stream
+Comment[nn]=Eit programtillegg for formatering av meldingstekst i application/octet-stream
+Comment[pl]=Wtyczka formatowania danych typu application/octet-stream
+Comment[pt]=Um 'plugin' de formatação para application/octet-stream
+Comment[pt_BR]=Um plug-in formatador de componente para application/octet-stream
+Comment[ru]=Форматирование application/octet-stream
+Comment[sk]=Formátovač tela pre MIME typ application/octet-stream
+Comment[sl]=Oblikovni vstavek za application/octet-stream
+Comment[sr]=Прукључак форматера тела за application/octet-stream
+Comment[sr@Latn]=Pruključak formatera tela za application/octet-stream
+Comment[sv]=Ett insticksprogram för brevtextformatering av application/octet-stream
+Comment[ta]=பயன்பாடு அல்லது எண்மத்திற்கான அங்க அமைப்பு சொருகி
+Comment[tg]=Модули ба андозадарории application/octet-stream
+Comment[tr]=application/octet-stream için bir gövde biçimleyici eklentisi
+Comment[uk]=Втулок для форматування application/octet-stream
+Comment[zh_CN]=application/octet-stream 的格式化插件
+
+[Plugin]
+Type=application/octet-stream
+X-KDE-Library=libkmail_bodypartformatter_application_octetstream
diff --git a/kmail/attachmentcollector.cpp b/kmail/attachmentcollector.cpp
new file mode 100644
index 00000000..7114242c
--- /dev/null
+++ b/kmail/attachmentcollector.cpp
@@ -0,0 +1,85 @@
+/* -*- c++ -*-
+ attachmentcollector.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2004 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "attachmentcollector.h"
+
+#include "partNode.h"
+
+static bool isInSkipList( partNode * ) {
+ return false;
+}
+
+static bool isInExclusionList( const partNode * node ) {
+ if ( !node )
+ return true;
+
+ switch ( node->type() ) {
+ case DwMime::kTypeApplication:
+ switch ( node->subType() ) {
+ case DwMime::kSubtypePkcs7Mime:
+ case DwMime::kSubtypePkcs7Signature:
+ case DwMime::kSubtypePgpSignature:
+ case DwMime::kSubtypePgpEncrypted:
+ return true;
+ }
+ break;
+ case DwMime::kTypeMultipart:
+ return true;
+ }
+ return false;
+}
+
+
+void KMail::AttachmentCollector::collectAttachmentsFrom( partNode * node ) {
+ while ( node ) {
+ if ( node->isFirstTextPart() ) {
+ node = node->next();
+ continue;
+ }
+ if ( isInSkipList( node ) ) {
+ node = node->next( false ); // skip even the children
+ continue;
+ }
+ if ( isInExclusionList( node ) ) {
+ node = node->next();
+ continue;
+ }
+
+ if ( node->isHeuristicalAttachment() ) {
+ mAttachments.push_back( node );
+ node = node->next( false ); // just make double sure
+ continue;
+ }
+
+ node = node->next();
+ }
+}
+
diff --git a/kmail/attachmentcollector.h b/kmail/attachmentcollector.h
new file mode 100644
index 00000000..52454e8d
--- /dev/null
+++ b/kmail/attachmentcollector.h
@@ -0,0 +1,75 @@
+/* -*- c++ -*-
+ attachmentcollector.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2004 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_ATTACHMENTCOLLECTOR_H__
+#define __KMAIL_ATTACHMENTCOLLECTOR_H__
+
+#include <vector>
+
+class partNode;
+
+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;
+ }
+
+ void collectAttachmentsFrom( partNode * node );
+
+ const std::vector<partNode*> & attachments() const { return mAttachments; }
+
+ private:
+ std::vector<partNode*> mAttachments;
+ bool mDiveIntoEncryptions : 1;
+ bool mDiveIntoSignatures : 1;
+ bool mDiveIntoMessages : 1;
+
+ private: // disabled
+ AttachmentCollector( const AttachmentCollector & );
+ void operator=( const AttachmentCollector & );
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_BODYPARTFORMATTER_H__
diff --git a/kmail/attachmentlistview.cpp b/kmail/attachmentlistview.cpp
new file mode 100644
index 00000000..b7c8bdd2
--- /dev/null
+++ b/kmail/attachmentlistview.cpp
@@ -0,0 +1,146 @@
+/* -*- c++ -*-
+ attachmentlistview.cpp
+
+ KMail, the KDE mail client.
+ Copyright (c) 2003 Ingo Kloecker <kloecker@kde.org>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License,
+ version 2.0, as published by the Free Software Foundation.
+ 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, US
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// my header file
+#include "attachmentlistview.h"
+
+// other KMail headers
+#include "kmmsgbase.h"
+#include "kmfolder.h"
+#include "kmcommands.h"
+#include "kmmsgdict.h"
+#include "composer.h"
+
+// other module headers
+#include <maillistdrag.h>
+using KPIM::MailListDrag;
+
+// other KDE headers
+#include <kurldrag.h>
+
+// other Qt headers
+#include <qevent.h>
+#include <qcstring.h>
+#include <qbuffer.h>
+#include <qptrlist.h>
+#include <qdatastream.h>
+#include <qstring.h>
+
+// other headers (none)
+
+
+namespace KMail {
+
+AttachmentListView::AttachmentListView( KMail::Composer * composer,
+ QWidget* parent,
+ const char* name )
+ : KListView( parent, name ),
+ mComposer( composer )
+{
+ setAcceptDrops( true );
+ setDragEnabled( true );
+}
+
+//-----------------------------------------------------------------------------
+
+AttachmentListView::~AttachmentListView()
+{
+}
+
+//-----------------------------------------------------------------------------
+
+void AttachmentListView::contentsDragEnterEvent( QDragEnterEvent* e )
+{
+ if( e->provides( MailListDrag::format() ) || KURLDrag::canDecode( e ) )
+ e->accept( true );
+ else
+ KListView::dragEnterEvent( e );
+}
+
+//-----------------------------------------------------------------------------
+
+void AttachmentListView::contentsDragMoveEvent( QDragMoveEvent* e )
+{
+ if( e->provides( MailListDrag::format() ) || KURLDrag::canDecode( e ) )
+ e->accept( true );
+ else
+ KListView::dragMoveEvent( e );
+}
+
+//-----------------------------------------------------------------------------
+
+void AttachmentListView::contentsDropEvent( QDropEvent* e )
+{
+ if( e->provides( MailListDrag::format() ) ) {
+ // Decode the list of serial numbers stored as the drag data
+ QByteArray serNums;
+ MailListDrag::decode( e, serNums );
+ QBuffer serNumBuffer( serNums );
+ serNumBuffer.open( IO_ReadOnly );
+ QDataStream serNumStream( &serNumBuffer );
+ Q_UINT32 serNum;
+ KMFolder *folder = 0;
+ int idx;
+ QPtrList<KMMsgBase> messageList;
+ while( !serNumStream.atEnd() ) {
+ KMMsgBase *msgBase = 0;
+ serNumStream >> serNum;
+ KMMsgDict::instance()->getLocation( serNum, &folder, &idx );
+ if( folder )
+ msgBase = folder->getMsgBase( idx );
+ if( msgBase )
+ messageList.append( msgBase );
+ }
+ serNumBuffer.close();
+ uint identity = folder ? folder->identity() : 0;
+ KMCommand *command = new KMForwardAttachedCommand( mComposer, messageList,
+ identity, mComposer );
+ command->start();
+ }
+ else if( KURLDrag::canDecode( e ) ) {
+ KURL::List urlList;
+ if( KURLDrag::decode( e, urlList ) ) {
+ for( KURL::List::Iterator it = urlList.begin();
+ it != urlList.end(); ++it ) {
+ mComposer->addAttach( *it );
+ }
+ }
+ }
+ else {
+ KListView::dropEvent( e );
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+void AttachmentListView::keyPressEvent( QKeyEvent * e )
+{
+ if ( e->key() == Key_Delete ) {
+ emit attachmentDeleted();
+ }
+}
+
+/*virtual*/
+void AttachmentListView::startDrag()
+{
+ emit dragStarted();
+}
+
+} // namespace KMail
+
+#include "attachmentlistview.moc"
diff --git a/kmail/attachmentlistview.h b/kmail/attachmentlistview.h
new file mode 100644
index 00000000..23ee6713
--- /dev/null
+++ b/kmail/attachmentlistview.h
@@ -0,0 +1,59 @@
+/* -*- c++ -*-
+ attachmentlistview.h
+
+ KMail, the KDE mail client.
+ Copyright (c) 2003 Ingo Kloecker <kloecker@kde.org>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License,
+ version 2.0, as published by the Free Software Foundation.
+ 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, US
+*/
+
+
+#ifndef _KMAIL_ATTACHMENTLISTVIEW_H_
+#define _KMAIL_ATTACHMENTLISTVIEW_H_
+
+#include <klistview.h>
+
+class QDragEnterEvent;
+class QDragMoveEvent;
+class QDropEvent;
+class KMComposeWin;
+
+namespace KMail {
+
+class Composer;
+
+class AttachmentListView : public KListView
+{
+ Q_OBJECT
+public:
+ AttachmentListView( KMail::Composer * composer = 0, QWidget* parent = 0,
+ const char* name = 0 );
+ virtual ~AttachmentListView();
+
+ /** Drag and drop methods */
+ void contentsDragEnterEvent( QDragEnterEvent* );
+ void contentsDragMoveEvent( QDragMoveEvent* );
+ void contentsDropEvent( QDropEvent* );
+
+protected:
+ virtual void keyPressEvent( QKeyEvent * e );
+ virtual void startDrag();
+
+private:
+ KMail::Composer * mComposer;
+
+signals:
+ void attachmentDeleted();
+ void dragStarted();
+
+};
+
+} // namespace KMail
+
+#endif // _KMAIL_ATTACHMENTLISTVIEW_H_
+
diff --git a/kmail/attachmentstrategy.cpp b/kmail/attachmentstrategy.cpp
new file mode 100644
index 00000000..37584d84
--- /dev/null
+++ b/kmail/attachmentstrategy.cpp
@@ -0,0 +1,209 @@
+/* -*- c++ -*-
+ attachmentstrategy.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "attachmentstrategy.h"
+
+#include "partNode.h"
+#include "kmmsgpart.h"
+
+#include <qstring.h>
+
+#include <kdebug.h>
+
+
+namespace KMail {
+
+
+ //
+ // IconicAttachmentStrategy:
+ // show everything but the first text/plain body as icons
+ //
+
+ class IconicAttachmentStrategy : public AttachmentStrategy {
+ friend class ::KMail::AttachmentStrategy;
+ protected:
+ IconicAttachmentStrategy() : AttachmentStrategy() {}
+ virtual ~IconicAttachmentStrategy() {}
+
+ public:
+ const char * name() const { return "iconic"; }
+ const AttachmentStrategy * next() const { return smart(); }
+ const AttachmentStrategy * prev() const { return hidden(); }
+
+ bool inlineNestedMessages() const { return false; }
+ Display defaultDisplay( const partNode * ) const { return AsIcon; }
+ };
+
+ //
+ // SmartAttachmentStrategy:
+ // in addition to Iconic, show all body parts
+ // with content-disposition == "inline" and
+ // all text parts without a filename or name parameter inline
+ //
+
+ class SmartAttachmentStrategy : public AttachmentStrategy {
+ friend class ::KMail::AttachmentStrategy;
+ protected:
+ SmartAttachmentStrategy() : AttachmentStrategy() {}
+ virtual ~SmartAttachmentStrategy() {}
+
+ public:
+ const char * name() const { return "smart"; }
+ const AttachmentStrategy * next() const { return inlined(); }
+ const AttachmentStrategy * prev() const { return iconic(); }
+
+ 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;
+ }
+ };
+
+ //
+ // InlinedAttachmentStrategy:
+ // show everything possible inline
+ //
+
+ class InlinedAttachmentStrategy : public AttachmentStrategy {
+ friend class ::KMail::AttachmentStrategy;
+ protected:
+ InlinedAttachmentStrategy() : AttachmentStrategy() {}
+ virtual ~InlinedAttachmentStrategy() {}
+
+ public:
+ const char * name() const { return "inlined"; }
+ const AttachmentStrategy * next() const { return hidden(); }
+ const AttachmentStrategy * prev() const { return smart(); }
+
+ bool inlineNestedMessages() const { return true; }
+ Display defaultDisplay( const partNode * ) const { return Inline; }
+ };
+
+ //
+ // HiddenAttachmentStrategy
+ // show nothing except the first text/plain body part _at all_
+ //
+
+ class HiddenAttachmentStrategy : public AttachmentStrategy {
+ friend class ::KMail::AttachmentStrategy;
+ protected:
+ HiddenAttachmentStrategy() : AttachmentStrategy() {}
+ virtual ~HiddenAttachmentStrategy() {}
+
+ public:
+ const char * name() const { return "hidden"; }
+ const AttachmentStrategy * next() const { return iconic(); }
+ const AttachmentStrategy * prev() const { return inlined(); }
+
+ bool inlineNestedMessages() const { return false; }
+ Display defaultDisplay( const partNode * ) const { return None; }
+ };
+
+
+ //
+ // AttachmentStrategy abstract base:
+ //
+
+ AttachmentStrategy::AttachmentStrategy() {
+
+ }
+
+ AttachmentStrategy::~AttachmentStrategy() {
+
+ }
+
+ const AttachmentStrategy * AttachmentStrategy::create( Type type ) {
+ switch ( type ) {
+ case Iconic: return iconic();
+ case Smart: return smart();
+ case Inlined: return inlined();
+ case Hidden: return hidden();
+ }
+ kdFatal( 5006 ) << "AttachmentStrategy::create(): Unknown attachment startegy ( type == "
+ << (int)type << " ) requested!" << endl;
+ return 0; // make compiler happy
+ }
+
+ const AttachmentStrategy * AttachmentStrategy::create( const QString & type ) {
+ QString lowerType = type.lower();
+ if ( lowerType == "iconic" ) return iconic();
+ //if ( lowerType == "smart" ) return smart(); // not needed, see below
+ if ( lowerType == "inlined" ) return inlined();
+ if ( lowerType == "hidden" ) return hidden();
+ // don't kdFatal here, b/c the strings are user-provided
+ // (KConfig), so fail gracefully to the default:
+ return smart();
+ }
+
+ static const AttachmentStrategy * iconicStrategy = 0;
+ static const AttachmentStrategy * smartStrategy = 0;
+ static const AttachmentStrategy * inlinedStrategy = 0;
+ static const AttachmentStrategy * hiddenStrategy = 0;
+
+ const AttachmentStrategy * AttachmentStrategy::iconic() {
+ if ( !iconicStrategy )
+ iconicStrategy = new IconicAttachmentStrategy();
+ return iconicStrategy;
+ }
+
+ const AttachmentStrategy * AttachmentStrategy::smart() {
+ if ( !smartStrategy )
+ smartStrategy = new SmartAttachmentStrategy();
+ return smartStrategy;
+ }
+
+ const AttachmentStrategy * AttachmentStrategy::inlined() {
+ if ( !inlinedStrategy )
+ inlinedStrategy = new InlinedAttachmentStrategy();
+ return inlinedStrategy;
+ }
+
+ const AttachmentStrategy * AttachmentStrategy::hidden() {
+ if ( !hiddenStrategy )
+ hiddenStrategy = new HiddenAttachmentStrategy();
+ return hiddenStrategy;
+ }
+
+} // namespace KMail
diff --git a/kmail/attachmentstrategy.h b/kmail/attachmentstrategy.h
new file mode 100644
index 00000000..bcd29cfd
--- /dev/null
+++ b/kmail/attachmentstrategy.h
@@ -0,0 +1,79 @@
+/* -*- c++ -*-
+ attachmentstrategy.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_ATTACHMENTSTRATEGY_H__
+#define __KMAIL_ATTACHMENTSTRATEGY_H__
+
+class QString;
+class partNode;
+
+namespace KMail {
+
+ class AttachmentStrategy {
+ protected:
+ AttachmentStrategy();
+ virtual ~AttachmentStrategy();
+
+ public:
+ //
+ // Factory methods:
+ //
+ enum Type { Iconic, Smart, Inlined, Hidden };
+
+ static const AttachmentStrategy * create( Type type );
+ static const AttachmentStrategy * create( const QString & type );
+
+ static const AttachmentStrategy * iconic();
+ static const AttachmentStrategy * smart();
+ static const AttachmentStrategy * inlined();
+ static const AttachmentStrategy * hidden();
+
+ //
+ // Navigation methods:
+ //
+
+ virtual const char * name() const = 0;
+ virtual const AttachmentStrategy * next() const = 0;
+ virtual const AttachmentStrategy * prev() const = 0;
+
+ //
+ // Bahavioural:
+ //
+
+ enum Display { None, AsIcon, Inline };
+
+ virtual bool inlineNestedMessages() const = 0;
+ virtual Display defaultDisplay( const partNode * node ) const = 0;
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_ATTACHMENTSTRATEGY_H__
diff --git a/kmail/avscripts/Makefile.am b/kmail/avscripts/Makefile.am
new file mode 100644
index 00000000..1f4bfe02
--- /dev/null
+++ b/kmail/avscripts/Makefile.am
@@ -0,0 +1,2 @@
+SUBDIRS = .
+bin_SCRIPTS = kmail_clamav.sh kmail_sav.sh kmail_fprot.sh kmail_antivir.sh
diff --git a/kmail/avscripts/kmail_antivir.sh b/kmail/avscripts/kmail_antivir.sh
new file mode 100755
index 00000000..183431b5
--- /dev/null
+++ b/kmail/avscripts/kmail_antivir.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+#
+# This file is part of KMail.
+# Copyright (c) 2004 Hendrik Muhs <Hendrik.Muhs@web.de>
+#
+# 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 Qt library by Trolltech AS, Norway (or with modified versions
+# of Qt that use the same license as Qt), and distribute linked
+# combinations including the two. You must obey the GNU General
+# Public License in all respects for all of the code used other than
+# Qt. If you modify this file, you may extend this exception to
+# your version of the file, but you are not obligated to do so. If
+# you do not wish to do so, delete this exception statement from
+# your version.
+#
+TEMPFILE=`mktemp`
+if [ $? != 0 ] ; then
+ TEMPFILE=`mktemp /tmp/kmail.XXXXXX`
+fi
+export TEMPFILE
+cat > $TEMPFILE
+if antivir --scan-in-archive --scan-in-mbox $TEMPFILE | grep -q ALERT; then
+echo "X-Virus-Flag: yes"
+else
+echo "X-Virus-Flag: no"
+fi
+cat $TEMPFILE
+rm $TEMPFILE
diff --git a/kmail/avscripts/kmail_clamav.sh b/kmail/avscripts/kmail_clamav.sh
new file mode 100755
index 00000000..b6227b39
--- /dev/null
+++ b/kmail/avscripts/kmail_clamav.sh
@@ -0,0 +1,53 @@
+#!/bin/sh
+#
+# This file is part of KMail.
+# Copyright (c) 2004 Fred Emmott <fred87@users.sf.net>
+#
+# 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 Qt library by Trolltech AS, Norway (or with modified versions
+# of Qt that use the same license as Qt), and distribute linked
+# combinations including the two. You must obey the GNU General
+# Public License in all respects for all of the code used other than
+# Qt. If you modify this file, you may extend this exception to
+# your version of the file, but you are not obligated to do so. If
+# you do not wish to do so, delete this exception statement from
+# your version.
+#
+TEMPFILE=`mktemp`
+if [ $? != 0 ] ; then
+ TEMPFILE=`mktemp /tmp/kmail.XXXXXX`
+fi
+export TEMPFILE
+cat > $TEMPFILE
+
+# check for a running daemon
+if [ "`ps -eo comm|grep clamd`" = "clamd" ]; then
+ chmod a+r $TEMPFILE
+ CLAMCOMANDO="clamdscan --stdout --no-summary "
+else
+ CLAMCOMANDO="clamscan --stdout --no-summary"
+fi
+
+# analyze the message
+if $CLAMCOMANDO $TEMPFILE | grep -q FOUND; then
+ echo "X-Virus-Flag: yes"
+else
+ echo "X-Virus-Flag: no"
+fi
+
+cat $TEMPFILE
+rm $TEMPFILE
diff --git a/kmail/avscripts/kmail_fprot.sh b/kmail/avscripts/kmail_fprot.sh
new file mode 100755
index 00000000..15698e94
--- /dev/null
+++ b/kmail/avscripts/kmail_fprot.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+#
+# This file is part of KMail.
+# Copyright (c) 2004 Fred Emmott <fred87@users.sf.net>
+#
+# 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 Qt library by Trolltech AS, Norway (or with modified versions
+# of Qt that use the same license as Qt), and distribute linked
+# combinations including the two. You must obey the GNU General
+# Public License in all respects for all of the code used other than
+# Qt. If you modify this file, you may extend this exception to
+# your version of the file, but you are not obligated to do so. If
+# you do not wish to do so, delete this exception statement from
+# your version.
+#
+TEMPFILE=`mktemp`
+if [ $? != 0 ] ; then
+ TEMPFILE=`mktemp /tmp/kmail.XXXXXX`
+fi
+export TEMPFILE
+cat > $TEMPFILE
+f-prot -archive 3 $TEMPFILE > /dev/null
+RC=$?
+if [ $RC -eq 0 ] ; then
+ echo "X-Virus-Flag: no"
+else
+ case $RC in
+ 1 ) DESC="no - Unrecoverable error" ;;
+ 2 ) DESC="no - Selftest failed" ;;
+ 3 ) DESC="yes - Virus-infected object found" ;;
+ 4 ) DESC="no - Reserved" ;;
+ 5 ) DESC="no - Abnormal termination" ;;
+ 6 ) DESC="no - Virus was removed" ;;
+ 7 ) DESC="no - Error, out of memory" ;;
+ 8 ) DESC="yes - Something suspicious found" ;;
+ esac
+ echo "X-Virus-Flag: $DESC"
+fi
+
+cat $TEMPFILE
+rm $TEMPFILE
diff --git a/kmail/avscripts/kmail_sav.sh b/kmail/avscripts/kmail_sav.sh
new file mode 100755
index 00000000..dbb794bb
--- /dev/null
+++ b/kmail/avscripts/kmail_sav.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+#
+# This file is part of KMail.
+# Copyright (c) 2004 Fred Emmott <fred87@users.sf.net>
+#
+# 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 Qt library by Trolltech AS, Norway (or with modified versions
+# of Qt that use the same license as Qt), and distribute linked
+# combinations including the two. You must obey the GNU General
+# Public License in all respects for all of the code used other than
+# Qt. If you modify this file, you may extend this exception to
+# your version of the file, but you are not obligated to do so. If
+# you do not wish to do so, delete this exception statement from
+# your version.
+#
+TEMPFILE=`mktemp`
+if [ $? != 0 ] ; then
+ TEMPFILE=`mktemp /tmp/kmail.XXXXXX`
+fi
+export TEMPFILE
+cat > $TEMPFILE
+if sweep -ss -mime $TEMPFILE | grep -q found; then
+echo "X-Virus-Flag: yes"
+else
+echo "X-Virus-Flag: no"
+fi
+cat $TEMPFILE
+rm $TEMPFILE
diff --git a/kmail/bodypartformatter.cpp b/kmail/bodypartformatter.cpp
new file mode 100644
index 00000000..efe12f67
--- /dev/null
+++ b/kmail/bodypartformatter.cpp
@@ -0,0 +1,334 @@
+/* -*- c++ -*-
+ bodypartformatter.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "bodypartformatter.h"
+#include "bodypartformatterfactory_p.h"
+#include "interfaces/bodypartformatter.h"
+
+#include "objecttreeparser.h"
+#include "partNode.h"
+
+#include <mimelib/enum.h>
+#include <mimelib/string.h>
+#include <mimelib/utility.h>
+
+#include <kdebug.h>
+#include <kasciistricmp.h>
+
+namespace {
+ class AnyTypeBodyPartFormatter
+ : public KMail::BodyPartFormatter,
+ public KMail::Interface::BodyPartFormatter {
+ static const AnyTypeBodyPartFormatter * self;
+ public:
+ Result format( KMail::Interface::BodyPart *, KMail::HtmlWriter * ) const {
+ kdDebug(5006) << "AnyTypeBodyPartFormatter::format() acting as a KMail::Interface::BodyPartFormatter!" << endl;
+ return AsIcon;
+ }
+
+ bool process( KMail::ObjectTreeParser *, partNode *, KMail::ProcessResult & result ) const {
+ result.setNeverDisplayInline( true );
+ return false;
+ }
+ static const KMail::BodyPartFormatter * create() {
+ if ( !self )
+ self = new AnyTypeBodyPartFormatter();
+ return self;
+ }
+ };
+
+ const AnyTypeBodyPartFormatter * AnyTypeBodyPartFormatter::self = 0;
+
+
+ class ImageTypeBodyPartFormatter : public KMail::BodyPartFormatter {
+ static const ImageTypeBodyPartFormatter * self;
+ public:
+ bool process( KMail::ObjectTreeParser *, partNode *, KMail::ProcessResult & result ) const {
+ result.setIsImage( true );
+ return false;
+ }
+ static const KMail::BodyPartFormatter * create() {
+ if ( !self )
+ self = new ImageTypeBodyPartFormatter();
+ return self;
+ }
+ };
+
+ const ImageTypeBodyPartFormatter * ImageTypeBodyPartFormatter::self = 0;
+
+#define CREATE_BODY_PART_FORMATTER(subtype) \
+ class subtype##BodyPartFormatter : public KMail::BodyPartFormatter { \
+ static const subtype##BodyPartFormatter * self; \
+ public: \
+ bool process( KMail::ObjectTreeParser *, partNode *, KMail::ProcessResult & ) const; \
+ static const KMail::BodyPartFormatter * create() { \
+ if ( !self ) \
+ self = new subtype##BodyPartFormatter(); \
+ return self; \
+ } \
+ }; \
+ \
+ const subtype##BodyPartFormatter * subtype##BodyPartFormatter::self; \
+ \
+ bool subtype##BodyPartFormatter::process( KMail::ObjectTreeParser * otp, partNode * node, KMail::ProcessResult & result ) const { \
+ return otp->process##subtype##Subtype( node, result ); \
+ }
+
+ CREATE_BODY_PART_FORMATTER(TextPlain)
+ CREATE_BODY_PART_FORMATTER(TextHtml)
+ //CREATE_BODY_PART_FORMATTER(TextEnriched)
+
+ CREATE_BODY_PART_FORMATTER(ApplicationOctetStream)
+ CREATE_BODY_PART_FORMATTER(ApplicationPkcs7Mime)
+ CREATE_BODY_PART_FORMATTER(ApplicationChiasmusText)
+ //CREATE_BODY_PART_FORMATTER(ApplicationPgp)
+ CREATE_BODY_PART_FORMATTER(ApplicationMsTnef)
+
+ CREATE_BODY_PART_FORMATTER(MessageRfc822)
+
+ CREATE_BODY_PART_FORMATTER(MultiPartMixed)
+ CREATE_BODY_PART_FORMATTER(MultiPartAlternative)
+ CREATE_BODY_PART_FORMATTER(MultiPartSigned)
+ CREATE_BODY_PART_FORMATTER(MultiPartEncrypted)
+
+ typedef TextPlainBodyPartFormatter ApplicationPgpBodyPartFormatter;
+
+
+#undef CREATE_BODY_PART_FORMATTER
+} // anon namespace
+
+// FIXME: port some more KMail::BodyPartFormatters to KMail::Interface::BodyPartFormatters
+void KMail::BodyPartFormatterFactoryPrivate::kmail_create_builtin_bodypart_formatters( KMail::BodyPartFormatterFactoryPrivate::TypeRegistry * reg ) {
+ if ( !reg ) return;
+ (*reg)["application"]["octet-stream"] = new AnyTypeBodyPartFormatter();
+}
+
+typedef const KMail::BodyPartFormatter * (*BodyPartFormatterCreator)();
+
+struct SubtypeBuiltin {
+ const char * subtype;
+ BodyPartFormatterCreator create;
+};
+
+static const SubtypeBuiltin applicationSubtypeBuiltins[] = {
+ { "octet-stream", &ApplicationOctetStreamBodyPartFormatter::create },
+ { "pkcs7-mime", &ApplicationPkcs7MimeBodyPartFormatter::create },
+ { "x-pkcs7-mime", &ApplicationPkcs7MimeBodyPartFormatter::create },
+ { "vnd.de.bund.bsi.chiasmus-text", &ApplicationChiasmusTextBodyPartFormatter::create },
+ { "pgp", &ApplicationPgpBodyPartFormatter::create },
+ { "ms-tnef", &ApplicationMsTnefBodyPartFormatter::create }
+};
+
+static const SubtypeBuiltin textSubtypeBuiltins[] = {
+ { "html", &TextHtmlBodyPartFormatter::create },
+ //{ "enriched", &TextEnrichedBodyPartFormatter::create },
+ { "x-vcard", &AnyTypeBodyPartFormatter::create },
+ { "vcard", &AnyTypeBodyPartFormatter::create },
+ { "rtf", &AnyTypeBodyPartFormatter::create },
+ { "*", &TextPlainBodyPartFormatter::create },
+};
+
+static const SubtypeBuiltin multipartSubtypeBuiltins[] = {
+ { "mixed", &MultiPartMixedBodyPartFormatter::create },
+ { "alternative", &MultiPartAlternativeBodyPartFormatter::create },
+ //{ "digest", &MultiPartDigestFormatter::create },
+ //{ "parallel", &MultiPartParallelFormatter::create },
+ //{ "related", &MultiPartRelatedFormatter::create },
+ { "signed", &MultiPartSignedBodyPartFormatter::create },
+ { "encrypted", &MultiPartEncryptedBodyPartFormatter::create },
+ //{ "report", &MultiPartReportFormatter::create },
+};
+
+static const SubtypeBuiltin messageSubtypeBuiltins[] = {
+ { "rfc822", &MessageRfc822BodyPartFormatter::create },
+};
+
+static const SubtypeBuiltin imageSubtypeBuiltins[] = {
+ { "*", &ImageTypeBodyPartFormatter::create },
+};
+
+static const SubtypeBuiltin anySubtypeBuiltins[] = {
+ { "*", &AnyTypeBodyPartFormatter::create },
+};
+
+#ifdef DIM
+#undef DIM
+#endif
+#define DIM(x) sizeof(x) / sizeof(*x)
+
+static const struct {
+ const char * type;
+ const SubtypeBuiltin * subtypes;
+ unsigned int num_subtypes;
+} builtins[] = {
+ { "application", applicationSubtypeBuiltins, DIM(applicationSubtypeBuiltins) },
+ { "text", textSubtypeBuiltins, DIM(textSubtypeBuiltins) },
+ { "multipart", multipartSubtypeBuiltins, DIM(multipartSubtypeBuiltins) },
+ { "message", messageSubtypeBuiltins, DIM(messageSubtypeBuiltins) },
+ { "image", imageSubtypeBuiltins, DIM(imageSubtypeBuiltins) },
+ //{ "audio", audioSubtypeBuiltins, DIM(audioSubtypeBuiltins) },
+ //{ "model", modelSubtypeBuiltins, DIM(modelSubtypeBuiltins) },
+ //{ "video", videoSubtypeBuiltins, DIM(videoSubtypeBuiltins) },
+ { "*", anySubtypeBuiltins, DIM(anySubtypeBuiltins) },
+};
+
+#undef DIM
+
+const KMail::BodyPartFormatter * KMail::BodyPartFormatter::createFor( int type, int subtype ) {
+ DwString t, st;
+ DwTypeEnumToStr( type, t );
+ DwSubtypeEnumToStr( subtype, st );
+ return createFor( t.c_str(), st.c_str() );
+}
+
+static const KMail::BodyPartFormatter * createForText( const char * subtype ) {
+ if ( subtype && *subtype )
+ switch ( subtype[0] ) {
+ case 'h':
+ case 'H':
+ if ( kasciistricmp( subtype, "html" ) == 0 )
+ return TextHtmlBodyPartFormatter::create();
+ break;
+ case 'r':
+ case 'R':
+ if ( kasciistricmp( subtype, "rtf" ) == 0 )
+ return AnyTypeBodyPartFormatter::create();
+ break;
+ case 'x':
+ case 'X':
+ case 'v':
+ case 'V':
+ if ( kasciistricmp( subtype, "x-vcard" ) == 0 ||
+ kasciistricmp( subtype, "vcard" ) == 0 )
+ return AnyTypeBodyPartFormatter::create();
+ break;
+ }
+
+ return TextPlainBodyPartFormatter::create();
+}
+
+static const KMail::BodyPartFormatter * createForImage( const char * ) {
+ return ImageTypeBodyPartFormatter::create();
+}
+
+static const KMail::BodyPartFormatter * createForMessage( const char * subtype ) {
+ if ( kasciistricmp( subtype, "rfc822" ) == 0 )
+ return MessageRfc822BodyPartFormatter::create();
+ return AnyTypeBodyPartFormatter::create();
+}
+
+static const KMail::BodyPartFormatter * createForMultiPart( const char * subtype ) {
+ if ( subtype && *subtype )
+ switch ( subtype[0] ) {
+ case 'a':
+ case 'A':
+ if ( kasciistricmp( subtype, "alternative" ) == 0 )
+ return MultiPartAlternativeBodyPartFormatter::create();
+ break;
+ case 'e':
+ case 'E':
+ if ( kasciistricmp( subtype, "encrypted" ) == 0 )
+ return MultiPartEncryptedBodyPartFormatter::create();
+ break;
+ case 's':
+ case 'S':
+ if ( kasciistricmp( subtype, "signed" ) == 0 )
+ return MultiPartSignedBodyPartFormatter::create();
+ break;
+ }
+
+ return MultiPartMixedBodyPartFormatter::create();
+}
+
+static const KMail::BodyPartFormatter * createForApplication( const char * subtype ) {
+ if ( subtype && *subtype )
+ switch ( subtype[0] ) {
+ case 'p':
+ case 'P':
+ if ( kasciistricmp( subtype, "pgp" ) == 0 )
+ return ApplicationPgpBodyPartFormatter::create();
+ // fall through
+ case 'x':
+ case 'X':
+ if ( kasciistricmp( subtype, "pkcs7-mime" ) == 0 ||
+ kasciistricmp( subtype, "x-pkcs7-mime" ) == 0 )
+ return ApplicationPkcs7MimeBodyPartFormatter::create();
+ break;
+ case 'm':
+ case 'M':
+ if ( kasciistricmp( subtype, "ms-tnef" ) == 0 )
+ return ApplicationMsTnefBodyPartFormatter::create();
+ break;
+ case 'v':
+ case 'V':
+ if ( kasciistricmp( subtype, "vnd.de.bund.bsi.chiasmus-text") == 0)
+ return ApplicationChiasmusTextBodyPartFormatter::create();
+ break;
+ }
+
+ return AnyTypeBodyPartFormatter::create();
+}
+
+// OK, replace this with a factory with plugin support later on...
+const KMail::BodyPartFormatter * KMail::BodyPartFormatter::createFor( const char * type, const char * subtype ) {
+ if ( type && *type )
+ switch ( type[0] ) {
+ case 'a': // application
+ case 'A':
+ if ( kasciistricmp( type, "application" ) == 0 )
+ return createForApplication( subtype );
+ break;
+ case 'i': // image
+ case 'I':
+ if ( kasciistricmp( type, "image" ) == 0 )
+ return createForImage( subtype );
+ break;
+ case 'm': // multipart / message
+ case 'M':
+ if ( kasciistricmp( type, "multipart" ) == 0 )
+ return createForMultiPart( subtype );
+ else if ( kasciistricmp( type, "message" ) == 0 )
+ return createForMessage( subtype );
+ break;
+ case 't': // text
+ case 'T':
+ if ( kasciistricmp( type, "text" ) == 0 )
+ return createForText( subtype );
+ break;
+ }
+
+ return AnyTypeBodyPartFormatter::create();
+}
+
diff --git a/kmail/bodypartformatter.h b/kmail/bodypartformatter.h
new file mode 100644
index 00000000..eda28a90
--- /dev/null
+++ b/kmail/bodypartformatter.h
@@ -0,0 +1,58 @@
+/* -*- c++ -*-
+ bodypartformatter.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_BODYPARTFORMATTER_H__
+#define __KMAIL_BODYPARTFORMATTER_H__
+
+class partNode;
+
+namespace KMail {
+
+ class ObjectTreeParser;
+ class ProcessResult;
+
+ class BodyPartFormatter {
+ const BodyPartFormatter & operator=( const BodyPartFormatter & );
+ BodyPartFormatter( const BodyPartFormatter & );
+ protected:
+ BodyPartFormatter() {}
+ public:
+ virtual ~BodyPartFormatter() {}
+
+ virtual bool process( ObjectTreeParser *, partNode *, ProcessResult & ) const = 0;
+
+ static const BodyPartFormatter * createFor( int type, int subtype );
+ static const BodyPartFormatter * createFor( const char * type, const char * subtype );
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_BODYPARTFORMATTER_H__
diff --git a/kmail/bodypartformatterfactory.cpp b/kmail/bodypartformatterfactory.cpp
new file mode 100644
index 00000000..24ee2fba
--- /dev/null
+++ b/kmail/bodypartformatterfactory.cpp
@@ -0,0 +1,191 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ bodypartformatterfactory.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2004 Marc Mutz <mutz@kde.org>,
+ Ingo Kloecker <kloecker@kde.org>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "bodypartformatterfactory.h"
+#include "bodypartformatterfactory_p.h"
+using namespace KMail::BodyPartFormatterFactoryPrivate;
+
+#include "interfaces/bodypartformatter.h"
+#include "urlhandlermanager.h"
+
+// libkdepim
+#include <libkdepim/pluginloader.h>
+
+// KDE
+#include <kdebug.h>
+
+// Qt
+#include <qstring.h>
+#include <qcstring.h>
+#include <qstringlist.h>
+
+#include <assert.h>
+
+namespace {
+
+ KPIM_DEFINE_PLUGIN_LOADER( BodyPartFormatterPluginLoader,
+ KMail::Interface::BodyPartFormatterPlugin,
+ "create_bodypart_formatter_plugin",
+ "kmail/plugins/bodypartformatter/*.desktop" )
+
+}
+
+KMail::BodyPartFormatterFactory * KMail::BodyPartFormatterFactory::mSelf = 0;
+
+const KMail::BodyPartFormatterFactory * KMail::BodyPartFormatterFactory::instance() {
+ if ( !mSelf )
+ mSelf = new BodyPartFormatterFactory();
+ return mSelf;
+}
+
+KMail::BodyPartFormatterFactory::BodyPartFormatterFactory() {
+ mSelf = this;
+}
+
+KMail::BodyPartFormatterFactory::~BodyPartFormatterFactory() {
+ mSelf = 0;
+}
+
+static TypeRegistry * all = 0;
+
+static void insertBodyPartFormatter( const char * type, const char * subtype,
+ const KMail::Interface::BodyPartFormatter * formatter ) {
+ if ( !type || !*type || !subtype || !*subtype || !formatter || !all )
+ return;
+
+ TypeRegistry::iterator type_it = all->find( type );
+ if ( type_it == all->end() ) {
+ kdDebug( 5006 ) << "BodyPartFormatterFactory: instantiating new Subtype Registry for \""
+ << type << "\"" << endl;
+ type_it = all->insert( std::make_pair( type, SubtypeRegistry() ) ).first;
+ assert( type_it != all->end() );
+ }
+
+ SubtypeRegistry & subtype_reg = type_it->second;
+ SubtypeRegistry::iterator subtype_it = subtype_reg.find( subtype );
+ if ( subtype_it != subtype_reg.end() ) {
+ kdDebug( 5006 ) << "BodyPartFormatterFactory: overwriting previously registered formatter for \""
+ << type << "/" << subtype << "\"" << endl;
+ subtype_reg.erase( subtype_it ); subtype_it = subtype_reg.end();
+ }
+
+ subtype_reg.insert( std::make_pair( subtype, formatter ) );
+}
+
+static void loadPlugins() {
+ const BodyPartFormatterPluginLoader * pl = BodyPartFormatterPluginLoader::instance();
+ if ( !pl ) {
+ kdWarning( 5006 ) << "BodyPartFormatterFactory: cannot instantiate plugin loader!" << endl;
+ return;
+ }
+ const QStringList types = pl->types();
+ kdDebug( 5006 ) << "BodyPartFormatterFactory: found " << types.size() << " plugins." << endl;
+ for ( QStringList::const_iterator it = types.begin() ; it != types.end() ; ++it ) {
+ const KMail::Interface::BodyPartFormatterPlugin * plugin = pl->createForName( *it );
+ if ( !plugin ) {
+ kdWarning( 5006 ) << "BodyPartFormatterFactory: plugin \"" << *it << "\" is not valid!" << endl;
+ continue;
+ }
+ for ( int i = 0 ; const KMail::Interface::BodyPartFormatter * bfp = plugin->bodyPartFormatter( i ) ; ++i ) {
+ const char * type = plugin->type( i );
+ if ( !type || !*type ) {
+ kdWarning( 5006 ) << "BodyPartFormatterFactory: plugin \"" << *it
+ << "\" returned empty type specification for index "
+ << i << endl;
+ break;
+ }
+ const char * subtype = plugin->subtype( i );
+ if ( !subtype || !*subtype ) {
+ kdWarning( 5006 ) << "BodyPartFormatterFactory: plugin \"" << *it
+ << "\" returned empty subtype specification for index "
+ << i << endl;
+ break;
+ }
+ insertBodyPartFormatter( type, subtype, bfp );
+ }
+ for ( int i = 0 ; const KMail::Interface::BodyPartURLHandler * handler = plugin->urlHandler( i ) ; ++i )
+ KMail::URLHandlerManager::instance()->registerHandler( handler );
+ }
+}
+
+static void setup() {
+ if ( !all ) {
+ all = new TypeRegistry();
+ kmail_create_builtin_bodypart_formatters( all );
+ loadPlugins();
+ }
+}
+
+
+const KMail::Interface::BodyPartFormatter * KMail::BodyPartFormatterFactory::createFor( const char * type, const char * subtype ) const {
+ if ( !type || !*type )
+ type = "*";
+ if ( !subtype || !*subtype )
+ subtype = "*";
+
+ setup();
+ assert( all );
+
+ if ( all->empty() )
+ return 0;
+
+ TypeRegistry::const_iterator type_it = all->find( type );
+ if ( type_it == all->end() )
+ type_it = all->find( "*" );
+ if ( type_it == all->end() )
+ return 0;
+
+ const SubtypeRegistry & subtype_reg = type_it->second;
+ if ( subtype_reg.empty() )
+ return 0;
+
+ SubtypeRegistry::const_iterator subtype_it = subtype_reg.find( subtype );
+ if ( subtype_it == subtype_reg.end() )
+ subtype_it = subtype_reg.find( "*" );
+ if ( subtype_it == subtype_reg.end() )
+ return 0;
+
+ kdWarning( !(*subtype_it).second, 5006 )
+ << "BodyPartFormatterFactory: a null bodypart formatter sneaked in for \""
+ << type << "/" << subtype << "\"!" << endl;
+
+ return (*subtype_it).second;
+}
+
+const KMail::Interface::BodyPartFormatter * KMail::BodyPartFormatterFactory::createFor( const QString & type, const QString & subtype ) const {
+ return createFor( type.latin1(), subtype.latin1() );
+}
+
+const KMail::Interface::BodyPartFormatter * KMail::BodyPartFormatterFactory::createFor( const QCString & type, const QCString & subtype ) const {
+ return createFor( type.data(), subtype.data() );
+}
diff --git a/kmail/bodypartformatterfactory.h b/kmail/bodypartformatterfactory.h
new file mode 100644
index 00000000..63f43bd7
--- /dev/null
+++ b/kmail/bodypartformatterfactory.h
@@ -0,0 +1,72 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ bodypartformatterfactory.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2004 Marc Mutz <mutz@kde.org>,
+ Ingo Kloecker <kloecker@kde.org>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_BODYPARTFORMATTERFACTORY_H__
+#define __KMAIL_BODYPARTFORMATTERFACTORY_H__
+
+class QString;
+class QCString;
+
+namespace KMail {
+
+ namespace Interface {
+ class BodyPartFormatter;
+ }
+
+ class BodyPartFormatterFactory {
+ class gcc_shut_up;
+ friend class ::KMail::BodyPartFormatterFactory::gcc_shut_up;
+ public:
+ ~BodyPartFormatterFactory();
+
+ static const BodyPartFormatterFactory * instance();
+
+ const Interface::BodyPartFormatter * createFor( const char * type, const char * subtype ) const;
+ const Interface::BodyPartFormatter * createFor( const QString & type, const QString & subtype ) const;
+ const Interface::BodyPartFormatter * createFor( const QCString & type, const QCString & subtype ) const;
+
+ //
+ // Only boring stuff below:
+ //
+ private:
+ BodyPartFormatterFactory();
+ static BodyPartFormatterFactory * mSelf;
+ private:
+ // disabled
+ const BodyPartFormatterFactory & operator=( const BodyPartFormatterFactory & );
+ BodyPartFormatterFactory( const BodyPartFormatterFactory & );
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_BODYPARTFORMATTERFACTORY_H__
diff --git a/kmail/bodypartformatterfactory_p.h b/kmail/bodypartformatterfactory_p.h
new file mode 100644
index 00000000..7171ba29
--- /dev/null
+++ b/kmail/bodypartformatterfactory_p.h
@@ -0,0 +1,62 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ bodypartformatterfactory.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2004 Marc Mutz <mutz@kde.org>,
+ Ingo Kloecker <kloecker@kde.org>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_BODYPARTFORMATTERFACTORY_P_H__
+#define __KMAIL_BODYPARTFORMATTERFACTORY_P_H__
+
+#include <qcstring.h>
+
+#include <map>
+
+namespace KMail {
+
+ namespace Interface {
+ class BodyPartFormatter;
+ }
+
+ namespace BodyPartFormatterFactoryPrivate {
+ struct ltstr {
+ bool operator()( const char * s1, const char * s2 ) const {
+ return qstricmp( s1, s2 ) < 0;
+ }
+ };
+
+ typedef std::map<const char*, const KMail::Interface::BodyPartFormatter*, ltstr> SubtypeRegistry;
+ typedef std::map<const char*, SubtypeRegistry, ltstr> TypeRegistry;
+
+ // defined in bodypartformatters.cpp
+ extern void kmail_create_builtin_bodypart_formatters( TypeRegistry * );
+ }
+} // namespace KMail
+
+#endif // __KMAIL_BODYPARTFORMATTERFACTORY_P_H__
diff --git a/kmail/bodyvisitor.cpp b/kmail/bodyvisitor.cpp
new file mode 100644
index 00000000..99af6619
--- /dev/null
+++ b/kmail/bodyvisitor.cpp
@@ -0,0 +1,211 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "bodyvisitor.h"
+#include "kmmsgpart.h"
+#include "attachmentstrategy.h"
+#include <kdebug.h>
+
+namespace KMail {
+
+ BodyVisitor* BodyVisitorFactory::getVisitor( const AttachmentStrategy* strategy )
+ {
+ if (strategy == AttachmentStrategy::smart())
+ {
+ return new BodyVisitorSmart();
+ } else if (strategy == AttachmentStrategy::iconic())
+ {
+ return new BodyVisitorHidden();
+ } else if (strategy == AttachmentStrategy::inlined())
+ {
+ return new BodyVisitorInline();
+ } else if (strategy == AttachmentStrategy::hidden())
+ {
+ return new BodyVisitorHidden();
+ }
+ // default
+ return new BodyVisitorSmart();
+ }
+
+ //=============================================================================
+
+ BodyVisitor::BodyVisitor()
+ {
+ // parts that are probably always ok to load
+ mBasicList.clear();
+ // body text
+ mBasicList += "TEXT/PLAIN";
+ mBasicList += "TEXT/HTML";
+ mBasicList += "MESSAGE/DELIVERY-STATUS";
+ // pgp stuff
+ mBasicList += "APPLICATION/PGP-SIGNATURE";
+ mBasicList += "APPLICATION/PGP";
+ mBasicList += "APPLICATION/PGP-ENCRYPTED";
+ mBasicList += "APPLICATION/PKCS7-SIGNATURE";
+ // groupware
+ mBasicList += "APPLICATION/MS-TNEF";
+ mBasicList += "TEXT/CALENDAR";
+ mBasicList += "TEXT/X-VCARD";
+ }
+
+ //-----------------------------------------------------------------------------
+ BodyVisitor::~BodyVisitor()
+ {
+ }
+
+ //-----------------------------------------------------------------------------
+ void BodyVisitor::visit( KMMessagePart * part )
+ {
+ mParts.append( part );
+ }
+
+ //-----------------------------------------------------------------------------
+ void BodyVisitor::visit( QPtrList<KMMessagePart> & list )
+ {
+ mParts = list;
+ }
+
+ //-----------------------------------------------------------------------------
+ QPtrList<KMMessagePart> BodyVisitor::partsToLoad()
+ {
+ QPtrListIterator<KMMessagePart> it( mParts );
+ QPtrList<KMMessagePart> selected;
+ KMMessagePart *part = 0;
+ bool headerCheck = false;
+ while ( (part = it.current()) != 0 )
+ {
+ ++it;
+ // skip this part if the parent part is already loading
+ if ( part->parent() &&
+ selected.contains( part->parent() ) &&
+ part->loadPart() )
+ continue;
+
+ if ( part->originalContentTypeStr().contains("SIGNED") )
+ {
+ // signed messages have to be loaded completely
+ // so construct a new dummy part that loads the body
+ KMMessagePart *fake = new KMMessagePart();
+ fake->setPartSpecifier( "TEXT" );
+ fake->setOriginalContentTypeStr("");
+ fake->setLoadPart( true );
+ selected.append( fake );
+ break;
+ }
+
+ if ( headerCheck && !part->partSpecifier().endsWith(".HEADER") )
+ {
+ // this is an embedded simple message (not multipart) so we get no header part
+ // from imap. As we probably need to load the header (e.g. in smart or inline mode)
+ // we add a fake part that is not included in the message itself
+ KMMessagePart *fake = new KMMessagePart();
+ QString partId = part->partSpecifier().section( '.', 0, -2 )+".HEADER";
+ fake->setPartSpecifier( partId );
+ fake->setOriginalContentTypeStr("");
+ fake->setLoadPart( true );
+ if ( addPartToList( fake ) )
+ selected.append( fake );
+ }
+
+ if ( part->originalContentTypeStr() == "MESSAGE/RFC822" )
+ headerCheck = true;
+ else
+ headerCheck = false;
+
+ // check whether to load this part or not:
+ // look at the basic list, ask the subclass and check the parent
+ if ( mBasicList.contains( part->originalContentTypeStr() ) ||
+ parentNeedsLoading( part ) ||
+ addPartToList( part ) )
+ {
+ if ( part->typeStr() != "MULTIPART" ||
+ part->partSpecifier().endsWith(".HEADER") )
+ {
+ // load the part itself
+ part->setLoadPart( true );
+ }
+ }
+ if ( !part->partSpecifier().endsWith(".HEADER") &&
+ part->typeStr() != "MULTIPART" )
+ part->setLoadHeaders( true ); // load MIME header
+
+ if ( part->loadHeaders() || part->loadPart() )
+ selected.append( part );
+ }
+ return selected;
+ }
+
+ //-----------------------------------------------------------------------------
+ bool BodyVisitor::parentNeedsLoading( KMMessagePart *msgPart )
+ {
+ KMMessagePart *part = msgPart;
+ while ( part )
+ {
+ if ( part->parent() &&
+ ( part->parent()->originalContentTypeStr() == "MULTIPART/SIGNED" ||
+ ( msgPart->originalContentTypeStr() == "APPLICATION/OCTET-STREAM" &&
+ part->parent()->originalContentTypeStr() == "MULTIPART/ENCRYPTED" ) ) )
+ return true;
+
+ part = part->parent();
+ }
+ return false;
+ }
+
+ //=============================================================================
+
+ BodyVisitorSmart::BodyVisitorSmart()
+ : BodyVisitor()
+ {
+ }
+
+ //-----------------------------------------------------------------------------
+ bool BodyVisitorSmart::addPartToList( KMMessagePart * part )
+ {
+ // header of an encapsulated message
+ if ( part->partSpecifier().endsWith(".HEADER") )
+ return true;
+
+ return false;
+ }
+
+ //=============================================================================
+
+ BodyVisitorInline::BodyVisitorInline()
+ : BodyVisitor()
+ {
+ }
+
+ //-----------------------------------------------------------------------------
+ bool BodyVisitorInline::addPartToList( KMMessagePart * part )
+ {
+ // header of an encapsulated message
+ if ( part->partSpecifier().endsWith(".HEADER") )
+ return true;
+ else if ( part->typeStr() == "IMAGE" ) // images
+ return true;
+ else if ( part->typeStr() == "TEXT" ) // text, diff and stuff
+ return true;
+
+ return false;
+ }
+
+ //=============================================================================
+
+ BodyVisitorHidden::BodyVisitorHidden()
+ : BodyVisitor()
+ {
+ }
+
+ //-----------------------------------------------------------------------------
+ bool BodyVisitorHidden::addPartToList( KMMessagePart * part )
+ {
+ // header of an encapsulated message
+ if ( part->partSpecifier().endsWith(".HEADER") )
+ return true;
+
+ return false;
+ }
+
+}
diff --git a/kmail/bodyvisitor.h b/kmail/bodyvisitor.h
new file mode 100644
index 00000000..fa5d2fb5
--- /dev/null
+++ b/kmail/bodyvisitor.h
@@ -0,0 +1,109 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ *
+ * This file is part of KMail, the KDE mail client.
+ * Copyright (c) 2003 Carsten Burghardt <burghardt@kde.org>
+ *
+ * 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 Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#ifndef bodyiterator_h
+#define bodyiterator_h
+
+#include <qptrlist.h>
+#include <qstringlist.h>
+
+class KMMessagePart;
+
+namespace KMail {
+
+ class AttachmentStrategy;
+
+ // Base class
+ class BodyVisitor
+ {
+ public:
+ BodyVisitor();
+ virtual ~BodyVisitor();
+
+ /** Register the parts that should be visited */
+ void visit( KMMessagePart * part );
+ void visit( QPtrList<KMMessagePart> & list );
+
+ /** Returns a list of parts that should be loaded */
+ QPtrList<KMMessagePart> partsToLoad();
+
+ /** Decides whether a part should be loaded.
+ This needs to be implemented by a subclass */
+ virtual bool addPartToList( KMMessagePart * part ) = 0;
+
+ protected:
+ /**
+ * Checks if one of the parents needs loaded children
+ * This is e.g. needed for multipart/signed where all parts have to be loaded
+ */
+ static bool parentNeedsLoading( KMMessagePart * part );
+
+ protected:
+ QPtrList<KMMessagePart> mParts;
+ QStringList mBasicList;
+ };
+
+ // Factory to create a visitor according to the Attachment Strategy
+ class BodyVisitorFactory
+ {
+ public:
+ static BodyVisitor* getVisitor( const AttachmentStrategy* strategy );
+ };
+
+ // Visitor for smart attachment display
+ class BodyVisitorSmart: public BodyVisitor
+ {
+ public:
+ BodyVisitorSmart();
+
+ bool addPartToList( KMMessagePart * part );
+ };
+
+ // Visitor for inline attachment display
+ class BodyVisitorInline: public BodyVisitor
+ {
+ public:
+ BodyVisitorInline();
+
+ bool addPartToList( KMMessagePart * part );
+ };
+
+ // Visitor for hidden attachment display
+ class BodyVisitorHidden: public BodyVisitor
+ {
+ public:
+ BodyVisitorHidden();
+
+ bool addPartToList( KMMessagePart * part );
+ };
+
+}
+
+#endif
diff --git a/kmail/cachedimapjob.cpp b/kmail/cachedimapjob.cpp
new file mode 100644
index 00000000..8c633961
--- /dev/null
+++ b/kmail/cachedimapjob.cpp
@@ -0,0 +1,841 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ *
+ * This file is part of KMail, the KDE mail client.
+ * Copyright (c) 2002-2004 Bo Thorsen <bo@sonofthor.dk>
+ * 2002-2003 Steffen Hansen <hansen@kde.org>
+ * 2002-2003 Zack Rusin <zack@kde.org>
+ *
+ * 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 Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "cachedimapjob.h"
+#include "imapaccountbase.h"
+
+#include "kmfoldermgr.h"
+#include "kmfolder.h"
+#include "kmfoldercachedimap.h"
+#include "kmailicalifaceimpl.h"
+#include "kmacctcachedimap.h"
+#include "kmmsgdict.h"
+#include "maildirjob.h"
+#include "scalix.h"
+#include "util.h"
+
+#include <kio/scheduler.h>
+#include <kio/job.h>
+
+#include <klocale.h>
+#include <kdebug.h>
+
+
+namespace KMail {
+
+// Get messages
+CachedImapJob::CachedImapJob( const QValueList<MsgForDownload>& msgs,
+ JobType type, KMFolderCachedImap* folder )
+ : FolderJob( type ), mFolder( folder ), mMsgsForDownload( msgs ),
+ mTotalBytes(0), mMsg(0), mParentFolder( 0 )
+{
+ QValueList<MsgForDownload>::ConstIterator it = msgs.begin();
+ for ( ; it != msgs.end() ; ++it )
+ mTotalBytes += (*it).size;
+}
+
+// Put messages
+CachedImapJob::CachedImapJob( const QPtrList<KMMessage>& msgs, JobType type,
+ KMFolderCachedImap* folder )
+ : FolderJob( msgs, QString::null, type, folder?folder->folder():0 ), mFolder( folder ),
+ mTotalBytes( msgs.count() ), // we abuse it as "total number of messages"
+ mMsg( 0 ), mParentFolder( 0 )
+{
+}
+
+CachedImapJob::CachedImapJob( const QValueList<unsigned long>& msgs,
+ JobType type, KMFolderCachedImap* folder )
+ : FolderJob( QPtrList<KMMessage>(), QString::null, type, folder?folder->folder():0 ),
+ mFolder( folder ), mSerNumMsgList( msgs ), mTotalBytes( msgs.count() ), mMsg( 0 ),
+ mParentFolder ( 0 )
+{
+}
+
+// Add sub folders
+CachedImapJob::CachedImapJob( const QValueList<KMFolderCachedImap*>& fList,
+ JobType type, KMFolderCachedImap* folder )
+ : FolderJob( type ), mFolder( folder ), mFolderList( fList ), mMsg( 0 ),
+ mParentFolder ( 0 )
+{
+}
+
+// Rename folder
+CachedImapJob::CachedImapJob( const QString& string1, JobType type,
+ KMFolderCachedImap* folder )
+ : FolderJob( type ), mFolder(folder), mMsg( 0 ), mString( string1 ),
+ mParentFolder ( 0 )
+{
+ assert( folder );
+ assert( type != tDeleteMessage ); // moved to another ctor
+}
+
+// Delete folders or messages
+CachedImapJob::CachedImapJob( const QStringList& foldersOrMsgs, JobType type,
+ KMFolderCachedImap* folder )
+ : FolderJob( type ), mFolder( folder ), mFoldersOrMessages( foldersOrMsgs ),
+ mMsg( 0 ), mParentFolder( 0 )
+{
+ assert( folder );
+}
+
+// Other jobs (list messages,expunge folder, check uid validity)
+CachedImapJob::CachedImapJob( JobType type, KMFolderCachedImap* folder )
+ : FolderJob( type ), mFolder( folder ), mMsg( 0 ), mParentFolder ( 0 )
+{
+ assert( folder );
+}
+
+CachedImapJob::~CachedImapJob()
+{
+ mAccount->mJobList.remove(this);
+}
+
+void CachedImapJob::execute()
+{
+ mSentBytes = 0;
+
+ if( !mFolder ) {
+ if( !mMsgList.isEmpty() ) {
+ mFolder = static_cast<KMFolderCachedImap*>(mMsgList.first()->storage());
+ }
+ }
+ assert( mFolder );
+ mAccount = mFolder->account();
+ assert( mAccount != 0 );
+ if( mAccount->makeConnection() != ImapAccountBase::Connected ) {
+ // No connection to the IMAP server
+ kdDebug(5006) << "mAccount->makeConnection() failed" << endl;
+ mPassiveDestructor = true;
+ delete this;
+ return;
+ } else
+ mPassiveDestructor = false;
+
+ // All necessary conditions have been met. Register this job
+ mAccount->mJobList.append(this);
+
+ /**
+ * The Scalix server requires to send him a custom X-SCALIX-ID command
+ * to switch it into a special mode.
+ *
+ * This should be done once after the login and before the first command.
+ */
+ if ( mAccount->groupwareType() == KMAcctCachedImap::GroupwareScalix ) {
+ if ( !mAccount->sentCustomLoginCommand() ) {
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+
+ const QString command = QString( "X-SCALIX-ID " );
+ const QString argument = QString( "(\"name\" \"Evolution\" \"version\" \"2.10.0\")" );
+
+ stream << (int) 'X' << 'N' << command << argument;
+
+ const KURL url = mAccount->getUrl();
+
+ ImapAccountBase::jobData jd( url.url(), mFolder->folder() );
+ jd.items << mFolder->label(); // for the err msg
+ KIO::SimpleJob *simpleJob = KIO::special( url.url(), packedArgs, false );
+ KIO::Scheduler::assignJobToSlave(mAccount->slave(), simpleJob);
+ mAccount->insertJob(simpleJob, jd);
+
+ mAccount->setSentCustomLoginCommand( true );
+ }
+ }
+
+ switch( mType ) {
+ case tGetMessage: slotGetNextMessage(); break;
+ case tPutMessage: slotPutNextMessage(); break;
+ case tDeleteMessage: slotDeleteNextMessages(); break;
+ case tExpungeFolder: expungeFolder(); break;
+ case tAddSubfolders: slotAddNextSubfolder(); break;
+ case tDeleteFolders: slotDeleteNextFolder(); break;
+ case tCheckUidValidity: checkUidValidity(); break;
+ case tRenameFolder: renameFolder(mString); break;
+ case tListMessages: listMessages(); break;
+ default:
+ assert( 0 );
+ }
+}
+
+void CachedImapJob::listMessages()
+{
+ KURL url = mAccount->getUrl();
+ url.setPath( mFolder->imapPath() + ";UID=1:*;SECTION=FLAGS RFC822.SIZE");
+
+ KIO::SimpleJob *job = KIO::get(url, false, false);
+ KIO::Scheduler::assignJobToSlave( mAccount->slave(), job );
+ ImapAccountBase::jobData jd( url.url(), mFolder->folder() );
+ jd.cancellable = true;
+ mAccount->insertJob( job, jd );
+ connect( job, SIGNAL( result(KIO::Job *) ),
+ this, SLOT( slotListMessagesResult( KIO::Job* ) ) );
+ // send the data directly for KMFolderCachedImap
+ connect( job, SIGNAL( data( KIO::Job*, const QByteArray& ) ),
+ mFolder, SLOT( slotGetMessagesData( KIO::Job* , const QByteArray& ) ) );
+}
+
+void CachedImapJob::slotDeleteNextMessages( KIO::Job* job )
+{
+ if (job) {
+ KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
+ if ( it == mAccount->jobsEnd() ) { // Shouldn't happen
+ delete this;
+ return;
+ }
+
+ if( job->error() ) {
+ mAccount->handleJobError( job, i18n( "Error while deleting messages on the server: " ) + '\n' );
+ delete this;
+ return;
+ }
+ mAccount->removeJob(it);
+ }
+
+ if( mFoldersOrMessages.isEmpty() ) {
+ // No more messages to delete
+ delete this;
+ return;
+ }
+
+ QString uids = mFoldersOrMessages.front(); mFoldersOrMessages.pop_front();
+
+ KURL url = mAccount->getUrl();
+ url.setPath( mFolder->imapPath() +
+ QString::fromLatin1(";UID=%1").arg(uids) );
+
+ KIO::SimpleJob *simpleJob = KIO::file_delete( url, false );
+ KIO::Scheduler::assignJobToSlave( mAccount->slave(), simpleJob );
+ ImapAccountBase::jobData jd( url.url(), mFolder->folder() );
+ mAccount->insertJob( simpleJob, jd );
+ connect( simpleJob, SIGNAL( result(KIO::Job *) ),
+ this, SLOT( slotDeleteNextMessages(KIO::Job *) ) );
+}
+
+void CachedImapJob::expungeFolder()
+{
+ KURL url = mAccount->getUrl();
+ // Special URL that means EXPUNGE
+ url.setPath( mFolder->imapPath() + QString::fromLatin1(";UID=*") );
+
+ KIO::SimpleJob *job = KIO::file_delete( url, false );
+ KIO::Scheduler::assignJobToSlave( mAccount->slave(), job );
+ ImapAccountBase::jobData jd( url.url(), mFolder->folder() );
+ mAccount->insertJob( job, jd );
+ connect( job, SIGNAL( result(KIO::Job *) ),
+ this, SLOT( slotExpungeResult(KIO::Job *) ) );
+}
+
+void CachedImapJob::slotExpungeResult( KIO::Job * job )
+{
+ KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
+ if ( it == mAccount->jobsEnd() ) { // Shouldn't happen
+ delete this;
+ return;
+ }
+
+ if (job->error()) {
+ mErrorCode = job->error();
+ mAccount->handleJobError( job, i18n( "Error while deleting messages on the server: " ) + '\n' );
+ }
+ else
+ mAccount->removeJob(it);
+
+ delete this;
+}
+
+void CachedImapJob::slotGetNextMessage(KIO::Job * job)
+{
+ if (job) {
+ KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
+ if ( it == mAccount->jobsEnd() ) { // Shouldn't happen
+ delete this;
+ return;
+ }
+
+ if (job->error()) {
+ mErrorCode = job->error();
+ mAccount->handleJobError( job, i18n( "Error while retrieving message on the server: " ) + '\n' );
+ delete this;
+ return;
+ }
+
+ ulong size = 0;
+ if ((*it).data.size() > 0) {
+ ulong uid = mMsg->UID();
+ size = mMsg->msgSizeServer();
+
+ // Convert CR/LF to LF.
+ size_t dataSize = (*it).data.size();
+ dataSize = Util::crlf2lf( (*it).data.data(), dataSize ); // always <=
+ (*it).data.resize( dataSize );
+
+ mMsg->setComplete( true );
+ mMsg->fromByteArray( (*it).data );
+ mMsg->setUID(uid);
+ mMsg->setMsgSizeServer(size);
+ mMsg->setTransferInProgress( false );
+ int index = 0;
+ mFolder->addMsgInternal( mMsg, true, &index );
+
+ if ( kmkernel->iCalIface().isResourceFolder( mFolder->folder() ) ) {
+ mFolder->setStatus( index, KMMsgStatusRead, false );
+ }
+
+ emit messageRetrieved( mMsg );
+ if ( index > 0 ) mFolder->unGetMsg( index );
+ } else {
+ emit messageRetrieved( 0 );
+ }
+ mMsg = 0;
+
+ mSentBytes += size;
+ emit progress( mSentBytes, mTotalBytes );
+ mAccount->removeJob(it);
+ } else
+ mFolder->quiet( true );
+
+ if( mMsgsForDownload.isEmpty() ) {
+ mFolder->quiet( false );
+ delete this;
+ return;
+ }
+
+ MsgForDownload mfd = mMsgsForDownload.front(); mMsgsForDownload.pop_front();
+
+ mMsg = new KMMessage;
+ mMsg->setUID(mfd.uid);
+ mMsg->setMsgSizeServer(mfd.size);
+ if( mfd.flags > 0 )
+ KMFolderImap::flagsToStatus(mMsg, mfd.flags, true, GlobalSettings::allowLocalFlags() ? mFolder->permanentFlags() : INT_MAX);
+ KURL url = mAccount->getUrl();
+ url.setPath(mFolder->imapPath() + QString(";UID=%1;SECTION=BODY.PEEK[]").arg(mfd.uid));
+
+ ImapAccountBase::jobData jd( url.url(), mFolder->folder() );
+ jd.cancellable = true;
+ mMsg->setTransferInProgress(true);
+ KIO::SimpleJob *simpleJob = KIO::get(url, false, false);
+ KIO::Scheduler::assignJobToSlave(mAccount->slave(), simpleJob);
+ mAccount->insertJob(simpleJob, jd);
+ connect(simpleJob, SIGNAL(processedSize(KIO::Job *, KIO::filesize_t)),
+ this, SLOT(slotProcessedSize(KIO::Job *, KIO::filesize_t)));
+ connect(simpleJob, SIGNAL(result(KIO::Job *)),
+ this, SLOT(slotGetNextMessage(KIO::Job *)));
+ connect(simpleJob, SIGNAL(data(KIO::Job *, const QByteArray &)),
+ mFolder, SLOT(slotSimpleData(KIO::Job *, const QByteArray &)));
+}
+
+void CachedImapJob::slotProcessedSize(KIO::Job *, KIO::filesize_t processed)
+{
+ emit progress( mSentBytes + processed, mTotalBytes );
+}
+
+void CachedImapJob::slotPutNextMessage()
+{
+ mMsg = 0;
+
+ // First try the message list
+ if( !mMsgList.isEmpty() ) {
+ mMsg = mMsgList.first();
+ mMsgList.removeFirst();
+ }
+
+ // Now try the serial number list
+ while( mMsg == 0 && !mSerNumMsgList.isEmpty() ) {
+ unsigned long serNum = mSerNumMsgList.first();
+ mSerNumMsgList.pop_front();
+
+ // Find the message with this serial number
+ int i = 0;
+ KMFolder* aFolder = 0;
+ KMMsgDict::instance()->getLocation( serNum, &aFolder, &i );
+ if( mFolder->folder() != aFolder )
+ // This message was moved or something
+ continue;
+ mMsg = mFolder->getMsg( i );
+ }
+
+ if( !mMsg ) {
+ // No message found for upload
+ delete this;
+ return;
+ }
+
+ KURL url = mAccount->getUrl();
+ QString flags = KMFolderImap::statusToFlags( mMsg->status(), mFolder->permanentFlags() );
+ url.setPath( mFolder->imapPath() + ";SECTION=" + flags );
+
+ ImapAccountBase::jobData jd( url.url(), mFolder->folder() );
+
+ mMsg->setUID( 0 ); // for the index
+ QCString cstr(mMsg->asString());
+ int a = cstr.find("\nX-UID: ");
+ int b = cstr.find('\n', a);
+ if (a != -1 && b != -1 && cstr.find("\n\n") > a) cstr.remove(a, b-a);
+ QCString mData(cstr.length() + cstr.contains('\n'));
+ unsigned int i = 0;
+ for( char *ch = cstr.data(); *ch; ch++ ) {
+ if ( *ch == '\n' ) {
+ mData.at(i) = '\r';
+ i++;
+ }
+ mData.at(i) = *ch; i++;
+ }
+ jd.data = mData;
+ jd.msgList.append( mMsg );
+
+ mMsg->setTransferInProgress(true);
+ KIO::SimpleJob *simpleJob = KIO::put(url, 0, false, false, false);
+ KIO::Scheduler::assignJobToSlave(mAccount->slave(), simpleJob);
+ mAccount->insertJob(simpleJob, jd);
+ connect( simpleJob, SIGNAL( result(KIO::Job *) ),
+ SLOT( slotPutMessageResult(KIO::Job *) ) );
+ connect( simpleJob, SIGNAL( dataReq(KIO::Job *, QByteArray &) ),
+ SLOT( slotPutMessageDataReq(KIO::Job *, QByteArray &) ) );
+ connect( simpleJob, SIGNAL( data(KIO::Job *, const QByteArray &) ),
+ mFolder, SLOT( slotSimpleData(KIO::Job *, const QByteArray &) ) );
+ connect( simpleJob, SIGNAL(infoMessage(KIO::Job *, const QString &)),
+ SLOT(slotPutMessageInfoData(KIO::Job *, const QString &)) );
+
+}
+
+//-----------------------------------------------------------------------------
+// TODO: port to KIO::StoredTransferJob once it's ok to require kdelibs-3.3
+void CachedImapJob::slotPutMessageDataReq(KIO::Job *job, QByteArray &data)
+{
+ KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
+ if ( it == mAccount->jobsEnd() ) { // Shouldn't happen
+ delete this;
+ return;
+ }
+ if ((*it).data.size() - (*it).offset > 0x8000) {
+ data.duplicate((*it).data.data() + (*it).offset, 0x8000);
+ (*it).offset += 0x8000;
+ } else if ((*it).data.size() - (*it).offset > 0) {
+ data.duplicate((*it).data.data() + (*it).offset,
+ (*it).data.size() - (*it).offset);
+ (*it).offset = (*it).data.size();
+ } else
+ data.resize(0);
+}
+
+//----------------------------------------------------------------------------
+void CachedImapJob::slotPutMessageInfoData(KIO::Job *job, const QString &data)
+{
+ KMFolderCachedImap * imapFolder = static_cast<KMFolderCachedImap*>(mDestFolder->storage());
+ 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 );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void CachedImapJob::slotPutMessageResult(KIO::Job *job)
+{
+ KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
+ if ( it == mAccount->jobsEnd() ) { // Shouldn't happen
+ delete this;
+ return;
+ }
+
+ if ( job->error() ) {
+ bool cont = mAccount->handlePutError( job, *it, mFolder->folder() );
+ if ( !cont ) {
+ delete this;
+ } else {
+ mMsg = 0;
+ slotPutNextMessage();
+ }
+ return;
+ }
+
+ emit messageStored( mMsg );
+
+ // we abuse those fields, the unit is the number of messages, here
+ ++mSentBytes;
+ emit progress( mSentBytes, mTotalBytes );
+
+ int i;
+ if( ( i = mFolder->find(mMsg) ) != -1 ) {
+ /*
+ * If we have aquired a uid during upload the server supports the uidnext
+ * extension and there is no need to redownload this mail, we already have
+ * it. Otherwise remove it, it will be redownloaded.
+ */
+ if ( mMsg->UID() == 0 ) {
+ mFolder->removeMsg(i);
+ } else {
+ // When removing+readding, no point in telling the imap resources about it
+ bool b = kmkernel->iCalIface().isResourceQuiet();
+ kmkernel->iCalIface().setResourceQuiet( true );
+
+ mFolder->takeTemporarily( i );
+ mFolder->addMsgKeepUID( mMsg );
+ mMsg->setTransferInProgress( false );
+
+ kmkernel->iCalIface().setResourceQuiet( b );
+ }
+ }
+ mMsg = NULL;
+ mAccount->removeJob( it );
+ slotPutNextMessage();
+}
+
+
+void CachedImapJob::slotAddNextSubfolder( KIO::Job * job )
+{
+ if (job) {
+ KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
+ if ( it == mAccount->jobsEnd() ) { // Shouldn't happen
+ delete this;
+ return;
+ }
+
+ // make copy of setting, to reset it before potentially destroying 'it'
+ bool silentUpload = static_cast<KMFolderCachedImap*>((*it).parent->storage())->silentUpload();
+ static_cast<KMFolderCachedImap*>((*it).parent->storage())->setSilentUpload( false );
+
+ if ( job->error() && !silentUpload ) {
+ QString myError = "<p><b>" + i18n("Error while uploading folder")
+ + "</b></p><p>" + i18n("Could not make the folder <b>%1</b> on the server.").arg((*it).items[0])
+ + "</p><p>" + i18n("This could be because you do not have permission to do this, or because the folder is already present on the server; the error message from the server communication is here:") + "</p>";
+ mAccount->handleJobError( job, myError );
+ }
+
+ if( job->error() ) {
+ delete this;
+ return;
+ } else {
+ KMFolderCachedImap* storage = static_cast<KMFolderCachedImap*>( (*it).current->storage() );
+ KMFolderCachedImap* parentStorage = static_cast<KMFolderCachedImap*>( (*it).parent->storage() );
+ Q_ASSERT( storage );
+ Q_ASSERT( parentStorage );
+ if ( storage->imapPath().isEmpty() ) {
+ QString path = mAccount->createImapPath( parentStorage->imapPath(), storage->folder()->name() );
+ if ( !storage->imapPathForCreation().isEmpty() )
+ path = storage->imapPathForCreation();
+ storage->setImapPath( path );
+ storage->writeConfig();
+ }
+ }
+ mAccount->removeJob( it );
+ }
+
+ if (mFolderList.isEmpty()) {
+ // No more folders to add
+ delete this;
+ return;
+ }
+
+ KMFolderCachedImap *folder = mFolderList.front();
+ mFolderList.pop_front();
+ KURL url = mAccount->getUrl();
+ QString path = mAccount->createImapPath( mFolder->imapPath(),
+ folder->folder()->name() );
+ if ( !folder->imapPathForCreation().isEmpty() ) {
+ // the folder knows it's namespace
+ path = folder->imapPathForCreation();
+ }
+ url.setPath( path );
+
+ if ( mAccount->groupwareType() != KMAcctCachedImap::GroupwareScalix ) {
+ // Associate the jobData with the parent folder, not with the child
+ // This is necessary in case of an error while creating the subfolder,
+ // so that folderComplete is called on the parent (and the sync resetted).
+ ImapAccountBase::jobData jd( url.url(), mFolder->folder() );
+ jd.items << folder->label(); // for the err msg
+ jd.current = folder->folder();
+ KIO::SimpleJob *simpleJob = KIO::mkdir(url);
+ KIO::Scheduler::assignJobToSlave(mAccount->slave(), simpleJob);
+ mAccount->insertJob(simpleJob, jd);
+ connect( simpleJob, SIGNAL(result(KIO::Job *)),
+ this, SLOT(slotAddNextSubfolder(KIO::Job *)) );
+ } else {
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+
+ const QString command = QString( "X-CREATE-SPECIAL" );
+ const QString argument = QString( "%1 %2" ).arg( Scalix::Utils::contentsTypeToScalixId( folder->contentsType() ) )
+ .arg( path );
+
+ stream << (int) 'X' << 'N' << command << argument;
+
+ ImapAccountBase::jobData jd( url.url(), mFolder->folder() );
+ jd.items << folder->label(); // for the err msg
+ jd.current = folder->folder();
+ KIO::SimpleJob *simpleJob = KIO::special( url.url(), packedArgs, false );
+ KIO::Scheduler::assignJobToSlave(mAccount->slave(), simpleJob);
+ mAccount->insertJob(simpleJob, jd);
+ connect( simpleJob, SIGNAL(result(KIO::Job *)),
+ this, SLOT(slotAddNextSubfolder(KIO::Job *)) );
+ }
+}
+
+
+void CachedImapJob::slotDeleteNextFolder( KIO::Job *job )
+{
+ if (job) {
+ KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
+ if ( it == mAccount->jobsEnd() ) { // Shouldn't happen
+ delete this;
+ return;
+ }
+
+ mAccount->removeDeletedFolder( (*it).path );
+
+ if( job->error() ) {
+ mAccount->handleJobError( job, i18n( "Error while deleting folder %1 on the server: " ).arg( (*it).path ) + '\n' );
+ delete this;
+ return;
+ }
+ mAccount->removeJob(it);
+ }
+
+ if( mFoldersOrMessages.isEmpty() ) {
+ // No more folders to delete
+ delete this;
+ return;
+ }
+
+ QString folderPath = mFoldersOrMessages.front();
+ mFoldersOrMessages.pop_front();
+ KURL url = mAccount->getUrl();
+ url.setPath(folderPath);
+ ImapAccountBase::jobData jd( url.url(), mFolder->folder() );
+ jd.path = url.path();
+ KIO::SimpleJob *simpleJob = KIO::file_delete(url, false);
+ KIO::Scheduler::assignJobToSlave(mAccount->slave(), simpleJob);
+ mAccount->insertJob(simpleJob, jd);
+ connect( simpleJob, SIGNAL( result(KIO::Job *) ),
+ SLOT( slotDeleteNextFolder(KIO::Job *) ) );
+}
+
+void CachedImapJob::checkUidValidity()
+{
+ KURL url = mAccount->getUrl();
+ url.setPath( mFolder->imapPath() + ";UID=0:0" );
+
+ ImapAccountBase::jobData jd( url.url(), mFolder->folder() );
+ jd.cancellable = true;
+
+ KIO::SimpleJob *job = KIO::get( url, false, false );
+ KIO::Scheduler::assignJobToSlave( mAccount->slave(), job );
+ mAccount->insertJob( job, jd );
+ connect( job, SIGNAL(result(KIO::Job *)),
+ SLOT(slotCheckUidValidityResult(KIO::Job *)) );
+ connect( job, SIGNAL(data(KIO::Job *, const QByteArray &)),
+ mFolder, SLOT(slotSimpleData(KIO::Job *, const QByteArray &)));
+}
+
+void CachedImapJob::slotCheckUidValidityResult(KIO::Job * job)
+{
+ KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
+ if ( it == mAccount->jobsEnd() ) { // Shouldn't happen
+ delete this;
+ return;
+ }
+
+ if( job->error() ) {
+ mErrorCode = job->error();
+ mAccount->handleJobError( job, i18n( "Error while reading folder %1 on the server: " ).arg( (*it).parent->label() ) + '\n' );
+ delete this;
+ return;
+ }
+
+ // Check the uidValidity
+ QCString cstr((*it).data.data(), (*it).data.size() + 1);
+ int a = cstr.find("X-uidValidity: ");
+ if (a < 0) {
+ // Something is seriously rotten here!
+ // TODO: Tell the user that he has a problem
+ kdDebug(5006) << "No uidvalidity available for folder "
+ << mFolder->name() << endl;
+ }
+ else {
+ int b = cstr.find("\r\n", a);
+ if ( (b - a - 15) >= 0 ) {
+ QString uidv = cstr.mid(a + 15, b - a - 15);
+ // kdDebug(5006) << "New uidv = " << uidv << ", old uidv = "
+ // << mFolder->uidValidity() << endl;
+ if( !mFolder->uidValidity().isEmpty() && mFolder->uidValidity() != uidv ) {
+ // kdDebug(5006) << "Expunging the mailbox " << mFolder->name()
+ // << "!" << endl;
+ mFolder->expunge();
+ mFolder->setLastUid( 0 );
+ mFolder->clearUidMap();
+ }
+ } else
+ kdDebug(5006) << "No uidvalidity available for folder "
+ << mFolder->name() << endl;
+ }
+
+ a = cstr.find( "X-PermanentFlags: " );
+ if ( a < 0 ) {
+ kdDebug(5006) << "no PERMANENTFLAGS response? assumming custom flags are not available" << endl;
+ } else {
+ int b = cstr.find( "\r\n", a );
+ if ( (b - a - 18) >= 0 ) {
+ int flags = cstr.mid( a + 18, b - a - 18 ).toInt();
+ emit permanentFlags( flags );
+ } else {
+ kdDebug(5006) << "PERMANENTFLAGS response broken, assumming custom flags are not available" << endl;
+ }
+ }
+
+ mAccount->removeJob(it);
+ delete this;
+}
+
+
+void CachedImapJob::renameFolder( const QString &newName )
+{
+ // Set the source URL
+ KURL urlSrc = mAccount->getUrl();
+ urlSrc.setPath( mFolder->imapPath() );
+
+ // Set the destination URL - this is a bit trickier
+ KURL urlDst = mAccount->getUrl();
+ QString imapPath( mFolder->imapPath() );
+ // Destination url = old imappath - oldname + new name
+ imapPath.truncate( imapPath.length() - mFolder->folder()->name().length() - 1);
+ imapPath += newName + '/';
+ urlDst.setPath( imapPath );
+
+ ImapAccountBase::jobData jd( newName, mFolder->folder() );
+ jd.path = imapPath;
+
+ KIO::SimpleJob *simpleJob = KIO::rename( urlSrc, urlDst, false );
+ KIO::Scheduler::assignJobToSlave( mAccount->slave(), simpleJob );
+ mAccount->insertJob( simpleJob, jd );
+ connect( simpleJob, SIGNAL(result(KIO::Job *)),
+ SLOT(slotRenameFolderResult(KIO::Job *)) );
+}
+
+static void renameChildFolders( KMFolderDir* dir, const QString& oldPath,
+ const QString& newPath )
+{
+ if( dir ) {
+ KMFolderNode *node = dir->first();
+ while( node ) {
+ if( !node->isDir() ) {
+ KMFolderCachedImap* imapFolder =
+ static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
+ if ( !imapFolder->imapPath().isEmpty() )
+ // Only rename folders that have been accepted by the server
+ if( imapFolder->imapPath().find( oldPath ) == 0 ) {
+ QString p = imapFolder->imapPath();
+ p = p.mid( oldPath.length() );
+ p.prepend( newPath );
+ imapFolder->setImapPath( p );
+ renameChildFolders( imapFolder->folder()->child(), oldPath, newPath );
+ }
+ }
+ node = dir->next();
+ }
+ }
+}
+
+void CachedImapJob::slotRenameFolderResult( KIO::Job *job )
+{
+ KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
+ if ( it == mAccount->jobsEnd() ) { // Shouldn't happen
+ delete this;
+ return;
+ }
+
+
+ if( job->error() ) {
+ // Error, revert label change
+ QMap<QString, KMAcctCachedImap::RenamedFolder>::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' );
+ } else {
+ // Okay, the folder seems to be renamed on the server,
+ // now rename it on disk
+ QString oldName = mFolder->name();
+ QString oldPath = mFolder->imapPath();
+ mAccount->removeRenamedFolder( oldPath );
+ mFolder->setImapPath( (*it).path );
+ mFolder->FolderStorage::rename( (*it).url );
+
+ if( oldPath.endsWith( "/" ) ) oldPath.truncate( oldPath.length() -1 );
+ QString newPath = mFolder->imapPath();
+ if( newPath.endsWith( "/" ) ) newPath.truncate( newPath.length() -1 );
+ renameChildFolders( mFolder->folder()->child(), oldPath, newPath );
+ kmkernel->dimapFolderMgr()->contentsChanged();
+
+ mAccount->removeJob(it);
+ }
+ delete this;
+}
+
+void CachedImapJob::slotListMessagesResult( KIO::Job * job )
+{
+ KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
+ if ( it == mAccount->jobsEnd() ) { // Shouldn't happen
+ delete this;
+ return;
+ }
+
+ if (job->error()) {
+ mErrorCode = job->error();
+ mAccount->handleJobError( job, i18n( "Error while deleting messages on the server: " ) + '\n' );
+ }
+ else
+ mAccount->removeJob(it);
+
+ delete this;
+}
+
+//-----------------------------------------------------------------------------
+void CachedImapJob::setParentFolder( const KMFolderCachedImap* parent )
+{
+ mParentFolder = const_cast<KMFolderCachedImap*>( parent );
+}
+
+}
+
+#include "cachedimapjob.moc"
diff --git a/kmail/cachedimapjob.h b/kmail/cachedimapjob.h
new file mode 100644
index 00000000..ad5579a0
--- /dev/null
+++ b/kmail/cachedimapjob.h
@@ -0,0 +1,141 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ *
+ * This file is part of KMail, the KDE mail client.
+ * Copyright (c) 2002-2003 Bo Thorsen <bo@sonofthor.dk>
+ * 2002-2003 Steffen Hansen <hansen@kde.org>
+ * 2002-2003 Zack Rusin <zack@kde.org>
+ *
+ * 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 Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#ifndef CACHEDIMAPJOB_H
+#define CACHEDIMAPJOB_H
+
+#include "folderjob.h"
+#include <kio/job.h>
+#include <kio/global.h>
+
+#include <qptrlist.h>
+#include <qvaluelist.h>
+#include <qcstring.h>
+
+class KMFolderCachedImap;
+class KMAcctCachedImap;
+class KMMessage;
+
+namespace KMail {
+
+class CachedImapJob : public FolderJob {
+ Q_OBJECT
+public:
+ /** Information about a message to be downloaded (from the 'IMAP envelope') */
+ struct MsgForDownload {
+ MsgForDownload() : uid(0),flags(0),size(0) {} // for QValueList only
+ MsgForDownload( ulong _uid, int _flags, ulong _size )
+ : uid(_uid), flags(_flags), size(_size) {}
+ ulong uid;
+ int flags;
+ ulong size;
+ };
+
+ // Get messages
+ CachedImapJob( const QValueList<MsgForDownload>& msgs,
+ JobType type = tGetMessage, KMFolderCachedImap* folder = 0 );
+ // Put messages
+ CachedImapJob( const QPtrList<KMMessage>& msgs,
+ JobType type, KMFolderCachedImap* folder=0 );
+ CachedImapJob( const QValueList<unsigned long>& msgs,
+ JobType type, KMFolderCachedImap* folder=0 );
+ // Add sub folders
+ CachedImapJob( const QValueList<KMFolderCachedImap*>& folders,
+ JobType type = tAddSubfolders,
+ KMFolderCachedImap* folder = 0 );
+ // Rename folder
+ CachedImapJob( const QString& string1, JobType type,
+ KMFolderCachedImap* folder );
+ // Delete folders or messages
+ CachedImapJob( const QStringList& foldersOrMsgs, JobType type,
+ KMFolderCachedImap* folder );
+ // Other jobs (list messages,expunge folder, check uid validity)
+ CachedImapJob( JobType type, KMFolderCachedImap* folder );
+
+ virtual ~CachedImapJob();
+
+ void setParentFolder( const KMFolderCachedImap* parent );
+
+signals:
+ // only emitted for uid validity checking jobs with PERMANENTFLAGS responses
+ void permanentFlags( int flags );
+
+protected:
+ virtual void execute();
+ void expungeFolder();
+ void checkUidValidity();
+ void renameFolder( const QString &newName );
+ void listMessages();
+
+protected slots:
+ virtual void slotGetNextMessage( KIO::Job *job = 0 );
+ virtual void slotAddNextSubfolder( KIO::Job *job = 0 );
+ virtual void slotPutNextMessage();
+ virtual void slotPutMessageDataReq( KIO::Job *job, QByteArray &data );
+ virtual void slotPutMessageResult( KIO::Job *job );
+ virtual void slotPutMessageInfoData(KIO::Job *, const QString &data);
+ virtual void slotExpungeResult( KIO::Job *job );
+ virtual void slotDeleteNextFolder( KIO::Job *job = 0 );
+ virtual void slotCheckUidValidityResult( KIO::Job *job );
+ virtual void slotRenameFolderResult( KIO::Job *job );
+ virtual void slotListMessagesResult( KIO::Job * job );
+ void slotDeleteNextMessages( KIO::Job* job = 0 );
+ void slotProcessedSize( KIO::Job *, KIO::filesize_t processed );
+
+private:
+ KMFolderCachedImap *mFolder;
+ KMAcctCachedImap *mAccount;
+ QValueList<KMFolderCachedImap*> mFolderList;
+ QValueList<MsgForDownload> mMsgsForDownload;
+ QValueList<unsigned long> mSerNumMsgList;
+ ulong mSentBytes; // previous messages
+ ulong mTotalBytes;
+ QStringList mFoldersOrMessages; // Folder deletion: path list. Message deletion: sets of uids
+ KMMessage* mMsg;
+ QString mString; // Used as uids and as rename target
+ KMFolderCachedImap *mParentFolder;
+};
+
+}
+
+#endif
diff --git a/kmail/callback.cpp b/kmail/callback.cpp
new file mode 100644
index 00000000..e124e0cc
--- /dev/null
+++ b/kmail/callback.cpp
@@ -0,0 +1,219 @@
+/* -*- c++ -*-
+ callback.cpp
+
+ This file is used by KMail's plugin interface.
+ Copyright (c) 2004 Bo Thorsen <bo@sonofthor.dk>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "callback.h"
+#include "kmkernel.h"
+#include "kmmessage.h"
+#include "kmmsgpart.h"
+#include <libemailfunctions/email.h>
+#include <libkpimidentities/identity.h>
+#include <libkpimidentities/identitymanager.h>
+#include "kmmainwin.h"
+#include "composer.h"
+#include "kmreaderwin.h"
+#include "secondarywindow.h"
+
+#include <mimelib/enum.h>
+
+#include <kinputdialog.h>
+#include <klocale.h>
+#include <kdebug.h>
+
+using namespace KMail;
+
+
+Callback::Callback( KMMessage* msg, KMReaderWin* readerWin )
+ : mMsg( msg ), mReaderWin( readerWin ), mReceiverSet( false )
+{
+}
+
+bool Callback::mailICal( const QString& to, const QString &iCal,
+ const QString& subject, const QString &status,
+ bool delMessage ) const
+{
+ kdDebug(5006) << "Mailing message:\n" << iCal << endl;
+
+ KMMessage *msg = new KMMessage;
+ msg->initHeader();
+ msg->setSubject( subject );
+ if ( GlobalSettings::self()->exchangeCompatibleInvitations() ) {
+ if ( status == QString("cancel") )
+ msg->setSubject( QString("Declined: %1").arg(subject).replace("Answer: ","") );
+ else if ( status == QString("tentative") )
+ msg->setSubject(QString("Tentative: %1").arg(subject).replace("Answer: ","") );
+ else if ( status == QString("accepted") )
+ msg->setSubject( QString("Accepted: %1").arg(subject).replace("Answer: ","") );
+ else if ( status == QString("delegated") )
+ msg->setSubject( QString("Delegated: %1").arg(subject).replace("Answer: ","") );
+ }
+ msg->setTo( to );
+ msg->setFrom( receiver() );
+
+ if ( !GlobalSettings::self()->exchangeCompatibleInvitations() ) {
+ msg->setHeaderField( "Content-Type",
+ "text/calendar; method=reply; charset=\"utf-8\"" );
+ msg->setBody( iCal.utf8() );
+ }
+
+ if ( delMessage && deleteInvitationAfterReply() )
+ /* We want the triggering mail to be moved to the trash once this one
+ * has been sent successfully. Set a link header which accomplishes that. */
+ msg->link( mMsg, KMMsgStatusDeleted );
+
+ // 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", QString::number( identity.uoid() ));
+ }
+ // Remove BCC from identity on ical invitations (https://intevation.de/roundup/kolab/issue474)
+ msg->setBcc( "" );
+ }
+
+ KMail::Composer * cWin = KMail::makeComposer();
+ cWin->setMsg( msg, false /* mayAutoSign */ );
+ // cWin->setCharset( "", true );
+ cWin->disableWordWrap();
+ cWin->setSigningAndEncryptionDisabled( true );
+
+ if( GlobalSettings::self()->exchangeCompatibleInvitations() ) {
+ // For Exchange, send ical as attachment, with proper
+ // parameters
+ msg->setSubject( status );
+ msg->setCharset( "utf-8" );
+ KMMessagePart *msgPart = new KMMessagePart;
+ msgPart->setName( "cal.ics" );
+ // msgPart->setCteStr( attachCte ); // "base64" ?
+ msgPart->setBodyEncoded( iCal.utf8() );
+ msgPart->setTypeStr( "text" );
+ msgPart->setSubtypeStr( "calendar" );
+ msgPart->setParameter( "method", "reply" );
+ cWin->addAttach( msgPart );
+ }
+
+ if ( options.readBoolEntry( "AutomaticSending", true ) ) {
+ cWin->setAutoDeleteWindow( true );
+ cWin->slotSendNow();
+ } else {
+ cWin->show();
+ }
+
+ return true;
+}
+
+QString Callback::receiver() const
+{
+ if ( mReceiverSet )
+ // Already figured this out
+ return mReceiver;
+
+ mReceiverSet = true;
+
+ QStringList addrs = KPIM::splitEmailAddrList( mMsg->to() );
+ int found = 0;
+ for( QStringList::Iterator it = addrs.begin(); it != addrs.end(); ++it ) {
+ if( kmkernel->identityManager()->identityForAddress( *it ) !=
+ KPIM::Identity::null() ) {
+ // Ok, this could be us
+ ++found;
+ mReceiver = *it;
+ }
+ }
+ QStringList ccaddrs = KPIM::splitEmailAddrList( mMsg->cc() );
+ for( QStringList::Iterator it = ccaddrs.begin(); it != ccaddrs.end(); ++it ) {
+ if( kmkernel->identityManager()->identityForAddress( *it ) !=
+ KPIM::Identity::null() ) {
+ // Ok, this could be us
+ ++found;
+ mReceiver = *it;
+ }
+ }
+ if( found != 1 ) {
+ bool ok;
+ QString selectMessage;
+ if (found == 0) {
+ selectMessage = i18n("<qt>None of your identities match the "
+ "receiver of this message,<br>please "
+ "choose which of the following addresses "
+ "is yours, if any:");
+ addrs += kmkernel->identityManager()->allEmails();
+ } else {
+ selectMessage = i18n("<qt>Several of your identities match the "
+ "receiver of this message,<br>please "
+ "choose which of the following addresses "
+ "is yours:");
+ }
+
+ mReceiver =
+ KInputDialog::getItem( i18n( "Select Address" ),
+ selectMessage,
+ addrs+ccaddrs, 0, FALSE, &ok, kmkernel->mainWin() );
+ if( !ok )
+ mReceiver = QString::null;
+ }
+
+ return mReceiver;
+}
+
+void Callback::closeIfSecondaryWindow() const
+{
+ KMail::SecondaryWindow *window = dynamic_cast<KMail::SecondaryWindow*>( mReaderWin->mainWindow() );
+ if ( window )
+ window->close();
+}
+
+bool Callback::askForComment( KCal::Attendee::PartStat status ) const
+{
+ if ( ( status != KCal::Attendee::Accepted
+ && GlobalSettings::self()->askForCommentWhenReactingToInvitation()
+ == GlobalSettings:: EnumAskForCommentWhenReactingToInvitation::AskForAllButAcceptance )
+ || GlobalSettings::self()->askForCommentWhenReactingToInvitation()
+ == GlobalSettings:: EnumAskForCommentWhenReactingToInvitation::AlwaysAsk )
+ return true;
+ return false;
+}
+
+bool Callback::deleteInvitationAfterReply() const
+{
+ return GlobalSettings::self()->deleteInvitationEmailsAfterSendingReply();
+}
+
+QString Callback::sender() const
+{
+ return mMsg->from();
+}
diff --git a/kmail/callback.h b/kmail/callback.h
new file mode 100644
index 00000000..b66903bf
--- /dev/null
+++ b/kmail/callback.h
@@ -0,0 +1,91 @@
+/* -*- c++ -*-
+ callback.h
+
+ This file is used by KMail's plugin interface.
+ Copyright (c) 2004 Bo Thorsen <bo@sonofthor.dk>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef CALLBACK_H
+#define CALLBACK_H
+
+#include <qstring.h>
+
+class KMMessage;
+class KMReaderWin;
+
+#include <kdepimmacros.h>
+
+
+#include <libkcal/attendee.h> // only for an enum, we are not linking
+
+
+namespace KMail {
+
+/** This class is used for callback hooks needed by bodypart
+ formatter plugins. It also holds a pointer to the message we are
+ working on.
+ Feel free to put whatever you want in here. It's not supposed to be
+ a nicely formatted class, but simply include everything necessary
+ for the plugins. */
+class KDE_EXPORT Callback {
+public:
+ Callback( KMMessage* msg, KMReaderWin* readerWin );
+
+ /** Get the full message */
+ KMMessage* getMsg() const { return mMsg; }
+
+ /** Mail a message
+ * @ param status can be accepted/cancel/tentative/delegated
+ */
+ bool mailICal( const QString &to, const QString &iCal,
+ const QString &subject, const QString &status,
+ bool delMessage = true ) const;
+
+ /** Get the receiver of the mail */
+ QString receiver() const;
+
+ /** Returns the sender of the mail. */
+ QString sender() const;
+
+ /** Close the main window showing this message, if it's a secondary window. */
+ void closeIfSecondaryWindow() const;
+
+ bool askForComment( KCal::Attendee::PartStat status ) const;
+ bool deleteInvitationAfterReply() const;
+
+private:
+ KMMessage* mMsg;
+ KMReaderWin* mReaderWin;
+ mutable QString mReceiver;
+ mutable bool mReceiverSet;
+};
+
+}
+
+#endif /* CALLBACK_H */
+
diff --git a/kmail/chiasmuskeyselector.cpp b/kmail/chiasmuskeyselector.cpp
new file mode 100644
index 00000000..aac44362
--- /dev/null
+++ b/kmail/chiasmuskeyselector.cpp
@@ -0,0 +1,55 @@
+#include "chiasmuskeyselector.h"
+
+#include <klineedit.h>
+#include <klistbox.h>
+#include <klocale.h>
+
+#include <qlayout.h>
+#include <qlabel.h>
+
+ChiasmusKeySelector::ChiasmusKeySelector( QWidget* parent, const QString& caption,
+ const QStringList& keys, const QString& currentKey,
+ const QString& lastOptions )
+ : KDialogBase( parent, "chiasmusKeySelector", true, caption, Ok|Cancel, Ok, true )
+{
+ QWidget *page = makeMainWidget();
+
+ QVBoxLayout *layout = new QVBoxLayout(page, KDialog::spacingHint());
+
+ mLabel = new QLabel( i18n( "Please select the Chiasmus key file to use:" ), page );
+ layout->addWidget( mLabel );
+
+ mListBox = new KListBox( page );
+ mListBox->insertStringList( keys );
+ const int current = keys.findIndex( currentKey );
+ mListBox->setSelected( QMAX( 0, current ), true );
+ mListBox->ensureCurrentVisible();
+ layout->addWidget( mListBox, 1 );
+
+ QLabel* optionLabel = new QLabel( i18n( "Additional arguments for chiasmus:" ), page );
+ layout->addWidget( optionLabel );
+
+ mOptions = new KLineEdit( lastOptions, page );
+ optionLabel->setBuddy( mOptions );
+ layout->addWidget( mOptions );
+
+ layout->addStretch();
+
+ connect( mListBox, SIGNAL( doubleClicked( QListBoxItem * ) ), this, SLOT( slotOk() ) );
+ connect( mListBox, SIGNAL( returnPressed( QListBoxItem * ) ), this, SLOT( slotOk() ) );
+
+ mListBox->setFocus();
+}
+
+QString ChiasmusKeySelector::key() const
+{
+ return mListBox->currentText();
+}
+
+QString ChiasmusKeySelector::options() const
+{
+ return mOptions->text();
+}
+
+
+#include "chiasmuskeyselector.moc"
diff --git a/kmail/chiasmuskeyselector.h b/kmail/chiasmuskeyselector.h
new file mode 100644
index 00000000..3af0ec45
--- /dev/null
+++ b/kmail/chiasmuskeyselector.h
@@ -0,0 +1,27 @@
+#ifndef CHIASMUSKEYSELECTOR_H
+#define CHIASMUSKEYSELECTOR_H
+
+#include <kdialogbase.h>
+class KListBox;
+class KLineEdit;
+class QLabel;
+
+class ChiasmusKeySelector : public KDialogBase
+{
+ Q_OBJECT
+
+public:
+ ChiasmusKeySelector( QWidget* parent, const QString& caption,
+ const QStringList& keys, const QString& currentKey,
+ const QString& lastOptions );
+
+ QString key() const;
+ QString options() const;
+
+private:
+ QLabel* mLabel;
+ KListBox* mListBox;
+ KLineEdit* mOptions;
+};
+
+#endif
diff --git a/kmail/colorlistbox.cpp b/kmail/colorlistbox.cpp
new file mode 100644
index 00000000..2c6887b3
--- /dev/null
+++ b/kmail/colorlistbox.cpp
@@ -0,0 +1,198 @@
+/*
+ * kmail: KDE mail client
+ * This file: Copyright (C) 2000 Espen Sand, espen@kde.org
+ *
+ * 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.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <qpainter.h>
+
+#include <kcolordialog.h>
+#include <kcolordrag.h>
+
+#include "colorlistbox.h"
+
+ColorListBox::ColorListBox( QWidget *parent, const char *name, WFlags f )
+ :KListBox( parent, name, f ), mCurrentOnDragEnter(-1)
+{
+ connect( this, SIGNAL(selected(int)), this, SLOT(newColor(int)) );
+ setAcceptDrops( true);
+}
+
+
+void ColorListBox::setEnabled( bool state )
+{
+ if( state == isEnabled() )
+ {
+ return;
+ }
+
+ QListBox::setEnabled( state );
+ for( uint i=0; i<count(); i++ )
+ {
+ updateItem( i );
+ }
+}
+
+
+void ColorListBox::setColor( uint index, const QColor &color )
+{
+ if( index < count() )
+ {
+ ColorListItem *colorItem = (ColorListItem*)item(index);
+ colorItem->setColor(color);
+ updateItem( colorItem );
+ emit changed();
+ }
+}
+
+
+QColor ColorListBox::color( uint index ) const
+{
+ if( index < count() )
+ {
+ ColorListItem *colorItem = (ColorListItem*)item(index);
+ return( colorItem->color() );
+ }
+ else
+ {
+ return( black );
+ }
+}
+
+
+void ColorListBox::newColor( int index )
+{
+ if( isEnabled() == false )
+ {
+ return;
+ }
+
+ if( (uint)index < count() )
+ {
+ QColor c = color( index );
+ if( KColorDialog::getColor( c, this ) != QDialog::Rejected )
+ {
+ setColor( index, c );
+ }
+ }
+}
+
+
+void ColorListBox::dragEnterEvent( QDragEnterEvent *e )
+{
+ if( KColorDrag::canDecode(e) && isEnabled() )
+ {
+ mCurrentOnDragEnter = currentItem();
+ e->accept( true );
+ }
+ else
+ {
+ mCurrentOnDragEnter = -1;
+ e->accept( false );
+ }
+}
+
+
+void ColorListBox::dragLeaveEvent( QDragLeaveEvent * )
+{
+ if( mCurrentOnDragEnter != -1 )
+ {
+ setCurrentItem( mCurrentOnDragEnter );
+ mCurrentOnDragEnter = -1;
+ }
+}
+
+
+void ColorListBox::dragMoveEvent( QDragMoveEvent *e )
+{
+ if( KColorDrag::canDecode(e) && isEnabled() )
+ {
+ ColorListItem *item = (ColorListItem*)itemAt( e->pos() );
+ if( item != 0 )
+ {
+ setCurrentItem ( item );
+ }
+ }
+}
+
+
+void ColorListBox::dropEvent( QDropEvent *e )
+{
+ QColor color;
+ if( KColorDrag::decode( e, color ) )
+ {
+ int index = currentItem();
+ if( index != -1 )
+ {
+ ColorListItem *colorItem = (ColorListItem*)item(index);
+ colorItem->setColor(color);
+ triggerUpdate( false ); // Redraw item
+ }
+ mCurrentOnDragEnter = -1;
+ }
+}
+
+
+
+ColorListItem::ColorListItem( const QString &text, const QColor &color )
+ : QListBoxItem(), mColor( color ), mBoxWidth( 30 )
+{
+ setText( text );
+}
+
+
+const QColor &ColorListItem::color( void )
+{
+ return( mColor );
+}
+
+
+void ColorListItem::setColor( const QColor &color )
+{
+ mColor = color;
+}
+
+
+void ColorListItem::paint( QPainter *p )
+{
+ QFontMetrics fm = p->fontMetrics();
+ int h = fm.height();
+
+ p->drawText( mBoxWidth+3*2, fm.ascent() + fm.leading()/2, text() );
+
+ p->setPen( Qt::black );
+ p->drawRect( 3, 1, mBoxWidth, h-1 );
+ p->fillRect( 4, 2, mBoxWidth-2, h-3, mColor );
+}
+
+
+int ColorListItem::height(const QListBox *lb ) const
+{
+ return( lb->fontMetrics().lineSpacing()+1 );
+}
+
+
+int ColorListItem::width(const QListBox *lb ) const
+{
+ return( mBoxWidth + lb->fontMetrics().width( text() ) + 6 );
+}
+
+#include "colorlistbox.moc"
diff --git a/kmail/colorlistbox.h b/kmail/colorlistbox.h
new file mode 100644
index 00000000..1759bc83
--- /dev/null
+++ b/kmail/colorlistbox.h
@@ -0,0 +1,73 @@
+/*
+ * kmail: KDE mail client
+ * This file: Copyright (C) 2000 Espen Sand, espen@kde.org
+ *
+ * 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 _COLOR_LISTBOX_H_
+#define _COLOR_LISTBOX_H_
+
+#include <klistbox.h>
+
+class ColorListBox : public KListBox
+{
+ Q_OBJECT
+
+ public:
+ ColorListBox( QWidget *parent=0, const char * name=0, WFlags f=0 );
+ void setColor( uint index, const QColor &color );
+ QColor color( uint index ) const;
+signals:
+ void changed();
+
+ public slots:
+ virtual void setEnabled( bool state );
+
+ protected:
+ void dragEnterEvent( QDragEnterEvent *e );
+ void dragLeaveEvent( QDragLeaveEvent *e );
+ void dragMoveEvent( QDragMoveEvent *e );
+ void dropEvent( QDropEvent *e );
+
+ private slots:
+ void newColor( int index );
+
+ private:
+ int mCurrentOnDragEnter;
+
+};
+
+
+class ColorListItem : public QListBoxItem
+{
+ public:
+ ColorListItem( const QString &text, const QColor &color=Qt::black );
+ const QColor &color( void );
+ void setColor( const QColor &color );
+
+ protected:
+ virtual void paint( QPainter * );
+ virtual int height( const QListBox * ) const;
+ virtual int width( const QListBox * ) const;
+
+ private:
+ QColor mColor;
+ int mBoxWidth;
+};
+
+#endif
+
diff --git a/kmail/compactionjob.cpp b/kmail/compactionjob.cpp
new file mode 100644
index 00000000..1b9c143b
--- /dev/null
+++ b/kmail/compactionjob.cpp
@@ -0,0 +1,292 @@
+/**
+ * Copyright (c) 2004 David Faure <faure@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+#include "compactionjob.h"
+#include "kmfolder.h"
+#include "broadcaststatus.h"
+using KPIM::BroadcastStatus;
+#include "kmfoldermbox.h"
+#include "kmfoldermaildir.h"
+
+#include <kdebug.h>
+#include <klocale.h>
+
+#include <qfile.h>
+#include <qfileinfo.h>
+#include <qdir.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+
+using namespace KMail;
+
+// Look at this number of messages in each slotDoWork call
+#define COMPACTIONJOB_NRMESSAGES 100
+// And wait this number of milliseconds before calling it again
+#define COMPACTIONJOB_TIMERINTERVAL 100
+
+MboxCompactionJob::MboxCompactionJob( KMFolder* folder, bool immediate )
+ : ScheduledJob( folder, immediate ), mTimer( this, "mTimer" ), mTmpFile( 0 ),
+ mCurrentIndex( 0 ), mFolderOpen( false ), mSilent( false )
+{
+}
+
+MboxCompactionJob::~MboxCompactionJob()
+{
+}
+
+void MboxCompactionJob::kill()
+{
+ Q_ASSERT( mCancellable );
+ // We must close the folder if we opened it and got interrupted
+ if ( mFolderOpen && mSrcFolder && mSrcFolder->storage() )
+ mSrcFolder->storage()->close("mboxcompact");
+
+ if ( mTmpFile )
+ fclose( mTmpFile );
+ mTmpFile = 0;
+ if ( !mTempName.isEmpty() )
+ QFile::remove( mTempName );
+ FolderJob::kill();
+}
+
+QString MboxCompactionJob::realLocation() const
+{
+ QString location = mSrcFolder->location();
+ QFileInfo inf( location );
+ if (inf.isSymLink()) {
+ KURL u; u.setPath( location );
+ // follow (and resolve) symlinks so that the final ::rename() always works
+ // KURL gives us support for absolute and relative links transparently.
+ return KURL( u, inf.readLink() ).path();
+ }
+ return location;
+}
+
+int MboxCompactionJob::executeNow( bool silent )
+{
+ mSilent = silent;
+ FolderStorage* storage = mSrcFolder->storage();
+ KMFolderMbox* mbox = static_cast<KMFolderMbox *>( storage );
+ if (!storage->compactable()) {
+ kdDebug(5006) << storage->location() << " compaction skipped." << endl;
+ if ( !mSilent ) {
+ QString str = i18n( "For safety reasons, compaction has been disabled for %1" ).arg( mbox->label() );
+ BroadcastStatus::instance()->setStatusMsg( str );
+ }
+ return 0;
+ }
+ kdDebug(5006) << "Compacting " << mSrcFolder->idString() << endl;
+
+ if (KMFolderIndex::IndexOk != mbox->indexStatus()) {
+ kdDebug(5006) << "Critical error: " << storage->location() <<
+ " has been modified by an external application while KMail was running." << endl;
+ // exit(1); backed out due to broken nfs
+ }
+
+ const QFileInfo pathInfo( realLocation() );
+ // Use /dir/.mailboxname.compacted so that it's hidden, and doesn't show up after restarting kmail
+ // (e.g. due to an unfortunate crash while compaction is happening)
+ mTempName = pathInfo.dirPath() + "/." + pathInfo.fileName() + ".compacted";
+
+ mode_t old_umask = umask(077);
+ mTmpFile = fopen(QFile::encodeName(mTempName), "w");
+ umask(old_umask);
+ if (!mTmpFile) {
+ kdWarning(5006) << "Couldn't start compacting " << mSrcFolder->label()
+ << " : " << strerror( errno )
+ << " while creating " << mTempName << endl;
+ return errno;
+ }
+ mOpeningFolder = true; // Ignore open-notifications while opening the folder
+ storage->open("mboxcompact");
+ mOpeningFolder = false;
+ mFolderOpen = true;
+ mOffset = 0;
+ mCurrentIndex = 0;
+
+ kdDebug(5006) << "MboxCompactionJob: starting to compact folder " << mSrcFolder->location() << " into " << mTempName << endl;
+ connect( &mTimer, SIGNAL( timeout() ), SLOT( slotDoWork() ) );
+ if ( !mImmediate )
+ mTimer.start( COMPACTIONJOB_TIMERINTERVAL );
+ slotDoWork();
+ return mErrorCode;
+}
+
+void MboxCompactionJob::slotDoWork()
+{
+ // No need to worry about mSrcFolder==0 here. The FolderStorage deletes the jobs on destruction.
+ KMFolderMbox* mbox = static_cast<KMFolderMbox *>( mSrcFolder->storage() );
+ bool bDone = false;
+ int nbMessages = mImmediate ? -1 /*all*/ : COMPACTIONJOB_NRMESSAGES;
+ int rc = mbox->compact( mCurrentIndex, nbMessages,
+ mTmpFile, mOffset /*in-out*/, bDone /*out*/ );
+ if ( !mImmediate )
+ mCurrentIndex += COMPACTIONJOB_NRMESSAGES;
+ if ( rc || bDone ) // error, or finished
+ done( rc );
+}
+
+void MboxCompactionJob::done( int rc )
+{
+ mTimer.stop();
+ mCancellable = false;
+ KMFolderMbox* mbox = static_cast<KMFolderMbox *>( mSrcFolder->storage() );
+ if (!rc)
+ rc = fflush(mTmpFile);
+ if (!rc)
+ rc = fsync(fileno(mTmpFile));
+ rc |= fclose(mTmpFile);
+ QString str;
+ if (!rc) {
+ bool autoCreate = mbox->autoCreateIndex();
+ QString box( realLocation() );
+ ::rename(QFile::encodeName(mTempName), QFile::encodeName(box));
+ mbox->writeIndex();
+ mbox->writeConfig();
+ mbox->setAutoCreateIndex( false );
+ mbox->close("mboxcompact", true);
+ mbox->setAutoCreateIndex( autoCreate );
+ mbox->setNeedsCompacting( false ); // We are clean now
+ str = i18n( "Folder \"%1\" successfully compacted" ).arg( mSrcFolder->label() );
+ kdDebug(5006) << str << endl;
+ } else {
+ mbox->close("mboxcompact");
+ str = i18n( "Error occurred while compacting \"%1\". Compaction aborted." ).arg( mSrcFolder->label() );
+ kdDebug(5006) << "Error occurred while compacting " << mbox->location() << endl;
+ kdDebug(5006) << "Compaction aborted." << endl;
+ QFile::remove( mTempName );
+ }
+ mErrorCode = rc;
+
+ if ( !mSilent )
+ BroadcastStatus::instance()->setStatusMsg( str );
+
+ mFolderOpen = false;
+ deleteLater(); // later, because of the "return mErrorCode"
+}
+
+////
+
+MaildirCompactionJob::MaildirCompactionJob( KMFolder* folder, bool immediate )
+ : ScheduledJob( folder, immediate ), mTimer( this, "mTimer" ),
+ mCurrentIndex( 0 ), mFolderOpen( false ), mSilent( false )
+{
+}
+
+MaildirCompactionJob::~MaildirCompactionJob()
+{
+}
+
+void MaildirCompactionJob::kill()
+{
+ Q_ASSERT( mCancellable );
+ // We must close the folder if we opened it and got interrupted
+ if ( mFolderOpen && mSrcFolder && mSrcFolder->storage() )
+ mSrcFolder->storage()->close("maildircompact");
+
+ FolderJob::kill();
+}
+
+int MaildirCompactionJob::executeNow( bool silent )
+{
+ mSilent = silent;
+ KMFolderMaildir* storage = static_cast<KMFolderMaildir *>( mSrcFolder->storage() );
+ kdDebug(5006) << "Compacting " << mSrcFolder->idString() << endl;
+
+ mOpeningFolder = true; // Ignore open-notifications while opening the folder
+ storage->open("maildircompact");
+ mOpeningFolder = false;
+ mFolderOpen = true;
+ QString subdirNew(storage->location() + "/new/");
+ QDir d(subdirNew);
+ mEntryList = d.entryList();
+ mCurrentIndex = 0;
+
+ kdDebug(5006) << "MaildirCompactionJob: starting to compact in folder " << mSrcFolder->location() << endl;
+ connect( &mTimer, SIGNAL( timeout() ), SLOT( slotDoWork() ) );
+ if ( !mImmediate )
+ mTimer.start( COMPACTIONJOB_TIMERINTERVAL );
+ slotDoWork();
+ return mErrorCode;
+}
+
+void MaildirCompactionJob::slotDoWork()
+{
+ // No need to worry about mSrcFolder==0 here. The FolderStorage deletes the jobs on destruction.
+ KMFolderMaildir* storage = static_cast<KMFolderMaildir *>( mSrcFolder->storage() );
+ bool bDone = false;
+ int nbMessages = mImmediate ? -1 /*all*/ : COMPACTIONJOB_NRMESSAGES;
+ int rc = storage->compact( mCurrentIndex, nbMessages, mEntryList, bDone /*out*/ );
+ if ( !mImmediate )
+ mCurrentIndex += COMPACTIONJOB_NRMESSAGES;
+ if ( rc || bDone ) // error, or finished
+ done( rc );
+}
+
+void MaildirCompactionJob::done( int rc )
+{
+ KMFolderMaildir* storage = static_cast<KMFolderMaildir *>( mSrcFolder->storage() );
+ mTimer.stop();
+ mCancellable = false;
+ QString str;
+ if ( !rc ) {
+ str = i18n( "Folder \"%1\" successfully compacted" ).arg( mSrcFolder->label() );
+ } else {
+ str = i18n( "Error occurred while compacting \"%1\". Compaction aborted." ).arg( mSrcFolder->label() );
+ }
+ mErrorCode = rc;
+ storage->setNeedsCompacting( false );
+ storage->close("maildircompact");
+ if ( storage->isOpened() )
+ storage->updateIndex();
+ if ( !mSilent )
+ BroadcastStatus::instance()->setStatusMsg( str );
+
+ mFolderOpen = false;
+ deleteLater(); // later, because of the "return mErrorCode"
+}
+
+////
+
+ScheduledJob* ScheduledCompactionTask::run()
+{
+ if ( !folder() || !folder()->needsCompacting() )
+ return 0;
+ switch( folder()->storage()->folderType() ) {
+ case KMFolderTypeMbox:
+ return new MboxCompactionJob( folder(), isImmediate() );
+ case KMFolderTypeCachedImap:
+ case KMFolderTypeMaildir:
+ return new MaildirCompactionJob( folder(), isImmediate() );
+ default: // imap, search, unknown...
+ return 0;
+ }
+}
+
+#include "compactionjob.moc"
diff --git a/kmail/compactionjob.h b/kmail/compactionjob.h
new file mode 100644
index 00000000..055713f6
--- /dev/null
+++ b/kmail/compactionjob.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2004 David Faure <faure@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+#ifndef COMPACTIONJOB_H
+#define COMPACTIONJOB_H
+
+#include "jobscheduler.h"
+#include <qstringlist.h>
+
+namespace KMail {
+
+/**
+ * A job that runs in the background and compacts mbox folders.
+ */
+class MboxCompactionJob : public ScheduledJob
+{
+ Q_OBJECT
+public:
+ /// @p folder should be a folder with a KMFolderMbox storage.
+ MboxCompactionJob( KMFolder* folder, bool immediate );
+ virtual ~MboxCompactionJob();
+
+ int executeNow( bool silent );
+ virtual void execute() { executeNow( false ); }
+ virtual void kill();
+
+private slots:
+ void slotDoWork();
+
+private:
+ // Real folder location, != location in case of symlinks
+ QString realLocation() const;
+ void done( int rc );
+
+private:
+ QTimer mTimer;
+ QString mTempName;
+ FILE *mTmpFile;
+ off_t mOffset;
+ int mCurrentIndex;
+ bool mFolderOpen;
+ bool mSilent;
+};
+
+/**
+ * A job that runs in the background and compacts maildir folders.
+ */
+class MaildirCompactionJob : public ScheduledJob
+{
+ Q_OBJECT
+public:
+ /// @p folder should be a folder with a KMFolderMaildir storage.
+ MaildirCompactionJob( KMFolder* folder, bool immediate );
+ virtual ~MaildirCompactionJob();
+
+ int executeNow( bool silent );
+ virtual void execute() { executeNow( false ); }
+ virtual void kill();
+
+private slots:
+ void slotDoWork();
+
+private:
+ void done( int rc );
+
+private:
+ QTimer mTimer;
+ QStringList mEntryList;
+ int mCurrentIndex;
+ bool mFolderOpen;
+ bool mSilent;
+};
+
+/// A scheduled "compact mails in this folder" task.
+class ScheduledCompactionTask : public ScheduledTask
+{
+public:
+ /// If immediate is set, the job will execute synchronously. This is used when
+ /// the user requests explicitely that the operation should happen immediately.
+ ScheduledCompactionTask( KMFolder* folder, bool immediate )
+ : ScheduledTask( folder, immediate ) {}
+ virtual ~ScheduledCompactionTask() {}
+ virtual ScheduledJob* run();
+ virtual int taskTypeId() const { return 2; }
+};
+
+} // namespace
+
+#endif /* COMPACTIONJOB_H */
+
diff --git a/kmail/composer.cpp b/kmail/composer.cpp
new file mode 100644
index 00000000..7a0e470c
--- /dev/null
+++ b/kmail/composer.cpp
@@ -0,0 +1,4 @@
+#include "composer.h"
+
+#include "composer.moc"
+
diff --git a/kmail/composer.h b/kmail/composer.h
new file mode 100644
index 00000000..58d0860d
--- /dev/null
+++ b/kmail/composer.h
@@ -0,0 +1,153 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ * KMComposeWin Header File
+ * Author: Markus Wuebben <markus.wuebben@kde.org>
+ */
+#ifndef __KMAIL_COMPOSER_H__
+#define __KMAIL_COMPOSER_H__
+
+#include "secondarywindow.h"
+
+#include <kurl.h>
+#include <kglobalsettings.h>
+
+#include <qstring.h>
+#include <qcstring.h>
+
+class KMMessage;
+class KMFolder;
+class KMMessagePart;
+class QListViewItem;
+class MailComposerIface;
+
+namespace KIO {
+ class Job;
+}
+
+namespace GpgME {
+ class Error;
+}
+
+namespace KMail {
+
+ class Composer;
+
+ Composer * makeComposer( KMMessage * msg=0, uint identity=0 );
+
+ class Composer : public KMail::SecondaryWindow {
+ Q_OBJECT
+ protected:
+ Composer( const char * name=0 ) : KMail::SecondaryWindow( name ) {}
+ public: // mailserviceimpl
+ /**
+ * From MailComposerIface
+ */
+ virtual void send( int how ) = 0;
+ virtual void addAttachmentsAndSend(const KURL::List &urls, const QString &comment, int how) = 0;
+ virtual void addAttachment( KURL url, QString comment ) = 0;
+ virtual void addAttachment( const QString & name,
+ const QCString & cte,
+ const QByteArray & data,
+ const QCString & type,
+ const QCString & subType,
+ const QCString & paramAttr,
+ const QString & paramValue,
+ const QCString & contDisp) = 0;
+ public: // kmcommand
+ virtual void setBody( QString body ) = 0;
+
+ virtual const MailComposerIface * asMailComposerIFace() const = 0;
+ virtual MailComposerIface * asMailComposerIFace() = 0;
+
+ public: // kmkernel, kmcommands, callback
+ /**
+ * Set the message the composer shall work with. This discards
+ * previous messages without calling applyChanges() on them before.
+ */
+ virtual void setMsg( KMMessage * newMsg, bool mayAutoSign=true,
+ bool allowDecryption=false, bool isModified=false) = 0;
+
+ public: // kmkernel
+ /**
+ * Set the filename which is used for autosaving.
+ */
+ virtual void setAutoSaveFilename( const QString & filename ) = 0;
+
+ public: // kmkernel, callback
+ /**
+ * If this flag is set the message of the composer is deleted when
+ * the composer is closed and the message was not sent. Default: FALSE
+ */
+ virtual void setAutoDelete( bool f ) = 0;
+
+ /**
+ * If this flag is set, the compose window will delete itself after
+ * the window has been closed.
+ */
+ virtual void setAutoDeleteWindow( bool f ) = 0;
+
+ public: // kmcommand
+ /**
+ * If this folder is set, the original message is inserted back after
+ * cancelling
+ */
+ virtual void setFolder( KMFolder * aFolder ) = 0;
+
+ public: // kmkernel, kmcommand, mailserviceimpl
+ /**
+ * Recode to the specified charset
+ */
+ virtual void setCharset( const QCString & aCharset, bool forceDefault=false ) = 0;
+
+ public: // kmcommand
+ /**
+ * Sets the focus to the edit-widget and the cursor below the
+ * "On ... you wrote" line when hasMessage is true.
+ * Make sure you call this _after_ setMsg().
+ */
+ virtual void setReplyFocus( bool hasMessage=true ) = 0;
+
+ /**
+ * Sets the focus to the subject line edit. For use when creating a
+ * message to a known recipient.
+ */
+ virtual void setFocusToSubject() = 0;
+
+ public: // callback
+ /** Disabled signing and encryption completely for this composer window. */
+ virtual void setSigningAndEncryptionDisabled( bool v ) = 0;
+
+ public slots: // kmkernel, callback
+ virtual void slotSendNow() = 0;
+
+ public slots: // kmkernel
+ /**
+ Tell the composer to always send the message, even if the user
+ hasn't changed the next. This is useful if a message is
+ autogenerated (e.g., via a DCOP call), and the user should
+ simply be able to confirm the message and send it.
+ */
+ virtual void slotSetAlwaysSend( bool bAlwaysSend ) = 0;
+ public slots: // kmkernel, callback
+ /**
+ * Switch wordWrap on/off
+ */
+ virtual void slotWordWrapToggled(bool) = 0;
+
+ public slots: // kmkernel
+ virtual void autoSaveMessage() = 0;
+
+ public: // kmkernel, attachmentlistview
+ virtual bool addAttach( const KURL url ) = 0;
+
+ virtual void disableWordWrap() = 0;
+
+ public: // kmcommand
+ /**
+ * Add an attachment to the list.
+ */
+ virtual void addAttach( const KMMessagePart * msgPart ) = 0;
+ };
+
+}
+
+#endif // __KMAIL_COMPOSER_H__
diff --git a/kmail/composercryptoconfiguration.ui b/kmail/composercryptoconfiguration.ui
new file mode 100644
index 00000000..c59c7af0
--- /dev/null
+++ b/kmail/composercryptoconfiguration.ui
@@ -0,0 +1,158 @@
+<!DOCTYPE UI><UI version="3.2" stdsetdef="1">
+<class>ComposerCryptoConfiguration</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>ComposerCryptoConfiguration</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>581</width>
+ <height>318</height>
+ </rect>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QGroupBox">
+ <property name="name">
+ <cstring>optionsGroup_2</cstring>
+ </property>
+ <property name="title">
+ <string>Signing</string>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>mAutoSignature</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Automatically sign messages</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>When this option is enabled, all messages you send will be signed by default; of course, it is still possible to disable signing for each message individually.</string>
+ </property>
+ </widget>
+ </vbox>
+ </widget>
+ <widget class="QGroupBox">
+ <property name="name">
+ <cstring>optionsGroup</cstring>
+ </property>
+ <property name="title">
+ <string>Encrypting</string>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>mEncToSelf</cstring>
+ </property>
+ <property name="text">
+ <string>When encrypting emails, always also encr&amp;ypt to the certificate of my own identity</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>When this option is enabled, the message/file will not only be encrypted with the receiver's public key, but also with your key. This will enable you to decrypt the message/file at a later time. This is generally a good idea.</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>mShowEncryptionResult</cstring>
+ </property>
+ <property name="text">
+ <string>Show s&amp;igned/encrypted text after composing</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>When this option is enabled, the signed/encrypted text will be shown in a separate window, enabling you to know how it will look before it is sent. This is a good idea when you are verifying that your encryption system works.</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>mStoreEncrypted</cstring>
+ </property>
+ <property name="text">
+ <string>Store sent messages encry&amp;pted</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Check to store messages encrypted </string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>&lt;qt&gt;
+&lt;h1&gt;Store Messages Encrypted&lt;/h1&gt;
+When this box is checked, sent messages are stored encrypted like they were sent. This is not recommended, as you will not be able to read the messages any longer if a necessary certificate expires.
+&lt;p&gt;
+However, there may be local rules that require you to turn this option on. When in doubt, check with your local administrator.
+&lt;/qt&gt;</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>mShowKeyApprovalDlg</cstring>
+ </property>
+ <property name="text">
+ <string>Always show the encryption keys &amp;for approval</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>When this option is enabled, the application will always show you a list of public keys from which you can choose the one it will use for encryption. If it is off, the application will only show the dialog if it cannot find the right key or if there are several which could be used.</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>mAutoEncrypt</cstring>
+ </property>
+ <property name="text">
+ <string>Automatically encrypt &amp;messages whenever possible</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>When this option is enabled, every message you send will be encrypted whenever encryption is possible and desired; of course, it is still possible to disable the automatic encryption for each message individually.</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>mNeverEncryptWhenSavingInDrafts</cstring>
+ </property>
+ <property name="text">
+ <string>Never sign/encrypt when sa&amp;ving as draft</string>
+ </property>
+ </widget>
+ </vbox>
+ </widget>
+ <spacer>
+ <property name="name">
+ <cstring>spacer22</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </vbox>
+</widget>
+<tabstops>
+ <tabstop>mEncToSelf</tabstop>
+ <tabstop>mShowEncryptionResult</tabstop>
+ <tabstop>mStoreEncrypted</tabstop>
+ <tabstop>mShowKeyApprovalDlg</tabstop>
+ <tabstop>mAutoEncrypt</tabstop>
+ <tabstop>mNeverEncryptWhenSavingInDrafts</tabstop>
+</tabstops>
+<layoutdefaults spacing="6" margin="11"/>
+</UI>
diff --git a/kmail/configure.in.bot b/kmail/configure.in.bot
new file mode 100644
index 00000000..ab0668f5
--- /dev/null
+++ b/kmail/configure.in.bot
@@ -0,0 +1,14 @@
+case "$missing_indexlib" in
+yes)
+ echo "You are missing indexlib, which is required to"
+ echo "enable full-text indexing in KMail. You can use"
+ echo "--with-indexlib to enable indexlib."
+ ;;
+no)
+ echo "You have indexlib enabled. Good luck!"
+ ;;
+ignore)
+ echo "If you would like to build-in KMail's experimental full-text"
+ echo "indexing please re-run configure with the --enable-indexlib option."
+ ;;
+esac
diff --git a/kmail/configure.in.in b/kmail/configure.in.in
new file mode 100644
index 00000000..bcf4e8bc
--- /dev/null
+++ b/kmail/configure.in.in
@@ -0,0 +1,90 @@
+AC_DEFUN([KMAIL_CHECK_INDEXLIB],
+[
+AC_REQUIRE([KDE_CHECK_LIB64])
+
+AC_MSG_CHECKING(for indexlib)
+
+missing_indexlib=ignore
+AC_ARG_ENABLE(indexlib,
+ AC_HELP_STRING([--enable-indexlib],[Enable EXPERIMENTAL full-text indexing in KMail.
+ Use are your own risk!]),
+ AC_DEFINE(HAVE_INDEXLIB,1,[Define if you want EXPERIMENTAL full-text indexing in KMail])
+ missing_indexlib=no
+ )
+
+
+AM_CONDITIONAL(add_indexlib, test no = "$missing_indexlib")
+
+
+
+])
+
+dnl The following test is taken from kdelibs/kio/kio
+dnl ------------------------------------------------------------------------
+dnl Try to find if we have Linux Inode based Dir Notification
+dnl ------------------------------------------------------------------------
+
+AC_ARG_ENABLE(inotify,
+AC_HELP_STRING([--disable-inotify],[enable use of Linux inode notifications]),
+[ kde_enable_inotify=$enableval ], [kde_enable_inotify=yes])dnl
+
+AC_CHECK_GNU_EXTENSIONS
+
+if test "x$kde_enable_inotify" = "xyes"; then
+ AC_MSG_CHECKING([for Linux Inotify Notification])
+ AC_CACHE_VAL(kde_cv_have_inotify,
+ [
+ kde_cv_have_inotify=no
+ AC_LANG_SAVE
+ AC_LANG_C
+
+ AC_TRY_COMPILE(
+ [
+#include <asm/unistd.h>
+#define _S390_BITOPS_H
+#include <linux/inotify.h>
+ ],
+ [
+#ifndef IN_ALL_EVENTS
+#error no inotify notification
+#endif
+#ifndef __NR_inotify_init
+#error no __NR_inotify_init
+#endif
+
+ ],kde_cv_have_inotify=yes,kde_cv_have_inotify=no)
+
+ AC_LANG_RESTORE
+ ])
+
+ AC_CACHE_VAL(kde_cv_have_sys_inotify,
+ [
+ kde_cv_have_sys_inotify=no
+ AC_LANG_SAVE
+ AC_LANG_C
+
+ AC_TRY_COMPILE(
+ [
+#include <sys/inotify.h>
+ ],
+ [
+#ifndef IN_ALL_EVENTS
+#error no inotify notification
+#endif
+ ],kde_cv_have_sys_inotify=yes,kde_cv_have_sys_inotify=no)
+
+ AC_LANG_RESTORE
+ ])
+
+ if test "$kde_cv_have_inotify" = "yes" -o "$kde_cv_have_sys_inotify" = "yes"; then
+ AC_DEFINE_UNQUOTED(HAVE_INOTIFY, 1, [Define if your system has Linux Inode Notification])
+ if test "$kde_cv_have_sys_inotify" = "yes"; then
+ AC_DEFINE_UNQUOTED(HAVE_SYS_INOTIFY, 1, [Define if your system has glibc support for inotify])
+ fi
+ AC_MSG_RESULT(yes)
+ else
+ AC_MSG_RESULT(no)
+ fi
+fi
+
+KMAIL_CHECK_INDEXLIB
diff --git a/kmail/configuredialog.cpp b/kmail/configuredialog.cpp
new file mode 100644
index 00000000..321a374c
--- /dev/null
+++ b/kmail/configuredialog.cpp
@@ -0,0 +1,5064 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ * kmail: KDE mail client
+ * This file: Copyright (C) 2000 Espen Sand, espen@kde.org
+ * Copyright (C) 2001-2003 Marc Mutz, mutz@kde.org
+ * Contains code segments and ideas from earlier kmail dialog code.
+ *
+ * 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.
+ *
+ */
+
+// This must be first
+#include <config.h>
+
+// my headers:
+#include "configuredialog.h"
+#include "configuredialog_p.h"
+
+#include "globalsettings.h"
+#include "replyphrases.h"
+#include "templatesconfiguration_kfg.h"
+
+// other KMail headers:
+#include "kmkernel.h"
+#include "simplestringlisteditor.h"
+#include "accountdialog.h"
+using KMail::AccountDialog;
+#include "colorlistbox.h"
+#include "kmacctseldlg.h"
+#include "messagesender.h"
+#include "kmtransport.h"
+#include "kmfoldermgr.h"
+#include <libkpimidentities/identitymanager.h>
+#include "identitylistview.h"
+using KMail::IdentityListView;
+using KMail::IdentityListViewItem;
+#include "kcursorsaver.h"
+#include "accountmanager.h"
+#include <composercryptoconfiguration.h>
+#include <warningconfiguration.h>
+#include <smimeconfiguration.h>
+#include "templatesconfiguration.h"
+#include "customtemplates.h"
+#include "folderrequester.h"
+using KMail::FolderRequester;
+#include "accountcombobox.h"
+#include "imapaccountbase.h"
+using KMail::ImapAccountBase;
+#include "folderstorage.h"
+#include "kmfolder.h"
+#include "kmmainwidget.h"
+#include "recentaddresses.h"
+using KRecentAddress::RecentAddresses;
+#include "completionordereditor.h"
+#include "ldapclient.h"
+#include "index.h"
+
+using KMail::IdentityListView;
+using KMail::IdentityListViewItem;
+#include "identitydialog.h"
+using KMail::IdentityDialog;
+
+// other kdenetwork headers:
+#include <libkpimidentities/identity.h>
+#include <kmime_util.h>
+using KMime::DateFormatter;
+#include <kleo/cryptoconfig.h>
+#include <kleo/cryptobackendfactory.h>
+#include <ui/backendconfigwidget.h>
+#include <ui/keyrequester.h>
+#include <ui/keyselectiondialog.h>
+
+// other KDE headers:
+#include <klocale.h>
+#include <kapplication.h>
+#include <kcharsets.h>
+#include <kasciistringtools.h>
+#include <kdebug.h>
+#include <knuminput.h>
+#include <kfontdialog.h>
+#include <kmessagebox.h>
+#include <kurlrequester.h>
+#include <kseparator.h>
+#include <kiconloader.h>
+#include <kstandarddirs.h>
+#include <kwin.h>
+#include <knotifydialog.h>
+#include <kconfig.h>
+#include <kactivelabel.h>
+#include <kcmultidialog.h>
+
+// Qt headers:
+#include <qvalidator.h>
+#include <qwhatsthis.h>
+#include <qvgroupbox.h>
+#include <qvbox.h>
+#include <qvbuttongroup.h>
+#include <qhbuttongroup.h>
+#include <qtooltip.h>
+#include <qlabel.h>
+#include <qtextcodec.h>
+#include <qheader.h>
+#include <qpopupmenu.h>
+#include <qradiobutton.h>
+#include <qlayout.h>
+#include <qcheckbox.h>
+#include <qwidgetstack.h>
+
+// other headers:
+#include <assert.h>
+#include <stdlib.h>
+
+#ifndef _PATH_SENDMAIL
+#define _PATH_SENDMAIL "/usr/sbin/sendmail"
+#endif
+
+#ifdef DIM
+#undef DIM
+#endif
+#define DIM(x) sizeof(x) / sizeof(*x)
+
+namespace {
+
+ struct EnumConfigEntryItem {
+ const char * key; // config key value, as appears in config file
+ const char * desc; // description, to be i18n()ized
+ };
+ struct EnumConfigEntry {
+ const char * group;
+ const char * key;
+ const char * desc;
+ const EnumConfigEntryItem * items;
+ int numItems;
+ int defaultItem;
+ };
+ struct BoolConfigEntry {
+ const char * group;
+ const char * key;
+ const char * desc;
+ bool defaultValue;
+ };
+
+ static const char * lockedDownWarning =
+ I18N_NOOP("<qt><p>This setting has been fixed by your administrator.</p>"
+ "<p>If you think this is an error, please contact him.</p></qt>");
+
+ void checkLockDown( QWidget * w, const KConfigBase & c, const char * key ) {
+ if ( c.entryIsImmutable( key ) ) {
+ w->setEnabled( false );
+ QToolTip::add( w, i18n( lockedDownWarning ) );
+ } else {
+ QToolTip::remove( w );
+ }
+ }
+
+ void populateButtonGroup( QButtonGroup * g, const EnumConfigEntry & e ) {
+ g->setTitle( i18n( e.desc ) );
+ g->layout()->setSpacing( KDialog::spacingHint() );
+ for ( int i = 0 ; i < e.numItems ; ++i )
+ g->insert( new QRadioButton( i18n( e.items[i].desc ), g ), i );
+ }
+
+ void populateCheckBox( QCheckBox * b, const BoolConfigEntry & e ) {
+ b->setText( i18n( e.desc ) );
+ }
+
+ void loadWidget( QCheckBox * b, const KConfigBase & c, const BoolConfigEntry & e ) {
+ Q_ASSERT( c.group() == e.group );
+ checkLockDown( b, c, e.key );
+ b->setChecked( c.readBoolEntry( e.key, e.defaultValue ) );
+ }
+
+ void loadWidget( QButtonGroup * g, const KConfigBase & c, const EnumConfigEntry & e ) {
+ Q_ASSERT( c.group() == e.group );
+ Q_ASSERT( g->count() == e.numItems );
+ checkLockDown( g, c, e.key );
+ const QString s = c.readEntry( e.key, e.items[e.defaultItem].key );
+ for ( int i = 0 ; i < e.numItems ; ++i )
+ if ( s == e.items[i].key ) {
+ g->setButton( i );
+ return;
+ }
+ g->setButton( e.defaultItem );
+ }
+
+ void saveCheckBox( QCheckBox * b, KConfigBase & c, const BoolConfigEntry & e ) {
+ Q_ASSERT( c.group() == e.group );
+ c.writeEntry( e.key, b->isChecked() );
+ }
+
+ void saveButtonGroup( QButtonGroup * g, KConfigBase & c, const EnumConfigEntry & e ) {
+ Q_ASSERT( c.group() == e.group );
+ Q_ASSERT( g->count() == e.numItems );
+ c.writeEntry( e.key, e.items[ g->id( g->selected() ) ].key );
+ }
+
+ template <typename T_Widget, typename T_Entry>
+ inline void loadProfile( T_Widget * g, const KConfigBase & c, const T_Entry & e ) {
+ if ( c.hasKey( e.key ) )
+ loadWidget( g, c, e );
+ }
+}
+
+
+ConfigureDialog::ConfigureDialog( QWidget *parent, const char *name, bool modal )
+ : KCMultiDialog( KDialogBase::IconList, KGuiItem( i18n( "&Load Profile..." ) ),
+ KGuiItem(), User2, i18n( "Configure" ), parent, name, modal )
+ , mProfileDialog( 0 )
+{
+ KWin::setIcons( winId(), kapp->icon(), kapp->miniIcon() );
+ showButton( User1, true );
+
+ addModule ( "kmail_config_identity", false );
+ addModule ( "kmail_config_accounts", false );
+ addModule ( "kmail_config_appearance", false );
+ addModule ( "kmail_config_composer", false );
+ addModule ( "kmail_config_security", false );
+ addModule ( "kmail_config_misc", false );
+
+ // We store the size of the dialog on hide, because otherwise
+ // the KCMultiDialog starts with the size of the first kcm, not
+ // the largest one. This way at least after the first showing of
+ // the largest kcm the size is kept.
+ KConfigGroup geometry( KMKernel::config(), "Geometry" );
+ int width = geometry.readNumEntry( "ConfigureDialogWidth" );
+ int height = geometry.readNumEntry( "ConfigureDialogHeight" );
+ if ( width != 0 && height != 0 ) {
+ setMinimumSize( width, height );
+ }
+
+}
+
+void ConfigureDialog::hideEvent( QHideEvent *ev ) {
+ KConfigGroup geometry( KMKernel::config(), "Geometry" );
+ geometry.writeEntry( "ConfigureDialogWidth", width() );
+ geometry.writeEntry( "ConfigureDialogHeight",height() );
+ KDialogBase::hideEvent( ev );
+}
+
+ConfigureDialog::~ConfigureDialog() {
+}
+
+void ConfigureDialog::slotApply() {
+ GlobalSettings::self()->writeConfig();
+ KCMultiDialog::slotApply();
+}
+
+void ConfigureDialog::slotOk() {
+ GlobalSettings::self()->writeConfig();
+ KCMultiDialog::slotOk();
+}
+
+void ConfigureDialog::slotUser2() {
+ if ( mProfileDialog ) {
+ mProfileDialog->raise();
+ return;
+ }
+ mProfileDialog = new ProfileDialog( this, "mProfileDialog" );
+ connect( mProfileDialog, SIGNAL(profileSelected(KConfig*)),
+ this, SIGNAL(installProfile(KConfig*)) );
+ mProfileDialog->show();
+}
+
+// *************************************************************
+// * *
+// * IdentityPage *
+// * *
+// *************************************************************
+QString IdentityPage::helpAnchor() const {
+ return QString::fromLatin1("configure-identity");
+}
+
+IdentityPage::IdentityPage( QWidget * parent, const char * name )
+ : ConfigModule( parent, name ),
+ mIdentityDialog( 0 )
+{
+ QHBoxLayout * hlay = new QHBoxLayout( this, 0, KDialog::spacingHint() );
+
+ mIdentityList = new IdentityListView( this );
+ connect( mIdentityList, SIGNAL(selectionChanged()),
+ SLOT(slotIdentitySelectionChanged()) );
+ connect( mIdentityList, SIGNAL(itemRenamed(QListViewItem*,const QString&,int)),
+ SLOT(slotRenameIdentity(QListViewItem*,const QString&,int)) );
+ connect( mIdentityList, SIGNAL(doubleClicked(QListViewItem*,const QPoint&,int)),
+ SLOT(slotModifyIdentity()) );
+ connect( mIdentityList, SIGNAL(contextMenu(KListView*,QListViewItem*,const QPoint&)),
+ SLOT(slotContextMenu(KListView*,QListViewItem*,const QPoint&)) );
+ // ### connect dragged(...), ...
+
+ hlay->addWidget( mIdentityList, 1 );
+
+ QVBoxLayout * vlay = new QVBoxLayout( hlay ); // inherits spacing
+
+ QPushButton * button = new QPushButton( i18n("&Add..."), this );
+ mModifyButton = new QPushButton( i18n("&Modify..."), this );
+ mRenameButton = new QPushButton( i18n("&Rename"), this );
+ mRemoveButton = new QPushButton( i18n("Remo&ve"), this );
+ mSetAsDefaultButton = new QPushButton( i18n("Set as &Default"), this );
+ button->setAutoDefault( false );
+ mModifyButton->setAutoDefault( false );
+ mModifyButton->setEnabled( false );
+ mRenameButton->setAutoDefault( false );
+ mRenameButton->setEnabled( false );
+ mRemoveButton->setAutoDefault( false );
+ mRemoveButton->setEnabled( false );
+ mSetAsDefaultButton->setAutoDefault( false );
+ mSetAsDefaultButton->setEnabled( false );
+ connect( button, SIGNAL(clicked()),
+ this, SLOT(slotNewIdentity()) );
+ connect( mModifyButton, SIGNAL(clicked()),
+ this, SLOT(slotModifyIdentity()) );
+ connect( mRenameButton, SIGNAL(clicked()),
+ this, SLOT(slotRenameIdentity()) );
+ connect( mRemoveButton, SIGNAL(clicked()),
+ this, SLOT(slotRemoveIdentity()) );
+ connect( mSetAsDefaultButton, SIGNAL(clicked()),
+ this, SLOT(slotSetAsDefault()) );
+ vlay->addWidget( button );
+ vlay->addWidget( mModifyButton );
+ vlay->addWidget( mRenameButton );
+ vlay->addWidget( mRemoveButton );
+ vlay->addWidget( mSetAsDefaultButton );
+ vlay->addStretch( 1 );
+ load();
+}
+
+void IdentityPage::load()
+{
+ KPIM::IdentityManager * im = kmkernel->identityManager();
+ mOldNumberOfIdentities = im->shadowIdentities().count();
+ // Fill the list:
+ mIdentityList->clear();
+ QListViewItem * item = 0;
+ for ( KPIM::IdentityManager::Iterator it = im->modifyBegin() ; it != im->modifyEnd() ; ++it )
+ item = new IdentityListViewItem( mIdentityList, item, *it );
+ mIdentityList->setSelected( mIdentityList->currentItem(), true );
+}
+
+void IdentityPage::save() {
+ assert( !mIdentityDialog );
+
+ kmkernel->identityManager()->sort();
+ kmkernel->identityManager()->commit();
+
+ if( mOldNumberOfIdentities < 2 && mIdentityList->childCount() > 1 ) {
+ // have more than one identity, so better show the combo in the
+ // composer now:
+ KConfigGroup composer( KMKernel::config(), "Composer" );
+ int showHeaders = composer.readNumEntry( "headers", HDR_STANDARD );
+ showHeaders |= HDR_IDENTITY;
+ composer.writeEntry( "headers", showHeaders );
+ }
+ // and now the reverse
+ if( mOldNumberOfIdentities > 1 && mIdentityList->childCount() < 2 ) {
+ // have only one identity, so remove the combo in the composer:
+ KConfigGroup composer( KMKernel::config(), "Composer" );
+ int showHeaders = composer.readNumEntry( "headers", HDR_STANDARD );
+ showHeaders &= ~HDR_IDENTITY;
+ composer.writeEntry( "headers", showHeaders );
+ }
+}
+
+void IdentityPage::slotNewIdentity()
+{
+ assert( !mIdentityDialog );
+
+ KPIM::IdentityManager * im = kmkernel->identityManager();
+ NewIdentityDialog dialog( im->shadowIdentities(), this, "new", true );
+
+ if( dialog.exec() == QDialog::Accepted ) {
+ QString identityName = dialog.identityName().stripWhiteSpace();
+ assert( !identityName.isEmpty() );
+
+ //
+ // Construct a new Identity:
+ //
+ switch ( dialog.duplicateMode() ) {
+ case NewIdentityDialog::ExistingEntry:
+ {
+ KPIM::Identity & dupThis = im->modifyIdentityForName( dialog.duplicateIdentity() );
+ im->newFromExisting( dupThis, identityName );
+ break;
+ }
+ case NewIdentityDialog::ControlCenter:
+ im->newFromControlCenter( identityName );
+ break;
+ case NewIdentityDialog::Empty:
+ im->newFromScratch( identityName );
+ default: ;
+ }
+
+ //
+ // Insert into listview:
+ //
+ KPIM::Identity & newIdent = im->modifyIdentityForName( identityName );
+ QListViewItem * item = mIdentityList->selectedItem();
+ if ( item )
+ item = item->itemAbove();
+ mIdentityList->setSelected( new IdentityListViewItem( mIdentityList,
+ /*after*/ item,
+ newIdent ), true );
+ slotModifyIdentity();
+ }
+}
+
+void IdentityPage::slotModifyIdentity() {
+ assert( !mIdentityDialog );
+
+ IdentityListViewItem * item =
+ dynamic_cast<IdentityListViewItem*>( mIdentityList->selectedItem() );
+ if ( !item ) return;
+
+ mIdentityDialog = new IdentityDialog( this );
+ mIdentityDialog->setIdentity( item->identity() );
+
+ // Hmm, an unmodal dialog would be nicer, but a modal one is easier ;-)
+ if ( mIdentityDialog->exec() == QDialog::Accepted ) {
+ mIdentityDialog->updateIdentity( item->identity() );
+ item->redisplay();
+ emit changed(true);
+ }
+
+ delete mIdentityDialog;
+ mIdentityDialog = 0;
+}
+
+void IdentityPage::slotRemoveIdentity()
+{
+ assert( !mIdentityDialog );
+
+ KPIM::IdentityManager * im = kmkernel->identityManager();
+ kdFatal( im->shadowIdentities().count() < 2 )
+ << "Attempted to remove the last identity!" << endl;
+
+ IdentityListViewItem * item =
+ dynamic_cast<IdentityListViewItem*>( mIdentityList->selectedItem() );
+ if ( !item ) return;
+
+ QString msg = i18n("<qt>Do you really want to remove the identity named "
+ "<b>%1</b>?</qt>").arg( item->identity().identityName() );
+ if( KMessageBox::warningContinueCancel( this, msg, i18n("Remove Identity"),
+ KGuiItem(i18n("&Remove"),"editdelete") ) == KMessageBox::Continue )
+ if ( im->removeIdentity( item->identity().identityName() ) ) {
+ delete item;
+ mIdentityList->setSelected( mIdentityList->currentItem(), true );
+ refreshList();
+ }
+}
+
+void IdentityPage::slotRenameIdentity() {
+ assert( !mIdentityDialog );
+
+ QListViewItem * item = mIdentityList->selectedItem();
+ if ( !item ) return;
+
+ mIdentityList->rename( item, 0 );
+}
+
+void IdentityPage::slotRenameIdentity( QListViewItem * i,
+ const QString & s, int col ) {
+ assert( col == 0 );
+ Q_UNUSED( col );
+
+ IdentityListViewItem * item = dynamic_cast<IdentityListViewItem*>( i );
+ if ( !item ) return;
+
+ QString newName = s.stripWhiteSpace();
+ if ( !newName.isEmpty() &&
+ !kmkernel->identityManager()->shadowIdentities().contains( newName ) ) {
+ KPIM::Identity & ident = item->identity();
+ ident.setIdentityName( newName );
+ emit changed(true);
+ }
+ item->redisplay();
+}
+
+void IdentityPage::slotContextMenu( KListView *, QListViewItem * i,
+ const QPoint & pos ) {
+ IdentityListViewItem * item = dynamic_cast<IdentityListViewItem*>( i );
+
+ QPopupMenu * menu = new QPopupMenu( this );
+ menu->insertItem( i18n("Add..."), this, SLOT(slotNewIdentity()) );
+ if ( item ) {
+ menu->insertItem( i18n("Modify..."), this, SLOT(slotModifyIdentity()) );
+ if ( mIdentityList->childCount() > 1 )
+ menu->insertItem( i18n("Remove"), this, SLOT(slotRemoveIdentity()) );
+ if ( !item->identity().isDefault() )
+ menu->insertItem( i18n("Set as Default"), this, SLOT(slotSetAsDefault()) );
+ }
+ menu->exec( pos );
+ delete menu;
+}
+
+
+void IdentityPage::slotSetAsDefault() {
+ assert( !mIdentityDialog );
+
+ IdentityListViewItem * item =
+ dynamic_cast<IdentityListViewItem*>( mIdentityList->selectedItem() );
+ if ( !item ) return;
+
+ KPIM::IdentityManager * im = kmkernel->identityManager();
+ im->setAsDefault( item->identity().identityName() );
+ refreshList();
+}
+
+void IdentityPage::refreshList() {
+ for ( QListViewItemIterator it( mIdentityList ) ; it.current() ; ++it ) {
+ IdentityListViewItem * item =
+ dynamic_cast<IdentityListViewItem*>(it.current());
+ if ( item )
+ item->redisplay();
+ }
+ emit changed(true);
+}
+
+void IdentityPage::slotIdentitySelectionChanged()
+{
+ IdentityListViewItem *item =
+ dynamic_cast<IdentityListViewItem*>( mIdentityList->selectedItem() );
+
+ mRemoveButton->setEnabled( item && mIdentityList->childCount() > 1 );
+ mModifyButton->setEnabled( item );
+ mRenameButton->setEnabled( item );
+ mSetAsDefaultButton->setEnabled( item && !item->identity().isDefault() );
+}
+
+void IdentityPage::slotUpdateTransportCombo( const QStringList & sl )
+{
+ if ( mIdentityDialog ) mIdentityDialog->slotUpdateTransportCombo( sl );
+}
+
+
+
+// *************************************************************
+// * *
+// * AccountsPage *
+// * *
+// *************************************************************
+QString AccountsPage::helpAnchor() const {
+ return QString::fromLatin1("configure-accounts");
+}
+
+AccountsPage::AccountsPage( QWidget * parent, const char * name )
+ : ConfigModuleWithTabs( parent, name )
+{
+ //
+ // "Receiving" tab:
+ //
+ mReceivingTab = new ReceivingTab();
+ addTab( mReceivingTab, i18n( "&Receiving" ) );
+ connect( mReceivingTab, SIGNAL(accountListChanged(const QStringList &)),
+ this, SIGNAL(accountListChanged(const QStringList &)) );
+
+ //
+ // "Sending" tab:
+ //
+ mSendingTab = new SendingTab();
+ addTab( mSendingTab, i18n( "&Sending" ) );
+ connect( mSendingTab, SIGNAL(transportListChanged(const QStringList&)),
+ this, SIGNAL(transportListChanged(const QStringList&)) );
+
+ load();
+}
+
+QString AccountsPage::SendingTab::helpAnchor() const {
+ return QString::fromLatin1("configure-accounts-sending");
+}
+
+AccountsPageSendingTab::AccountsPageSendingTab( QWidget * parent, const char * name )
+ : ConfigModuleTab( parent, name )
+{
+ mTransportInfoList.setAutoDelete( true );
+ // temp. vars:
+ QVBoxLayout *vlay;
+ QVBoxLayout *btn_vlay;
+ QHBoxLayout *hlay;
+ QGridLayout *glay;
+ QPushButton *button;
+ QGroupBox *group;
+
+ vlay = new QVBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
+ // label: zero stretch ### FIXME more
+ vlay->addWidget( new QLabel( i18n("Outgoing accounts (add at least one):"), this ) );
+
+ // hbox layout: stretch 10, spacing inherited from vlay
+ hlay = new QHBoxLayout();
+ vlay->addLayout( hlay, 10 ); // high stretch b/c of the groupbox's sizeHint
+
+ // transport list: left widget in hlay; stretch 1
+ // ### FIXME: allow inline renaming of the account:
+ mTransportList = new ListView( this, "transportList", 5 );
+ mTransportList->addColumn( i18n("Name") );
+ mTransportList->addColumn( i18n("Type") );
+ mTransportList->setAllColumnsShowFocus( true );
+ mTransportList->setSorting( -1 );
+ connect( mTransportList, SIGNAL(selectionChanged()),
+ this, SLOT(slotTransportSelected()) );
+ connect( mTransportList, SIGNAL(doubleClicked( QListViewItem *)),
+ this, SLOT(slotModifySelectedTransport()) );
+ hlay->addWidget( mTransportList, 1 );
+
+ // a vbox layout for the buttons: zero stretch, spacing inherited from hlay
+ btn_vlay = new QVBoxLayout( hlay );
+
+ // "add..." button: stretch 0
+ button = new QPushButton( i18n("A&dd..."), this );
+ button->setAutoDefault( false );
+ connect( button, SIGNAL(clicked()),
+ this, SLOT(slotAddTransport()) );
+ btn_vlay->addWidget( button );
+
+ // "modify..." button: stretch 0
+ mModifyTransportButton = new QPushButton( i18n("&Modify..."), this );
+ mModifyTransportButton->setAutoDefault( false );
+ mModifyTransportButton->setEnabled( false ); // b/c no item is selected yet
+ connect( mModifyTransportButton, SIGNAL(clicked()),
+ this, SLOT(slotModifySelectedTransport()) );
+ btn_vlay->addWidget( mModifyTransportButton );
+
+ // "remove" button: stretch 0
+ mRemoveTransportButton = new QPushButton( i18n("R&emove"), this );
+ mRemoveTransportButton->setAutoDefault( false );
+ mRemoveTransportButton->setEnabled( false ); // b/c no item is selected yet
+ connect( mRemoveTransportButton, SIGNAL(clicked()),
+ this, SLOT(slotRemoveSelectedTransport()) );
+ btn_vlay->addWidget( mRemoveTransportButton );
+
+ mSetDefaultTransportButton = new QPushButton( i18n("Set Default"), this );
+ mSetDefaultTransportButton->setAutoDefault( false );
+ mSetDefaultTransportButton->setEnabled( false );
+ connect ( mSetDefaultTransportButton, SIGNAL(clicked()),
+ this, SLOT(slotSetDefaultTransport()) );
+ btn_vlay->addWidget( mSetDefaultTransportButton );
+ btn_vlay->addStretch( 1 ); // spacer
+
+ // "Common options" groupbox:
+ group = new QGroupBox( 0, Qt::Vertical,
+ i18n("Common Options"), this );
+ vlay->addWidget(group);
+
+ // a grid layout for the contents of the "common options" group box
+ glay = new QGridLayout( group->layout(), 5, 3, KDialog::spacingHint() );
+ glay->setColStretch( 2, 10 );
+
+ // "confirm before send" check box:
+ mConfirmSendCheck = new QCheckBox( i18n("Confirm &before send"), group );
+ glay->addMultiCellWidget( mConfirmSendCheck, 0, 0, 0, 1 );
+ connect( mConfirmSendCheck, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ // "send on check" combo:
+ mSendOnCheckCombo = new QComboBox( false, group );
+ mSendOnCheckCombo->insertStringList( QStringList()
+ << i18n("Never Automatically")
+ << i18n("On Manual Mail Checks")
+ << i18n("On All Mail Checks") );
+ glay->addWidget( mSendOnCheckCombo, 1, 1 );
+ connect( mSendOnCheckCombo, SIGNAL( activated( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ // "default send method" combo:
+ mSendMethodCombo = new QComboBox( false, group );
+ mSendMethodCombo->insertStringList( QStringList()
+ << i18n("Send Now")
+ << i18n("Send Later") );
+ glay->addWidget( mSendMethodCombo, 2, 1 );
+ connect( mSendMethodCombo, SIGNAL( activated( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+
+ // "message property" combo:
+ // ### FIXME: remove completely?
+ mMessagePropertyCombo = new QComboBox( false, group );
+ mMessagePropertyCombo->insertStringList( QStringList()
+ << i18n("Allow 8-bit")
+ << i18n("MIME Compliant (Quoted Printable)") );
+ glay->addWidget( mMessagePropertyCombo, 3, 1 );
+ connect( mMessagePropertyCombo, SIGNAL( activated( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ // "default domain" input field:
+ mDefaultDomainEdit = new KLineEdit( group );
+ glay->addMultiCellWidget( mDefaultDomainEdit, 4, 4, 1, 2 );
+ connect( mDefaultDomainEdit, SIGNAL( textChanged( const QString& ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ // labels:
+ QLabel *l = new QLabel( mSendOnCheckCombo, /*buddy*/
+ i18n("Send &messages in outbox folder:"), group );
+ glay->addWidget( l, 1, 0 );
+
+ QString msg = i18n( GlobalSettings::self()->sendOnCheckItem()->whatsThis().utf8() );
+ QWhatsThis::add( l, msg );
+ QWhatsThis::add( mSendOnCheckCombo, msg );
+
+ glay->addWidget( new QLabel( mSendMethodCombo, /*buddy*/
+ i18n("Defa&ult send method:"), group ), 2, 0 );
+ glay->addWidget( new QLabel( mMessagePropertyCombo, /*buddy*/
+ i18n("Message &property:"), group ), 3, 0 );
+ l = new QLabel( mDefaultDomainEdit, /*buddy*/
+ i18n("Defaul&t domain:"), group );
+ glay->addWidget( l, 4, 0 );
+
+ // and now: add QWhatsThis:
+ msg = i18n( "<qt><p>The default domain is used to complete email "
+ "addresses that only consist of the user's name."
+ "</p></qt>" );
+ QWhatsThis::add( l, msg );
+ QWhatsThis::add( mDefaultDomainEdit, msg );
+}
+
+
+void AccountsPage::SendingTab::slotTransportSelected()
+{
+ QListViewItem *cur = mTransportList->selectedItem();
+ mModifyTransportButton->setEnabled( cur );
+ mRemoveTransportButton->setEnabled( cur );
+ mSetDefaultTransportButton->setEnabled( cur );
+}
+
+// adds a number to @p name to make the name unique
+static inline QString uniqueName( const QStringList & list,
+ const QString & name )
+{
+ int suffix = 1;
+ QString result = name;
+ while ( list.find( result ) != list.end() ) {
+ result = i18n("%1: name; %2: number appended to it to make it unique "
+ "among a list of names", "%1 %2")
+ .arg( name ).arg( suffix );
+ suffix++;
+ }
+ return result;
+}
+
+void AccountsPage::SendingTab::slotSetDefaultTransport()
+{
+ QListViewItem *item = mTransportList->selectedItem();
+ if ( !item ) return;
+
+ KMTransportInfo ti;
+
+ QListViewItemIterator it( mTransportList );
+ for ( ; it.current(); ++it ) {
+ ti.readConfig( KMTransportInfo::findTransport( it.current()->text(0) ));
+ if ( ti.type != "sendmail" ) {
+ it.current()->setText( 1, "smtp" );
+ } else {
+ it.current()->setText( 1, "sendmail" );
+ }
+ }
+
+ if ( item->text(1) != "sendmail" ) {
+ item->setText( 1, i18n( "smtp (Default)" ));
+ } else {
+ item->setText( 1, i18n( "sendmail (Default)" ));
+ }
+
+ GlobalSettings::self()->setDefaultTransport( item->text(0) );
+
+}
+
+void AccountsPage::SendingTab::slotAddTransport()
+{
+ int transportType;
+
+ { // limit scope of selDialog
+ KMTransportSelDlg selDialog( this );
+ if ( selDialog.exec() != QDialog::Accepted ) return;
+ transportType = selDialog.selected();
+ }
+
+ KMTransportInfo *transportInfo = new KMTransportInfo();
+ switch ( transportType ) {
+ case 0: // smtp
+ transportInfo->type = QString::fromLatin1("smtp");
+ break;
+ case 1: // sendmail
+ transportInfo->type = QString::fromLatin1("sendmail");
+ transportInfo->name = i18n("Sendmail");
+ transportInfo->host = _PATH_SENDMAIL; // ### FIXME: use const, not #define
+ break;
+ default:
+ assert( 0 );
+ }
+
+ KMTransportDialog dialog( i18n("Add Transport"), transportInfo, this );
+
+ // create list of names:
+ // ### move behind dialog.exec()?
+ QStringList transportNames;
+ QPtrListIterator<KMTransportInfo> it( mTransportInfoList );
+ for ( it.toFirst() ; it.current() ; ++it )
+ transportNames << (*it)->name;
+
+ if( dialog.exec() != QDialog::Accepted ) {
+ delete transportInfo;
+ return;
+ }
+
+ // disambiguate the name by appending a number:
+ // ### FIXME: don't allow this error to happen in the first place!
+ transportInfo->name = uniqueName( transportNames, transportInfo->name );
+ // append to names and transportinfo lists:
+ transportNames << transportInfo->name;
+ mTransportInfoList.append( transportInfo );
+
+ // append to listview:
+ // ### FIXME: insert before the selected item, append on empty selection
+ QListViewItem *lastItem = mTransportList->firstChild();
+ QString typeDisplayName;
+ if ( lastItem ) {
+ typeDisplayName = transportInfo->type;
+ } else {
+ typeDisplayName = i18n("%1: type of transport. Result used in "
+ "Configure->Accounts->Sending listview, \"type\" "
+ "column, first row, to indicate that this is the "
+ "default transport", "%1 (Default)")
+ .arg( transportInfo->type );
+ GlobalSettings::self()->setDefaultTransport( transportInfo->name );
+ }
+ (void) new QListViewItem( mTransportList, lastItem, transportInfo->name,
+ typeDisplayName );
+
+ // notify anyone who cares:
+ emit transportListChanged( transportNames );
+ emit changed( true );
+}
+
+void AccountsPage::SendingTab::slotModifySelectedTransport()
+{
+ QListViewItem *item = mTransportList->selectedItem();
+ if ( !item ) return;
+
+ const QString& originalTransport = item->text(0);
+
+ QPtrListIterator<KMTransportInfo> it( mTransportInfoList );
+ for ( it.toFirst() ; it.current() ; ++it )
+ if ( (*it)->name == item->text(0) ) break;
+ if ( !it.current() ) return;
+
+ KMTransportDialog dialog( i18n("Modify Transport"), (*it), this );
+
+ if ( dialog.exec() != QDialog::Accepted ) return;
+
+ // create the list of names of transports, but leave out the current
+ // item:
+ QStringList transportNames;
+ QPtrListIterator<KMTransportInfo> jt( mTransportInfoList );
+ int entryLocation = -1;
+ for ( jt.toFirst() ; jt.current() ; ++jt )
+ if ( jt != it )
+ transportNames << (*jt)->name;
+ else
+ entryLocation = transportNames.count();
+ assert( entryLocation >= 0 );
+
+ // make the new name unique by appending a high enough number:
+ (*it)->name = uniqueName( transportNames, (*it)->name );
+ // change the list item to the new name
+ item->setText( 0, (*it)->name );
+ // and insert the new name at the position of the old in the list of
+ // strings; then broadcast the new list:
+ transportNames.insert( transportNames.at( entryLocation ), (*it)->name );
+ const QString& newTransportName = (*it)->name;
+
+ QStringList changedIdents;
+ KPIM::IdentityManager * im = kmkernel->identityManager();
+ for ( KPIM::IdentityManager::Iterator it = im->modifyBegin(); it != im->modifyEnd(); ++it ) {
+ if ( originalTransport == (*it).transport() ) {
+ (*it).setTransport( newTransportName );
+ changedIdents += (*it).identityName();
+ }
+ }
+
+ if ( !changedIdents.isEmpty() ) {
+ QString information = i18n( "This identity has been changed to use the modified transport:",
+ "These %n identities have been changed to use the modified transport:",
+ changedIdents.count() );
+ KMessageBox::informationList( this, information, changedIdents );
+ }
+
+ emit transportListChanged( transportNames );
+ emit changed( true );
+}
+
+void AccountsPage::SendingTab::slotRemoveSelectedTransport()
+{
+ QListViewItem *item = mTransportList->selectedItem();
+ if ( !item ) return;
+
+ QStringList changedIdents;
+ KPIM::IdentityManager * im = kmkernel->identityManager();
+ for ( KPIM::IdentityManager::Iterator it = im->modifyBegin(); it != im->modifyEnd(); ++it ) {
+ if ( item->text( 0 ) == (*it).transport() ) {
+ (*it).setTransport( QString::null );
+ changedIdents += (*it).identityName();
+ }
+ }
+
+ // if the deleted transport is the currently used transport reset it to default
+ const QString& currentTransport = GlobalSettings::self()->currentTransport();
+ if ( item->text( 0 ) == currentTransport ) {
+ GlobalSettings::self()->setCurrentTransport( QString::null );
+ }
+
+ if ( !changedIdents.isEmpty() ) {
+ QString information = i18n( "This identity has been changed to use the default transport:",
+ "These %n identities have been changed to use the default transport:",
+ changedIdents.count() );
+ KMessageBox::informationList( this, information, changedIdents );
+ }
+
+ QPtrListIterator<KMTransportInfo> it( mTransportInfoList );
+ for ( it.toFirst() ; it.current() ; ++it )
+ if ( (*it)->name == item->text(0) ) break;
+ if ( !it.current() ) return;
+
+ KMTransportInfo ti;
+
+ QListViewItem *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 ( ti.type != "sendmail" ) {
+ newCurrent->setText( 1, i18n("smtp (Default)") );
+ } else {
+ newCurrent->setText( 1, i18n("sendmail (Default)" ));
+ }
+ }
+ } else {
+ GlobalSettings::self()->setDefaultTransport( QString::null );
+ }
+
+ delete item;
+ mTransportInfoList.remove( it );
+
+ QStringList transportNames;
+ for ( it.toFirst() ; it.current() ; ++it )
+ transportNames << (*it)->name;
+ emit transportListChanged( transportNames );
+ emit changed( true );
+}
+
+void AccountsPage::SendingTab::doLoadFromGlobalSettings() {
+ mSendOnCheckCombo->setCurrentItem( GlobalSettings::self()->sendOnCheck() );
+}
+
+void AccountsPage::SendingTab::doLoadOther() {
+ KConfigGroup general( KMKernel::config(), "General");
+ KConfigGroup composer( KMKernel::config(), "Composer");
+
+ int numTransports = general.readNumEntry("transports", 0);
+
+ QListViewItem *top = 0;
+ mTransportInfoList.clear();
+ mTransportList->clear();
+ QStringList transportNames;
+ for ( int i = 1 ; i <= numTransports ; i++ ) {
+ KMTransportInfo *ti = new KMTransportInfo();
+ ti->readConfig(i);
+ mTransportInfoList.append( ti );
+ transportNames << ti->name;
+ top = new QListViewItem( mTransportList, top, ti->name, ti->type );
+ }
+ emit transportListChanged( transportNames );
+
+ const QString &defaultTransport = GlobalSettings::self()->defaultTransport();
+
+ QListViewItemIterator it( mTransportList );
+ for ( ; it.current(); ++it ) {
+ if ( it.current()->text(0) == defaultTransport ) {
+ if ( it.current()->text(1) != "sendmail" ) {
+ it.current()->setText( 1, i18n( "smtp (Default)" ));
+ } else {
+ it.current()->setText( 1, i18n( "sendmail (Default)" ));
+ }
+ } else {
+ if ( it.current()->text(1) != "sendmail" ) {
+ it.current()->setText( 1, "smtp" );
+ } else {
+ it.current()->setText( 1, "sendmail" );
+ }
+ }
+ }
+
+ mSendMethodCombo->setCurrentItem(
+ kmkernel->msgSender()->sendImmediate() ? 0 : 1 );
+ mMessagePropertyCombo->setCurrentItem(
+ kmkernel->msgSender()->sendQuotedPrintable() ? 1 : 0 );
+
+ mConfirmSendCheck->setChecked( composer.readBoolEntry( "confirm-before-send",
+ false ) );
+ QString str = general.readEntry( "Default domain" );
+ if( str.isEmpty() )
+ {
+ //### FIXME: Use the global convenience function instead of the homebrewed
+ // solution once we can rely on HEAD kdelibs.
+ //str = KGlobal::hostname(); ???????
+ char buffer[256];
+ if ( !gethostname( buffer, 255 ) )
+ // buffer need not be NUL-terminated if it has full length
+ buffer[255] = 0;
+ else
+ buffer[0] = 0;
+ str = QString::fromLatin1( *buffer ? buffer : "localhost" );
+ }
+ mDefaultDomainEdit->setText( str );
+}
+
+void AccountsPage::SendingTab::save() {
+ KConfigGroup general( KMKernel::config(), "General" );
+ KConfigGroup composer( KMKernel::config(), "Composer" );
+
+ // Save transports:
+ general.writeEntry( "transports", mTransportInfoList.count() );
+ QPtrListIterator<KMTransportInfo> it( mTransportInfoList );
+ for ( int i = 1 ; it.current() ; ++it, ++i )
+ (*it)->writeConfig(i);
+
+ // Save common options:
+ GlobalSettings::self()->setSendOnCheck( mSendOnCheckCombo->currentItem() );
+ kmkernel->msgSender()->setSendImmediate(
+ mSendMethodCombo->currentItem() == 0 );
+ kmkernel->msgSender()->setSendQuotedPrintable(
+ mMessagePropertyCombo->currentItem() == 1 );
+ kmkernel->msgSender()->writeConfig( false ); // don't sync
+ composer.writeEntry("confirm-before-send", mConfirmSendCheck->isChecked() );
+ general.writeEntry( "Default domain", mDefaultDomainEdit->text() );
+}
+
+QString AccountsPage::ReceivingTab::helpAnchor() const {
+ return QString::fromLatin1("configure-accounts-receiving");
+}
+
+AccountsPageReceivingTab::AccountsPageReceivingTab( QWidget * parent, const char * name )
+ : ConfigModuleTab ( parent, name )
+{
+ // temp. vars:
+ QVBoxLayout *vlay;
+ QVBoxLayout *btn_vlay;
+ QHBoxLayout *hlay;
+ QPushButton *button;
+ QGroupBox *group;
+
+ vlay = new QVBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
+
+ // label: zero stretch
+ vlay->addWidget( new QLabel( i18n("Incoming accounts (add at least one):"), this ) );
+
+ // hbox layout: stretch 10, spacing inherited from vlay
+ hlay = new QHBoxLayout();
+ vlay->addLayout( hlay, 10 ); // high stretch to suppress groupbox's growing
+
+ // account list: left widget in hlay; stretch 1
+ mAccountList = new ListView( this, "accountList", 5 );
+ mAccountList->addColumn( i18n("Name") );
+ mAccountList->addColumn( i18n("Type") );
+ mAccountList->addColumn( i18n("Folder") );
+ mAccountList->setAllColumnsShowFocus( true );
+ mAccountList->setSorting( -1 );
+ connect( mAccountList, SIGNAL(selectionChanged()),
+ this, SLOT(slotAccountSelected()) );
+ connect( mAccountList, SIGNAL(doubleClicked( QListViewItem *)),
+ this, SLOT(slotModifySelectedAccount()) );
+ hlay->addWidget( mAccountList, 1 );
+
+ // a vbox layout for the buttons: zero stretch, spacing inherited from hlay
+ btn_vlay = new QVBoxLayout( hlay );
+
+ // "add..." button: stretch 0
+ button = new QPushButton( i18n("A&dd..."), this );
+ button->setAutoDefault( false );
+ connect( button, SIGNAL(clicked()),
+ this, SLOT(slotAddAccount()) );
+ btn_vlay->addWidget( button );
+
+ // "modify..." button: stretch 0
+ mModifyAccountButton = new QPushButton( i18n("&Modify..."), this );
+ mModifyAccountButton->setAutoDefault( false );
+ mModifyAccountButton->setEnabled( false ); // b/c no item is selected yet
+ connect( mModifyAccountButton, SIGNAL(clicked()),
+ this, SLOT(slotModifySelectedAccount()) );
+ btn_vlay->addWidget( mModifyAccountButton );
+
+ // "remove..." button: stretch 0
+ mRemoveAccountButton = new QPushButton( i18n("R&emove"), this );
+ mRemoveAccountButton->setAutoDefault( false );
+ mRemoveAccountButton->setEnabled( false ); // b/c no item is selected yet
+ connect( mRemoveAccountButton, SIGNAL(clicked()),
+ this, SLOT(slotRemoveSelectedAccount()) );
+ btn_vlay->addWidget( mRemoveAccountButton );
+ btn_vlay->addStretch( 1 ); // spacer
+
+ mCheckmailStartupCheck = new QCheckBox( i18n("Chec&k mail on startup"), this );
+ vlay->addWidget( mCheckmailStartupCheck );
+ connect( mCheckmailStartupCheck, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ // "New Mail Notification" group box: stretch 0
+ group = new QVGroupBox( i18n("New Mail Notification"), this );
+ vlay->addWidget( group );
+ group->layout()->setSpacing( KDialog::spacingHint() );
+
+ // "beep on new mail" check box:
+ mBeepNewMailCheck = new QCheckBox(i18n("&Beep"), group );
+ mBeepNewMailCheck->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding,
+ QSizePolicy::Fixed ) );
+ connect( mBeepNewMailCheck, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ // "Detailed new mail notification" check box
+ mVerboseNotificationCheck =
+ new QCheckBox( i18n( "Deta&iled new mail notification" ), group );
+ mVerboseNotificationCheck->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding,
+ QSizePolicy::Fixed ) );
+ QToolTip::add( mVerboseNotificationCheck,
+ i18n( "Show for each folder the number of newly arrived "
+ "messages" ) );
+ QWhatsThis::add( mVerboseNotificationCheck,
+ GlobalSettings::self()->verboseNewMailNotificationItem()->whatsThis() );
+ connect( mVerboseNotificationCheck, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged() ) );
+
+ // "Other Actions" button:
+ mOtherNewMailActionsButton = new QPushButton( i18n("Other Actio&ns"), group );
+ mOtherNewMailActionsButton->setSizePolicy( QSizePolicy( QSizePolicy::Fixed,
+ QSizePolicy::Fixed ) );
+ connect( mOtherNewMailActionsButton, SIGNAL(clicked()),
+ this, SLOT(slotEditNotifications()) );
+}
+
+AccountsPageReceivingTab::~AccountsPageReceivingTab()
+{
+ // When hitting Cancel or closing the dialog with the window-manager-button,
+ // we have a number of things to clean up:
+
+ // The newly created accounts
+ QValueList< QGuardedPtr<KMAccount> >::Iterator it;
+ for (it = mNewAccounts.begin(); it != mNewAccounts.end(); ++it ) {
+ delete (*it);
+ }
+ mNewAccounts.clear();
+
+ // The modified accounts
+ QValueList<ModifiedAccountsType*>::Iterator j;
+ for ( j = mModifiedAccounts.begin() ; j != mModifiedAccounts.end() ; ++j ) {
+ delete (*j)->newAccount;
+ delete (*j);
+ }
+ mModifiedAccounts.clear();
+
+
+}
+
+void AccountsPage::ReceivingTab::slotAccountSelected()
+{
+ QListViewItem * item = mAccountList->selectedItem();
+ mModifyAccountButton->setEnabled( item );
+ mRemoveAccountButton->setEnabled( item );
+}
+
+QStringList AccountsPage::ReceivingTab::occupiedNames()
+{
+ QStringList accountNames = kmkernel->acctMgr()->getAccounts();
+
+ QValueList<ModifiedAccountsType*>::Iterator k;
+ for (k = mModifiedAccounts.begin(); k != mModifiedAccounts.end(); ++k )
+ if ((*k)->oldAccount)
+ accountNames.remove( (*k)->oldAccount->name() );
+
+ QValueList< QGuardedPtr<KMAccount> >::Iterator l;
+ for (l = mAccountsToDelete.begin(); l != mAccountsToDelete.end(); ++l )
+ if (*l)
+ accountNames.remove( (*l)->name() );
+
+ QValueList< QGuardedPtr<KMAccount> >::Iterator it;
+ for (it = mNewAccounts.begin(); it != mNewAccounts.end(); ++it )
+ if (*it)
+ accountNames += (*it)->name();
+
+ QValueList<ModifiedAccountsType*>::Iterator j;
+ for (j = mModifiedAccounts.begin(); j != mModifiedAccounts.end(); ++j )
+ accountNames += (*j)->newAccount->name();
+
+ return accountNames;
+}
+
+void AccountsPage::ReceivingTab::slotAddAccount() {
+ KMAcctSelDlg accountSelectorDialog( this );
+ if( accountSelectorDialog.exec() != QDialog::Accepted ) return;
+
+ const char *accountType = 0;
+ switch ( accountSelectorDialog.selected() ) {
+ case 0: accountType = "local"; break;
+ case 1: accountType = "pop"; break;
+ case 2: accountType = "imap"; break;
+ case 3: accountType = "cachedimap"; break;
+ case 4: accountType = "maildir"; break;
+
+ default:
+ // ### FIXME: How should this happen???
+ // replace with assert.
+ KMessageBox::sorry( this, i18n("Unknown account type selected") );
+ return;
+ }
+
+ KMAccount *account
+ = kmkernel->acctMgr()->create( QString::fromLatin1( accountType ) );
+ if ( !account ) {
+ // ### FIXME: Give the user more information. Is this error
+ // recoverable?
+ KMessageBox::sorry( this, i18n("Unable to create account") );
+ return;
+ }
+
+ account->init(); // fill the account fields with good default values
+
+ AccountDialog dialog( i18n("Add Account"), account, this );
+
+ QStringList accountNames = occupiedNames();
+
+ if( dialog.exec() != QDialog::Accepted ) {
+ delete account;
+ return;
+ }
+
+ account->deinstallTimer();
+ account->setName( uniqueName( accountNames, account->name() ) );
+
+ QListViewItem *after = mAccountList->firstChild();
+ while ( after && after->nextSibling() )
+ after = after->nextSibling();
+
+ QListViewItem *listItem =
+ new QListViewItem( mAccountList, after, account->name(), account->type() );
+ if( account->folder() )
+ listItem->setText( 2, account->folder()->label() );
+
+ mNewAccounts.append( account );
+ emit changed( true );
+}
+
+
+
+void AccountsPage::ReceivingTab::slotModifySelectedAccount()
+{
+ QListViewItem *listItem = mAccountList->selectedItem();
+ if( !listItem ) return;
+
+ KMAccount *account = 0;
+ QValueList<ModifiedAccountsType*>::Iterator j;
+ for (j = mModifiedAccounts.begin(); j != mModifiedAccounts.end(); ++j )
+ if ( (*j)->newAccount->name() == listItem->text(0) ) {
+ account = (*j)->newAccount;
+ break;
+ }
+
+ if ( !account ) {
+ QValueList< QGuardedPtr<KMAccount> >::Iterator it;
+ for ( it = mNewAccounts.begin() ; it != mNewAccounts.end() ; ++it )
+ if ( (*it)->name() == listItem->text(0) ) {
+ account = *it;
+ break;
+ }
+
+ if ( !account ) {
+ account = kmkernel->acctMgr()->findByName( listItem->text(0) );
+ if( !account ) {
+ // ### FIXME: How should this happen? See above.
+ KMessageBox::sorry( this, i18n("Unable to locate account") );
+ return;
+ }
+ if ( account->type() == "imap" || account->type() == "cachedimap" )
+ {
+ ImapAccountBase* ai = static_cast<ImapAccountBase*>( account );
+ if ( ai->namespaces().isEmpty() || ai->namespaceToDelimiter().isEmpty() )
+ {
+ // connect to server - the namespaces are fetched automatically
+ kdDebug(5006) << "slotModifySelectedAccount - connect" << endl;
+ ai->makeConnection();
+ }
+ }
+
+ ModifiedAccountsType *mod = new ModifiedAccountsType;
+ mod->oldAccount = account;
+ mod->newAccount = kmkernel->acctMgr()->create( account->type(),
+ account->name() );
+ mod->newAccount->pseudoAssign( account );
+ mModifiedAccounts.append( mod );
+ account = mod->newAccount;
+ }
+ }
+
+ QStringList accountNames = occupiedNames();
+ accountNames.remove( account->name() );
+
+ AccountDialog dialog( i18n("Modify Account"), account, this );
+
+ if( dialog.exec() != QDialog::Accepted ) return;
+
+ account->setName( uniqueName( accountNames, account->name() ) );
+
+ listItem->setText( 0, account->name() );
+ listItem->setText( 1, account->type() );
+ if( account->folder() )
+ listItem->setText( 2, account->folder()->label() );
+
+ emit changed( true );
+}
+
+
+
+void AccountsPage::ReceivingTab::slotRemoveSelectedAccount() {
+ QListViewItem *listItem = mAccountList->selectedItem();
+ if( !listItem ) return;
+
+ KMAccount *acct = 0;
+ QValueList<ModifiedAccountsType*>::Iterator j;
+ for ( j = mModifiedAccounts.begin() ; j != mModifiedAccounts.end() ; ++j )
+ if ( (*j)->newAccount->name() == listItem->text(0) ) {
+ acct = (*j)->oldAccount;
+ mAccountsToDelete.append( acct );
+ mModifiedAccounts.remove( j );
+ break;
+ }
+ if ( !acct ) {
+ QValueList< QGuardedPtr<KMAccount> >::Iterator it;
+ for ( it = mNewAccounts.begin() ; it != mNewAccounts.end() ; ++it )
+ if ( (*it)->name() == listItem->text(0) ) {
+ acct = *it;
+ mNewAccounts.remove( it );
+ break;
+ }
+ }
+ if ( !acct ) {
+ acct = kmkernel->acctMgr()->findByName( listItem->text(0) );
+ if ( acct )
+ mAccountsToDelete.append( acct );
+ }
+ if ( !acct ) {
+ // ### FIXME: see above
+ KMessageBox::sorry( this, i18n("<qt>Unable to locate account <b>%1</b>.</qt>")
+ .arg(listItem->text(0)) );
+ return;
+ }
+
+ QListViewItem * item = listItem->itemBelow();
+ if ( !item ) item = listItem->itemAbove();
+ delete listItem;
+
+ if ( item )
+ mAccountList->setSelected( item, true );
+
+ emit changed( true );
+}
+
+void AccountsPage::ReceivingTab::slotEditNotifications()
+{
+ if(kmkernel->xmlGuiInstance())
+ KNotifyDialog::configure(this, 0, kmkernel->xmlGuiInstance()->aboutData());
+ else
+ KNotifyDialog::configure(this);
+}
+
+void AccountsPage::ReceivingTab::doLoadFromGlobalSettings() {
+ mVerboseNotificationCheck->setChecked( GlobalSettings::self()->verboseNewMailNotification() );
+}
+
+void AccountsPage::ReceivingTab::doLoadOther() {
+ KConfigGroup general( KMKernel::config(), "General" );
+
+ mAccountList->clear();
+ QListViewItem *top = 0;
+
+ for( KMAccount *a = kmkernel->acctMgr()->first(); a!=0;
+ a = kmkernel->acctMgr()->next() ) {
+ QListViewItem *listItem =
+ new QListViewItem( mAccountList, top, a->name(), a->type() );
+ if( a->folder() )
+ listItem->setText( 2, a->folder()->label() );
+ top = listItem;
+ }
+ QListViewItem *listItem = mAccountList->firstChild();
+ if ( listItem ) {
+ mAccountList->setCurrentItem( listItem );
+ mAccountList->setSelected( listItem, true );
+ }
+
+ mBeepNewMailCheck->setChecked( general.readBoolEntry("beep-on-mail", false ) );
+ mCheckmailStartupCheck->setChecked( general.readBoolEntry("checkmail-startup", false) );
+ QTimer::singleShot( 0, this, SLOT( slotTweakAccountList() ) );
+}
+
+void AccountsPage::ReceivingTab::slotTweakAccountList()
+{
+ // Force the contentsWidth of mAccountList to be recalculated so that items can be
+ // selected in the normal way. It would be best if this were not necessary.
+ mAccountList->resizeContents( mAccountList->visibleWidth(), mAccountList->contentsHeight() );
+}
+
+void AccountsPage::ReceivingTab::save() {
+ // Add accounts marked as new
+ QValueList< QGuardedPtr<KMAccount> >::Iterator it;
+ for (it = mNewAccounts.begin(); it != mNewAccounts.end(); ++it ) {
+ kmkernel->acctMgr()->add( *it ); // calls installTimer too
+ }
+
+ // Update accounts that have been modified
+ QValueList<ModifiedAccountsType*>::Iterator j;
+ for ( j = mModifiedAccounts.begin() ; j != mModifiedAccounts.end() ; ++j ) {
+ (*j)->oldAccount->pseudoAssign( (*j)->newAccount );
+ delete (*j)->newAccount;
+ delete (*j);
+ }
+ mModifiedAccounts.clear();
+
+ // Delete accounts marked for deletion
+ for ( it = mAccountsToDelete.begin() ;
+ it != mAccountsToDelete.end() ; ++it ) {
+ kmkernel->acctMgr()->writeConfig( true );
+ if ( (*it) && !kmkernel->acctMgr()->remove(*it) )
+ KMessageBox::sorry( this, i18n("<qt>Unable to locate account <b>%1</b>.</qt>")
+ .arg( (*it)->name() ) );
+ }
+ mAccountsToDelete.clear();
+
+ // Incoming mail
+ kmkernel->acctMgr()->writeConfig( false );
+ kmkernel->cleanupImapFolders();
+
+ // Save Mail notification settings
+ KConfigGroup general( KMKernel::config(), "General" );
+ general.writeEntry( "beep-on-mail", mBeepNewMailCheck->isChecked() );
+ GlobalSettings::self()->setVerboseNewMailNotification( mVerboseNotificationCheck->isChecked() );
+
+ general.writeEntry( "checkmail-startup", mCheckmailStartupCheck->isChecked() );
+
+ // Sync new IMAP accounts ASAP:
+ for (it = mNewAccounts.begin(); it != mNewAccounts.end(); ++it ) {
+ KMAccount *macc = (*it);
+ ImapAccountBase *acc = dynamic_cast<ImapAccountBase*> (macc);
+ if ( acc ) {
+ AccountUpdater *au = new AccountUpdater( acc );
+ au->update();
+ }
+ }
+ mNewAccounts.clear();
+
+}
+
+// *************************************************************
+// * *
+// * AppearancePage *
+// * *
+// *************************************************************
+QString AppearancePage::helpAnchor() const {
+ return QString::fromLatin1("configure-appearance");
+}
+
+AppearancePage::AppearancePage( QWidget * parent, const char * name )
+ : ConfigModuleWithTabs( parent, name )
+{
+ //
+ // "Fonts" tab:
+ //
+ mFontsTab = new FontsTab();
+ addTab( mFontsTab, i18n("&Fonts") );
+
+ //
+ // "Colors" tab:
+ //
+ mColorsTab = new ColorsTab();
+ addTab( mColorsTab, i18n("Color&s") );
+
+ //
+ // "Layout" tab:
+ //
+ mLayoutTab = new LayoutTab();
+ addTab( mLayoutTab, i18n("La&yout") );
+
+ //
+ // "Headers" tab:
+ //
+ mHeadersTab = new HeadersTab();
+ addTab( mHeadersTab, i18n("M&essage List") );
+
+ //
+ // "Reader window" tab:
+ //
+ mReaderTab = new ReaderTab();
+ addTab( mReaderTab, i18n("Message W&indow") );
+
+ //
+ // "System Tray" tab:
+ //
+ mSystemTrayTab = new SystemTrayTab();
+ addTab( mSystemTrayTab, i18n("System &Tray") );
+
+ load();
+}
+
+
+QString AppearancePage::FontsTab::helpAnchor() const {
+ return QString::fromLatin1("configure-appearance-fonts");
+}
+
+static const struct {
+ const char * configName;
+ const char * displayName;
+ bool enableFamilyAndSize;
+ bool onlyFixed;
+} fontNames[] = {
+ { "body-font", I18N_NOOP("Message Body"), true, false },
+ { "list-font", I18N_NOOP("Message List"), true, false },
+ { "list-new-font", I18N_NOOP("Message List - New Messages"), true, false },
+ { "list-unread-font", I18N_NOOP("Message List - Unread Messages"), true, false },
+ { "list-important-font", I18N_NOOP("Message List - Important Messages"), true, false },
+ { "list-todo-font", I18N_NOOP("Message List - Todo Messages"), true, false },
+ { "list-date-font", I18N_NOOP("Message List - Date Field"), true, false },
+ { "folder-font", I18N_NOOP("Folder List"), true, false },
+ { "quote1-font", I18N_NOOP("Quoted Text - First Level"), false, false },
+ { "quote2-font", I18N_NOOP("Quoted Text - Second Level"), false, false },
+ { "quote3-font", I18N_NOOP("Quoted Text - Third Level"), false, false },
+ { "fixed-font", I18N_NOOP("Fixed Width Font"), true, true },
+ { "composer-font", I18N_NOOP("Composer"), true, false },
+ { "print-font", I18N_NOOP("Printing Output"), true, false },
+};
+static const int numFontNames = sizeof fontNames / sizeof *fontNames;
+
+AppearancePageFontsTab::AppearancePageFontsTab( QWidget * parent, const char * name )
+ : ConfigModuleTab( parent, name ), mActiveFontIndex( -1 )
+{
+ assert( numFontNames == sizeof mFont / sizeof *mFont );
+ // tmp. vars:
+ QVBoxLayout *vlay;
+ QHBoxLayout *hlay;
+ QLabel *label;
+
+ // "Use custom fonts" checkbox, followed by <hr>
+ vlay = new QVBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
+ mCustomFontCheck = new QCheckBox( i18n("&Use custom fonts"), this );
+ vlay->addWidget( mCustomFontCheck );
+ vlay->addWidget( new KSeparator( KSeparator::HLine, this ) );
+ connect ( mCustomFontCheck, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ // "font location" combo box and label:
+ hlay = new QHBoxLayout( vlay ); // inherites spacing
+ mFontLocationCombo = new QComboBox( false, this );
+ mFontLocationCombo->setEnabled( false ); // !mCustomFontCheck->isChecked()
+
+ QStringList fontDescriptions;
+ for ( int i = 0 ; i < numFontNames ; i++ )
+ fontDescriptions << i18n( fontNames[i].displayName );
+ mFontLocationCombo->insertStringList( fontDescriptions );
+
+ label = new QLabel( mFontLocationCombo, i18n("Apply &to:"), this );
+ label->setEnabled( false ); // since !mCustomFontCheck->isChecked()
+ hlay->addWidget( label );
+
+ hlay->addWidget( mFontLocationCombo );
+ hlay->addStretch( 10 );
+ vlay->addSpacing( KDialog::spacingHint() );
+ mFontChooser = new KFontChooser( this, "font", false, QStringList(),
+ false, 4 );
+ mFontChooser->setEnabled( false ); // since !mCustomFontCheck->isChecked()
+ vlay->addWidget( mFontChooser );
+ connect ( mFontChooser, SIGNAL( fontSelected( const QFont& ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+
+ // {en,dis}able widgets depending on the state of mCustomFontCheck:
+ connect( mCustomFontCheck, SIGNAL(toggled(bool)),
+ label, SLOT(setEnabled(bool)) );
+ connect( mCustomFontCheck, SIGNAL(toggled(bool)),
+ mFontLocationCombo, SLOT(setEnabled(bool)) );
+ connect( mCustomFontCheck, SIGNAL(toggled(bool)),
+ mFontChooser, SLOT(setEnabled(bool)) );
+ // load the right font settings into mFontChooser:
+ connect( mFontLocationCombo, SIGNAL(activated(int) ),
+ this, SLOT(slotFontSelectorChanged(int)) );
+}
+
+
+void AppearancePage::FontsTab::slotFontSelectorChanged( int index )
+{
+ kdDebug(5006) << "slotFontSelectorChanged() called" << endl;
+ if( index < 0 || index >= mFontLocationCombo->count() )
+ return; // Should never happen, but it is better to check.
+
+ // Save current fontselector setting before we install the new:
+ if( mActiveFontIndex == 0 ) {
+ mFont[0] = mFontChooser->font();
+ // hardcode the family and size of "message body" dependant fonts:
+ for ( int i = 0 ; i < numFontNames ; i++ )
+ if ( !fontNames[i].enableFamilyAndSize ) {
+ // ### shall we copy the font and set the save and re-set
+ // {regular,italic,bold,bold italic} property or should we
+ // copy only family and pointSize?
+ mFont[i].setFamily( mFont[0].family() );
+ mFont[i].setPointSize/*Float?*/( mFont[0].pointSize/*Float?*/() );
+ }
+ } else if ( mActiveFontIndex > 0 )
+ mFont[ mActiveFontIndex ] = mFontChooser->font();
+ mActiveFontIndex = index;
+
+ // Disonnect so the "Apply" button is not activated by the change
+ disconnect ( mFontChooser, SIGNAL( fontSelected( const QFont& ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ // Display the new setting:
+ mFontChooser->setFont( mFont[index], fontNames[index].onlyFixed );
+
+ connect ( mFontChooser, SIGNAL( fontSelected( const QFont& ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ // Disable Family and Size list if we have selected a quote font:
+ mFontChooser->enableColumn( KFontChooser::FamilyList|KFontChooser::SizeList,
+ fontNames[ index ].enableFamilyAndSize );
+}
+
+void AppearancePage::FontsTab::doLoadOther() {
+ KConfigGroup fonts( KMKernel::config(), "Fonts" );
+
+ mFont[0] = KGlobalSettings::generalFont();
+ QFont fixedFont = KGlobalSettings::fixedFont();
+ for ( int i = 0 ; i < numFontNames ; i++ )
+ mFont[i] = fonts.readFontEntry( fontNames[i].configName,
+ (fontNames[i].onlyFixed) ? &fixedFont : &mFont[0] );
+
+ mCustomFontCheck->setChecked( !fonts.readBoolEntry( "defaultFonts", true ) );
+ mFontLocationCombo->setCurrentItem( 0 );
+ slotFontSelectorChanged( 0 );
+}
+
+void AppearancePage::FontsTab::installProfile( KConfig * profile ) {
+ KConfigGroup fonts( profile, "Fonts" );
+
+ // read fonts that are defined in the profile:
+ bool needChange = false;
+ for ( int i = 0 ; i < numFontNames ; i++ )
+ if ( fonts.hasKey( fontNames[i].configName ) ) {
+ needChange = true;
+ mFont[i] = fonts.readFontEntry( fontNames[i].configName );
+ kdDebug(5006) << "got font \"" << fontNames[i].configName
+ << "\" thusly: \"" << mFont[i].toString() << "\"" << endl;
+ }
+ if ( needChange && mFontLocationCombo->currentItem() > 0 )
+ mFontChooser->setFont( mFont[ mFontLocationCombo->currentItem() ],
+ fontNames[ mFontLocationCombo->currentItem() ].onlyFixed );
+
+ if ( fonts.hasKey( "defaultFonts" ) )
+ mCustomFontCheck->setChecked( !fonts.readBoolEntry( "defaultFonts" ) );
+}
+
+void AppearancePage::FontsTab::save() {
+ KConfigGroup fonts( KMKernel::config(), "Fonts" );
+
+ // read the current font (might have been modified)
+ if ( mActiveFontIndex >= 0 )
+ mFont[ mActiveFontIndex ] = mFontChooser->font();
+
+ bool customFonts = mCustomFontCheck->isChecked();
+ fonts.writeEntry( "defaultFonts", !customFonts );
+ for ( int i = 0 ; i < numFontNames ; i++ )
+ if ( customFonts || fonts.hasKey( fontNames[i].configName ) )
+ // Don't write font info when we use default fonts, but write
+ // if it's already there:
+ fonts.writeEntry( fontNames[i].configName, mFont[i] );
+}
+
+QString AppearancePage::ColorsTab::helpAnchor() const {
+ return QString::fromLatin1("configure-appearance-colors");
+}
+
+
+static const struct {
+ const char * configName;
+ const char * displayName;
+} colorNames[] = { // adjust setup() if you change this:
+ { "BackgroundColor", I18N_NOOP("Composer Background") },
+ { "AltBackgroundColor", I18N_NOOP("Alternative Background Color") },
+ { "ForegroundColor", I18N_NOOP("Normal Text") },
+ { "QuotedText1", I18N_NOOP("Quoted Text - First Level") },
+ { "QuotedText2", I18N_NOOP("Quoted Text - Second Level") },
+ { "QuotedText3", I18N_NOOP("Quoted Text - Third Level") },
+ { "LinkColor", I18N_NOOP("Link") },
+ { "FollowedColor", I18N_NOOP("Followed Link") },
+ { "MisspelledColor", I18N_NOOP("Misspelled Words") },
+ { "NewMessage", I18N_NOOP("New Message") },
+ { "UnreadMessage", I18N_NOOP("Unread Message") },
+ { "FlagMessage", I18N_NOOP("Important Message") },
+ { "TodoMessage", I18N_NOOP("Todo Message") },
+ { "PGPMessageEncr", I18N_NOOP("OpenPGP Message - Encrypted") },
+ { "PGPMessageOkKeyOk", I18N_NOOP("OpenPGP Message - Valid Signature with Trusted Key") },
+ { "PGPMessageOkKeyBad", I18N_NOOP("OpenPGP Message - Valid Signature with Untrusted Key") },
+ { "PGPMessageWarn", I18N_NOOP("OpenPGP Message - Unchecked Signature") },
+ { "PGPMessageErr", I18N_NOOP("OpenPGP Message - Bad Signature") },
+ { "HTMLWarningColor", I18N_NOOP("Border Around Warning Prepending HTML Messages") },
+ { "CloseToQuotaColor", I18N_NOOP("Folder Name and Size When Close to Quota") },
+ { "ColorbarBackgroundPlain", I18N_NOOP("HTML Status Bar Background - No HTML Message") },
+ { "ColorbarForegroundPlain", I18N_NOOP("HTML Status Bar Foreground - No HTML Message") },
+ { "ColorbarBackgroundHTML", I18N_NOOP("HTML Status Bar Background - HTML Message") },
+ { "ColorbarForegroundHTML", I18N_NOOP("HTML Status Bar Foreground - HTML Message") },
+};
+static const int numColorNames = sizeof colorNames / sizeof *colorNames;
+
+AppearancePageColorsTab::AppearancePageColorsTab( QWidget * parent, const char * name )
+ : ConfigModuleTab( parent, name )
+{
+ // tmp. vars:
+ QVBoxLayout *vlay;
+
+ // "use custom colors" check box
+ vlay = new QVBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
+ mCustomColorCheck = new QCheckBox( i18n("&Use custom colors"), this );
+ vlay->addWidget( mCustomColorCheck );
+ connect( mCustomColorCheck, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ // color list box:
+ mColorList = new ColorListBox( this );
+ mColorList->setEnabled( false ); // since !mCustomColorCheck->isChecked()
+ QStringList modeList;
+ for ( int i = 0 ; i < numColorNames ; i++ )
+ mColorList->insertItem( new ColorListItem( i18n( colorNames[i].displayName ) ) );
+ vlay->addWidget( mColorList, 1 );
+
+ // "recycle colors" check box:
+ mRecycleColorCheck =
+ new QCheckBox( i18n("Recycle colors on deep &quoting"), this );
+ mRecycleColorCheck->setEnabled( false );
+ vlay->addWidget( mRecycleColorCheck );
+ connect( mRecycleColorCheck, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ // close to quota threshold
+ QHBoxLayout *hbox = new QHBoxLayout(vlay);
+ QLabel *l = new QLabel( i18n("Close to quota threshold"), this );
+ hbox->addWidget( l );
+ l->setEnabled( false );
+ mCloseToQuotaThreshold = new QSpinBox( 0, 100, 1, this );
+ connect( mCloseToQuotaThreshold, SIGNAL( valueChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+ mCloseToQuotaThreshold->setSuffix( i18n("%"));
+ hbox->addWidget( mCloseToQuotaThreshold );
+ hbox->addWidget( new QWidget(this), 2 );
+
+ // {en,dir}able widgets depending on the state of mCustomColorCheck:
+ connect( mCustomColorCheck, SIGNAL(toggled(bool)),
+ mColorList, SLOT(setEnabled(bool)) );
+ connect( mCustomColorCheck, SIGNAL(toggled(bool)),
+ mRecycleColorCheck, SLOT(setEnabled(bool)) );
+ connect( mCustomColorCheck, SIGNAL(toggled(bool)),
+ l, SLOT(setEnabled(bool)) );
+
+ connect( mCustomColorCheck, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+}
+
+void AppearancePage::ColorsTab::doLoadOther() {
+ KConfigGroup reader( KMKernel::config(), "Reader" );
+
+ mCustomColorCheck->setChecked( !reader.readBoolEntry( "defaultColors", true ) );
+ mRecycleColorCheck->setChecked( reader.readBoolEntry( "RecycleQuoteColors", false ) );
+ mCloseToQuotaThreshold->setValue( GlobalSettings::closeToQuotaThreshold() );
+
+ static const QColor defaultColor[ numColorNames ] = {
+ kapp->palette().active().base(), // bg
+ KGlobalSettings::alternateBackgroundColor(), // alt bg
+ kapp->palette().active().text(), // fg
+ QColor( 0x00, 0x80, 0x00 ), // quoted l1
+ QColor( 0x00, 0x70, 0x00 ), // quoted l2
+ QColor( 0x00, 0x60, 0x00 ), // quoted l3
+ KGlobalSettings::linkColor(), // link
+ KGlobalSettings::visitedLinkColor(), // visited link
+ Qt::red, // misspelled words
+ Qt::red, // new msg
+ Qt::blue, // unread mgs
+ QColor( 0x00, 0x7F, 0x00 ), // important msg
+ Qt::blue, // todo mgs
+ QColor( 0x00, 0x80, 0xFF ), // light blue // pgp encrypted
+ QColor( 0x40, 0xFF, 0x40 ), // light green // pgp ok, trusted key
+ QColor( 0xFF, 0xFF, 0x40 ), // light yellow // pgp ok, untrusted key
+ QColor( 0xFF, 0xFF, 0x40 ), // light yellow // pgp unchk
+ Qt::red, // pgp bad
+ QColor( 0xFF, 0x40, 0x40 ), // warning text color: light red
+ Qt::red, // close to quota
+ Qt::lightGray, // colorbar plain bg
+ Qt::black, // colorbar plain fg
+ Qt::black, // colorbar html bg
+ Qt::white, // colorbar html fg
+ };
+
+ for ( int i = 0 ; i < numColorNames ; i++ ) {
+ mColorList->setColor( i,
+ reader.readColorEntry( colorNames[i].configName, &defaultColor[i] ) );
+ }
+ connect( mColorList, SIGNAL( changed( ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+}
+
+void AppearancePage::ColorsTab::installProfile( KConfig * profile ) {
+ KConfigGroup reader( profile, "Reader" );
+
+ if ( reader.hasKey( "defaultColors" ) )
+ mCustomColorCheck->setChecked( !reader.readBoolEntry( "defaultColors" ) );
+ if ( reader.hasKey( "RecycleQuoteColors" ) )
+ mRecycleColorCheck->setChecked( reader.readBoolEntry( "RecycleQuoteColors" ) );
+
+ for ( int i = 0 ; i < numColorNames ; i++ )
+ if ( reader.hasKey( colorNames[i].configName ) )
+ mColorList->setColor( i, reader.readColorEntry( colorNames[i].configName ) );
+}
+
+void AppearancePage::ColorsTab::save() {
+ KConfigGroup reader( KMKernel::config(), "Reader" );
+
+ bool customColors = mCustomColorCheck->isChecked();
+ reader.writeEntry( "defaultColors", !customColors );
+
+ for ( int i = 0 ; i < numColorNames ; i++ )
+ // Don't write color info when we use default colors, but write
+ // if it's already there:
+ if ( customColors || reader.hasKey( colorNames[i].configName ) )
+ reader.writeEntry( colorNames[i].configName, mColorList->color(i) );
+
+ reader.writeEntry( "RecycleQuoteColors", mRecycleColorCheck->isChecked() );
+ GlobalSettings::setCloseToQuotaThreshold( mCloseToQuotaThreshold->value() );
+}
+
+QString AppearancePage::LayoutTab::helpAnchor() const {
+ return QString::fromLatin1("configure-appearance-layout");
+}
+
+static const EnumConfigEntryItem folderListModes[] = {
+ { "long", I18N_NOOP("Lon&g folder list") },
+ { "short", I18N_NOOP("Shor&t folder list" ) }
+};
+static const EnumConfigEntry folderListMode = {
+ "Geometry", "FolderList", I18N_NOOP("Folder List"),
+ folderListModes, DIM(folderListModes), 0
+};
+
+
+static const EnumConfigEntryItem mimeTreeLocations[] = {
+ { "top", I18N_NOOP("Abo&ve the message pane") },
+ { "bottom", I18N_NOOP("&Below the message pane") }
+};
+static const EnumConfigEntry mimeTreeLocation = {
+ "Reader", "MimeTreeLocation", I18N_NOOP("Message Structure Viewer Placement"),
+ mimeTreeLocations, DIM(mimeTreeLocations), 1
+};
+
+static const EnumConfigEntryItem mimeTreeModes[] = {
+ { "never", I18N_NOOP("Show &never") },
+ { "smart", I18N_NOOP("Show only for non-plaintext &messages") },
+ { "always", I18N_NOOP("Show alway&s") }
+};
+static const EnumConfigEntry mimeTreeMode = {
+ "Reader", "MimeTreeMode", I18N_NOOP("Message Structure Viewer"),
+ mimeTreeModes, DIM(mimeTreeModes), 1
+};
+
+
+static const EnumConfigEntryItem readerWindowModes[] = {
+ { "hide", I18N_NOOP("&Do not show a message preview pane") },
+ { "below", I18N_NOOP("Show the message preview pane belo&w the message list") },
+ { "right", I18N_NOOP("Show the message preview pane ne&xt to the message list") }
+};
+static const EnumConfigEntry readerWindowMode = {
+ "Geometry", "readerWindowMode", I18N_NOOP("Message Preview Pane"),
+ readerWindowModes, DIM(readerWindowModes), 1
+};
+
+AppearancePageLayoutTab::AppearancePageLayoutTab( QWidget * parent, const char * name )
+ : ConfigModuleTab( parent, name )
+{
+ // tmp. vars:
+ QVBoxLayout * vlay;
+
+ vlay = new QVBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
+
+ // "folder list" radio buttons:
+ populateButtonGroup( mFolderListGroup = new QHButtonGroup( this ), folderListMode );
+ vlay->addWidget( mFolderListGroup );
+ connect( mFolderListGroup, SIGNAL ( clicked( int ) ),
+ this, SLOT( slotEmitChanged() ) );
+
+ mFavoriteFolderViewCB = new QCheckBox( i18n("Show favorite folder view"), this );
+ connect( mFavoriteFolderViewCB, SIGNAL(toggled(bool)), SLOT(slotEmitChanged()) );
+ vlay->addWidget( mFavoriteFolderViewCB );
+
+ mFolderQuickSearchCB = new QCheckBox( i18n("Show folder quick search field"), this );
+ connect( mFolderQuickSearchCB, SIGNAL(toggled(bool)), SLOT(slotEmitChanged()) );
+ vlay->addWidget( mFolderQuickSearchCB );
+
+ // "show reader window" radio buttons:
+ populateButtonGroup( mReaderWindowModeGroup = new QVButtonGroup( this ), readerWindowMode );
+ vlay->addWidget( mReaderWindowModeGroup );
+ connect( mReaderWindowModeGroup, SIGNAL ( clicked( int ) ),
+ this, SLOT( slotEmitChanged() ) );
+
+ // "Show MIME Tree" radio buttons:
+ populateButtonGroup( mMIMETreeModeGroup = new QVButtonGroup( this ), mimeTreeMode );
+ vlay->addWidget( mMIMETreeModeGroup );
+ connect( mMIMETreeModeGroup, SIGNAL ( clicked( int ) ),
+ this, SLOT( slotEmitChanged() ) );
+
+ // "MIME Tree Location" radio buttons:
+ populateButtonGroup( mMIMETreeLocationGroup = new QHButtonGroup( this ), mimeTreeLocation );
+ vlay->addWidget( mMIMETreeLocationGroup );
+ connect( mMIMETreeLocationGroup, SIGNAL ( clicked( int ) ),
+ this, SLOT( slotEmitChanged() ) );
+
+ vlay->addStretch( 10 ); // spacer
+}
+
+void AppearancePage::LayoutTab::doLoadOther() {
+ const KConfigGroup reader( KMKernel::config(), "Reader" );
+ const KConfigGroup geometry( KMKernel::config(), "Geometry" );
+
+ loadWidget( mFolderListGroup, geometry, folderListMode );
+ loadWidget( mMIMETreeLocationGroup, reader, mimeTreeLocation );
+ loadWidget( mMIMETreeModeGroup, reader, mimeTreeMode );
+ loadWidget( mReaderWindowModeGroup, geometry, readerWindowMode );
+ mFavoriteFolderViewCB->setChecked( GlobalSettings::self()->enableFavoriteFolderView() );
+ mFolderQuickSearchCB->setChecked( GlobalSettings::self()->enableFolderQuickSearch() );
+}
+
+void AppearancePage::LayoutTab::installProfile( KConfig * profile ) {
+ const KConfigGroup reader( profile, "Reader" );
+ const KConfigGroup geometry( profile, "Geometry" );
+
+ loadProfile( mFolderListGroup, geometry, folderListMode );
+ loadProfile( mMIMETreeLocationGroup, reader, mimeTreeLocation );
+ loadProfile( mMIMETreeModeGroup, reader, mimeTreeMode );
+ loadProfile( mReaderWindowModeGroup, geometry, readerWindowMode );
+}
+
+void AppearancePage::LayoutTab::save() {
+ KConfigGroup reader( KMKernel::config(), "Reader" );
+ KConfigGroup geometry( KMKernel::config(), "Geometry" );
+
+ saveButtonGroup( mFolderListGroup, geometry, folderListMode );
+ saveButtonGroup( mMIMETreeLocationGroup, reader, mimeTreeLocation );
+ saveButtonGroup( mMIMETreeModeGroup, reader, mimeTreeMode );
+ saveButtonGroup( mReaderWindowModeGroup, geometry, readerWindowMode );
+ GlobalSettings::self()->setEnableFavoriteFolderView( mFavoriteFolderViewCB->isChecked() );
+ GlobalSettings::self()->setEnableFolderQuickSearch( mFolderQuickSearchCB->isChecked() );
+}
+
+//
+// Appearance Message List
+//
+
+QString AppearancePage::HeadersTab::helpAnchor() const {
+ return QString::fromLatin1("configure-appearance-headers");
+}
+
+static const struct {
+ const char * displayName;
+ DateFormatter::FormatType dateDisplay;
+} dateDisplayConfig[] = {
+ { I18N_NOOP("Sta&ndard format (%1)"), KMime::DateFormatter::CTime },
+ { I18N_NOOP("Locali&zed format (%1)"), KMime::DateFormatter::Localized },
+ { I18N_NOOP("Fancy for&mat (%1)"), KMime::DateFormatter::Fancy },
+ { I18N_NOOP("C&ustom format (Shift+F1 for help):"),
+ KMime::DateFormatter::Custom }
+};
+static const int numDateDisplayConfig =
+ sizeof dateDisplayConfig / sizeof *dateDisplayConfig;
+
+AppearancePageHeadersTab::AppearancePageHeadersTab( QWidget * parent, const char * name )
+ : ConfigModuleTab( parent, name ),
+ mCustomDateFormatEdit( 0 )
+{
+ // tmp. vars:
+ QButtonGroup * group;
+ QRadioButton * radio;
+
+ QVBoxLayout * vlay = new QVBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
+
+ // "General Options" group:
+ group = new QVButtonGroup( i18n( "General Options" ), this );
+ group->layout()->setSpacing( KDialog::spacingHint() );
+
+ mShowQuickSearch = new QCheckBox( i18n("Show Quick Search"), group );
+
+ mMessageSizeCheck = new QCheckBox( i18n("Display messa&ge sizes"), group );
+
+ mCryptoIconsCheck = new QCheckBox( i18n( "Show crypto &icons" ), group );
+
+ mAttachmentCheck = new QCheckBox( i18n("Show attachment icon"), group );
+
+ mNestedMessagesCheck =
+ new QCheckBox( i18n("&Threaded message list"), group );
+
+ connect( mShowQuickSearch, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+ connect( mMessageSizeCheck, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+ connect( mAttachmentCheck, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+ connect( mCryptoIconsCheck, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+ connect( mNestedMessagesCheck, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+
+ vlay->addWidget( group );
+
+ // "Message Header Threading Options" group:
+ mNestingPolicy =
+ new QVButtonGroup( i18n("Threaded Message List Options"), this );
+ mNestingPolicy->layout()->setSpacing( KDialog::spacingHint() );
+
+ mNestingPolicy->insert(
+ new QRadioButton( i18n("Always &keep threads open"),
+ mNestingPolicy ), 0 );
+ mNestingPolicy->insert(
+ new QRadioButton( i18n("Threads default to o&pen"),
+ mNestingPolicy ), 1 );
+ mNestingPolicy->insert(
+ new QRadioButton( i18n("Threads default to closed"),
+ mNestingPolicy ), 2 );
+ mNestingPolicy->insert(
+ new QRadioButton( i18n("Open threads that contain ne&w, unread "
+ "or important messages and open watched threads."),
+ mNestingPolicy ), 3 );
+
+ vlay->addWidget( mNestingPolicy );
+
+ connect( mNestingPolicy, SIGNAL( clicked( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ // "Date Display" group:
+ mDateDisplay = new QVButtonGroup( i18n("Date Display"), this );
+ mDateDisplay->layout()->setSpacing( KDialog::spacingHint() );
+
+ for ( int i = 0 ; i < numDateDisplayConfig ; i++ ) {
+ QString buttonLabel = i18n(dateDisplayConfig[i].displayName);
+ if ( buttonLabel.contains("%1") )
+ buttonLabel = buttonLabel.arg( DateFormatter::formatCurrentDate( dateDisplayConfig[i].dateDisplay ) );
+ radio = new QRadioButton( buttonLabel, mDateDisplay );
+ mDateDisplay->insert( radio, i );
+ if ( dateDisplayConfig[i].dateDisplay == DateFormatter::Custom ) {
+ mCustomDateFormatEdit = new KLineEdit( mDateDisplay );
+ mCustomDateFormatEdit->setEnabled( false );
+ connect( radio, SIGNAL(toggled(bool)),
+ mCustomDateFormatEdit, SLOT(setEnabled(bool)) );
+ connect( mCustomDateFormatEdit, SIGNAL(textChanged(const QString&)),
+ this, SLOT(slotEmitChanged(void)) );
+ QString customDateWhatsThis =
+ i18n("<qt><p><strong>These expressions may be used for the date:"
+ "</strong></p>"
+ "<ul>"
+ "<li>d - the day as a number without a leading zero (1-31)</li>"
+ "<li>dd - the day as a number with a leading zero (01-31)</li>"
+ "<li>ddd - the abbreviated day name (Mon - Sun)</li>"
+ "<li>dddd - the long day name (Monday - Sunday)</li>"
+ "<li>M - the month as a number without a leading zero (1-12)</li>"
+ "<li>MM - the month as a number with a leading zero (01-12)</li>"
+ "<li>MMM - the abbreviated month name (Jan - Dec)</li>"
+ "<li>MMMM - the long month name (January - December)</li>"
+ "<li>yy - the year as a two digit number (00-99)</li>"
+ "<li>yyyy - the year as a four digit number (0000-9999)</li>"
+ "</ul>"
+ "<p><strong>These expressions may be used for the time:"
+ "</string></p> "
+ "<ul>"
+ "<li>h - the hour without a leading zero (0-23 or 1-12 if AM/PM display)</li>"
+ "<li>hh - the hour with a leading zero (00-23 or 01-12 if AM/PM display)</li>"
+ "<li>m - the minutes without a leading zero (0-59)</li>"
+ "<li>mm - the minutes with a leading zero (00-59)</li>"
+ "<li>s - the seconds without a leading zero (0-59)</li>"
+ "<li>ss - the seconds with a leading zero (00-59)</li>"
+ "<li>z - the milliseconds without leading zeroes (0-999)</li>"
+ "<li>zzz - the milliseconds with leading zeroes (000-999)</li>"
+ "<li>AP - switch to AM/PM display. AP will be replaced by either \"AM\" or \"PM\".</li>"
+ "<li>ap - switch to AM/PM display. ap will be replaced by either \"am\" or \"pm\".</li>"
+ "<li>Z - time zone in numeric form (-0500)</li>"
+ "</ul>"
+ "<p><strong>All other input characters will be ignored."
+ "</strong></p></qt>");
+ QWhatsThis::add( mCustomDateFormatEdit, customDateWhatsThis );
+ QWhatsThis::add( radio, customDateWhatsThis );
+ }
+ } // end for loop populating mDateDisplay
+
+ vlay->addWidget( mDateDisplay );
+ connect( mDateDisplay, SIGNAL( clicked( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+
+ vlay->addStretch( 10 ); // spacer
+}
+
+void AppearancePage::HeadersTab::doLoadOther() {
+ KConfigGroup general( KMKernel::config(), "General" );
+ KConfigGroup geometry( KMKernel::config(), "Geometry" );
+
+ // "General Options":
+ mNestedMessagesCheck->setChecked( geometry.readBoolEntry( "nestedMessages", false ) );
+ 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 );
+ if ( num < 0 || num > 3 ) num = 3;
+ mNestingPolicy->setButton( num );
+
+ // "Date Display":
+ setDateDisplay( general.readNumEntry( "dateFormat", DateFormatter::Fancy ),
+ general.readEntry( "customDateFormat" ) );
+}
+
+void AppearancePage::HeadersTab::setDateDisplay( int num, const QString & format ) {
+ DateFormatter::FormatType dateDisplay =
+ static_cast<DateFormatter::FormatType>( num );
+
+ // special case: needs text for the line edit:
+ if ( dateDisplay == DateFormatter::Custom )
+ mCustomDateFormatEdit->setText( format );
+
+ for ( int i = 0 ; i < numDateDisplayConfig ; i++ )
+ if ( dateDisplay == dateDisplayConfig[i].dateDisplay ) {
+ mDateDisplay->setButton( i );
+ return;
+ }
+ // fell through since none found:
+ mDateDisplay->setButton( numDateDisplayConfig - 2 ); // default
+}
+
+void AppearancePage::HeadersTab::installProfile( KConfig * profile ) {
+ KConfigGroup general( profile, "General" );
+ KConfigGroup geometry( profile, "Geometry" );
+
+ if ( geometry.hasKey( "nestedMessages" ) )
+ mNestedMessagesCheck->setChecked( geometry.readBoolEntry( "nestedMessages" ) );
+ if ( general.hasKey( "showMessageSize" ) )
+ mMessageSizeCheck->setChecked( general.readBoolEntry( "showMessageSize" ) );
+
+ if( general.hasKey( "showCryptoIcons" ) )
+ mCryptoIconsCheck->setChecked( general.readBoolEntry( "showCryptoIcons" ) );
+ if ( general.hasKey( "showAttachmentIcon" ) )
+ mAttachmentCheck->setChecked( general.readBoolEntry( "showAttachmentIcon" ) );
+
+ if ( geometry.hasKey( "nestingPolicy" ) ) {
+ int num = geometry.readNumEntry( "nestingPolicy" );
+ if ( num < 0 || num > 3 ) num = 3;
+ mNestingPolicy->setButton( num );
+ }
+
+ if ( general.hasKey( "dateFormat" ) )
+ setDateDisplay( general.readNumEntry( "dateFormat" ),
+ general.readEntry( "customDateFormat" ) );
+}
+
+void AppearancePage::HeadersTab::save() {
+ KConfigGroup general( KMKernel::config(), "General" );
+ KConfigGroup geometry( KMKernel::config(), "Geometry" );
+
+ if ( geometry.readBoolEntry( "nestedMessages", false )
+ != mNestedMessagesCheck->isChecked() ) {
+ int result = KMessageBox::warningContinueCancel( this,
+ i18n("Changing the global threading setting will override "
+ "all folder specific values."),
+ QString::null, KStdGuiItem::cont(), "threadOverride" );
+ if ( result == KMessageBox::Continue ) {
+ geometry.writeEntry( "nestedMessages", mNestedMessagesCheck->isChecked() );
+ // remove all threadMessagesOverride keys from all [Folder-*] groups:
+ QStringList groups = KMKernel::config()->groupList().grep( QRegExp("^Folder-") );
+ kdDebug(5006) << "groups.count() == " << groups.count() << endl;
+ for ( QStringList::const_iterator it = groups.begin() ; it != groups.end() ; ++it ) {
+ KConfigGroup group( KMKernel::config(), *it );
+ group.deleteEntry( "threadMessagesOverride" );
+ }
+ }
+ }
+
+ geometry.writeEntry( "nestingPolicy",
+ mNestingPolicy->id( mNestingPolicy->selected() ) );
+ 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:
+ assert( dateDisplayID >= 0 ); assert( dateDisplayID < numDateDisplayConfig );
+ general.writeEntry( "dateFormat",
+ dateDisplayConfig[ dateDisplayID ].dateDisplay );
+ general.writeEntry( "customDateFormat", mCustomDateFormatEdit->text() );
+}
+
+
+//
+// Message Window
+//
+
+
+static const BoolConfigEntry showColorbarMode = {
+ "Reader", "showColorbar", I18N_NOOP("Show HTML stat&us bar"), false
+};
+
+static const BoolConfigEntry showSpamStatusMode = {
+ "Reader", "showSpamStatus", I18N_NOOP("Show s&pam status in fancy headers"), true
+};
+
+static const BoolConfigEntry showEmoticons = {
+ "Reader", "ShowEmoticons", I18N_NOOP("Replace smileys by emoticons"), true
+};
+
+static const BoolConfigEntry shrinkQuotes = {
+ "Reader", "ShrinkQuotes", I18N_NOOP("Use smaller font for quoted text"), false
+};
+
+static const BoolConfigEntry showExpandQuotesMark= {
+ "Reader", "ShowExpandQuotesMark", I18N_NOOP("Show expand/collapse quote marks"), false
+};
+
+
+QString AppearancePage::ReaderTab::helpAnchor() const {
+ return QString::fromLatin1("configure-appearance-reader");
+}
+
+AppearancePageReaderTab::AppearancePageReaderTab( QWidget * parent,
+ const char * name )
+ : ConfigModuleTab( parent, name )
+{
+ QVBoxLayout *vlay = new QVBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
+
+ // "show colorbar" check box:
+ populateCheckBox( mShowColorbarCheck = new QCheckBox( this ), showColorbarMode );
+ vlay->addWidget( mShowColorbarCheck );
+ connect( mShowColorbarCheck, SIGNAL ( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged() ) );
+
+ // "show spam status" check box;
+ populateCheckBox( mShowSpamStatusCheck = new QCheckBox( this ), showSpamStatusMode );
+ vlay->addWidget( mShowSpamStatusCheck );
+ connect( mShowSpamStatusCheck, SIGNAL ( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged() ) );
+
+ // "replace smileys by emoticons" check box;
+ populateCheckBox( mShowEmoticonsCheck = new QCheckBox( this ), showEmoticons );
+ vlay->addWidget( mShowEmoticonsCheck );
+ connect( mShowEmoticonsCheck, SIGNAL ( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged() ) );
+
+ // "Use smaller font for quoted text" check box
+ mShrinkQuotesCheck = new QCheckBox( i18n( shrinkQuotes.desc ), this,
+ "kcfg_ShrinkQuotes" );
+ vlay->addWidget( mShrinkQuotesCheck );
+ connect( mShrinkQuotesCheck, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged() ) );
+
+ // "Show expand/collaps quote marks" check box;
+ QHBoxLayout *hlay= new QHBoxLayout( vlay ); // inherits spacing
+ populateCheckBox( mShowExpandQuotesMark= new QCheckBox( this ), showExpandQuotesMark);
+ hlay->addWidget( mShowExpandQuotesMark);
+ connect( mShowExpandQuotesMark, SIGNAL ( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged() ) );
+
+ hlay->addStretch( 1 );
+ mCollapseQuoteLevelSpin = new KIntSpinBox( 0/*min*/,10/*max*/,1/*step*/,
+ 3/*init*/,10/*base*/,this );
+
+ QLabel *label = new QLabel( mCollapseQuoteLevelSpin,
+ GlobalSettings::self()->collapseQuoteLevelSpinItem()->label(), this );
+
+ hlay->addWidget( label );
+
+ mCollapseQuoteLevelSpin->setEnabled( false ); //since !mShowExpandQuotesMark->isCheckec()
+ connect( mCollapseQuoteLevelSpin, SIGNAL( valueChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+ hlay->addWidget( mCollapseQuoteLevelSpin);
+
+ connect( mShowExpandQuotesMark, SIGNAL( toggled( bool ) ),
+ mCollapseQuoteLevelSpin, SLOT( setEnabled( bool ) ) );
+
+ // Fallback Character Encoding
+ hlay = new QHBoxLayout( vlay ); // inherits spacing
+ mCharsetCombo = new QComboBox( this );
+ mCharsetCombo->insertStringList( KMMsgBase::supportedEncodings( false ) );
+
+ connect( mCharsetCombo, SIGNAL( activated( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ QString fallbackCharsetWhatsThis =
+ i18n( GlobalSettings::self()->fallbackCharacterEncodingItem()->whatsThis().utf8() );
+ QWhatsThis::add( mCharsetCombo, fallbackCharsetWhatsThis );
+
+ label = new QLabel( i18n("Fallback ch&aracter encoding:"), this );
+ label->setBuddy( mCharsetCombo );
+
+ hlay->addWidget( label );
+ hlay->addWidget( mCharsetCombo );
+
+ // Override Character Encoding
+ QHBoxLayout *hlay2 = new QHBoxLayout( vlay ); // inherits spacing
+ mOverrideCharsetCombo = new QComboBox( this );
+ QStringList encodings = KMMsgBase::supportedEncodings( false );
+ encodings.prepend( i18n( "Auto" ) );
+ mOverrideCharsetCombo->insertStringList( encodings );
+ mOverrideCharsetCombo->setCurrentItem(0);
+
+ connect( mOverrideCharsetCombo, SIGNAL( activated( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ QString overrideCharsetWhatsThis =
+ i18n( GlobalSettings::self()->overrideCharacterEncodingItem()->whatsThis().utf8() );
+ QWhatsThis::add( mOverrideCharsetCombo, overrideCharsetWhatsThis );
+
+ label = new QLabel( i18n("&Override character encoding:"), this );
+ label->setBuddy( mOverrideCharsetCombo );
+
+ hlay2->addWidget( label );
+ hlay2->addWidget( mOverrideCharsetCombo );
+
+ vlay->addStretch( 100 ); // spacer
+}
+
+
+void AppearancePage::ReaderTab::readCurrentFallbackCodec()
+{
+ QStringList encodings = KMMsgBase::supportedEncodings( false );
+ QStringList::ConstIterator it( encodings.begin() );
+ QStringList::ConstIterator end( encodings.end() );
+ QString currentEncoding = GlobalSettings::self()->fallbackCharacterEncoding();
+ currentEncoding = currentEncoding.replace( "iso ", "iso-", false );
+ ///kdDebug(5006) << "Looking for encoding: " << currentEncoding << endl;
+ int i = 0;
+ int indexOfLatin9 = 0;
+ bool found = false;
+ for( ; it != end; ++it)
+ {
+ const QString encoding = KGlobal::charsets()->encodingForName(*it);
+ if ( encoding == "iso-8859-15" )
+ indexOfLatin9 = i;
+ if( encoding == currentEncoding )
+ {
+ mCharsetCombo->setCurrentItem( i );
+ found = true;
+ break;
+ }
+ i++;
+ }
+ if ( !found ) // nothing matched, use latin9
+ mCharsetCombo->setCurrentItem( indexOfLatin9 );
+}
+
+void AppearancePage::ReaderTab::readCurrentOverrideCodec()
+{
+ const QString &currentOverrideEncoding = GlobalSettings::self()->overrideCharacterEncoding();
+ if ( currentOverrideEncoding.isEmpty() ) {
+ mOverrideCharsetCombo->setCurrentItem( 0 );
+ return;
+ }
+ QStringList encodings = KMMsgBase::supportedEncodings( false );
+ encodings.prepend( i18n( "Auto" ) );
+ QStringList::Iterator it( encodings.begin() );
+ QStringList::Iterator end( encodings.end() );
+ uint i = 0;
+ for( ; it != end; ++it)
+ {
+ if( KGlobal::charsets()->encodingForName(*it) == currentOverrideEncoding )
+ {
+ mOverrideCharsetCombo->setCurrentItem( i );
+ break;
+ }
+ i++;
+ }
+ if ( i == encodings.size() ) {
+ // the current value of overrideCharacterEncoding is an unknown encoding => reset to Auto
+ kdWarning(5006) << "Unknown override character encoding \"" << currentOverrideEncoding
+ << "\". Resetting to Auto." << endl;
+ mOverrideCharsetCombo->setCurrentItem( 0 );
+ GlobalSettings::self()->setOverrideCharacterEncoding( QString::null );
+ }
+}
+
+void AppearancePage::ReaderTab::doLoadFromGlobalSettings()
+{
+ mShowEmoticonsCheck->setChecked( GlobalSettings::self()->showEmoticons() );
+ mShrinkQuotesCheck->setChecked( GlobalSettings::self()->shrinkQuotes() );
+ mShowExpandQuotesMark->setChecked( GlobalSettings::self()->showExpandQuotesMark() );
+ mCollapseQuoteLevelSpin->setValue( GlobalSettings::self()->collapseQuoteLevelSpin() );
+ readCurrentFallbackCodec();
+ readCurrentOverrideCodec();
+}
+
+void AppearancePage::ReaderTab::doLoadOther()
+{
+ const KConfigGroup reader( KMKernel::config(), "Reader" );
+ loadWidget( mShowColorbarCheck, reader, showColorbarMode );
+ loadWidget( mShowSpamStatusCheck, reader, showSpamStatusMode );
+}
+
+
+void AppearancePage::ReaderTab::save() {
+ KConfigGroup reader( KMKernel::config(), "Reader" );
+ saveCheckBox( mShowColorbarCheck, reader, showColorbarMode );
+ saveCheckBox( mShowSpamStatusCheck, reader, showSpamStatusMode );
+ GlobalSettings::self()->setShowEmoticons( mShowEmoticonsCheck->isChecked() );
+ GlobalSettings::self()->setShrinkQuotes( mShrinkQuotesCheck->isChecked() );
+ GlobalSettings::self()->setShowExpandQuotesMark( mShowExpandQuotesMark->isChecked() );
+
+ GlobalSettings::self()->setCollapseQuoteLevelSpin( mCollapseQuoteLevelSpin->value() );
+ GlobalSettings::self()->setFallbackCharacterEncoding(
+ KGlobal::charsets()->encodingForName( mCharsetCombo->currentText() ) );
+ GlobalSettings::self()->setOverrideCharacterEncoding(
+ mOverrideCharsetCombo->currentItem() == 0 ?
+ QString() :
+ KGlobal::charsets()->encodingForName( mOverrideCharsetCombo->currentText() ) );
+}
+
+
+void AppearancePage::ReaderTab::installProfile( KConfig * /* profile */ ) {
+ const KConfigGroup reader( KMKernel::config(), "Reader" );
+ loadProfile( mShowColorbarCheck, reader, showColorbarMode );
+ loadProfile( mShowSpamStatusCheck, reader, showSpamStatusMode );
+ loadProfile( mShowEmoticonsCheck, reader, showEmoticons );
+ loadProfile( mShrinkQuotesCheck, reader, shrinkQuotes );
+ loadProfile( mShowExpandQuotesMark, reader, showExpandQuotesMark);
+}
+
+
+QString AppearancePage::SystemTrayTab::helpAnchor() const {
+ return QString::fromLatin1("configure-appearance-systemtray");
+}
+
+AppearancePageSystemTrayTab::AppearancePageSystemTrayTab( QWidget * parent,
+ const char * name )
+ : ConfigModuleTab( parent, name )
+{
+ QVBoxLayout * vlay = new QVBoxLayout( this, KDialog::marginHint(),
+ KDialog::spacingHint() );
+
+ // "Enable system tray applet" check box
+ mSystemTrayCheck = new QCheckBox( i18n("Enable system tray icon"), this );
+ vlay->addWidget( mSystemTrayCheck );
+ connect( mSystemTrayCheck, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ // System tray modes
+ mSystemTrayGroup = new QVButtonGroup( i18n("System Tray Mode"), this );
+ mSystemTrayGroup->layout()->setSpacing( KDialog::spacingHint() );
+ vlay->addWidget( mSystemTrayGroup );
+ connect( mSystemTrayGroup, SIGNAL( clicked( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+ connect( mSystemTrayCheck, SIGNAL( toggled( bool ) ),
+ mSystemTrayGroup, SLOT( setEnabled( bool ) ) );
+
+ mSystemTrayGroup->insert( new QRadioButton( i18n("Always show KMail in system tray"), mSystemTrayGroup ),
+ GlobalSettings::EnumSystemTrayPolicy::ShowAlways );
+
+ mSystemTrayGroup->insert( new QRadioButton( i18n("Only show KMail in system tray if there are unread messages"), mSystemTrayGroup ),
+ GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread );
+
+ vlay->addStretch( 10 ); // spacer
+}
+
+void AppearancePage::SystemTrayTab::doLoadFromGlobalSettings() {
+ mSystemTrayCheck->setChecked( GlobalSettings::self()->systemTrayEnabled() );
+ mSystemTrayGroup->setButton( GlobalSettings::self()->systemTrayPolicy() );
+ mSystemTrayGroup->setEnabled( mSystemTrayCheck->isChecked() );
+}
+
+void AppearancePage::SystemTrayTab::installProfile( KConfig * profile ) {
+ KConfigGroup general( profile, "General" );
+
+ if ( general.hasKey( "SystemTrayEnabled" ) ) {
+ mSystemTrayCheck->setChecked( general.readBoolEntry( "SystemTrayEnabled" ) );
+ }
+ if ( general.hasKey( "SystemTrayPolicy" ) ) {
+ mSystemTrayGroup->setButton( general.readNumEntry( "SystemTrayPolicy" ) );
+ }
+ mSystemTrayGroup->setEnabled( mSystemTrayCheck->isChecked() );
+}
+
+void AppearancePage::SystemTrayTab::save() {
+ GlobalSettings::self()->setSystemTrayEnabled( mSystemTrayCheck->isChecked() );
+ GlobalSettings::self()->setSystemTrayPolicy( mSystemTrayGroup->id( mSystemTrayGroup->selected() ) );
+}
+
+
+// *************************************************************
+// * *
+// * ComposerPage *
+// * *
+// *************************************************************
+
+QString ComposerPage::helpAnchor() const {
+ return QString::fromLatin1("configure-composer");
+}
+
+ComposerPage::ComposerPage( QWidget * parent, const char * name )
+ : ConfigModuleWithTabs( parent, name )
+{
+ //
+ // "General" tab:
+ //
+ mGeneralTab = new GeneralTab();
+ addTab( mGeneralTab, i18n("&General") );
+ addConfig( GlobalSettings::self(), mGeneralTab );
+
+ //
+ // "Phrases" tab:
+ //
+ // mPhrasesTab = new PhrasesTab();
+ // addTab( mPhrasesTab, i18n("&Phrases") );
+
+ //
+ // "Templates" tab:
+ //
+ mTemplatesTab = new TemplatesTab();
+ addTab( mTemplatesTab, i18n("&Templates") );
+
+ //
+ // "Custom Templates" tab:
+ //
+ mCustomTemplatesTab = new CustomTemplatesTab();
+ addTab( mCustomTemplatesTab, i18n("&Custom Templates") );
+
+ //
+ // "Subject" tab:
+ //
+ mSubjectTab = new SubjectTab();
+ addTab( mSubjectTab, i18n("&Subject") );
+ addConfig( GlobalSettings::self(), mSubjectTab );
+
+ //
+ // "Charset" tab:
+ //
+ mCharsetTab = new CharsetTab();
+ addTab( mCharsetTab, i18n("Cha&rset") );
+
+ //
+ // "Headers" tab:
+ //
+ mHeadersTab = new HeadersTab();
+ addTab( mHeadersTab, i18n("H&eaders") );
+
+ //
+ // "Attachments" tab:
+ //
+ mAttachmentsTab = new AttachmentsTab();
+ addTab( mAttachmentsTab, i18n("Config->Composer->Attachments", "A&ttachments") );
+ load();
+}
+
+QString ComposerPage::GeneralTab::helpAnchor() const {
+ return QString::fromLatin1("configure-composer-general");
+}
+
+ComposerPageGeneralTab::ComposerPageGeneralTab( QWidget * parent, const char * name )
+ : ConfigModuleTab( parent, name )
+{
+ // tmp. vars:
+ QVBoxLayout *vlay;
+ QHBoxLayout *hlay;
+ QGroupBox *group;
+ QLabel *label;
+ QHBox *hbox;
+ QString msg;
+
+ vlay = new QVBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
+
+ // some check buttons...
+ mAutoAppSignFileCheck = new QCheckBox(
+ GlobalSettings::self()->autoTextSignatureItem()->label(),
+ this );
+ vlay->addWidget( mAutoAppSignFileCheck );
+ connect( mAutoAppSignFileCheck, SIGNAL( stateChanged(int) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ mTopQuoteCheck =
+ new QCheckBox( GlobalSettings::self()->prependSignatureItem()->label(), this );
+ vlay->addWidget( mTopQuoteCheck);
+ connect( mTopQuoteCheck, SIGNAL( stateChanged(int) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ mSmartQuoteCheck = new QCheckBox(
+ GlobalSettings::self()->smartQuoteItem()->label(),
+ this, "kcfg_SmartQuote" );
+ vlay->addWidget( mSmartQuoteCheck );
+ connect( mSmartQuoteCheck, SIGNAL( stateChanged(int) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ mAutoRequestMDNCheck = new QCheckBox(
+ GlobalSettings::self()->requestMDNItem()->label(),
+ this, "kcfg_RequestMDN" );
+ vlay->addWidget( mAutoRequestMDNCheck );
+ connect( mAutoRequestMDNCheck, SIGNAL( stateChanged(int) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ mShowRecentAddressesInComposer = new QCheckBox(
+ GlobalSettings::self()->showRecentAddressesInComposerItem()->label(),
+ this, "kcfg_ShowRecentAddressesInComposer" );
+ vlay->addWidget( mShowRecentAddressesInComposer );
+ connect( mShowRecentAddressesInComposer, SIGNAL( stateChanged(int) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ // a checkbox for "word wrap" and a spinbox for the column in
+ // which to wrap:
+ hlay = new QHBoxLayout( vlay ); // inherits spacing
+ mWordWrapCheck = new QCheckBox(
+ GlobalSettings::self()->wordWrapItem()->label(),
+ this, "kcfg_WordWrap" );
+ hlay->addWidget( mWordWrapCheck );
+ connect( mWordWrapCheck, SIGNAL( stateChanged(int) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ mWrapColumnSpin = new KIntSpinBox( 30/*min*/, 78/*max*/, 1/*step*/,
+ 78/*init*/, 10 /*base*/, this, "kcfg_LineWrapWidth" );
+ mWrapColumnSpin->setEnabled( false ); // since !mWordWrapCheck->isChecked()
+ connect( mWrapColumnSpin, SIGNAL( valueChanged(int) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ hlay->addWidget( mWrapColumnSpin );
+ hlay->addStretch( 1 );
+ // only enable the spinbox if the checkbox is checked:
+ connect( mWordWrapCheck, SIGNAL(toggled(bool)),
+ mWrapColumnSpin, SLOT(setEnabled(bool)) );
+
+ hlay = new QHBoxLayout( vlay ); // inherits spacing
+ mAutoSave = new KIntSpinBox( 0, 60, 1, 1, 10, this, "kcfg_AutosaveInterval" );
+ label = new QLabel( mAutoSave,
+ GlobalSettings::self()->autosaveIntervalItem()->label(), this );
+ hlay->addWidget( label );
+ hlay->addWidget( mAutoSave );
+ mAutoSave->setSpecialValueText( i18n("No autosave") );
+ mAutoSave->setSuffix( i18n(" min") );
+ hlay->addStretch( 1 );
+ connect( mAutoSave, SIGNAL( valueChanged(int) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ hlay = new QHBoxLayout( vlay ); // inherits spacing
+ QPushButton *completionOrderBtn = new QPushButton( i18n( "Configure Completion Order" ), this );
+ connect( completionOrderBtn, SIGNAL( clicked() ),
+ this, SLOT( slotConfigureCompletionOrder() ) );
+ hlay->addWidget( completionOrderBtn );
+ hlay->addItem( new QSpacerItem(0, 0) );
+
+ // recent addresses
+ hlay = new QHBoxLayout( vlay ); // inherits spacing
+ QPushButton *recentAddressesBtn = new QPushButton( i18n( "Edit Recent Addresses..." ), this );
+ connect( recentAddressesBtn, SIGNAL( clicked() ),
+ this, SLOT( slotConfigureRecentAddresses() ) );
+ hlay->addWidget( recentAddressesBtn );
+ hlay->addItem( new QSpacerItem(0, 0) );
+
+ // The "external editor" group:
+ group = new QVGroupBox( i18n("External Editor"), this );
+ group->layout()->setSpacing( KDialog::spacingHint() );
+
+ mExternalEditorCheck = new QCheckBox(
+ GlobalSettings::self()->useExternalEditorItem()->label(),
+ group, "kcfg_UseExternalEditor" );
+ connect( mExternalEditorCheck, SIGNAL( toggled( bool ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ hbox = new QHBox( group );
+ label = new QLabel( GlobalSettings::self()->externalEditorItem()->label(),
+ hbox );
+ mEditorRequester = new KURLRequester( hbox, "kcfg_ExternalEditor" );
+ connect( mEditorRequester, SIGNAL( urlSelected(const QString&) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+ connect( mEditorRequester, SIGNAL( textChanged(const QString&) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ hbox->setStretchFactor( mEditorRequester, 1 );
+ label->setBuddy( mEditorRequester );
+ label->setEnabled( false ); // since !mExternalEditorCheck->isChecked()
+ // ### FIXME: allow only executables (x-bit when available..)
+ mEditorRequester->setFilter( "application/x-executable "
+ "application/x-shellscript "
+ "application/x-desktop" );
+ mEditorRequester->setEnabled( false ); // !mExternalEditorCheck->isChecked()
+ connect( mExternalEditorCheck, SIGNAL(toggled(bool)),
+ label, SLOT(setEnabled(bool)) );
+ connect( mExternalEditorCheck, SIGNAL(toggled(bool)),
+ mEditorRequester, SLOT(setEnabled(bool)) );
+
+ label = new QLabel( i18n("<b>%f</b> will be replaced with the "
+ "filename to edit."), group );
+ label->setEnabled( false ); // see above
+ connect( mExternalEditorCheck, SIGNAL(toggled(bool)),
+ label, SLOT(setEnabled(bool)) );
+
+ vlay->addWidget( group );
+ vlay->addStretch( 100 );
+}
+
+void ComposerPage::GeneralTab::doLoadFromGlobalSettings() {
+ // various check boxes:
+
+ mAutoAppSignFileCheck->setChecked(
+ GlobalSettings::self()->autoTextSignature()=="auto" );
+ mTopQuoteCheck->setChecked( GlobalSettings::self()->prependSignature() );
+ mSmartQuoteCheck->setChecked( GlobalSettings::self()->smartQuote() );
+ mAutoRequestMDNCheck->setChecked( GlobalSettings::self()->requestMDN() );
+ mWordWrapCheck->setChecked( GlobalSettings::self()->wordWrap() );
+
+ mWrapColumnSpin->setValue( GlobalSettings::self()->lineWrapWidth() );
+ mAutoSave->setValue( GlobalSettings::self()->autosaveInterval() );
+
+ // editor group:
+ mExternalEditorCheck->setChecked( GlobalSettings::self()->useExternalEditor() );
+ mEditorRequester->setURL( GlobalSettings::self()->externalEditor() );
+}
+
+void ComposerPage::GeneralTab::installProfile( KConfig * profile ) {
+ KConfigGroup composer( profile, "Composer" );
+ KConfigGroup general( profile, "General" );
+
+ if ( composer.hasKey( "signature" ) ) {
+ bool state = composer.readBoolEntry("signature");
+ mAutoAppSignFileCheck->setChecked( state );
+ }
+ if ( composer.hasKey( "prepend-signature" ) )
+ mTopQuoteCheck->setChecked( composer.readBoolEntry( "prepend-signature" ) );
+ if ( composer.hasKey( "smart-quote" ) )
+ mSmartQuoteCheck->setChecked( composer.readBoolEntry( "smart-quote" ) );
+ 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( "autosave" ) )
+ mAutoSave->setValue( composer.readNumEntry( "autosave" ) );
+
+ if ( general.hasKey( "use-external-editor" )
+ && general.hasKey( "external-editor" ) ) {
+ mExternalEditorCheck->setChecked( general.readBoolEntry( "use-external-editor" ) );
+ mEditorRequester->setURL( general.readPathEntry( "external-editor" ) );
+ }
+}
+
+void ComposerPage::GeneralTab::save() {
+ GlobalSettings::self()->setAutoTextSignature(
+ mAutoAppSignFileCheck->isChecked() ? "auto" : "manual" );
+ GlobalSettings::self()->setPrependSignature( mTopQuoteCheck->isChecked());
+ GlobalSettings::self()->setSmartQuote( mSmartQuoteCheck->isChecked() );
+ GlobalSettings::self()->setRequestMDN( mAutoRequestMDNCheck->isChecked() );
+ GlobalSettings::self()->setWordWrap( mWordWrapCheck->isChecked() );
+
+ GlobalSettings::self()->setLineWrapWidth( mWrapColumnSpin->value() );
+ GlobalSettings::self()->setAutosaveInterval( mAutoSave->value() );
+
+ // editor group:
+ GlobalSettings::self()->setUseExternalEditor( mExternalEditorCheck->isChecked() );
+ GlobalSettings::self()->setExternalEditor( mEditorRequester->url() );
+}
+
+void ComposerPage::GeneralTab::slotConfigureRecentAddresses( )
+{
+ KRecentAddress::RecentAddressDialog dlg( this );
+ dlg.setAddresses( RecentAddresses::self( KMKernel::config() )->addresses() );
+ if ( dlg.exec() ) {
+ RecentAddresses::self( KMKernel::config() )->clear();
+ const QStringList &addrList = dlg.addresses();
+ QStringList::ConstIterator it;
+ for ( it = addrList.constBegin(); it != addrList.constEnd(); ++it )
+ RecentAddresses::self( KMKernel::config() )->add( *it );
+ }
+}
+
+void ComposerPage::GeneralTab::slotConfigureCompletionOrder( )
+{
+ KPIM::LdapSearch search;
+ KPIM::CompletionOrderEditor editor( &search, this );
+ editor.exec();
+}
+
+QString ComposerPage::PhrasesTab::helpAnchor() const {
+ return QString::fromLatin1("configure-composer-phrases");
+}
+
+ComposerPagePhrasesTab::ComposerPagePhrasesTab( QWidget * parent, const char * name )
+ : ConfigModuleTab( parent, name )
+{
+ // tmp. vars:
+ QGridLayout *glay;
+ QPushButton *button;
+
+ glay = new QGridLayout( this, 7, 3, KDialog::spacingHint() );
+ glay->setMargin( KDialog::marginHint() );
+ glay->setColStretch( 1, 1 );
+ glay->setColStretch( 2, 1 );
+ glay->setRowStretch( 7, 1 );
+
+ // row 0: help text
+ glay->addMultiCellWidget( new QLabel( i18n("<qt>The following placeholders are "
+ "supported in the reply phrases:<br>"
+ "<b>%D</b>: date, <b>%S</b>: subject,<br>"
+ "<b>%e</b>: sender's address, <b>%F</b>: sender's name, <b>%f</b>: sender's initials,<br>"
+ "<b>%T</b>: recipient's name, <b>%t</b>: recipient's name and address,<br>"
+ "<b>%C</b>: carbon copy names, <b>%c</b>: carbon copy names and addresses,<br>"
+ "<b>%%</b>: percent sign, <b>%_</b>: space, "
+ "<b>%L</b>: linebreak</qt>"), this ),
+ 0, 0, 0, 2 ); // row 0; cols 0..2
+
+ // row 1: label and language combo box:
+ mPhraseLanguageCombo = new LanguageComboBox( false, this );
+ glay->addWidget( new QLabel( mPhraseLanguageCombo,
+ i18n("Lang&uage:"), this ), 1, 0 );
+ glay->addMultiCellWidget( mPhraseLanguageCombo, 1, 1, 1, 2 );
+ connect( mPhraseLanguageCombo, SIGNAL(activated(const QString&)),
+ this, SLOT(slotLanguageChanged(const QString&)) );
+
+ // row 2: "add..." and "remove" push buttons:
+ button = new QPushButton( i18n("A&dd..."), this );
+ button->setAutoDefault( false );
+ glay->addWidget( button, 2, 1 );
+ mRemoveButton = new QPushButton( i18n("Re&move"), this );
+ mRemoveButton->setAutoDefault( false );
+ mRemoveButton->setEnabled( false ); // combo doesn't contain anything...
+ glay->addWidget( mRemoveButton, 2, 2 );
+ connect( button, SIGNAL(clicked()),
+ this, SLOT(slotNewLanguage()) );
+ connect( mRemoveButton, SIGNAL(clicked()),
+ this, SLOT(slotRemoveLanguage()) );
+
+ // row 3: "reply to sender" line edit and label:
+ mPhraseReplyEdit = new KLineEdit( this );
+ connect( mPhraseReplyEdit, SIGNAL( textChanged( const QString& ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+ glay->addWidget( new QLabel( mPhraseReplyEdit,
+ i18n("Reply to se&nder:"), this ), 3, 0 );
+ glay->addMultiCellWidget( mPhraseReplyEdit, 3, 3, 1, 2 ); // cols 1..2
+
+ // row 4: "reply to all" line edit and label:
+ mPhraseReplyAllEdit = new KLineEdit( this );
+ connect( mPhraseReplyAllEdit, SIGNAL( textChanged( const QString& ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+ glay->addWidget( new QLabel( mPhraseReplyAllEdit,
+ i18n("Repl&y to all:"), this ), 4, 0 );
+ glay->addMultiCellWidget( mPhraseReplyAllEdit, 4, 4, 1, 2 ); // cols 1..2
+
+ // row 5: "forward" line edit and label:
+ mPhraseForwardEdit = new KLineEdit( this );
+ connect( mPhraseForwardEdit, SIGNAL( textChanged( const QString& ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+ glay->addWidget( new QLabel( mPhraseForwardEdit,
+ i18n("&Forward:"), this ), 5, 0 );
+ glay->addMultiCellWidget( mPhraseForwardEdit, 5, 5, 1, 2 ); // cols 1..2
+
+ // row 6: "quote indicator" line edit and label:
+ mPhraseIndentPrefixEdit = new KLineEdit( this );
+ connect( mPhraseIndentPrefixEdit, SIGNAL( textChanged( const QString& ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+ glay->addWidget( new QLabel( mPhraseIndentPrefixEdit,
+ i18n("&Quote indicator:"), this ), 6, 0 );
+ glay->addMultiCellWidget( mPhraseIndentPrefixEdit, 6, 6, 1, 2 );
+
+ // row 7: spacer
+}
+
+
+void ComposerPage::PhrasesTab::setLanguageItemInformation( int index ) {
+ assert( 0 <= index && index < (int)mLanguageList.count() );
+
+ LanguageItem &l = *mLanguageList.at( index );
+
+ mPhraseReplyEdit->setText( l.mReply );
+ mPhraseReplyAllEdit->setText( l.mReplyAll );
+ mPhraseForwardEdit->setText( l.mForward );
+ mPhraseIndentPrefixEdit->setText( l.mIndentPrefix );
+}
+
+void ComposerPage::PhrasesTab::saveActiveLanguageItem() {
+ int index = mActiveLanguageItem;
+ if (index == -1) return;
+ assert( 0 <= index && index < (int)mLanguageList.count() );
+
+ LanguageItem &l = *mLanguageList.at( index );
+
+ l.mReply = mPhraseReplyEdit->text();
+ l.mReplyAll = mPhraseReplyAllEdit->text();
+ l.mForward = mPhraseForwardEdit->text();
+ l.mIndentPrefix = mPhraseIndentPrefixEdit->text();
+}
+
+void ComposerPage::PhrasesTab::slotNewLanguage()
+{
+ NewLanguageDialog dialog( mLanguageList, parentWidget(), "New", true );
+ if ( dialog.exec() == QDialog::Accepted ) slotAddNewLanguage( dialog.language() );
+}
+
+void ComposerPage::PhrasesTab::slotAddNewLanguage( const QString& lang )
+{
+ mPhraseLanguageCombo->setCurrentItem(
+ mPhraseLanguageCombo->insertLanguage( lang ) );
+ KLocale locale("kmail");
+ locale.setLanguage( lang );
+ mLanguageList.append(
+ LanguageItem( lang,
+ locale.translate("On %D, you wrote:"),
+ locale.translate("On %D, %F wrote:"),
+ locale.translate("Forwarded Message"),
+ locale.translate(">%_") ) );
+ mRemoveButton->setEnabled( true );
+ slotLanguageChanged( QString::null );
+}
+
+void ComposerPage::PhrasesTab::slotRemoveLanguage()
+{
+ assert( mPhraseLanguageCombo->count() > 1 );
+ int index = mPhraseLanguageCombo->currentItem();
+ assert( 0 <= index && index < (int)mLanguageList.count() );
+
+ // remove current item from internal list and combobox:
+ mLanguageList.remove( mLanguageList.at( index ) );
+ mPhraseLanguageCombo->removeItem( index );
+
+ if ( index >= (int)mLanguageList.count() ) index--;
+
+ mActiveLanguageItem = index;
+ setLanguageItemInformation( index );
+ mRemoveButton->setEnabled( mLanguageList.count() > 1 );
+ emit changed( true );
+}
+
+void ComposerPage::PhrasesTab::slotLanguageChanged( const QString& )
+{
+ int index = mPhraseLanguageCombo->currentItem();
+ assert( index < (int)mLanguageList.count() );
+ saveActiveLanguageItem();
+ mActiveLanguageItem = index;
+ setLanguageItemInformation( index );
+ emit changed( true );
+}
+
+
+void ComposerPage::PhrasesTab::doLoadFromGlobalSettings() {
+ mLanguageList.clear();
+ mPhraseLanguageCombo->clear();
+ mActiveLanguageItem = -1;
+
+ int numLang = GlobalSettings::self()->replyLanguagesCount();
+ int currentNr = GlobalSettings::self()->replyCurrentLanguage();
+
+ // build mLanguageList and mPhraseLanguageCombo:
+ for ( int i = 0 ; i < numLang ; i++ ) {
+ ReplyPhrases replyPhrases( QString::number(i) );
+ replyPhrases.readConfig();
+ QString lang = replyPhrases.language();
+ mLanguageList.append(
+ LanguageItem( lang,
+ replyPhrases.phraseReplySender(),
+ replyPhrases.phraseReplyAll(),
+ replyPhrases.phraseForward(),
+ replyPhrases.indentPrefix() ) );
+ mPhraseLanguageCombo->insertLanguage( lang );
+ }
+
+ if ( currentNr >= numLang || currentNr < 0 )
+ currentNr = 0;
+
+ if ( numLang == 0 ) {
+ slotAddNewLanguage( KGlobal::locale()->language() );
+ }
+
+ mPhraseLanguageCombo->setCurrentItem( currentNr );
+ mActiveLanguageItem = currentNr;
+ setLanguageItemInformation( currentNr );
+ mRemoveButton->setEnabled( mLanguageList.count() > 1 );
+}
+
+void ComposerPage::PhrasesTab::save() {
+ GlobalSettings::self()->setReplyLanguagesCount( mLanguageList.count() );
+ GlobalSettings::self()->setReplyCurrentLanguage( mPhraseLanguageCombo->currentItem() );
+
+ saveActiveLanguageItem();
+ LanguageItemList::Iterator it = mLanguageList.begin();
+ for ( int i = 0 ; it != mLanguageList.end() ; ++it, ++i ) {
+ ReplyPhrases replyPhrases( QString::number(i) );
+ replyPhrases.setLanguage( (*it).mLanguage );
+ replyPhrases.setPhraseReplySender( (*it).mReply );
+ replyPhrases.setPhraseReplyAll( (*it).mReplyAll );
+ replyPhrases.setPhraseForward( (*it).mForward );
+ replyPhrases.setIndentPrefix( (*it).mIndentPrefix );
+ replyPhrases.writeConfig();
+ }
+}
+
+QString ComposerPage::TemplatesTab::helpAnchor() const {
+ return QString::fromLatin1("configure-composer-templates");
+}
+
+ComposerPageTemplatesTab::ComposerPageTemplatesTab( QWidget * parent, const char * name )
+ : ConfigModuleTab ( parent, name )
+{
+ QVBoxLayout* vlay = new QVBoxLayout( this, 0, KDialog::spacingHint() );
+
+ mWidget = new TemplatesConfiguration( this );
+ vlay->addWidget( mWidget );
+
+ connect( mWidget, SIGNAL( changed() ),
+ this, SLOT( slotEmitChanged( void ) ) );
+}
+
+void ComposerPage::TemplatesTab::doLoadFromGlobalSettings() {
+ mWidget->loadFromGlobal();
+}
+
+void ComposerPage::TemplatesTab::save() {
+ mWidget->saveToGlobal();
+}
+
+QString ComposerPage::CustomTemplatesTab::helpAnchor() const {
+ return QString::fromLatin1("configure-composer-custom-templates");
+}
+
+ComposerPageCustomTemplatesTab::ComposerPageCustomTemplatesTab( QWidget * parent, const char * name )
+ : ConfigModuleTab ( parent, name )
+{
+ QVBoxLayout* vlay = new QVBoxLayout( this, 0, KDialog::spacingHint() );
+
+ mWidget = new CustomTemplates( this );
+ vlay->addWidget( mWidget );
+
+ connect( mWidget, SIGNAL( changed() ),
+ this, SLOT( slotEmitChanged( void ) ) );
+}
+
+void ComposerPage::CustomTemplatesTab::doLoadFromGlobalSettings() {
+ mWidget->load();
+}
+
+void ComposerPage::CustomTemplatesTab::save() {
+ mWidget->save();
+}
+
+QString ComposerPage::SubjectTab::helpAnchor() const {
+ return QString::fromLatin1("configure-composer-subject");
+}
+
+ComposerPageSubjectTab::ComposerPageSubjectTab( QWidget * parent, const char * name )
+ : ConfigModuleTab( parent, name )
+{
+ // tmp. vars:
+ QVBoxLayout *vlay;
+ QGroupBox *group;
+ QLabel *label;
+
+
+ vlay = new QVBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
+
+ group = new QVGroupBox( i18n("Repl&y Subject Prefixes"), this );
+ group->layout()->setSpacing( KDialog::spacingHint() );
+
+ // row 0: help text:
+ label = new QLabel( i18n("Recognize any sequence of the following prefixes\n"
+ "(entries are case-insensitive regular expressions):"), group );
+ label->setAlignment( AlignLeft|WordBreak );
+
+ // row 1, string list editor:
+ SimpleStringListEditor::ButtonCode buttonCode =
+ static_cast<SimpleStringListEditor::ButtonCode>( SimpleStringListEditor::Add | SimpleStringListEditor::Remove | SimpleStringListEditor::Modify );
+ mReplyListEditor =
+ new SimpleStringListEditor( group, 0, buttonCode,
+ i18n("A&dd..."), i18n("Re&move"),
+ i18n("Mod&ify..."),
+ i18n("Enter new reply prefix:") );
+ connect( mReplyListEditor, SIGNAL( changed( void ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ // row 2: "replace [...]" check box:
+ mReplaceReplyPrefixCheck = new QCheckBox(
+ GlobalSettings::self()->replaceReplyPrefixItem()->label(),
+ group, "kcfg_ReplaceReplyPrefix" );
+ connect( mReplaceReplyPrefixCheck, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ vlay->addWidget( group );
+
+
+ group = new QVGroupBox( i18n("For&ward Subject Prefixes"), this );
+ group->layout()->setSpacing( KDialog::marginHint() );
+
+ // row 0: help text:
+ label= new QLabel( i18n("Recognize any sequence of the following prefixes\n"
+ "(entries are case-insensitive regular expressions):"), group );
+ label->setAlignment( AlignLeft|WordBreak );
+
+ // row 1: string list editor
+ mForwardListEditor =
+ new SimpleStringListEditor( group, 0, buttonCode,
+ i18n("Add..."),
+ i18n("Remo&ve"),
+ i18n("Modify..."),
+ i18n("Enter new forward prefix:") );
+ connect( mForwardListEditor, SIGNAL( changed( void ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ // row 3: "replace [...]" check box:
+ mReplaceForwardPrefixCheck = new QCheckBox(
+ GlobalSettings::self()->replaceForwardPrefixItem()->label(),
+ group, "kcfg_ReplaceForwardPrefix" );
+ connect( mReplaceForwardPrefixCheck, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ vlay->addWidget( group );
+}
+
+void ComposerPage::SubjectTab::doLoadFromGlobalSettings() {
+ mReplyListEditor->setStringList( GlobalSettings::self()->replyPrefixes() );
+ mReplaceReplyPrefixCheck->setChecked( GlobalSettings::self()->replaceReplyPrefix() );
+ mForwardListEditor->setStringList( GlobalSettings::self()->forwardPrefixes() );
+ mReplaceForwardPrefixCheck->setChecked( GlobalSettings::self()->replaceForwardPrefix() );
+}
+
+void ComposerPage::SubjectTab::save() {
+ GlobalSettings::self()->setReplyPrefixes( mReplyListEditor->stringList() );
+ GlobalSettings::self()->setForwardPrefixes( mForwardListEditor->stringList() );
+}
+
+QString ComposerPage::CharsetTab::helpAnchor() const {
+ return QString::fromLatin1("configure-composer-charset");
+}
+
+ComposerPageCharsetTab::ComposerPageCharsetTab( QWidget * parent, const char * name )
+ : ConfigModuleTab( parent, name )
+{
+ // tmp. vars:
+ QVBoxLayout *vlay;
+ QLabel *label;
+
+ vlay = new QVBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
+
+ label = new QLabel( i18n("This list is checked for every outgoing message "
+ "from the top to the bottom for a charset that "
+ "contains all required characters."), this );
+ label->setAlignment( WordBreak);
+ vlay->addWidget( label );
+
+ mCharsetListEditor =
+ new SimpleStringListEditor( this, 0, SimpleStringListEditor::All,
+ i18n("A&dd..."), i18n("Remo&ve"),
+ i18n("&Modify..."), i18n("Enter charset:") );
+ connect( mCharsetListEditor, SIGNAL( changed( void ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ vlay->addWidget( mCharsetListEditor, 1 );
+
+ mKeepReplyCharsetCheck = new QCheckBox( i18n("&Keep original charset when "
+ "replying or forwarding (if "
+ "possible)"), this );
+ connect( mKeepReplyCharsetCheck, SIGNAL ( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+ vlay->addWidget( mKeepReplyCharsetCheck );
+
+ connect( mCharsetListEditor, SIGNAL(aboutToAdd(QString&)),
+ this, SLOT(slotVerifyCharset(QString&)) );
+}
+
+void ComposerPage::CharsetTab::slotVerifyCharset( QString & charset ) {
+ if ( charset.isEmpty() ) return;
+
+ // KCharsets::codecForName("us-ascii") returns "iso-8859-1" (cf. Bug #49812)
+ // therefore we have to treat this case specially
+ if ( charset.lower() == QString::fromLatin1("us-ascii") ) {
+ charset = QString::fromLatin1("us-ascii");
+ return;
+ }
+
+ if ( charset.lower() == QString::fromLatin1("locale") ) {
+ charset = QString::fromLatin1("%1 (locale)")
+ .arg( QCString( kmkernel->networkCodec()->mimeName() ).lower() );
+ return;
+ }
+
+ bool ok = false;
+ QTextCodec *codec = KGlobal::charsets()->codecForName( charset, ok );
+ if ( ok && codec ) {
+ charset = QString::fromLatin1( codec->mimeName() ).lower();
+ return;
+ }
+
+ KMessageBox::sorry( this, i18n("This charset is not supported.") );
+ charset = QString::null;
+}
+
+void ComposerPage::CharsetTab::doLoadOther() {
+ KConfigGroup composer( KMKernel::config(), "Composer" );
+
+ QStringList charsets = composer.readListEntry( "pref-charsets" );
+ for ( QStringList::Iterator it = charsets.begin() ;
+ it != charsets.end() ; ++it )
+ if ( (*it) == QString::fromLatin1("locale") ) {
+ QCString cset = kmkernel->networkCodec()->mimeName();
+ KPIM::kAsciiToLower( cset.data() );
+ (*it) = QString("%1 (locale)").arg( cset );
+ }
+
+ mCharsetListEditor->setStringList( charsets );
+ mKeepReplyCharsetCheck->setChecked( !composer.readBoolEntry( "force-reply-charset", false ) );
+}
+
+void ComposerPage::CharsetTab::save() {
+ KConfigGroup composer( KMKernel::config(), "Composer" );
+
+ QStringList charsetList = mCharsetListEditor->stringList();
+ QStringList::Iterator it = charsetList.begin();
+ for ( ; it != charsetList.end() ; ++it )
+ if ( (*it).endsWith("(locale)") )
+ (*it) = "locale";
+ composer.writeEntry( "pref-charsets", charsetList );
+ composer.writeEntry( "force-reply-charset",
+ !mKeepReplyCharsetCheck->isChecked() );
+}
+
+QString ComposerPage::HeadersTab::helpAnchor() const {
+ return QString::fromLatin1("configure-composer-headers");
+}
+
+ComposerPageHeadersTab::ComposerPageHeadersTab( QWidget * parent, const char * name )
+ : ConfigModuleTab( parent, name )
+{
+ // tmp. vars:
+ QVBoxLayout *vlay;
+ QHBoxLayout *hlay;
+ QGridLayout *glay;
+ QLabel *label;
+ QPushButton *button;
+
+ vlay = new QVBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
+
+ // "Use custom Message-Id suffix" checkbox:
+ mCreateOwnMessageIdCheck =
+ new QCheckBox( i18n("&Use custom message-id suffix"), this );
+ connect( mCreateOwnMessageIdCheck, SIGNAL ( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+ vlay->addWidget( mCreateOwnMessageIdCheck );
+
+ // "Message-Id suffix" line edit and label:
+ hlay = new QHBoxLayout( vlay ); // inherits spacing
+ mMessageIdSuffixEdit = new KLineEdit( this );
+ // only ASCII letters, digits, plus, minus and dots are allowed
+ mMessageIdSuffixValidator =
+ new QRegExpValidator( QRegExp( "[a-zA-Z0-9+-]+(?:\\.[a-zA-Z0-9+-]+)*" ), this );
+ mMessageIdSuffixEdit->setValidator( mMessageIdSuffixValidator );
+ label = new QLabel( mMessageIdSuffixEdit,
+ i18n("Custom message-&id suffix:"), this );
+ label->setEnabled( false ); // since !mCreateOwnMessageIdCheck->isChecked()
+ mMessageIdSuffixEdit->setEnabled( false );
+ hlay->addWidget( label );
+ hlay->addWidget( mMessageIdSuffixEdit, 1 );
+ connect( mCreateOwnMessageIdCheck, SIGNAL(toggled(bool) ),
+ label, SLOT(setEnabled(bool)) );
+ connect( mCreateOwnMessageIdCheck, SIGNAL(toggled(bool) ),
+ mMessageIdSuffixEdit, SLOT(setEnabled(bool)) );
+ connect( mMessageIdSuffixEdit, SIGNAL( textChanged( const QString& ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ // horizontal rule and "custom header fields" label:
+ vlay->addWidget( new KSeparator( KSeparator::HLine, this ) );
+ vlay->addWidget( new QLabel( i18n("Define custom mime header fields:"), this) );
+
+ // "custom header fields" listbox:
+ glay = new QGridLayout( vlay, 5, 3 ); // inherits spacing
+ glay->setRowStretch( 2, 1 );
+ glay->setColStretch( 1, 1 );
+ mTagList = new ListView( this, "tagList" );
+ mTagList->addColumn( i18n("Name") );
+ mTagList->addColumn( i18n("Value") );
+ mTagList->setAllColumnsShowFocus( true );
+ mTagList->setSorting( -1 );
+ connect( mTagList, SIGNAL(selectionChanged()),
+ this, SLOT(slotMimeHeaderSelectionChanged()) );
+ glay->addMultiCellWidget( mTagList, 0, 2, 0, 1 );
+
+ // "new" and "remove" buttons:
+ button = new QPushButton( i18n("Ne&w"), this );
+ connect( button, SIGNAL(clicked()), this, SLOT(slotNewMimeHeader()) );
+ button->setAutoDefault( false );
+ glay->addWidget( button, 0, 2 );
+ mRemoveHeaderButton = new QPushButton( i18n("Re&move"), this );
+ connect( mRemoveHeaderButton, SIGNAL(clicked()),
+ this, SLOT(slotRemoveMimeHeader()) );
+ button->setAutoDefault( false );
+ glay->addWidget( mRemoveHeaderButton, 1, 2 );
+
+ // "name" and "value" line edits and labels:
+ mTagNameEdit = new KLineEdit( this );
+ mTagNameEdit->setEnabled( false );
+ mTagNameLabel = new QLabel( mTagNameEdit, i18n("&Name:"), this );
+ mTagNameLabel->setEnabled( false );
+ glay->addWidget( mTagNameLabel, 3, 0 );
+ glay->addWidget( mTagNameEdit, 3, 1 );
+ connect( mTagNameEdit, SIGNAL(textChanged(const QString&)),
+ this, SLOT(slotMimeHeaderNameChanged(const QString&)) );
+
+ mTagValueEdit = new KLineEdit( this );
+ mTagValueEdit->setEnabled( false );
+ mTagValueLabel = new QLabel( mTagValueEdit, i18n("&Value:"), this );
+ mTagValueLabel->setEnabled( false );
+ glay->addWidget( mTagValueLabel, 4, 0 );
+ glay->addWidget( mTagValueEdit, 4, 1 );
+ connect( mTagValueEdit, SIGNAL(textChanged(const QString&)),
+ this, SLOT(slotMimeHeaderValueChanged(const QString&)) );
+}
+
+void ComposerPage::HeadersTab::slotMimeHeaderSelectionChanged()
+{
+ QListViewItem * item = mTagList->selectedItem();
+
+ if ( item ) {
+ mTagNameEdit->setText( item->text( 0 ) );
+ mTagValueEdit->setText( item->text( 1 ) );
+ } else {
+ mTagNameEdit->clear();
+ mTagValueEdit->clear();
+ }
+ mRemoveHeaderButton->setEnabled( item );
+ mTagNameEdit->setEnabled( item );
+ mTagValueEdit->setEnabled( item );
+ mTagNameLabel->setEnabled( item );
+ mTagValueLabel->setEnabled( item );
+}
+
+
+void ComposerPage::HeadersTab::slotMimeHeaderNameChanged( const QString & text ) {
+ // is called on ::setup(), when clearing the line edits. So be
+ // prepared to not find a selection:
+ QListViewItem * item = mTagList->selectedItem();
+ if ( item )
+ item->setText( 0, text );
+ emit changed( true );
+}
+
+
+void ComposerPage::HeadersTab::slotMimeHeaderValueChanged( const QString & text ) {
+ // is called on ::setup(), when clearing the line edits. So be
+ // prepared to not find a selection:
+ QListViewItem * item = mTagList->selectedItem();
+ if ( item )
+ item->setText( 1, text );
+ emit changed( true );
+}
+
+
+void ComposerPage::HeadersTab::slotNewMimeHeader()
+{
+ QListViewItem *listItem = new QListViewItem( mTagList );
+ mTagList->setCurrentItem( listItem );
+ mTagList->setSelected( listItem, true );
+ emit changed( true );
+}
+
+
+void ComposerPage::HeadersTab::slotRemoveMimeHeader()
+{
+ // calling this w/o selection is a programming error:
+ QListViewItem * item = mTagList->selectedItem();
+ if ( !item ) {
+ kdDebug(5006) << "==================================================\n"
+ << "Error: Remove button was pressed although no custom header was selected\n"
+ << "==================================================\n";
+ return;
+ }
+
+ QListViewItem * below = item->nextSibling();
+ delete item;
+
+ if ( below )
+ mTagList->setSelected( below, true );
+ else if ( mTagList->lastItem() )
+ mTagList->setSelected( mTagList->lastItem(), true );
+ emit changed( true );
+}
+
+void ComposerPage::HeadersTab::doLoadOther() {
+ KConfigGroup general( KMKernel::config(), "General" );
+
+ QString suffix = general.readEntry( "myMessageIdSuffix" );
+ mMessageIdSuffixEdit->setText( suffix );
+ bool state = ( !suffix.isEmpty() &&
+ general.readBoolEntry( "useCustomMessageIdSuffix", false ) );
+ mCreateOwnMessageIdCheck->setChecked( state );
+
+ mTagList->clear();
+ mTagNameEdit->clear();
+ mTagValueEdit->clear();
+
+ QListViewItem * item = 0;
+
+ int count = general.readNumEntry( "mime-header-count", 0 );
+ for( int i = 0 ; i < count ; i++ ) {
+ KConfigGroup config( KMKernel::config(),
+ QCString("Mime #") + QCString().setNum(i) );
+ QString name = config.readEntry( "name" );
+ QString value = config.readEntry( "value" );
+ if( !name.isEmpty() )
+ item = new QListViewItem( mTagList, item, name, value );
+ }
+ if ( mTagList->childCount() ) {
+ mTagList->setCurrentItem( mTagList->firstChild() );
+ mTagList->setSelected( mTagList->firstChild(), true );
+ }
+ else {
+ // disable the "Remove" button
+ mRemoveHeaderButton->setEnabled( false );
+ }
+}
+
+void ComposerPage::HeadersTab::save() {
+ KConfigGroup general( KMKernel::config(), "General" );
+
+ general.writeEntry( "useCustomMessageIdSuffix",
+ mCreateOwnMessageIdCheck->isChecked() );
+ general.writeEntry( "myMessageIdSuffix",
+ mMessageIdSuffixEdit->text() );
+
+ int numValidEntries = 0;
+ QListViewItem * item = mTagList->firstChild();
+ for ( ; item ; item = item->itemBelow() )
+ if( !item->text(0).isEmpty() ) {
+ KConfigGroup config( KMKernel::config(), QCString("Mime #")
+ + QCString().setNum( numValidEntries ) );
+ config.writeEntry( "name", item->text( 0 ) );
+ config.writeEntry( "value", item->text( 1 ) );
+ numValidEntries++;
+ }
+ general.writeEntry( "mime-header-count", numValidEntries );
+}
+
+QString ComposerPage::AttachmentsTab::helpAnchor() const {
+ return QString::fromLatin1("configure-composer-attachments");
+}
+
+ComposerPageAttachmentsTab::ComposerPageAttachmentsTab( QWidget * parent,
+ const char * name )
+ : ConfigModuleTab( parent, name ) {
+ // tmp. vars:
+ QVBoxLayout *vlay;
+ QLabel *label;
+
+ vlay = new QVBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
+
+ // "Outlook compatible attachment naming" check box
+ mOutlookCompatibleCheck =
+ new QCheckBox( i18n( "Outlook-compatible attachment naming" ), this );
+ mOutlookCompatibleCheck->setChecked( false );
+ QToolTip::add( mOutlookCompatibleCheck, i18n(
+ "Turn this option on to make Outlook(tm) understand attachment names "
+ "containing non-English characters" ) );
+ connect( mOutlookCompatibleCheck, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+ connect( mOutlookCompatibleCheck, SIGNAL( clicked() ),
+ this, SLOT( slotOutlookCompatibleClicked() ) );
+ vlay->addWidget( mOutlookCompatibleCheck );
+ vlay->addSpacing( 5 );
+
+ // "Enable detection of missing attachments" check box
+ mMissingAttachmentDetectionCheck =
+ new QCheckBox( i18n("E&nable detection of missing attachments"), this );
+ mMissingAttachmentDetectionCheck->setChecked( true );
+ connect( mMissingAttachmentDetectionCheck, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+ vlay->addWidget( mMissingAttachmentDetectionCheck );
+
+ // "Attachment key words" label and string list editor
+ label = new QLabel( i18n("Recognize any of the following key words as "
+ "intention to attach a file:"), this );
+ label->setAlignment( AlignLeft|WordBreak );
+ vlay->addWidget( label );
+
+ SimpleStringListEditor::ButtonCode buttonCode =
+ static_cast<SimpleStringListEditor::ButtonCode>( SimpleStringListEditor::Add | SimpleStringListEditor::Remove | SimpleStringListEditor::Modify );
+ mAttachWordsListEditor =
+ new SimpleStringListEditor( this, 0, buttonCode,
+ i18n("A&dd..."), i18n("Re&move"),
+ i18n("Mod&ify..."),
+ i18n("Enter new key word:") );
+ connect( mAttachWordsListEditor, SIGNAL( changed( void ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+ vlay->addWidget( mAttachWordsListEditor );
+
+ connect( mMissingAttachmentDetectionCheck, SIGNAL(toggled(bool) ),
+ label, SLOT(setEnabled(bool)) );
+ connect( mMissingAttachmentDetectionCheck, SIGNAL(toggled(bool) ),
+ mAttachWordsListEditor, SLOT(setEnabled(bool)) );
+}
+
+void ComposerPage::AttachmentsTab::doLoadFromGlobalSettings() {
+ mOutlookCompatibleCheck->setChecked(
+ GlobalSettings::self()->outlookCompatibleAttachments() );
+ mMissingAttachmentDetectionCheck->setChecked(
+ GlobalSettings::self()->showForgottenAttachmentWarning() );
+ QStringList attachWordsList = GlobalSettings::self()->attachmentKeywords();
+ if ( attachWordsList.isEmpty() ) {
+ // default value
+ attachWordsList << QString::fromLatin1("attachment")
+ << QString::fromLatin1("attached");
+ if ( QString::fromLatin1("attachment") != i18n("attachment") )
+ attachWordsList << i18n("attachment");
+ if ( QString::fromLatin1("attached") != i18n("attached") )
+ attachWordsList << i18n("attached");
+ }
+
+ mAttachWordsListEditor->setStringList( attachWordsList );
+}
+
+void ComposerPage::AttachmentsTab::save() {
+ GlobalSettings::self()->setOutlookCompatibleAttachments(
+ mOutlookCompatibleCheck->isChecked() );
+ GlobalSettings::self()->setShowForgottenAttachmentWarning(
+ mMissingAttachmentDetectionCheck->isChecked() );
+ GlobalSettings::self()->setAttachmentKeywords(
+ mAttachWordsListEditor->stringList() );
+}
+
+void ComposerPageAttachmentsTab::slotOutlookCompatibleClicked()
+{
+ if (mOutlookCompatibleCheck->isChecked()) {
+ KMessageBox::information(0,i18n("You have chosen to "
+ "encode attachment names containing non-English characters in a way that "
+ "is understood by Outlook(tm) and other mail clients that do not "
+ "support standard-compliant encoded attachment names.\n"
+ "Note that KMail may create non-standard compliant messages, "
+ "and consequently it is possible that your messages will not be "
+ "understood by standard-compliant mail clients; so, unless you have no "
+ "other choice, you should not enable this option." ) );
+ }
+}
+
+// *************************************************************
+// * *
+// * SecurityPage *
+// * *
+// *************************************************************
+QString SecurityPage::helpAnchor() const {
+ return QString::fromLatin1("configure-security");
+}
+
+SecurityPage::SecurityPage( QWidget * parent, const char * name )
+ : ConfigModuleWithTabs( parent, name )
+{
+ //
+ // "Reading" tab:
+ //
+ mGeneralTab = new GeneralTab(); // @TODO: rename
+ addTab( mGeneralTab, i18n("&Reading") );
+
+ //
+ // "Composing" tab:
+ //
+ mComposerCryptoTab = new ComposerCryptoTab();
+ addTab( mComposerCryptoTab, i18n("Composing") );
+
+ //
+ // "Warnings" tab:
+ //
+ mWarningTab = new WarningTab();
+ addTab( mWarningTab, i18n("Warnings") );
+
+ //
+ // "S/MIME Validation" tab:
+ //
+ mSMimeTab = new SMimeTab();
+ addTab( mSMimeTab, i18n("S/MIME &Validation") );
+
+ //
+ // "Crypto Backends" tab:
+ //
+ mCryptPlugTab = new CryptPlugTab();
+ addTab( mCryptPlugTab, i18n("Crypto Backe&nds") );
+ load();
+}
+
+
+void SecurityPage::installProfile( KConfig * profile ) {
+ mGeneralTab->installProfile( profile );
+ mComposerCryptoTab->installProfile( profile );
+ mWarningTab->installProfile( profile );
+ mSMimeTab->installProfile( profile );
+}
+
+QString SecurityPage::GeneralTab::helpAnchor() const {
+ return QString::fromLatin1("configure-security-reading");
+}
+
+SecurityPageGeneralTab::SecurityPageGeneralTab( QWidget * parent, const char * name )
+ : ConfigModuleTab ( parent, name )
+{
+ // tmp. vars:
+ QVBoxLayout *vlay;
+ QHBox *hbox;
+ QGroupBox *group;
+ QRadioButton *radio;
+ KActiveLabel *label;
+ QWidget *w;
+ QString msg;
+
+ vlay = new QVBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
+
+ // QWhat'sThis texts
+ QString htmlWhatsThis = i18n( "<qt><p>Messages sometimes come in both formats. "
+ "This option controls whether you want the HTML part or the plain "
+ "text part to be displayed.</p>"
+ "<p>Displaying the HTML part makes the message look better, "
+ "but at the same time increases the risk of security holes "
+ "being exploited.</p>"
+ "<p>Displaying the plain text part loses much of the message's "
+ "formatting, but makes it almost <em>impossible</em> "
+ "to exploit security holes in the HTML renderer (Konqueror).</p>"
+ "<p>The option below guards against one common misuse of HTML "
+ "messages, but it cannot guard against security issues that were "
+ "not known at the time this version of KMail was written.</p>"
+ "<p>It is therefore advisable to <em>not</em> prefer HTML to "
+ "plain text.</p>"
+ "<p><b>Note:</b> You can set this option on a per-folder basis "
+ "from the <i>Folder</i> menu of KMail's main window.</p></qt>" );
+
+ QString externalWhatsThis = i18n( "<qt><p>Some mail advertisements are in HTML "
+ "and contain references to, for example, images that the advertisers"
+ " employ to find out that you have read their message "
+ "(&quot;web bugs&quot;).</p>"
+ "<p>There is no valid reason to load images off the Internet like "
+ "this, since the sender can always attach the required images "
+ "directly to the message.</p>"
+ "<p>To guard from such a misuse of the HTML displaying feature "
+ "of KMail, this option is <em>disabled</em> by default.</p>"
+ "<p>However, if you wish to, for example, view images in HTML "
+ "messages that were not attached to it, you can enable this "
+ "option, but you should be aware of the possible problem.</p></qt>" );
+
+ QString receiptWhatsThis = i18n( "<qt><h3>Message Disposition "
+ "Notification Policy</h3>"
+ "<p>MDNs are a generalization of what is commonly called <b>read "
+ "receipt</b>. The message author requests a disposition "
+ "notification to be sent and the receiver's mail program "
+ "generates a reply from which the author can learn what "
+ "happened to his message. Common disposition types include "
+ "<b>displayed</b> (i.e. read), <b>deleted</b> and <b>dispatched</b> "
+ "(e.g. forwarded).</p>"
+ "<p>The following options are available to control KMail's "
+ "sending of MDNs:</p>"
+ "<ul>"
+ "<li><em>Ignore</em>: Ignores any request for disposition "
+ "notifications. No MDN will ever be sent automatically "
+ "(recommended).</li>"
+ "<li><em>Ask</em>: Answers requests only after asking the user "
+ "for permission. This way, you can send MDNs for selected "
+ "messages while denying or ignoring them for others.</li>"
+ "<li><em>Deny</em>: Always sends a <b>denied</b> notification. This "
+ "is only <em>slightly</em> better than always sending MDNs. "
+ "The author will still know that the messages has been acted "
+ "upon, he just cannot tell whether it was deleted or read etc.</li>"
+ "<li><em>Always send</em>: Always sends the requested "
+ "disposition notification. That means that the author of the "
+ "message gets to know when the message was acted upon and, "
+ "in addition, what happened to it (displayed, deleted, "
+ "etc.). This option is strongly discouraged, but since it "
+ "makes much sense e.g. for customer relationship management, "
+ "it has been made available.</li>"
+ "</ul></qt>" );
+
+
+ // "HTML Messages" group box:
+ group = new QVGroupBox( i18n( "HTML Messages" ), this );
+ group->layout()->setSpacing( KDialog::spacingHint() );
+
+ mHtmlMailCheck = new QCheckBox( i18n("Prefer H&TML to plain text"), group );
+ QWhatsThis::add( mHtmlMailCheck, htmlWhatsThis );
+ connect( mHtmlMailCheck, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+ mExternalReferences = new QCheckBox( i18n("Allow messages to load e&xternal "
+ "references from the Internet" ), group );
+ QWhatsThis::add( mExternalReferences, externalWhatsThis );
+ connect( mExternalReferences, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+ label = new KActiveLabel( i18n("<b>WARNING:</b> Allowing HTML in email may "
+ "increase the risk that your system will be "
+ "compromised by present and anticipated security "
+ "exploits. <a href=\"whatsthis:%1\">More about "
+ "HTML mails...</a> <a href=\"whatsthis:%2\">More "
+ "about external references...</a>")
+ .arg(htmlWhatsThis).arg(externalWhatsThis),
+ group );
+
+ vlay->addWidget( group );
+
+ // encrypted messages group
+ group = new QVGroupBox( i18n("Encrypted Messages"), this );
+ group->layout()->setSpacing( KDialog::spacingHint() );
+ mAlwaysDecrypt = new QCheckBox( i18n( "Attempt decryption of encrypted messages when viewing" ), group );
+ connect( mAlwaysDecrypt, SIGNAL(stateChanged(int)), this, SLOT(slotEmitChanged()) );
+ vlay->addWidget( group );
+
+ // "Message Disposition Notification" groupbox:
+ group = new QVGroupBox( i18n("Message Disposition Notifications"), this );
+ group->layout()->setSpacing( KDialog::spacingHint() );
+
+
+ // "ignore", "ask", "deny", "always send" radiobutton line:
+ mMDNGroup = new QButtonGroup( group );
+ mMDNGroup->hide();
+ connect( mMDNGroup, SIGNAL( clicked( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+ hbox = new QHBox( group );
+ hbox->setSpacing( KDialog::spacingHint() );
+
+ (void)new QLabel( i18n("Send policy:"), hbox );
+
+ radio = new QRadioButton( i18n("&Ignore"), hbox );
+ mMDNGroup->insert( radio );
+
+ radio = new QRadioButton( i18n("As&k"), hbox );
+ mMDNGroup->insert( radio );
+
+ radio = new QRadioButton( i18n("&Deny"), hbox );
+ mMDNGroup->insert( radio );
+
+ radio = new QRadioButton( i18n("Al&ways send"), hbox );
+ mMDNGroup->insert( radio );
+
+ for ( int i = 0 ; i < mMDNGroup->count() ; ++i )
+ QWhatsThis::add( mMDNGroup->find( i ), receiptWhatsThis );
+
+ w = new QWidget( hbox ); // spacer
+ hbox->setStretchFactor( w, 1 );
+
+ // "Original Message quote" radiobutton line:
+ mOrigQuoteGroup = new QButtonGroup( group );
+ mOrigQuoteGroup->hide();
+ connect( mOrigQuoteGroup, SIGNAL( clicked( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ hbox = new QHBox( group );
+ hbox->setSpacing( KDialog::spacingHint() );
+
+ (void)new QLabel( i18n("Quote original message:"), hbox );
+
+ radio = new QRadioButton( i18n("Nothin&g"), hbox );
+ mOrigQuoteGroup->insert( radio );
+
+ radio = new QRadioButton( i18n("&Full message"), hbox );
+ mOrigQuoteGroup->insert( radio );
+
+ radio = new QRadioButton( i18n("Onl&y headers"), hbox );
+ mOrigQuoteGroup->insert( radio );
+
+ w = new QWidget( hbox );
+ hbox->setStretchFactor( w, 1 );
+
+ mNoMDNsWhenEncryptedCheck = new QCheckBox( i18n("Do not send MDNs in response to encrypted messages"), group );
+ connect( mNoMDNsWhenEncryptedCheck, SIGNAL(toggled(bool)), SLOT(slotEmitChanged()) );
+
+ // Warning label:
+ label = new KActiveLabel( i18n("<b>WARNING:</b> Unconditionally returning "
+ "confirmations undermines your privacy. "
+ "<a href=\"whatsthis:%1\">More...</a>")
+ .arg(receiptWhatsThis),
+ group );
+
+ vlay->addWidget( group );
+
+ // "Attached keys" group box:
+ group = new QVGroupBox( i18n( "Certificate && Key Bundle Attachments" ), this );
+ group->layout()->setSpacing( KDialog::spacingHint() );
+
+ mAutomaticallyImportAttachedKeysCheck = new QCheckBox( i18n("Automatically import keys and certificates"), group );
+ connect( mAutomaticallyImportAttachedKeysCheck, SIGNAL(toggled(bool)), SLOT(slotEmitChanged()) );
+
+ vlay->addWidget( group );
+
+
+
+ vlay->addStretch( 10 ); // spacer
+}
+
+void SecurityPage::GeneralTab::doLoadOther() {
+ const KConfigGroup reader( KMKernel::config(), "Reader" );
+
+ mHtmlMailCheck->setChecked( reader.readBoolEntry( "htmlMail", false ) );
+ mExternalReferences->setChecked( reader.readBoolEntry( "htmlLoadExternal", false ) );
+ mAutomaticallyImportAttachedKeysCheck->setChecked( reader.readBoolEntry( "AutoImportKeys", false ) );
+
+ mAlwaysDecrypt->setChecked( GlobalSettings::self()->alwaysDecrypt() );
+
+ const KConfigGroup mdn( KMKernel::config(), "MDN" );
+
+ int num = mdn.readNumEntry( "default-policy", 0 );
+ if ( num < 0 || num >= mMDNGroup->count() ) num = 0;
+ mMDNGroup->setButton( num );
+ num = mdn.readNumEntry( "quote-message", 0 );
+ if ( num < 0 || num >= mOrigQuoteGroup->count() ) num = 0;
+ mOrigQuoteGroup->setButton( num );
+ mNoMDNsWhenEncryptedCheck->setChecked(mdn.readBoolEntry( "not-send-when-encrypted", true ));
+}
+
+void SecurityPage::GeneralTab::installProfile( KConfig * profile ) {
+ const KConfigGroup reader( profile, "Reader" );
+ const KConfigGroup mdn( profile, "MDN" );
+
+ if ( reader.hasKey( "htmlMail" ) )
+ mHtmlMailCheck->setChecked( reader.readBoolEntry( "htmlMail" ) );
+ if ( reader.hasKey( "htmlLoadExternal" ) )
+ mExternalReferences->setChecked( reader.readBoolEntry( "htmlLoadExternal" ) );
+ if ( reader.hasKey( "AutoImportKeys" ) )
+ mAutomaticallyImportAttachedKeysCheck->setChecked( reader.readBoolEntry( "AutoImportKeys" ) );
+
+ if ( mdn.hasKey( "default-policy" ) ) {
+ int num = mdn.readNumEntry( "default-policy" );
+ if ( num < 0 || num >= mMDNGroup->count() ) num = 0;
+ mMDNGroup->setButton( num );
+ }
+ if ( mdn.hasKey( "quote-message" ) ) {
+ int num = mdn.readNumEntry( "quote-message" );
+ if ( num < 0 || num >= mOrigQuoteGroup->count() ) num = 0;
+ mOrigQuoteGroup->setButton( num );
+ }
+ if ( mdn.hasKey( "not-send-when-encrypted" ) )
+ mNoMDNsWhenEncryptedCheck->setChecked(mdn.readBoolEntry( "not-send-when-encrypted" ));
+}
+
+void SecurityPage::GeneralTab::save() {
+ KConfigGroup reader( KMKernel::config(), "Reader" );
+ KConfigGroup mdn( KMKernel::config(), "MDN" );
+
+ if (reader.readBoolEntry( "htmlMail", false ) != mHtmlMailCheck->isChecked())
+ {
+ if (KMessageBox::warningContinueCancel(this, i18n("Changing the global "
+ "HTML setting will override all folder specific values."), QString::null,
+ KStdGuiItem::cont(), "htmlMailOverride") == KMessageBox::Continue)
+ {
+ reader.writeEntry( "htmlMail", mHtmlMailCheck->isChecked() );
+ QStringList names;
+ QValueList<QGuardedPtr<KMFolder> > folders;
+ kmkernel->folderMgr()->createFolderList(&names, &folders);
+ kmkernel->imapFolderMgr()->createFolderList(&names, &folders);
+ kmkernel->dimapFolderMgr()->createFolderList(&names, &folders);
+ kmkernel->searchFolderMgr()->createFolderList(&names, &folders);
+ for (QValueList<QGuardedPtr<KMFolder> >::iterator it = folders.begin();
+ it != folders.end(); ++it)
+ {
+ if (*it)
+ {
+ KConfigGroupSaver saver(KMKernel::config(),
+ "Folder-" + (*it)->idString());
+ KMKernel::config()->writeEntry("htmlMailOverride", false);
+ }
+ }
+ }
+ }
+ reader.writeEntry( "htmlLoadExternal", mExternalReferences->isChecked() );
+ reader.writeEntry( "AutoImportKeys", mAutomaticallyImportAttachedKeysCheck->isChecked() );
+ mdn.writeEntry( "default-policy", mMDNGroup->id( mMDNGroup->selected() ) );
+ mdn.writeEntry( "quote-message", mOrigQuoteGroup->id( mOrigQuoteGroup->selected() ) );
+ mdn.writeEntry( "not-send-when-encrypted", mNoMDNsWhenEncryptedCheck->isChecked() );
+ GlobalSettings::self()->setAlwaysDecrypt( mAlwaysDecrypt->isChecked() );
+}
+
+
+QString SecurityPage::ComposerCryptoTab::helpAnchor() const {
+ return QString::fromLatin1("configure-security-composing");
+}
+
+SecurityPageComposerCryptoTab::SecurityPageComposerCryptoTab( QWidget * parent, const char * name )
+ : ConfigModuleTab ( parent, name )
+{
+ // the margins are inside mWidget itself
+ QVBoxLayout* vlay = new QVBoxLayout( this, 0, 0 );
+
+ mWidget = new ComposerCryptoConfiguration( this );
+ connect( mWidget->mAutoSignature, SIGNAL( toggled(bool) ), this, SLOT( slotEmitChanged() ) );
+ connect( mWidget->mEncToSelf, SIGNAL( toggled(bool) ), this, SLOT( slotEmitChanged() ) );
+ connect( mWidget->mShowEncryptionResult, SIGNAL( toggled(bool) ), this, SLOT( slotEmitChanged() ) );
+ connect( mWidget->mShowKeyApprovalDlg, SIGNAL( toggled(bool) ), this, SLOT( slotEmitChanged() ) );
+ connect( mWidget->mAutoEncrypt, SIGNAL( toggled(bool) ), this, SLOT( slotEmitChanged() ) );
+ connect( mWidget->mNeverEncryptWhenSavingInDrafts, SIGNAL( toggled(bool) ), this, SLOT( slotEmitChanged() ) );
+ connect( mWidget->mStoreEncrypted, SIGNAL( toggled(bool) ), this, SLOT( slotEmitChanged() ) );
+ vlay->addWidget( mWidget );
+}
+
+void SecurityPage::ComposerCryptoTab::doLoadOther() {
+ const KConfigGroup composer( KMKernel::config(), "Composer" );
+
+ // If you change default values, sync messagecomposer.cpp too
+
+ mWidget->mAutoSignature->setChecked( composer.readBoolEntry( "pgp-auto-sign", false ) );
+
+ mWidget->mEncToSelf->setChecked( composer.readBoolEntry( "crypto-encrypt-to-self", true ) );
+ mWidget->mShowEncryptionResult->setChecked( false ); //composer.readBoolEntry( "crypto-show-encryption-result", true ) );
+ mWidget->mShowEncryptionResult->hide();
+ mWidget->mShowKeyApprovalDlg->setChecked( composer.readBoolEntry( "crypto-show-keys-for-approval", true ) );
+
+ mWidget->mAutoEncrypt->setChecked( composer.readBoolEntry( "pgp-auto-encrypt", false ) );
+ mWidget->mNeverEncryptWhenSavingInDrafts->setChecked( composer.readBoolEntry( "never-encrypt-drafts", true ) );
+
+ mWidget->mStoreEncrypted->setChecked( composer.readBoolEntry( "crypto-store-encrypted", true ) );
+}
+
+void SecurityPage::ComposerCryptoTab::installProfile( KConfig * profile ) {
+ const KConfigGroup composer( profile, "Composer" );
+
+ if ( composer.hasKey( "pgp-auto-sign" ) )
+ mWidget->mAutoSignature->setChecked( composer.readBoolEntry( "pgp-auto-sign" ) );
+
+ if ( composer.hasKey( "crypto-encrypt-to-self" ) )
+ mWidget->mEncToSelf->setChecked( composer.readBoolEntry( "crypto-encrypt-to-self" ) );
+ if ( composer.hasKey( "crypto-show-encryption-result" ) )
+ mWidget->mShowEncryptionResult->setChecked( composer.readBoolEntry( "crypto-show-encryption-result" ) );
+ if ( composer.hasKey( "crypto-show-keys-for-approval" ) )
+ mWidget->mShowKeyApprovalDlg->setChecked( composer.readBoolEntry( "crypto-show-keys-for-approval" ) );
+ if ( composer.hasKey( "pgp-auto-encrypt" ) )
+ mWidget->mAutoEncrypt->setChecked( composer.readBoolEntry( "pgp-auto-encrypt" ) );
+ if ( composer.hasKey( "never-encrypt-drafts" ) )
+ mWidget->mNeverEncryptWhenSavingInDrafts->setChecked( composer.readBoolEntry( "never-encrypt-drafts" ) );
+
+ if ( composer.hasKey( "crypto-store-encrypted" ) )
+ mWidget->mStoreEncrypted->setChecked( composer.readBoolEntry( "crypto-store-encrypted" ) );
+}
+
+void SecurityPage::ComposerCryptoTab::save() {
+ KConfigGroup composer( KMKernel::config(), "Composer" );
+
+ composer.writeEntry( "pgp-auto-sign", mWidget->mAutoSignature->isChecked() );
+
+ composer.writeEntry( "crypto-encrypt-to-self", mWidget->mEncToSelf->isChecked() );
+ composer.writeEntry( "crypto-show-encryption-result", mWidget->mShowEncryptionResult->isChecked() );
+ composer.writeEntry( "crypto-show-keys-for-approval", mWidget->mShowKeyApprovalDlg->isChecked() );
+
+ composer.writeEntry( "pgp-auto-encrypt", mWidget->mAutoEncrypt->isChecked() );
+ composer.writeEntry( "never-encrypt-drafts", mWidget->mNeverEncryptWhenSavingInDrafts->isChecked() );
+
+ composer.writeEntry( "crypto-store-encrypted", mWidget->mStoreEncrypted->isChecked() );
+}
+
+QString SecurityPage::WarningTab::helpAnchor() const {
+ return QString::fromLatin1("configure-security-warnings");
+}
+
+SecurityPageWarningTab::SecurityPageWarningTab( QWidget * parent, const char * name )
+ : ConfigModuleTab( parent, name )
+{
+ // the margins are inside mWidget itself
+ QVBoxLayout* vlay = new QVBoxLayout( this, 0, 0 );
+
+ mWidget = new WarningConfiguration( this );
+ vlay->addWidget( mWidget );
+
+ connect( mWidget->warnGroupBox, SIGNAL(toggled(bool)), SLOT(slotEmitChanged()) );
+ connect( mWidget->mWarnUnsigned, SIGNAL(toggled(bool)), SLOT(slotEmitChanged()) );
+ connect( mWidget->warnUnencryptedCB, SIGNAL(toggled(bool)), SLOT(slotEmitChanged()) );
+ connect( mWidget->warnReceiverNotInCertificateCB, SIGNAL(toggled(bool)), SLOT(slotEmitChanged()) );
+ connect( mWidget->mWarnSignKeyExpiresSB, SIGNAL( valueChanged( int ) ), SLOT( slotEmitChanged() ) );
+ connect( mWidget->mWarnSignChainCertExpiresSB, SIGNAL( valueChanged( int ) ), SLOT( slotEmitChanged() ) );
+ connect( mWidget->mWarnSignRootCertExpiresSB, SIGNAL( valueChanged( int ) ), SLOT( slotEmitChanged() ) );
+
+ connect( mWidget->mWarnEncrKeyExpiresSB, SIGNAL( valueChanged( int ) ), SLOT( slotEmitChanged() ) );
+ connect( mWidget->mWarnEncrChainCertExpiresSB, SIGNAL( valueChanged( int ) ), SLOT( slotEmitChanged() ) );
+ connect( mWidget->mWarnEncrRootCertExpiresSB, SIGNAL( valueChanged( int ) ), SLOT( slotEmitChanged() ) );
+
+ connect( mWidget->enableAllWarningsPB, SIGNAL(clicked()),
+ SLOT(slotReenableAllWarningsClicked()) );
+}
+
+void SecurityPage::WarningTab::doLoadOther() {
+ const KConfigGroup composer( KMKernel::config(), "Composer" );
+
+ mWidget->warnUnencryptedCB->setChecked( composer.readBoolEntry( "crypto-warning-unencrypted", false ) );
+ mWidget->mWarnUnsigned->setChecked( composer.readBoolEntry( "crypto-warning-unsigned", false ) );
+ mWidget->warnReceiverNotInCertificateCB->setChecked( composer.readBoolEntry( "crypto-warn-recv-not-in-cert", true ) );
+
+ // The "-int" part of the key name is because there used to be a separate boolean
+ // config entry for enabling/disabling. This is done with the single bool value now.
+ mWidget->warnGroupBox->setChecked( composer.readBoolEntry( "crypto-warn-when-near-expire", true ) );
+
+ mWidget->mWarnSignKeyExpiresSB->setValue( composer.readNumEntry( "crypto-warn-sign-key-near-expire-int", 14 ) );
+ mWidget->mWarnSignChainCertExpiresSB->setValue( composer.readNumEntry( "crypto-warn-sign-chaincert-near-expire-int", 14 ) );
+ mWidget->mWarnSignRootCertExpiresSB->setValue( composer.readNumEntry( "crypto-warn-sign-root-near-expire-int", 14 ) );
+
+ mWidget->mWarnEncrKeyExpiresSB->setValue( composer.readNumEntry( "crypto-warn-encr-key-near-expire-int", 14 ) );
+ mWidget->mWarnEncrChainCertExpiresSB->setValue( composer.readNumEntry( "crypto-warn-encr-chaincert-near-expire-int", 14 ) );
+ mWidget->mWarnEncrRootCertExpiresSB->setValue( composer.readNumEntry( "crypto-warn-encr-root-near-expire-int", 14 ) );
+
+ mWidget->enableAllWarningsPB->setEnabled( true );
+}
+
+void SecurityPage::WarningTab::installProfile( KConfig * profile ) {
+ const KConfigGroup composer( profile, "Composer" );
+
+ if ( composer.hasKey( "crypto-warning-unencrypted" ) )
+ mWidget->warnUnencryptedCB->setChecked( composer.readBoolEntry( "crypto-warning-unencrypted" ) );
+ if ( composer.hasKey( "crypto-warning-unsigned" ) )
+ mWidget->mWarnUnsigned->setChecked( composer.readBoolEntry( "crypto-warning-unsigned" ) );
+ if ( composer.hasKey( "crypto-warn-recv-not-in-cert" ) )
+ mWidget->warnReceiverNotInCertificateCB->setChecked( composer.readBoolEntry( "crypto-warn-recv-not-in-cert" ) );
+
+ if ( composer.hasKey( "crypto-warn-when-near-expire" ) )
+ mWidget->warnGroupBox->setChecked( composer.readBoolEntry( "crypto-warn-when-near-expire" ) );
+
+ if ( composer.hasKey( "crypto-warn-sign-key-near-expire-int" ) )
+ mWidget->mWarnSignKeyExpiresSB->setValue( composer.readNumEntry( "crypto-warn-sign-key-near-expire-int" ) );
+ if ( composer.hasKey( "crypto-warn-sign-chaincert-near-expire-int" ) )
+ mWidget->mWarnSignChainCertExpiresSB->setValue( composer.readNumEntry( "crypto-warn-sign-chaincert-near-expire-int" ) );
+ if ( composer.hasKey( "crypto-warn-sign-root-near-expire-int" ) )
+ mWidget->mWarnSignRootCertExpiresSB->setValue( composer.readNumEntry( "crypto-warn-sign-root-near-expire-int" ) );
+
+ if ( composer.hasKey( "crypto-warn-encr-key-near-expire-int" ) )
+ mWidget->mWarnEncrKeyExpiresSB->setValue( composer.readNumEntry( "crypto-warn-encr-key-near-expire-int" ) );
+ if ( composer.hasKey( "crypto-warn-encr-chaincert-near-expire-int" ) )
+ mWidget->mWarnEncrChainCertExpiresSB->setValue( composer.readNumEntry( "crypto-warn-encr-chaincert-near-expire-int" ) );
+ if ( composer.hasKey( "crypto-warn-encr-root-near-expire-int" ) )
+ mWidget->mWarnEncrRootCertExpiresSB->setValue( composer.readNumEntry( "crypto-warn-encr-root-near-expire-int" ) );
+}
+
+void SecurityPage::WarningTab::save() {
+ KConfigGroup composer( KMKernel::config(), "Composer" );
+
+ composer.writeEntry( "crypto-warn-recv-not-in-cert", mWidget->warnReceiverNotInCertificateCB->isChecked() );
+ composer.writeEntry( "crypto-warning-unencrypted", mWidget->warnUnencryptedCB->isChecked() );
+ composer.writeEntry( "crypto-warning-unsigned", mWidget->mWarnUnsigned->isChecked() );
+
+ composer.writeEntry( "crypto-warn-when-near-expire", mWidget->warnGroupBox->isChecked() );
+ composer.writeEntry( "crypto-warn-sign-key-near-expire-int",
+ mWidget->mWarnSignKeyExpiresSB->value() );
+ composer.writeEntry( "crypto-warn-sign-chaincert-near-expire-int",
+ mWidget->mWarnSignChainCertExpiresSB->value() );
+ composer.writeEntry( "crypto-warn-sign-root-near-expire-int",
+ mWidget->mWarnSignRootCertExpiresSB->value() );
+
+ composer.writeEntry( "crypto-warn-encr-key-near-expire-int",
+ mWidget->mWarnEncrKeyExpiresSB->value() );
+ composer.writeEntry( "crypto-warn-encr-chaincert-near-expire-int",
+ mWidget->mWarnEncrChainCertExpiresSB->value() );
+ composer.writeEntry( "crypto-warn-encr-root-near-expire-int",
+ mWidget->mWarnEncrRootCertExpiresSB->value() );
+}
+
+void SecurityPage::WarningTab::slotReenableAllWarningsClicked() {
+ KMessageBox::enableAllMessages();
+ mWidget->enableAllWarningsPB->setEnabled( false );
+}
+
+////
+
+QString SecurityPage::SMimeTab::helpAnchor() const {
+ return QString::fromLatin1("configure-security-smime-validation");
+}
+
+SecurityPageSMimeTab::SecurityPageSMimeTab( QWidget * parent, const char * name )
+ : ConfigModuleTab( parent, name )
+{
+ // the margins are inside mWidget itself
+ QVBoxLayout* vlay = new QVBoxLayout( this, 0, 0 );
+
+ mWidget = new SMimeConfiguration( this );
+ vlay->addWidget( mWidget );
+
+ // Button-group for exclusive radiobuttons
+ QButtonGroup* bg = new QButtonGroup( mWidget );
+ bg->hide();
+ bg->insert( mWidget->CRLRB );
+ bg->insert( mWidget->OCSPRB );
+
+ // Settings for the keyrequester custom widget
+ mWidget->OCSPResponderSignature->setAllowedKeys(
+ Kleo::KeySelectionDialog::SMIMEKeys
+ | Kleo::KeySelectionDialog::TrustedKeys
+ | Kleo::KeySelectionDialog::ValidKeys
+ | Kleo::KeySelectionDialog::SigningKeys
+ | Kleo::KeySelectionDialog::PublicKeys );
+ mWidget->OCSPResponderSignature->setMultipleKeysEnabled( false );
+
+ mConfig = Kleo::CryptoBackendFactory::instance()->config();
+
+ connect( mWidget->CRLRB, SIGNAL( toggled( bool ) ), this, SLOT( slotEmitChanged() ) );
+ connect( mWidget->OCSPRB, SIGNAL( toggled( bool ) ), this, SLOT( slotEmitChanged() ) );
+ connect( mWidget->OCSPResponderURL, SIGNAL( textChanged( const QString& ) ), this, SLOT( slotEmitChanged() ) );
+ connect( mWidget->OCSPResponderSignature, SIGNAL( changed() ), this, SLOT( slotEmitChanged() ) );
+ connect( mWidget->doNotCheckCertPolicyCB, SIGNAL( toggled( bool ) ), this, SLOT( slotEmitChanged() ) );
+ connect( mWidget->neverConsultCB, SIGNAL( toggled( bool ) ), this, SLOT( slotEmitChanged() ) );
+ connect( mWidget->fetchMissingCB, SIGNAL( toggled( bool ) ), this, SLOT( slotEmitChanged() ) );
+
+ connect( mWidget->ignoreServiceURLCB, SIGNAL( toggled( bool ) ), this, SLOT( slotEmitChanged() ) );
+ connect( mWidget->ignoreHTTPDPCB, SIGNAL( toggled( bool ) ), this, SLOT( slotEmitChanged() ) );
+ connect( mWidget->disableHTTPCB, SIGNAL( toggled( bool ) ), this, SLOT( slotEmitChanged() ) );
+ connect( mWidget->honorHTTPProxyRB, SIGNAL( toggled( bool ) ), this, SLOT( slotEmitChanged() ) );
+ connect( mWidget->useCustomHTTPProxyRB, SIGNAL( toggled( bool ) ), this, SLOT( slotEmitChanged() ) );
+ connect( mWidget->customHTTPProxy, SIGNAL( textChanged( const QString& ) ), this, SLOT( slotEmitChanged() ) );
+ connect( mWidget->ignoreLDAPDPCB, SIGNAL( toggled( bool ) ), this, SLOT( slotEmitChanged() ) );
+ connect( mWidget->disableLDAPCB, SIGNAL( toggled( bool ) ), this, SLOT( slotEmitChanged() ) );
+ connect( mWidget->customLDAPProxy, SIGNAL( textChanged( const QString& ) ), this, SLOT( slotEmitChanged() ) );
+
+ connect( mWidget->disableHTTPCB, SIGNAL( toggled( bool ) ),
+ this, SLOT( slotUpdateHTTPActions() ) );
+ connect( mWidget->ignoreHTTPDPCB, SIGNAL( toggled( bool ) ),
+ this, SLOT( slotUpdateHTTPActions() ) );
+
+ // Button-group for exclusive radiobuttons
+ QButtonGroup* bgHTTPProxy = new QButtonGroup( mWidget );
+ bgHTTPProxy->hide();
+ bgHTTPProxy->insert( mWidget->honorHTTPProxyRB );
+ bgHTTPProxy->insert( mWidget->useCustomHTTPProxyRB );
+
+ if ( !connectDCOPSignal( 0, "KPIM::CryptoConfig", "changed()",
+ "load()", false ) )
+ kdError(5650) << "SecurityPageSMimeTab: connection to CryptoConfig's changed() failed" << endl;
+
+}
+
+SecurityPageSMimeTab::~SecurityPageSMimeTab()
+{
+}
+
+static void disableDirmngrWidget( QWidget* w ) {
+ w->setEnabled( false );
+ QWhatsThis::remove( w );
+ QWhatsThis::add( w, i18n( "This option requires dirmngr >= 0.9.0" ) );
+}
+
+static void initializeDirmngrCheckbox( QCheckBox* cb, Kleo::CryptoConfigEntry* entry ) {
+ if ( entry )
+ cb->setChecked( entry->boolValue() );
+ else
+ disableDirmngrWidget( cb );
+}
+
+struct SMIMECryptoConfigEntries {
+ SMIMECryptoConfigEntries( Kleo::CryptoConfig* config )
+ : mConfig( config ) {
+
+ // Checkboxes
+ mCheckUsingOCSPConfigEntry = configEntry( "gpgsm", "Security", "enable-ocsp", Kleo::CryptoConfigEntry::ArgType_None, false );
+ mEnableOCSPsendingConfigEntry = configEntry( "dirmngr", "OCSP", "allow-ocsp", Kleo::CryptoConfigEntry::ArgType_None, false );
+ mDoNotCheckCertPolicyConfigEntry = configEntry( "gpgsm", "Security", "disable-policy-checks", Kleo::CryptoConfigEntry::ArgType_None, false );
+ mNeverConsultConfigEntry = configEntry( "gpgsm", "Security", "disable-crl-checks", Kleo::CryptoConfigEntry::ArgType_None, false );
+ mFetchMissingConfigEntry = configEntry( "gpgsm", "Security", "auto-issuer-key-retrieve", Kleo::CryptoConfigEntry::ArgType_None, false );
+ // dirmngr-0.9.0 options
+ mIgnoreServiceURLEntry = configEntry( "dirmngr", "OCSP", "ignore-ocsp-service-url", Kleo::CryptoConfigEntry::ArgType_None, false );
+ mIgnoreHTTPDPEntry = configEntry( "dirmngr", "HTTP", "ignore-http-dp", Kleo::CryptoConfigEntry::ArgType_None, false );
+ mDisableHTTPEntry = configEntry( "dirmngr", "HTTP", "disable-http", Kleo::CryptoConfigEntry::ArgType_None, false );
+ mHonorHTTPProxy = configEntry( "dirmngr", "HTTP", "honor-http-proxy", Kleo::CryptoConfigEntry::ArgType_None, false );
+
+ mIgnoreLDAPDPEntry = configEntry( "dirmngr", "LDAP", "ignore-ldap-dp", Kleo::CryptoConfigEntry::ArgType_None, false );
+ mDisableLDAPEntry = configEntry( "dirmngr", "LDAP", "disable-ldap", Kleo::CryptoConfigEntry::ArgType_None, false );
+ // Other widgets
+ mOCSPResponderURLConfigEntry = configEntry( "dirmngr", "OCSP", "ocsp-responder", Kleo::CryptoConfigEntry::ArgType_String, false );
+ mOCSPResponderSignature = configEntry( "dirmngr", "OCSP", "ocsp-signer", Kleo::CryptoConfigEntry::ArgType_String, false );
+ mCustomHTTPProxy = configEntry( "dirmngr", "HTTP", "http-proxy", Kleo::CryptoConfigEntry::ArgType_String, false );
+ mCustomLDAPProxy = configEntry( "dirmngr", "LDAP", "ldap-proxy", Kleo::CryptoConfigEntry::ArgType_String, false );
+ }
+
+ Kleo::CryptoConfigEntry* configEntry( const char* componentName,
+ const char* groupName,
+ const char* entryName,
+ int argType,
+ bool isList );
+
+ // Checkboxes
+ Kleo::CryptoConfigEntry* mCheckUsingOCSPConfigEntry;
+ Kleo::CryptoConfigEntry* mEnableOCSPsendingConfigEntry;
+ Kleo::CryptoConfigEntry* mDoNotCheckCertPolicyConfigEntry;
+ Kleo::CryptoConfigEntry* mNeverConsultConfigEntry;
+ Kleo::CryptoConfigEntry* mFetchMissingConfigEntry;
+ Kleo::CryptoConfigEntry* mIgnoreServiceURLEntry;
+ Kleo::CryptoConfigEntry* mIgnoreHTTPDPEntry;
+ Kleo::CryptoConfigEntry* mDisableHTTPEntry;
+ Kleo::CryptoConfigEntry* mHonorHTTPProxy;
+ Kleo::CryptoConfigEntry* mIgnoreLDAPDPEntry;
+ Kleo::CryptoConfigEntry* mDisableLDAPEntry;
+ // Other widgets
+ Kleo::CryptoConfigEntry* mOCSPResponderURLConfigEntry;
+ Kleo::CryptoConfigEntry* mOCSPResponderSignature;
+ Kleo::CryptoConfigEntry* mCustomHTTPProxy;
+ Kleo::CryptoConfigEntry* mCustomLDAPProxy;
+
+ Kleo::CryptoConfig* mConfig;
+};
+
+void SecurityPage::SMimeTab::doLoadOther() {
+ if ( !mConfig ) {
+ setEnabled( false );
+ return;
+ }
+
+ // Force re-parsing gpgconf data, in case e.g. kleopatra or "configure backend" was used
+ // (which ends up calling us via dcop)
+ mConfig->clear();
+
+ // Create config entries
+ // Don't keep them around, they'll get deleted by clear(), which could be
+ // done by the "configure backend" button even before we save().
+ SMIMECryptoConfigEntries e( mConfig );
+
+ // Initialize GUI items from the config entries
+
+ if ( e.mCheckUsingOCSPConfigEntry ) {
+ bool b = e.mCheckUsingOCSPConfigEntry->boolValue();
+ mWidget->OCSPRB->setChecked( b );
+ mWidget->CRLRB->setChecked( !b );
+ mWidget->OCSPGroupBox->setEnabled( b );
+ } else {
+ mWidget->OCSPGroupBox->setEnabled( false );
+ }
+ if ( e.mDoNotCheckCertPolicyConfigEntry )
+ mWidget->doNotCheckCertPolicyCB->setChecked( e.mDoNotCheckCertPolicyConfigEntry->boolValue() );
+ if ( e.mNeverConsultConfigEntry )
+ mWidget->neverConsultCB->setChecked( e.mNeverConsultConfigEntry->boolValue() );
+ if ( e.mFetchMissingConfigEntry )
+ mWidget->fetchMissingCB->setChecked( e.mFetchMissingConfigEntry->boolValue() );
+
+ if ( e.mOCSPResponderURLConfigEntry )
+ mWidget->OCSPResponderURL->setText( e.mOCSPResponderURLConfigEntry->stringValue() );
+ if ( e.mOCSPResponderSignature ) {
+ mWidget->OCSPResponderSignature->setFingerprint( e.mOCSPResponderSignature->stringValue() );
+ }
+
+ // dirmngr-0.9.0 options
+ initializeDirmngrCheckbox( mWidget->ignoreServiceURLCB, e.mIgnoreServiceURLEntry );
+ initializeDirmngrCheckbox( mWidget->ignoreHTTPDPCB, e.mIgnoreHTTPDPEntry );
+ initializeDirmngrCheckbox( mWidget->disableHTTPCB, e.mDisableHTTPEntry );
+ initializeDirmngrCheckbox( mWidget->ignoreLDAPDPCB, e.mIgnoreLDAPDPEntry );
+ initializeDirmngrCheckbox( mWidget->disableLDAPCB, e.mDisableLDAPEntry );
+ if ( e.mCustomHTTPProxy ) {
+ QString systemProxy = QString::fromLocal8Bit( getenv( "http_proxy" ) );
+ if ( systemProxy.isEmpty() )
+ systemProxy = i18n( "no proxy" );
+ mWidget->systemHTTPProxy->setText( i18n( "(Current system setting: %1)" ).arg( systemProxy ) );
+ bool honor = e.mHonorHTTPProxy && e.mHonorHTTPProxy->boolValue();
+ mWidget->honorHTTPProxyRB->setChecked( honor );
+ mWidget->useCustomHTTPProxyRB->setChecked( !honor );
+ mWidget->customHTTPProxy->setText( e.mCustomHTTPProxy->stringValue() );
+ } else {
+ disableDirmngrWidget( mWidget->honorHTTPProxyRB );
+ disableDirmngrWidget( mWidget->useCustomHTTPProxyRB );
+ disableDirmngrWidget( mWidget->systemHTTPProxy );
+ disableDirmngrWidget( mWidget->customHTTPProxy );
+ }
+ if ( e.mCustomLDAPProxy )
+ mWidget->customLDAPProxy->setText( e.mCustomLDAPProxy->stringValue() );
+ else {
+ disableDirmngrWidget( mWidget->customLDAPProxy );
+ disableDirmngrWidget( mWidget->customLDAPLabel );
+ }
+ slotUpdateHTTPActions();
+}
+
+void SecurityPage::SMimeTab::slotUpdateHTTPActions() {
+ mWidget->ignoreHTTPDPCB->setEnabled( !mWidget->disableHTTPCB->isChecked() );
+
+ // The proxy settings only make sense when "Ignore HTTP CRL DPs of certificate" is checked.
+ bool enableProxySettings = !mWidget->disableHTTPCB->isChecked()
+ && mWidget->ignoreHTTPDPCB->isChecked();
+ mWidget->systemHTTPProxy->setEnabled( enableProxySettings );
+ mWidget->useCustomHTTPProxyRB->setEnabled( enableProxySettings );
+ mWidget->honorHTTPProxyRB->setEnabled( enableProxySettings );
+ mWidget->customHTTPProxy->setEnabled( enableProxySettings );
+}
+
+void SecurityPage::SMimeTab::installProfile( KConfig * ) {
+}
+
+static void saveCheckBoxToKleoEntry( QCheckBox* cb, Kleo::CryptoConfigEntry* entry ) {
+ const bool b = cb->isChecked();
+ if ( entry && entry->boolValue() != b )
+ entry->setBoolValue( b );
+}
+
+void SecurityPage::SMimeTab::save() {
+ if ( !mConfig ) {
+ return;
+ }
+ // Create config entries
+ // Don't keep them around, they'll get deleted by clear(), which could be done by the
+ // "configure backend" button.
+ SMIMECryptoConfigEntries e( mConfig );
+
+ bool b = mWidget->OCSPRB->isChecked();
+ if ( e.mCheckUsingOCSPConfigEntry && e.mCheckUsingOCSPConfigEntry->boolValue() != b )
+ e.mCheckUsingOCSPConfigEntry->setBoolValue( b );
+ // Set allow-ocsp together with enable-ocsp
+ if ( e.mEnableOCSPsendingConfigEntry && e.mEnableOCSPsendingConfigEntry->boolValue() != b )
+ e.mEnableOCSPsendingConfigEntry->setBoolValue( b );
+
+ saveCheckBoxToKleoEntry( mWidget->doNotCheckCertPolicyCB, e.mDoNotCheckCertPolicyConfigEntry );
+ saveCheckBoxToKleoEntry( mWidget->neverConsultCB, e.mNeverConsultConfigEntry );
+ saveCheckBoxToKleoEntry( mWidget->fetchMissingCB, e.mFetchMissingConfigEntry );
+
+ QString txt = mWidget->OCSPResponderURL->text();
+ if ( e.mOCSPResponderURLConfigEntry && e.mOCSPResponderURLConfigEntry->stringValue() != txt )
+ e.mOCSPResponderURLConfigEntry->setStringValue( txt );
+
+ txt = mWidget->OCSPResponderSignature->fingerprint();
+ if ( e.mOCSPResponderSignature && e.mOCSPResponderSignature->stringValue() != txt ) {
+ e.mOCSPResponderSignature->setStringValue( txt );
+ }
+
+ //dirmngr-0.9.0 options
+ saveCheckBoxToKleoEntry( mWidget->ignoreServiceURLCB, e.mIgnoreServiceURLEntry );
+ saveCheckBoxToKleoEntry( mWidget->ignoreHTTPDPCB, e.mIgnoreHTTPDPEntry );
+ saveCheckBoxToKleoEntry( mWidget->disableHTTPCB, e.mDisableHTTPEntry );
+ saveCheckBoxToKleoEntry( mWidget->ignoreLDAPDPCB, e.mIgnoreLDAPDPEntry );
+ saveCheckBoxToKleoEntry( mWidget->disableLDAPCB, e.mDisableLDAPEntry );
+ if ( e.mCustomHTTPProxy ) {
+ const bool honor = mWidget->honorHTTPProxyRB->isChecked();
+ if ( e.mHonorHTTPProxy && e.mHonorHTTPProxy->boolValue() != honor )
+ e.mHonorHTTPProxy->setBoolValue( honor );
+
+ QString chosenProxy = mWidget->customHTTPProxy->text();
+ if ( chosenProxy != e.mCustomHTTPProxy->stringValue() )
+ e.mCustomHTTPProxy->setStringValue( chosenProxy );
+ }
+ txt = mWidget->customLDAPProxy->text();
+ if ( e.mCustomLDAPProxy && e.mCustomLDAPProxy->stringValue() != txt )
+ e.mCustomLDAPProxy->setStringValue( mWidget->customLDAPProxy->text() );
+
+ mConfig->sync( true );
+}
+
+bool SecurityPageSMimeTab::process(const QCString &fun, const QByteArray &data, QCString& replyType, QByteArray &replyData)
+{
+ if ( fun == "load()" ) {
+ replyType = "void";
+ load();
+ } else {
+ return DCOPObject::process( fun, data, replyType, replyData );
+ }
+ return true;
+}
+
+QCStringList SecurityPageSMimeTab::interfaces()
+{
+ QCStringList ifaces = DCOPObject::interfaces();
+ ifaces += "SecurityPageSMimeTab";
+ return ifaces;
+}
+
+QCStringList SecurityPageSMimeTab::functions()
+{
+ // Hide our slot, just because it's simpler to do so.
+ return DCOPObject::functions();
+}
+
+Kleo::CryptoConfigEntry* SMIMECryptoConfigEntries::configEntry( const char* componentName,
+ const char* groupName,
+ const char* entryName,
+ int /*Kleo::CryptoConfigEntry::ArgType*/ argType,
+ bool isList )
+{
+ Kleo::CryptoConfigEntry* entry = mConfig->entry( componentName, groupName, entryName );
+ if ( !entry ) {
+ kdWarning(5006) << QString( "Backend error: gpgconf doesn't seem to know the entry for %1/%2/%3" ).arg( componentName, groupName, entryName ) << endl;
+ return 0;
+ }
+ if( entry->argType() != argType || entry->isList() != isList ) {
+ kdWarning(5006) << QString( "Backend error: gpgconf has wrong type for %1/%2/%3: %4 %5" ).arg( componentName, groupName, entryName ).arg( entry->argType() ).arg( entry->isList() ) << endl;
+ return 0;
+ }
+ return entry;
+}
+
+////
+
+QString SecurityPage::CryptPlugTab::helpAnchor() const {
+ return QString::fromLatin1("configure-security-crypto-backends");
+}
+
+SecurityPageCryptPlugTab::SecurityPageCryptPlugTab( QWidget * parent, const char * name )
+ : ConfigModuleTab( parent, name )
+{
+ QVBoxLayout * vlay = new QVBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
+
+ mBackendConfig = Kleo::CryptoBackendFactory::instance()->configWidget( this, "mBackendConfig" );
+ connect( mBackendConfig, SIGNAL( changed( bool ) ), this, SIGNAL( changed( bool ) ) );
+
+ vlay->addWidget( mBackendConfig );
+}
+
+SecurityPageCryptPlugTab::~SecurityPageCryptPlugTab()
+{
+
+}
+
+void SecurityPage::CryptPlugTab::doLoadOther() {
+ mBackendConfig->load();
+}
+
+void SecurityPage::CryptPlugTab::save() {
+ mBackendConfig->save();
+}
+
+// *************************************************************
+// * *
+// * MiscPage *
+// * *
+// *************************************************************
+QString MiscPage::helpAnchor() const {
+ return QString::fromLatin1("configure-misc");
+}
+
+MiscPage::MiscPage( QWidget * parent, const char * name )
+ : ConfigModuleWithTabs( parent, name )
+{
+ mFolderTab = new FolderTab();
+ addTab( mFolderTab, i18n("&Folders") );
+
+ mGroupwareTab = new GroupwareTab();
+ addTab( mGroupwareTab, i18n("&Groupware") );
+ load();
+}
+
+QString MiscPage::FolderTab::helpAnchor() const {
+ return QString::fromLatin1("configure-misc-folders");
+}
+
+MiscPageFolderTab::MiscPageFolderTab( QWidget * parent, const char * name )
+ : ConfigModuleTab( parent, name )
+{
+ // temp. vars:
+ QVBoxLayout *vlay;
+ QHBoxLayout *hlay;
+ QLabel *label;
+
+ vlay = new QVBoxLayout( this, KDialog::marginHint(), KDialog::spacingHint() );
+
+ // "confirm before emptying folder" check box: stretch 0
+ mEmptyFolderConfirmCheck =
+ new QCheckBox( i18n("Corresponds to Folder->Move All Messages to Trash",
+ "Ask for co&nfirmation before moving all messages to "
+ "trash"),
+ this );
+ vlay->addWidget( mEmptyFolderConfirmCheck );
+ connect( mEmptyFolderConfirmCheck, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+ mExcludeImportantFromExpiry =
+ new QCheckBox( i18n("E&xclude important messages from expiry"), this );
+ vlay->addWidget( mExcludeImportantFromExpiry );
+ connect( mExcludeImportantFromExpiry, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ // "when trying to find unread messages" combo + label: stretch 0
+ hlay = new QHBoxLayout( vlay ); // inherits spacing
+ mLoopOnGotoUnread = new QComboBox( false, this );
+ label = new QLabel( mLoopOnGotoUnread,
+ i18n("to be continued with \"do not loop\", \"loop in current folder\", "
+ "and \"loop in all folders\".",
+ "When trying to find unread messages:"), this );
+ mLoopOnGotoUnread->insertStringList( QStringList()
+ << i18n("continuation of \"When trying to find unread messages:\"",
+ "Do not Loop")
+ << i18n("continuation of \"When trying to find unread messages:\"",
+ "Loop in Current Folder")
+ << i18n("continuation of \"When trying to find unread messages:\"",
+ "Loop in All Folders"));
+ hlay->addWidget( label );
+ hlay->addWidget( mLoopOnGotoUnread, 1 );
+ connect( mLoopOnGotoUnread, SIGNAL( activated( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ // when entering a folder
+ hlay = new QHBoxLayout( vlay ); // inherits spacing
+ mActionEnterFolder = new QComboBox( false, this );
+ label = new QLabel( mActionEnterFolder,
+ i18n("to be continued with \"jump to first new message\", "
+ "\"jump to first unread or new message\","
+ "and \"jump to last selected message\".",
+ "When entering a folder:"), this );
+ mActionEnterFolder->insertStringList( QStringList()
+ << i18n("continuation of \"When entering a folder:\"",
+ "Jump to First New Message")
+ << 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"));
+ hlay->addWidget( label );
+ hlay->addWidget( mActionEnterFolder, 1 );
+ connect( mActionEnterFolder, SIGNAL( activated( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ hlay = new QHBoxLayout( vlay ); // inherits spacing
+ mDelayedMarkAsRead = new QCheckBox( i18n("Mar&k selected message as read after"), this );
+ hlay->addWidget( mDelayedMarkAsRead );
+ mDelayedMarkTime = new KIntSpinBox( 0 /*min*/, 60 /*max*/, 1/*step*/,
+ 0 /*init*/, 10 /*base*/, this);
+ mDelayedMarkTime->setSuffix( i18n(" sec") );
+ mDelayedMarkTime->setEnabled( false ); // since mDelayedMarkAsREad is off
+ hlay->addWidget( mDelayedMarkTime );
+ hlay->addStretch( 1 );
+ connect( mDelayedMarkTime, SIGNAL( valueChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+ connect( mDelayedMarkAsRead, SIGNAL(toggled(bool)),
+ mDelayedMarkTime, SLOT(setEnabled(bool)));
+ connect( mDelayedMarkAsRead, SIGNAL(toggled(bool)),
+ this , SLOT(slotEmitChanged( void )));
+
+ // "show popup after Drag'n'Drop" checkbox: stretch 0
+ mShowPopupAfterDnD =
+ new QCheckBox( i18n("Ask for action after &dragging messages to another folder"), this );
+ vlay->addWidget( mShowPopupAfterDnD );
+ connect( mShowPopupAfterDnD, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ // "default mailbox format" combo + label: stretch 0
+ hlay = new QHBoxLayout( vlay ); // inherits spacing
+ mMailboxPrefCombo = new QComboBox( false, this );
+ label = new QLabel( mMailboxPrefCombo,
+ i18n("to be continued with \"flat files\" and "
+ "\"directories\", resp.",
+ "By default, &message folders on disk are:"), this );
+ mMailboxPrefCombo->insertStringList( QStringList()
+ << i18n("continuation of \"By default, &message folders on disk are\"",
+ "Flat Files (\"mbox\" format)")
+ << i18n("continuation of \"By default, &message folders on disk are\"",
+ "Directories (\"maildir\" format)") );
+ // and now: add QWhatsThis:
+ QString msg = i18n( "what's this help",
+ "<qt><p>This selects which mailbox format will be "
+ "the default for local folders:</p>"
+ "<p><b>mbox:</b> KMail's mail "
+ "folders are represented by a single file each. "
+ "Individual messages are separated from each other by a "
+ "line starting with \"From \". This saves space on "
+ "disk, but may be less robust, e.g. when moving messages "
+ "between folders.</p>"
+ "<p><b>maildir:</b> KMail's mail folders are "
+ "represented by real folders on disk. Individual messages "
+ "are separate files. This may waste a bit of space on "
+ "disk, but should be more robust, e.g. when moving "
+ "messages between folders.</p></qt>");
+ QWhatsThis::add( mMailboxPrefCombo, msg );
+ QWhatsThis::add( label, msg );
+ hlay->addWidget( label );
+ hlay->addWidget( mMailboxPrefCombo, 1 );
+ connect( mMailboxPrefCombo, SIGNAL( activated( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ // "On startup..." option:
+ hlay = new QHBoxLayout( vlay ); // inherits spacing
+ mOnStartupOpenFolder = new FolderRequester( this,
+ kmkernel->getKMMainWidget()->folderTree() );
+ label = new QLabel( mOnStartupOpenFolder,
+ i18n("Open this folder on startup:"), this );
+ hlay->addWidget( label );
+ hlay->addWidget( mOnStartupOpenFolder, 1 );
+ connect( mOnStartupOpenFolder, SIGNAL( folderChanged( KMFolder* ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ // "Empty &trash on program exit" option:
+ hlay = new QHBoxLayout( vlay ); // inherits spacing
+ mEmptyTrashCheck = new QCheckBox( i18n("Empty local &trash folder on program exit"),
+ this );
+ hlay->addWidget( mEmptyTrashCheck );
+ connect( mEmptyTrashCheck, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+#ifdef HAVE_INDEXLIB
+ // indexing enabled option:
+ mIndexingEnabled = new QCheckBox( i18n("Enable full text &indexing"), this );
+ vlay->addWidget( mIndexingEnabled );
+ connect( mIndexingEnabled, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+#endif
+
+ // "Quota Units"
+ hlay = new QHBoxLayout( vlay ); // inherits spacing
+ mQuotaCmbBox = new QComboBox( false, this );
+ label = new QLabel( mQuotaCmbBox,
+ i18n("Quota units: "), this );
+ mQuotaCmbBox->insertStringList( QStringList()
+ << i18n("KB")
+ << i18n("MB")
+ << i18n("GB") );
+ hlay->addWidget( label );
+ hlay->addWidget( mQuotaCmbBox, 1 );
+ connect( mQuotaCmbBox, SIGNAL( activated( int ) ), this, SLOT( slotEmitChanged( void ) ) );
+
+ vlay->addStretch( 1 );
+
+ // @TODO: Till, move into .kcgc file
+ msg = i18n( "what's this help",
+ "<qt><p>When jumping to the next unread message, it may occur "
+ "that no more unread messages are below the current message.</p>"
+ "<p><b>Do not loop:</b> The search will stop at the last message in "
+ "the current folder.</p>"
+ "<p><b>Loop in current folder:</b> The search will continue at the "
+ "top of the message list, but not go to another folder.</p>"
+ "<p><b>Loop in all folders:</b> The search will continue at the top of "
+ "the message list. If no unread messages are found it will then continue "
+ "to the next folder.</p>"
+ "<p>Similarly, when searching for the previous unread message, "
+ "the search will start from the bottom of the message list and continue to "
+ "the previous folder depending on which option is selected.</p></qt>" );
+ QWhatsThis::add( mLoopOnGotoUnread, msg );
+
+#ifdef HAVE_INDEXLIB
+ // this is probably overly pessimistic
+ msg = i18n( "what's this help",
+ "<qt><p>Full text indexing allows very fast searches on the content "
+ "of your messages. When enabled, the search dialog will work very fast. "
+ "Also, the search tool bar will select messages based on content.</p>"
+ "<p>It takes up a certain amount of disk space "
+ "(about half the disk space for the messages).</p>"
+ "<p>After enabling, the index will need to be built, but you can continue to use KMail "
+ "while this operation is running.</p>"
+ "</qt>"
+ );
+
+ QWhatsThis::add( mIndexingEnabled, msg );
+#endif
+}
+
+void MiscPage::FolderTab::doLoadFromGlobalSettings() {
+ mExcludeImportantFromExpiry->setChecked( GlobalSettings::self()->excludeImportantMailFromExpiry() );
+ // default = "Loop in current folder"
+ mLoopOnGotoUnread->setCurrentItem( GlobalSettings::self()->loopOnGotoUnread() );
+ mActionEnterFolder->setCurrentItem( GlobalSettings::self()->actionEnterFolder() );
+ mDelayedMarkAsRead->setChecked( GlobalSettings::self()->delayedMarkAsRead() );
+ mDelayedMarkTime->setValue( GlobalSettings::self()->delayedMarkTime() );
+ mShowPopupAfterDnD->setChecked( GlobalSettings::self()->showPopupAfterDnD() );
+ mQuotaCmbBox->setCurrentItem( GlobalSettings::self()->quotaUnit() );
+}
+
+void MiscPage::FolderTab::doLoadOther() {
+ KConfigGroup general( KMKernel::config(), "General" );
+
+ mEmptyTrashCheck->setChecked( general.readBoolEntry( "empty-trash-on-exit", true ) );
+ mOnStartupOpenFolder->setFolder( general.readEntry( "startupFolder",
+ kmkernel->inboxFolder()->idString() ) );
+ mEmptyFolderConfirmCheck->setChecked( general.readBoolEntry( "confirm-before-empty", true ) );
+
+ int num = general.readNumEntry("default-mailbox-format", 1 );
+ if ( num < 0 || num > 1 ) num = 1;
+ mMailboxPrefCombo->setCurrentItem( num );
+
+#ifdef HAVE_INDEXLIB
+ mIndexingEnabled->setChecked( kmkernel->msgIndex() && kmkernel->msgIndex()->isEnabled() );
+#endif
+}
+
+void MiscPage::FolderTab::save() {
+ KConfigGroup general( KMKernel::config(), "General" );
+
+ general.writeEntry( "empty-trash-on-exit", mEmptyTrashCheck->isChecked() );
+ general.writeEntry( "confirm-before-empty", mEmptyFolderConfirmCheck->isChecked() );
+ general.writeEntry( "default-mailbox-format", mMailboxPrefCombo->currentItem() );
+ general.writeEntry( "startupFolder", mOnStartupOpenFolder->folder() ?
+ mOnStartupOpenFolder->folder()->idString() : QString::null );
+
+ GlobalSettings::self()->setDelayedMarkAsRead( mDelayedMarkAsRead->isChecked() );
+ GlobalSettings::self()->setDelayedMarkTime( mDelayedMarkTime->value() );
+ GlobalSettings::self()->setActionEnterFolder( mActionEnterFolder->currentItem() );
+ GlobalSettings::self()->setLoopOnGotoUnread( mLoopOnGotoUnread->currentItem() );
+ GlobalSettings::self()->setShowPopupAfterDnD( mShowPopupAfterDnD->isChecked() );
+ GlobalSettings::self()->setExcludeImportantMailFromExpiry(
+ mExcludeImportantFromExpiry->isChecked() );
+ GlobalSettings::self()->setQuotaUnit( mQuotaCmbBox->currentItem() );
+#ifdef HAVE_INDEXLIB
+ if ( kmkernel->msgIndex() ) kmkernel->msgIndex()->setEnabled( mIndexingEnabled->isChecked() );
+#endif
+}
+
+QString MiscPage::GroupwareTab::helpAnchor() const {
+ return QString::fromLatin1("configure-misc-groupware");
+}
+
+MiscPageGroupwareTab::MiscPageGroupwareTab( QWidget* parent, const char* name )
+ : ConfigModuleTab( parent, name )
+{
+ QBoxLayout* vlay = new QVBoxLayout( this, KDialog::marginHint(),
+ KDialog::spacingHint() );
+ vlay->setAutoAdd( true );
+
+ // IMAP resource setup
+ QVGroupBox* b1 = new QVGroupBox( i18n("&IMAP Resource Folder Options"),
+ this );
+
+ mEnableImapResCB =
+ new QCheckBox( i18n("&Enable IMAP resource functionality"), b1 );
+ QToolTip::add( mEnableImapResCB, i18n( "This enables the IMAP storage for "
+ "the Kontact applications" ) );
+ QWhatsThis::add( mEnableImapResCB,
+ i18n( GlobalSettings::self()->theIMAPResourceEnabledItem()->whatsThis().utf8() ) );
+ connect( mEnableImapResCB, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ mBox = new QWidget( b1 );
+ QGridLayout* grid = new QGridLayout( mBox, 5, 2, 0, KDialog::spacingHint() );
+ grid->setColStretch( 1, 1 );
+ connect( mEnableImapResCB, SIGNAL( toggled(bool) ),
+ mBox, SLOT( setEnabled(bool) ) );
+
+ QLabel* storageFormatLA = new QLabel( i18n("&Format used for the groupware folders:"),
+ mBox );
+ QString toolTip = i18n( "Choose the format to use to store the contents of the groupware folders." );
+ QString whatsThis = i18n( GlobalSettings::self()
+ ->theIMAPResourceStorageFormatItem()->whatsThis().utf8() );
+ grid->addWidget( storageFormatLA, 0, 0 );
+ QToolTip::add( storageFormatLA, toolTip );
+ QWhatsThis::add( storageFormatLA, whatsThis );
+ mStorageFormatCombo = new QComboBox( false, mBox );
+ storageFormatLA->setBuddy( mStorageFormatCombo );
+ QStringList formatLst;
+ formatLst << i18n("Standard (Ical / Vcard)") << i18n("Kolab (XML)");
+ mStorageFormatCombo->insertStringList( formatLst );
+ grid->addWidget( mStorageFormatCombo, 0, 1 );
+ QToolTip::add( mStorageFormatCombo, toolTip );
+ QWhatsThis::add( mStorageFormatCombo, whatsThis );
+ connect( mStorageFormatCombo, SIGNAL( activated( int ) ),
+ this, SLOT( slotStorageFormatChanged( int ) ) );
+
+ QLabel* languageLA = new QLabel( i18n("&Language of the groupware folders:"),
+ mBox );
+
+ toolTip = i18n( "Set the language of the folder names" );
+ whatsThis = i18n( GlobalSettings::self()
+ ->theIMAPResourceFolderLanguageItem()->whatsThis().utf8() );
+ grid->addWidget( languageLA, 1, 0 );
+ QToolTip::add( languageLA, toolTip );
+ QWhatsThis::add( languageLA, whatsThis );
+ mLanguageCombo = new QComboBox( false, mBox );
+ languageLA->setBuddy( mLanguageCombo );
+ QStringList lst;
+ lst << i18n("English") << i18n("German") << i18n("French") << i18n("Dutch");
+ mLanguageCombo->insertStringList( lst );
+ grid->addWidget( mLanguageCombo, 1, 1 );
+ QToolTip::add( mLanguageCombo, toolTip );
+ QWhatsThis::add( mLanguageCombo, whatsThis );
+ connect( mLanguageCombo, SIGNAL( activated( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ mFolderComboLabel = new QLabel( mBox ); // text depends on storage format
+ toolTip = i18n( "Set the parent of the resource folders" );
+ whatsThis = i18n( GlobalSettings::self()->theIMAPResourceFolderParentItem()->whatsThis().utf8() );
+ QToolTip::add( mFolderComboLabel, toolTip );
+ QWhatsThis::add( mFolderComboLabel, whatsThis );
+ grid->addWidget( mFolderComboLabel, 2, 0 );
+
+ mFolderComboStack = new QWidgetStack( mBox );
+ grid->addWidget( mFolderComboStack, 2, 1 );
+
+ // First possibility in the widgetstack: a combo showing the list of all folders
+ // This is used with the ical/vcard storage
+ mFolderCombo = new FolderRequester( mBox,
+ kmkernel->getKMMainWidget()->folderTree() );
+ mFolderComboStack->addWidget( mFolderCombo, 0 );
+ QToolTip::add( mFolderCombo, toolTip );
+ QWhatsThis::add( mFolderCombo, whatsThis );
+ connect( mFolderCombo, SIGNAL( folderChanged( KMFolder* ) ),
+ this, SLOT( slotEmitChanged() ) );
+
+ // Second possibility in the widgetstack: a combo showing the list of accounts
+ // This is used with the kolab xml storage since the groupware folders
+ // are always under the inbox.
+ mAccountCombo = new KMail::AccountComboBox( mBox );
+ mFolderComboStack->addWidget( mAccountCombo, 1 );
+ QToolTip::add( mAccountCombo, toolTip );
+ QWhatsThis::add( mAccountCombo, whatsThis );
+ connect( mAccountCombo, SIGNAL( activated( int ) ),
+ this, SLOT( slotEmitChanged() ) );
+
+ mHideGroupwareFolders = new QCheckBox( i18n( "&Hide groupware folders" ),
+ mBox, "HideGroupwareFoldersBox" );
+ grid->addMultiCellWidget( mHideGroupwareFolders, 3, 3, 0, 0 );
+ QToolTip::add( mHideGroupwareFolders,
+ i18n( "When this is checked, you will not see the IMAP "
+ "resource folders in the folder tree." ) );
+ QWhatsThis::add( mHideGroupwareFolders, i18n( GlobalSettings::self()
+ ->hideGroupwareFoldersItem()->whatsThis().utf8() ) );
+ connect( mHideGroupwareFolders, SIGNAL( toggled( bool ) ),
+ this, SLOT( slotEmitChanged() ) );
+
+ mOnlyShowGroupwareFolders = new QCheckBox( i18n( "&Only show groupware folders for this account" ),
+ mBox, "OnlyGroupwareFoldersBox" );
+ grid->addMultiCellWidget( mOnlyShowGroupwareFolders, 3, 3, 1, 1 );
+ QToolTip::add( mOnlyShowGroupwareFolders,
+ i18n( "When this is checked, you will not see normal "
+ "mail folders in the folder tree for the account "
+ "configured for groupware." ) );
+ QWhatsThis::add( mOnlyShowGroupwareFolders, i18n( GlobalSettings::self()
+ ->showOnlyGroupwareFoldersForGroupwareAccountItem()->whatsThis().utf8() ) );
+ connect( mOnlyShowGroupwareFolders, SIGNAL( toggled( bool ) ),
+ this, SLOT( slotEmitChanged() ) );
+
+ mSyncImmediately = new QCheckBox( i18n( "Synchronize groupware changes immediately" ), mBox );
+ QToolTip::add( mSyncImmediately,
+ i18n( "Synchronize groupware changes in disconnected IMAP folders immediately when being online." ) );
+ connect( mSyncImmediately, SIGNAL(toggled(bool)), SLOT(slotEmitChanged()) );
+ grid->addMultiCellWidget( mSyncImmediately, 4, 4, 0, 1 );
+
+ mDeleteInvitations = new QCheckBox(
+ i18n( GlobalSettings::self()->deleteInvitationEmailsAfterSendingReplyItem()->label().utf8() ), mBox );
+ QWhatsThis::add( mDeleteInvitations, i18n( GlobalSettings::self()
+ ->deleteInvitationEmailsAfterSendingReplyItem()->whatsThis().utf8() ) );
+ connect( mDeleteInvitations, SIGNAL(toggled(bool)), SLOT(slotEmitChanged()) );
+ grid->addMultiCellWidget( mDeleteInvitations, 5, 5, 0, 1 );
+
+ // Groupware functionality compatibility setup
+ b1 = new QVGroupBox( i18n("Groupware Compatibility && Legacy Options"), this );
+
+ gBox = new QVBox( b1 );
+#if 0
+ // Currently believed to be disused.
+ mEnableGwCB = new QCheckBox( i18n("&Enable groupware functionality"), b1 );
+ gBox->setSpacing( KDialog::spacingHint() );
+ connect( mEnableGwCB, SIGNAL( toggled(bool) ),
+ gBox, SLOT( setEnabled(bool) ) );
+ connect( mEnableGwCB, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+#endif
+ mEnableGwCB = 0;
+ mLegacyMangleFromTo = new QCheckBox( i18n( "Mangle From:/To: headers in replies to invitations" ), gBox );
+ QToolTip::add( mLegacyMangleFromTo, i18n( "Turn this option on in order to make Outlook(tm) understand your answers to invitation replies" ) );
+ QWhatsThis::add( mLegacyMangleFromTo, i18n( GlobalSettings::self()->
+ legacyMangleFromToHeadersItem()->whatsThis().utf8() ) );
+ connect( mLegacyMangleFromTo, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+ mLegacyBodyInvites = new QCheckBox( i18n( "Send invitations in the mail body" ), gBox );
+ QToolTip::add( mLegacyBodyInvites, i18n( "Turn this option on in order to make Outlook(tm) understand your answers to invitations" ) );
+ QWhatsThis::add( mLegacyMangleFromTo, i18n( GlobalSettings::self()->
+ legacyBodyInvitesItem()->whatsThis().utf8() ) );
+ connect( mLegacyBodyInvites, SIGNAL( toggled( bool ) ),
+ this, SLOT( slotLegacyBodyInvitesToggled( bool ) ) );
+ connect( mLegacyBodyInvites, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ mExchangeCompatibleInvitations = new QCheckBox( i18n( "Exchange compatible invitation naming" ), gBox );
+ QToolTip::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." ) );
+ QWhatsThis::add( mExchangeCompatibleInvitations, i18n( GlobalSettings::self()->
+ exchangeCompatibleInvitationsItem()->whatsThis().utf8() ) );
+ connect( mExchangeCompatibleInvitations, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ mAutomaticSending = new QCheckBox( i18n( "Automatic invitation sending" ), gBox );
+ QToolTip::add( mAutomaticSending, i18n( "When this is on, the user will not see the mail composer window. Invitation mails are sent automatically" ) );
+ QWhatsThis::add( mAutomaticSending, i18n( GlobalSettings::self()->
+ automaticSendingItem()->whatsThis().utf8() ) );
+ connect( mAutomaticSending, SIGNAL( stateChanged( int ) ),
+ this, SLOT( slotEmitChanged( void ) ) );
+
+ // Open space padding at the end
+ new QLabel( this );
+}
+
+void MiscPageGroupwareTab::slotLegacyBodyInvitesToggled( bool on )
+{
+ if ( on ) {
+ QString txt = i18n( "<qt>Invitations are normally sent as attachments to "
+ "a mail. This switch changes the invitation mails to "
+ "be sent in the text of the mail instead; this is "
+ "necessary to send invitations and replies to "
+ "Microsoft Outlook.<br>But, when you do this, you no "
+ "longer get descriptive text that mail programs "
+ "can read; so, to people who have email programs "
+ "that do not understand the invitations, the "
+ "resulting messages look very odd.<br>People that have email "
+ "programs that do understand invitations will still "
+ "be able to work with this.</qt>" );
+ KMessageBox::information( this, txt, QString::null,
+ "LegacyBodyInvitesWarning" );
+ }
+ // Invitations in the body are autosent in any case (no point in editing raw ICAL)
+ // So the autosend option is only available if invitations are sent as attachment.
+ mAutomaticSending->setEnabled( !mLegacyBodyInvites->isChecked() );
+}
+
+void MiscPage::GroupwareTab::doLoadFromGlobalSettings() {
+ if ( mEnableGwCB ) {
+ mEnableGwCB->setChecked( GlobalSettings::self()->groupwareEnabled() );
+ gBox->setEnabled( mEnableGwCB->isChecked() );
+ }
+
+ mLegacyMangleFromTo->setChecked( GlobalSettings::self()->legacyMangleFromToHeaders() );
+ mLegacyBodyInvites->blockSignals( true );
+
+ mLegacyBodyInvites->setChecked( GlobalSettings::self()->legacyBodyInvites() );
+ mLegacyBodyInvites->blockSignals( false );
+
+ mExchangeCompatibleInvitations->setChecked( GlobalSettings::self()->exchangeCompatibleInvitations() );
+
+ mAutomaticSending->setChecked( GlobalSettings::self()->automaticSending() );
+ mAutomaticSending->setEnabled( !mLegacyBodyInvites->isChecked() );
+
+ // Read the IMAP resource config
+ mEnableImapResCB->setChecked( GlobalSettings::self()->theIMAPResourceEnabled() );
+ mBox->setEnabled( mEnableImapResCB->isChecked() );
+
+ mHideGroupwareFolders->setChecked( GlobalSettings::self()->hideGroupwareFolders() );
+ int i = GlobalSettings::self()->theIMAPResourceFolderLanguage();
+ mLanguageCombo->setCurrentItem(i);
+ i = GlobalSettings::self()->theIMAPResourceStorageFormat();
+ mStorageFormatCombo->setCurrentItem(i);
+ slotStorageFormatChanged( i );
+ mOnlyShowGroupwareFolders->setChecked( GlobalSettings::self()->showOnlyGroupwareFoldersForGroupwareAccount() );
+ mSyncImmediately->setChecked( GlobalSettings::self()->immediatlySyncDIMAPOnGroupwareChanges() );
+ mDeleteInvitations->setChecked( GlobalSettings::self()->deleteInvitationEmailsAfterSendingReply() );
+
+ QString folderId( GlobalSettings::self()->theIMAPResourceFolderParent() );
+ if( !folderId.isNull() && kmkernel->findFolderById( folderId ) ) {
+ mFolderCombo->setFolder( folderId );
+ } else {
+ // Folder was deleted, we have to choose a new one
+ mFolderCombo->setFolder( i18n( "<Choose a Folder>" ) );
+ }
+
+ KMAccount* selectedAccount = 0;
+ int accountId = GlobalSettings::self()->theIMAPResourceAccount();
+ if ( accountId )
+ selectedAccount = kmkernel->acctMgr()->find( accountId );
+ else {
+ // Fallback: iterate over accounts to select folderId if found (as an inbox folder)
+ for( KMAccount *a = kmkernel->acctMgr()->first(); a!=0;
+ a = kmkernel->acctMgr()->next() ) {
+ if( a->folder() && a->folder()->child() ) {
+ // Look inside that folder for an INBOX
+ KMFolderNode *node;
+ for (node = a->folder()->child()->first(); node; node = a->folder()->child()->next())
+ if (!node->isDir() && node->name() == "INBOX") break;
+
+ if ( node && static_cast<KMFolder*>(node)->idString() == folderId ) {
+ selectedAccount = a;
+ break;
+ }
+ }
+ }
+ }
+ if ( selectedAccount )
+ mAccountCombo->setCurrentAccount( selectedAccount );
+ else if ( GlobalSettings::self()->theIMAPResourceStorageFormat() == 1 )
+ kdDebug(5006) << "Folder " << folderId << " not found as an account's inbox" << endl;
+}
+
+void MiscPage::GroupwareTab::save() {
+ KConfigGroup groupware( KMKernel::config(), "Groupware" );
+
+ // Write the groupware config
+ if ( mEnableGwCB ) {
+ groupware.writeEntry( "GroupwareEnabled", mEnableGwCB->isChecked() );
+ }
+ groupware.writeEntry( "LegacyMangleFromToHeaders", mLegacyMangleFromTo->isChecked() );
+ groupware.writeEntry( "LegacyBodyInvites", mLegacyBodyInvites->isChecked() );
+ groupware.writeEntry( "ExchangeCompatibleInvitations", mExchangeCompatibleInvitations->isChecked() );
+ groupware.writeEntry( "AutomaticSending", mAutomaticSending->isChecked() );
+
+ if ( mEnableGwCB ) {
+ GlobalSettings::self()->setGroupwareEnabled( mEnableGwCB->isChecked() );
+ }
+ GlobalSettings::self()->setLegacyMangleFromToHeaders( mLegacyMangleFromTo->isChecked() );
+ GlobalSettings::self()->setLegacyBodyInvites( mLegacyBodyInvites->isChecked() );
+ GlobalSettings::self()->setExchangeCompatibleInvitations( mExchangeCompatibleInvitations->isChecked() );
+ GlobalSettings::self()->setAutomaticSending( mAutomaticSending->isChecked() );
+
+ int format = mStorageFormatCombo->currentItem();
+ GlobalSettings::self()->setTheIMAPResourceStorageFormat( format );
+
+ // Write the IMAP resource config
+ GlobalSettings::self()->setHideGroupwareFolders( mHideGroupwareFolders->isChecked() );
+ GlobalSettings::self()->setShowOnlyGroupwareFoldersForGroupwareAccount( mOnlyShowGroupwareFolders->isChecked() );
+ GlobalSettings::self()->setImmediatlySyncDIMAPOnGroupwareChanges( mSyncImmediately->isChecked() );
+ GlobalSettings::self()->setDeleteInvitationEmailsAfterSendingReply( mDeleteInvitations->isChecked() );
+
+ // If there is a leftover folder in the foldercombo, getFolder can
+ // return 0. In that case we really don't have it enabled
+ QString folderId;
+ if ( format == 0 ) {
+ KMFolder* folder = mFolderCombo->folder();
+ if ( folder )
+ folderId = folder->idString();
+ KMAccount* account = 0;
+ // Didn't find an easy way to find the account for a given folder...
+ // Fallback: iterate over accounts to select folderId if found (as an inbox folder)
+ for( KMAccount *a = kmkernel->acctMgr()->first();
+ a && !account; // stop when found
+ a = kmkernel->acctMgr()->next() ) {
+ if( a->folder() && a->folder()->child() ) {
+ KMFolderNode *node;
+ for ( node = a->folder()->child()->first(); node; node = a->folder()->child()->next() )
+ {
+ if ( static_cast<KMFolder*>(node) == folder ) {
+ account = a;
+ break;
+ }
+ }
+ }
+ }
+ GlobalSettings::self()->setTheIMAPResourceAccount( account ? account->id() : 0 );
+ } else {
+ // Inbox folder of the selected account
+ KMAccount* acct = mAccountCombo->currentAccount();
+ if ( acct ) {
+ folderId = QString( ".%1.directory/INBOX" ).arg( acct->id() );
+ GlobalSettings::self()->setTheIMAPResourceAccount( acct->id() );
+ }
+ }
+
+ bool enabled = mEnableImapResCB->isChecked() && !folderId.isEmpty();
+ GlobalSettings::self()->setTheIMAPResourceEnabled( enabled );
+ GlobalSettings::self()->setTheIMAPResourceFolderLanguage( mLanguageCombo->currentItem() );
+ GlobalSettings::self()->setTheIMAPResourceFolderParent( folderId );
+}
+
+void MiscPage::GroupwareTab::slotStorageFormatChanged( int format )
+{
+ mLanguageCombo->setEnabled( format == 0 ); // only ical/vcard needs the language hack
+ mFolderComboStack->raiseWidget( format );
+ if ( format == 0 ) {
+ mFolderComboLabel->setText( i18n("&Resource folders are subfolders of:") );
+ mFolderComboLabel->setBuddy( mFolderCombo );
+ } else {
+ mFolderComboLabel->setText( i18n("&Resource folders are in account:") );
+ mFolderComboLabel->setBuddy( mAccountCombo );
+ }
+ slotEmitChanged();
+}
+
+
+// *************************************************************
+// * *
+// * AccountUpdater *
+// * *
+// *************************************************************
+AccountUpdater::AccountUpdater(ImapAccountBase *account)
+ : QObject()
+{
+ mAccount = account;
+}
+
+void AccountUpdater::update()
+{
+ connect( mAccount, SIGNAL( connectionResult(int, const QString&) ),
+ this, SLOT( namespacesFetched() ) );
+ mAccount->makeConnection();
+}
+
+void AccountUpdater::namespacesFetched()
+{
+ mAccount->setCheckingMail( true );
+ mAccount->processNewMail( false );
+ deleteLater();
+}
+
+#undef DIM
+
+//----------------------------
+#include "configuredialog.moc"
diff --git a/kmail/configuredialog.h b/kmail/configuredialog.h
new file mode 100644
index 00000000..408a9d3f
--- /dev/null
+++ b/kmail/configuredialog.h
@@ -0,0 +1,90 @@
+/* -*- c++ -*-
+ * kmail: KDE mail client
+ * This file: Copyright (C) 2000 Espen Sand, espen@kde.org
+ * Copyright (C) 2001-2002 Marc Mutz <mutz@kde.org>
+ *
+ * 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 _CONFIGURE_DIALOG_H_
+#define _CONFIGURE_DIALOG_H_
+
+#include <qguardedptr.h>
+#include <kcmultidialog.h>
+
+class KConfig;
+class ProfileDialog;
+namespace KMail {
+ class ImapAccountBase;
+}
+
+class ConfigureDialog : public KCMultiDialog
+{
+ Q_OBJECT
+
+public:
+ ConfigureDialog( QWidget *parent=0, const char *name=0, bool modal=true );
+ ~ConfigureDialog();
+
+signals:
+ /** Installs a new profile (in the dislog's widgets; to apply, the
+ user has to hit the apply button). Profiles are normal kmail
+ config files which have an additonal group "KMail Profile"
+ containing keys "Name" and "Comment" for the name and description,
+ resp. Only keys that this profile is supposed to alter should be
+ included in the file.
+ */
+ void installProfile( KConfig *profile );
+protected:
+ void hideEvent( QHideEvent *i );
+protected slots:
+ /** @reimplemented
+ * Saves the GlobalSettings stuff before passing on to KCMultiDialog.
+ */
+ void slotApply();
+
+ /** @reimplemented
+ * Saves the GlobalSettings stuff before passing on to KCMultiDialog.
+ */
+ void slotOk();
+
+ /** @reimplemented
+ * Brings up the profile loading/editing dialog. We can't use User1, as
+ * KCMultiDialog uses that for "Reset". */
+ void slotUser2();
+
+private:
+ QGuardedPtr<ProfileDialog> mProfileDialog;
+};
+
+/**
+ * DImap accounts need to be updated after just being created to show the folders it has.
+ * This has to be done a-synchronically due to the nature of the account, so this object
+ * takes care of that.
+ */
+class AccountUpdater : public QObject {
+ Q_OBJECT
+ public:
+ AccountUpdater(KMail::ImapAccountBase *account);
+ void update();
+ public slots:
+ void namespacesFetched();
+ private:
+ KMail::ImapAccountBase *mAccount;
+};
+
+
+#endif
diff --git a/kmail/configuredialog_p.cpp b/kmail/configuredialog_p.cpp
new file mode 100644
index 00000000..2b716b2d
--- /dev/null
+++ b/kmail/configuredialog_p.cpp
@@ -0,0 +1,437 @@
+#ifndef KDE_USE_FINAL
+#define QT_NO_CAST_ASCII
+#endif
+// configuredialog_p.cpp: classes internal to ConfigureDialog
+// see configuredialog.cpp for details.
+
+// This must be first
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+// my header:
+#include "configuredialog_p.h"
+
+// other KMail headers:
+#include "kmtransport.h"
+#include "globalsettings.h"
+#include "kmacctcachedimap.h"
+
+// other kdenetwork headers: (none)
+
+// other KDE headers:
+#include <ksimpleconfig.h>
+#include <kstandarddirs.h>
+#include <klocale.h>
+#include <kdebug.h>
+
+// Qt headers:
+#include <qheader.h>
+#include <qtabwidget.h>
+#include <qradiobutton.h>
+#include <qbuttongroup.h>
+#include <qlabel.h>
+#include <qlayout.h>
+
+// Other headers:
+#include <assert.h>
+
+
+NewIdentityDialog::NewIdentityDialog( const QStringList & identities,
+ QWidget *parent, const char *name,
+ bool modal )
+ : KDialogBase( parent, name, modal, i18n("New Identity"),
+ Ok|Cancel|Help, Ok, true )
+{
+ setHelp( QString::fromLatin1("configure-identity-newidentitydialog") );
+ QWidget * page = makeMainWidget();
+ QVBoxLayout * vlay = new QVBoxLayout( page, 0, spacingHint() );
+
+ // row 0: line edit with label
+ QHBoxLayout * hlay = new QHBoxLayout( vlay ); // inherits spacing
+ mLineEdit = new KLineEdit( page );
+ mLineEdit->setFocus();
+ hlay->addWidget( new QLabel( mLineEdit, i18n("&New identity:"), page ) );
+ hlay->addWidget( mLineEdit, 1 );
+ connect( mLineEdit, SIGNAL(textChanged(const QString&)),
+ this, SLOT(slotEnableOK(const QString&)) );
+
+ mButtonGroup = new QButtonGroup( page );
+ mButtonGroup->hide();
+
+ // row 1: radio button
+ QRadioButton *radio = new QRadioButton( i18n("&With empty fields"), page );
+ radio->setChecked( true );
+ mButtonGroup->insert( radio, Empty );
+ vlay->addWidget( radio );
+
+ // row 2: radio button
+ radio = new QRadioButton( i18n("&Use Control Center settings"), page );
+ mButtonGroup->insert( radio, ControlCenter );
+ vlay->addWidget( radio );
+
+ // row 3: radio button
+ radio = new QRadioButton( i18n("&Duplicate existing identity"), page );
+ mButtonGroup->insert( radio, ExistingEntry );
+ vlay->addWidget( radio );
+
+ // row 4: combobox with existing identities and label
+ hlay = new QHBoxLayout( vlay ); // inherits spacing
+ mComboBox = new QComboBox( false, page );
+ mComboBox->insertStringList( identities );
+ mComboBox->setEnabled( false );
+ QLabel *label = new QLabel( mComboBox, i18n("&Existing identities:"), page );
+ label->setEnabled( false );
+ hlay->addWidget( label );
+ hlay->addWidget( mComboBox, 1 );
+
+ vlay->addStretch( 1 ); // spacer
+
+ // enable/disable combobox and label depending on the third radio
+ // button's state:
+ connect( radio, SIGNAL(toggled(bool)),
+ label, SLOT(setEnabled(bool)) );
+ connect( radio, SIGNAL(toggled(bool)),
+ mComboBox, SLOT(setEnabled(bool)) );
+
+ enableButtonOK( false ); // since line edit is empty
+}
+
+NewIdentityDialog::DuplicateMode NewIdentityDialog::duplicateMode() const {
+ int id = mButtonGroup->id( mButtonGroup->selected() );
+ assert( id == (int)Empty
+ || id == (int)ControlCenter
+ || id == (int)ExistingEntry );
+ return static_cast<DuplicateMode>( id );
+}
+
+void NewIdentityDialog::slotEnableOK( const QString & proposedIdentityName ) {
+ // OK button is disabled if
+ QString name = proposedIdentityName.stripWhiteSpace();
+ // name isn't empty
+ if ( name.isEmpty() ) {
+ enableButtonOK( false );
+ return;
+ }
+ // or name doesn't yet exist.
+ for ( int i = 0 ; i < mComboBox->count() ; i++ )
+ if ( mComboBox->text(i) == name ) {
+ enableButtonOK( false );
+ return;
+ }
+ enableButtonOK( true );
+}
+
+ListView::ListView( QWidget *parent, const char *name,
+ int visibleItem )
+ : KListView( parent, name )
+{
+ setVisibleItem(visibleItem);
+}
+
+
+void ListView::resizeEvent( QResizeEvent *e )
+{
+ KListView::resizeEvent(e);
+ resizeColums();
+}
+
+
+void ListView::showEvent( QShowEvent *e )
+{
+ KListView::showEvent(e);
+ resizeColums();
+}
+
+
+void ListView::resizeColums()
+{
+ int c = columns();
+ if( c == 0 )
+ {
+ return;
+ }
+
+ int w1 = viewport()->width();
+ int w2 = w1 / c;
+ int w3 = w1 - (c-1)*w2;
+
+ for( int i=0; i<c-1; i++ )
+ {
+ setColumnWidth( i, w2 );
+ }
+ setColumnWidth( c-1, w3 );
+}
+
+
+void ListView::setVisibleItem( int visibleItem, bool updateSize )
+{
+ mVisibleItem = QMAX( 1, visibleItem );
+ if( updateSize == true )
+ {
+ QSize s = sizeHint();
+ setMinimumSize( s.width() + verticalScrollBar()->sizeHint().width() +
+ lineWidth() * 2, s.height() );
+ }
+}
+
+
+QSize ListView::sizeHint() const
+{
+ QSize s = QListView::sizeHint();
+
+ int h = fontMetrics().height() + 2*itemMargin();
+ if( h % 2 > 0 ) { h++; }
+
+ s.setHeight( h*mVisibleItem + lineWidth()*2 + header()->sizeHint().height());
+ return s;
+}
+
+
+static QString flagPng = QString::fromLatin1("/flag.png");
+
+NewLanguageDialog::NewLanguageDialog( LanguageItemList & suppressedLangs,
+ QWidget *parent, const char *name,
+ bool modal )
+ : KDialogBase( parent, name, modal, i18n("New Language"), Ok|Cancel, Ok, true )
+{
+ // layout the page (a combobox with label):
+ QWidget *page = makeMainWidget();
+ QHBoxLayout *hlay = new QHBoxLayout( page, 0, spacingHint() );
+ mComboBox = new QComboBox( false, page );
+ hlay->addWidget( new QLabel( mComboBox, i18n("Choose &language:"), page ) );
+ hlay->addWidget( mComboBox, 1 );
+
+ QStringList pathList = KGlobal::dirs()->findAllResources( "locale",
+ QString::fromLatin1("*/entry.desktop") );
+ // extract a list of language tags that should not be included:
+ QStringList suppressedAcronyms;
+ for ( LanguageItemList::Iterator lit = suppressedLangs.begin();
+ lit != suppressedLangs.end(); ++lit )
+ suppressedAcronyms << (*lit).mLanguage;
+
+ // populate the combo box:
+ for ( QStringList::ConstIterator it = pathList.begin();
+ it != pathList.end(); ++it )
+ {
+ KSimpleConfig entry( *it );
+ entry.setGroup( "KCM Locale" );
+ // full name:
+ QString name = entry.readEntry( "Name" );
+ // {2,3}-letter abbreviation:
+ // we extract it from the path: "/prefix/de/entry.desktop" -> "de"
+ QString acronym = (*it).section( '/', -2, -2 );
+
+ if ( suppressedAcronyms.find( acronym ) == suppressedAcronyms.end() ) {
+ // not found:
+ QString displayname = QString::fromLatin1("%1 (%2)")
+ .arg( name ).arg( acronym );
+ QPixmap flag( locate("locale", acronym + flagPng ) );
+ mComboBox->insertItem( flag, displayname );
+ }
+ }
+ if ( !mComboBox->count() ) {
+ mComboBox->insertItem( i18n("No More Languages Available") );
+ enableButtonOK( false );
+ } else mComboBox->listBox()->sort();
+}
+
+QString NewLanguageDialog::language() const
+{
+ QString s = mComboBox->currentText();
+ int i = s.findRev( '(' );
+ return s.mid( i + 1, s.length() - i - 2 );
+}
+
+
+LanguageComboBox::LanguageComboBox( bool rw, QWidget *parent, const char *name )
+ : QComboBox( rw, parent, name )
+{
+}
+
+int LanguageComboBox::insertLanguage( const QString & language )
+{
+ static QString entryDesktop = QString::fromLatin1("/entry.desktop");
+ KSimpleConfig entry( locate("locale", language + entryDesktop) );
+ entry.setGroup( "KCM Locale" );
+ QString name = entry.readEntry( "Name" );
+ QString output = QString::fromLatin1("%1 (%2)").arg( name ).arg( language );
+ insertItem( QPixmap( locate("locale", language + flagPng ) ), output );
+ return listBox()->index( listBox()->findItem(output) );
+}
+
+QString LanguageComboBox::language() const
+{
+ QString s = currentText();
+ int i = s.findRev( '(' );
+ return s.mid( i + 1, s.length() - i - 2 );
+}
+
+void LanguageComboBox::setLanguage( const QString & language )
+{
+ QString parenthizedLanguage = QString::fromLatin1("(%1)").arg( language );
+ for (int i = 0; i < count(); i++)
+ // ### FIXME: use .endWith():
+ if ( text(i).find( parenthizedLanguage ) >= 0 ) {
+ setCurrentItem(i);
+ return;
+ }
+}
+
+//
+//
+// ProfileDialog
+//
+//
+
+ProfileDialog::ProfileDialog( QWidget * parent, const char * name, bool modal )
+ : KDialogBase( parent, name, modal, i18n("Load Profile"), Ok|Cancel, Ok, true )
+{
+ // tmp. vars:
+ QWidget * page = makeMainWidget();
+ QVBoxLayout * vlay = new QVBoxLayout( page, 0, spacingHint() );
+
+ mListView = new KListView( page, "mListView" );
+ mListView->addColumn( i18n("Available Profiles") );
+ mListView->addColumn( i18n("Description") );
+ mListView->setFullWidth( true );
+ mListView->setAllColumnsShowFocus( true );
+ mListView->setSorting( -1 );
+
+ vlay->addWidget( new QLabel( mListView,
+ i18n("&Select a profile and click 'OK' to "
+ "load its settings:"), page ) );
+ vlay->addWidget( mListView, 1 );
+
+ setup();
+
+ connect( mListView, SIGNAL(selectionChanged()),
+ SLOT(slotSelectionChanged()) );
+ connect( mListView, SIGNAL(doubleClicked ( QListViewItem *, const QPoint &, int ) ),
+ SLOT(slotOk()) );
+
+ connect( this, SIGNAL(finished()), SLOT(delayedDestruct()) );
+
+ enableButtonOK( false );
+}
+
+void ProfileDialog::slotSelectionChanged()
+{
+ enableButtonOK( mListView->selectedItem() );
+}
+
+void ProfileDialog::setup() {
+ mListView->clear();
+ // find all profiles (config files named "profile-xyz-rc"):
+ const QString profileFilenameFilter = QString::fromLatin1("kmail/profile-*-rc");
+ mProfileList = KGlobal::dirs()->findAllResources( "data", profileFilenameFilter );
+
+ kdDebug(5006) << "Profile manager: found " << mProfileList.count()
+ << " profiles:" << endl;
+
+ // build the list and populate the list view:
+ QListViewItem * listItem = 0;
+ for ( QStringList::const_iterator it = mProfileList.begin() ;
+ it != mProfileList.end() ; ++it ) {
+ KConfig profile( *it, true /* read-only */, false /* no KDE global */ );
+ profile.setGroup("KMail Profile");
+ QString name = profile.readEntry( "Name" );
+ if ( name.isEmpty() ) {
+ kdWarning(5006) << "File \"" << (*it)
+ << "\" doesn't provide a profile name!" << endl;
+ name = i18n("Missing profile name placeholder","Unnamed");
+ }
+ QString desc = profile.readEntry( "Comment" );
+ if ( desc.isEmpty() ) {
+ kdWarning(5006) << "File \"" << (*it)
+ << "\" doesn't provide a description!" << endl;
+ desc = i18n("Missing profile description placeholder","Not available");
+ }
+ listItem = new QListViewItem( mListView, listItem, name, desc );
+ }
+}
+
+void ProfileDialog::slotOk() {
+ const int index = mListView->itemIndex( mListView->selectedItem() );
+ if ( index < 0 )
+ return; // none selected
+
+ assert( (unsigned int)index < mProfileList.count() );
+
+ KConfig profile( *mProfileList.at(index), true, false );
+ emit profileSelected( &profile );
+ KDialogBase::slotOk();
+}
+
+
+ConfigModuleWithTabs::ConfigModuleWithTabs( QWidget * parent,
+ const char * name )
+ : ConfigModule( parent, name )
+{
+ QVBoxLayout *vlay = new QVBoxLayout( this, 0, KDialog::spacingHint() );
+ mTabWidget = new QTabWidget( this );
+ vlay->addWidget( mTabWidget );
+}
+
+void ConfigModuleWithTabs::addTab( ConfigModuleTab* tab, const QString & title ) {
+ mTabWidget->addTab( tab, title );
+ connect( tab, SIGNAL(changed( bool )),
+ this, SIGNAL(changed( bool )) );
+}
+
+void ConfigModuleWithTabs::load() {
+ for ( int i = 0 ; i < mTabWidget->count() ; ++i ) {
+ ConfigModuleTab *tab = dynamic_cast<ConfigModuleTab*>( mTabWidget->page(i) );
+ if ( tab )
+ tab->load();
+ }
+ KCModule::load();
+}
+
+void ConfigModuleWithTabs::save() {
+ KCModule::save();
+ for ( int i = 0 ; i < mTabWidget->count() ; ++i ) {
+ ConfigModuleTab *tab = dynamic_cast<ConfigModuleTab*>( mTabWidget->page(i) );
+ if ( tab )
+ tab->save();
+ }
+}
+
+void ConfigModuleWithTabs::defaults() {
+ ConfigModuleTab *tab = dynamic_cast<ConfigModuleTab*>( mTabWidget->currentPage() );
+ if ( tab )
+ tab->defaults();
+ KCModule::defaults();
+}
+
+void ConfigModuleWithTabs::installProfile(KConfig * /* profile */ ) {
+ for ( int i = 0 ; i < mTabWidget->count() ; ++i ) {
+ ConfigModuleTab *tab = dynamic_cast<ConfigModuleTab*>( mTabWidget->page(i) );
+ if ( tab )
+ tab->installProfile();
+ }
+}
+
+void ConfigModuleTab::load()
+{
+ doLoadFromGlobalSettings();
+ doLoadOther();
+}
+
+void ConfigModuleTab::defaults()
+{
+ // reset settings which are available via GlobalSettings to their defaults
+ // (stolen from KConfigDialogManager::updateWidgetsDefault())
+ const bool bUseDefaults = GlobalSettings::self()->useDefaults( true );
+ doLoadFromGlobalSettings();
+ GlobalSettings::self()->useDefaults( bUseDefaults );
+ // reset other settings to default values
+ doResetToDefaultsOther();
+}
+
+void ConfigModuleTab::slotEmitChanged( void ) {
+ emit changed( true );
+}
+
+
+#include "configuredialog_p.moc"
diff --git a/kmail/configuredialog_p.h b/kmail/configuredialog_p.h
new file mode 100644
index 00000000..79c2a885
--- /dev/null
+++ b/kmail/configuredialog_p.h
@@ -0,0 +1,1064 @@
+// -*- c++ -*-
+// configuredialog_p.h: classes internal to ConfigureDialog
+// see configuredialog.h for details.
+
+#ifndef _CONFIGURE_DIALOG_PRIVATE_H_
+#define _CONFIGURE_DIALOG_PRIVATE_H_
+
+#include <klineedit.h>
+#include <qcombobox.h>
+#include <qguardedptr.h>
+#include <qptrlist.h>
+#include <qstring.h>
+#include <qvaluelist.h>
+#include <qstringlist.h>
+#include <dcopobject.h>
+
+#include <kdialogbase.h>
+#include <klistview.h>
+#include <kcmodule.h>
+#include <klocale.h>
+#include <kdepimmacros.h>
+
+class QPushButton;
+class QLabel;
+class QCheckBox;
+class KURLRequester;
+class KFontChooser;
+class QRadioButton;
+class ColorListBox;
+class QFont;
+class QListViewItem;
+class QTabWidget;
+class QListBox;
+class QButtonGroup;
+class QRegExpValidator;
+class QVBox;
+class KMAccount;
+class KMTransportInfo;
+class ListView;
+class ConfigureDialog;
+class KIntSpinBox;
+class SimpleStringListEditor;
+class KConfig;
+class QPoint;
+class ComposerCryptoConfiguration;
+class WarningConfiguration;
+class SMimeConfiguration;
+class TemplatesConfiguration;
+class CustomTemplates;
+class QGroupBox;
+class QVGroupBox;
+#include <qdict.h>
+class QLineEdit;
+class KMMsgTagDesc;
+class KListBox;
+class KColorCombo;
+class KFontRequester;
+class KIconButton;
+class KKeyButton;
+class QSpinBox;
+
+namespace Kpgp {
+ class Config;
+}
+namespace KMail {
+ class IdentityDialog;
+ class IdentityListView;
+ class AccountComboBox;
+ class FolderRequester;
+}
+namespace Kleo {
+ class BackendConfigWidget;
+ class CryptoConfig;
+ class CryptoConfigEntry;
+}
+
+class NewIdentityDialog : public KDialogBase
+{
+ Q_OBJECT
+
+public:
+ enum DuplicateMode { Empty, ControlCenter, ExistingEntry };
+
+ NewIdentityDialog( const QStringList & identities,
+ QWidget *parent=0, const char *name=0, bool modal=true );
+
+ QString identityName() const { return mLineEdit->text(); }
+ QString duplicateIdentity() const { return mComboBox->currentText(); }
+ DuplicateMode duplicateMode() const;
+
+protected slots:
+ virtual void slotEnableOK( const QString & );
+
+private:
+ QLineEdit *mLineEdit;
+ QComboBox *mComboBox;
+ QButtonGroup *mButtonGroup;
+};
+
+
+//
+//
+// Language item handling
+//
+//
+
+struct LanguageItem
+{
+ LanguageItem() {}
+ LanguageItem( const QString & language, const QString & reply=QString::null,
+ const QString & replyAll=QString::null,
+ const QString & forward=QString::null,
+ const QString & indentPrefix=QString::null ) :
+ mLanguage( language ), mReply( reply ), mReplyAll( replyAll ),
+ mForward( forward ), mIndentPrefix( indentPrefix ) {}
+
+ QString mLanguage, mReply, mReplyAll, mForward, mIndentPrefix;
+};
+
+typedef QValueList<LanguageItem> LanguageItemList;
+
+class NewLanguageDialog : public KDialogBase
+{
+ Q_OBJECT
+
+ public:
+ NewLanguageDialog( LanguageItemList & suppressedLangs, QWidget *parent=0,
+ const char *name=0, bool modal=true );
+ QString language() const;
+
+ private:
+ QComboBox *mComboBox;
+};
+
+
+class LanguageComboBox : public QComboBox
+{
+ Q_OBJECT
+
+ public:
+ LanguageComboBox( bool rw, QWidget *parent=0, const char *name=0 );
+ int insertLanguage( const QString & language );
+ QString language() const;
+ void setLanguage( const QString & language );
+};
+
+//
+//
+// Profile dialog
+//
+//
+
+class ProfileDialog : public KDialogBase {
+ Q_OBJECT
+public:
+ ProfileDialog( QWidget * parent=0, const char * name=0, bool modal=false );
+
+signals:
+ void profileSelected( KConfig * profile );
+
+private slots:
+ void slotSelectionChanged();
+ void slotOk();
+
+private:
+ void setup();
+
+private:
+ KListView *mListView;
+ QStringList mProfileList;
+};
+
+#include <kdialog.h>
+class ConfigModule : public KCModule {
+ Q_OBJECT
+public:
+ ConfigModule( QWidget * parent=0, const char * name=0 )
+ : KCModule ( parent, name )
+ {}
+ ~ConfigModule() {}
+
+ virtual void load() = 0;
+ virtual void save() = 0;
+ virtual void defaults() {}
+
+ /** Should return the help anchor for this page or tab */
+ virtual QString helpAnchor() const = 0;
+
+signals:
+ /** Emitted when the installation of a profile is
+ requested. All connected kcms should load the values
+ from the profile only for those entries that
+ really have keys defined in the profile.
+ */
+ void installProfile( KConfig * profile );
+
+};
+
+
+// Individual tab of a ConfigModuleWithTabs
+class ConfigModuleTab : public QWidget {
+ Q_OBJECT
+public:
+ ConfigModuleTab( QWidget *parent=0, const char* name=0 )
+ :QWidget( parent, name )
+ {}
+ ~ConfigModuleTab() {}
+ void load();
+ virtual void save() = 0;
+ void defaults();
+ // the below are optional
+ virtual void installProfile(){}
+signals:
+ // forwarded to the ConfigModule
+ void changed(bool);
+public slots:
+ void slotEmitChanged();
+private:
+ // reimplement this for loading values of settings which are available
+ // via GlobalSettings
+ virtual void doLoadFromGlobalSettings() {}
+ // reimplement this for loading values of settings which are not available
+ // via GlobalSettings
+ virtual void doLoadOther() {}
+ // reimplement this for loading default values of settings which are
+ // not available via GlobalSettings (KConfigXT).
+ virtual void doResetToDefaultsOther() {}
+};
+
+
+/*
+ * ConfigModuleWithTabs represents a kcm with several tabs.
+ * It simply forwards load and save operations to all tabs.
+ */
+class ConfigModuleWithTabs : public ConfigModule {
+ Q_OBJECT
+public:
+ ConfigModuleWithTabs( QWidget * parent=0, const char * name=0 );
+ ~ConfigModuleWithTabs() {}
+
+ // don't reimplement any of those methods
+ virtual void load();
+ virtual void save();
+ virtual void defaults();
+ virtual void installProfile( KConfig * profile );
+
+protected:
+ void addTab( ConfigModuleTab* tab, const QString & title );
+
+private:
+ QTabWidget *mTabWidget;
+
+};
+
+
+//
+//
+// IdentityPage
+//
+//
+
+class KDE_EXPORT IdentityPage : public ConfigModule {
+ Q_OBJECT
+public:
+ IdentityPage( QWidget * parent=0, const char * name=0 );
+ ~IdentityPage() {}
+
+ QString helpAnchor() const;
+
+ void load();
+ void save();
+
+public slots:
+ void slotUpdateTransportCombo( const QStringList & );
+
+private slots:
+ void slotNewIdentity();
+ void slotModifyIdentity();
+ void slotRemoveIdentity();
+ /** Connected to @p mRenameButton's clicked() signal. Just does a
+ KListView::rename on the selected item */
+ void slotRenameIdentity();
+ /** connected to @p mIdentityList's renamed() signal. Validates the
+ new name and sets it in the KPIM::IdentityManager */
+ void slotRenameIdentity( QListViewItem *, const QString &, int );
+ void slotContextMenu( KListView*, QListViewItem *, const QPoint & );
+ void slotSetAsDefault();
+ void slotIdentitySelectionChanged();
+
+private: // methods
+ void refreshList();
+
+private: // data members
+ KMail::IdentityDialog * mIdentityDialog;
+ int mOldNumberOfIdentities;
+
+ KMail::IdentityListView * mIdentityList;
+ QPushButton * mModifyButton;
+ QPushButton * mRenameButton;
+ QPushButton * mRemoveButton;
+ QPushButton * mSetAsDefaultButton;
+};
+
+
+//
+//
+// AccountsPage
+//
+//
+
+// subclasses: one class per tab:
+class AccountsPageSendingTab : public ConfigModuleTab {
+ Q_OBJECT
+public:
+ AccountsPageSendingTab( QWidget * parent=0, const char * name=0 );
+ QString helpAnchor() const;
+ void save();
+
+signals:
+ void transportListChanged( const QStringList & );
+
+private slots:
+ void slotTransportSelected();
+ void slotAddTransport();
+ void slotModifySelectedTransport();
+ void slotRemoveSelectedTransport();
+ void slotSetDefaultTransport();
+
+private:
+ virtual void doLoadFromGlobalSettings();
+ virtual void doLoadOther();
+ //FIXME virtual void doResetToDefaultsOther();
+
+private:
+ ListView *mTransportList;
+ QPushButton *mModifyTransportButton;
+ QPushButton *mRemoveTransportButton;
+ QPushButton *mSetDefaultTransportButton;
+ QCheckBox *mConfirmSendCheck;
+ QComboBox *mSendOnCheckCombo;
+ QComboBox *mSendMethodCombo;
+ QComboBox *mMessagePropertyCombo;
+ QLineEdit *mDefaultDomainEdit;
+
+ QPtrList< KMTransportInfo > mTransportInfoList;
+};
+
+
+class AccountsPageReceivingTab : public ConfigModuleTab {
+ Q_OBJECT
+public:
+ AccountsPageReceivingTab( QWidget * parent=0, const char * name=0 );
+ ~AccountsPageReceivingTab();
+ QString helpAnchor() const;
+ void save();
+
+signals:
+ void accountListChanged( const QStringList & );
+
+private slots:
+ void slotAccountSelected();
+ void slotAddAccount();
+ void slotModifySelectedAccount();
+ void slotRemoveSelectedAccount();
+ void slotEditNotifications();
+ void slotTweakAccountList();
+
+private:
+ virtual void doLoadFromGlobalSettings();
+ virtual void doLoadOther();
+ //FIXME virtual void doResetToDefaultsOther();
+ QStringList occupiedNames();
+
+private:
+ ListView *mAccountList;
+ QPushButton *mModifyAccountButton;
+ QPushButton *mRemoveAccountButton;
+ QCheckBox *mBeepNewMailCheck;
+ QCheckBox *mVerboseNotificationCheck;
+ QCheckBox *mCheckmailStartupCheck;
+ QPushButton *mOtherNewMailActionsButton;
+
+ QValueList< QGuardedPtr<KMAccount> > mAccountsToDelete;
+ QValueList< QGuardedPtr<KMAccount> > mNewAccounts;
+ struct ModifiedAccountsType {
+ QGuardedPtr< KMAccount > oldAccount;
+ QGuardedPtr< KMAccount > newAccount;
+ };
+ // ### make this value-based:
+ QValueList< ModifiedAccountsType* > mModifiedAccounts;
+};
+
+class KDE_EXPORT AccountsPage : public ConfigModuleWithTabs {
+ Q_OBJECT
+public:
+ AccountsPage( QWidget * parent=0, const char * name=0 );
+ QString helpAnchor() const;
+
+
+ // hrmpf. moc doesn't like nested classes with slots/signals...:
+ typedef AccountsPageSendingTab SendingTab;
+ typedef AccountsPageReceivingTab ReceivingTab;
+
+signals:
+ void transportListChanged( const QStringList & );
+ void accountListChanged( const QStringList & );
+
+private:
+ SendingTab *mSendingTab;
+ ReceivingTab *mReceivingTab;
+};
+
+
+//
+//
+// AppearancePage
+//
+//
+
+class AppearancePageFontsTab : public ConfigModuleTab {
+ Q_OBJECT
+public:
+ AppearancePageFontsTab( QWidget * parent=0, const char * name=0 );
+ QString helpAnchor() const;
+ void save();
+
+ void installProfile( KConfig * profile );
+
+private slots:
+ void slotFontSelectorChanged( int );
+
+private:
+ //virtual void doLoadFromGlobalSettings();
+ virtual void doLoadOther();
+ //FIXME virtual void doResetToDefaultsOther();
+ void updateFontSelector();
+
+private:
+ QCheckBox *mCustomFontCheck;
+ QComboBox *mFontLocationCombo;
+ KFontChooser *mFontChooser;
+
+ int mActiveFontIndex;
+ QFont mFont[14];
+};
+
+class AppearancePageColorsTab : public ConfigModuleTab {
+ Q_OBJECT
+public:
+ AppearancePageColorsTab( QWidget * parent=0, const char * name=0 );
+ QString helpAnchor() const;
+ void save();
+
+ void installProfile( KConfig * profile );
+
+private:
+ //virtual void doLoadFromGlobalSettings();
+ virtual void doLoadOther();
+ //FIXME virtual void doResetToDefaultsOther();
+
+private:
+ QCheckBox *mCustomColorCheck;
+ ColorListBox *mColorList;
+ QCheckBox *mRecycleColorCheck;
+ QSpinBox *mCloseToQuotaThreshold;
+};
+
+class AppearancePageLayoutTab : public ConfigModuleTab {
+ Q_OBJECT
+public:
+ AppearancePageLayoutTab( QWidget * parent=0, const char * name=0 );
+ QString helpAnchor() const;
+
+ void save();
+ void installProfile( KConfig * profile );
+
+private:
+ //virtual void doLoadFromGlobalSettings();
+ virtual void doLoadOther();
+ //FIXME virtual void doResetToDefaultsOther();
+
+private: // data
+ QButtonGroup *mFolderListGroup;
+ QButtonGroup *mMIMETreeLocationGroup;
+ QButtonGroup *mMIMETreeModeGroup;
+ QButtonGroup *mReaderWindowModeGroup;
+ QCheckBox *mFavoriteFolderViewCB;
+ QCheckBox *mFolderQuickSearchCB;
+};
+
+class AppearancePageHeadersTab : public ConfigModuleTab {
+ Q_OBJECT
+public:
+ AppearancePageHeadersTab( QWidget * parent=0, const char * name=0 );
+
+ QString helpAnchor() const;
+
+ void save();
+ void installProfile( KConfig * profile );
+
+private: // methods
+ //virtual void doLoadFromGlobalSettings();
+ virtual void doLoadOther();
+ //FIXME virtual void doResetToDefaultsOther();
+ void setDateDisplay( int id, const QString & format );
+
+private: // data
+ QCheckBox *mShowQuickSearch;
+ QCheckBox *mMessageSizeCheck;
+ QCheckBox *mAttachmentCheck;
+ QCheckBox *mNestedMessagesCheck;
+ QCheckBox *mCryptoIconsCheck;
+ QButtonGroup *mNestingPolicy;
+ QButtonGroup *mDateDisplay;
+ QLineEdit *mCustomDateFormatEdit;
+};
+
+class AppearancePageReaderTab : public ConfigModuleTab {
+ Q_OBJECT
+public:
+ AppearancePageReaderTab( QWidget * parent=0, const char * name=0 );
+
+ QString helpAnchor() const;
+
+ void save();
+ void installProfile( KConfig * profile );
+
+private:
+ virtual void doLoadFromGlobalSettings();
+ virtual void doLoadOther();
+ //FIXME virtual void doResetToDefaultsOther();
+ void readCurrentFallbackCodec();
+ void readCurrentOverrideCodec();
+
+private: // data
+ QCheckBox *mShowColorbarCheck;
+ QCheckBox *mShowSpamStatusCheck;
+ QCheckBox *mShowEmoticonsCheck;
+ QCheckBox *mShowExpandQuotesMark;
+ KIntSpinBox *mCollapseQuoteLevelSpin;
+ QCheckBox *mShrinkQuotesCheck;
+ QComboBox *mCharsetCombo;
+ QComboBox *mOverrideCharsetCombo;
+};
+
+
+class AppearancePageSystemTrayTab : public ConfigModuleTab {
+ Q_OBJECT
+public:
+ AppearancePageSystemTrayTab( QWidget * parent=0, const char * name=0 );
+
+ QString helpAnchor() const;
+
+ void save();
+ void installProfile( KConfig * profile );
+
+private:
+ virtual void doLoadFromGlobalSettings();
+
+private: // data
+ QCheckBox *mSystemTrayCheck;
+ QButtonGroup *mSystemTrayGroup;
+};
+
+class KDE_EXPORT AppearancePage : public ConfigModuleWithTabs {
+ Q_OBJECT
+public:
+ AppearancePage( QWidget * parent=0, const char * name=0 );
+
+ QString helpAnchor() const;
+
+ // hrmpf. moc doesn't like nested classes with slots/signals...:
+ typedef AppearancePageFontsTab FontsTab;
+ typedef AppearancePageColorsTab ColorsTab;
+ typedef AppearancePageLayoutTab LayoutTab;
+ typedef AppearancePageHeadersTab HeadersTab;
+ typedef AppearancePageReaderTab ReaderTab;
+ typedef AppearancePageSystemTrayTab SystemTrayTab;
+
+private:
+ FontsTab *mFontsTab;
+ ColorsTab *mColorsTab;
+ LayoutTab *mLayoutTab;
+ HeadersTab *mHeadersTab;
+ ReaderTab *mReaderTab;
+ SystemTrayTab *mSystemTrayTab;
+};
+
+//
+//
+// Composer Page
+//
+//
+
+class ComposerPageGeneralTab : public ConfigModuleTab {
+ Q_OBJECT
+public:
+ ComposerPageGeneralTab( QWidget * parent=0, const char * name=0 );
+ QString helpAnchor() const;
+
+ void save();
+ void installProfile( KConfig * profile );
+protected slots:
+ void slotConfigureRecentAddresses();
+ void slotConfigureCompletionOrder();
+
+private:
+ virtual void doLoadFromGlobalSettings();
+
+private:
+ QCheckBox *mAutoAppSignFileCheck;
+ QCheckBox *mTopQuoteCheck;
+ QCheckBox *mSmartQuoteCheck;
+ QCheckBox *mAutoRequestMDNCheck;
+ QCheckBox *mShowRecentAddressesInComposer;
+ QCheckBox *mWordWrapCheck;
+ KIntSpinBox *mWrapColumnSpin;
+ KIntSpinBox *mAutoSave;
+ QCheckBox *mExternalEditorCheck;
+ KURLRequester *mEditorRequester;
+};
+
+class ComposerPagePhrasesTab : public ConfigModuleTab {
+ Q_OBJECT
+public:
+ ComposerPagePhrasesTab( QWidget * parent=0, const char * name=0 );
+ QString helpAnchor() const;
+
+ void save();
+
+private slots:
+ void slotNewLanguage();
+ void slotRemoveLanguage();
+ void slotLanguageChanged( const QString& );
+ void slotAddNewLanguage( const QString& );
+
+private:
+ virtual void doLoadFromGlobalSettings();
+ void setLanguageItemInformation( int index );
+ void saveActiveLanguageItem();
+
+private:
+ LanguageComboBox *mPhraseLanguageCombo;
+ QPushButton *mRemoveButton;
+ QLineEdit *mPhraseReplyEdit;
+ QLineEdit *mPhraseReplyAllEdit;
+ QLineEdit *mPhraseForwardEdit;
+ QLineEdit *mPhraseIndentPrefixEdit;
+
+ int mActiveLanguageItem;
+ LanguageItemList mLanguageList;
+};
+
+class ComposerPageTemplatesTab : public ConfigModuleTab {
+ Q_OBJECT
+public:
+ ComposerPageTemplatesTab( QWidget * parent=0, const char * name=0 );
+ QString helpAnchor() const;
+
+ void save();
+
+private slots:
+
+private:
+ virtual void doLoadFromGlobalSettings();
+
+private:
+ TemplatesConfiguration* mWidget;
+};
+
+class ComposerPageCustomTemplatesTab : public ConfigModuleTab {
+ Q_OBJECT
+public:
+ ComposerPageCustomTemplatesTab( QWidget * parent=0, const char * name=0 );
+ QString helpAnchor() const;
+
+ void save();
+
+private slots:
+
+private:
+ virtual void doLoadFromGlobalSettings();
+
+private:
+ CustomTemplates* mWidget;
+};
+
+class ComposerPageSubjectTab : public ConfigModuleTab {
+ Q_OBJECT
+public:
+ ComposerPageSubjectTab( QWidget * parent=0, const char * name=0 );
+ QString helpAnchor() const;
+
+ void save();
+
+private:
+ virtual void doLoadFromGlobalSettings();
+
+private:
+ SimpleStringListEditor *mReplyListEditor;
+ QCheckBox *mReplaceReplyPrefixCheck;
+ SimpleStringListEditor *mForwardListEditor;
+ QCheckBox *mReplaceForwardPrefixCheck;
+};
+
+class ComposerPageCharsetTab : public ConfigModuleTab {
+ Q_OBJECT
+public:
+ ComposerPageCharsetTab( QWidget * parent=0, const char * name=0 );
+ QString helpAnchor() const;
+
+ void save();
+
+private slots:
+ void slotVerifyCharset(QString&);
+
+private:
+ //virtual void doLoadFromGlobalSettings();
+ virtual void doLoadOther();
+ //FIXME virtual void doResetToDefaultsOther();
+
+private:
+ SimpleStringListEditor *mCharsetListEditor;
+ QCheckBox *mKeepReplyCharsetCheck;
+};
+
+class ComposerPageHeadersTab : public ConfigModuleTab {
+ Q_OBJECT
+public:
+ ComposerPageHeadersTab( QWidget * parent=0, const char * name=0 );
+ QString helpAnchor() const;
+
+ void save();
+
+private slots:
+ void slotMimeHeaderSelectionChanged();
+ void slotMimeHeaderNameChanged( const QString & );
+ void slotMimeHeaderValueChanged( const QString & );
+ void slotNewMimeHeader();
+ void slotRemoveMimeHeader();
+
+private:
+ //virtual void doLoadFromGlobalSettings();
+ virtual void doLoadOther();
+ //FIXME virtual void doResetToDefaultsOther();
+
+private:
+ QCheckBox *mCreateOwnMessageIdCheck;
+ QLineEdit *mMessageIdSuffixEdit;
+ QRegExpValidator *mMessageIdSuffixValidator;
+ QListView *mTagList;
+ QPushButton *mRemoveHeaderButton;
+ QLineEdit *mTagNameEdit;
+ QLineEdit *mTagValueEdit;
+ QLabel *mTagNameLabel;
+ QLabel *mTagValueLabel;
+};
+
+class ComposerPageAttachmentsTab : public ConfigModuleTab {
+ Q_OBJECT
+public:
+ ComposerPageAttachmentsTab( QWidget * parent=0, const char * name=0 );
+ QString helpAnchor() const;
+
+ void save();
+
+private slots:
+ void slotOutlookCompatibleClicked();
+
+private:
+ virtual void doLoadFromGlobalSettings();
+ //FIXME virtual void doResetToDefaultsOther();
+
+private:
+ QCheckBox *mOutlookCompatibleCheck;
+ QCheckBox *mMissingAttachmentDetectionCheck;
+ SimpleStringListEditor *mAttachWordsListEditor;
+};
+
+class KDE_EXPORT ComposerPage : public ConfigModuleWithTabs {
+ Q_OBJECT
+public:
+ ComposerPage( QWidget * parent=0, const char * name=0 );
+
+ QString helpAnchor() const;
+
+ // hrmpf. moc doesn't like nested classes with slots/signals...:
+ typedef ComposerPageGeneralTab GeneralTab;
+ typedef ComposerPagePhrasesTab PhrasesTab;
+ typedef ComposerPageTemplatesTab TemplatesTab;
+ typedef ComposerPageCustomTemplatesTab CustomTemplatesTab;
+ typedef ComposerPageSubjectTab SubjectTab;
+ typedef ComposerPageCharsetTab CharsetTab;
+ typedef ComposerPageHeadersTab HeadersTab;
+ typedef ComposerPageAttachmentsTab AttachmentsTab;
+
+private:
+ GeneralTab *mGeneralTab;
+ PhrasesTab *mPhrasesTab;
+ TemplatesTab *mTemplatesTab;
+ CustomTemplatesTab *mCustomTemplatesTab;
+ SubjectTab *mSubjectTab;
+ CharsetTab *mCharsetTab;
+ HeadersTab *mHeadersTab;
+ AttachmentsTab *mAttachmentsTab;
+};
+
+//
+//
+// SecurityPage
+//
+//
+
+class SecurityPageGeneralTab : public ConfigModuleTab {
+ Q_OBJECT
+public:
+ SecurityPageGeneralTab( QWidget * parent=0, const char * name=0 );
+ QString helpAnchor() const;
+
+ void save();
+ void installProfile( KConfig * profile );
+
+private:
+ //virtual void doLoadFromGlobalSettings();
+ virtual void doLoadOther();
+ //FIXME virtual void doResetToDefaultsOther();
+
+private:
+ QCheckBox *mExternalReferences;
+ QCheckBox *mHtmlMailCheck;
+ QCheckBox *mNoMDNsWhenEncryptedCheck;
+ QButtonGroup *mMDNGroup;
+ QButtonGroup *mOrigQuoteGroup;
+ QCheckBox *mAutomaticallyImportAttachedKeysCheck;
+ QCheckBox *mAlwaysDecrypt;
+};
+
+
+class SecurityPageComposerCryptoTab : public ConfigModuleTab {
+ Q_OBJECT
+public:
+ SecurityPageComposerCryptoTab( QWidget * parent=0, const char * name=0 );
+
+ QString helpAnchor() const;
+
+ void save();
+ void installProfile( KConfig * profile );
+
+private:
+ //virtual void doLoadFromGlobalSettings();
+ virtual void doLoadOther();
+ //FIXME virtual void doResetToDefaultsOther();
+
+private:
+ ComposerCryptoConfiguration* mWidget;
+};
+
+class SecurityPageWarningTab : public ConfigModuleTab {
+ Q_OBJECT
+public:
+ SecurityPageWarningTab( QWidget * parent=0, const char * name=0 );
+
+ QString helpAnchor() const;
+
+ void save();
+ void installProfile( KConfig * profile );
+
+private slots:
+ void slotReenableAllWarningsClicked();
+
+private:
+ //virtual void doLoadFromGlobalSettings();
+ virtual void doLoadOther();
+ //FIXME virtual void doResetToDefaultsOther();
+
+private:
+ WarningConfiguration* mWidget;
+};
+
+class SecurityPageSMimeTab : public ConfigModuleTab, public DCOPObject {
+ Q_OBJECT
+ K_DCOP
+public:
+ SecurityPageSMimeTab( QWidget * parent=0, const char * name=0 );
+ ~SecurityPageSMimeTab();
+
+ QString helpAnchor() const;
+
+ // Can't use k_dcop here. dcopidl can't parse this file, dcopidlng has a namespace bug.
+ void save();
+ void installProfile( KConfig * profile );
+
+private slots:
+ void slotUpdateHTTPActions();
+
+private:
+ //virtual void doLoadFromGlobalSettings();
+ virtual void doLoadOther();
+ //FIXME virtual void doResetToDefaultsOther();
+
+private:
+ SMimeConfiguration* mWidget;
+ Kleo::CryptoConfig* mConfig;
+};
+
+class SecurityPageCryptPlugTab : public ConfigModuleTab
+{
+ Q_OBJECT
+public:
+ SecurityPageCryptPlugTab( QWidget * parent = 0, const char* name = 0 );
+ ~SecurityPageCryptPlugTab();
+
+ QString helpAnchor() const;
+
+ void save();
+
+private:
+ virtual void doLoadOther();
+ //virtual void doResetToDefaultsOther();
+
+private:
+ Kleo::BackendConfigWidget * mBackendConfig;
+};
+
+class KDE_EXPORT SecurityPage : public ConfigModuleWithTabs {
+ Q_OBJECT
+public:
+ SecurityPage( QWidget * parent=0, const char * name=0 );
+
+ QString helpAnchor() const;
+
+ // OpenPGP tab is special:
+ void installProfile( KConfig * profile );
+
+ typedef SecurityPageGeneralTab GeneralTab;
+ typedef SecurityPageComposerCryptoTab ComposerCryptoTab;
+ typedef SecurityPageWarningTab WarningTab;
+ typedef SecurityPageSMimeTab SMimeTab;
+ typedef SecurityPageCryptPlugTab CryptPlugTab;
+
+private:
+ GeneralTab *mGeneralTab;
+ ComposerCryptoTab *mComposerCryptoTab;
+ WarningTab *mWarningTab;
+ SMimeTab *mSMimeTab;
+ CryptPlugTab *mCryptPlugTab;
+};
+
+
+//
+//
+// MiscPage
+//
+//
+
+class MiscPageFolderTab : public ConfigModuleTab {
+ Q_OBJECT
+public:
+ MiscPageFolderTab( QWidget * parent=0, const char * name=0 );
+
+ void save();
+ QString helpAnchor() const;
+
+private:
+ virtual void doLoadFromGlobalSettings();
+ virtual void doLoadOther();
+ //FIXME virtual void doResetToDefaultsOther();
+
+private:
+ QCheckBox *mEmptyFolderConfirmCheck;
+ QCheckBox *mExcludeImportantFromExpiry;
+ QComboBox *mLoopOnGotoUnread;
+ QComboBox *mMailboxPrefCombo;
+ QComboBox *mActionEnterFolder;
+ QCheckBox *mEmptyTrashCheck;
+#ifdef HAVE_INDEXLIB
+ QCheckBox *mIndexingEnabled;
+#endif
+ QCheckBox *mDelayedMarkAsRead;
+ KIntSpinBox *mDelayedMarkTime;
+ QCheckBox *mShowPopupAfterDnD;
+ KMail::FolderRequester *mOnStartupOpenFolder;
+ QComboBox *mQuotaCmbBox;
+};
+
+class MiscPageGroupwareTab : public ConfigModuleTab {
+ Q_OBJECT
+public:
+ MiscPageGroupwareTab( QWidget * parent=0, const char * name=0 );
+ void save();
+ QString helpAnchor() const;
+
+private slots:
+ void slotStorageFormatChanged( int );
+ void slotLegacyBodyInvitesToggled( bool on );
+
+private:
+ virtual void doLoadFromGlobalSettings();
+
+private:
+ QCheckBox* mEnableGwCB;
+ QCheckBox* mEnableImapResCB;
+
+ QWidget* mBox;
+ QVBox* gBox;
+
+ QComboBox* mStorageFormatCombo;
+ QComboBox* mLanguageCombo;
+
+ QLabel* mFolderComboLabel;
+ QWidgetStack* mFolderComboStack;
+ KMail::FolderRequester* mFolderCombo; // in the widgetstack
+ KMail::AccountComboBox* mAccountCombo; // in the widgetstack
+
+ QCheckBox* mHideGroupwareFolders;
+ QCheckBox* mOnlyShowGroupwareFolders;
+ QCheckBox* mSyncImmediately;
+ QCheckBox* mDeleteInvitations;
+
+ QCheckBox* mLegacyMangleFromTo;
+ QCheckBox* mLegacyBodyInvites;
+ QCheckBox* mExchangeCompatibleInvitations;
+ QCheckBox* mAutomaticSending;
+};
+
+class KDE_EXPORT MiscPage : public ConfigModuleWithTabs {
+ Q_OBJECT
+public:
+ MiscPage( QWidget * parent=0, const char * name=0 );
+ QString helpAnchor() const;
+
+ typedef MiscPageFolderTab FolderTab;
+ typedef MiscPageGroupwareTab GroupwareTab;
+
+private:
+ FolderTab * mFolderTab;
+ GroupwareTab * mGroupwareTab;
+};
+
+//
+//
+// further helper classes:
+//
+//
+
+class ListView : public KListView {
+ Q_OBJECT
+public:
+ ListView( QWidget *parent=0, const char *name=0, int visibleItem=10 );
+ void resizeColums();
+
+ void setVisibleItem( int visibleItem, bool updateSize=true );
+ virtual QSize sizeHint() const;
+
+protected:
+ virtual void resizeEvent( QResizeEvent *e );
+ virtual void showEvent( QShowEvent *e );
+
+private:
+ int mVisibleItem;
+};
+
+
+#endif // _CONFIGURE_DIALOG_PRIVATE_H_
diff --git a/kmail/copyfolderjob.cpp b/kmail/copyfolderjob.cpp
new file mode 100644
index 00000000..63402e2b
--- /dev/null
+++ b/kmail/copyfolderjob.cpp
@@ -0,0 +1,283 @@
+/**
+ * Copyright (c) 2005 Till Adam <adam@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#include "copyfolderjob.h"
+#include "folderstorage.h"
+#include "kmacctcachedimap.h"
+#include "kmfoldercachedimap.h"
+#include "kmfolder.h"
+#include "kmfolderdir.h"
+#include "kmfoldertype.h"
+#include "kmfoldermgr.h"
+#include "kmcommands.h"
+#include "kmmsgbase.h"
+#include "undostack.h"
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <config.h>
+
+using namespace KMail;
+
+CopyFolderJob::CopyFolderJob( FolderStorage* const storage, KMFolderDir* const newParent )
+ : FolderJob( 0, tOther, (storage ? storage->folder() : 0) ),
+ mStorage( storage ), mNewParent( newParent ),
+ mNewFolder( 0 ), mChildFolderNodeIterator( *mStorage->folder()->createChildFolder() ),
+ mNextChildFolder( 0 )
+{
+ mStorage->open( "copyfolder" );
+}
+
+CopyFolderJob::~CopyFolderJob()
+{
+ kdDebug(5006) << k_funcinfo << endl;
+ if ( mNewFolder )
+ mNewFolder->setMoveInProgress( false );
+ if ( mStorage )
+ {
+ mStorage->folder()->setMoveInProgress( false );
+ mStorage->close( "copyfolder" );
+ }
+}
+
+/*
+ * The basic strategy is to first create the target folder, then copy all the mail
+ * from the source to the target folder, then recurse for each of the folder's children
+ */
+void CopyFolderJob::execute()
+{
+ if ( createTargetDir() ) {
+ copyMessagesToTargetDir();
+ }
+}
+
+void CopyFolderJob::copyMessagesToTargetDir()
+{
+ // Hmmmm. Tasty hack. Can I have fries with that?
+ mStorage->blockSignals( true );
+ // move all messages to the new folder
+ QPtrList<KMMsgBase> msgList;
+ for ( int i = 0; i < mStorage->count(); i++ )
+ {
+ const KMMsgBase* msgBase = mStorage->getMsgBase( i );
+ assert( msgBase );
+ msgList.append( msgBase );
+ }
+ if ( msgList.count() == 0 ) {
+ mStorage->blockSignals( false );
+ // ### be careful, after slotCopyNextChild() the source folder
+ // (including mStorage) might already be deleted!
+ slotCopyNextChild(); // no contents, check subfolders
+ } else {
+ KMCommand *command = new KMCopyCommand( mNewFolder, msgList );
+ connect( command, SIGNAL( completed( KMCommand * ) ),
+ this, SLOT( slotCopyCompleted( KMCommand * ) ) );
+ command->start();
+ }
+}
+
+void CopyFolderJob::slotCopyCompleted( KMCommand* command )
+{
+ kdDebug(5006) << k_funcinfo << (command?command->result():0) << endl;
+ disconnect( command, SIGNAL( completed( KMCommand * ) ),
+ this, SLOT( slotCopyCompleted( KMCommand * ) ) );
+
+ mStorage->blockSignals( false );
+
+ if ( command && command->result() != KMCommand::OK ) {
+ rollback();
+ return;
+ }
+ // if we have children, recurse
+ if ( mStorage->folder()->child() ) {
+ slotCopyNextChild();
+ } else {
+ emit folderCopyComplete( true );
+ deleteLater();
+ }
+}
+
+void CopyFolderJob::slotCopyNextChild( bool success )
+{
+ //kdDebug(5006) << k_funcinfo << endl;
+ if ( mNextChildFolder )
+ mNextChildFolder->close( "copyfolder" ); // refcount
+ // previous sibling failed
+ if ( !success ) {
+ kdDebug(5006) << "Failed to copy one subfolder, let's not continue: " << mNewFolder->prettyURL() << endl;
+ rollback();
+ emit folderCopyComplete( false );
+ deleteLater();
+ }
+
+ KMFolderNode* node = mChildFolderNodeIterator.current();
+ while ( node && node->isDir() ) {
+ ++mChildFolderNodeIterator;
+ node = mChildFolderNodeIterator.current();
+ }
+ if ( node ) {
+ mNextChildFolder = static_cast<KMFolder*>(node);
+ ++mChildFolderNodeIterator;
+ } else {
+ // no more children, we are done
+ emit folderCopyComplete( true );
+ deleteLater();
+ return;
+ }
+
+ KMFolderDir * const dir = mNewFolder->createChildFolder();
+ if ( !dir ) {
+ kdDebug(5006) << "Failed to create subfolders of: " << mNewFolder->prettyURL() << endl;
+ emit folderCopyComplete( false );
+ deleteLater();
+ return;
+ }
+ // let it do its thing and report back when we are ready to do the next sibling
+ mNextChildFolder->open( "copyfolder" ); // refcount
+ FolderJob* job = new CopyFolderJob( mNextChildFolder->storage(), dir);
+ connect( job, SIGNAL( folderCopyComplete( bool ) ),
+ this, SLOT( slotCopyNextChild( bool ) ) );
+ job->start();
+}
+
+
+// FIXME factor into CreateFolderJob and make async, so it works with online imap
+// (create folder code taken from newfolderdialog.cpp)
+bool CopyFolderJob::createTargetDir()
+{
+ // get the default mailbox type
+ KConfig * const config = KMKernel::config();
+ KConfigGroupSaver saver(config, "General");
+ int deftype = config->readNumEntry("default-mailbox-format", 1);
+ if ( deftype < 0 || deftype > 1 ) deftype = 1;
+
+ // the type of the new folder
+ KMFolderType typenew =
+ ( deftype == 0 ) ? KMFolderTypeMbox : KMFolderTypeMaildir;
+ if ( mNewParent->owner() )
+ typenew = mNewParent->owner()->folderType();
+
+ bool success = false, waitForFolderCreation = false;
+
+ if ( mNewParent->owner() && mNewParent->owner()->folderType() == KMFolderTypeImap ) {
+ KMFolderImap* selectedStorage = static_cast<KMFolderImap*>( mNewParent->owner()->storage() );
+ KMAcctImap *anAccount = selectedStorage->account();
+ // check if a connection is available BEFORE creating the folder
+ if (anAccount->makeConnection() == ImapAccountBase::Connected) {
+ mNewFolder = kmkernel->imapFolderMgr()->createFolder( mStorage->folder()->name(), false, typenew, mNewParent );
+ if ( mNewFolder ) {
+ QString imapPath;
+ imapPath = anAccount->createImapPath( selectedStorage->imapPath(), mStorage->folder()->name() );
+ KMFolderImap* newStorage = static_cast<KMFolderImap*>( mNewFolder->storage() );
+ connect( selectedStorage, SIGNAL(folderCreationResult(const QString&, bool)),
+ this, SLOT(folderCreationDone(const QString&, bool)) );
+ selectedStorage->createFolder(mStorage->folder()->name(), QString::null); // create it on the server
+ newStorage->initializeFrom( selectedStorage, imapPath, QString::null );
+ static_cast<KMFolderImap*>(mNewParent->owner()->storage())->setAccount( selectedStorage->account() );
+ waitForFolderCreation = true;
+ success = true;
+ }
+ }
+ } else if ( mNewParent->owner() && mNewParent->owner()->folderType() == KMFolderTypeCachedImap ) {
+ mNewFolder = kmkernel->dimapFolderMgr()->createFolder( mStorage->folder()->name(), false, typenew, mNewParent );
+ if ( mNewFolder ) {
+ KMFolderCachedImap* selectedStorage = static_cast<KMFolderCachedImap*>( mNewParent->owner()->storage() );
+ KMFolderCachedImap* newStorage = static_cast<KMFolderCachedImap*>( mNewFolder->storage() );
+ newStorage->initializeFrom( selectedStorage );
+ success = true;
+ }
+ } else {
+ // local folder
+ mNewFolder = kmkernel->folderMgr()->createFolder(mStorage->folder()->name(), false, typenew, mNewParent );
+ if ( mNewFolder )
+ success = true;
+ }
+
+ if ( !success ) {
+ kdWarning(5006) << k_funcinfo << "could not create folder" << endl;
+ emit folderCopyComplete( false );
+ deleteLater();
+ return false;
+ }
+
+ mNewFolder->setMoveInProgress( true );
+ mStorage->folder()->setMoveInProgress( true );
+
+ // inherit the folder type
+ // FIXME we should probably copy over most if not all settings
+ mNewFolder->storage()->setContentsType( mStorage->contentsType(), true /*quiet*/ );
+ mNewFolder->storage()->writeConfig();
+ kdDebug(5006)<< "CopyJob::createTargetDir - " << mStorage->folder()->idString()
+ << " |=> " << mNewFolder->idString() << endl;
+ return !waitForFolderCreation;
+}
+
+
+void CopyFolderJob::rollback()
+{
+ // copy failed - rollback the last transaction
+// kmkernel->undoStack()->undo();
+ // .. and delete the new folder
+ if ( mNewFolder ) {
+ if ( mNewFolder->folderType() == KMFolderTypeImap )
+ {
+ kmkernel->imapFolderMgr()->remove( mNewFolder );
+ } else if ( mNewFolder->folderType() == KMFolderTypeCachedImap )
+ {
+ // tell the account (see KMFolderCachedImap::listDirectory2)
+ KMFolderCachedImap* folder = static_cast<KMFolderCachedImap*>(mNewFolder->storage());
+ KMAcctCachedImap* acct = folder->account();
+ if ( acct )
+ acct->addDeletedFolder( folder->imapPath() );
+ kmkernel->dimapFolderMgr()->remove( mNewFolder );
+ } else if ( mNewFolder->folderType() == KMFolderTypeSearch )
+ {
+ // invalid
+ kdWarning(5006) << k_funcinfo << "cannot remove a search folder" << endl;
+ } else {
+ kmkernel->folderMgr()->remove( mNewFolder );
+ }
+ }
+
+ emit folderCopyComplete( false );
+ deleteLater();
+}
+
+void CopyFolderJob::folderCreationDone(const QString & name, bool success)
+{
+ if ( mStorage->folder()->name() != name )
+ return; // not our business
+ kdDebug(5006) << k_funcinfo << success << endl;
+
+ if ( !success ) {
+ rollback();
+ } else {
+ copyMessagesToTargetDir();
+ }
+}
+#include "copyfolderjob.moc"
diff --git a/kmail/copyfolderjob.h b/kmail/copyfolderjob.h
new file mode 100644
index 00000000..a23aa5d0
--- /dev/null
+++ b/kmail/copyfolderjob.h
@@ -0,0 +1,110 @@
+/**
+ * Copyright (c) 2005 Till Adam <adam@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+#ifndef COPYFOLDERJOB_H
+#define COPYFOLDERJOB_H
+
+#include "folderjob.h"
+
+#include <qguardedptr.h>
+
+class FolderStorage;
+class KMFolderDir;
+class KMFolder;
+class KMCommand;
+
+class KMFolderNode;
+
+namespace KMail {
+
+/**
+ * Copy a hierarchy of folders somewhere else in the folder tree. Currently
+ * online imap folders are not supported as target folders, and the same is
+ * true for search folders where it does not make much sense for them to be
+ * target folders.
+ */
+class CopyFolderJob : public FolderJob
+{
+ Q_OBJECT
+public:
+ /**
+ * Create a new job
+ * @param storage of the folder that should be copied
+ * @param newParent the target parent folder
+ */
+ CopyFolderJob( FolderStorage* const storage, KMFolderDir* const newParent = 0 );
+
+ virtual ~CopyFolderJob();
+
+ virtual void execute();
+
+ /**
+ Returns the newly created target folder.
+ */
+ KMFolder* targetFolder() const { return mNewFolder; }
+
+protected slots:
+
+ /** Create the target directory under the new parent. Returns success or failure.*/
+ bool createTargetDir();
+
+ /** Copy all messages from the original folder to mNewFolder */
+ void copyMessagesToTargetDir();
+
+ /** Called when the CopyCommand has either succesfully completed copying
+ * the contents of our folder to the new location or failed. */
+ void slotCopyCompleted( KMCommand *command );
+
+ /** Called when the previous sibling's copy operation completed.
+ * @param success indicates whether the last copy was successful. */
+ void slotCopyNextChild( bool success = true );
+
+ /** Called when one of the operations of the foldre itself or one of it's
+ * child folders failed and the already created target folder needs to be
+ * removed again. */
+ void rollback();
+
+ /**
+ Called when the online IMAP folder creation finished.
+ */
+ void folderCreationDone( const QString &name, bool success );
+
+signals:
+ /** Emitted when the job is done, check the success bool */
+ void folderCopyComplete( bool success );
+
+protected:
+ QGuardedPtr<FolderStorage> const mStorage;
+ KMFolderDir* const mNewParent;
+ QGuardedPtr<KMFolder> mNewFolder;
+ QPtrListIterator<KMFolderNode> mChildFolderNodeIterator;
+ KMFolder* mNextChildFolder;
+};
+
+} // namespace KMail
+
+#endif /* COPYFOLDERJOB_H */
diff --git a/kmail/cr22-app-kmaillight.png b/kmail/cr22-app-kmaillight.png
new file mode 100644
index 00000000..69c0bf9a
--- /dev/null
+++ b/kmail/cr22-app-kmaillight.png
Binary files differ
diff --git a/kmail/cr32-app-kmaillight.png b/kmail/cr32-app-kmaillight.png
new file mode 100644
index 00000000..4bb4340c
--- /dev/null
+++ b/kmail/cr32-app-kmaillight.png
Binary files differ
diff --git a/kmail/csshelper.cpp b/kmail/csshelper.cpp
new file mode 100644
index 00000000..4fa2588c
--- /dev/null
+++ b/kmail/csshelper.cpp
@@ -0,0 +1,93 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ csshelper.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "csshelper.h"
+#include "kmkernel.h"
+
+#include "globalsettings.h"
+
+#include <kconfig.h>
+
+#include <qcolor.h>
+#include <qfont.h>
+
+namespace KMail {
+
+ CSSHelper::CSSHelper( const QPaintDeviceMetrics &pdm ) :
+ KPIM::CSSHelper( pdm )
+ {
+ KConfig * config = KMKernel::config();
+
+ KConfigGroup reader( config, "Reader" );
+ KConfigGroup fonts( config, "Fonts" );
+ KConfigGroup pixmaps( config, "Pixmaps" );
+
+ mRecycleQuoteColors = reader.readBoolEntry( "RecycleQuoteColors", false );
+
+ if ( !reader.readBoolEntry( "defaultColors", true ) ) {
+ mForegroundColor = reader.readColorEntry("ForegroundColor",&mForegroundColor);
+ mLinkColor = reader.readColorEntry("LinkColor",&mLinkColor);
+ mVisitedLinkColor = reader.readColorEntry("FollowedColor",&mVisitedLinkColor);
+ mBackgroundColor = reader.readColorEntry("BackgroundColor",&mBackgroundColor);
+ cPgpEncrH = reader.readColorEntry( "PGPMessageEncr", &cPgpEncrH );
+ cPgpOk1H = reader.readColorEntry( "PGPMessageOkKeyOk", &cPgpOk1H );
+ cPgpOk0H = reader.readColorEntry( "PGPMessageOkKeyBad", &cPgpOk0H );
+ cPgpWarnH = reader.readColorEntry( "PGPMessageWarn", &cPgpWarnH );
+ cPgpErrH = reader.readColorEntry( "PGPMessageErr", &cPgpErrH );
+ cHtmlWarning = reader.readColorEntry( "HTMLWarningColor", &cHtmlWarning );
+ for ( int i = 0 ; i < 3 ; ++i ) {
+ const QString key = "QuotedText" + QString::number( i+1 );
+ mQuoteColor[i] = reader.readColorEntry( key, &mQuoteColor[i] );
+ }
+ }
+
+ if ( !fonts.readBoolEntry( "defaultFonts", true ) ) {
+ mBodyFont = fonts.readFontEntry( "body-font", &mBodyFont);
+ mPrintFont = fonts.readFontEntry( "print-font", &mPrintFont);
+ mFixedFont = fonts.readFontEntry( "fixed-font", &mFixedFont);
+ mFixedPrintFont = mFixedFont; // FIXME when we have a separate fixed print font
+ QFont defaultFont = mBodyFont;
+ defaultFont.setItalic( true );
+ for ( int i = 0 ; i < 3 ; ++i ) {
+ const QString key = QString( "quote%1-font" ).arg( i+1 );
+ mQuoteFont[i] = fonts.readFontEntry( key, &defaultFont );
+ }
+ }
+
+ mShrinkQuotes = GlobalSettings::self()->shrinkQuotes();
+
+ mBackingPixmapStr = pixmaps.readPathEntry("Readerwin");
+ mBackingPixmapOn = !mBackingPixmapStr.isEmpty();
+
+ recalculatePGPColors();
+ }
+} // namespace KMail
+
diff --git a/kmail/csshelper.h b/kmail/csshelper.h
new file mode 100644
index 00000000..eaa82391
--- /dev/null
+++ b/kmail/csshelper.h
@@ -0,0 +1,46 @@
+/* -*- c++ -*-
+ csshelper.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_CSSHELPER_H__
+#define __KMAIL_CSSHELPER_H__
+
+#include <libkdepim/csshelper.h>
+
+namespace KMail {
+
+ class CSSHelper : public KPIM::CSSHelper {
+ public:
+ CSSHelper( const QPaintDeviceMetrics &pdm );
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_CSSHELPER_H__
diff --git a/kmail/custommimeheader.kcfg b/kmail/custommimeheader.kcfg
new file mode 100644
index 00000000..11949bf7
--- /dev/null
+++ b/kmail/custommimeheader.kcfg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
+ http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
+ <kcfgfile name="kmailrc">
+ <parameter name="pairId" />
+ </kcfgfile>
+
+ <group name="Mime #$(pairId)">
+ <entry name="CustHeaderName" type="String" key="name" />
+ <entry name="CustHeaderValue" type="String" key="value" />
+ </group>
+
+</kcfg>
diff --git a/kmail/custommimeheader.kcfgc b/kmail/custommimeheader.kcfgc
new file mode 100644
index 00000000..5265473e
--- /dev/null
+++ b/kmail/custommimeheader.kcfgc
@@ -0,0 +1,5 @@
+File=custommimeheader.kcfg
+ClassName=CustomMimeHeader
+Mutators=true
+ItemAccessors=true
+SetUserTexts=true
diff --git a/kmail/customtemplates.cpp b/kmail/customtemplates.cpp
new file mode 100644
index 00000000..4030862c
--- /dev/null
+++ b/kmail/customtemplates.cpp
@@ -0,0 +1,376 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ * kmail: KDE mail client
+ * This file: Copyright (C) 2006 Dmitry Morozhnikov
+ *
+ * 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 <config.h>
+
+#include <klocale.h>
+#include <kglobal.h>
+#include <qpopupmenu.h>
+#include <qpushbutton.h>
+#include <qtextedit.h>
+#include <qlineedit.h>
+#include <qtoolbox.h>
+#include <kdebug.h>
+#include <qfont.h>
+#include <kiconloader.h>
+#include <kpushbutton.h>
+#include <klistview.h>
+#include <klineedit.h>
+#include <qcombobox.h>
+#include <kshortcut.h>
+#include <kmessagebox.h>
+#include <kkeybutton.h>
+#include <kactivelabel.h>
+
+#include "customtemplates_base.h"
+#include "customtemplates_kfg.h"
+#include "globalsettings.h"
+#include "kmkernel.h"
+#include "kmmainwidget.h"
+
+#include "customtemplates.h"
+
+CustomTemplates::CustomTemplates( QWidget *parent, const char *name )
+ :CustomTemplatesBase( parent, name ), mCurrentItem( 0 )
+{
+ QFont f = KGlobalSettings::fixedFont();
+ mEdit->setFont( f );
+
+ mAdd->setIconSet( BarIconSet( "add", KIcon::SizeSmall ) );
+ mRemove->setIconSet( BarIconSet( "remove", KIcon::SizeSmall ) );
+
+ mList->setColumnWidth( 0, 50 );
+ mList->setColumnWidth( 1, 100 );
+
+ mEditFrame->setEnabled( false );
+
+ connect( mEdit, SIGNAL( textChanged() ),
+ this, SLOT( slotTextChanged( void ) ) );
+
+ connect( mInsertCommand, SIGNAL( insertCommand(QString, int) ),
+ this, SLOT( slotInsertCommand(QString, int) ) );
+
+ connect( mAdd, SIGNAL( clicked() ),
+ this, SLOT( slotAddClicked() ) );
+ connect( mRemove, SIGNAL( clicked() ),
+ this, SLOT( slotRemoveClicked() ) );
+ connect( mList, SIGNAL( selectionChanged() ),
+ this, SLOT( slotListSelectionChanged() ) );
+ connect( mType, SIGNAL( activated( int ) ),
+ this, SLOT( slotTypeActivated( int ) ) );
+
+ connect( mKeyButton, SIGNAL( capturedShortcut( const KShortcut& ) ),
+ this, SLOT( slotShortcutCaptured( const KShortcut& ) ) );
+
+ mReplyPix = KIconLoader().loadIcon( "mail_reply", KIcon::Small );
+ mReplyAllPix = KIconLoader().loadIcon( "mail_replyall", KIcon::Small );
+ mForwardPix = KIconLoader().loadIcon( "mail_forward", KIcon::Small );
+
+ mType->clear();
+ mType->insertItem( QPixmap(), i18n( "Message->", "Universal" ), TUniversal );
+ mType->insertItem( mReplyPix, i18n( "Message->", "Reply" ), TReply );
+ mType->insertItem( mReplyAllPix, i18n( "Message->", "Reply to All" ), TReplyAll );
+ mType->insertItem( mForwardPix, i18n( "Message->", "Forward" ), TForward );
+
+ QString help =
+ i18n( "<qt>"
+ "<p>Here you can add, edit, and delete custom message "
+ "templates to use when you compose a reply or forwarding message. "
+ "Create the custom template by selecting it using the right mouse "
+ " button menu or toolbar menu. Also, you can bind a keyboard "
+ "combination to the template for faster operations.</p>"
+ "<p>Message templates support substitution commands "
+ "by simple typing them or selecting them from menu "
+ "<i>Insert command</i>.</p>"
+ "<p>There are four types of custom templates: used to "
+ "<i>Reply</i>, <i>Reply to All</i>, <i>Forward</i>, and "
+ "<i>Universal</i> which can be used for all kind of operations. "
+ "You cannot bind keyboard shortcut to <i>Universal</i> templates.</p>"
+ "</qt>" );
+ mHelp->setText( i18n( "<a href=\"whatsthis:%1\">How does this work?</a>" ).arg( help ) );
+}
+
+CustomTemplates::~CustomTemplates()
+{
+ QDictIterator<CustomTemplateItem> it(mItemList);
+ for ( ; it.current() ; ++it ) {
+ CustomTemplateItem *vitem = mItemList.take( it.currentKey() );
+ if ( vitem ) {
+ delete vitem;
+ }
+ }
+}
+
+QString CustomTemplates::indexToType( int index )
+{
+ QString typeStr;
+ switch ( index ) {
+ case TUniversal:
+ // typeStr = i18n( "Any" ); break;
+ break;
+/* case TNewMessage:
+ typeStr = i18n( "New Message" ); break;*/
+ case TReply:
+ typeStr = i18n( "Message->", "Reply" ); break;
+ case TReplyAll:
+ typeStr = i18n( "Message->", "Reply to All" ); break;
+ case TForward:
+ typeStr = i18n( "Message->", "Forward" ); break;
+ default:
+ typeStr = i18n( "Message->", "Unknown" ); break;
+ }
+ return typeStr;
+}
+
+void CustomTemplates::slotTextChanged()
+{
+ emit changed();
+}
+
+void CustomTemplates::load()
+{
+ QStringList list = GlobalSettings::self()->customTemplates();
+ for ( QStringList::iterator it = list.begin(); it != list.end(); ++it ) {
+ CTemplates t(*it);
+ // QString typeStr = indexToType( t.type() );
+ QString typeStr;
+ KShortcut shortcut( t.shortcut() );
+ CustomTemplateItem *vitem =
+ new CustomTemplateItem( *it, t.content(),
+ shortcut,
+ static_cast<Type>( t.type() ) );
+ mItemList.insert( *it, vitem );
+ QListViewItem *item = new QListViewItem( mList, typeStr, *it, t.content() );
+ switch ( t.type() ) {
+ case TReply:
+ item->setPixmap( 0, mReplyPix );
+ break;
+ case TReplyAll:
+ item->setPixmap( 0, mReplyAllPix );
+ break;
+ case TForward:
+ item->setPixmap( 0, mForwardPix );
+ break;
+ default:
+ item->setPixmap( 0, QPixmap() );
+ item->setText( 0, indexToType( t.type() ) );
+ break;
+ };
+ }
+}
+
+void CustomTemplates::save()
+{
+ if ( mCurrentItem ) {
+ CustomTemplateItem *vitem = mItemList[ mCurrentItem->text( 1 ) ];
+ if ( vitem ) {
+ vitem->mContent = mEdit->text();
+ vitem->mShortcut = mKeyButton->shortcut();
+ }
+ }
+ QStringList list;
+ QListViewItemIterator lit( mList );
+ while ( lit.current() ) {
+ list.append( (*lit)->text( 1 ) );
+ ++lit;
+ }
+ QDictIterator<CustomTemplateItem> it( mItemList );
+ for ( ; it.current() ; ++it ) {
+ // list.append( (*it)->mName );
+ CTemplates t( (*it)->mName );
+ QString &content = (*it)->mContent;
+ if ( content.stripWhiteSpace().isEmpty() ) {
+ content = "%BLANK";
+ }
+ t.setContent( content );
+ t.setShortcut( (*it)->mShortcut.toString() );
+ t.setType( (*it)->mType );
+ t.writeConfig();
+ }
+ GlobalSettings::self()->setCustomTemplates( list );
+ GlobalSettings::self()->writeConfig();
+
+ // update kmail menus related to custom templates
+ if ( kmkernel->getKMMainWidget() )
+ kmkernel->getKMMainWidget()->updateCustomTemplateMenus();
+}
+
+void CustomTemplates::slotInsertCommand( QString cmd, int adjustCursor )
+{
+ int para, index;
+ mEdit->getCursorPosition( &para, &index );
+ mEdit->insertAt( cmd, para, index );
+
+ index += adjustCursor;
+
+ mEdit->setCursorPosition( para, index + cmd.length() );
+}
+
+void CustomTemplates::slotAddClicked()
+{
+ QString str = mName->text();
+ if ( !str.isEmpty() ) {
+ CustomTemplateItem *vitem = mItemList[ str ];
+ if ( !vitem ) {
+ vitem = new CustomTemplateItem( str, "", KShortcut::null(), TUniversal );
+ mItemList.insert( str, vitem );
+ QListViewItem *item =
+ new QListViewItem( mList, indexToType( TUniversal ), str, "" );
+ mList->setSelected( item, true );
+ mKeyButton->setEnabled( false );
+ emit changed();
+ }
+ }
+}
+
+void CustomTemplates::slotRemoveClicked()
+{
+ if ( mCurrentItem ) {
+ CustomTemplateItem *vitem = mItemList.take( mCurrentItem->text( 1 ) );
+ if ( vitem ) {
+ delete vitem;
+ }
+ delete mCurrentItem;
+ mCurrentItem = 0;
+ emit changed();
+ }
+}
+
+void CustomTemplates::slotListSelectionChanged()
+{
+ if ( mCurrentItem ) {
+ CustomTemplateItem *vitem = mItemList[ mCurrentItem->text( 1 ) ];
+ if ( vitem ) {
+ vitem->mContent = mEdit->text();
+ vitem->mShortcut = mKeyButton->shortcut();
+ }
+ }
+ QListViewItem *item = mList->selectedItem();
+ if ( item ) {
+ mEditFrame->setEnabled( true );
+ mCurrentItem = item;
+ CustomTemplateItem *vitem = mItemList[ mCurrentItem->text( 1 ) ];
+ if ( vitem ) {
+ // avoid emit changed()
+ disconnect( mEdit, SIGNAL( textChanged() ),
+ this, SLOT( slotTextChanged( void ) ) );
+
+ mEdit->setText( vitem->mContent );
+ mKeyButton->setShortcut( vitem->mShortcut, false );
+ mType->setCurrentItem( vitem->mType );
+
+ connect( mEdit, SIGNAL( textChanged() ),
+ this, SLOT( slotTextChanged( void ) ) );
+
+ if ( vitem->mType == TUniversal )
+ {
+ mKeyButton->setEnabled( false );
+ } else {
+ mKeyButton->setEnabled( true );
+ }
+ }
+ } else {
+ mEditFrame->setEnabled( false );
+ mCurrentItem = 0;
+ mEdit->clear();
+ mKeyButton->setShortcut( KShortcut::null(), false );
+ mType->setCurrentItem( 0 );
+ }
+}
+
+void CustomTemplates::slotTypeActivated( int index )
+{
+ if ( mCurrentItem ) {
+ // mCurrentItem->setText( 0, indexToType( index ) );
+ CustomTemplateItem *vitem = mItemList[ mCurrentItem->text( 1 ) ];
+ if ( !vitem ) {
+ return;
+ }
+ vitem->mType = static_cast<Type>(index);
+ switch ( vitem->mType ) {
+ case TReply:
+ mCurrentItem->setPixmap( 0, mReplyPix );
+ break;
+ case TReplyAll:
+ mCurrentItem->setPixmap( 0, mReplyAllPix );
+ break;
+ case TForward:
+ mCurrentItem->setPixmap( 0, mForwardPix );
+ break;
+ default:
+ mCurrentItem->setPixmap( 0, QPixmap() );
+ break;
+ };
+ if ( index == TUniversal )
+ {
+ mKeyButton->setEnabled( false );
+ } else {
+ mKeyButton->setEnabled( true );
+ }
+ emit changed();
+ }
+}
+
+void CustomTemplates::slotShortcutCaptured( const KShortcut &shortcut )
+{
+ KShortcut sc( shortcut );
+ if ( sc == mKeyButton->shortcut() )
+ return;
+ if ( sc.isNull() || sc.toString().isEmpty() )
+ sc.clear();
+ bool assign = true;
+ bool customused = false;
+ // check if shortcut is already used for custom templates
+ QDictIterator<CustomTemplateItem> it(mItemList);
+ for ( ; it.current() ; ++it ) {
+ if ( !mCurrentItem || (*it)->mName != mCurrentItem->text( 1 ) )
+ {
+ if ( (*it)->mShortcut == sc )
+ {
+ QString title( I18N_NOOP("Key Conflict") );
+ QString msg( I18N_NOOP("The selected shortcut is already used "
+ "for another custom template, "
+ "would you still like to continue with the assignment?" ) );
+ assign = ( KMessageBox::warningYesNo( this, msg, title )
+ == KMessageBox::Yes );
+ if ( assign )
+ {
+ (*it)->mShortcut = KShortcut::null();
+ }
+ customused = true;
+ }
+ }
+ }
+ // check if shortcut is used somewhere else
+ if ( !customused && !sc.isNull() &&
+ !( kmkernel->getKMMainWidget()->shortcutIsValid( sc ) ) ) {
+ QString title( I18N_NOOP("Key Conflict") );
+ QString msg( I18N_NOOP("The selected shortcut is already used, "
+ "would you still like to continue with the assignment?" ) );
+ assign = ( KMessageBox::warningYesNo( this, msg, title )
+ == KMessageBox::Yes );
+ }
+ if ( assign ) {
+ mKeyButton->setShortcut( sc, false );
+ emit changed();
+ }
+}
+
+#include "customtemplates.moc"
diff --git a/kmail/customtemplates.h b/kmail/customtemplates.h
new file mode 100644
index 00000000..a2b27678
--- /dev/null
+++ b/kmail/customtemplates.h
@@ -0,0 +1,92 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ * kmail: KDE mail client
+ * This file: Copyright (C) 2006 Dmitry Morozhnikov
+ *
+ * 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 <config.h>
+
+#ifndef CUSTOMTEMPLATES_H
+#define CUSTOMTEMPLATES_H
+
+#include "customtemplates_base.h"
+#include "templatesinsertcommand.h"
+
+struct CustomTemplateItem;
+typedef QDict<CustomTemplateItem> CustomTemplateItemList;
+class KShortcut;
+
+class CustomTemplates : public CustomTemplatesBase
+{
+ Q_OBJECT
+
+ public:
+
+ enum Type { TUniversal, TReply, TReplyAll, TForward };
+
+ public:
+
+ CustomTemplates( QWidget *parent = 0, const char *name = 0 );
+ ~CustomTemplates();
+
+ void load();
+ void save();
+
+ QString indexToType( int index );
+
+ public slots:
+
+ void slotInsertCommand( QString cmd, int adjustCursor = 0 );
+
+ void slotTextChanged();
+
+ void slotAddClicked();
+ void slotRemoveClicked();
+ void slotListSelectionChanged();
+ void slotTypeActivated( int index );
+ void slotShortcutCaptured( const KShortcut &shortcut );
+
+ signals:
+
+ void changed();
+
+ protected:
+
+ QListViewItem *mCurrentItem;
+ CustomTemplateItemList mItemList;
+
+ QPixmap mReplyPix;
+ QPixmap mReplyAllPix;
+ QPixmap mForwardPix;
+
+};
+
+struct CustomTemplateItem
+{
+ CustomTemplateItem() {}
+ CustomTemplateItem( const QString &name,
+ const QString &content,
+ KShortcut &shortcut,
+ CustomTemplates::Type type ) :
+ mName( name ), mContent( content ), mShortcut(shortcut), mType( type ) {}
+
+ QString mName, mContent;
+ KShortcut mShortcut;
+ CustomTemplates::Type mType;
+};
+
+#endif // CUSTOMTEMPLATES_H
diff --git a/kmail/customtemplates_base.ui b/kmail/customtemplates_base.ui
new file mode 100644
index 00000000..5a4cb9d4
--- /dev/null
+++ b/kmail/customtemplates_base.ui
@@ -0,0 +1,303 @@
+<!DOCTYPE UI><UI version="3.3" stdsetdef="1">
+<class>CustomTemplatesBase</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>Form1</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>600</width>
+ <height>480</height>
+ </rect>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QSplitter">
+ <property name="name">
+ <cstring>splitter2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout9</cstring>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout8</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <widget class="KLineEdit">
+ <property name="name">
+ <cstring>mName</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>3</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
+ </property>
+ </widget>
+ <widget class="KPushButton">
+ <property name="name">
+ <cstring>mAdd</cstring>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ </widget>
+ <widget class="KPushButton">
+ <property name="name">
+ <cstring>mRemove</cstring>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ <widget class="QListView">
+ <column>
+ <property name="text">
+ <string>Type</string>
+ </property>
+ <property name="clickable">
+ <bool>true</bool>
+ </property>
+ <property name="resizable">
+ <bool>true</bool>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Name</string>
+ </property>
+ <property name="clickable">
+ <bool>true</bool>
+ </property>
+ <property name="resizable">
+ <bool>true</bool>
+ </property>
+ </column>
+ <property name="name">
+ <cstring>mList</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>7</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="allColumnsShowFocus">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </vbox>
+ </widget>
+ <widget class="QFrame">
+ <property name="name">
+ <cstring>mEditFrame</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>7</hsizetype>
+ <vsizetype>5</vsizetype>
+ <horstretch>12</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Raised</enum>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <widget class="QTextEdit">
+ <property name="name">
+ <cstring>mEdit</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>7</hsizetype>
+ <vsizetype>7</vsizetype>
+ <horstretch>3</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="textFormat">
+ <enum>PlainText</enum>
+ </property>
+ <property name="wordWrap">
+ <enum>NoWrap</enum>
+ </property>
+ </widget>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout4</cstring>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QComboBox" row="1" column="2">
+ <item>
+ <property name="text">
+ <string>Universal</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Reply</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Reply to All</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Forward</string>
+ </property>
+ </item>
+ <property name="name">
+ <cstring>mType</cstring>
+ </property>
+ </widget>
+ <widget class="QLabel" row="0" column="1">
+ <property name="name">
+ <cstring>textLabel1_2</cstring>
+ </property>
+ <property name="text">
+ <string>Shortc&amp;ut:</string>
+ </property>
+ <property name="alignment">
+ <set>AlignVCenter|AlignRight</set>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>mKeyButton</cstring>
+ </property>
+ </widget>
+ <widget class="QLabel" row="1" column="1">
+ <property name="name">
+ <cstring>textLabel1</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>5</vsizetype>
+ <horstretch>1</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&amp;Template type:</string>
+ </property>
+ <property name="alignment">
+ <set>AlignVCenter|AlignRight</set>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>mType</cstring>
+ </property>
+ </widget>
+ <widget class="TemplatesInsertCommand" row="1" column="0">
+ <property name="name">
+ <cstring>mInsertCommand</cstring>
+ </property>
+ </widget>
+ <widget class="KKeyButton" row="0" column="2">
+ <property name="name">
+ <cstring>mKeyButton</cstring>
+ </property>
+ <property name="text">
+ <string>None</string>
+ </property>
+ </widget>
+ <widget class="KActiveLabel" row="0" column="0">
+ <property name="name">
+ <cstring>mHelp</cstring>
+ </property>
+ <property name="text">
+ <string>How does this work?</string>
+ </property>
+ </widget>
+ </grid>
+ </widget>
+ </vbox>
+ </widget>
+ </widget>
+ </vbox>
+</widget>
+<customwidgets>
+ <customwidget>
+ <class>TemplatesInsertCommand</class>
+ <header location="local">templatesinsertcommand.h</header>
+ <sizehint>
+ <width>50</width>
+ <height>10</height>
+ </sizehint>
+ <container>0</container>
+ <sizepolicy>
+ <hordata>5</hordata>
+ <verdata>5</verdata>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ <pixmap>image0</pixmap>
+ <signal>insertCommand(int)</signal>
+ </customwidget>
+</customwidgets>
+<images>
+ <image name="image0">
+ <data format="PNG" length="807">89504e470d0a1a0a0000000d49484452000000100000001008060000001ff3ff61000002ee49444154388d7d93cd6b5c6514c67fe7bdef9d8f24d349c66412a203b1b4282d21140918824816418a0b454d75e1a28ab8c8dfe1c25d70e34e0812ea4644d13610c43a1121b5d84cab6943486227499d869ae6633e32f7de79ef71d58644f0593dabdf39cf7938a2aa4c4f4f9b300c0b954ae56ca55249876128aa2a9c90738eadadada0542afd954ea7cb636363a19d9f9f4755cff6f4a53e4d67ed78f7b37ebb8b05e3810818017d0a68f1dcc36463a7ea8a4b8bdb9fcccdcdddf6eaf5bae4f3f9f7bb7a773faee94fed81b983a6d6d9ab6eb2f5f73affec6de1a536c9e4d7497696c9f63cf237d66b85b5e570cf3977c7aeaeae9a42a130d03d506bdbaddda3d1805faec6dc5a68e2a208e30124187a39c9ebefa6c8f544f84949a9ea69e75cd60641c0e1e1a1b8b849142ac51f226ece1f32fc4a86f31732f86db076178ad70e20765cfae8142207009eaafac639270022c2fd15a1b470c8f81b59defed0637024c44596c269cb3b1fe4585c68f2c76f0e23064000639e5c5804caab2d8c24187ed5c3265adc5bf4b9f2f91e377eae73fe254b6fbfc7ea7283588f9a314756089b31ed5925dd61b87b2bc5d75fd4a91d843c7fa68d5caf23fb4c8246fdc9f01300e3412e9f60f7313c285b6edf0c696bf338f3620773df3de2ea15d8de0ce92f2448f8472bd8a700515e18146e5c3714afd5197f3343ae3b463ce1cbcf94efbf7a48b6cb303462595c3801505514280c182ebed5ce37333bec3f8e387721859f80da7e8c114714097b3be65804eb799e562a95e641358e4d47cb8cbc66c9f5f6f1eb8f55967eafa140bedfe7e27b7dac2c4588a7ec6cb7622004629b4c2629954a7fda6c6b3f5f08baac1f61bd2683c3867a358931865339c8e69a9c1b323cb87fc0fa72d81091351189646262426667677b620e2fabea25942e504144fef34d80735a752daeab9a6f7ddf5f9162b1c8e4e4a45f2e974f0541d0a3aa19c03f16f4b862a061adddc96432bba2aaccccccc8d4d494b7b1b1910c8220a1aae6ff00d6da56676767303a3a1afd0b29d2596f22d0b7b20000000049454e44ae426082</data>
+ </image>
+</images>
+<slots>
+ <slot>mAdd_clicked()</slot>
+ <slot>mRemove_clicked()</slot>
+</slots>
+<layoutdefaults spacing="6" margin="11"/>
+<includehints>
+ <includehint>klineedit.h</includehint>
+ <includehint>kpushbutton.h</includehint>
+ <includehint>kpushbutton.h</includehint>
+ <includehint>templatesinsertcommand.h</includehint>
+ <includehint>kkeybutton.h</includehint>
+ <includehint>kactivelabel.h</includehint>
+</includehints>
+</UI>
diff --git a/kmail/customtemplates_kfg.kcfg b/kmail/customtemplates_kfg.kcfg
new file mode 100644
index 00000000..f5689810
--- /dev/null
+++ b/kmail/customtemplates_kfg.kcfg
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
+ http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
+ <kcfgfile name="kmailrc">
+ <parameter name="name" />
+ </kcfgfile>
+
+ <group name="CTemplates #$(name)">
+ <entry name="Content" type="String" key="Content">
+ <label>Template content</label>
+ <whatsthis></whatsthis>
+ <default></default>
+ </entry>
+ <entry name="Shortcut" type="String" key="Shortcut">
+ <label>Template shortcut</label>
+ <whatsthis></whatsthis>
+ <default></default>
+ </entry>
+ <entry name="Type" type="Int" key="Type">
+ <label>Template type</label>
+ <whatsthis></whatsthis>
+ <default>0</default>
+ </entry>
+ </group>
+
+</kcfg>
diff --git a/kmail/customtemplates_kfg.kcfgc b/kmail/customtemplates_kfg.kcfgc
new file mode 100644
index 00000000..c31ef7d1
--- /dev/null
+++ b/kmail/customtemplates_kfg.kcfgc
@@ -0,0 +1,5 @@
+File=customtemplates_kfg.kcfg
+ClassName=CTemplates
+Mutators=true
+ItemAccessors=true
+SetUserTexts=true
diff --git a/kmail/dcopimap.desktop b/kmail/dcopimap.desktop
new file mode 100644
index 00000000..c1ed3737
--- /dev/null
+++ b/kmail/dcopimap.desktop
@@ -0,0 +1,55 @@
+[Desktop Entry]
+Type=ServiceType
+X-KDE-ServiceType=DCOP/ResourceBackend/IMAP
+Comment=Mail program with a DCOP interface
+Comment[af]=E-pos program met 'n DCOP koppelvlak
+Comment[be]=Паштовая праграма з DCOP інтэрфэйсам
+Comment[bg]=Клиент на електронна поща за KDE
+Comment[bs]=Mail program sa DCOP interfejsom
+Comment[ca]=Un programa de correu amb una interfície DCOP
+Comment[cs]=Poštvní program s DCOP rozhraním
+Comment[cy]=Rhaglen ebost gyda rhyngwyneb DCOP
+Comment[da]=E-mail-program med en DCOP-grænseflade
+Comment[de]=Mail-Programm mit DCOP-Schnittstelle
+Comment[el]=Πρόγραμμα mail με ένα περιβάλλον χρήσης DCOP
+Comment[eo]=Retposhtprogramo kun DCOP-interfaco
+Comment[es]=Programa de correo con un interfaz DCOP
+Comment[et]=E-posti rakendus DCOP-liidesega
+Comment[eu]=DCOP interfazedun posta programa
+Comment[fa]=برنامۀ نامه با واسط DCOP
+Comment[fi]=Sähköpostisovellus DCOP-rajapinnalla
+Comment[fr]=Programme de messagerie avec une interface DCOP
+Comment[fy]=E-postprogramma mei in DCOP-ynterface
+Comment[gl]=Programa de correo-e cunha interface DCOP
+Comment[hi]=डीकॉप इंटरफेस सहित एक डाक प्रोग्राम
+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
+Comment[mk]=Програма за е-пошта, со DCOP-интерфејс
+Comment[ms]=Program Mel dengan antara muka DCOP
+Comment[nb]=E-postprogram med DCOP grensesnitt
+Comment[nds]=Nettpostprogramm mit DCOP-Koppelsteed
+Comment[ne]=DCOP इन्टरफेसमा मेल कार्यक्रम
+Comment[nl]=E-mailprogramma met een DCOP-interface
+Comment[nn]=E-postprogram med eit DCOP-grensesnitt
+Comment[pl]=Program pocztowy z interfejsem DCOP
+Comment[pt]=Um programa de correio electrónico com uma interface DCOP
+Comment[pt_BR]=Programa de e-mail com uma interface DCOP
+Comment[ru]=Почтовая программа с интерфейсом DCOP
+Comment[se]=E-boastaprográmma mas lea DCOP-lakta
+Comment[sk]=Poštový program s DCOP rozhraním
+Comment[sl]=Poštni program z vmesnikom DCOP
+Comment[sr]=Поштански програм са DCOP интерфејсом
+Comment[sr@Latn]=Poštanski program sa DCOP interfejsom
+Comment[sv]=E-postprogram med DCOP-gränssnitt
+Comment[ta]=dcop முகப்பு கூடிய அஞ்சல் நிரல்
+Comment[tg]=Барномаи почтавӣ бо интерфейси DCOP
+Comment[tr]=DCOP arayüzü ile posta programı
+Comment[uk]=Програма для роботи з ел. поштою з інтерфейсом DCOP
+Comment[zh_CN]=具有 DCOP 接口的邮件程序
+Comment[zh_TW]=有 DCOP 介面的郵件程式
diff --git a/kmail/dcopmail.desktop b/kmail/dcopmail.desktop
new file mode 100644
index 00000000..6772a1f6
--- /dev/null
+++ b/kmail/dcopmail.desktop
@@ -0,0 +1,55 @@
+[Desktop Entry]
+Type=ServiceType
+X-KDE-ServiceType=DCOP/Mailer
+Comment=Mail program with a DCOP interface
+Comment[af]=E-pos program met 'n DCOP koppelvlak
+Comment[be]=Паштовая праграма з DCOP інтэрфэйсам
+Comment[bg]=Клиент на електронна поща за KDE
+Comment[bs]=Mail program sa DCOP interfejsom
+Comment[ca]=Un programa de correu amb una interfície DCOP
+Comment[cs]=Poštvní program s DCOP rozhraním
+Comment[cy]=Rhaglen ebost gyda rhyngwyneb DCOP
+Comment[da]=E-mail-program med en DCOP-grænseflade
+Comment[de]=Mail-Programm mit DCOP-Schnittstelle
+Comment[el]=Πρόγραμμα mail με ένα περιβάλλον χρήσης DCOP
+Comment[eo]=Retposhtprogramo kun DCOP-interfaco
+Comment[es]=Programa de correo con un interfaz DCOP
+Comment[et]=E-posti rakendus DCOP-liidesega
+Comment[eu]=DCOP interfazedun posta programa
+Comment[fa]=برنامۀ نامه با واسط DCOP
+Comment[fi]=Sähköpostisovellus DCOP-rajapinnalla
+Comment[fr]=Programme de messagerie avec une interface DCOP
+Comment[fy]=E-postprogramma mei in DCOP-ynterface
+Comment[gl]=Programa de correo-e cunha interface DCOP
+Comment[hi]=डीकॉप इंटरफेस सहित एक डाक प्रोग्राम
+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
+Comment[mk]=Програма за е-пошта, со DCOP-интерфејс
+Comment[ms]=Program Mel dengan antara muka DCOP
+Comment[nb]=E-postprogram med DCOP grensesnitt
+Comment[nds]=Nettpostprogramm mit DCOP-Koppelsteed
+Comment[ne]=DCOP इन्टरफेसमा मेल कार्यक्रम
+Comment[nl]=E-mailprogramma met een DCOP-interface
+Comment[nn]=E-postprogram med eit DCOP-grensesnitt
+Comment[pl]=Program pocztowy z interfejsem DCOP
+Comment[pt]=Um programa de correio electrónico com uma interface DCOP
+Comment[pt_BR]=Programa de e-mail com uma interface DCOP
+Comment[ru]=Почтовая программа с интерфейсом DCOP
+Comment[se]=E-boastaprográmma mas lea DCOP-lakta
+Comment[sk]=Poštový program s DCOP rozhraním
+Comment[sl]=Poštni program z vmesnikom DCOP
+Comment[sr]=Поштански програм са DCOP интерфејсом
+Comment[sr@Latn]=Poštanski program sa DCOP interfejsom
+Comment[sv]=E-postprogram med DCOP-gränssnitt
+Comment[ta]=dcop முகப்பு கூடிய அஞ்சல் நிரல்
+Comment[tg]=Барномаи почтавӣ бо интерфейси DCOP
+Comment[tr]=DCOP arayüzü ile posta programı
+Comment[uk]=Програма для роботи з ел. поштою з інтерфейсом DCOP
+Comment[zh_CN]=具有 DCOP 接口的邮件程序
+Comment[zh_TW]=有 DCOP 介面的郵件程式
diff --git a/kmail/dcoptest.cpp b/kmail/dcoptest.cpp
new file mode 100644
index 00000000..ecf51207
--- /dev/null
+++ b/kmail/dcoptest.cpp
@@ -0,0 +1,40 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <kdebug.h>
+#include <kapplication.h>
+#include <dcopclient.h>
+#include <kcmdlineargs.h>
+#include "aboutdata.h"
+
+#include <kmailIface_stub.h>
+#include <mailcomposerIface_stub.h>
+
+int main(int argc,char **argv)
+{
+ kdDebug(5006) << "Test KMail DCOP interface." << endl;
+
+ KAboutData aboutData( "testKMailDCOP",
+ "Test for KMail DCOP interface", "0.0" );
+ KCmdLineArgs::init(argc, argv, &aboutData);
+ KApplication app;
+ app.dcopClient()->attach();
+
+ KMailIface_stub kmailStub("kmail","KMailIface");
+
+ kmailStub.openComposer("to 1","","","First test","simple openComp call",0,
+ KURL());
+
+ DCOPRef ref = kmailStub.openComposer("to 2","","","Second test",
+ "DCOP ref call",0);
+ MailComposerIface_stub composerStub(ref.app(),ref.object());
+ QCString data = "BEGIN:VCALENDAR\nEND:VCALENDAR";
+ composerStub.addAttachment("test.ics","7bit",data,"text","calendar","method",
+ "publish","attachement;");
+ composerStub.send(2);
+
+ kdDebug(5006) << "testDCOP done." << endl;
+
+ return 0;
+}
diff --git a/kmail/dictionarycombobox.cpp b/kmail/dictionarycombobox.cpp
new file mode 100644
index 00000000..59e0affd
--- /dev/null
+++ b/kmail/dictionarycombobox.cpp
@@ -0,0 +1,149 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ dictionarycombobox.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2003 Ingo Kloecker <kloecker@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "dictionarycombobox.h"
+
+#include <ksconfig.h>
+#include <kdebug.h>
+
+#include <qstringlist.h>
+
+namespace KMail {
+
+ DictionaryComboBox::DictionaryComboBox( QWidget * parent, const char * name )
+ : QComboBox( false, parent, name ),
+ mSpellConfig( 0 ),
+ mDefaultDictionary( 0 )
+ {
+ reloadCombo();
+ connect( this, SIGNAL( activated( int ) ),
+ this, SLOT( slotDictionaryChanged( int ) ) );
+ connect( this, SIGNAL( dictionaryChanged( int ) ),
+ mSpellConfig, SLOT( sSetDictionary( int ) ) );
+ }
+
+ DictionaryComboBox::~DictionaryComboBox()
+ {
+ delete mSpellConfig;
+ mSpellConfig = 0;
+ }
+
+ QString DictionaryComboBox::currentDictionaryName() const
+ {
+ return currentText();
+ }
+
+ QString DictionaryComboBox::currentDictionary() const
+ {
+ QString dict = mDictionaries[ currentItem() ];
+ if ( dict.isEmpty() )
+ return "<default>";
+ else
+ return dict;
+ }
+
+ void DictionaryComboBox::setCurrentByDictionaryName( const QString & name )
+ {
+ if ( name.isEmpty() )
+ return;
+
+ for ( int i = 0; i < count(); ++i ) {
+ if ( text( i ) == name ) {
+ if ( i != currentItem() ) {
+ setCurrentItem( i );
+ slotDictionaryChanged( i );
+ }
+ return;
+ }
+ }
+ }
+
+ void DictionaryComboBox::setCurrentByDictionary( const QString & dictionary )
+ {
+ if ( !dictionary.isEmpty() ) {
+ // first handle the special case of the default dictionary
+ if ( dictionary == "<default>" ) {
+ if ( 0 != currentItem() ) {
+ setCurrentItem( 0 );
+ slotDictionaryChanged( 0 );
+ }
+ return;
+ }
+
+ int i = 0;
+ for ( QStringList::ConstIterator it = mDictionaries.begin();
+ it != mDictionaries.end();
+ ++it, ++i ) {
+ if ( *it == dictionary ) {
+ if ( i != currentItem() ) {
+ setCurrentItem( i );
+ slotDictionaryChanged( i );
+ }
+ return;
+ }
+ }
+ }
+
+ // If dictionary is empty or doesn't exist fall back to the global default
+ if ( mDefaultDictionary != currentItem() ) {
+ setCurrentItem( mDefaultDictionary );
+ slotDictionaryChanged( mDefaultDictionary );
+ }
+ }
+
+ KSpellConfig* DictionaryComboBox::spellConfig() const
+ {
+ return mSpellConfig;
+ }
+
+ void DictionaryComboBox::reloadCombo()
+ {
+ delete mSpellConfig;
+ mSpellConfig = new KSpellConfig( 0, 0, 0, false );
+ mSpellConfig->fillDicts( this, &mDictionaries );
+ mDefaultDictionary = currentItem();
+ }
+
+ void DictionaryComboBox::slotDictionaryChanged( int idx )
+ {
+ kdDebug( 5006 ) << "DictionaryComboBox::slotDictionaryChanged( " << idx
+ << " )" << endl;
+ emit dictionaryChanged( mDictionaries[idx] );
+ emit dictionaryChanged( idx );
+ }
+
+} // namespace KMail
+
+#include "dictionarycombobox.moc"
diff --git a/kmail/dictionarycombobox.h b/kmail/dictionarycombobox.h
new file mode 100644
index 00000000..0e7a3e19
--- /dev/null
+++ b/kmail/dictionarycombobox.h
@@ -0,0 +1,83 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ dictionarycombobox.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2003 Ingo Kloecker <kloecker@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef _KMAIL_DICTIONARYCOMBOBOX_H_
+#define _KMAIL_DICTIONARYCOMBOBOX_H_
+
+#include <qcombobox.h>
+
+class KSpellConfig;
+class QStringList;
+class QString;
+
+namespace KMail {
+
+ /**
+ * @short A combo box for selecting the dictionary used for spell checking.
+ * @author Ingo Kloecker <kloecker@kde.org>
+ **/
+
+ class DictionaryComboBox : public QComboBox {
+ Q_OBJECT
+ public:
+ DictionaryComboBox( QWidget * parent=0, const char * name=0 );
+ ~DictionaryComboBox();
+
+ QString currentDictionaryName() const;
+ QString currentDictionary() const;
+ void setCurrentByDictionaryName( const QString & dictionaryName );
+ void setCurrentByDictionary( const QString & dictionary );
+
+ KSpellConfig* spellConfig() const;
+
+ signals:
+ /** @em Emitted whenever the current dictionary changes. Either
+ * by user intervention or on setCurrentByDictionaryName() or on
+ * setCurrentByDictionary().
+ **/
+ void dictionaryChanged( const QString & dictionary );
+ void dictionaryChanged( int );
+
+ protected slots:
+ void slotDictionaryChanged( int );
+
+ protected:
+ void reloadCombo();
+
+ protected:
+ QStringList mDictionaries;
+ KSpellConfig* mSpellConfig;
+ int mDefaultDictionary;
+ };
+
+} // namespace KMail
+
+#endif // _KMAIL_DICTIONARYCOMBOBOX_H_
diff --git a/kmail/distributionlistdialog.cpp b/kmail/distributionlistdialog.cpp
new file mode 100644
index 00000000..9c8d6fd2
--- /dev/null
+++ b/kmail/distributionlistdialog.cpp
@@ -0,0 +1,261 @@
+/*
+ This file is part of KMail.
+
+ Copyright (c) 2005 Cornelius Schumacher <schumacher@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include <config.h> // for KDEPIM_NEW_DISTRLISTS
+
+#include "distributionlistdialog.h"
+
+#include <libemailfunctions/email.h>
+#include <kabc/resource.h>
+#include <kabc/stdaddressbook.h>
+#include <kabc/distributionlist.h>
+
+#ifdef KDEPIM_NEW_DISTRLISTS
+#include <libkdepim/distributionlist.h>
+#endif
+
+#include <klistview.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kmessagebox.h>
+#include <kinputdialog.h>
+
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qlineedit.h>
+
+class DistributionListItem : public QCheckListItem
+{
+ public:
+ DistributionListItem( QListView *list )
+ : QCheckListItem( list, QString::null, CheckBox )
+ {
+ }
+
+ void setAddressee( const KABC::Addressee &a, const QString &email )
+ {
+ mIsTransient = false;
+ init( a, email );
+ }
+
+ void setTransientAddressee( const KABC::Addressee &a, const QString &email )
+ {
+ mIsTransient = true;
+ init( a, email );
+ }
+
+ void init( const KABC::Addressee &a, const QString &email )
+ {
+ mAddressee = a;
+ mEmail = email;
+ setText( 1, mAddressee.realName() );
+ setText( 2, mEmail );
+ }
+
+ KABC::Addressee addressee() const
+ {
+ return mAddressee;
+ }
+
+ QString email() const
+ {
+ return mEmail;
+ }
+
+ bool isTransient() const
+ {
+ return mIsTransient;
+ }
+
+ private:
+ KABC::Addressee mAddressee;
+ QString mEmail;
+ bool mIsTransient;
+};
+
+
+DistributionListDialog::DistributionListDialog( QWidget *parent )
+ : KDialogBase( Plain, i18n("Save Distribution List"), User1 | Cancel,
+ User1, parent, 0, false, false, i18n("Save List") )
+{
+ QFrame *topFrame = plainPage();
+
+ QBoxLayout *topLayout = new QVBoxLayout( topFrame );
+ topLayout->setSpacing( spacingHint() );
+
+ QBoxLayout *titleLayout = new QHBoxLayout( topLayout );
+
+ QLabel *label = new QLabel( i18n("Name:"), topFrame );
+ titleLayout->addWidget( label );
+
+ mTitleEdit = new QLineEdit( topFrame );
+ titleLayout->addWidget( mTitleEdit );
+ mTitleEdit->setFocus();
+
+ mRecipientsList = new KListView( topFrame );
+ mRecipientsList->addColumn( QString::null );
+ mRecipientsList->addColumn( i18n("Name") );
+ mRecipientsList->addColumn( i18n("Email") );
+ topLayout->addWidget( mRecipientsList );
+}
+
+void DistributionListDialog::setRecipients( const Recipient::List &recipients )
+{
+ Recipient::List::ConstIterator it;
+ for( it = recipients.begin(); it != recipients.end(); ++it ) {
+ QStringList emails = KPIM::splitEmailAddrList( (*it).email() );
+ QStringList::ConstIterator it2;
+ for( it2 = emails.begin(); it2 != emails.end(); ++it2 ) {
+ QString name;
+ QString email;
+ KABC::Addressee::parseEmailAddress( *it2, name, email );
+ if ( !email.isEmpty() ) {
+ DistributionListItem *item = new DistributionListItem( mRecipientsList );
+ KABC::Addressee::List addressees =
+ KABC::StdAddressBook::self( true )->findByEmail( email );
+ if ( addressees.isEmpty() ) {
+ KABC::Addressee a;
+ a.setNameFromString( name );
+ a.insertEmail( email );
+ item->setTransientAddressee( a, email );
+ item->setOn( true );
+ } else {
+ KABC::Addressee::List::ConstIterator it3;
+ for( it3 = addressees.begin(); it3 != addressees.end(); ++it3 ) {
+ item->setAddressee( *it3, email );
+ if ( it3 == addressees.begin() ) item->setOn( true );
+ }
+ }
+ }
+ }
+ }
+}
+
+void DistributionListDialog::slotUser1()
+{
+ bool isEmpty = true;
+
+ KABC::AddressBook *ab = KABC::StdAddressBook::self( true );
+
+ QListViewItem *i = mRecipientsList->firstChild();
+ while( i ) {
+ DistributionListItem *item = static_cast<DistributionListItem *>( i );
+ if ( item->isOn() ) {
+ isEmpty = false;
+ break;
+ }
+ i = i->nextSibling();
+ }
+
+ if ( isEmpty ) {
+ KMessageBox::information( this,
+ i18n("There are no recipients in your list. "
+ "First select some recipients, "
+ "then try again.") );
+ return;
+ }
+
+#ifndef KDEPIM_NEW_DISTRLISTS
+ KABC::DistributionListManager manager( ab );
+ manager.load();
+#endif
+
+ QString name = mTitleEdit->text();
+
+ if ( name.isEmpty() ) {
+ bool ok = false;
+ name = KInputDialog::getText( i18n("New Distribution List"),
+ i18n("Please enter name:"), QString::null, &ok, this );
+ if ( !ok || name.isEmpty() )
+ return;
+ }
+
+#ifdef KDEPIM_NEW_DISTRLISTS
+ if ( !KPIM::DistributionList::findByName( ab, name ).isEmpty() ) {
+#else
+ if ( manager.list( name ) ) {
+#endif
+ KMessageBox::information( this,
+ i18n( "<qt>Distribution list with the given name <b>%1</b> "
+ "already exists. Please select a different name.</qt>" ).arg( name ) );
+ return;
+ }
+
+#ifdef KDEPIM_NEW_DISTRLISTS
+ KPIM::DistributionList dlist;
+ dlist.setName( name );
+
+ i = mRecipientsList->firstChild();
+ while( i ) {
+ DistributionListItem *item = static_cast<DistributionListItem *>( i );
+ if ( item->isOn() ) {
+ kdDebug() << " " << item->addressee().fullEmail() << endl;
+ if ( item->isTransient() ) {
+ ab->insertAddressee( item->addressee() );
+ }
+ if ( item->email() == item->addressee().preferredEmail() ) {
+ dlist.insertEntry( item->addressee() );
+ } else {
+ dlist.insertEntry( item->addressee(), item->email() );
+ }
+ }
+ i = i->nextSibling();
+ }
+
+ ab->insertAddressee( dlist );
+#else
+ KABC::DistributionList *dlist = new KABC::DistributionList( &manager, name );
+ i = mRecipientsList->firstChild();
+ while( i ) {
+ DistributionListItem *item = static_cast<DistributionListItem *>( i );
+ if ( item->isOn() ) {
+ kdDebug() << " " << item->addressee().fullEmail() << endl;
+ if ( item->isTransient() ) {
+ ab->insertAddressee( item->addressee() );
+ }
+ if ( item->email() == item->addressee().preferredEmail() ) {
+ dlist->insertEntry( item->addressee() );
+ } else {
+ dlist->insertEntry( item->addressee(), item->email() );
+ }
+ }
+ i = i->nextSibling();
+ }
+#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 )
+ kdWarning(5006) << k_funcinfo << " Couldn't save new addresses in the distribution list just created to the address book" << endl;
+
+#ifndef KDEPIM_NEW_DISTRLISTS
+ manager.save();
+#endif
+
+ close();
+}
diff --git a/kmail/distributionlistdialog.h b/kmail/distributionlistdialog.h
new file mode 100644
index 00000000..092cf84c
--- /dev/null
+++ b/kmail/distributionlistdialog.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of KMail.
+
+ Copyright (c) 2005 Cornelius Schumacher <schumacher@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef DISTRIBUTIONLISTDIALOG_H
+#define DISTRIBUTIONLISTDIALOG_H
+
+#include <kdialogbase.h>
+
+#include "recipientseditor.h"
+
+class QLineEdit;
+class KListView;
+
+class DistributionListDialog : public KDialogBase
+{
+ public:
+ DistributionListDialog( QWidget *parent );
+
+ void setRecipients( const Recipient::List & );
+
+ protected:
+ void slotUser1();
+
+ private:
+ QLineEdit *mTitleEdit;
+ KListView *mRecipientsList;
+};
+
+#endif
diff --git a/kmail/editorwatcher.cpp b/kmail/editorwatcher.cpp
new file mode 100644
index 00000000..9e1bb150
--- /dev/null
+++ b/kmail/editorwatcher.cpp
@@ -0,0 +1,181 @@
+/*
+ Copyright (c) 2007 Volker Krause <vkrause@kde.org>
+
+ 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 "editorwatcher.h"
+
+#include <config.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kopenwith.h>
+#include <kprocess.h>
+#include <kuserprofile.h>
+
+#include <qsocketnotifier.h>
+
+#include <cassert>
+
+// inotify stuff taken from kdelibs/kio/kio/kdirwatch.cpp
+#ifdef HAVE_SYS_INOTIFY
+#include <sys/ioctl.h>
+#include <sys/inotify.h>
+#include <fcntl.h>
+#elif HAVE_INOTIFY
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/syscall.h>
+#include <linux/types.h>
+// Linux kernel headers are documented to not compile
+#define _S390_BITOPS_H
+#include <linux/inotify.h>
+
+static inline int inotify_init (void)
+{
+ return syscall (__NR_inotify_init);
+}
+
+static inline int inotify_add_watch (int fd, const char *name, __u32 mask)
+{
+ return syscall (__NR_inotify_add_watch, fd, name, mask);
+}
+
+static inline int inotify_rm_watch (int fd, __u32 wd)
+{
+ return syscall (__NR_inotify_rm_watch, fd, wd);
+}
+#endif
+
+using namespace KMail;
+
+EditorWatcher::EditorWatcher(const KURL & url, const QString &mimeType, bool openWith, QObject * parent) :
+ QObject( parent ),
+ mUrl( url ),
+ mMimeType( mimeType ),
+ mOpenWith( openWith ),
+ mEditor( 0 ),
+ mHaveInotify( false ),
+ mFileOpen( false ),
+ mEditorRunning( false ),
+ mFileModified( true ), // assume the worst unless we know better
+ mDone( false )
+{
+ assert( mUrl.isLocalFile() );
+ connect( &mTimer, SIGNAL(timeout()), SLOT(checkEditDone()) );
+}
+
+bool EditorWatcher::start()
+{
+ // find an editor
+ KURL::List list;
+ list.append( mUrl );
+ KService::Ptr offer = KServiceTypeProfile::preferredService( mMimeType, "Application" );
+ if ( mOpenWith || !offer ) {
+ KOpenWithDlg dlg( list, i18n("Edit with:"), QString::null, 0 );
+ if ( !dlg.exec() )
+ return false;
+ offer = dlg.service();
+ if ( !offer )
+ return false;
+ }
+
+#ifdef HAVE_INOTIFY
+ // monitor file
+ mInotifyFd = inotify_init();
+ if ( mInotifyFd > 0 ) {
+ mInotifyWatch = inotify_add_watch( mInotifyFd, mUrl.path().latin1(), IN_CLOSE | IN_OPEN | IN_MODIFY );
+ if ( mInotifyWatch >= 0 ) {
+ QSocketNotifier *sn = new QSocketNotifier( mInotifyFd, QSocketNotifier::Read, this );
+ connect( sn, SIGNAL(activated(int)), SLOT(inotifyEvent()) );
+ mHaveInotify = true;
+ mFileModified = false;
+ }
+ } else {
+ kdWarning(5006) << k_funcinfo << "Failed to activate INOTIFY!" << endl;
+ }
+#endif
+
+ // start the editor
+ QStringList params = KRun::processDesktopExec( *offer, list, false );
+ mEditor = new KProcess( this );
+ *mEditor << params;
+ connect( mEditor, SIGNAL(processExited(KProcess*)), SLOT(editorExited()) );
+ if ( !mEditor->start() )
+ return false;
+ mEditorRunning = true;
+
+ mEditTime.start();
+ return true;
+}
+
+void EditorWatcher::inotifyEvent()
+{
+ assert( mHaveInotify );
+#ifdef HAVE_INOTIFY
+ int pending = -1;
+ char buffer[4096];
+ ioctl( mInotifyFd, FIONREAD, &pending );
+ while ( pending > 0 ) {
+ int size = read( mInotifyFd, buffer, QMIN( pending, (int)sizeof(buffer) ) );
+ pending -= size;
+ if ( size < 0 )
+ break; // error
+ int offset = 0;
+ while ( size > 0 ) {
+ struct inotify_event *event = (struct inotify_event *) &buffer[offset];
+ size -= sizeof( struct inotify_event ) + event->len;
+ offset += sizeof( struct inotify_event ) + event->len;
+ if ( event->mask & IN_OPEN )
+ mFileOpen = true;
+ if ( event->mask & IN_CLOSE )
+ mFileOpen = false;
+ if ( event->mask & IN_MODIFY )
+ mFileModified = true;
+ }
+ }
+#endif
+ mTimer.start( 500, true );
+
+}
+
+void EditorWatcher::editorExited()
+{
+ mEditorRunning = false;
+ mTimer.start( 500, true );
+}
+
+void EditorWatcher::checkEditDone()
+{
+ if ( mEditorRunning || (mFileOpen && mHaveInotify) || mDone )
+ return;
+ // protect us against double-deletion by calling this method again while
+ // the subeventloop of the message box is running
+ mDone = true;
+ // 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") );
+ }
+
+ emit editDone( this );
+ deleteLater();
+}
+
+#include "editorwatcher.moc"
diff --git a/kmail/editorwatcher.h b/kmail/editorwatcher.h
new file mode 100644
index 00000000..d7fc9e3e
--- /dev/null
+++ b/kmail/editorwatcher.h
@@ -0,0 +1,77 @@
+/*
+ Copyright (c) 2007 Volker Krause <vkrause@kde.org>
+
+ 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_EDITORWATCHER_H
+#define KMAIL_EDITORWATCHER_H
+
+#include <kurl.h>
+
+#include <qdatetime.h>
+#include <qobject.h>
+#include <qtimer.h>
+
+class KProcess;
+
+namespace KMail {
+
+/**
+ Starts an editor for the given URL and emits an signal when
+ editing has been finished. Both, the editor process as well
+ as the edited file are watched to work with as many as possible
+ editors.
+*/
+class EditorWatcher : public QObject
+{
+ Q_OBJECT
+ public:
+ EditorWatcher( const KURL &url, const QString &mimeType, bool openWith, QObject *parent = 0 );
+ bool start();
+ bool fileChanged() const { return mFileModified; }
+ signals:
+ void editDone( KMail::EditorWatcher* watcher );
+
+ private slots:
+ void editorExited();
+ void inotifyEvent();
+ void checkEditDone();
+
+ private:
+ KURL mUrl;
+ QString mMimeType;
+ bool mOpenWith;
+ KProcess *mEditor;
+
+ int mInotifyFd;
+ int mInotifyWatch;
+ bool mHaveInotify;
+
+ bool mFileOpen;
+ bool mEditorRunning;
+
+ bool mFileModified;
+
+ QTimer mTimer;
+ QTime mEditTime;
+
+ bool mError;
+ bool mDone;
+};
+
+}
+
+#endif
diff --git a/kmail/encodingdetector.cpp b/kmail/encodingdetector.cpp
new file mode 100644
index 00000000..e5881d6f
--- /dev/null
+++ b/kmail/encodingdetector.cpp
@@ -0,0 +1,1377 @@
+/*
+ This file was taken from the KDE 4.x libraries and backported to Qt 3.
+
+ Copyright (C) 1999 Lars Knoll (knoll@kde.org)
+ Copyright (C) 2003 Dirk Mueller (mueller@kde.org)
+ Copyright (C) 2003 Apple Computer, Inc.
+ Copyright (C) 2007 Nick Shaforostoff (shafff@ukr.net)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+//----------------------------------------------------------------------------
+//
+// decoder for input stream
+
+#include "encodingdetector.h"
+
+#undef DECODE_DEBUG
+//#define DECODE_DEBUG
+
+#define MAX_BUFFER 16*1024
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "encodingdetector_ja_p.h"
+
+#include <qregexp.h>
+#include <qtextcodec.h>
+
+#include <kglobal.h>
+#include <kcharsets.h>
+#include <kdebug.h>
+#include <klocale.h>
+
+#include <ctype.h>
+
+// The following table was taken from libpango 1.19.3 and slightly modified.
+// Multiple scripts per language were removed and the entries were reordered so
+// that simple substring matching will work. For example, bam was put before ba
+// so that the first match will be likely the right match. Otherwise "ba" would
+// match "bam" but we would have to search on to find "bam" which is what we want.
+// The original file is called pango-script-lang-table.h
+
+/* pango-script-lang-table.h:
+ *
+ * Generated by gen-script-for-lang-new.c
+ * Date: 2007-10-26
+ * Source: fontconfig-2.4.91
+ *
+ * Do not edit. // I did. Sue me ;)
+ */
+typedef struct _PangoScriptForLang {
+ const char lang[6];
+ EncodingDetector::AutoDetectScript scripts[1];
+} PangoScriptForLang;
+
+//Unfortunately EncodingDetector does not know all scripts that Pango knows.
+//Also, using EncodingDetector::CentralEuropean for the appropriate countries
+//might give better results in some cases.
+//One especially important (many speakers/literates) omission is the lack of
+//Indian scripts.
+
+#define PANGO_SCRIPT_ARMENIAN EncodingDetector::None
+#define PANGO_SCRIPT_BENGALI EncodingDetector::None
+#define PANGO_SCRIPT_CANADIAN_ABORIGINAL EncodingDetector::None
+#define PANGO_SCRIPT_CHEROKEE EncodingDetector::None
+#define PANGO_SCRIPT_DEVANAGARI EncodingDetector::None
+#define PANGO_SCRIPT_ETHIOPIC EncodingDetector::None
+#define PANGO_SCRIPT_GUJARATI EncodingDetector::None
+#define PANGO_SCRIPT_GURMUKHI EncodingDetector::None
+#define PANGO_SCRIPT_KANNADA EncodingDetector::None
+#define PANGO_SCRIPT_KHMER EncodingDetector::None
+#define PANGO_SCRIPT_LAO EncodingDetector::None
+#define PANGO_SCRIPT_MALAYALAM EncodingDetector::None
+#define PANGO_SCRIPT_MONGOLIAN EncodingDetector::None
+#define PANGO_SCRIPT_MYANMAR EncodingDetector::None
+#define PANGO_SCRIPT_ORIYA EncodingDetector::None
+#define PANGO_SCRIPT_SINHALA EncodingDetector::None
+#define PANGO_SCRIPT_SYRIAC EncodingDetector::None
+#define PANGO_SCRIPT_TAGALOG EncodingDetector::None
+#define PANGO_SCRIPT_TAMIL EncodingDetector::None
+#define PANGO_SCRIPT_TIBETAN EncodingDetector::None
+#define PANGO_SCRIPT_TELUGU EncodingDetector::None
+
+//Instead of changing the table even more...
+#define PANGO_SCRIPT_ARABIC EncodingDetector::Arabic
+#define PANGO_SCRIPT_CYRILLIC EncodingDetector::Cyrillic
+#define PANGO_SCRIPT_GEORGIAN EncodingDetector::SouthEasternEurope
+#define PANGO_SCRIPT_GREEK EncodingDetector::Greek
+#define PANGO_SCRIPT_HEBREW EncodingDetector::Hebrew
+#define PANGO_SCRIPT_LATIN EncodingDetector::WesternEuropean
+#define PANGO_SCRIPT_THAI EncodingDetector::Thai
+
+
+static const PangoScriptForLang pango_script_for_lang[] = {
+ { "aa", { PANGO_SCRIPT_LATIN/*62*/ } },
+ { "ab", { PANGO_SCRIPT_CYRILLIC/*90*/ } },
+ { "af", { PANGO_SCRIPT_LATIN/*69*/ } },
+ { "am", { PANGO_SCRIPT_ETHIOPIC/*218*/ } },
+ { "ar", { PANGO_SCRIPT_ARABIC/*125*/ } },
+ { "as", { PANGO_SCRIPT_BENGALI/*89*/ } },
+ { "ast", { PANGO_SCRIPT_LATIN/*66*/ } },
+ { "ava", { PANGO_SCRIPT_CYRILLIC/*67*/ } },
+ { "ay", { PANGO_SCRIPT_LATIN/*60*/ } },
+ { "az-ir", { PANGO_SCRIPT_ARABIC/*129*/ } },
+ { "az", { PANGO_SCRIPT_CYRILLIC/*80*/ } }, //, PANGO_SCRIPT_LATIN/*68*/ } },
+ { "bam", { PANGO_SCRIPT_LATIN/*60*/ } },
+ { "ba", { PANGO_SCRIPT_CYRILLIC/*82*/ } },
+ { "be", { PANGO_SCRIPT_CYRILLIC/*68*/ } },
+ { "bg", { PANGO_SCRIPT_CYRILLIC/*60*/ } },
+ { "bh", { PANGO_SCRIPT_DEVANAGARI/*68*/ } },
+ { "bho", { PANGO_SCRIPT_DEVANAGARI/*68*/ } },
+ { "bi", { PANGO_SCRIPT_LATIN/*58*/ } },
+ { "bin", { PANGO_SCRIPT_LATIN/*76*/ } },
+ { "bn", { PANGO_SCRIPT_BENGALI/*89*/ } },
+ { "bo", { PANGO_SCRIPT_TIBETAN/*95*/ } },
+ { "br", { PANGO_SCRIPT_LATIN/*64*/ } },
+ { "bs", { PANGO_SCRIPT_LATIN/*62*/ } },
+ { "bua", { PANGO_SCRIPT_CYRILLIC/*70*/ } },
+ { "ca", { PANGO_SCRIPT_LATIN/*74*/ } },
+ { "ce", { PANGO_SCRIPT_CYRILLIC/*67*/ } },
+ { "chm", { PANGO_SCRIPT_CYRILLIC/*76*/ } },
+ { "chr", { PANGO_SCRIPT_CHEROKEE/*85*/ } },
+ { "ch", { PANGO_SCRIPT_LATIN/*58*/ } },
+ { "co", { PANGO_SCRIPT_LATIN/*84*/ } },
+ { "cs", { PANGO_SCRIPT_LATIN/*82*/ } },
+ { "cu", { PANGO_SCRIPT_CYRILLIC/*103*/ } },
+ { "cv", { PANGO_SCRIPT_CYRILLIC/*72*/ } }, //, PANGO_SCRIPT_LATIN/*2*/ } },
+ { "cy", { PANGO_SCRIPT_LATIN/*78*/ } },
+ { "da", { PANGO_SCRIPT_LATIN/*70*/ } },
+ { "de", { PANGO_SCRIPT_LATIN/*59*/ } },
+ { "dz", { PANGO_SCRIPT_TIBETAN/*95*/ } },
+ { "el", { PANGO_SCRIPT_GREEK/*69*/ } },
+ { "en", { PANGO_SCRIPT_LATIN/*72*/ } },
+ { "eo", { PANGO_SCRIPT_LATIN/*64*/ } },
+ { "es", { PANGO_SCRIPT_LATIN/*66*/ } },
+// { "et", { PANGO_SCRIPT_LATIN/*64*/ } },
+ { "et", { EncodingDetector::Baltic } },
+ { "eu", { PANGO_SCRIPT_LATIN/*56*/ } },
+ { "fa", { PANGO_SCRIPT_ARABIC/*129*/ } },
+ { "fi", { PANGO_SCRIPT_LATIN/*62*/ } },
+ { "fj", { PANGO_SCRIPT_LATIN/*52*/ } },
+ { "fo", { PANGO_SCRIPT_LATIN/*68*/ } },
+ { "fr", { PANGO_SCRIPT_LATIN/*84*/ } },
+ { "ful", { PANGO_SCRIPT_LATIN/*62*/ } },
+ { "fur", { PANGO_SCRIPT_LATIN/*66*/ } },
+ { "fy", { PANGO_SCRIPT_LATIN/*75*/ } },
+ { "ga", { PANGO_SCRIPT_LATIN/*80*/ } },
+ { "gd", { PANGO_SCRIPT_LATIN/*70*/ } },
+ { "gez", { PANGO_SCRIPT_ETHIOPIC/*218*/ } },
+ { "gl", { PANGO_SCRIPT_LATIN/*66*/ } },
+ { "gn", { PANGO_SCRIPT_LATIN/*70*/ } },
+ { "gu", { PANGO_SCRIPT_GUJARATI/*78*/ } },
+ { "gv", { PANGO_SCRIPT_LATIN/*54*/ } },
+ { "ha", { PANGO_SCRIPT_LATIN/*60*/ } },
+ { "haw", { PANGO_SCRIPT_LATIN/*62*/ } },
+ { "he", { PANGO_SCRIPT_HEBREW/*27*/ } },
+ { "hi", { PANGO_SCRIPT_DEVANAGARI/*68*/ } },
+ { "ho", { PANGO_SCRIPT_LATIN/*52*/ } },
+ { "hr", { PANGO_SCRIPT_LATIN/*62*/ } },
+ { "hu", { PANGO_SCRIPT_LATIN/*70*/ } },
+ { "hy", { PANGO_SCRIPT_ARMENIAN/*77*/ } },
+ { "ia", { PANGO_SCRIPT_LATIN/*52*/ } },
+ { "ibo", { PANGO_SCRIPT_LATIN/*58*/ } },
+ { "id", { PANGO_SCRIPT_LATIN/*54*/ } },
+ { "ie", { PANGO_SCRIPT_LATIN/*52*/ } },
+ { "ik", { PANGO_SCRIPT_CYRILLIC/*68*/ } },
+ { "io", { PANGO_SCRIPT_LATIN/*52*/ } },
+ { "is", { PANGO_SCRIPT_LATIN/*70*/ } },
+ { "it", { PANGO_SCRIPT_LATIN/*72*/ } },
+ { "iu", { PANGO_SCRIPT_CANADIAN_ABORIGINAL/*161*/ } },
+// { "ja", { PANGO_SCRIPT_HAN/*6356*/, PANGO_SCRIPT_KATAKANA/*88*/, PANGO_SCRIPT_HIRAGANA/*85*/ } },
+ { "ja", { EncodingDetector::Japanese } },
+ { "kaa", { PANGO_SCRIPT_CYRILLIC/*78*/ } },
+ { "ka", { PANGO_SCRIPT_GEORGIAN/*33*/ } },
+ { "ki", { PANGO_SCRIPT_LATIN/*56*/ } },
+ { "kk", { PANGO_SCRIPT_CYRILLIC/*77*/ } },
+ { "kl", { PANGO_SCRIPT_LATIN/*81*/ } },
+ { "km", { PANGO_SCRIPT_KHMER/*70*/ } },
+ { "kn", { PANGO_SCRIPT_KANNADA/*80*/ } },
+// { "ko", { PANGO_SCRIPT_HANGUL/*2443*/ } },
+ { "ko", { EncodingDetector::Korean } },
+ { "kok", { PANGO_SCRIPT_DEVANAGARI/*68*/ } },
+ { "ks", { PANGO_SCRIPT_DEVANAGARI/*68*/ } },
+ { "ku-ir", { PANGO_SCRIPT_ARABIC/*32*/ } },
+ { "ku", { PANGO_SCRIPT_CYRILLIC/*60*/ } }, //, PANGO_SCRIPT_LATIN/*4*/ } },
+ { "kum", { PANGO_SCRIPT_CYRILLIC/*66*/ } },
+ { "kv", { PANGO_SCRIPT_CYRILLIC/*70*/ } },
+ { "kw", { PANGO_SCRIPT_LATIN/*64*/ } },
+ { "ky", { PANGO_SCRIPT_CYRILLIC/*70*/ } },
+ { "la", { PANGO_SCRIPT_LATIN/*68*/ } },
+ { "lb", { PANGO_SCRIPT_LATIN/*75*/ } },
+ { "lez", { PANGO_SCRIPT_CYRILLIC/*67*/ } },
+ { "ln", { PANGO_SCRIPT_LATIN/*78*/ } },
+ { "lo", { PANGO_SCRIPT_LAO/*65*/ } },
+// { "lt", { PANGO_SCRIPT_LATIN/*70*/ } },
+ { "lt", { EncodingDetector::Baltic } },
+// { "lv", { PANGO_SCRIPT_LATIN/*78*/ } },
+ { "lv", { EncodingDetector::Baltic } },
+ { "mg", { PANGO_SCRIPT_LATIN/*56*/ } },
+ { "mh", { PANGO_SCRIPT_LATIN/*62*/ } },
+ { "mi", { PANGO_SCRIPT_LATIN/*64*/ } },
+ { "mk", { PANGO_SCRIPT_CYRILLIC/*42*/ } },
+ { "ml", { PANGO_SCRIPT_MALAYALAM/*78*/ } },
+ { "mn", { PANGO_SCRIPT_MONGOLIAN/*130*/ } },
+ { "mo", { PANGO_SCRIPT_CYRILLIC/*66*/ } }, //, PANGO_SCRIPT_LATIN/*62*/ } },
+ { "mr", { PANGO_SCRIPT_DEVANAGARI/*68*/ } },
+ { "mt", { PANGO_SCRIPT_LATIN/*72*/ } },
+ { "my", { PANGO_SCRIPT_MYANMAR/*48*/ } },
+ { "nb", { PANGO_SCRIPT_LATIN/*70*/ } },
+ { "nds", { PANGO_SCRIPT_LATIN/*59*/ } },
+ { "ne", { PANGO_SCRIPT_DEVANAGARI/*68*/ } },
+ { "nl", { PANGO_SCRIPT_LATIN/*82*/ } },
+ { "nn", { PANGO_SCRIPT_LATIN/*76*/ } },
+ { "no", { PANGO_SCRIPT_LATIN/*70*/ } },
+ { "nr", { PANGO_SCRIPT_LATIN/*52*/ } },
+ { "nso", { PANGO_SCRIPT_LATIN/*58*/ } },
+ { "ny", { PANGO_SCRIPT_LATIN/*54*/ } },
+ { "oc", { PANGO_SCRIPT_LATIN/*70*/ } },
+ { "om", { PANGO_SCRIPT_LATIN/*52*/ } },
+ { "or", { PANGO_SCRIPT_ORIYA/*79*/ } },
+ { "os", { PANGO_SCRIPT_CYRILLIC/*66*/ } },
+ { "pa", { PANGO_SCRIPT_GURMUKHI/*63*/ } },
+ { "pl", { PANGO_SCRIPT_LATIN/*70*/ } },
+ { "ps-af", { PANGO_SCRIPT_ARABIC/*49*/ } },
+ { "ps-pk", { PANGO_SCRIPT_ARABIC/*49*/ } },
+ { "pt", { PANGO_SCRIPT_LATIN/*82*/ } },
+ { "rm", { PANGO_SCRIPT_LATIN/*66*/ } },
+ { "ro", { PANGO_SCRIPT_LATIN/*62*/ } },
+ { "ru", { PANGO_SCRIPT_CYRILLIC/*66*/ } },
+ { "sah", { PANGO_SCRIPT_CYRILLIC/*76*/ } },
+ { "sa", { PANGO_SCRIPT_DEVANAGARI/*68*/ } },
+ { "sco", { PANGO_SCRIPT_LATIN/*56*/ } },
+ { "sel", { PANGO_SCRIPT_CYRILLIC/*66*/ } },
+ { "se", { PANGO_SCRIPT_LATIN/*66*/ } },
+ { "sh", { PANGO_SCRIPT_CYRILLIC/*76*/ } },
+ { "si", { PANGO_SCRIPT_SINHALA/*77*/ } },
+ { "sk", { PANGO_SCRIPT_LATIN/*86*/ } },
+ { "sl", { PANGO_SCRIPT_LATIN/*62*/ } },
+ { "sma", { PANGO_SCRIPT_LATIN/*60*/ } },
+ { "smj", { PANGO_SCRIPT_LATIN/*60*/ } },
+ { "smn", { PANGO_SCRIPT_LATIN/*68*/ } },
+ { "sms", { PANGO_SCRIPT_LATIN/*80*/ } },
+ { "sm", { PANGO_SCRIPT_LATIN/*52*/ } },
+ { "so", { PANGO_SCRIPT_LATIN/*52*/ } },
+ { "sq", { PANGO_SCRIPT_LATIN/*56*/ } },
+ { "sr", { PANGO_SCRIPT_CYRILLIC/*76*/ } },
+ { "ss", { PANGO_SCRIPT_LATIN/*52*/ } },
+ { "st", { PANGO_SCRIPT_LATIN/*52*/ } },
+ { "sv", { PANGO_SCRIPT_LATIN/*68*/ } },
+ { "sw", { PANGO_SCRIPT_LATIN/*52*/ } },
+ { "syr", { PANGO_SCRIPT_SYRIAC/*45*/ } },
+ { "ta", { PANGO_SCRIPT_TAMIL/*48*/ } },
+ { "te", { PANGO_SCRIPT_TELUGU/*80*/ } },
+ { "tg", { PANGO_SCRIPT_CYRILLIC/*78*/ } },
+ { "th", { PANGO_SCRIPT_THAI/*86*/ } },
+ { "ti-er", { PANGO_SCRIPT_ETHIOPIC/*255*/ } },
+ { "ti-et", { PANGO_SCRIPT_ETHIOPIC/*255*/ } },
+ { "tig", { PANGO_SCRIPT_ETHIOPIC/*221*/ } },
+ { "tk", { PANGO_SCRIPT_CYRILLIC/*74*/ } },
+ { "tl", { PANGO_SCRIPT_TAGALOG/*19*/ } },
+ { "tn", { PANGO_SCRIPT_LATIN/*58*/ } },
+ { "to", { PANGO_SCRIPT_LATIN/*52*/ } },
+// { "tr", { PANGO_SCRIPT_LATIN/*70*/ } },
+ { "tr", { EncodingDetector::Turkish } },
+ { "ts", { PANGO_SCRIPT_LATIN/*52*/ } },
+ { "tt", { PANGO_SCRIPT_CYRILLIC/*76*/ } },
+ { "tw", { PANGO_SCRIPT_LATIN/*70*/ } },
+ { "tyv", { PANGO_SCRIPT_CYRILLIC/*70*/ } },
+ { "ug", { PANGO_SCRIPT_ARABIC/*125*/ } },
+ { "uk", { PANGO_SCRIPT_CYRILLIC/*72*/ } },
+ { "ur", { PANGO_SCRIPT_ARABIC/*145*/ } },
+ { "uz", { PANGO_SCRIPT_CYRILLIC/*68*/ } },
+ { "ven", { PANGO_SCRIPT_LATIN/*62*/ } },
+ { "vi", { PANGO_SCRIPT_LATIN/*186*/ } },
+ { "vot", { PANGO_SCRIPT_LATIN/*62*/ } },
+ { "vo", { PANGO_SCRIPT_LATIN/*54*/ } },
+ { "wa", { PANGO_SCRIPT_LATIN/*70*/ } },
+ { "wen", { PANGO_SCRIPT_LATIN/*76*/ } },
+ { "wo", { PANGO_SCRIPT_LATIN/*66*/ } },
+ { "xh", { PANGO_SCRIPT_LATIN/*52*/ } },
+ { "yap", { PANGO_SCRIPT_LATIN/*58*/ } },
+ { "yi", { PANGO_SCRIPT_HEBREW/*27*/ } },
+ { "yo", { PANGO_SCRIPT_LATIN/*114*/ } },
+// { "zh-cn", { PANGO_SCRIPT_HAN/*6763*/ } },
+ { "zh-cn", { EncodingDetector::ChineseSimplified } },
+// { "zh-hk", { PANGO_SCRIPT_HAN/*2213*/ } },
+ { "zh-hk", { EncodingDetector::ChineseTraditional } },
+// { "zh-mo", { PANGO_SCRIPT_HAN/*2213*/ } },
+ { "zh-mo", { EncodingDetector::ChineseTraditional } },
+// { "zh-sg", { PANGO_SCRIPT_HAN/*6763*/ } },
+ { "zh-sg", { EncodingDetector::ChineseSimplified } },
+// { "zh-tw", { PANGO_SCRIPT_HAN/*13063*/ } },
+ { "zh-tw", { EncodingDetector::ChineseTraditional } },
+ { "zu", { PANGO_SCRIPT_LATIN/*52*/ } },
+ { "\x00", { EncodingDetector::None } } //end mark
+};
+
+enum MIB
+{
+ MibLatin1 = 4,
+ Mib8859_8 = 85,
+ MibUtf8 = 106,
+ MibUcs2 = 1000,
+ MibUtf16 = 1015,
+ MibUtf16BE = 1013,
+ MibUtf16LE = 1014
+};
+
+static bool is16Bit(QTextCodec* codec)
+{
+ switch (codec->mibEnum())
+ {
+ case MibUtf16:
+ case MibUtf16BE:
+ case MibUtf16LE:
+ case MibUcs2:
+ return true;
+ default:
+ return false;
+ }
+}
+
+class EncodingDetectorPrivate
+{
+public:
+ QTextCodec *m_codec;
+ QTextDecoder *m_decoder; // utf16
+ QTextCodec *m_defaultCodec;
+ QCString m_storeDecoderName;
+
+ EncodingDetector::EncodingChoiceSource m_source;
+ EncodingDetector::AutoDetectScript m_autoDetectLanguage;
+
+ bool m_visualRTL : 1;
+ bool m_seenBody : 1;
+ bool m_writtingHappened : 1;
+ bool m_analyzeCalled : 1; //for decode()
+ int m_multiByte;
+
+ QCString m_bufferForDefferedEncDetection;
+
+ EncodingDetectorPrivate()
+ : m_codec(QTextCodec::codecForMib(MibLatin1))
+ , m_decoder(m_codec->makeDecoder())
+ , m_defaultCodec(m_codec)
+ , m_source(EncodingDetector::DefaultEncoding)
+ , m_autoDetectLanguage(EncodingDetector::SemiautomaticDetection)
+ , m_visualRTL(false)
+ , m_seenBody(false)
+ , m_writtingHappened(false)
+ , m_analyzeCalled(false)
+ , m_multiByte(0)
+ {
+ }
+
+ EncodingDetectorPrivate(QTextCodec* codec,EncodingDetector::EncodingChoiceSource source, EncodingDetector::AutoDetectScript script)
+ : m_codec(codec)
+ , m_decoder(m_codec->makeDecoder())
+ , m_defaultCodec(m_codec)
+ , m_source(source)
+ , m_autoDetectLanguage(script)
+ , m_visualRTL(false)
+ , m_seenBody(false)
+ , m_writtingHappened(false)
+ , m_analyzeCalled(false)
+ , m_multiByte(0)
+ {
+ }
+
+ ~EncodingDetectorPrivate()
+ {
+ delete m_decoder;
+ }
+};
+
+
+static QCString automaticDetectionForArabic( const unsigned char* ptr, int size )
+{
+ for ( int i = 0; i < size; ++i ) {
+ if ( ( ptr[ i ] >= 0x80 && ptr[ i ] <= 0x9F ) || ptr[ i ] == 0xA1 || ptr[ i ] == 0xA2 || ptr[ i ] == 0xA3
+ || ( ptr[ i ] >= 0xA5 && ptr[ i ] <= 0xAB ) || ( ptr[ i ] >= 0xAE && ptr[ i ] <= 0xBA )
+ || ptr[ i ] == 0xBC || ptr[ i ] == 0xBD || ptr[ i ] == 0xBE || ptr[ i ] == 0xC0
+ || ( ptr[ i ] >= 0xDB && ptr[ i ] <= 0xDF ) || ( ptr[ i ] >= 0xF3 ) ) {
+ return "cp1256";
+ }
+ }
+
+ return "iso-8859-6";
+}
+
+static QCString automaticDetectionForBaltic( const unsigned char* ptr, int size )
+{
+ for ( int i = 0; i < size; ++i ) {
+ if ( ( ptr[ i ] >= 0x80 && ptr[ i ] <= 0x9E ) )
+ return "cp1257";
+
+ if ( ptr[ i ] == 0xA1 || ptr[ i ] == 0xA5 )
+ return "iso-8859-13";
+ }
+
+ return "iso-8859-13";
+}
+
+static QCString automaticDetectionForCentralEuropean(const unsigned char* ptr, int size )
+{
+ QCString charset;
+ for ( int i = 0; i < size; ++i ) {
+ if ( ptr[ i ] >= 0x80 && ptr[ i ] <= 0x9F ) {
+ if ( ptr[ i ] == 0x81 || ptr[ i ] == 0x83 || ptr[ i ] == 0x90 || ptr[ i ] == 0x98 )
+ return "ibm852";
+
+ if ( i + 1 > size )
+ return "cp1250";
+ else { // maybe ibm852 ?
+ charset = "cp1250";
+ continue;
+ }
+ }
+ if ( ptr[ i ] == 0xA5 || ptr[ i ] == 0xAE || ptr[ i ] == 0xBE || ptr[ i ] == 0xC3 || ptr[ i ] == 0xD0 || ptr[ i ] == 0xE3 || ptr[ i ] == 0xF0 ) {
+ if ( i + 1 > size )
+ return "iso-8859-2";
+ else { // maybe ibm852 ?
+ if ( charset.isNull() )
+ charset = "iso-8859-2";
+ continue;
+ }
+ }
+ }
+
+ if ( charset.isNull() )
+ charset = "iso-8859-3";
+
+ return charset.data();
+}
+
+static QCString automaticDetectionForCyrillic( const unsigned char* ptr, int size)
+{
+#ifdef DECODE_DEBUG
+ kWarning() << "EncodingDetector: Cyr heuristics";
+#endif
+
+// if (ptr[0]==0xef && ptr[1]==0xbb && ptr[2]==0xbf)
+// return "utf8";
+ int utf8_mark=0;
+ int koi_score=0;
+ int cp1251_score=0;
+
+ int koi_st=0;
+ int cp1251_st=0;
+
+// int koi_na=0;
+// int cp1251_na=0;
+
+ int koi_o_capital=0;
+ int koi_o=0;
+ int cp1251_o_capital=0;
+ int cp1251_o=0;
+
+ int koi_a_capital=0;
+ int koi_a=0;
+ int cp1251_a_capital=0;
+ int cp1251_a=0;
+
+ int koi_s_capital=0;
+ int koi_s=0;
+ int cp1251_s_capital=0;
+ int cp1251_s=0;
+
+ int koi_i_capital=0;
+ int koi_i=0;
+ int cp1251_i_capital=0;
+ int cp1251_i=0;
+
+ int cp1251_small_range=0;
+ int koi_small_range=0;
+ int ibm866_small_range=0;
+
+ int i;
+ for (i=1; (i<size) && (cp1251_small_range+koi_small_range<1000) ;++i)
+ {
+ if (ptr[i]>0xdf)
+ {
+ ++cp1251_small_range;
+
+ if (ptr[i]==0xee)//small o
+ ++cp1251_o;
+ else if (ptr[i]==0xe0)//small a
+ ++cp1251_a;
+ else if (ptr[i]==0xe8)//small i
+ ++cp1251_i;
+ else if (ptr[i]==0xf1)//small s
+ ++cp1251_s;
+ else if (ptr[i]==0xf2 && ptr[i-1]==0xf1)//small st
+ ++cp1251_st;
+
+ else if (ptr[i]==0xef)
+ ++koi_o_capital;
+ else if (ptr[i]==0xe1)
+ ++koi_a_capital;
+ else if (ptr[i]==0xe9)
+ ++koi_i_capital;
+ else if (ptr[i]==0xf3)
+ ++koi_s_capital;
+
+ }
+ else if (ptr[i]>0xbf)
+ {
+ ++koi_small_range;
+
+ if (ptr[i]==0xd0||ptr[i]==0xd1)//small o
+ ++utf8_mark;
+ else if (ptr[i]==0xcf)//small o
+ ++koi_o;
+ else if (ptr[i]==0xc1)//small a
+ ++koi_a;
+ else if (ptr[i]==0xc9)//small i
+ ++koi_i;
+ else if (ptr[i]==0xd3)//small s
+ ++koi_s;
+ else if (ptr[i]==0xd4 && ptr[i-1]==0xd3)//small st
+ ++koi_st;
+
+ else if (ptr[i]==0xce)
+ ++cp1251_o_capital;
+ else if (ptr[i]==0xc0)
+ ++cp1251_a_capital;
+ else if (ptr[i]==0xc8)
+ ++cp1251_i_capital;
+ else if (ptr[i]==0xd1)
+ ++cp1251_s_capital;
+ }
+ else if (ptr[i]>0x9f && ptr[i]<0xb0) //first 16 letterz is 60%
+ ++ibm866_small_range;
+
+ }
+
+ //cannot decide?
+ if (cp1251_small_range+koi_small_range+ibm866_small_range<8)
+ {
+ return "";
+ }
+
+ if (3*utf8_mark>cp1251_small_range+koi_small_range+ibm866_small_range)
+ {
+#ifdef DECODE_DEBUG
+ kWarning() << "Cyr Enc Detection: UTF8";
+#endif
+ return "UTF-8";
+ }
+
+ if (ibm866_small_range>cp1251_small_range+koi_small_range)
+ return "ibm866";
+
+// QCString koi_string = "koi8-u";
+// QCString cp1251_string = "cp1251";
+
+ if (cp1251_st==0 && koi_st>1)
+ koi_score+=10;
+ else if (koi_st==0 && cp1251_st>1)
+ cp1251_score+=10;
+
+ if (cp1251_st && koi_st)
+ {
+ if (cp1251_st/koi_st>2)
+ cp1251_score+=20;
+ else if (koi_st/cp1251_st>2)
+ koi_score+=20;
+ }
+
+ if (cp1251_a>koi_a)
+ cp1251_score+=10;
+ else if (cp1251_a || koi_a)
+ koi_score+=10;
+
+ if (cp1251_o>koi_o)
+ cp1251_score+=10;
+ else if (cp1251_o || koi_o)
+ koi_score+=10;
+
+ if (cp1251_i>koi_i)
+ cp1251_score+=10;
+ else if (cp1251_i || koi_i)
+ koi_score+=10;
+
+ if (cp1251_s>koi_s)
+ cp1251_score+=10;
+ else if (cp1251_s || koi_s)
+ koi_score+=10;
+
+ if (cp1251_a_capital>koi_a_capital)
+ cp1251_score+=9;
+ else if (cp1251_a_capital || koi_a_capital)
+ koi_score+=9;
+
+ if (cp1251_o_capital>koi_o_capital)
+ cp1251_score+=9;
+ else if (cp1251_o_capital || koi_o_capital)
+ koi_score+=9;
+
+ if (cp1251_i_capital>koi_i_capital)
+ cp1251_score+=9;
+ else if (cp1251_i_capital || koi_i_capital)
+ koi_score+=9;
+
+ if (cp1251_s_capital>koi_s_capital)
+ cp1251_score+=9;
+ else if (cp1251_s_capital || koi_s_capital)
+ koi_score+=9;
+#ifdef DECODE_DEBUG
+ kWarning()<<"koi_score " << koi_score << " cp1251_score " << cp1251_score;
+#endif
+ if (abs(koi_score-cp1251_score)<10)
+ {
+ //fallback...
+ cp1251_score=cp1251_small_range;
+ koi_score=koi_small_range;
+ }
+ if (cp1251_score>koi_score)
+ return "cp1251";
+ else
+ return "koi8-u";
+
+
+// if (cp1251_score>koi_score)
+// setEncoding("cp1251",AutoDetectedEncoding);
+// else
+// setEncoding("koi8-u",AutoDetectedEncoding);
+// return true;
+
+}
+
+static QCString automaticDetectionForGreek( const unsigned char* ptr, int size )
+{
+ for ( int i = 0; i < size; ++i ) {
+ if ( ptr[ i ] == 0x80 || ( ptr[ i ] >= 0x82 && ptr[ i ] <= 0x87 ) || ptr[ i ] == 0x89 || ptr[ i ] == 0x8B
+ || ( ptr[ i ] >= 0x91 && ptr[ i ] <= 0x97 ) || ptr[ i ] == 0x99 || ptr[ i ] == 0x9B || ptr[ i ] == 0xA4
+ || ptr[ i ] == 0xA5 || ptr[ i ] == 0xAE ) {
+ return "cp1253";
+ }
+ }
+
+ return "iso-8859-7";
+}
+
+static QCString automaticDetectionForHebrew( const unsigned char* ptr, int size )
+{
+ for ( int i = 0; i < size; ++i ) {
+ if ( ptr[ i ] == 0x80 || ( ptr[ i ] >= 0x82 && ptr[ i ] <= 0x89 ) || ptr[ i ] == 0x8B
+ || ( ptr[ i ] >= 0x91 && ptr[ i ] <= 0x99 ) || ptr[ i ] == 0x9B || ptr[ i ] == 0xA1 || ( ptr[ i ] >= 0xBF && ptr[ i ] <= 0xC9 )
+ || ( ptr[ i ] >= 0xCB && ptr[ i ] <= 0xD8 ) ) {
+ return "cp1255";
+ }
+
+ if ( ptr[ i ] == 0xDF )
+ return "iso-8859-8-i";
+ }
+
+ return "iso-8859-8-i";
+}
+
+static QCString automaticDetectionForJapanese( const unsigned char* ptr, int size )
+{
+ JapaneseCode kc;
+
+ switch ( kc.guess_jp( (const char*)ptr, size ) ) {
+ case JapaneseCode::JIS:
+ return "jis7";
+ case JapaneseCode::EUC:
+ return "eucjp";
+ case JapaneseCode::SJIS:
+ return "sjis";
+ case JapaneseCode::UTF8:
+ return "utf8";
+ default:
+ break;
+ }
+
+ return "";
+}
+
+static QCString automaticDetectionForTurkish( const unsigned char* ptr, int size )
+{
+ for ( int i = 0; i < size; ++i ) {
+ if ( ptr[ i ] == 0x80 || ( ptr[ i ] >= 0x82 && ptr[ i ] <= 0x8C ) || ( ptr[ i ] >= 0x91 && ptr[ i ] <= 0x9C ) || ptr[ i ] == 0x9F ) {
+ return "cp1254";
+ }
+ }
+
+ return "iso-8859-9";
+}
+
+static QCString automaticDetectionForWesternEuropean( const unsigned char* ptr, int size )
+{
+ uint nonansi_count=0;
+ for (int i=0; i<size; ++i)
+ {
+ if (ptr[i]>0x79)
+ {
+ ++nonansi_count;
+ if ( ptr[i]>0xc1 && ptr[i]<0xf0 && i+1<size && ptr[i+1]>0x7f && ptr[i+1]<0xc0)
+ {
+ return "UTF-8";
+ }
+ if (ptr[i] >= 0x78 && ptr[i] <= 0x9 )
+ {
+ return "cp1252";
+ }
+ }
+
+ }
+
+ if (nonansi_count>0)
+ return "iso-8859-15";
+
+ 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 QCString &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]<=' ')
+ ++pos;
+
+ //Bail out if nothing after
+ // Skip equals sign.
+ if (pos>=len || str[pos] != '=')
+ return -1;
+ ++pos;
+
+ // Skip spaces and stray control characters.
+ while (pos<len && str[pos]<=' ')
+ ++pos;
+
+ //Bail out if nothing after
+ if (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 && str[end]!=quoteMark)
+ ++end;
+
+ if (end>=len)
+ return -1;
+
+ encodingLength = end-pos;
+ return pos;
+}
+
+
+bool EncodingDetector::errorsIfUtf8 (const char* data, int length)
+{
+ if (d->m_codec->mibEnum()!=MibUtf8)
+ return false; //means no errors
+// #define highest1Bits (unsigned char)0x80
+// #define highest2Bits (unsigned char)0xC0
+// #define highest3Bits (unsigned char)0xE0
+// #define highest4Bits (unsigned char)0xF0
+// #define highest5Bits (unsigned char)0xF8
+static const unsigned char highest1Bits = 0x80;
+static const unsigned char highest2Bits = 0xC0;
+static const unsigned char highest3Bits = 0xE0;
+static const unsigned char highest4Bits = 0xF0;
+static const unsigned char highest5Bits = 0xF8;
+
+ for (int i=0; i<length; ++i)
+ {
+ unsigned char c = data[i];
+
+ if (d->m_multiByte>0)
+ {
+ if ((c & highest2Bits) == 0x80)
+ {
+ --(d->m_multiByte);
+ continue;
+ }
+#ifdef DECODE_DEBUG
+ kWarning() << "EncDetector: Broken UTF8";
+#endif
+ return true;
+ }
+
+ // most significant bit zero, single char
+ if ((c & highest1Bits) == 0x00)
+ continue;
+
+ // 110xxxxx => init 1 following bytes
+ if ((c & highest3Bits) == 0xC0)
+ {
+ d->m_multiByte = 1;
+ continue;
+ }
+
+ // 1110xxxx => init 2 following bytes
+ if ((c & highest4Bits) == 0xE0)
+ {
+ d->m_multiByte = 2;
+ continue;
+ }
+
+ // 11110xxx => init 3 following bytes
+ if ((c & highest5Bits) == 0xF0)
+ {
+ d->m_multiByte = 3;
+ continue;
+ }
+#ifdef DECODE_DEBUG
+ kWarning() << "EncDetector:_Broken UTF8";
+#endif
+ return true;
+ }
+ return false;
+}
+
+EncodingDetector::EncodingDetector() : d(new EncodingDetectorPrivate)
+{
+}
+
+EncodingDetector::EncodingDetector(QTextCodec* codec, EncodingChoiceSource source, AutoDetectScript script) :
+ d(new EncodingDetectorPrivate(codec,source,script))
+{
+}
+
+EncodingDetector::~EncodingDetector()
+{
+ delete d;
+}
+
+void EncodingDetector::setAutoDetectLanguage( EncodingDetector::AutoDetectScript lang)
+{
+ d->m_autoDetectLanguage=lang;
+}
+EncodingDetector::AutoDetectScript EncodingDetector::autoDetectLanguage() const
+{
+ return d->m_autoDetectLanguage;
+}
+
+EncodingDetector::EncodingChoiceSource EncodingDetector::encodingChoiceSource() const
+{
+ return d->m_source;
+}
+
+const char* EncodingDetector::encoding() const
+{
+ d->m_storeDecoderName = d->m_codec->name();
+ return d->m_storeDecoderName.data();
+}
+
+bool EncodingDetector::visuallyOrdered() const
+{
+ return d->m_visualRTL;
+}
+
+// const QTextCodec* EncodingDetector::codec() const
+// {
+// return d->m_codec;
+// }
+
+QTextDecoder* EncodingDetector::decoder()
+{
+ return d->m_decoder;
+}
+
+bool EncodingDetector::setEncoding(const char *_encoding, EncodingChoiceSource type)
+{
+ QTextCodec *codec;
+ QCString enc(_encoding);
+ if(/*enc.isNull() || */enc.isEmpty())
+ {
+ if (type==DefaultEncoding)
+ codec=d->m_defaultCodec;
+ else
+ return false;
+ }
+ else
+ {
+ //QString->QTextCodec
+
+ enc = enc.lower();
+ // hebrew visually ordered
+ if(enc=="visual")
+ enc="iso8859-8";
+ bool b;
+ codec = KGlobal::charsets()->codecForName(enc, b);
+ if (!b)
+ return false;
+ }
+
+ if (d->m_codec->mibEnum()==codec->mibEnum())
+ return true;
+
+ if ((type==EncodingFromMetaTag || type==EncodingFromXMLHeader) && is16Bit(codec))
+ {
+ //Sometimes the codec specified is absurd, i.e. UTF-16 despite
+ //us decoding a meta tag as ASCII. In that case, ignore it.
+ return false;
+ }
+
+ if (codec->mibEnum() == Mib8859_8)
+ {
+ //We do NOT want to use Qt's QHebrewCodec, since it tries to reorder itself.
+ codec = QTextCodec::codecForName("iso8859-8-i");
+
+ // visually ordered unless one of the following
+ if(!(enc=="iso-8859-8-i"||enc=="iso_8859-8-i"||enc=="csiso88598i"||enc=="logical"))
+ d->m_visualRTL = true;
+ }
+
+ d->m_codec = codec;
+ d->m_source = type;
+ delete d->m_decoder;
+ d->m_decoder = d->m_codec->makeDecoder();
+#ifdef DECODE_DEBUG
+ kDebug(6005) << "EncodingDetector::encoding used is" << d->m_codec->name();
+#endif
+ return true;
+}
+
+bool EncodingDetector::analyze(const QByteArray &data)
+{
+ return analyze( data.data(), data.size() );
+}
+
+bool EncodingDetector::analyze(const char *data, int len)
+{
+ // Check for UTF-16 or UTF-8 BOM mark at the beginning, which is a sure sign of a Unicode encoding.
+ // maximumBOMLength = 10
+ // Even if the user has chosen utf16 we still need to auto-detect the endianness
+ if (len >= 10 && ((d->m_source != UserChosenEncoding) || is16Bit(d->m_codec)))
+ {
+ // Extract the first three bytes.
+ const uchar *udata = (const uchar *)data;
+ uchar c1 = *udata++;
+ uchar c2 = *udata++;
+ uchar c3 = *udata++;
+
+ // Check for the BOM
+ const char *autoDetectedEncoding;
+ if ((c1 == 0xFE && c2 == 0xFF) || (c1 == 0xFF && c2 == 0xFE))
+ {
+ autoDetectedEncoding = "ISO-10646-UCS-2";
+ }
+ else if (c1 == 0xEF && c2 == 0xBB && c3 == 0xBF)
+ {
+ autoDetectedEncoding = "UTF-8";
+ }
+ else if (c1 == 0x00 || c2 == 0x00)
+ {
+ uchar c4 = *udata++;
+ uchar c5 = *udata++;
+ uchar c6 = *udata++;
+ uchar c7 = *udata++;
+ uchar c8 = *udata++;
+ uchar c9 = *udata++;
+ uchar c10 = *udata++;
+
+ int nul_count_even = (c2 != 0) + (c4 != 0) + (c6 != 0) + (c8 != 0) + (c10 != 0);
+ int nul_count_odd = (c1 != 0) + (c3 != 0) + (c5 != 0) + (c7 != 0) + (c9 != 0);
+ if ((nul_count_even==0 && nul_count_odd==5) || (nul_count_even==5 && nul_count_odd==0))
+ autoDetectedEncoding = "ISO-10646-UCS-2";
+ else
+ autoDetectedEncoding = 0;
+ }
+ else
+ {
+ autoDetectedEncoding = 0;
+ }
+
+ // If we found a BOM, use the encoding it implies.
+ if (autoDetectedEncoding != 0)
+ {
+ d->m_source = BOM;
+ d->m_codec = QTextCodec::codecForName(autoDetectedEncoding);
+ assert(d->m_codec);
+ //enc = d->m_codec->name();
+ delete d->m_decoder;
+ d->m_decoder = d->m_codec->makeDecoder();
+#ifdef DECODE_DEBUG
+ kWarning() << "Detection by BOM";
+#endif
+ if (is16Bit(d->m_codec) && c2==0x00)
+ {
+ // utf16LE, we need to put the decoder in LE mode
+ char reverseUtf16[3] = {(char)0xFF, (char)0xFE, 0x00};
+ d->m_decoder->toUnicode(reverseUtf16, 2);
+ }
+ return true;
+ }
+ }
+
+ //exit from routine in case it was called to only detect byte order for utf-16
+ if (d->m_source==UserChosenEncoding)
+ {
+#ifdef DECODE_DEBUG
+ kWarning() << "EncodingDetector: UserChosenEncoding exit ";
+#endif
+
+ if (errorsIfUtf8(data, len))
+ setEncoding("",DefaultEncoding);
+ return true;
+ }
+#if 0 //This is for plaintext, so don't try to parse HTML headers -- ahartmetz
+ if (!d->m_seenBody)
+ {
+ // we still don't have an encoding, and are in the head
+ // the following tags are allowed in <head>:
+ // SCRIPT|STYLE|META|LINK|OBJECT|TITLE|BASE
+ const char *ptr = data;
+ const char *pEnd = data+len;
+
+ while(ptr != pEnd)
+ {
+ if(*ptr!='<')
+ {
+ ++ptr;
+ continue;
+ }
+ ++ptr;
+ // Handle comments.
+ if (ptr[0] == '!' && ptr[1] == '-' && ptr[2] == '-')
+ {
+ ptr += 3;
+ skipComment(ptr, pEnd);
+ continue;
+ }
+
+ // Handle XML header, which can have encoding in it.
+ if (ptr[0]=='?' && ptr[1]=='x' && ptr[2]=='m' && ptr[3]=='l')
+ {
+ const char *end = ptr;
+ while (*end != '>' && end < pEnd)
+ end++;
+ if (*end == '\0' || end == pEnd)
+ break;
+ QCString str(ptr, end - ptr + 1);
+ int length;
+ int pos = findXMLEncoding(str, length);
+ // also handles the case when specified encoding aint correct
+ if (pos!=-1 && setEncoding(str.mid(pos, length), EncodingFromXMLHeader))
+ {
+ return true;
+ }
+ }
+
+ //look for <meta>, stop if we reach <body>
+ while (
+ !((*ptr >= 'a') && (*ptr <= 'z') ||
+ (*ptr >= 'A') && (*ptr <= 'Z'))
+ && ptr < pEnd
+ )
+ ++ptr;
+
+ char tmp[5];
+ int length=0;
+ const char* max=ptr+4;
+ if (pEnd<max)
+ max=pEnd;
+ while (
+ ((*ptr >= 'a') && (*ptr <= 'z') ||
+ (*ptr >= 'A') && (*ptr <= 'Z') ||
+ (*ptr >= '0') && (*ptr <= '9'))
+ && ptr < max
+ )
+ {
+ tmp[length] = tolower( *ptr );
+ ++ptr;
+ ++length;
+ }
+ tmp[length] = 0;
+ if (tmp[0]=='m'&&tmp[1]=='e'&&tmp[2]=='t'&&tmp[3]=='a')
+ {
+ // found a meta tag...
+ const char* end = ptr;
+ while(*end != '>' && *end != '\0' && end<pEnd)
+ end++;
+ //if ( *end == '\0' ) break;
+ QCString str( ptr, (end-ptr)+1);
+ str = str.lower();
+ int pos=0;
+ //if( (pos = str.find("http-equiv", pos)) == -1) break;
+ //if( (pos = str.find("content-type", pos)) == -1) break;
+ if( (pos = str.find("charset")) == -1)
+ continue;
+ pos+=6;
+ // skip to '='
+ if( (pos = str.find('=', pos)) == -1)
+ continue;
+
+ // skip whitespace before encoding itself
+ while (pos < (int)str.length() && str[pos] <= ' ')
+ ++pos;
+ if ( pos == (int)str.length())
+ continue;
+
+ int endpos = pos;
+ while( endpos < str.length() &&
+ (str[endpos] != ' ' && str[endpos] != '"' && str[endpos] != '\''
+ && str[endpos] != ';' && str[endpos] != '>') )
+ ++endpos;
+ #ifdef DECODE_DEBUG
+ kDebug( 6005 ) << "EncodingDetector: found charset in <meta>: " << str.mid(pos,endpos-pos).data();
+ #endif
+ if (setEncoding(str.mid(pos,endpos-pos), EncodingFromMetaTag))
+ return true;
+ }
+ else if (tmp[0]=='b'&&tmp[1]=='o'&&tmp[2]=='d'&&tmp[3]=='y')
+ {
+ d->m_seenBody=true;
+ break;
+ }
+ }
+ }
+
+ if (d->m_source==EncodingFromHTTPHeader)
+ return true;
+#endif
+ //if (len<20) //make a guess even if the file is short -- ahartmetz
+ if (len < 1)
+ {
+ setEncoding("",DefaultEncoding);
+ return false;
+ }
+#ifdef DECODE_DEBUG
+ kDebug( 6005 ) << "EncodingDetector: using heuristics (" << strlen(data) << ")";
+#endif
+
+ switch ( d->m_autoDetectLanguage )
+ {
+ case EncodingDetector::Arabic:
+ return setEncoding(automaticDetectionForArabic( (const unsigned char*) data, len ), AutoDetectedEncoding);
+// break;
+ case EncodingDetector::Baltic:
+ return setEncoding(automaticDetectionForBaltic( (const unsigned char*) data, len ), AutoDetectedEncoding);
+// break;
+ case EncodingDetector::CentralEuropean:
+ return setEncoding(automaticDetectionForCentralEuropean( (const unsigned char*) data, len ), AutoDetectedEncoding);
+ break;
+ case EncodingDetector::Cyrillic:
+ return setEncoding(automaticDetectionForCyrillic( (const unsigned char*) data, len), AutoDetectedEncoding);
+// break;
+ case EncodingDetector::Greek:
+ return setEncoding(automaticDetectionForGreek( (const unsigned char*) data, len ), AutoDetectedEncoding);
+// break;
+ case EncodingDetector::Hebrew:
+ return setEncoding(automaticDetectionForHebrew( (const unsigned char*) data, len ), AutoDetectedEncoding);
+// break;
+ case EncodingDetector::Japanese:
+ return setEncoding(automaticDetectionForJapanese( (const unsigned char*) data, len ), AutoDetectedEncoding);
+// break;
+ case EncodingDetector::Turkish:
+ return setEncoding(automaticDetectionForTurkish( (const unsigned char*) data, len ), AutoDetectedEncoding);
+// break;
+ case EncodingDetector::WesternEuropean:
+ if (setEncoding(automaticDetectionForWesternEuropean( (const unsigned char*) data, len ), AutoDetectedEncoding))
+ return true;
+ else if (d->m_defaultCodec->mibEnum()==MibLatin1) //detection for khtml
+ {
+ return setEncoding("iso-8859-15",AutoDetectedEncoding);
+ }
+ else //use default provided by eg katepart
+ {
+ return setEncoding("",DefaultEncoding);
+ }
+// break;
+ case EncodingDetector::SemiautomaticDetection:
+ case EncodingDetector::ChineseSimplified:
+ case EncodingDetector::ChineseTraditional:
+ case EncodingDetector::Korean:
+ case EncodingDetector::Thai:
+ case EncodingDetector::Unicode:
+ case EncodingDetector::NorthernSaami:
+ case EncodingDetector::SouthEasternEurope:
+ case EncodingDetector::None:
+ // huh. somethings broken in this code ### FIXME
+ //enc = 0; //Reset invalid codec we tried, so we get back to latin1 fallback.
+ break;
+ }
+
+ setEncoding("",DefaultEncoding);
+ return true;
+}
+
+
+EncodingDetector::AutoDetectScript EncodingDetector::scriptForName(const QString& lang)
+{
+ if (lang.isEmpty())
+ return EncodingDetector::None;
+ else if (lang==i18n("@item Text character set", "Unicode"))
+ return EncodingDetector::Unicode;
+ else if (lang==i18n("@item Text character set", "Cyrillic"))
+ return EncodingDetector::Cyrillic;
+ else if (lang==i18n("@item Text character set", "Western European"))
+ return EncodingDetector::WesternEuropean;
+ else if (lang==i18n("@item Text character set", "Central European"))
+ return EncodingDetector::CentralEuropean;
+ else if (lang==i18n("@item Text character set", "Greek"))
+ return EncodingDetector::Greek;
+ else if (lang==i18n("@item Text character set", "Hebrew"))
+ return EncodingDetector::Hebrew;
+ else if (lang==i18n("@item Text character set", "Turkish"))
+ return EncodingDetector::Turkish;
+ else if (lang==i18n("@item Text character set", "Japanese"))
+ return EncodingDetector::Japanese;
+ else if (lang==i18n("@item Text character set", "Baltic"))
+ return EncodingDetector::Baltic;
+ else if (lang==i18n("@item Text character set", "Arabic"))
+ return EncodingDetector::Arabic;
+
+ return EncodingDetector::None;
+}
+
+bool EncodingDetector::hasAutoDetectionForScript(EncodingDetector::AutoDetectScript script)
+{
+ switch (script)
+ {
+ case EncodingDetector::Arabic:
+ return true;
+ case EncodingDetector::Baltic:
+ return true;
+ case EncodingDetector::CentralEuropean:
+ return true;
+ case EncodingDetector::Cyrillic:
+ return true;
+ case EncodingDetector::Greek:
+ return true;
+ case EncodingDetector::Hebrew:
+ return true;
+ case EncodingDetector::Japanese:
+ return true;
+ case EncodingDetector::Turkish:
+ return true;
+ case EncodingDetector::WesternEuropean:
+ return true;
+ case EncodingDetector::ChineseTraditional:
+ return true;
+ case EncodingDetector::ChineseSimplified:
+ return true;
+ case EncodingDetector::Unicode:
+ return true;
+ break;
+ default:
+ return false;
+ }
+}
+
+QString EncodingDetector::nameForScript(EncodingDetector::AutoDetectScript script)
+{
+ switch (script)
+ {
+ case EncodingDetector::Arabic:
+ return i18n("@item Text character set", "Arabic");
+ break;
+ case EncodingDetector::Baltic:
+ return i18n("@item Text character set", "Baltic");
+ break;
+ case EncodingDetector::CentralEuropean:
+ return i18n("@item Text character set", "Central European");
+ break;
+ case EncodingDetector::Cyrillic:
+ return i18n("@item Text character set", "Cyrillic");
+ break;
+ case EncodingDetector::Greek:
+ return i18n("@item Text character set", "Greek");
+ break;
+ case EncodingDetector::Hebrew:
+ return i18n("@item Text character set", "Hebrew");
+ break;
+ case EncodingDetector::Japanese:
+ return i18n("@item Text character set", "Japanese");
+ break;
+ case EncodingDetector::Turkish:
+ return i18n("@item Text character set", "Turkish");
+ break;
+ case EncodingDetector::WesternEuropean:
+ return i18n("@item Text character set", "Western European");
+ break;
+ case EncodingDetector::ChineseTraditional:
+ return i18n("@item Text character set", "Chinese Traditional");
+ break;
+ case EncodingDetector::ChineseSimplified:
+ return i18n("@item Text character set", "Chinese Simplified");
+ break;
+ case EncodingDetector::Korean:
+ return i18n("@item Text character set", "Korean");
+ break;
+ case EncodingDetector::Thai:
+ return i18n("@item Text character set", "Thai");
+ break;
+ case EncodingDetector::Unicode:
+ return i18n("@item Text character set", "Unicode");
+ break;
+ //case EncodingDetector::SemiautomaticDetection:
+ default:
+ return QString();
+
+ }
+}
+
+EncodingDetector::AutoDetectScript EncodingDetector::scriptForLanguageCode(const QString &lc)
+{
+ // It might make sense to do something special if the locale ends with
+ // ".UTF-8" or "@utf8"
+ const char *langStr = pango_script_for_lang[0].lang;
+ // There is obvious optimization potential...
+ for ( int i = 0; langStr; i++ ) {
+ langStr = pango_script_for_lang[i].lang;
+ // startsWith() works for empty strings: every string "starts with" an empty string.
+ if ( lc.startsWith( QString::fromAscii( langStr ) ) )
+ return pango_script_for_lang[i].scripts[0];
+ }
+ return None;
+}
+
+#undef DECODE_DEBUG
+
diff --git a/kmail/encodingdetector.h b/kmail/encodingdetector.h
new file mode 100644
index 00000000..51ccb469
--- /dev/null
+++ b/kmail/encodingdetector.h
@@ -0,0 +1,166 @@
+/*
+ This file was taken from the KDE 4.x libraries and backported to Qt 3.
+
+ Copyright (C) 1999 Lars Knoll (knoll@mpi-hd.mpg.de)
+ Copyright (C) 2007 Nick Shaforostoff (shafff@ukr.net)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+
+*/
+#ifndef ENCODINGDETECTOR_H
+#define ENCODINGDETECTOR_H
+
+#include <qstring.h>
+
+class QTextCodec;
+class QTextDecoder;
+class EncodingDetectorPrivate;
+
+/**
+ * @short Provides encoding detection capabilities.
+ *
+ * Searches for encoding declaration inside raw data -- meta and xml tags.
+ * In the case it can't find it, uses heuristics for specified language.
+ *
+ * If it finds unicode BOM marks, it changes encoding regardless of what the user has told
+ *
+ * Intended lifetime of the object: one instance per document.
+ *
+ * Typical use:
+ * \code
+ * QByteArray data;
+ * ...
+ * EncodingDetector detector;
+ * detector.setAutoDetectLanguage(EncodingDetector::Cyrillic);
+ * QString out=detector.decode(data);
+ * \endcode
+ *
+ *
+ * Do not mix decode() with decodeWithBuffering()
+ *
+ * @short Guess encoding of char array
+ *
+ */
+class EncodingDetector
+{
+public:
+ enum EncodingChoiceSource
+ {
+ DefaultEncoding,
+ AutoDetectedEncoding,
+ BOM,
+ EncodingFromXMLHeader,
+ EncodingFromMetaTag,
+ EncodingFromHTTPHeader,
+ UserChosenEncoding
+ };
+
+ enum AutoDetectScript
+ {
+ None,
+ SemiautomaticDetection,
+ Arabic,
+ Baltic,
+ CentralEuropean,
+ ChineseSimplified,
+ ChineseTraditional,
+ Cyrillic,
+ Greek,
+ Hebrew,
+ Japanese,
+ Korean,
+ NorthernSaami,
+ SouthEasternEurope,
+ Thai,
+ Turkish,
+ Unicode,
+ WesternEuropean
+ };
+
+ /**
+ * Default codec is latin1 (as html spec says), EncodingChoiceSource is default, AutoDetectScript=Semiautomatic
+ */
+ EncodingDetector();
+
+ /**
+ * Allows to set Default codec, EncodingChoiceSource, AutoDetectScript
+ */
+ EncodingDetector(QTextCodec* codec, EncodingChoiceSource source, AutoDetectScript script=None);
+ ~EncodingDetector();
+
+ //const QTextCodec* codec() const;
+
+ /**
+ * @returns true if specified encoding was recognized
+ */
+ bool setEncoding(const char *encoding, EncodingChoiceSource type);
+
+ /**
+ * Convenience method.
+ * @returns mime name of detected encoding
+ */
+ const char* encoding() const;
+
+ bool visuallyOrdered() const;
+
+// void setAutoDetectLanguage( const QString& );
+// const QString& autoDetectLanguage() const;
+
+ void setAutoDetectLanguage( AutoDetectScript );
+ AutoDetectScript autoDetectLanguage() const;
+
+ EncodingChoiceSource encodingChoiceSource() const;
+
+ /**
+ * Analyze text data.
+ * @returns true if there was enough data for accurate detection
+ */
+ bool analyze( const char *data, int len );
+
+ /**
+ * Analyze text data.
+ * @returns true if there was enough data for accurate detection
+ */
+ bool analyze( const QByteArray &data );
+
+ /**
+ * Takes lang name _after_ it were i18n()'ed
+ */
+ static AutoDetectScript scriptForName(const QString& lang);
+ static QString nameForScript(AutoDetectScript);
+ static AutoDetectScript scriptForLanguageCode(const QString &lang);
+ static bool hasAutoDetectionForScript(AutoDetectScript);
+
+protected:
+ /**
+ * Check if we are really utf8. Taken from kate
+ *
+ * @returns true if current encoding is utf8 and the text cannot be in this encoding
+ *
+ * Please somebody read http://de.wikipedia.org/wiki/UTF-8 and check this code...
+ */
+ bool errorsIfUtf8 (const char* data, int length);
+
+ /**
+ * @returns QTextDecoder for detected encoding
+ */
+ QTextDecoder* decoder();
+
+private:
+ EncodingDetectorPrivate* const d;
+};
+
+#endif
diff --git a/kmail/encodingdetector_ja.cpp b/kmail/encodingdetector_ja.cpp
new file mode 100644
index 00000000..0c42f7e4
--- /dev/null
+++ b/kmail/encodingdetector_ja.cpp
@@ -0,0 +1,376 @@
+/*
+ * This file is part of the KDE libraries
+ *
+ * Copyright 2000-2003 Shiro Kawai <shiro@acm.org>, All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the authors nor the names of its contributors
+ * may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+/*
+ * original code is here.
+ * http://cvs.sourceforge.net/viewcvs.py/gauche/Gauche/ext/charconv/guess.c?view=markup
+ */
+
+/*
+ * Maybe we should use QTextCodec::heuristicContentMatch()
+ * But it fails detection. It's not useful.
+ */
+#include "encodingdetector_ja_p.h"
+
+/* DFA tables */
+const dfa_table guess_eucj_st[] = {
+ { /* state init */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 2,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, -1,
+ },
+ { /* state jis0201_kana */
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ },
+ { /* state jis0213_1 */
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, -1,
+ },
+ { /* state jis0213_2 */
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, -1,
+ },
+};
+
+guess_arc guess_eucj_ar[7] = {
+ { 0, 1.0 }, /* init -> init */
+ { 1, 0.8 }, /* init -> jis0201_kana */
+ { 3, 0.95 }, /* init -> jis0213_2 */
+ { 2, 1.0 }, /* init -> jis0213_1 */
+ { 0, 1.0 }, /* jis0201_kana -> init */
+ { 0, 1.0 }, /* jis0213_1 -> init */
+ { 0, 1.0 }, /* jis0213_2 -> init */
+};
+
+const dfa_table guess_sjis_st[] = {
+ { /* state init */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ -1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4,
+ },
+ { /* state jis0213 */
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, -1,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, -1, -1, -1,
+ },
+};
+
+guess_arc guess_sjis_ar[6] = {
+ { 0, 1.0 }, /* init -> init */
+ { 1, 1.0 }, /* init -> jis0213 */
+ { 0, 0.8 }, /* init -> init */
+ { 1, 0.95 }, /* init -> jis0213 */
+ { 0, 0.8 }, /* init -> init */
+ { 0, 1.0 }, /* jis0213 -> init */
+};
+
+const dfa_table guess_utf8_st[] = {
+ { /* state init */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, -1, -1,
+ },
+ { /* state 1byte_more */
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ },
+ { /* state 2byte_more */
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ },
+ { /* state 3byte_more */
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ },
+ { /* state 4byte_more */
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ },
+ { /* state 5byte_more */
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ },
+};
+
+guess_arc guess_utf8_ar[11] = {
+ { 0, 1.0 }, /* init -> init */
+ { 1, 1.0 }, /* init -> 1byte_more */
+ { 2, 1.0 }, /* init -> 2byte_more */
+ { 3, 1.0 }, /* init -> 3byte_more */
+ { 4, 1.0 }, /* init -> 4byte_more */
+ { 5, 1.0 }, /* init -> 5byte_more */
+ { 0, 1.0 }, /* 1byte_more -> init */
+ { 1, 1.0 }, /* 2byte_more -> 1byte_more */
+ { 2, 1.0 }, /* 3byte_more -> 2byte_more */
+ { 3, 1.0 }, /* 4byte_more -> 3byte_more */
+ { 4, 1.0 }, /* 5byte_more -> 4byte_more */
+};
+
+/* Guessing Routine */
+enum JapaneseCode::Type JapaneseCode::guess_jp(const char *buf, int buflen)
+{
+ int i;
+ guess_dfa *top = 0;
+
+ for (i=0; i<buflen; i++) {
+ int c = (unsigned char)buf[i];
+
+ /* special treatment of jis escape sequence */
+ if (c == 0x1b || last_JIS_escape) {
+ if (i < buflen-1) {
+ if (last_JIS_escape)
+ c = (unsigned char)buf[i];
+ else
+ c = (unsigned char)buf[++i];
+ last_JIS_escape = false;
+
+ if (c == '$' || c == '(') {
+ return JapaneseCode::JIS;
+ }
+ } else {
+ last_JIS_escape = true;
+ }
+ }
+
+ if (DFA_ALIVE(eucj)) {
+ if (!DFA_ALIVE(sjis) && !DFA_ALIVE(utf8)) return JapaneseCode::EUC;
+ DFA_NEXT(eucj, c);
+ }
+ if (DFA_ALIVE(sjis)) {
+ if (!DFA_ALIVE(eucj) && !DFA_ALIVE(utf8)) return JapaneseCode::SJIS;
+ DFA_NEXT(sjis, c);
+ }
+ if (DFA_ALIVE(utf8)) {
+ if (!DFA_ALIVE(sjis) && !DFA_ALIVE(eucj)) return JapaneseCode::UTF8;
+ DFA_NEXT(utf8, c);
+ }
+
+ if (!DFA_ALIVE(eucj) && !DFA_ALIVE(sjis) && !DFA_ALIVE(utf8)) {
+ /* we ran out the possibilities */
+ return JapaneseCode::ASCII;
+ }
+ }
+
+ /* ascii code check */
+ if (eucj->score == 1.0 && sjis->score == 1.0 && utf8->score == 1.0)
+ return JapaneseCode::ASCII;
+
+ /* Now, we have ambigous code. Pick the highest score. If more than
+ one candidate tie, pick the default encoding. */
+ if (DFA_ALIVE(eucj)) top = eucj;
+ if (DFA_ALIVE(utf8)) {
+ if (top) {
+ if (top->score < utf8->score) top = utf8;
+ } else {
+ top = utf8;
+ }
+ }
+ if (DFA_ALIVE(sjis)) {
+ if (top) {
+ if (top->score <= sjis->score) top = sjis;
+ } else {
+ top = sjis;
+ }
+ }
+
+ if (top == eucj) return JapaneseCode::EUC;
+ if (top == utf8) return JapaneseCode::UTF8;
+ if (top == sjis) return JapaneseCode::SJIS;
+
+ return JapaneseCode::ASCII;
+}
diff --git a/kmail/encodingdetector_ja_p.h b/kmail/encodingdetector_ja_p.h
new file mode 100644
index 00000000..c23de835
--- /dev/null
+++ b/kmail/encodingdetector_ja_p.h
@@ -0,0 +1,126 @@
+/*
+ * This file is part of the KDE libraries
+ *
+ * Copyright 2000-2003 Shiro Kawai <shiro@acm.org>, All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the authors nor the names of its contributors
+ * may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+/*
+ * original code is here.
+ * http://cvs.sourceforge.net/viewcvs.py/gauche/Gauche/ext/charconv/guess.c?view=markup
+ */
+#ifndef GUESS_JA_H
+#define GUESS_JA_H
+
+#include <qglobal.h>
+#ifdef Q_WS_WIN
+#undef UNICODE
+#endif
+namespace khtml {
+ class guess_arc {
+ public:
+ unsigned int next; /* next state */
+ double score; /* score */
+ };
+}
+
+using namespace khtml;
+
+typedef signed char dfa_table[256];
+
+/* DFA tables declared in guess_ja.cpp */
+extern const dfa_table guess_eucj_st[];
+extern guess_arc guess_eucj_ar[7];
+extern const dfa_table guess_sjis_st[];
+extern guess_arc guess_sjis_ar[6];
+extern const dfa_table guess_utf8_st[];
+extern guess_arc guess_utf8_ar[11];
+
+namespace khtml {
+
+ class guess_dfa {
+ public:
+ const dfa_table *states;
+ const guess_arc *arcs;
+ int state;
+ double score;
+
+ guess_dfa (const dfa_table stable[], const guess_arc *atable) :
+ states(stable), arcs(atable)
+ {
+ state = 0;
+ score = 1.0;
+ }
+ };
+
+ class JapaneseCode
+ {
+ public:
+ enum Type {ASCII, JIS, EUC, SJIS, UNICODE, UTF8 };
+ enum Type guess_jp(const char* buf, int buflen);
+
+ JapaneseCode () {
+ eucj = new guess_dfa(guess_eucj_st, guess_eucj_ar);
+ sjis = new guess_dfa(guess_sjis_st, guess_sjis_ar);
+ utf8 = new guess_dfa(guess_utf8_st, guess_utf8_ar);
+ last_JIS_escape = false;
+ }
+
+ ~JapaneseCode () {
+ if (eucj) delete eucj;
+ if (sjis) delete sjis;
+ if (utf8) delete utf8;
+ }
+
+ protected:
+ guess_dfa *eucj;
+ guess_dfa *sjis;
+ guess_dfa *utf8;
+
+ bool last_JIS_escape;
+ };
+}
+
+#define DFA_NEXT(dfa, ch) \
+ do { \
+ int arc__; \
+ if (dfa->state >= 0) { \
+ arc__ = dfa->states[dfa->state][ch]; \
+ if (arc__ < 0) { \
+ dfa->state = -1; \
+ } else { \
+ dfa->state = dfa->arcs[arc__].next; \
+ dfa->score *= dfa->arcs[arc__].score; \
+ } \
+ } \
+ } while (0)
+
+#define DFA_ALIVE(dfa) (dfa->state >= 0)
+
+#endif /* GUESS_JA_H */
diff --git a/kmail/eventsrc b/kmail/eventsrc
new file mode 100644
index 00000000..76720efe
--- /dev/null
+++ b/kmail/eventsrc
@@ -0,0 +1,126 @@
+[!Global!]
+IconName=kmail
+Comment=KMail
+Comment[hi]=के-मेल
+Comment[mk]=КПошта
+Comment[ne]=केडीई मेल
+Comment[pa]=ਕੇ-ਪੱਤਰ
+Comment[sv]=Kmail
+Comment[ta]=kஅஞ்சல்
+
+[new-mail-arrived]
+Name=New Mail Arrived
+Name[af]=Nuwe pos het gearriveer
+Name[ar]=وصل بريد جديد
+Name[bg]=Имате нова поща
+Name[br]=Deuet eo ur postel nevez
+Name[bs]=Stigla vam je nova pošta
+Name[ca]=Ha arribat correu nou
+Name[cs]=Přišla nová pošta
+Name[da]=Ny post ankommet
+Name[de]=Neue Nachrichten sind angekommen
+Name[el]=Έφτασε νέα αλληλογραφία
+Name[eo]=Nova Poŝto Alvenis
+Name[es]=Hay correo nuevo
+Name[et]=Saabus uus kiri
+Name[eu]=Posta berri bat iritsi da
+Name[fa]=نامۀ جدید رسیده
+Name[fi]=Uutta postia saapunut
+Name[fr]=Un nouveau message est arrivé
+Name[fy]=Nije berjochten oankaam
+Name[gl]=Hai Correo Novo
+Name[he]=דוא"ל חדש הגיע
+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
+Name[mk]=Пристигната е нова е-пошта
+Name[ms]=Mel Baru Sampai
+Name[nb]=Ny e-post ankommet
+Name[nds]=Niege Narichten sünd ankamen
+Name[ne]=नयाँ पत्र आइपुग्यो
+Name[nl]=Nieuwe berichten gearriveerd
+Name[nn]=Ny e-post er komen
+Name[pa]=ਨਵਾਂ ਪੱਤਰ ਆਇਆ
+Name[pl]=Otrzymano nową pocztę
+Name[pt]=Chegou Correio Novo
+Name[pt_BR]=Chegou Nova Mensagem
+Name[ru]=Получена новая почта
+Name[se]=Ođđa e-boasta bođii
+Name[sk]=Prišla nová pošta
+Name[sl]=Prispela je nova pošta
+Name[sr]=Стигла је нова пошта
+Name[sr@Latn]=Stigla je nova pošta
+Name[sv]=Ny post har anlänt
+Name[ta]=புதிய அஞ்சல் வந்துள்ளது
+Name[tg]=Почтаи нав қабул шуд
+Name[tr]=Yeni E-posta Geldi
+Name[uk]=Отримана нова пошта
+Name[uz]=Yangi xat keldi
+Name[uz@cyrillic]=Янги хат келди
+Name[zh_CN]=新邮件到达
+Name[zh_TW]=您有新郵件
+Comment=New mail arrived
+Comment[af]=Nuwe pos het gearriveer
+Comment[ar]=وصل بريد جديد
+Comment[bg]=Имате ново писмо
+Comment[br]=Deuet eo ur postel nevez
+Comment[bs]=Stigla vam je nova pošta
+Comment[ca]=Ha arribat correu nou
+Comment[cs]=Přišla nová pošta
+Comment[cy]=Cyrhaeddodd ebost newydd
+Comment[da]=Ny post ankommet
+Comment[de]=Neue Nachrichten sind angekommen
+Comment[el]=Έφτασε νέα αλληλογραφία
+Comment[eo]=Nova poŝto alvenis
+Comment[es]=Hay correo nuevo
+Comment[et]=Saabus uus kiri
+Comment[eu]=Posta berri bat iritsi da
+Comment[fa]=نامۀ جدید رسیده
+Comment[fi]=Uutta postia saapunut
+Comment[fr]=Un nouveau message est arrivé
+Comment[fy]=Nij berjocht oankaam
+Comment[gl]=Hai correo novo
+Comment[he]=דוא"ל חדש הגיע
+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
+Comment[mk]=Пристигната е нова е-пошта
+Comment[ms]=Mel baru sampai
+Comment[nb]=Ny e-post ankommet
+Comment[nds]=Niege Narichten sünd ankamen
+Comment[ne]=नयाँ पत्र आइपुग्यो
+Comment[nl]=Nieuw bericht ontvangen
+Comment[nn]=Ny e-post er komen
+Comment[pa]=ਨਵਾਂ ਪੱਤਰ ਆਇਆ
+Comment[pl]=Otrzymano nową pocztę
+Comment[pt]=Chegou correio novo
+Comment[pt_BR]=Chegou Nova Mensagem
+Comment[ru]=Получена новая почта
+Comment[se]=Ođđa e-boasta bođii
+Comment[sk]=Prišla nová pošta
+Comment[sl]=Prispela je nova pošta
+Comment[sr]=Стигла је нова пошта
+Comment[sr@Latn]=Stigla je nova pošta
+Comment[sv]=Ny post har anlänt
+Comment[ta]=புதிய அஞ்சல் வந்துள்ளது
+Comment[tg]=Почтаи нав қабул шуд
+Comment[tr]=Yeni e-posta geldi
+Comment[uk]=Надійшла нова пошта
+Comment[uz]=Yangi xat keldi
+Comment[uz@cyrillic]=Янги хат келди
+Comment[zh_CN]=新邮件到达
+Comment[zh_TW]=您有新郵件
+default_sound=
+default_presentation=0
+# None = 0, Sound = 1, Messagebox = 2, Logfile = 4, Stderr = 8, PassivePopup = 16, Execute = 32
+
diff --git a/kmail/expirejob.cpp b/kmail/expirejob.cpp
new file mode 100644
index 00000000..e7e853b0
--- /dev/null
+++ b/kmail/expirejob.cpp
@@ -0,0 +1,252 @@
+/**
+ * Copyright (c) 2004 David Faure <faure@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#include "expirejob.h"
+#include "kmfolder.h"
+#include "globalsettings.h"
+#include "folderstorage.h"
+#include "broadcaststatus.h"
+using KPIM::BroadcastStatus;
+#include "kmcommands.h"
+
+#include <kdebug.h>
+#include <klocale.h>
+
+using namespace KMail;
+
+// Look at this number of messages in each slotDoWork call
+#define EXPIREJOB_NRMESSAGES 100
+// And wait this number of milliseconds before calling it again
+#define EXPIREJOB_TIMERINTERVAL 100
+
+/*
+ Testcases for folder expiry:
+ Automatic expiry:
+ - normal case (ensure folder has old mails and expiry settings, wait for auto-expiry)
+ - having the folder selected when the expiry job would run (gets delayed)
+ - selecting a folder while an expiry job is running for it (should interrupt)
+ - exiting kmail while an expiry job is running (should abort & delete things cleanly)
+ Manual expiry:
+ - RMB / expire (for one folder) [KMMainWidget::slotExpireFolder()]
+ - RMB on Local Folders / Expire All Folders [KMFolderMgr::expireAll()]
+ - Expire All Folders [KMMainWidget::slotExpireAll()]
+*/
+
+
+ExpireJob::ExpireJob( KMFolder* folder, bool immediate )
+ : ScheduledJob( folder, immediate ), mTimer( this ), mCurrentIndex( 0 ),
+ mFolderOpen( false ), mMoveToFolder( 0 )
+{
+}
+
+ExpireJob::~ExpireJob()
+{
+}
+
+void ExpireJob::kill()
+{
+ Q_ASSERT( mCancellable );
+ // We must close the folder if we opened it and got interrupted
+ if ( mFolderOpen && mSrcFolder && mSrcFolder->storage() )
+ mSrcFolder->storage()->close( "expirejob" );
+ FolderJob::kill();
+}
+
+void ExpireJob::execute()
+{
+ mMaxUnreadTime = 0;
+ mMaxReadTime = 0;
+ mCurrentIndex = 0;
+
+ int unreadDays, readDays;
+ mSrcFolder->daysToExpire( unreadDays, readDays );
+ if (unreadDays > 0) {
+ kdDebug(5006) << "ExpireJob: deleting unread older than "<< unreadDays << " days" << endl;
+ mMaxUnreadTime = time(0) - unreadDays * 3600 * 24;
+ }
+ if (readDays > 0) {
+ kdDebug(5006) << "ExpireJob: deleting read older than "<< readDays << " days" << endl;
+ mMaxReadTime = time(0) - readDays * 3600 * 24;
+ }
+
+ if ((mMaxUnreadTime == 0) && (mMaxReadTime == 0)) {
+ kdDebug(5006) << "ExpireJob: nothing to do" << endl;
+ delete this;
+ return;
+ }
+
+ FolderStorage* storage = mSrcFolder->storage();
+ mOpeningFolder = true; // Ignore open-notifications while opening the folder
+ storage->open( "expirejob" );
+ mOpeningFolder = false;
+ mFolderOpen = true;
+ mCurrentIndex = storage->count()-1;
+ kdDebug(5006) << "ExpireJob: starting to expire in folder " << mSrcFolder->location() << endl;
+ connect( &mTimer, SIGNAL( timeout() ), SLOT( slotDoWork() ) );
+ mTimer.start( EXPIREJOB_TIMERINTERVAL );
+ slotDoWork();
+ // do nothing here, we might be deleted!
+}
+
+void ExpireJob::slotDoWork()
+{
+ // No need to worry about mSrcFolder==0 here. The FolderStorage deletes the jobs on destruction.
+ FolderStorage* storage = mSrcFolder->storage();
+ int stopIndex = mImmediate ? 0 : QMAX( 0, mCurrentIndex - EXPIREJOB_NRMESSAGES );
+#ifdef DEBUG_SCHEDULER
+ kdDebug(5006) << "ExpireJob: checking messages " << mCurrentIndex << " to " << stopIndex << endl;
+#endif
+ for( ; mCurrentIndex >= stopIndex; mCurrentIndex-- ) {
+ const KMMsgBase *mb = storage->getMsgBase( mCurrentIndex );
+ if (mb == 0)
+ continue;
+ if ( ( mb->isImportant() || mb->isTodo() || mb->isWatched() )
+ && GlobalSettings::self()->excludeImportantMailFromExpiry() )
+ continue;
+
+ time_t maxTime = mb->isUnread() ? mMaxUnreadTime : mMaxReadTime;
+
+ if (mb->date() < maxTime) {
+ mRemovedMsgs.append( storage->getMsgBase( mCurrentIndex ) );
+ }
+ }
+ if ( stopIndex == 0 )
+ done();
+}
+
+void ExpireJob::done()
+{
+ mTimer.stop();
+
+ QString str;
+ bool moving = false;
+
+ if ( !mRemovedMsgs.isEmpty() ) {
+ int count = mRemovedMsgs.count();
+ // The command shouldn't kill us because it opens the folder
+ mCancellable = false;
+ if ( mSrcFolder->expireAction() == KMFolder::ExpireDelete ) {
+ // Expire by deletion, i.e. move to null target folder
+ kdDebug(5006) << "ExpireJob: finished expiring in folder "
+ << mSrcFolder->location()
+ << " " << count << " messages to remove." << endl;
+ KMMoveCommand* cmd = new KMMoveCommand( 0, mRemovedMsgs );
+ connect( cmd, SIGNAL( completed( KMCommand * ) ),
+ this, SLOT( slotMessagesMoved( KMCommand * ) ) );
+ cmd->start();
+ moving = true;
+ str = i18n( "Removing 1 old message from folder %1...",
+ "Removing %n old messages from folder %1...", count )
+ .arg( mSrcFolder->label() );
+ } else {
+ // Expire by moving
+ mMoveToFolder =
+ kmkernel->findFolderById( mSrcFolder->expireToFolderId() );
+ if ( !mMoveToFolder ) {
+ str = i18n( "Cannot expire messages from folder %1: destination "
+ "folder %2 not found" )
+ .arg( mSrcFolder->label(), mSrcFolder->expireToFolderId() );
+ kdWarning(5006) << str << endl;
+ } else {
+ kdDebug(5006) << "ExpireJob: finished expiring in folder "
+ << mSrcFolder->location() << " "
+ << mRemovedMsgs.count() << " messages to move to "
+ << mMoveToFolder->label() << endl;
+ KMMoveCommand* cmd = new KMMoveCommand( mMoveToFolder, mRemovedMsgs );
+ connect( cmd, SIGNAL( completed( KMCommand * ) ),
+ this, SLOT( slotMessagesMoved( KMCommand * ) ) );
+ cmd->start();
+ moving = true;
+ str = i18n( "Moving 1 old message from folder %1 to folder %2...",
+ "Moving %n old messages from folder %1 to folder %2...",
+ count )
+ .arg( mSrcFolder->label(), mMoveToFolder->label() );
+ }
+ }
+ }
+ if ( !str.isEmpty() )
+ BroadcastStatus::instance()->setStatusMsg( str );
+
+ KConfigGroup group( KMKernel::config(), "Folder-" + mSrcFolder->idString() );
+ group.writeEntry( "Current", -1 ); // i.e. make it invalid, the serial number will be used
+
+ if ( !moving ) {
+ mSrcFolder->storage()->close( "expirejob" );
+ mFolderOpen = false;
+ delete this;
+ }
+}
+
+void ExpireJob::slotMessagesMoved( KMCommand *command )
+{
+ mSrcFolder->storage()->close( "expirejob" );
+ mFolderOpen = false;
+ QString msg;
+ switch ( command->result() ) {
+ case KMCommand::OK:
+ if ( mSrcFolder->expireAction() == KMFolder::ExpireDelete ) {
+ msg = i18n( "Removed 1 old message from folder %1.",
+ "Removed %n old messages from folder %1.",
+ mRemovedMsgs.count() )
+ .arg( mSrcFolder->label() );
+ }
+ else {
+ msg = i18n( "Moved 1 old message from folder %1 to folder %2.",
+ "Moved %n old messages from folder %1 to folder %2.",
+ mRemovedMsgs.count() )
+ .arg( mSrcFolder->label(), mMoveToFolder->label() );
+ }
+ break;
+ case KMCommand::Failed:
+ if ( mSrcFolder->expireAction() == KMFolder::ExpireDelete ) {
+ msg = i18n( "Removing old messages from folder %1 failed." )
+ .arg( mSrcFolder->label() );
+ }
+ else {
+ msg = i18n( "Moving old messages from folder %1 to folder %2 failed." )
+ .arg( mSrcFolder->label(), mMoveToFolder->label() );
+ }
+ break;
+ case KMCommand::Canceled:
+ if ( mSrcFolder->expireAction() == KMFolder::ExpireDelete ) {
+ msg = i18n( "Removing old messages from folder %1 was canceled." )
+ .arg( mSrcFolder->label() );
+ }
+ else {
+ msg = i18n( "Moving old messages from folder %1 to folder %2 was "
+ "canceled." )
+ .arg( mSrcFolder->label(), mMoveToFolder->label() );
+ }
+ default: ;
+ }
+ BroadcastStatus::instance()->setStatusMsg( msg );
+
+ deleteLater();
+}
+
+#include "expirejob.moc"
diff --git a/kmail/expirejob.h b/kmail/expirejob.h
new file mode 100644
index 00000000..71bfc65e
--- /dev/null
+++ b/kmail/expirejob.h
@@ -0,0 +1,81 @@
+/* -*- mode: C++ -*-
+ * Copyright (c) 2004 David Faure <faure@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+#ifndef EXPIREJOB_H
+#define EXPIREJOB_H
+
+#include "jobscheduler.h"
+#include "kmcommands.h"
+
+namespace KMail {
+
+class ExpireJob : public ScheduledJob
+{
+ Q_OBJECT
+public:
+ ExpireJob( KMFolder* folder, bool immediate );
+ virtual ~ExpireJob();
+
+ virtual void execute();
+ virtual void kill();
+
+private slots:
+ void slotDoWork();
+ void slotMessagesMoved( KMCommand *command );
+
+private:
+ void done();
+
+private:
+ QTimer mTimer;
+ QPtrList<KMMsgBase> mRemovedMsgs;
+ int mCurrentIndex;
+ int mMaxUnreadTime;
+ int mMaxReadTime;
+ bool mFolderOpen;
+ KMFolder *mMoveToFolder;
+};
+
+/// A scheduled "expire mails in this folder" task.
+class ScheduledExpireTask : public ScheduledTask
+{
+public:
+ /// If immediate is set, the job will execute synchronously. This is used when
+ /// the user requests explicitely that the operation should happen immediately.
+ ScheduledExpireTask( KMFolder* folder, bool immediate )
+ : ScheduledTask( folder, immediate ) {}
+ virtual ~ScheduledExpireTask() {}
+ virtual ScheduledJob* run() {
+ return folder() ? new ExpireJob( folder(), isImmediate() ) : 0;
+ }
+ virtual int taskTypeId() const { return 1; }
+};
+
+} // namespace
+
+#endif /* EXPIREJOB_H */
+
diff --git a/kmail/expirypropertiesdialog.cpp b/kmail/expirypropertiesdialog.cpp
new file mode 100644
index 00000000..62d9047f
--- /dev/null
+++ b/kmail/expirypropertiesdialog.cpp
@@ -0,0 +1,197 @@
+
+#include "expirypropertiesdialog.h"
+#include "folderrequester.h"
+#include "kmfolder.h"
+#include "kmfoldertree.h"
+
+#include <qvariant.h>
+#include <qpushbutton.h>
+#include <qgroupbox.h>
+#include <qcheckbox.h>
+#include <qspinbox.h>
+#include <qlabel.h>
+#include <qradiobutton.h>
+#include <qbuttongroup.h>
+#include <qcombobox.h>
+#include <qlayout.h>
+#include <qtooltip.h>
+#include <qwhatsthis.h>
+
+#include <klocale.h>
+#include <kmessagebox.h>
+
+using namespace KMail;
+
+/*
+ * Constructs a ExpiryPropertiesDialog as a child of 'parent', with the
+ * name 'name'.
+ *
+ */
+ExpiryPropertiesDialog::ExpiryPropertiesDialog( KMFolderTree* tree, KMFolder* folder )
+ : KDialogBase( tree, "expiry_properties", false, i18n( "Mail Expiry Properties" ),
+ KDialogBase::Ok|KDialogBase::Cancel,
+ KDialogBase::Ok, true ),
+ mFolder( folder )
+{
+ setWFlags( getWFlags() | WDestructiveClose );
+ QWidget* privateLayoutWidget = new QWidget( this, "globalVBox" );
+ setMainWidget( privateLayoutWidget );
+ privateLayoutWidget->setGeometry( QRect( 10, 20, 270, 138 ) );
+ globalVBox = new QVBoxLayout( privateLayoutWidget, 11, 6, "globalVBox");
+ globalVBox->setSpacing( 20 );
+
+ readHBox = new QHBoxLayout( 0, 0, 6, "readHBox");
+
+ expireReadMailCB = new QCheckBox( privateLayoutWidget, "expireReadMailCB" );
+ expireReadMailCB->setText( i18n( "Expire read mails after" ) );
+ connect( expireReadMailCB, SIGNAL( toggled( bool )),
+ this, SLOT( slotUpdateControls() ) );
+ readHBox->addWidget( expireReadMailCB );
+
+ expireReadMailSB = new QSpinBox( privateLayoutWidget, "expireReadMailSB" );
+ expireReadMailSB->setMaxValue( 999999 );
+ expireReadMailSB->setValue( 30 );
+ readHBox->addWidget( expireReadMailSB );
+
+ labelDays = new QLabel( privateLayoutWidget, "labelDays" );
+ labelDays->setText( i18n( "days" ) );
+ readHBox->addWidget( labelDays );
+ globalVBox->addLayout( readHBox );
+
+ unreadHBox = new QHBoxLayout( 0, 0, 6, "unreadHBox");
+
+ expireUnreadMailCB = new QCheckBox( privateLayoutWidget, "expireUnreadMailCB" );
+ expireUnreadMailCB->setText( i18n( "Expire unread mails after" ) );
+ connect( expireUnreadMailCB, SIGNAL( toggled( bool )),
+ this, SLOT( slotUpdateControls() ) );
+ unreadHBox->addWidget( expireUnreadMailCB );
+
+ expireUnreadMailSB = new QSpinBox( privateLayoutWidget, "expireUnreadMailSB" );
+ expireUnreadMailSB->setMaxValue( 99999 );
+ expireUnreadMailSB->setValue( 30 );
+ unreadHBox->addWidget( expireUnreadMailSB );
+
+ labelDays2 = new QLabel( privateLayoutWidget, "labelDays2" );
+ labelDays2->setText( i18n( "days" ) );
+ labelDays2->setAlignment( int( QLabel::AlignTop ) );
+ unreadHBox->addWidget( labelDays2 );
+ globalVBox->addLayout( unreadHBox );
+
+ expiryActionHBox = new QHBoxLayout( 0, 0, 6, "expiryActionHBox");
+
+ expiryActionLabel = new QLabel( privateLayoutWidget, "expiryActionLabel" );
+ expiryActionLabel->setText( i18n( "Expiry action:" ) );
+ expiryActionLabel->setAlignment( int( QLabel::AlignVCenter ) );
+ expiryActionHBox->addWidget( expiryActionLabel );
+
+ actionsHBox = new QVBoxLayout( 0, 0, 6, "actionsHBox");
+ actionsGroup = new QButtonGroup( this );
+ actionsGroup->hide(); // for mutual exclusion of the radio buttons
+
+ moveToHBox = new QHBoxLayout( 0, 0, 6, "moveToHBox");
+
+ moveToRB = new QRadioButton( privateLayoutWidget, "moveToRB" );
+ actionsGroup->insert( moveToRB );
+ connect( moveToRB, SIGNAL( toggled( bool ) ),
+ this, SLOT( slotUpdateControls() ) );
+ moveToRB->setText( i18n( "Move to:" ) );
+ moveToHBox->addWidget( moveToRB );
+
+ folderSelector = new KMail::FolderRequester( privateLayoutWidget, tree );
+ folderSelector->setMustBeReadWrite( true );
+ moveToHBox->addWidget( folderSelector );
+ actionsHBox->addLayout( moveToHBox );
+
+ deletePermanentlyRB = new QRadioButton( privateLayoutWidget, "deletePermanentlyRB" );
+ actionsGroup->insert( deletePermanentlyRB );
+ deletePermanentlyRB->setText( i18n( "Delete permanently" ) );
+ actionsHBox->addWidget( deletePermanentlyRB );
+ expiryActionHBox->addLayout( actionsHBox );
+ globalVBox->addLayout( expiryActionHBox );
+
+ note = new QLabel( privateLayoutWidget, "note" );
+ note->setText( i18n( "Note: Expiry action will be applied immediately after confirming settings." ) );
+ note->setAlignment( int( QLabel::WordBreak | QLabel::AlignVCenter ) );
+ globalVBox->addWidget( note );
+
+ // Load the values from the folder
+ bool expiryGloballyOn = mFolder->isAutoExpire();
+ int daysToExpireRead, daysToExpireUnread;
+ mFolder->daysToExpire( daysToExpireUnread, daysToExpireRead);
+
+ if ( expiryGloballyOn
+ && mFolder->getReadExpireUnits() != expireNever
+ && daysToExpireRead >= 0 ) {
+ expireReadMailCB->setChecked( true );
+ expireReadMailSB->setValue( daysToExpireRead );
+ }
+ if ( expiryGloballyOn
+ && mFolder->getUnreadExpireUnits() != expireNever
+ && daysToExpireUnread >= 0 ) {
+ expireUnreadMailCB->setChecked( true );
+ expireUnreadMailSB->setValue( daysToExpireUnread );
+ }
+
+ if ( mFolder->expireAction() == KMFolder::ExpireDelete )
+ deletePermanentlyRB->setChecked( true );
+ else
+ moveToRB->setChecked( true );
+
+ QString destFolderID = mFolder->expireToFolderId();
+ if ( !destFolderID.isEmpty() ) {
+ KMFolder* destFolder = kmkernel->findFolderById( destFolderID );
+ if ( destFolder )
+ folderSelector->setFolder( destFolder );
+ }
+ slotUpdateControls();
+ resize( QSize(295, 204).expandedTo(minimumSizeHint()) );
+ clearWState( WState_Polished );
+}
+
+/*
+ * Destroys the object and frees any allocated resources
+ */
+ExpiryPropertiesDialog::~ExpiryPropertiesDialog()
+{
+ // no need to delete child widgets, Qt does it all for us
+}
+
+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" ) );
+ return;
+ }
+ mFolder->setAutoExpire( enableGlobally );
+ // we always write out days now
+ mFolder->setReadExpireAge( expireReadMailSB->value() );
+ mFolder->setUnreadExpireAge( expireUnreadMailSB->value() );
+ mFolder->setReadExpireUnits( expireReadMailCB->isChecked()? expireDays : expireNever );
+ mFolder->setUnreadExpireUnits( expireUnreadMailCB->isChecked()? expireDays : expireNever );
+
+ if ( deletePermanentlyRB->isChecked() )
+ 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 )
+ mFolder->expireOldMessages( true /*immediate*/);
+ KDialogBase::slotOk();
+}
+
+void ExpiryPropertiesDialog::slotUpdateControls()
+{
+ bool showExpiryActions = expireReadMailCB->isChecked() || expireUnreadMailCB->isChecked();
+ moveToRB->setEnabled( showExpiryActions );
+ folderSelector->setEnabled( showExpiryActions && moveToRB->isChecked() );
+ deletePermanentlyRB->setEnabled( showExpiryActions );
+}
+
+
+#include "expirypropertiesdialog.moc"
diff --git a/kmail/expirypropertiesdialog.h b/kmail/expirypropertiesdialog.h
new file mode 100644
index 00000000..9ac32500
--- /dev/null
+++ b/kmail/expirypropertiesdialog.h
@@ -0,0 +1,68 @@
+/****************************************************************************
+** Form interface generated from reading ui file 'expirypropertiesdialog.ui'
+**
+** Created: Sat Jan 29 12:59:18 2005
+** by: The User Interface Compiler ($Id$)
+**
+** WARNING! All changes made in this file will be lost!
+****************************************************************************/
+
+#ifndef EXPIRYPROPERTIESDIALOG_H
+#define EXPIRYPROPERTIESDIALOG_H
+
+#include <qvariant.h>
+#include <kdialogbase.h>
+
+class QVBoxLayout;
+class QHBoxLayout;
+class QGridLayout;
+class QSpacerItem;
+class QGroupBox;
+class QCheckBox;
+class QSpinBox;
+class QLabel;
+class QRadioButton;
+class QButtonGroup;
+class KMFolderTree;
+class KMFolder;
+
+namespace KMail {
+
+ class FolderRequester;
+
+class ExpiryPropertiesDialog : public KDialogBase
+{
+ Q_OBJECT
+
+public:
+ ExpiryPropertiesDialog( KMFolderTree* tree, KMFolder* folder );
+ ~ExpiryPropertiesDialog();
+
+ QCheckBox* expireReadMailCB;
+ QSpinBox* expireReadMailSB;
+ QLabel* labelDays;
+ QCheckBox* expireUnreadMailCB;
+ QSpinBox* expireUnreadMailSB;
+ QLabel* labelDays2;
+ QLabel* expiryActionLabel;
+ QRadioButton* moveToRB;
+ FolderRequester *folderSelector;
+ QRadioButton* deletePermanentlyRB;
+ QLabel* note;
+ QButtonGroup* actionsGroup;
+
+protected slots:
+ void slotOk();
+ void slotUpdateControls();
+
+protected:
+ QVBoxLayout* globalVBox;
+ QHBoxLayout* readHBox;
+ QHBoxLayout* unreadHBox;
+ QHBoxLayout* expiryActionHBox;
+ QVBoxLayout* actionsHBox;
+ QHBoxLayout* moveToHBox;
+ KMFolder* mFolder;
+};
+} // namespace
+#endif // EXPIRYPROPERTIESDIALOG_H
diff --git a/kmail/favoritefolderview.cpp b/kmail/favoritefolderview.cpp
new file mode 100644
index 00000000..abe223d8
--- /dev/null
+++ b/kmail/favoritefolderview.cpp
@@ -0,0 +1,517 @@
+/*
+ Copyright (c) 2007 Volker Krause <vkrause@kde.org>
+
+ 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 "favoritefolderview.h"
+
+#include "kmfolder.h"
+#include "kmfoldermgr.h"
+#include "kmfolderseldlg.h"
+#include "kmmainwidget.h"
+#include "kmailicalifaceimpl.h"
+#include "folderstorage.h"
+#include "kmfolderimap.h"
+#include "kmfoldercachedimap.h"
+#include "kmacctcachedimap.h"
+#include "folderviewtooltip.h"
+#include "korghelper.h"
+
+#include <libkdepim/maillistdrag.h>
+#include <libkdepim/kaddrbook.h>
+
+#include <dcopclient.h>
+#include <kdebug.h>
+#include <kglobalsettings.h>
+#include <kiconloader.h>
+#include <kinputdialog.h>
+#include <klocale.h>
+#include <kpopupmenu.h>
+#include <kio/global.h>
+
+#include <qheader.h>
+#include <qtimer.h>
+
+#include <cassert>
+
+using namespace KMail;
+
+FavoriteFolderViewItem::FavoriteFolderViewItem(FavoriteFolderView * parent, const QString & name, KMFolder * folder)
+ : KMFolderTreeItem( parent, name, folder ),
+ mOldName( folder->label() )
+{
+ // same stuff as in KMFolderTreeItem again, this time even with virtual methods working
+ init();
+ connect( folder, SIGNAL(nameChanged()), SLOT(nameChanged()) );
+ connect( folder, SIGNAL(iconsChanged()), SLOT(slotIconsChanged()) );
+
+ connect( folder, SIGNAL(msgAdded(KMFolder*,Q_UINT32)), SLOT(updateCount()) );
+ connect( folder, SIGNAL(numUnreadMsgsChanged(KMFolder*)), SLOT(updateCount()) );
+ connect( folder, SIGNAL(msgRemoved(KMFolder*)), SLOT(updateCount()) );
+ connect( folder, SIGNAL(folderSizeChanged( KMFolder* )), SLOT(updateCount()) );
+
+ QTimer::singleShot( 0, this, SLOT(updateCount()) );
+
+ if ( unreadCount() > 0 )
+ setPixmap( 0, unreadIcon( iconSize() ) );
+ else
+ setPixmap( 0, normalIcon( iconSize() ) );
+}
+
+void FavoriteFolderViewItem::nameChanged()
+{
+ QString txt = text( 0 );
+ txt.replace( mOldName, folder()->label() );
+ setText( 0, txt );
+ mOldName = folder()->label();
+}
+
+QValueList<FavoriteFolderView*> FavoriteFolderView::mInstances;
+
+FavoriteFolderView::FavoriteFolderView( KMMainWidget *mainWidget, QWidget * parent) :
+ FolderTreeBase( mainWidget, parent ),
+ mContextMenuItem( 0 ),
+ mReadingConfig( false )
+{
+ assert( mainWidget );
+ addColumn( i18n("Favorite Folders") );
+ setResizeMode( LastColumn );
+ header()->setClickEnabled( false );
+ setDragEnabled( true );
+ setAcceptDrops( true );
+ setRootIsDecorated( false );
+ setSelectionModeExt( KListView::Single );
+ setSorting( -1 );
+ setShowSortIndicator( false );
+
+ connect( this, SIGNAL(selectionChanged()), SLOT(selectionChanged()) );
+ connect( this, SIGNAL(clicked(QListViewItem*)), SLOT(itemClicked(QListViewItem*)) );
+ connect( this, SIGNAL(dropped(QDropEvent*,QListViewItem*)), SLOT(dropped(QDropEvent*,QListViewItem*)) );
+ connect( this, SIGNAL(contextMenuRequested(QListViewItem*, const QPoint &, int)),
+ SLOT(contextMenu(QListViewItem*,const QPoint&)) );
+ connect( this, SIGNAL(moved()), SLOT(notifyInstancesOnChange()) );
+ connect( this, SIGNAL(triggerRefresh()), SLOT(refresh()) );
+
+ connect( kmkernel->folderMgr(), SIGNAL(changed()), SLOT(initializeFavorites()) );
+ connect( kmkernel->dimapFolderMgr(), SIGNAL(changed()), SLOT(initializeFavorites()) );
+ connect( kmkernel->imapFolderMgr(), SIGNAL(changed()), SLOT(initializeFavorites()) );
+ connect( kmkernel->searchFolderMgr(), SIGNAL(changed()), SLOT(initializeFavorites()) );
+
+ connect( kmkernel->folderMgr(), SIGNAL(folderRemoved(KMFolder*)), SLOT(folderRemoved(KMFolder*)) );
+ connect( kmkernel->dimapFolderMgr(), SIGNAL(folderRemoved(KMFolder*)), SLOT(folderRemoved(KMFolder*)) );
+ connect( kmkernel->imapFolderMgr(), SIGNAL(folderRemoved(KMFolder*)), SLOT(folderRemoved(KMFolder*)) );
+ connect( kmkernel->searchFolderMgr(), SIGNAL(folderRemoved(KMFolder*)), SLOT(folderRemoved(KMFolder*)) );
+
+ QFont f = font();
+ f.setItalic( true );
+ setFont( f );
+
+ new FolderViewToolTip( this );
+
+ mInstances.append( this );
+}
+
+FavoriteFolderView::~FavoriteFolderView()
+{
+ mInstances.remove( this );
+}
+
+void FavoriteFolderView::readConfig()
+{
+ mReadingConfig = true;
+ clear();
+ QValueList<int> folderIds = GlobalSettings::self()->favoriteFolderIds();
+ QStringList folderNames = GlobalSettings::self()->favoriteFolderNames();
+ QListViewItem *afterItem = 0;
+ for ( uint i = 0; i < folderIds.count(); ++i ) {
+ KMFolder *folder = kmkernel->folderMgr()->findById( folderIds[i] );
+ if ( !folder )
+ folder = kmkernel->imapFolderMgr()->findById( folderIds[i] );
+ if ( !folder )
+ folder = kmkernel->dimapFolderMgr()->findById( folderIds[i] );
+ if ( !folder )
+ folder = kmkernel->searchFolderMgr()->findById( folderIds[i] );
+ QString name;
+ if ( folderNames.count() > i )
+ name = folderNames[i];
+ afterItem = addFolder( folder, name, afterItem );
+ }
+ if ( firstChild() )
+ ensureItemVisible( firstChild() );
+
+ // folder tree is not yet populated at this point
+ QTimer::singleShot( 0, this, SLOT(initializeFavorites()) );
+
+ readColorConfig();
+ mReadingConfig = false;
+}
+
+void FavoriteFolderView::writeConfig()
+{
+ QValueList<int> folderIds;
+ QStringList folderNames;
+ for ( QListViewItemIterator it( this ); it.current(); ++it ) {
+ KMFolderTreeItem *fti = static_cast<KMFolderTreeItem*>( it.current() );
+ folderIds << fti->folder()->id();
+ folderNames << fti->text( 0 );
+ }
+ GlobalSettings::self()->setFavoriteFolderIds( folderIds );
+ GlobalSettings::self()->setFavoriteFolderNames( folderNames );
+}
+
+bool FavoriteFolderView::acceptDrag(QDropEvent * e) const
+{
+ KMFolderTree *ft = mainWidget()->folderTree();
+ assert( ft );
+ if ( e->provides( "application/x-qlistviewitem" ) &&
+ (e->source() == ft->viewport() || e->source() == viewport() ) )
+ return true;
+ return FolderTreeBase::acceptDrag( e );
+}
+
+KMFolderTreeItem* FavoriteFolderView::addFolder(KMFolder * folder, const QString &name, QListViewItem *after)
+{
+ if ( !folder )
+ return 0;
+ KMFolderTreeItem *item = new FavoriteFolderViewItem( this, name.isEmpty() ? folder->label() : name, folder );
+ if ( after )
+ item->moveItem( after );
+ else
+ item->moveItem( lastItem() );
+ ensureItemVisible( item );
+ insertIntoFolderToItemMap( folder, item );
+ notifyInstancesOnChange();
+ return item;
+}
+
+void FavoriteFolderView::selectionChanged()
+{
+ KMFolderTreeItem *fti = static_cast<KMFolderTreeItem*>( selectedItem() );
+ if ( !fti )
+ return;
+ KMFolderTree *ft = mainWidget()->folderTree();
+ assert( ft );
+ assert( fti );
+ ft->showFolder( fti->folder() );
+ handleGroupwareFolder( fti );
+}
+
+void FavoriteFolderView::handleGroupwareFolder( KMFolderTreeItem *fti )
+{
+ if ( !fti || !fti->folder() || !fti->folder()->storage() )
+ return;
+ switch ( fti->folder()->storage()->contentsType() ) {
+ case KMail::ContentsTypeContact:
+ KAddrBookExternal::openAddressBook( this );
+ break;
+ case KMail::ContentsTypeNote:
+ {
+ QByteArray arg;
+ QDataStream s( arg, IO_WriteOnly );
+ s << QString( "kontact_knotesplugin" );
+ kapp->dcopClient()->send( "kontact", "KontactIface", "selectPlugin(QString)", arg );
+ break;
+ }
+ case KMail::ContentsTypeCalendar:
+ case KMail::ContentsTypeTask:
+ case KMail::ContentsTypeJournal:
+ {
+ KMail::KorgHelper::ensureRunning();
+ QByteArray arg;
+ QDataStream s( arg, IO_WriteOnly );
+ switch ( fti->folder()->storage()->contentsType() ) {
+ case KMail::ContentsTypeCalendar:
+ s << QString( "kontact_korganizerplugin" ); break;
+ case KMail::ContentsTypeTask:
+ s << QString( "kontact_todoplugin" ); break;
+ case KMail::ContentsTypeJournal:
+ s << QString( "kontact_journalplugin" ); break;
+ default: assert( false );
+ }
+ kapp->dcopClient()->send( "kontact", "KontactIface", "selectPlugin(QString)", arg );
+ break;
+ }
+ default: break;
+ }
+}
+
+void FavoriteFolderView::itemClicked(QListViewItem * item)
+{
+ if ( !item ) return;
+ if ( !item->isSelected() )
+ item->setSelected( true );
+ item->repaint();
+ handleGroupwareFolder( static_cast<KMFolderTreeItem*>( item ) );
+}
+
+void FavoriteFolderView::folderTreeSelectionChanged(KMFolder * folder)
+{
+ blockSignals( true );
+ bool found = false;
+ for ( QListViewItemIterator it( this ); it.current(); ++it ) {
+ KMFolderTreeItem *fti = static_cast<KMFolderTreeItem*>( it.current() );
+ if ( fti->folder() == folder && !fti->isSelected() ) {
+ fti->setSelected( true );
+ setCurrentItem( fti );
+ ensureItemVisible( fti );
+ fti->repaint();
+ found = true;
+ } else if ( fti->folder() != folder && fti->isSelected() ) {
+ fti->setSelected( false );
+ fti->repaint();
+ }
+ }
+ blockSignals( false );
+ if ( !found ) {
+ clearSelection();
+ setSelectionModeExt( KListView::NoSelection );
+ setSelectionModeExt( KListView::Single );
+ }
+}
+
+void FavoriteFolderView::folderRemoved(KMFolder * folder)
+{
+ QValueList<KMFolderTreeItem*> delItems;
+ for ( QListViewItemIterator it( this ); it.current(); ++it ) {
+ KMFolderTreeItem *fti = static_cast<KMFolderTreeItem*>( it.current() );
+ if ( fti->folder() == folder )
+ delItems << fti;
+ if ( fti == mContextMenuItem )
+ mContextMenuItem = 0;
+ }
+ for ( uint i = 0; i < delItems.count(); ++i )
+ delete delItems[i];
+ removeFromFolderToItemMap(folder);
+}
+
+void FavoriteFolderView::dropped(QDropEvent * e, QListViewItem * after)
+{
+ QListViewItem* afterItem = after;
+ KMFolderTree *ft = mainWidget()->folderTree();
+ assert( ft );
+ if ( e->source() == ft->viewport() && e->provides( "application/x-qlistviewitem" ) ) {
+ for ( QListViewItemIterator it( ft ); it.current(); ++it ) {
+ if ( !it.current()->isSelected() )
+ continue;
+ KMFolderTreeItem *fti = static_cast<KMFolderTreeItem*>( it.current() );
+ if ( !fti->folder() )
+ continue;
+ afterItem = addFolder( fti->folder(), prettyName( fti ), afterItem );
+ }
+ e->accept();
+ }
+}
+
+void FavoriteFolderView::contextMenu(QListViewItem * item, const QPoint & point)
+{
+ KMFolderTree *ft = mainWidget()->folderTree();
+ assert( ft );
+ KMFolderTreeItem *fti = static_cast<KMFolderTreeItem*>( item );
+ mContextMenuItem = fti;
+ KPopupMenu contextMenu;
+ if ( fti && fti->folder() ) {
+ contextMenu.insertItem( SmallIconSet("editdelete"), i18n("Remove From Favorites"),
+ this, SLOT(removeFolder()) );
+ contextMenu.insertItem( SmallIconSet("edit"), i18n("Rename Favorite"), this, 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 );
+
+ contextMenu.insertItem( SmallIconSet("configure_shortcuts"), i18n("&Assign Shortcut..."), fti, SLOT(assignShortcut()) );
+ contextMenu.insertItem( i18n("Expire..."), fti, SLOT(slotShowExpiryProperties()) );
+ mainWidget()->action("modify")->plug( &contextMenu );
+ } else {
+ contextMenu.insertItem( SmallIconSet("bookmark_add"), i18n("Add Favorite Folder..."),
+ this, SLOT(addFolder()) );
+ }
+ contextMenu.exec( point, 0 );
+}
+
+void FavoriteFolderView::removeFolder()
+{
+ delete mContextMenuItem;
+ mContextMenuItem = 0;
+ notifyInstancesOnChange();
+}
+
+void FavoriteFolderView::initializeFavorites()
+{
+ QValueList<int> seenInboxes = GlobalSettings::self()->favoriteFolderViewSeenInboxes();
+ KMFolderTree *ft = mainWidget()->folderTree();
+ assert( ft );
+ for ( QListViewItemIterator it( ft ); it.current(); ++it ) {
+ KMFolderTreeItem *fti = static_cast<KMFolderTreeItem*>( it.current() );
+ if ( fti->type() == KFolderTreeItem::Inbox && fti->folder() && !seenInboxes.contains( fti->folder()->id() ) ) {
+ seenInboxes.append( fti->folder()->id() );
+ if ( fti->folder() == kmkernel->inboxFolder() && hideLocalInbox() )
+ continue;
+ if ( kmkernel->iCalIface().hideResourceFolder( fti->folder() ) )
+ continue;
+ addFolder( fti->folder(), prettyName( fti ) );
+ }
+ }
+ GlobalSettings::self()->setFavoriteFolderViewSeenInboxes( seenInboxes );
+}
+
+void FavoriteFolderView::renameFolder()
+{
+ if ( !mContextMenuItem )
+ return;
+ bool ok;
+ QString name = KInputDialog::getText( i18n("Rename Favorite"), i18n("Name"), mContextMenuItem->text( 0 ), &ok, this );
+ if ( !ok )
+ return;
+ mContextMenuItem->setText( 0, name );
+ notifyInstancesOnChange();
+}
+
+QString FavoriteFolderView::prettyName(KMFolderTreeItem * fti)
+{
+ assert( fti );
+ assert( fti->folder() );
+ QString name = fti->folder()->label();
+ QListViewItem *accountFti = fti;
+ while ( accountFti->parent() )
+ accountFti = accountFti->parent();
+ if ( fti->type() == KFolderTreeItem::Inbox ) {
+ if ( fti->protocol() == KFolderTreeItem::Local || fti->protocol() == KFolderTreeItem::NONE ) {
+ name = i18n( "Local Inbox" );
+ } else {
+ name = i18n( "Inbox of %1" ).arg( accountFti->text( 0 ) );
+ }
+ } else {
+ if ( fti->protocol() != KFolderTreeItem::Local && fti->protocol() != KFolderTreeItem::NONE ) {
+ name = i18n( "%1 on %2" ).arg( fti->text( 0 ) ).arg( accountFti->text( 0 ) );
+ } else {
+ name = i18n( "%1 (local)" ).arg( fti->text( 0 ) );
+ }
+ }
+ return name;
+}
+
+void FavoriteFolderView::contentsDragEnterEvent(QDragEnterEvent * e)
+{
+ if ( e->provides( "application/x-qlistviewitem" ) ) {
+ setDropVisualizer( true );
+ setDropHighlighter( false );
+ } else if ( e->provides( KPIM::MailListDrag::format() ) ) {
+ setDropVisualizer( false );
+ setDropHighlighter( true );
+ } else {
+ setDropVisualizer( false );
+ setDropHighlighter( false );
+ }
+ FolderTreeBase::contentsDragEnterEvent( e );
+}
+
+void FavoriteFolderView::readColorConfig()
+{
+ FolderTreeBase::readColorConfig();
+ KConfig* conf = KMKernel::config();
+ // Custom/System color support
+ KConfigGroupSaver saver(conf, "Reader");
+ QColor c = KGlobalSettings::alternateBackgroundColor();
+ if ( !conf->readBoolEntry("defaultColors", true) )
+ mPaintInfo.colBack = conf->readColorEntry( "AltBackgroundColor",&c );
+ else
+ mPaintInfo.colBack = c;
+
+ QPalette newPal = palette();
+ newPal.setColor( QColorGroup::Base, mPaintInfo.colBack );
+ setPalette( newPal );
+}
+
+void FavoriteFolderView::addFolder()
+{
+ KMFolderSelDlg dlg( mainWidget(), i18n("Add Favorite Folder"), false );
+ if ( dlg.exec() != QDialog::Accepted )
+ return;
+ KMFolder *folder = dlg.folder();
+ if ( !folder )
+ return;
+ KMFolderTreeItem *fti = findFolderTreeItem( folder );
+ addFolder( folder, fti ? prettyName( fti ) : folder->label() );
+}
+
+void KMail::FavoriteFolderView::addFolder(KMFolderTreeItem * fti)
+{
+ if ( !fti || !fti->folder() )
+ return;
+ addFolder( fti->folder(), prettyName( fti ) );
+}
+
+KMFolderTreeItem * FavoriteFolderView::findFolderTreeItem(KMFolder * folder) const
+{
+ assert( folder );
+ KMFolderTree *ft = mainWidget()->folderTree();
+ assert( ft );
+ for ( QListViewItemIterator it( ft ); it.current(); ++it ) {
+ KMFolderTreeItem *fti = static_cast<KMFolderTreeItem*>( it.current() );
+ if ( fti->folder() == folder )
+ return fti;
+ }
+ return 0;
+}
+
+void FavoriteFolderView::checkMail()
+{
+ bool found = false;
+ for ( QListViewItemIterator it( this ); it.current(); ++it ) {
+ KMFolderTreeItem *fti = static_cast<KMFolderTreeItem*>( it.current() );
+ if ( fti->folder()->folderType() == KMFolderTypeImap || fti->folder()->folderType() == KMFolderTypeCachedImap ) {
+ if ( !found )
+ if ( !kmkernel->askToGoOnline() )
+ break;
+ found = true;
+ if ( fti->folder()->folderType() == KMFolderTypeImap ) {
+ KMFolderImap *imap = static_cast<KMFolderImap*>( fti->folder()->storage() );
+ imap->getAndCheckFolder();
+ } else if ( fti->folder()->folderType() == KMFolderTypeCachedImap ) {
+ KMFolderCachedImap* f = static_cast<KMFolderCachedImap*>( fti->folder()->storage() );
+ f->account()->processNewMailSingleFolder( fti->folder() );
+ }
+ }
+ }
+}
+
+void FavoriteFolderView::notifyInstancesOnChange()
+{
+ if ( mReadingConfig )
+ return;
+ writeConfig();
+ for ( QValueList<FavoriteFolderView*>::ConstIterator it = mInstances.begin(); it != mInstances.end(); ++it ) {
+ if ( (*it) == this || (*it)->mReadingConfig )
+ continue;
+ (*it)->readConfig();
+ }
+}
+
+void FavoriteFolderView::refresh()
+{
+ for ( QListViewItemIterator it( this ) ; it.current() ; ++it ) {
+ KMFolderTreeItem* fti = static_cast<KMFolderTreeItem*>(it.current());
+ if (!fti || !fti->folder())
+ continue;
+ fti->repaint();
+ }
+ update();
+}
+
+#include "favoritefolderview.moc"
diff --git a/kmail/favoritefolderview.h b/kmail/favoritefolderview.h
new file mode 100644
index 00000000..6958a732
--- /dev/null
+++ b/kmail/favoritefolderview.h
@@ -0,0 +1,95 @@
+/*
+ Copyright (c) 2007 Volker Krause <vkrause@kde.org>
+
+ 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_FAVORITEFOLDERVIEW_H
+#define KMAIL_FAVORITEFOLDERVIEW_H
+
+#include "kmfoldertree.h"
+
+namespace KMail {
+
+class FavoriteFolderView;
+
+class FavoriteFolderViewItem : public KMFolderTreeItem
+{
+ Q_OBJECT
+ public:
+ FavoriteFolderViewItem( FavoriteFolderView *parent, const QString & name, KMFolder* folder );
+
+ protected:
+ bool useTopLevelIcon() const { return false; }
+ int iconSize() const { return 22; }
+
+ private slots:
+ void nameChanged();
+
+ private:
+ QString mOldName;
+};
+
+class FavoriteFolderView : public FolderTreeBase
+{
+ Q_OBJECT
+
+ public:
+ FavoriteFolderView( KMMainWidget *mainWidget, QWidget *parent = 0 );
+ ~FavoriteFolderView();
+
+ void readConfig();
+ void writeConfig();
+
+ KMFolderTreeItem* addFolder( KMFolder *folder, const QString &name = QString::null,
+ QListViewItem *after = 0 );
+ void addFolder( KMFolderTreeItem *fti );
+
+ public slots:
+ void folderTreeSelectionChanged( KMFolder *folder );
+ void checkMail();
+
+ protected:
+ bool acceptDrag(QDropEvent* e) const;
+ void contentsDragEnterEvent( QDragEnterEvent *e );
+ void readColorConfig();
+
+ private:
+ static QString prettyName( KMFolderTreeItem* fti );
+ KMFolderTreeItem* findFolderTreeItem( KMFolder* folder ) const;
+ void handleGroupwareFolder( KMFolderTreeItem *fti );
+
+ private slots:
+ void selectionChanged();
+ void itemClicked( QListViewItem *item );
+ void folderRemoved( KMFolder *folder );
+ void dropped( QDropEvent *e, QListViewItem *after );
+ void contextMenu( QListViewItem *item, const QPoint &point );
+ void removeFolder();
+ void initializeFavorites();
+ void renameFolder();
+ void addFolder();
+ void notifyInstancesOnChange();
+ void refresh();
+
+ private:
+ KMFolderTreeItem* mContextMenuItem;
+ static QValueList<FavoriteFolderView*> mInstances;
+ bool mReadingConfig;
+};
+
+}
+
+#endif
diff --git a/kmail/filehtmlwriter.cpp b/kmail/filehtmlwriter.cpp
new file mode 100644
index 00000000..28e1b758
--- /dev/null
+++ b/kmail/filehtmlwriter.cpp
@@ -0,0 +1,108 @@
+/* -*- c++ -*-
+ filehtmlwriter.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "filehtmlwriter.h"
+
+#include <kdebug.h>
+
+
+namespace KMail {
+
+ FileHtmlWriter::FileHtmlWriter( const QString & filename )
+ : HtmlWriter(),
+ mFile( filename.isEmpty() ? QString( "filehtmlwriter.out" ) : filename )
+ {
+ mStream.setEncoding( QTextStream::UnicodeUTF8 );
+ }
+
+ FileHtmlWriter::~FileHtmlWriter() {
+ if ( mFile.isOpen() ) {
+ kdWarning( 5006 ) << "FileHtmlWriter: file still open!" << endl;
+ mStream.unsetDevice();
+ mFile.close();
+ }
+ }
+
+ void FileHtmlWriter::begin( const QString & css ) {
+ openOrWarn();
+ if ( !css.isEmpty() )
+ write( "<!-- CSS Definitions \n" + css + "-->\n" );
+ }
+
+ void FileHtmlWriter::end() {
+ flush();
+ mStream.unsetDevice();
+ mFile.close();
+ }
+
+ void FileHtmlWriter::reset() {
+ if ( mFile.isOpen() ) {
+ mStream.unsetDevice();
+ mFile.close();
+ }
+ }
+
+ void FileHtmlWriter::write( const QString & str ) {
+ mStream << str;
+ flush();
+ }
+
+ void FileHtmlWriter::queue( const QString & str ) {
+ write( str );
+ }
+
+ void FileHtmlWriter::flush() {
+ mFile.flush();
+ }
+
+ void FileHtmlWriter::openOrWarn() {
+ if ( mFile.isOpen() ) {
+ kdWarning( 5006 ) << "FileHtmlWriter: file still open!" << endl;
+ mStream.unsetDevice();
+ mFile.close();
+ }
+ if ( !mFile.open( IO_WriteOnly ) )
+ kdWarning( 5006 ) << "FileHtmlWriter: Cannot open file " << mFile.name() << endl;
+ else
+ mStream.setDevice( &mFile );
+ }
+
+ void FileHtmlWriter::embedPart( const QCString & contentId, const QString & url ) {
+ mStream << "<!-- embedPart(contentID=" << contentId << ", url=" << url << ") -->" << endl;
+ flush();
+ }
+
+
+} // namespace KMail
diff --git a/kmail/filehtmlwriter.h b/kmail/filehtmlwriter.h
new file mode 100644
index 00000000..f0b81e5a
--- /dev/null
+++ b/kmail/filehtmlwriter.h
@@ -0,0 +1,67 @@
+/* -*- c++ -*-
+ filehtmlwriter.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_FILEHTMLWRITER_H__
+#define __KMAIL_FILEHTMLWRITER_H__
+
+#include "interfaces/htmlwriter.h"
+
+#include <qfile.h>
+#include <qtextstream.h>
+
+class QString;
+
+namespace KMail {
+
+ class FileHtmlWriter : public KMail::HtmlWriter {
+ public:
+ FileHtmlWriter( const QString & filename );
+ virtual ~FileHtmlWriter();
+
+ void begin( const QString & cssDefs );
+ void end();
+ void reset();
+ void write( const QString & str );
+ void queue( const QString & str );
+ void flush();
+ void embedPart( const QCString & contentId, const QString & url );
+
+ private:
+ void openOrWarn();
+
+ private:
+ QFile mFile;
+ QTextStream mStream;
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_FILEHTMLWRITER_H__
diff --git a/kmail/filterimporterexporter.cpp b/kmail/filterimporterexporter.cpp
new file mode 100644
index 00000000..af77541d
--- /dev/null
+++ b/kmail/filterimporterexporter.cpp
@@ -0,0 +1,216 @@
+/*
+ This file is part of KMail.
+ Copyright (c) 2007 Till Adam <adam@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "filterimporterexporter.h"
+
+#include "kmfilter.h"
+#include "kmfilteraction.h"
+#include "util.h"
+
+#include <kconfig.h>
+#include <kdebug.h>
+#include <kfiledialog.h>
+#include <kdialogbase.h>
+#include <klistview.h>
+
+#include <qregexp.h>
+
+
+using namespace KMail;
+
+class FilterSelectionDialog : public KDialogBase
+{
+public:
+ FilterSelectionDialog( QWidget * 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( QListView::NoSelection );
+ filtersListView->addColumn( i18n("Filters"), 300 );
+ filtersListView->setFullWidth( true );
+ resize( 300, 350 );
+ }
+
+ virtual ~FilterSelectionDialog()
+ {
+ }
+
+ virtual void slotCancel()
+ {
+ wasCancelled = true;
+ KDialogBase::slotCancel();
+ }
+
+ bool cancelled()
+ {
+ return wasCancelled;
+ }
+
+ void setFilters( const QValueList<KMFilter*>& filters )
+ {
+ originalFilters = filters;
+ filtersListView->clear();
+ QValueListConstIterator<KMFilter*> it = filters.constEnd();
+ while ( it != filters.constBegin() ) {
+ --it;
+ KMFilter* filter = *it;
+ QCheckListItem* item = new QCheckListItem( filtersListView, filter->name(), QCheckListItem::CheckBox );
+ item->setOn( true );
+ }
+ }
+
+ QValueList<KMFilter*> selectedFilters() const
+ {
+ QValueList<KMFilter*> filters;
+ QListViewItemIterator it( filtersListView );
+ int i = 0;
+ while( it.current() ) {
+ QCheckListItem* item = static_cast<QCheckListItem*>( it.current() );
+ if ( item->isOn() )
+ filters << originalFilters[i];
+ ++i; ++it;
+ }
+ return filters;
+ }
+private:
+ KListView *filtersListView;
+ QValueList<KMFilter*> originalFilters;
+ bool wasCancelled;
+};
+
+/* static */
+QValueList<KMFilter*> FilterImporterExporter::readFiltersFromConfig( KConfig* config, bool bPopFilter )
+{
+ KConfigGroupSaver saver(config, "General");
+ int numFilters = 0;
+ if (bPopFilter)
+ numFilters = config->readNumEntry("popfilters",0);
+ else
+ numFilters = config->readNumEntry("filters",0);
+
+ QValueList<KMFilter*> filters;
+ for ( int i=0 ; i < numFilters ; ++i ) {
+ QString grpName;
+ grpName.sprintf("%s #%d", (bPopFilter ? "PopFilter" : "Filter") , i);
+ KConfigGroupSaver saver(config, grpName);
+ KMFilter * filter = new KMFilter(config, bPopFilter);
+ filter->purify();
+ if ( filter->isEmpty() ) {
+ #ifndef NDEBUG
+ kdDebug(5006) << "KMFilter::readConfig: filter\n" << filter->asString()
+ << "is empty!" << endl;
+ #endif
+ delete filter;
+ } else
+ filters.append(filter);
+ }
+ return filters;
+}
+
+/* static */
+void FilterImporterExporter::writeFiltersToConfig( const QValueList<KMFilter*>& filters, KConfig* config, bool bPopFilter )
+{
+ // first, delete all groups:
+ QStringList filterGroups =
+ config->groupList().grep( QRegExp( bPopFilter ? "PopFilter #\\d+" : "Filter #\\d+" ) );
+ for ( QStringList::Iterator it = filterGroups.begin() ;
+ it != filterGroups.end() ; ++it )
+ config->deleteGroup( *it );
+
+ int i = 0;
+ for ( QValueListConstIterator<KMFilter*> it = filters.constBegin() ;
+ it != filters.constEnd() ; ++it ) {
+ if ( !(*it)->isEmpty() ) {
+ QString grpName;
+ if ( bPopFilter )
+ grpName.sprintf("PopFilter #%d", i);
+ else
+ grpName.sprintf("Filter #%d", i);
+ KConfigGroupSaver saver(config, grpName);
+ (*it)->writeConfig(config);
+ ++i;
+ }
+ }
+ KConfigGroupSaver saver(config, "General");
+ if (bPopFilter)
+ config->writeEntry("popfilters", i);
+ else
+ config->writeEntry("filters", i);
+}
+
+
+FilterImporterExporter::FilterImporterExporter( QWidget* parent, bool popFilter )
+:mParent( parent), mPopFilter( popFilter )
+{
+}
+
+FilterImporterExporter::~FilterImporterExporter()
+{
+}
+
+QValueList<KMFilter*> FilterImporterExporter::importFilters()
+{
+ QString fileName = KFileDialog::getOpenFileName( QDir::homeDirPath(), QString::null, mParent, i18n("Import Filters") );
+ if ( fileName.isEmpty() )
+ return QValueList<KMFilter*>(); // cancel
+
+ { // scoping
+ QFile f( fileName );
+ if ( !f.open( IO_ReadOnly ) ) {
+ KMessageBox::error( mParent, i18n("The selected file is not readable. Your file access permissions might be insufficient.") );
+ return QValueList<KMFilter*>();
+ }
+ }
+
+ KConfig config( fileName );
+ QValueList<KMFilter*> imported = readFiltersFromConfig( &config, mPopFilter );
+ FilterSelectionDialog dlg( mParent );
+ dlg.setFilters( imported );
+ dlg.exec();
+ return dlg.cancelled() ? QValueList<KMFilter*>() : dlg.selectedFilters();
+}
+
+void FilterImporterExporter::exportFilters(const QValueList<KMFilter*> & filters )
+{
+ KURL saveUrl = KFileDialog::getSaveURL( QDir::homeDirPath(), QString::null, mParent, i18n("Export Filters") );
+
+ if ( saveUrl.isEmpty() || !Util::checkOverwrite( saveUrl, mParent ) )
+ return;
+
+ KConfig config( saveUrl.path() );
+ FilterSelectionDialog dlg( mParent );
+ dlg.setFilters( filters );
+ dlg.exec();
+ if ( !dlg.cancelled() )
+ writeFiltersToConfig( dlg.selectedFilters(), &config, mPopFilter );
+}
+
diff --git a/kmail/filterimporterexporter.h b/kmail/filterimporterexporter.h
new file mode 100644
index 00000000..72d29e97
--- /dev/null
+++ b/kmail/filterimporterexporter.h
@@ -0,0 +1,70 @@
+/*
+ This file is part of KMail.
+ Copyright (c) 2007 Till Adam <adam@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __FILTERIMPORTEREXPORTER_H__
+#define __FILTERIMPORTEREXPORTER_H__
+
+#include <qvaluelist.h>
+
+class KMFilter;
+class KConfig;
+class QWidget;
+
+namespace KMail
+{
+
+/**
+ @short Utility class that provides persisting of filters to/from KConfig.
+ @author Till Adam <till@kdab.net>
+ */
+class FilterImporterExporter
+{
+public:
+ FilterImporterExporter( QWidget* parent, bool popFilter = false );
+ virtual ~FilterImporterExporter();
+
+ /** 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 QValueList<KMFilter*> & );
+
+ /** Import filters. Ask the user where to import them from
+ * and which filters to import. */
+ QValueList<KMFilter*> importFilters();
+
+ static void writeFiltersToConfig( const QValueList<KMFilter*>& filters, KConfig* config, bool bPopFilter );
+ static QValueList<KMFilter*> readFiltersFromConfig( KConfig* config, bool bPopFilter );
+private:
+ QWidget* mParent;
+ bool mPopFilter;
+};
+
+}
+
+#endif /* __FILTERIMPORTEREXPORTER_H__ */
diff --git a/kmail/filterlog.cpp b/kmail/filterlog.cpp
new file mode 100644
index 00000000..23848dfb
--- /dev/null
+++ b/kmail/filterlog.cpp
@@ -0,0 +1,165 @@
+/*
+ This file is part of KMail.
+ Copyright (c) 2003 Andreas Gungl <a.gungl@gmx.de>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "filterlog.h"
+
+#include <kdebug.h>
+
+#include <qdatetime.h>
+#include <qfile.h>
+
+#include <sys/stat.h>
+
+
+using namespace KMail;
+
+
+FilterLog * FilterLog::mSelf = NULL;
+
+
+FilterLog::FilterLog()
+{
+ mSelf = this;
+ // start with logging disabled by default
+ mLogging = false;
+ // better limit the log to 512 KByte to avoid out of memory situations
+ // when the log i sgoing to become very long
+ mMaxLogSize = 512 * 1024;
+ mCurrentLogSize = 0;
+ mAllowedTypes = meta | patternDesc | ruleResult |
+ patternResult | appliedAction;
+}
+
+
+FilterLog::~FilterLog()
+{}
+
+
+FilterLog * FilterLog::instance()
+{
+ if ( !mSelf ) mSelf = new FilterLog();
+ return mSelf;
+}
+
+
+void FilterLog::add( QString logEntry, ContentType contentType )
+{
+ if ( isLogging() && ( mAllowedTypes & contentType ) )
+ {
+ QString timedLog = "[" + QTime::currentTime().toString() + "] ";
+ if ( contentType & ~meta )
+ timedLog += logEntry;
+ else
+ timedLog = logEntry;
+ mLogEntries.append( timedLog );
+ emit logEntryAdded( timedLog );
+ mCurrentLogSize += timedLog.length();
+ checkLogSize();
+ }
+}
+
+
+void FilterLog::setMaxLogSize( long size )
+{
+ if ( size < -1)
+ size = -1;
+ // do not allow less than 1 KByte except unlimited (-1)
+ if ( size >= 0 && size < 1024 )
+ size = 1024;
+ mMaxLogSize = size;
+ emit logStateChanged();
+ checkLogSize();
+}
+
+
+void FilterLog::dump()
+{
+#ifndef NDEBUG
+ kdDebug(5006) << "----- starting filter log -----" << endl;
+ for ( QStringList::Iterator it = mLogEntries.begin();
+ it != mLogEntries.end(); ++it )
+ {
+ kdDebug(5006) << *it << endl;
+ }
+ kdDebug(5006) << "------ end of filter log ------" << endl;
+#endif
+}
+
+
+void FilterLog::checkLogSize()
+{
+ if ( mCurrentLogSize > mMaxLogSize && mMaxLogSize > -1 )
+ {
+ kdDebug(5006) << "Filter log: memory limit reached, starting to discard old items, size = "
+ << QString::number( mCurrentLogSize ) << endl;
+ // avoid some kind of hysteresis, shrink the log to 90% of its maximum
+ while ( mCurrentLogSize > ( mMaxLogSize * 0.9 ) )
+ {
+ QValueListIterator<QString> it = mLogEntries.begin();
+ if ( it != mLogEntries.end())
+ {
+ mCurrentLogSize -= (*it).length();
+ mLogEntries.remove( it );
+ kdDebug(5006) << "Filter log: new size = "
+ << QString::number( mCurrentLogSize ) << endl;
+ }
+ else
+ {
+ kdDebug(5006) << "Filter log: size reduction disaster!" << endl;
+ clear();
+ }
+ }
+ emit logShrinked();
+ }
+}
+
+
+bool FilterLog::saveToFile( QString fileName )
+{
+ QFile file( fileName );
+ if( file.open( IO_WriteOnly ) ) {
+ fchmod( file.handle(), S_IRUSR | S_IWUSR );
+ {
+ QDataStream ds( &file );
+ for ( QStringList::Iterator it = mLogEntries.begin();
+ it != mLogEntries.end(); ++it )
+ {
+ QString tmpString = *it + '\n';
+ QCString cstr( tmpString.local8Bit() );
+ ds.writeRawBytes( cstr, cstr.size() );
+ }
+ }
+ return true;
+ }
+ else
+ return false;
+}
+
+
+#include "filterlog.moc"
diff --git a/kmail/filterlog.h b/kmail/filterlog.h
new file mode 100644
index 00000000..81455679
--- /dev/null
+++ b/kmail/filterlog.h
@@ -0,0 +1,161 @@
+/*
+ This file is part of KMail.
+ Copyright (c) 2003 Andreas Gungl <a.gungl@gmx.de>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+#ifndef KMAIL_FILTERLOG_H
+#define KMAIL_FILTERLOG_H
+
+#include <qobject.h>
+#include <qstringlist.h>
+#include <qstylesheet.h>
+
+namespace KMail {
+
+ /**
+ @short KMail Filter Log Collector.
+ @author Andreas Gungl <a.gungl@gmx.de>
+
+ The filter log helps to collect log information about the
+ filter process in KMail. It's implemented as singleton,
+ so it's easy to direct pieces of information to a unique
+ instance.
+ It's possible to activate / deactivate logging. All
+ collected log information can get thrown away, the
+ next added log entry is the first one until another
+ clearing.
+ A signal is emitted whenever a new logentry is added,
+ when the log was cleared or any log state was changed.
+ */
+ class FilterLog : public QObject
+ {
+ Q_OBJECT
+
+ public:
+ /** access to the singleton instance */
+ static FilterLog * instance();
+
+ /** log data types */
+ enum ContentType
+ {
+ meta = 1,
+ patternDesc = 2,
+ ruleResult = 4,
+ patternResult = 8,
+ appliedAction = 16
+ };
+
+
+ /** check the logging state */
+ bool isLogging() { return mLogging; };
+ /** set the logging state */
+ void setLogging( bool active )
+ {
+ mLogging = active;
+ emit logStateChanged();
+ };
+
+
+ /** control the size of the log */
+ void setMaxLogSize( long size = -1 );
+ long getMaxLogSize() { return mMaxLogSize; };
+
+
+ /** add/remove a content type to the set of logged ones */
+ void setContentTypeEnabled( ContentType contentType, bool b )
+ {
+ if ( b )
+ mAllowedTypes |= contentType;
+ else
+ mAllowedTypes &= ~contentType;
+ emit logStateChanged();
+ };
+
+ /** check a content type for inclusion in the set of logged ones */
+ bool isContentTypeEnabled( ContentType contentType )
+ {
+ return mAllowedTypes & contentType;
+ };
+
+
+ /** add a log entry */
+ void add( QString logEntry, ContentType contentType );
+ /** add a separating line in the log */
+ void addSeparator() { add( "------------------------------", meta ); };
+ /** discard collected log data */
+ void clear()
+ {
+ mLogEntries.clear();
+ mCurrentLogSize = 0;
+ emit logShrinked();
+ };
+
+
+ /** get access to the log entries */
+ const QStringList & getLogEntries() { return mLogEntries; };
+ /** dump the log - for testing purposes */
+ void dump();
+ /** save the log to a file - returns true if okay */
+ bool saveToFile( QString fileName );
+
+ /** destructor */
+ virtual ~FilterLog();
+
+ static QString recode( const QString & plain ) { return QStyleSheet::escape(plain); };
+
+ signals:
+ void logEntryAdded( QString );
+ void logShrinked();
+ void logStateChanged();
+
+ protected:
+ /** Non-public constructor needed by the singleton implementation */
+ FilterLog();
+
+ /** The list contains the single log pieces */
+ QStringList mLogEntries;
+
+ /** the log status */
+ bool mLogging;
+
+ /** max size for kept log items, when reached
+ the last recently added items are discarded
+ -1 means unlimited */
+ long mMaxLogSize;
+ long mCurrentLogSize;
+
+ /** types currently allowed to be legged */
+ int mAllowedTypes;
+
+ void checkLogSize();
+
+ private:
+ static FilterLog * mSelf;
+ };
+
+} // namespace KMail
+
+#endif // KMAIL_FILTERLOG_H
diff --git a/kmail/filterlogdlg.cpp b/kmail/filterlogdlg.cpp
new file mode 100644
index 00000000..a10a056e
--- /dev/null
+++ b/kmail/filterlogdlg.cpp
@@ -0,0 +1,268 @@
+/*
+ This file is part of KMail.
+ Copyright (c) 2003 Andreas Gungl <a.gungl@gmx.de>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "filterlogdlg.h"
+#include "filterlog.h"
+
+#include <kdebug.h>
+#include <kdeversion.h>
+#include <kfiledialog.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+
+#include <qcheckbox.h>
+#include <qlabel.h>
+#include <qspinbox.h>
+#include <qstringlist.h>
+#include <qtextedit.h>
+#include <qvbox.h>
+#include <qwhatsthis.h>
+#include <qvgroupbox.h>
+
+#include <errno.h>
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+using namespace KMail;
+
+
+FilterLogDialog::FilterLogDialog( QWidget * parent )
+: KDialogBase( parent, "FilterLogDlg", false, i18n( "Filter Log Viewer" ),
+ User1|User2|Close, Close, true, KStdGuiItem::clear(), KStdGuiItem::saveAs() )
+{
+ setWFlags( WDestructiveClose );
+ QVBox *page = makeVBoxMainWidget();
+
+ mTextEdit = new QTextEdit( page );
+ mTextEdit->setReadOnly( true );
+ mTextEdit->setWordWrap( QTextEdit::NoWrap );
+ mTextEdit->setTextFormat( QTextEdit::LogText );
+
+ QStringList logEntries = FilterLog::instance()->getLogEntries();
+ for ( QStringList::Iterator it = logEntries.begin();
+ it != logEntries.end(); ++it )
+ {
+ mTextEdit->append( *it );
+ }
+
+ mLogActiveBox = new QCheckBox( i18n("&Log filter activities"), page );
+ mLogActiveBox->setChecked( FilterLog::instance()->isLogging() );
+ connect( mLogActiveBox, SIGNAL(clicked()),
+ this, SLOT(slotSwitchLogState(void)) );
+ QWhatsThis::add( mLogActiveBox,
+ i18n( "You can turn logging of filter activities on and off here. "
+ "Of course, log data is collected and shown only when logging "
+ "is turned on. " ) );
+
+ mLogDetailsBox = new QVGroupBox( i18n( "Logging Details" ), page );
+ mLogDetailsBox->setEnabled( mLogActiveBox->isChecked() );
+ connect( mLogActiveBox, SIGNAL( toggled( bool ) ),
+ mLogDetailsBox, SLOT( setEnabled( bool ) ) );
+
+ mLogPatternDescBox = new QCheckBox( i18n("Log pattern description"),
+ mLogDetailsBox );
+ mLogPatternDescBox->setChecked(
+ FilterLog::instance()->isContentTypeEnabled( FilterLog::patternDesc ) );
+ connect( mLogPatternDescBox, SIGNAL(clicked()),
+ this, SLOT(slotChangeLogDetail(void)) );
+ // TODO
+ //QWhatsThis::add( mLogPatternDescBox,
+ // i18n( "" ) );
+
+ mLogRuleEvaluationBox = new QCheckBox( i18n("Log filter &rule evaluation"),
+ mLogDetailsBox );
+ mLogRuleEvaluationBox->setChecked(
+ FilterLog::instance()->isContentTypeEnabled( FilterLog::ruleResult ) );
+ connect( mLogRuleEvaluationBox, SIGNAL(clicked()),
+ this, SLOT(slotChangeLogDetail(void)) );
+ QWhatsThis::add( mLogRuleEvaluationBox,
+ i18n( "You can control the feedback in the log concerning the "
+ "evaluation of the filter rules of applied filters: "
+ "having this option checked will give detailed feedback "
+ "for each single filter rule; alternatively, only "
+ "feedback about the result of the evaluation of all rules "
+ "of a single filter will be given." ) );
+
+ mLogPatternResultBox = new QCheckBox( i18n("Log filter pattern evaluation"),
+ mLogDetailsBox );
+ mLogPatternResultBox->setChecked(
+ FilterLog::instance()->isContentTypeEnabled( FilterLog::patternResult ) );
+ connect( mLogPatternResultBox, SIGNAL(clicked()),
+ this, SLOT(slotChangeLogDetail(void)) );
+ // TODO
+ //QWhatsThis::add( mLogPatternResultBox,
+ // i18n( "" ) );
+
+ mLogFilterActionBox = new QCheckBox( i18n("Log filter actions"),
+ mLogDetailsBox );
+ mLogFilterActionBox->setChecked(
+ FilterLog::instance()->isContentTypeEnabled( FilterLog::appliedAction ) );
+ connect( mLogFilterActionBox, SIGNAL(clicked()),
+ this, SLOT(slotChangeLogDetail(void)) );
+ // TODO
+ //QWhatsThis::add( mLogFilterActionBox,
+ // i18n( "" ) );
+
+ QHBox * hbox = new QHBox( page );
+ new QLabel( i18n("Log size limit:"), hbox );
+ mLogMemLimitSpin = new QSpinBox( hbox );
+ mLogMemLimitSpin->setMinValue( 1 );
+ mLogMemLimitSpin->setMaxValue( 1024 * 256 ); // 256 MB
+ // value in the QSpinBox is in KB while it's in Byte in the FilterLog
+ mLogMemLimitSpin->setValue( FilterLog::instance()->getMaxLogSize() / 1024 );
+ mLogMemLimitSpin->setSuffix( " KB" );
+ mLogMemLimitSpin->setSpecialValueText( i18n("unlimited") );
+ connect( mLogMemLimitSpin, SIGNAL(valueChanged(int)),
+ this, SLOT(slotChangeLogMemLimit(int)) );
+ QWhatsThis::add( mLogMemLimitSpin,
+ i18n( "Collecting log data uses memory to temporarily store the "
+ "log data; here you can limit the maximum amount of memory "
+ "to be used: if the size of the collected log data exceeds "
+ "this limit then the oldest data will be discarded until "
+ "the limit is no longer exceeded. " ) );
+
+ connect(FilterLog::instance(), SIGNAL(logEntryAdded(QString)),
+ this, SLOT(slotLogEntryAdded(QString)));
+ connect(FilterLog::instance(), SIGNAL(logShrinked(void)),
+ this, SLOT(slotLogShrinked(void)));
+ connect(FilterLog::instance(), SIGNAL(logStateChanged(void)),
+ this, SLOT(slotLogStateChanged(void)));
+
+ setInitialSize( QSize( 500, 500 ) );
+#if !KDE_IS_VERSION( 3, 2, 91 )
+ // HACK - KWin keeps all dialogs on top of their mainwindows, but that's probably
+ // wrong (#76026), and should be done only for modals. CVS HEAD should get
+ // proper fix in KWin (see also searchwindow.cpp)
+ XDeleteProperty( qt_xdisplay(), winId(), XA_WM_TRANSIENT_FOR );
+#endif
+}
+
+
+void FilterLogDialog::slotLogEntryAdded( QString logEntry )
+{
+ mTextEdit->append( logEntry );
+}
+
+
+void FilterLogDialog::slotLogShrinked()
+{
+ // limit the size of the shown log lines as soon as
+ // the log has reached it's memory limit
+ if ( mTextEdit->maxLogLines() == -1 )
+ mTextEdit->setMaxLogLines( mTextEdit->lines() );
+}
+
+
+void FilterLogDialog::slotLogStateChanged()
+{
+ mLogActiveBox->setChecked( FilterLog::instance()->isLogging() );
+ mLogPatternDescBox->setChecked(
+ FilterLog::instance()->isContentTypeEnabled( FilterLog::patternDesc ) );
+ mLogRuleEvaluationBox->setChecked(
+ FilterLog::instance()->isContentTypeEnabled( FilterLog::ruleResult ) );
+ mLogPatternResultBox->setChecked(
+ FilterLog::instance()->isContentTypeEnabled( FilterLog::patternResult ) );
+ mLogFilterActionBox->setChecked(
+ FilterLog::instance()->isContentTypeEnabled( FilterLog::appliedAction ) );
+
+ // value in the QSpinBox is in KB while it's in Byte in the FilterLog
+ int newLogSize = FilterLog::instance()->getMaxLogSize() / 1024;
+ if ( mLogMemLimitSpin->value() != newLogSize )
+ mLogMemLimitSpin->setValue( newLogSize );
+}
+
+
+void FilterLogDialog::slotChangeLogDetail()
+{
+ if ( mLogPatternDescBox->isChecked() !=
+ FilterLog::instance()->isContentTypeEnabled( FilterLog::patternDesc ) )
+ FilterLog::instance()->setContentTypeEnabled( FilterLog::patternDesc,
+ mLogPatternDescBox->isChecked() );
+
+ if ( mLogRuleEvaluationBox->isChecked() !=
+ FilterLog::instance()->isContentTypeEnabled( FilterLog::ruleResult ) )
+ FilterLog::instance()->setContentTypeEnabled( FilterLog::ruleResult,
+ mLogRuleEvaluationBox->isChecked() );
+
+ if ( mLogPatternResultBox->isChecked() !=
+ FilterLog::instance()->isContentTypeEnabled( FilterLog::patternResult ) )
+ FilterLog::instance()->setContentTypeEnabled( FilterLog::patternResult,
+ mLogPatternResultBox->isChecked() );
+
+ if ( mLogFilterActionBox->isChecked() !=
+ FilterLog::instance()->isContentTypeEnabled( FilterLog::appliedAction ) )
+ FilterLog::instance()->setContentTypeEnabled( FilterLog::appliedAction,
+ mLogFilterActionBox->isChecked() );
+}
+
+
+void FilterLogDialog::slotSwitchLogState()
+{
+ FilterLog::instance()->setLogging( mLogActiveBox->isChecked() );
+}
+
+
+void FilterLogDialog::slotChangeLogMemLimit( int value )
+{
+ FilterLog::instance()->setMaxLogSize( value * 1024 );
+}
+
+
+void FilterLogDialog::slotUser1()
+{
+ FilterLog::instance()->clear();
+ mTextEdit->clear();
+}
+
+
+void FilterLogDialog::slotUser2()
+{
+ QString fileName;
+ KFileDialog fdlg( QString::null, QString::null, this, 0, true );
+
+ fdlg.setMode( KFile::File );
+ fdlg.setSelection( "kmail-filter.log" );
+ fdlg.setOperationMode( KFileDialog::Saving );
+ if ( fdlg.exec() )
+ {
+ fileName = fdlg.selectedFile();
+ if ( !FilterLog::instance()->saveToFile( fileName ) )
+ {
+ KMessageBox::error( this,
+ i18n( "Could not write the file %1:\n"
+ "\"%2\" is the detailed error description." )
+ .arg( fileName,
+ QString::fromLocal8Bit( strerror( errno ) ) ),
+ i18n( "KMail Error" ) );
+ }
+ }
+}
+
+
+#include "filterlogdlg.moc"
diff --git a/kmail/filterlogdlg.h b/kmail/filterlogdlg.h
new file mode 100644
index 00000000..9f05298d
--- /dev/null
+++ b/kmail/filterlogdlg.h
@@ -0,0 +1,80 @@
+/*
+ This file is part of KMail.
+ Copyright (c) 2003 Andreas Gungl <a.gungl@gmx.de>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+#ifndef KMAIL_FILTERLOGDLG_H
+#define KMAIL_FILTERLOGDLG_H
+
+#include <kdialogbase.h>
+
+class QTextEdit;
+class QCheckBox;
+class QSpinBox;
+class QGroupBox;
+
+namespace KMail {
+
+ /**
+ @short KMail Filter Log Collector.
+ @author Andreas Gungl <a.gungl@gmx.de>
+
+ The filter log dialog allows a continued observation of the
+ filter log of KMail.
+ */
+ class FilterLogDialog : public KDialogBase
+ {
+ Q_OBJECT
+
+ public:
+ /** constructor */
+ FilterLogDialog( QWidget * parent );
+
+ protected slots:
+ void slotLogEntryAdded( QString logEntry );
+ void slotLogShrinked();
+ void slotLogStateChanged();
+ void slotChangeLogDetail();
+ void slotSwitchLogState();
+ void slotChangeLogMemLimit( int value );
+
+ protected:
+ virtual void slotUser1();
+ virtual void slotUser2();
+
+ QTextEdit * mTextEdit;
+ QCheckBox * mLogActiveBox;
+ QGroupBox * mLogDetailsBox;
+ QCheckBox * mLogPatternDescBox;
+ QCheckBox * mLogRuleEvaluationBox;
+ QCheckBox * mLogPatternResultBox;
+ QCheckBox * mLogFilterActionBox;
+ QSpinBox * mLogMemLimitSpin;
+ };
+
+} // namespace KMail
+
+#endif // KMAIL_FILTERLOGDLG_H
diff --git a/kmail/folderIface.cpp b/kmail/folderIface.cpp
new file mode 100644
index 00000000..3a6bc6f0
--- /dev/null
+++ b/kmail/folderIface.cpp
@@ -0,0 +1,150 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ *
+ * This file is part of KMail, the KDE mail client.
+ * Copyright (c) 2003 Zack Rusin <zack@kde.org>
+ *
+ * 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 Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "folderIface.h"
+
+#include "kmmainwin.h"
+#include "kmfoldertree.h"
+#include "kmfoldermgr.h"
+#include "kmfolder.h"
+
+#include <kapplication.h>
+#include <kdebug.h>
+
+#include <stdlib.h>
+
+namespace KMail {
+
+FolderIface::FolderIface( const QString& vpath )
+ : DCOPObject( "FolderIface" ), mPath( vpath )
+{
+ //kdDebug(5006)<<"FolderIface folder = "<< mPath <<endl;
+ mFolder = kmkernel->folderMgr()->getFolderByURL( mPath );
+ if ( !mFolder )
+ mFolder = kmkernel->imapFolderMgr()->getFolderByURL( mPath );
+ if ( !mFolder )
+ mFolder = kmkernel->dimapFolderMgr()->getFolderByURL( mPath );
+ Q_ASSERT( mFolder );
+}
+
+QString
+FolderIface::path() const
+{
+ return mPath;
+}
+
+QString
+FolderIface::displayName() const
+{
+ return mFolder->label();
+}
+
+QString
+FolderIface::displayPath() const
+{
+ return mFolder->prettyURL();
+}
+
+bool
+FolderIface::usesCustomIcons() const
+{
+ return mFolder->useCustomIcons();
+}
+
+QString
+FolderIface::normalIconPath() const
+{
+ return mFolder->normalIconPath();
+}
+
+QString
+FolderIface::unreadIconPath() const
+{
+ return mFolder->unreadIconPath();
+}
+
+int
+FolderIface::messages()
+{
+ // if the folder isn't open then return the cached count
+ return mFolder->count( !mFolder->isOpened() );
+}
+
+int
+FolderIface::unreadMessages()
+{
+ return mFolder->countUnread();
+}
+
+int
+FolderIface::unreadRecursiveMessages()
+{
+ return mFolder->countUnreadRecursive();
+}
+
+//The reason why this function is disabled is that we loose
+//the message as soon as we get it (after we switch folders,
+//it's being unget. We need a reference count on message to make it work
+/*
+QValueList<DCOPRef>
+FolderIface::messageRefs()
+{
+ QValueList<DCOPRef> refList;
+ KMMsgList messageCache;
+ KMFolderIndex *indexFolder = static_cast<KMFolderIndex*>( mFolder);
+
+ if ( indexFolder ) {
+ mFolder->open();
+ indexFolder->readIndex();
+ messageCache = indexFolder->mMsgList;
+ mFolder->close();
+ } else
+ return refList;
+
+ kdDebug(5006)<<"refList == "<<messageCache.count()<<endl;
+
+ for( int i = 0; i < messageCache.size(); ++i ) {
+ KMMsgBase *msg = messageCache[i];
+ if ( msg ) {
+ KMMessage *fmsg = msg->parent()->getMsg( msg->parent()->find( msg ) );
+ refList.append( DCOPRef( new MessageIface( fmsg ) ) );
+ }
+ }
+
+ kdDebug(5006)<<"Reflist size = "<<refList.count()<<endl;
+ return refList;
+}*/
+
+}
+
+#include "folderIface.moc"
diff --git a/kmail/folderIface.h b/kmail/folderIface.h
new file mode 100644
index 00000000..4c8b4136
--- /dev/null
+++ b/kmail/folderIface.h
@@ -0,0 +1,71 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ *
+ * This file is part of KMail, the KDE mail client.
+ * Copyright (c) 2003 Zack Rusin <zack@kde.org>
+ *
+ * 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 Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#ifndef FOLDERIFACE_H
+#define FOLDERIFACE_H
+
+#include <qobject.h>
+#include <dcopobject.h>
+
+class KMFolder;
+class KMMainWidget;
+
+namespace KMail {
+
+ class FolderIface : public QObject,
+ public DCOPObject
+ {
+ Q_OBJECT
+ K_DCOP
+
+ public:
+ FolderIface( const QString& vpath );
+
+ k_dcop:
+ virtual QString path() const;
+ virtual QString displayName() const;
+ virtual QString displayPath() const;
+ virtual bool usesCustomIcons() const;
+ virtual QString normalIconPath() const;
+ virtual QString unreadIconPath() const;
+ virtual int messages();
+ virtual int unreadMessages();
+ virtual int unreadRecursiveMessages();
+
+ //not yet
+ //virtual QValueList<DCOPRef> messageRefs();
+ protected:
+ KMFolder* mFolder;
+ QString mPath;
+ };
+
+}
+
+#endif
diff --git a/kmail/folderdiaacltab.cpp b/kmail/folderdiaacltab.cpp
new file mode 100644
index 00000000..4b4a5b6b
--- /dev/null
+++ b/kmail/folderdiaacltab.cpp
@@ -0,0 +1,817 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+/**
+ * folderdiaacltab.cpp
+ *
+ * Copyright (c) 2004 David Faure <faure@kde.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#include <config.h>
+
+#include "folderdiaacltab.h"
+#include "acljobs.h"
+#include "kmfolderimap.h"
+#include "kmfoldercachedimap.h"
+#include "kmacctcachedimap.h"
+#include "kmfolder.h"
+
+#include <addressesdialog.h>
+#include <kabc/addresseelist.h>
+#ifdef KDEPIM_NEW_DISTRLISTS
+#include <libkdepim/distributionlist.h> // libkdepim
+#else
+#include <kabc/distributionlist.h>
+#endif
+#include <kabc/stdaddressbook.h>
+#include <kaddrbook.h>
+#include <kpushbutton.h>
+#include <kdebug.h>
+#include <klocale.h>
+
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qvbox.h>
+#include <qvbuttongroup.h>
+#include <qwidgetstack.h>
+#include <qradiobutton.h>
+#include <qwhatsthis.h>
+
+#include <assert.h>
+#include <kmessagebox.h>
+
+using namespace KMail;
+
+// In case your kdelibs is < 3.3
+#ifndef I18N_NOOP2
+#define I18N_NOOP2( comment,x ) x
+#endif
+
+// The set of standard permission sets
+static const struct {
+ unsigned int permissions;
+ const char* userString;
+} standardPermissions[] = {
+ { 0, I18N_NOOP2( "Permissions", "None" ) },
+ { ACLJobs::List | ACLJobs::Read | ACLJobs::WriteSeenFlag, I18N_NOOP2( "Permissions", "Read" ) },
+ { ACLJobs::List | ACLJobs::Read | ACLJobs::WriteSeenFlag | ACLJobs::Insert | ACLJobs::Post, I18N_NOOP2( "Permissions", "Append" ) },
+ { ACLJobs::AllWrite, I18N_NOOP2( "Permissions", "Write" ) },
+ { ACLJobs::All, I18N_NOOP2( "Permissions", "All" ) }
+};
+
+
+KMail::ACLEntryDialog::ACLEntryDialog( IMAPUserIdFormat userIdFormat, const QString& caption, QWidget* parent, const char* name )
+ : KDialogBase( parent, name, true /*modal*/, caption,
+ KDialogBase::Ok|KDialogBase::Cancel, KDialogBase::Ok, true /*sep*/ )
+ , mUserIdFormat( userIdFormat )
+{
+ QWidget *page = new QWidget( this );
+ setMainWidget(page);
+ QGridLayout *topLayout = new QGridLayout( page, 3 /*rows*/, 3 /*cols*/, 0, spacingHint() );
+
+ QLabel *label = new QLabel( i18n( "&User identifier:" ), page );
+ topLayout->addWidget( label, 0, 0 );
+
+ mUserIdLineEdit = new KLineEdit( page );
+ topLayout->addWidget( mUserIdLineEdit, 0, 1 );
+ label->setBuddy( mUserIdLineEdit );
+ QWhatsThis::add( mUserIdLineEdit, i18n( "The User Identifier is the login of the user on the IMAP server. This can be a simple user name or the full email address of the user; the login for your own account on the server will tell you which one it is." ) );
+
+ QPushButton* kabBtn = new QPushButton( "...", page );
+ topLayout->addWidget( kabBtn, 0, 2 );
+
+ mButtonGroup = new QVButtonGroup( i18n( "Permissions" ), page );
+ topLayout->addMultiCellWidget( mButtonGroup, 1, 1, 0, 2 );
+
+ for ( unsigned int i = 0;
+ i < sizeof( standardPermissions ) / sizeof( *standardPermissions );
+ ++i ) {
+ QRadioButton* cb = new QRadioButton( i18n( "Permissions", standardPermissions[i].userString ), mButtonGroup );
+ // We store the permission value (bitfield) as the id of the radiobutton in the group
+ mButtonGroup->insert( cb, standardPermissions[i].permissions );
+ }
+ topLayout->setRowStretch(2, 10);
+
+ connect( mUserIdLineEdit, SIGNAL( textChanged( const QString& ) ), SLOT( slotChanged() ) );
+ connect( kabBtn, SIGNAL( clicked() ), SLOT( slotSelectAddresses() ) );
+ connect( mButtonGroup, SIGNAL( clicked( int ) ), SLOT( slotChanged() ) );
+ enableButtonOK( false );
+
+ mUserIdLineEdit->setFocus();
+ // Ensure the lineedit is rather wide so that email addresses can be read in it
+ incInitialSize( QSize( 200, 0 ) );
+}
+
+void KMail::ACLEntryDialog::slotChanged()
+{
+ enableButtonOK( !mUserIdLineEdit->text().isEmpty() && mButtonGroup->selected() != 0 );
+}
+
+static QString addresseeToUserId( const KABC::Addressee& addr, IMAPUserIdFormat userIdFormat )
+{
+ QString email = addr.preferredEmail();
+ if ( userIdFormat == FullEmail )
+ return email;
+ else { // mUserIdFormat == UserName
+ email.truncate( email.find( '@' ) );
+ return email;
+ }
+}
+
+void KMail::ACLEntryDialog::slotSelectAddresses()
+{
+ KPIM::AddressesDialog dlg( this );
+ dlg.setShowCC( false );
+ dlg.setShowBCC( false );
+ if ( mUserIdFormat == FullEmail ) // otherwise we have no way to go back from userid to email
+ dlg.setSelectedTo( userIds() );
+ if ( dlg.exec() != QDialog::Accepted )
+ return;
+
+ const QStringList distrLists = dlg.toDistributionLists();
+ QString txt = distrLists.join( ", " );
+ const KABC::Addressee::List lst = dlg.toAddresses();
+ if ( !lst.isEmpty() ) {
+ for( QValueList<KABC::Addressee>::ConstIterator it = lst.begin(); it != lst.end(); ++it ) {
+ if ( !txt.isEmpty() )
+ txt += ", ";
+ txt += addresseeToUserId( *it, mUserIdFormat );
+ }
+ }
+ mUserIdLineEdit->setText( txt );
+}
+
+void KMail::ACLEntryDialog::setValues( const QString& userId, unsigned int permissions )
+{
+ mUserIdLineEdit->setText( userId );
+ mButtonGroup->setButton( permissions );
+ enableButtonOK( !userId.isEmpty() );
+}
+
+QString KMail::ACLEntryDialog::userId() const
+{
+ return mUserIdLineEdit->text();
+}
+
+QStringList KMail::ACLEntryDialog::userIds() const
+{
+ QStringList lst = QStringList::split( ",", mUserIdLineEdit->text() );
+ for( QStringList::Iterator it = lst.begin(); it != lst.end(); ++it ) {
+ // Strip white space (in particular, due to ", ")
+ *it = (*it).stripWhiteSpace();
+ }
+ return lst;
+}
+
+unsigned int KMail::ACLEntryDialog::permissions() const
+{
+ return mButtonGroup->selectedId();
+}
+
+// class KMail::FolderDiaACLTab::ListView : public KListView
+// {
+// public:
+// ListView( QWidget* parent, const char* name = 0 ) : KListView( parent, name ) {}
+// };
+
+class KMail::FolderDiaACLTab::ListViewItem : public KListViewItem
+{
+public:
+ ListViewItem( QListView* listview )
+ : KListViewItem( listview, listview->lastItem() ),
+ mModified( false ), mNew( false ) {}
+
+ void load( const ACLListEntry& entry );
+ void save( ACLList& list,
+#ifdef KDEPIM_NEW_DISTRLISTS
+ KABC::AddressBook* abook,
+#else
+ KABC::DistributionListManager& manager,
+#endif
+ IMAPUserIdFormat userIdFormat );
+
+ QString userId() const { return text( 0 ); }
+ void setUserId( const QString& userId ) { setText( 0, userId ); }
+
+ unsigned int permissions() const { return mPermissions; }
+ void setPermissions( unsigned int permissions );
+
+ bool isModified() const { return mModified; }
+ void setModified( bool b ) { mModified = b; }
+
+ // The fact that an item is new doesn't matter much.
+ // This bool is only used to handle deletion differently
+ bool isNew() const { return mNew; }
+ void setNew( bool b ) { mNew = b; }
+
+private:
+ unsigned int mPermissions;
+ QString mInternalRightsList; ///< protocol-dependent string (e.g. IMAP rights list)
+ bool mModified;
+ bool mNew;
+};
+
+// internalRightsList is only used if permissions doesn't match the standard set
+static QString permissionsToUserString( unsigned int permissions, const QString& internalRightsList )
+{
+ for ( unsigned int i = 0;
+ i < sizeof( standardPermissions ) / sizeof( *standardPermissions );
+ ++i ) {
+ if ( permissions == standardPermissions[i].permissions )
+ return i18n( "Permissions", standardPermissions[i].userString );
+ }
+ if ( internalRightsList.isEmpty() )
+ return i18n( "Custom Permissions" ); // not very helpful, but shouldn't happen
+ else
+ return i18n( "Custom Permissions (%1)" ).arg( internalRightsList );
+}
+
+void KMail::FolderDiaACLTab::ListViewItem::setPermissions( unsigned int permissions )
+{
+ mPermissions = permissions;
+ setText( 1, permissionsToUserString( permissions, QString::null ) );
+}
+
+void KMail::FolderDiaACLTab::ListViewItem::load( const ACLListEntry& entry )
+{
+ // Don't allow spaces in userids. If you need this, fix the slave->app communication,
+ // since it uses space as a separator (imap4.cc, look for GETACL)
+ // It's ok in distribution list names though, that's why this check is only done here
+ // and also why there's no validator on the lineedit.
+ if ( entry.userId.contains( ' ' ) )
+ kdWarning(5006) << "Userid contains a space!!! '" << entry.userId << "'" << endl;
+
+ setUserId( entry.userId );
+ mPermissions = entry.permissions;
+ mInternalRightsList = entry.internalRightsList;
+ setText( 1, permissionsToUserString( entry.permissions, entry.internalRightsList ) );
+ mModified = entry.changed; // for dimap, so that earlier changes are still marked as changes
+}
+
+void KMail::FolderDiaACLTab::ListViewItem::save( ACLList& aclList,
+#ifdef KDEPIM_NEW_DISTRLISTS
+ KABC::AddressBook* addressBook,
+#else
+ KABC::DistributionListManager& manager,
+#endif
+ IMAPUserIdFormat userIdFormat )
+{
+ // expand distribution lists
+#ifdef KDEPIM_NEW_DISTRLISTS
+ KPIM::DistributionList list = KPIM::DistributionList::findByName( addressBook, userId(), false );
+ if ( !list.isEmpty() ) {
+ Q_ASSERT( mModified ); // it has to be new, it couldn't be stored as a distr list name....
+ KPIM::DistributionList::Entry::List entryList = list.entries(addressBook);
+ KPIM::DistributionList::Entry::List::ConstIterator it;
+ // (we share for loop with the old-distrlist-code)
+#else
+ // kaddrbook.cpp has a strange two-pass case-insensitive lookup; is it ok to be case sensitive?
+ KABC::DistributionList* list = manager.list( userId() );
+ if ( list ) {
+ Q_ASSERT( mModified ); // it has to be new, it couldn't be stored as a distr list name....
+ KABC::DistributionList::Entry::List entryList = list->entries();
+ KABC::DistributionList::Entry::List::ConstIterator it; // nice number of "::"!
+#endif
+ for( it = entryList.begin(); it != entryList.end(); ++it ) {
+ QString email = (*it).email;
+ if ( email.isEmpty() )
+ email = addresseeToUserId( (*it).addressee, userIdFormat );
+ ACLListEntry entry( email, QString::null, mPermissions );
+ entry.changed = true;
+ aclList.append( entry );
+ }
+ } else { // it wasn't a distribution list
+ ACLListEntry entry( userId(), mInternalRightsList, mPermissions );
+ if ( mModified ) {
+ entry.internalRightsList = QString::null;
+ entry.changed = true;
+ }
+ aclList.append( entry );
+ }
+}
+
+////
+
+KMail::FolderDiaACLTab::FolderDiaACLTab( KMFolderDialog* dlg, QWidget* parent, const char* name )
+ : FolderDiaTab( parent, name ),
+ mImapAccount( 0 ),
+ mUserRights( 0 ),
+ mDlg( dlg ),
+ mChanged( false ), mAccepting( false ), mSaving( false )
+{
+ QVBoxLayout* topLayout = new QVBoxLayout( this );
+ // We need a widget stack to show either a label ("no acl support", "please wait"...)
+ // or a listview.
+ mStack = new QWidgetStack( this );
+ topLayout->addWidget( mStack );
+
+ mLabel = new QLabel( mStack );
+ mLabel->setAlignment( AlignHCenter | AlignVCenter | WordBreak );
+ mStack->addWidget( mLabel );
+
+ mACLWidget = new QHBox( mStack );
+ mACLWidget->setSpacing( KDialog::spacingHint() );
+ mListView = new KListView( mACLWidget );
+ mListView->setAllColumnsShowFocus( true );
+ mStack->addWidget( mACLWidget );
+ mListView->addColumn( i18n( "User Id" ) );
+ mListView->addColumn( i18n( "Permissions" ) );
+
+ connect( mListView, SIGNAL(doubleClicked(QListViewItem*,const QPoint&,int)),
+ SLOT(slotEditACL(QListViewItem*)) );
+ connect( mListView, SIGNAL(returnPressed(QListViewItem*)),
+ SLOT(slotEditACL(QListViewItem*)) );
+ connect( mListView, SIGNAL(selectionChanged(QListViewItem*)),
+ SLOT(slotSelectionChanged(QListViewItem*)) );
+
+ QVBox* buttonBox = new QVBox( mACLWidget );
+ buttonBox->setSpacing( KDialog::spacingHint() );
+ mAddACL = new KPushButton( i18n( "Add Entry..." ), buttonBox );
+ mEditACL = new KPushButton( i18n( "Modify Entry..." ), buttonBox );
+ mRemoveACL = new KPushButton( i18n( "Remove Entry" ), buttonBox );
+ QWidget *spacer = new QWidget( buttonBox );
+ spacer->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Expanding );
+
+ connect( mAddACL, SIGNAL( clicked() ), SLOT( slotAddACL() ) );
+ connect( mEditACL, SIGNAL( clicked() ), SLOT( slotEditACL() ) );
+ connect( mRemoveACL, SIGNAL( clicked() ), SLOT( slotRemoveACL() ) );
+ mEditACL->setEnabled( false );
+ mRemoveACL->setEnabled( false );
+
+ connect( this, SIGNAL( changed(bool) ), SLOT( slotChanged(bool) ) );
+}
+
+// Warning before save() this will return the url of the _parent_ folder, when creating a new one
+KURL KMail::FolderDiaACLTab::imapURL() const
+{
+ KURL url = mImapAccount->getUrl();
+ url.setPath( mImapPath );
+ return url;
+}
+
+void KMail::FolderDiaACLTab::initializeWithValuesFromFolder( KMFolder* folder )
+{
+ // This can be simplified once KMFolderImap and KMFolderCachedImap have a common base class
+ mFolderType = folder->folderType();
+ if ( mFolderType == KMFolderTypeImap ) {
+ KMFolderImap* folderImap = static_cast<KMFolderImap*>( folder->storage() );
+ mImapPath = folderImap->imapPath();
+ mImapAccount = folderImap->account();
+ mUserRights = folderImap->userRights();
+ }
+ else if ( mFolderType == KMFolderTypeCachedImap ) {
+ KMFolderCachedImap* folderImap = static_cast<KMFolderCachedImap*>( folder->storage() );
+ mImapPath = folderImap->imapPath();
+ mImapAccount = folderImap->account();
+ mUserRights = folderImap->userRights();
+ }
+ else
+ assert( 0 ); // see KMFolderDialog constructor
+}
+
+void KMail::FolderDiaACLTab::load()
+{
+ if ( mDlg->folder() ) {
+ // existing folder
+ initializeWithValuesFromFolder( mDlg->folder() );
+ } else if ( mDlg->parentFolder() ) {
+ // new folder
+ initializeWithValuesFromFolder( mDlg->parentFolder() );
+ mChanged = true; // ensure that saving happens
+ }
+
+ // KABC knows email addresses.
+ // We want LDAP userids.
+ // Depending on the IMAP server setup, the userid can be the full email address,
+ // or just the username part of it.
+ // To know which one it is, we currently have a hidden config option,
+ // but the default value is determined from the current user's own id.
+ QString defaultFormat = "fullemail";
+ // warning mImapAccount can be 0 if creating a subsubsubfolder with dimap... (bug?)
+ if ( mImapAccount && mImapAccount->login().find('@') == -1 )
+ defaultFormat = "username"; // no @ found, so we assume it's just the username
+ KConfigGroup configGroup( kmkernel->config(), "IMAP" );
+ QString str = configGroup.readEntry( "UserIdFormat", defaultFormat );
+ mUserIdFormat = FullEmail;
+ if ( str == "username" )
+ mUserIdFormat = UserName;
+
+ if ( mFolderType == KMFolderTypeCachedImap ) {
+ KMFolder* folder = mDlg->folder() ? mDlg->folder() : mDlg->parentFolder();
+ KMFolderCachedImap* folderImap = static_cast<KMFolderCachedImap*>( 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\"." ) );
+ } else {
+ loadFinished( folderImap->aclList() );
+ }
+ return;
+ }
+
+ // Loading, for online IMAP, consists of four steps:
+ // 1) connect
+ // 2) get user rights
+ // 3) load ACLs
+
+ // First ensure we are connected
+ mStack->raiseWidget( mLabel );
+ if ( !mImapAccount ) { // hmmm?
+ mLabel->setText( i18n( "Error: no IMAP account defined for this folder" ) );
+ return;
+ }
+ KMFolder* folder = mDlg->folder() ? mDlg->folder() : mDlg->parentFolder();
+ if ( folder && folder->storage() == mImapAccount->rootFolder() )
+ return; // nothing to be done for the (virtual) account folder
+ mLabel->setText( i18n( "Connecting to server %1, please wait..." ).arg( mImapAccount->host() ) );
+ ImapAccountBase::ConnectionState state = mImapAccount->makeConnection();
+ if ( state == ImapAccountBase::Error ) { // Cancelled by user, or slave can't start
+ slotConnectionResult( -1, QString::null );
+ } else if ( state == ImapAccountBase::Connecting ) {
+ connect( mImapAccount, SIGNAL( connectionResult(int, const QString&) ),
+ this, SLOT( slotConnectionResult(int, const QString&) ) );
+ } else { // Connected
+ slotConnectionResult( 0, QString::null );
+ }
+}
+
+void KMail::FolderDiaACLTab::slotConnectionResult( int errorCode, const QString& errorMsg )
+{
+ disconnect( mImapAccount, SIGNAL( connectionResult(int, const QString&) ),
+ this, SLOT( slotConnectionResult(int, const QString&) ) );
+ if ( errorCode ) {
+ if ( errorCode == -1 ) // unspecified error
+ mLabel->setText( i18n( "Error connecting to server %1" ).arg( mImapAccount->host() ) );
+ else
+ // Connection error (error message box already shown by the account)
+ mLabel->setText( KIO::buildErrorString( errorCode, errorMsg ) );
+ return;
+ }
+
+ if ( mUserRights == 0 ) {
+ connect( mImapAccount, SIGNAL( receivedUserRights( KMFolder* ) ),
+ this, SLOT( slotReceivedUserRights( KMFolder* ) ) );
+ KMFolder* folder = mDlg->folder() ? mDlg->folder() : mDlg->parentFolder();
+ mImapAccount->getUserRights( folder, mImapPath );
+ }
+ else
+ startListing();
+}
+
+void KMail::FolderDiaACLTab::slotReceivedUserRights( KMFolder* folder )
+{
+ if ( !mImapAccount->hasACLSupport() ) {
+ mLabel->setText( i18n( "This IMAP server does not have support for access control lists (ACL)" ) );
+ return;
+ }
+
+ if ( folder == mDlg->folder() ? mDlg->folder() : mDlg->parentFolder() ) {
+ KMFolderImap* folderImap = static_cast<KMFolderImap*>( folder->storage() );
+ mUserRights = folderImap->userRights();
+ startListing();
+ }
+}
+
+void KMail::FolderDiaACLTab::startListing()
+{
+ // List ACLs of folder - or its parent, if creating a new folder
+ mImapAccount->getACL( mDlg->folder() ? mDlg->folder() : mDlg->parentFolder(), mImapPath );
+ connect( mImapAccount, SIGNAL(receivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )),
+ this, SLOT(slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )) );
+}
+
+void KMail::FolderDiaACLTab::slotReceivedACL( KMFolder* folder, KIO::Job* job, const KMail::ACLList& aclList )
+{
+ if ( folder == ( mDlg->folder() ? mDlg->folder() : mDlg->parentFolder() ) ) {
+ disconnect( mImapAccount, SIGNAL(receivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )),
+ this, SLOT(slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )) );
+
+ if ( job && job->error() ) {
+ if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION )
+ mLabel->setText( i18n( "This IMAP server does not have support for access control lists (ACL)" ) );
+ else
+ mLabel->setText( i18n( "Error retrieving access control list (ACL) from server\n%1" ).arg( job->errorString() ) );
+ return;
+ }
+
+ loadFinished( aclList );
+ }
+}
+
+void KMail::FolderDiaACLTab::loadListView( const ACLList& aclList )
+{
+ mListView->clear();
+ for( ACLList::const_iterator it = aclList.begin(); it != aclList.end(); ++it ) {
+ // -1 means deleted (for cachedimap), don't show those
+ if ( (*it).permissions > -1 ) {
+ ListViewItem* item = new ListViewItem( mListView );
+ item->load( *it );
+ if ( !mDlg->folder() ) // new folder? everything is new then
+ item->setModified( true );
+ }
+ }
+}
+
+void KMail::FolderDiaACLTab::loadFinished( const ACLList& aclList )
+{
+ loadListView( aclList );
+ if ( mDlg->folder() ) // not when creating a new folder
+ mInitialACLList = aclList;
+ mStack->raiseWidget( mACLWidget );
+ slotSelectionChanged( mListView->selectedItem() );
+}
+
+void KMail::FolderDiaACLTab::slotEditACL(QListViewItem* item)
+{
+ if ( !item ) return;
+ bool canAdmin = ( mUserRights & ACLJobs::Administer );
+ // Same logic as in slotSelectionChanged, but this is also needed for double-click IIRC
+ if ( canAdmin && mImapAccount && item ) {
+ // Don't allow users to remove their own admin permissions - there's no way back
+ ListViewItem* ACLitem = static_cast<ListViewItem *>( item );
+ if ( mImapAccount->login() == ACLitem->userId() && ACLitem->permissions() == ACLJobs::All )
+ canAdmin = false;
+ }
+ if ( !canAdmin ) return;
+
+ ListViewItem* ACLitem = static_cast<ListViewItem *>( mListView->currentItem() );
+ ACLEntryDialog dlg( mUserIdFormat, i18n( "Modify Permissions" ), this );
+ dlg.setValues( ACLitem->userId(), ACLitem->permissions() );
+ if ( dlg.exec() == QDialog::Accepted ) {
+ QStringList userIds = dlg.userIds();
+ Q_ASSERT( !userIds.isEmpty() ); // impossible, the OK button is disabled in that case
+ ACLitem->setUserId( dlg.userIds().front() );
+ ACLitem->setPermissions( dlg.permissions() );
+ ACLitem->setModified( true );
+ emit changed(true);
+ if ( userIds.count() > 1 ) { // more emails were added, append them
+ userIds.pop_front();
+ addACLs( userIds, dlg.permissions() );
+ }
+ }
+}
+
+void KMail::FolderDiaACLTab::slotEditACL()
+{
+ slotEditACL( mListView->currentItem() );
+}
+
+void KMail::FolderDiaACLTab::addACLs( const QStringList& userIds, unsigned int permissions )
+{
+ for( QStringList::const_iterator it = userIds.begin(); it != userIds.end(); ++it ) {
+ ListViewItem* ACLitem = new ListViewItem( mListView );
+ ACLitem->setUserId( *it );
+ ACLitem->setPermissions( permissions );
+ ACLitem->setModified( true );
+ ACLitem->setNew( true );
+ }
+}
+
+void KMail::FolderDiaACLTab::slotAddACL()
+{
+ ACLEntryDialog dlg( mUserIdFormat, i18n( "Add Permissions" ), this );
+ if ( dlg.exec() == QDialog::Accepted ) {
+ const QStringList userIds = dlg.userIds();
+ addACLs( dlg.userIds(), dlg.permissions() );
+ emit changed(true);
+ }
+}
+
+void KMail::FolderDiaACLTab::slotSelectionChanged(QListViewItem* item)
+{
+ bool canAdmin = ( mUserRights & ACLJobs::Administer );
+ bool canAdminThisItem = canAdmin;
+ if ( canAdmin && mImapAccount && item ) {
+ // Don't allow users to remove their own admin permissions - there's no way back
+ ListViewItem* ACLitem = static_cast<ListViewItem *>( item );
+ if ( mImapAccount->login() == ACLitem->userId() && ACLitem->permissions() == ACLJobs::All )
+ canAdminThisItem = false;
+ }
+
+ bool lvVisible = mStack->visibleWidget() == mACLWidget;
+ mAddACL->setEnabled( lvVisible && canAdmin && !mSaving );
+ mEditACL->setEnabled( item && lvVisible && canAdminThisItem && !mSaving );
+ mRemoveACL->setEnabled( item && lvVisible && canAdminThisItem && !mSaving );
+}
+
+void KMail::FolderDiaACLTab::slotRemoveACL()
+{
+ ListViewItem* ACLitem = static_cast<ListViewItem *>( mListView->currentItem() );
+ if ( !ACLitem )
+ return;
+ if ( !ACLitem->isNew() ) {
+ if ( mImapAccount && mImapAccount->login() == ACLitem->userId() ) {
+ if ( KMessageBox::Cancel == KMessageBox::warningContinueCancel( topLevelWidget(),
+ i18n( "Do you really want to remove your own permissions for this folder? You will not be able to access it afterwards." ), i18n( "Remove" ) ) )
+ return;
+ }
+ mRemovedACLs.append( ACLitem->userId() );
+ }
+ delete ACLitem;
+ emit changed(true);
+}
+
+KMail::FolderDiaTab::AcceptStatus KMail::FolderDiaACLTab::accept()
+{
+ if ( !mChanged || !mImapAccount )
+ return Accepted; // (no change made), ok for accepting the dialog immediately
+ // If there were changes, we need to apply them first (which is async)
+ save();
+ if ( mFolderType == KMFolderTypeCachedImap )
+ return Accepted; // cached imap: changes saved immediately into the folder
+ // disconnected imap: async job[s] running
+ mAccepting = true;
+ return Delayed;
+}
+
+bool KMail::FolderDiaACLTab::save()
+{
+ if ( !mChanged || !mImapAccount ) // no changes
+ return true;
+ assert( mDlg->folder() ); // should have been created already
+
+ // Expand distribution lists. This is necessary because after Apply
+ // we would otherwise be able to "modify" the permissions for a distr list,
+ // which wouldn't work since the ACLList and the server only know about the
+ // individual addresses.
+ // slotACLChanged would have trouble matching the item too.
+ // After reloading we'd see the list expanded anyway,
+ // so this is more consistent.
+ // But we do it now and not when inserting it, because this allows to
+ // immediately remove a wrongly inserted distr list without having to
+ // remove 100 items.
+ // Now, how to expand them? Playing with listviewitem iterators and inserting
+ // listviewitems at the same time sounds dangerous, so let's just save into
+ // ACLList and reload that.
+ KABC::AddressBook *addressBook = KABC::StdAddressBook::self( true );
+#ifndef KDEPIM_NEW_DISTRLISTS
+ KABC::DistributionListManager manager( addressBook );
+ manager.load();
+#endif
+ ACLList aclList;
+ for ( QListViewItem* item = mListView->firstChild(); item; item = item->nextSibling() ) {
+ ListViewItem* ACLitem = static_cast<ListViewItem *>( item );
+ ACLitem->save( aclList,
+#ifdef KDEPIM_NEW_DISTRLISTS
+ addressBook,
+#else
+ manager,
+#endif
+ mUserIdFormat );
+ }
+ loadListView( aclList );
+
+ // Now compare with the initial ACLList, because if the user renamed a userid
+ // we have to add the old userid to the "to be deleted" list.
+ for( ACLList::ConstIterator init = mInitialACLList.begin(); init != mInitialACLList.end(); ++init ) {
+ bool isInNewList = false;
+ QString uid = (*init).userId;
+ for( ACLList::ConstIterator it = aclList.begin(); it != aclList.end() && !isInNewList; ++it )
+ isInNewList = uid == (*it).userId;
+ if ( !isInNewList && !mRemovedACLs.contains(uid) )
+ mRemovedACLs.append( uid );
+ }
+
+ for ( QStringList::ConstIterator rit = mRemovedACLs.begin(); rit != mRemovedACLs.end(); ++rit ) {
+ // We use permissions == -1 to signify deleting. At least on cyrus, setacl(0) or deleteacl are the same,
+ // but I'm not sure if that's true for all servers.
+ ACLListEntry entry( *rit, QString::null, -1 );
+ entry.changed = true;
+ aclList.append( entry );
+ }
+
+ // aclList is finally ready. We can save it (dimap) or apply it (imap).
+
+ if ( mFolderType == KMFolderTypeCachedImap ) {
+ // Apply the changes to the aclList stored in the folder.
+ // We have to do this now and not before, so that cancel really cancels.
+ KMFolderCachedImap* folderImap = static_cast<KMFolderCachedImap*>( mDlg->folder()->storage() );
+ folderImap->setACLList( aclList );
+ return true;
+ }
+
+ mACLList = aclList;
+
+ KMFolderImap* parentImap = mDlg->parentFolder() ? static_cast<KMFolderImap*>( mDlg->parentFolder()->storage() ) : 0;
+
+ if ( mDlg->isNewFolder() ) {
+ // The folder isn't created yet, wait for it
+ // It's a two-step process (mkdir+listDir) so we wait for the dir listing to be complete
+ connect( parentImap, SIGNAL( directoryListingFinished(KMFolderImap*) ),
+ this, SLOT( slotDirectoryListingFinished(KMFolderImap*) ) );
+ } else {
+ slotDirectoryListingFinished( parentImap );
+ }
+ return true;
+}
+
+void KMail::FolderDiaACLTab::slotDirectoryListingFinished(KMFolderImap* f)
+{
+ if ( !f ||
+ f != static_cast<KMFolderImap*>( mDlg->parentFolder()->storage() ) ||
+ !mDlg->folder() ||
+ !mDlg->folder()->storage() ) {
+ emit readyForAccept();
+ return;
+ }
+
+ // When creating a new folder with online imap, update mImapPath
+ KMFolderImap* folderImap = static_cast<KMFolderImap*>( mDlg->folder()->storage() );
+ if ( !folderImap || folderImap->imapPath().isEmpty() )
+ return;
+ mImapPath = folderImap->imapPath();
+
+ KIO::Job* job = ACLJobs::multiSetACL( mImapAccount->slave(), imapURL(), mACLList );
+ ImapAccountBase::jobData jd;
+ jd.total = 1; jd.done = 0; jd.parent = 0;
+ mImapAccount->insertJob(job, jd);
+
+ connect(job, SIGNAL(result(KIO::Job *)),
+ SLOT(slotMultiSetACLResult(KIO::Job *)));
+ connect(job, SIGNAL(aclChanged( const QString&, int )),
+ SLOT(slotACLChanged( const QString&, int )) );
+}
+
+void KMail::FolderDiaACLTab::slotMultiSetACLResult(KIO::Job* job)
+{
+ ImapAccountBase::JobIterator it = mImapAccount->findJob( job );
+ if ( it == mImapAccount->jobsEnd() ) return;
+ mImapAccount->removeJob( it );
+
+ if ( job->error() ) {
+ job->showErrorDialog( this );
+ if ( mAccepting ) {
+ emit cancelAccept();
+ mAccepting = false; // don't emit readyForAccept anymore
+ }
+ } else {
+ if ( mAccepting )
+ emit readyForAccept();
+ }
+}
+
+void KMail::FolderDiaACLTab::slotACLChanged( const QString& userId, int permissions )
+{
+ // The job indicates success in changing the permissions for this user
+ // -> we note that it's been done.
+ bool ok = false;
+ if ( permissions > -1 ) {
+ for ( QListViewItem* item = mListView->firstChild(); item; item = item->nextSibling() ) {
+ ListViewItem* ACLitem = static_cast<ListViewItem *>( item );
+ if ( ACLitem->userId() == userId ) {
+ ACLitem->setModified( false );
+ ACLitem->setNew( false );
+ ok = true;
+ break;
+ }
+ }
+ } else {
+ uint nr = mRemovedACLs.remove( userId );
+ ok = ( nr > 0 );
+ }
+ if ( !ok )
+ kdWarning(5006) << k_funcinfo << " no item found for userId " << userId << endl;
+}
+
+void KMail::FolderDiaACLTab::slotChanged( bool b )
+{
+ mChanged = b;
+}
+
+bool KMail::FolderDiaACLTab::supports( KMFolder* refFolder )
+{
+ ImapAccountBase* imapAccount = 0;
+ if ( refFolder->folderType() == KMFolderTypeImap )
+ imapAccount = static_cast<KMFolderImap*>( refFolder->storage() )->account();
+ else
+ imapAccount = static_cast<KMFolderCachedImap*>( refFolder->storage() )->account();
+ return imapAccount && imapAccount->hasACLSupport(); // support for ACLs (or not tried connecting yet)
+}
+
+#include "folderdiaacltab.moc"
diff --git a/kmail/folderdiaacltab.h b/kmail/folderdiaacltab.h
new file mode 100644
index 00000000..a800af9b
--- /dev/null
+++ b/kmail/folderdiaacltab.h
@@ -0,0 +1,157 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+/**
+ * folderdiaacltab.h
+ *
+ * Copyright (c) 2004 David Faure <faure@kde.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+#ifndef FOLDERDIAACL_H
+#define FOLDERDIAACL_H
+
+#include "kmfolderdia.h"
+#include "kmfoldertype.h"
+
+class KMFolderImap;
+class KPushButton;
+class QWidgetStack;
+class QHBox;
+class QVButtonGroup;
+class KListView;
+namespace KIO { class Job; }
+
+namespace KMail {
+
+enum IMAPUserIdFormat { FullEmail, UserName };
+
+struct ACLListEntry;
+typedef QValueVector<KMail::ACLListEntry> ACLList;
+
+class ImapAccountBase;
+
+/**
+ * "New Access Control Entry" dialog.
+ * Internal class, only used by FolderDiaACLTab
+ */
+class ACLEntryDialog :public KDialogBase {
+ Q_OBJECT
+
+public:
+ ACLEntryDialog( IMAPUserIdFormat userIdFormat, const QString& caption, QWidget* parent, const char* name = 0 );
+
+ void setValues( const QString& userId, unsigned int permissions );
+
+ QString userId() const;
+ QStringList userIds() const;
+ unsigned int permissions() const;
+
+private slots:
+ void slotSelectAddresses();
+ void slotChanged();
+
+private:
+ QVButtonGroup* mButtonGroup;
+ KLineEdit* mUserIdLineEdit;
+ IMAPUserIdFormat mUserIdFormat;
+};
+
+/**
+ * "Access Control" tab in the folder dialog
+ * Internal class, only used by KMFolderDialog
+ */
+class FolderDiaACLTab : public FolderDiaTab
+{
+ Q_OBJECT
+
+public:
+ FolderDiaACLTab( KMFolderDialog* dlg, QWidget* parent, const char* name = 0 );
+
+ virtual void load();
+ virtual bool save();
+ virtual AcceptStatus accept();
+
+ static bool supports( KMFolder* refFolder );
+
+private slots:
+ // Network (KIO) slots
+ void slotConnectionResult( int, const QString& );
+ void slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& );
+ void slotMultiSetACLResult(KIO::Job *);
+ void slotACLChanged( const QString&, int );
+ void slotReceivedUserRights( KMFolder* folder );
+ void slotDirectoryListingFinished(KMFolderImap*);
+
+ // User (KListView) slots
+ void slotEditACL(QListViewItem*);
+ void slotSelectionChanged(QListViewItem*);
+
+ // User (pushbuttons) slots
+ void slotAddACL();
+ void slotEditACL();
+ void slotRemoveACL();
+
+ void slotChanged( bool b );
+
+private:
+ KURL imapURL() const;
+ void initializeWithValuesFromFolder( KMFolder* folder );
+ void startListing();
+ void loadListView( const KMail::ACLList& aclList );
+ void loadFinished( const KMail::ACLList& aclList );
+ void addACLs( const QStringList& userIds, unsigned int permissions );
+
+private:
+ // The widget containing the ACL widgets (listview and buttons)
+ QHBox* mACLWidget;
+ //class ListView;
+ class ListViewItem;
+ KListView* mListView;
+ KPushButton* mAddACL;
+ KPushButton* mEditACL;
+ KPushButton* mRemoveACL;
+
+ QStringList mRemovedACLs;
+ QString mImapPath;
+ ImapAccountBase* mImapAccount;
+ int mUserRights;
+ KMFolderType mFolderType;
+ ACLList mInitialACLList;
+ ACLList mACLList; // to be set
+ IMAPUserIdFormat mUserIdFormat;
+
+ QLabel* mLabel;
+ QWidgetStack* mStack;
+ KMFolderDialog* mDlg;
+
+ bool mChanged;
+ bool mAccepting; // i.e. close when done
+ bool mSaving;
+};
+
+} // end of namespace KMail
+
+#endif /* FOLDERDIAACL_H */
+
diff --git a/kmail/folderdiaquotatab.cpp b/kmail/folderdiaquotatab.cpp
new file mode 100644
index 00000000..dc7582ea
--- /dev/null
+++ b/kmail/folderdiaquotatab.cpp
@@ -0,0 +1,215 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+/**
+ * folderdiaquotatab.cpp
+ *
+ * Copyright (c) 2006 Till Adam <adam@kde.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#include "folderdiaquotatab.h"
+#include "kmfolder.h"
+#include "kmfoldertype.h"
+#include "kmfolderimap.h"
+#include "kmfoldercachedimap.h"
+#include "kmacctcachedimap.h"
+#include "imapaccountbase.h"
+
+#include <qwidgetstack.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qprogressbar.h>
+#include <qwhatsthis.h>
+
+#include "folderdiaquotatab_p.h"
+
+#include <assert.h>
+
+using namespace KMail;
+
+KMail::FolderDiaQuotaTab::FolderDiaQuotaTab( KMFolderDialog* dlg, QWidget* parent, const char* name )
+ : FolderDiaTab( parent, name ),
+ mImapAccount( 0 ),
+ mDlg( dlg )
+{
+ QVBoxLayout* topLayout = new QVBoxLayout( this );
+ // We need a widget stack to show either a label ("no qutoa support", "please wait"...)
+ // or quota info
+ mStack = new QWidgetStack( this );
+ topLayout->addWidget( mStack );
+
+ mLabel = new QLabel( mStack );
+ mLabel->setAlignment( AlignHCenter | AlignVCenter | WordBreak );
+ mStack->addWidget( mLabel );
+
+ mQuotaWidget = new KMail::QuotaWidget( mStack );
+}
+
+
+void KMail::FolderDiaQuotaTab::initializeWithValuesFromFolder( KMFolder* folder )
+{
+ // This can be simplified once KMFolderImap and KMFolderCachedImap have a common base class
+ mFolderType = folder->folderType();
+ if ( mFolderType == KMFolderTypeImap ) {
+ KMFolderImap* folderImap = static_cast<KMFolderImap*>( folder->storage() );
+ mImapAccount = folderImap->account();
+ mImapPath = folderImap->imapPath();
+ }
+ else if ( mFolderType == KMFolderTypeCachedImap ) {
+ KMFolderCachedImap* folderImap = static_cast<KMFolderCachedImap*>( folder->storage() );
+ mImapAccount = folderImap->account();
+ mQuotaInfo = folderImap->quotaInfo();
+ }
+ else
+ assert( 0 ); // see KMFolderDialog constructor
+}
+
+void KMail::FolderDiaQuotaTab::load()
+{
+ if ( mDlg->folder() ) {
+ // existing folder
+ initializeWithValuesFromFolder( mDlg->folder() );
+ } else if ( mDlg->parentFolder() ) {
+ // new folder
+ initializeWithValuesFromFolder( mDlg->parentFolder() );
+ }
+
+ if ( mFolderType == KMFolderTypeCachedImap ) {
+ showQuotaWidget();
+ return;
+ }
+
+ assert( mFolderType == KMFolderTypeImap );
+
+ // Loading, for online IMAP, consists of two steps:
+ // 1) connect
+ // 2) get quota info
+
+ // First ensure we are connected
+ mStack->raiseWidget( mLabel );
+ if ( !mImapAccount ) { // hmmm?
+ mLabel->setText( i18n( "Error: no IMAP account defined for this folder" ) );
+ return;
+ }
+ KMFolder* folder = mDlg->folder() ? mDlg->folder() : mDlg->parentFolder();
+ if ( folder && folder->storage() == mImapAccount->rootFolder() )
+ return; // nothing to be done for the (virtual) account folder
+ mLabel->setText( i18n( "Connecting to server %1, please wait..." ).arg( mImapAccount->host() ) );
+ ImapAccountBase::ConnectionState state = mImapAccount->makeConnection();
+ if ( state == ImapAccountBase::Error ) { // Cancelled by user, or slave can't start
+ slotConnectionResult( -1, QString::null );
+ } else if ( state == ImapAccountBase::Connecting ) {
+ connect( mImapAccount, SIGNAL( connectionResult(int, const QString&) ),
+ this, SLOT( slotConnectionResult(int, const QString&) ) );
+ } else { // Connected
+ slotConnectionResult( 0, QString::null );
+ }
+
+}
+
+void KMail::FolderDiaQuotaTab::slotConnectionResult( int errorCode, const QString& errorMsg )
+{
+ disconnect( mImapAccount, SIGNAL( connectionResult(int, const QString&) ),
+ this, SLOT( slotConnectionResult(int, const QString&) ) );
+ if ( errorCode ) {
+ if ( errorCode == -1 ) // unspecified error
+ mLabel->setText( i18n( "Error connecting to server %1" ).arg( mImapAccount->host() ) );
+ else
+ // Connection error (error message box already shown by the account)
+ mLabel->setText( KIO::buildErrorString( errorCode, errorMsg ) );
+ return;
+ }
+ connect( mImapAccount, SIGNAL( receivedStorageQuotaInfo( KMFolder*, KIO::Job*, const KMail::QuotaInfo& ) ),
+ this, SLOT( slotReceivedQuotaInfo( KMFolder*, KIO::Job*, const KMail::QuotaInfo& ) ) );
+ KMFolder* folder = mDlg->folder() ? mDlg->folder() : mDlg->parentFolder();
+ mImapAccount->getStorageQuotaInfo( folder, mImapPath );
+}
+
+void KMail::FolderDiaQuotaTab::slotReceivedQuotaInfo( KMFolder* folder,
+ KIO::Job* job,
+ const KMail::QuotaInfo& info )
+{
+ if ( folder == mDlg->folder() ? mDlg->folder() : mDlg->parentFolder() ) {
+ //KMFolderImap* folderImap = static_cast<KMFolderImap*>( folder->storage() );
+
+ disconnect( mImapAccount, SIGNAL(receivedStorageQuotaInfo( KMFolder*, KIO::Job*, const KMail::QuotaInfo& )),
+ this, SLOT(slotReceivedQuotaInfo( KMFolder*, KIO::Job*, const KMail::QuotaInfo& )) );
+
+ if ( job && job->error() ) {
+ if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION )
+ mLabel->setText( i18n( "This account does not have support for quota information." ) );
+ else
+ mLabel->setText( i18n( "Error retrieving quota information from server\n%1" ).arg( job->errorString() ) );
+ } else {
+ mQuotaInfo = info;
+ }
+ showQuotaWidget();
+ }
+}
+
+void KMail::FolderDiaQuotaTab::showQuotaWidget()
+{
+ if ( !mQuotaInfo.isValid() ) {
+ if ( !mImapAccount->hasQuotaSupport() ) {
+ mLabel->setText( i18n( "This account does not have support for quota information." ) );
+ }
+ } else {
+ if ( !mQuotaInfo.isEmpty() ) {
+ mStack->raiseWidget( mQuotaWidget );
+ mQuotaWidget->setQuotaInfo( mQuotaInfo );
+ } else {
+ mLabel->setText( i18n( "No quota is set for this folder." ) );
+ }
+ }
+}
+
+
+KMail::FolderDiaTab::AcceptStatus KMail::FolderDiaQuotaTab::accept()
+{
+ if ( mFolderType == KMFolderTypeCachedImap || mFolderType == KMFolderTypeImap )
+ return Accepted;
+ else
+ assert(0);
+ return Accepted; // our code sanity checker doesn't know there is no coming back from assert(0)
+}
+
+bool KMail::FolderDiaQuotaTab::save()
+{
+ // nothing to do, we are read-only
+ return true;
+}
+
+bool KMail::FolderDiaQuotaTab::supports( KMFolder* refFolder )
+{
+ ImapAccountBase* imapAccount = 0;
+ if ( refFolder->folderType() == KMFolderTypeImap )
+ imapAccount = static_cast<KMFolderImap*>( refFolder->storage() )->account();
+ else if ( refFolder->folderType() == KMFolderTypeCachedImap )
+ imapAccount = static_cast<KMFolderCachedImap*>( refFolder->storage() )->account();
+ return imapAccount && imapAccount->hasQuotaSupport(); // support for Quotas (or not tried connecting yet)
+}
+
+#include "folderdiaquotatab.moc"
diff --git a/kmail/folderdiaquotatab.h b/kmail/folderdiaquotatab.h
new file mode 100644
index 00000000..599a7322
--- /dev/null
+++ b/kmail/folderdiaquotatab.h
@@ -0,0 +1,91 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+/**
+ * folderdiaquotatab.h
+ *
+ * Copyright (c) 2006 Till Adam <adam@kde.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+#ifndef FOLDERDIAQUOTA_H
+#define FOLDERDIAQUOTA_H
+
+#include "kmfolderdia.h"
+#include "kmfoldertype.h"
+#include "quotajobs.h"
+
+namespace KMail {
+ class QuotaWidget;
+}
+class QVBox;
+class QWidgetStack;
+
+namespace KMail {
+
+class ImapAccountBase;
+
+/**
+ * "Quota" tab in the folder dialog
+ * Internal class, only used by KMFolderDialog
+ */
+class FolderDiaQuotaTab : public FolderDiaTab
+{
+ Q_OBJECT
+
+public:
+ FolderDiaQuotaTab( KMFolderDialog* dlg, QWidget* parent, const char* name = 0 );
+
+ virtual void load();
+ virtual bool save();
+ virtual AcceptStatus accept();
+
+ static bool supports( KMFolder* refFolder );
+
+private:
+ void initializeWithValuesFromFolder( KMFolder* folder );
+ void showQuotaWidget();
+private slots:
+ // Network (KIO) slots
+ void slotConnectionResult( int, const QString& );
+ void slotReceivedQuotaInfo( KMFolder*, KIO::Job*, const KMail::QuotaInfo& );
+
+
+private:
+
+ QLabel* mLabel;
+ KMail::QuotaWidget* mQuotaWidget;
+ QWidgetStack* mStack;
+ ImapAccountBase* mImapAccount;
+ QString mImapPath;
+ KMFolderDialog* mDlg;
+
+ QuotaInfo mQuotaInfo;
+ KMFolderType mFolderType;
+};
+
+} // end of namespace KMail
+
+#endif /* FOLDERDIAQUOTA_H */
+
diff --git a/kmail/folderdiaquotatab_p.cpp b/kmail/folderdiaquotatab_p.cpp
new file mode 100644
index 00000000..4cb4b550
--- /dev/null
+++ b/kmail/folderdiaquotatab_p.cpp
@@ -0,0 +1,88 @@
+/**
+ * folderdiaquotatab.cpp
+ *
+ * Copyright (c) 2006 Till Adam <adam@kde.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#include "folderdiaquotatab_p.h"
+
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qprogressbar.h>
+#include <qwhatsthis.h>
+#include <qcombobox.h>
+
+#include <math.h>
+
+#include "kmkernel.h"
+#include "klocale.h"
+#include "kconfig.h"
+#include "kdebug.h"
+#include "kdialog.h"
+#include "globalsettings.h"
+#include "quotajobs.h"
+
+using namespace KMail;
+
+struct QuotaInfo;
+
+QuotaWidget::QuotaWidget( QWidget* parent, const char* name )
+ :QWidget( parent, name )
+{
+ QVBoxLayout *box = new QVBoxLayout(this);
+ QWidget *stuff = new QWidget( this );
+ QGridLayout* layout =
+ new QGridLayout( stuff, 3, 3,
+ KDialog::marginHint(),
+ KDialog::spacingHint() );
+ mInfoLabel = new QLabel("", stuff );
+ mRootLabel = new QLabel("", stuff );
+ mProgressBar = new QProgressBar( stuff );
+ layout->addWidget( new QLabel( i18n("Root:" ), stuff ), 0, 0 );
+ layout->addWidget( mRootLabel, 0, 1 );
+ layout->addWidget( new QLabel( i18n("Usage:"), stuff ), 1, 0 );
+ //layout->addWidget( new QLabel( i18n("Status:"), stuff ), 2, 0 );
+ layout->addWidget( mInfoLabel, 1, 1 );
+ layout->addWidget( mProgressBar, 2, 1 );
+ box->addWidget( stuff );
+ box->addStretch( 2 );
+}
+
+void QuotaWidget::setQuotaInfo( const QuotaInfo& info )
+{
+ // we are assuming only to get STORAGE type info here, thus
+ // casting to int is safe
+ int current = info.current().toInt();
+ int max = info.max().toInt();
+ mProgressBar->setProgress( current, max );
+ mInfoLabel->setText( info.toString() );
+ mRootLabel->setText( info.root() );
+}
+
+
+#include "folderdiaquotatab_p.moc"
diff --git a/kmail/folderdiaquotatab_p.h b/kmail/folderdiaquotatab_p.h
new file mode 100644
index 00000000..4fbf1ad8
--- /dev/null
+++ b/kmail/folderdiaquotatab_p.h
@@ -0,0 +1,63 @@
+/**
+ * folderdiaquotatab.h
+ *
+ * Copyright (c) 2006 Till Adam <adam@kde.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+
+#ifndef FOLDERDIAQUOTA_P_H
+#define FOLDERDIAQUOTA_P_H
+
+
+#include <qlabel.h>
+#include <qprogressbar.h>
+#include <qwhatsthis.h>
+
+#include "quotajobs.h"
+
+namespace KMail {
+
+class QuotaWidget : public QWidget {
+
+ Q_OBJECT
+public:
+ QuotaWidget( QWidget* parent, const char* name = 0 );
+ virtual ~QuotaWidget() { }
+ void setQuotaInfo( const KMail::QuotaInfo& info );
+
+private:
+ QLabel* mInfoLabel;
+ QLabel* mRootLabel;
+ QProgressBar* mProgressBar;
+ QString mUnits;
+ int mFactor;
+};
+
+}//end of namespace KMail
+
+#endif /* FOLDERDIAQUOTA_H */
diff --git a/kmail/folderjob.cpp b/kmail/folderjob.cpp
new file mode 100644
index 00000000..e689e36a
--- /dev/null
+++ b/kmail/folderjob.cpp
@@ -0,0 +1,130 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ *
+ * This file is part of KMail, the KDE mail client.
+ * Copyright (c) 2003 Zack Rusin <zack@kde.org>
+ *
+ * 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 Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "folderjob.h"
+
+#include "kmfolder.h"
+#include "globalsettings.h"
+#include "folderstorage.h"
+
+#include <kdebug.h>
+#include <kio/global.h>
+
+namespace KMail {
+
+//----------------------------------------------------------------------------
+FolderJob::FolderJob( KMMessage *msg, JobType jt, KMFolder* folder,
+ QString partSpecifier )
+ : mType( jt ), mSrcFolder( 0 ), mDestFolder( folder ), mPartSpecifier( partSpecifier ),
+ mErrorCode( 0 ),
+ mPassiveDestructor( false ), mStarted( false )
+{
+ if ( msg ) {
+ mMsgList.append(msg);
+ mSets = msg->headerField("X-UID");
+ }
+ init();
+}
+
+//----------------------------------------------------------------------------
+FolderJob::FolderJob( const QPtrList<KMMessage>& msgList, const QString& sets,
+ JobType jt, KMFolder *folder )
+ : mMsgList( msgList ),mType( jt ),
+ mSets( sets ), mSrcFolder( 0 ), mDestFolder( folder ),
+ mErrorCode( 0 ),
+ mPassiveDestructor( false ), mStarted( false )
+{
+ init();
+}
+
+//----------------------------------------------------------------------------
+FolderJob::FolderJob( JobType jt )
+ : mType( jt ),
+ mErrorCode( 0 ),
+ mPassiveDestructor( false ), mStarted( false )
+{
+ init();
+}
+
+//----------------------------------------------------------------------------
+void FolderJob::init()
+{
+ switch ( mType ) {
+ case tListMessages:
+ case tGetFolder:
+ case tGetMessage:
+ case tCheckUidValidity:
+ mCancellable = true;
+ break;
+ default:
+ mCancellable = false;
+ }
+}
+
+//----------------------------------------------------------------------------
+FolderJob::~FolderJob()
+{
+ if( !mPassiveDestructor ) {
+ emit result( this );
+ emit finished();
+ }
+}
+
+//----------------------------------------------------------------------------
+void
+FolderJob::start()
+{
+ if (!mStarted)
+ {
+ mStarted = true;
+ execute();
+ }
+}
+
+//----------------------------------------------------------------------------
+void FolderJob::kill()
+{
+ mErrorCode = KIO::ERR_USER_CANCELED;
+ delete this;
+}
+
+//----------------------------------------------------------------------------
+QPtrList<KMMessage>
+FolderJob::msgList() const
+{
+ return mMsgList;
+}
+
+}
+
+#include "folderjob.moc"
diff --git a/kmail/folderjob.h b/kmail/folderjob.h
new file mode 100644
index 00000000..84e28586
--- /dev/null
+++ b/kmail/folderjob.h
@@ -0,0 +1,188 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ *
+ * This file is part of KMail, the KDE mail client.
+ * Copyright (c) 2003 Zack Rusin <zack@kde.org>
+ *
+ * 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 Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#ifndef FOLDERJOB_H
+#define FOLDERJOB_H
+
+#include "kmmessage.h"
+
+#include <qobject.h>
+#include <qptrlist.h>
+#include <qstring.h>
+
+class KMFolder;
+
+namespace KMail {
+
+class FolderJob : public QObject
+{
+ Q_OBJECT
+
+public:
+ enum JobType { tListMessages, tGetFolder, tCreateFolder, tExpungeFolder,
+ tDeleteMessage, tGetMessage, tPutMessage, tAddSubfolders,
+ tDeleteFolders, tCheckUidValidity, tRenameFolder,
+ tCopyMessage, tMoveMessage, tOther /* used by subclasses */ };
+ /**
+ * Constructs a new job, operating on the message msg, of type
+ * @p jt and with a parent folder @p folder.
+ */
+ FolderJob( KMMessage *msg, JobType jt = tGetMessage, KMFolder *folder = 0,
+ QString partSpecifier = QString::null );
+
+ /**
+ * Constructs a new job, operating on a message list @p msgList,
+ * set @sets, JobType @p jt and with the parent folder @p folder.
+ *
+ */
+ FolderJob( const QPtrList<KMMessage>& msgList, const QString& sets,
+ JobType jt = tGetMessage, KMFolder *folder = 0 );
+ /**
+ * This one should ONLY be used in derived folders, when a job
+ * does something internal and needs to construct an empty parent
+ * FolderJob
+ */
+ FolderJob( JobType jt );
+ virtual ~FolderJob();
+
+ QPtrList<KMMessage> msgList() const;
+ /**
+ * Start the job
+ */
+ void start();
+
+ /**
+ * Interrupt the job. Note that the finished() and result() signal
+ * will be emitted, unless you called setPassiveDestructor(true) before.
+ * This kills the job, don't use it afterwards.
+ */
+ virtual void kill();
+
+ /**
+ * @return the error code of the job. This must only be called from
+ * the slot connected to the finished() signal.
+ */
+ int error() const { return mErrorCode; }
+
+ /**
+ * @return true if this job can be cancelled, e.g. to exit the application
+ */
+ bool isCancellable() const { return mCancellable; }
+
+ /**
+ * Call this to change the "cancellable" property of this job.
+ * By default, tListMessages, tGetMessage, tGetFolder and tCheckUidValidity
+ * are cancellable, the others are not. But when copying, a non-cancellable
+ * tGetMessage is needed.
+ */
+ void setCancellable( bool b ) { mCancellable = b; }
+
+ void setPassiveDestructor( bool passive ) { mPassiveDestructor = passive; }
+ bool passiveDestructor() { return mPassiveDestructor; }
+
+signals:
+ /**
+ * Emitted whenever a KMMessage has been completely
+ * retrieved from the server/folder.
+ */
+ void messageRetrieved( KMMessage * );
+
+ /**
+ * Emitted whenever a KMMessage was updated
+ */
+ void messageUpdated( KMMessage *, QString );
+
+ /**
+ * Emitted whenever a message has been stored in
+ * the folder.
+ */
+ void messageStored( KMMessage * );
+
+ /**
+ * Emitted when a list of messages has been
+ * copied to the specified location. QPtrList contains
+ * the list of the copied messages.
+ */
+ void messageCopied( QPtrList<KMMessage> );
+
+ /**
+ * Overloaded signal to the one above. A lot of copying
+ * specifies only one message as the argument and this
+ * signal is easier to use when this happens.
+ */
+ void messageCopied( KMMessage * );
+
+ /**
+ * Emitted when the job finishes all processing.
+ */
+ void finished();
+
+ /**
+ * Emitted when the job finishes all processing.
+ * More convenient signal than finished(), since it provides a pointer to the job.
+ * This signal is emitted by the FolderJob destructor => do NOT downcast
+ * the job to a subclass!
+ */
+ void result( KMail::FolderJob* job );
+
+ /**
+ * This progress signal contains the "done" and the "total" numbers so
+ * that the caller can either make a % out of it, or combine it into
+ * a higher-level progress info.
+ */
+ void progress( unsigned long bytesDownloaded, unsigned long bytesTotal );
+
+private:
+ void init();
+
+protected:
+ /**
+ * Has to be reimplemented. It's called by the start() method. Should
+ * start the processing of the specified job function.
+ */
+ virtual void execute()=0;
+
+ QPtrList<KMMessage> mMsgList;
+ JobType mType;
+ QString mSets;
+ KMFolder* mSrcFolder;
+ KMFolder* mDestFolder;
+ QString mPartSpecifier;
+ int mErrorCode;
+
+ //finished() won't be emitted when this is set
+ bool mPassiveDestructor;
+ bool mStarted;
+ bool mCancellable;
+};
+
+}
+
+#endif
diff --git a/kmail/folderpropertiesdialog.ui b/kmail/folderpropertiesdialog.ui
new file mode 100644
index 00000000..417e8df4
--- /dev/null
+++ b/kmail/folderpropertiesdialog.ui
@@ -0,0 +1,638 @@
+<!DOCTYPE UI><UI version="3.2" stdsetdef="1">
+<class>KMFolderDialogUI</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>KMFolderDialogUI</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>344</width>
+ <height>428</height>
+ </rect>
+ </property>
+ <property name="caption">
+ <string>Folder Properties</string>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <widget class="QGroupBox">
+ <property name="name">
+ <cstring>GroupBox2</cstring>
+ </property>
+ <property name="title">
+ <string>General</string>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLineEdit" row="0" column="2" rowspan="1" colspan="6">
+ <property name="name">
+ <cstring>mNameEdit</cstring>
+ </property>
+ <property name="frameShape">
+ <enum>LineEditPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Sunken</enum>
+ </property>
+ </widget>
+ <widget class="QLabel" row="0" column="0" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>TextLabel1</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Name:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>mNameEdit</cstring>
+ </property>
+ </widget>
+ <widget class="QCheckBox" row="3" column="0" rowspan="1" colspan="8">
+ <property name="name">
+ <cstring>mHoldsMailingList</cstring>
+ </property>
+ <property name="text">
+ <string>Folder holds a &amp;mailing list</string>
+ </property>
+ </widget>
+ <spacer row="4" column="0">
+ <property name="name">
+ <cstring>Spacer1_2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Fixed</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QLabel" row="4" column="1" rowspan="1" colspan="3">
+ <property name="name">
+ <cstring>TextLabel2_2</cstring>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>List &amp;address:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>mMailingListPostAddress</cstring>
+ </property>
+ </widget>
+ <spacer row="2" column="0">
+ <property name="name">
+ <cstring>Spacer1</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Fixed</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QLabel" row="2" column="1" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>TextLabel3</cstring>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>N&amp;ormal:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>mNormalIconButton</cstring>
+ </property>
+ </widget>
+ <widget class="KIconButton" row="2" column="3">
+ <property name="name">
+ <cstring>mNormalIconButton</cstring>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>28</width>
+ <height>28</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>28</width>
+ <height>28</height>
+ </size>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ </widget>
+ <spacer row="2" column="4">
+ <property name="name">
+ <cstring>Spacer1_4</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Fixed</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="KIconButton" row="2" column="6">
+ <property name="name">
+ <cstring>mUnreadIconButton</cstring>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>0</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>28</width>
+ <height>28</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>28</width>
+ <height>28</height>
+ </size>
+ </property>
+ <property name="text">
+ <string></string>
+ </property>
+ </widget>
+ <widget class="QLabel" row="2" column="5">
+ <property name="name">
+ <cstring>TextLabel4</cstring>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Unr&amp;ead:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>mUnreadIconButton</cstring>
+ </property>
+ </widget>
+ <spacer row="2" column="7">
+ <property name="name">
+ <cstring>spacer9</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>101</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QCheckBox" row="1" column="0" rowspan="1" colspan="8">
+ <property name="name">
+ <cstring>mIconsCheckBox</cstring>
+ </property>
+ <property name="text">
+ <string>Use custom &amp;icons</string>
+ </property>
+ </widget>
+ <widget class="QLineEdit" row="4" column="4" rowspan="1" colspan="4">
+ <property name="name">
+ <cstring>mMailingListPostAddress</cstring>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>7</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </grid>
+ </widget>
+ <widget class="QGroupBox">
+ <property name="name">
+ <cstring>GroupBox3</cstring>
+ </property>
+ <property name="title">
+ <string>Message Expiring</string>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel" row="1" column="1">
+ <property name="name">
+ <cstring>TextLabel2_3</cstring>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>E&amp;xpire after:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>mExpireReadNum</cstring>
+ </property>
+ </widget>
+ <spacer row="1" column="0">
+ <property name="name">
+ <cstring>Spacer1_2_2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Fixed</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QCheckBox" row="0" column="0" rowspan="1" colspan="4">
+ <property name="name">
+ <cstring>mExpireRead</cstring>
+ </property>
+ <property name="text">
+ <string>Expire &amp;read messages</string>
+ </property>
+ </widget>
+ <spacer row="3" column="0">
+ <property name="name">
+ <cstring>Spacer1_2_2_2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Fixed</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QLabel" row="3" column="1">
+ <property name="name">
+ <cstring>TextLabel2_3_2</cstring>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Expire a&amp;fter:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>mExpireUnreadNum</cstring>
+ </property>
+ </widget>
+ <widget class="QCheckBox" row="2" column="0" rowspan="1" colspan="4">
+ <property name="name">
+ <cstring>mExpireUnread</cstring>
+ </property>
+ <property name="text">
+ <string>Expire &amp;unread messages</string>
+ </property>
+ </widget>
+ <widget class="KIntNumInput" row="1" column="2">
+ <property name="name">
+ <cstring>mExpireReadNum</cstring>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minValue">
+ <number>1</number>
+ </property>
+ <property name="maxValue">
+ <number>500</number>
+ </property>
+ </widget>
+ <widget class="KIntNumInput" row="3" column="2">
+ <property name="name">
+ <cstring>mExpireUnreadNum</cstring>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minValue">
+ <number>1</number>
+ </property>
+ <property name="maxValue">
+ <number>500</number>
+ </property>
+ </widget>
+ <widget class="KComboBox" row="3" column="3">
+ <item>
+ <property name="text">
+ <string>Day(s)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Week(s)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Month(s)</string>
+ </property>
+ </item>
+ <property name="name">
+ <cstring>mExpireUnreadUnits</cstring>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ <widget class="KComboBox" row="1" column="3">
+ <item>
+ <property name="text">
+ <string>Day(s)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Week(s)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Month(s)</string>
+ </property>
+ </item>
+ <property name="name">
+ <cstring>mExpireReadUnits</cstring>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ <spacer row="0" column="4" rowspan="4" colspan="1">
+ <property name="name">
+ <cstring>Spacer6</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </grid>
+ </widget>
+ <widget class="QGroupBox">
+ <property name="name">
+ <cstring>GroupBox1</cstring>
+ </property>
+ <property name="title">
+ <string>Advanced</string>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel" row="1" column="0">
+ <property name="name">
+ <cstring>textLabel1</cstring>
+ </property>
+ <property name="text">
+ <string>Sender identit&amp;y:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>mIdentity</cstring>
+ </property>
+ </widget>
+ <widget class="QComboBox" row="1" column="1">
+ <property name="name">
+ <cstring>mIdentity</cstring>
+ </property>
+ </widget>
+ <widget class="KComboBox" row="0" column="1">
+ <property name="name">
+ <cstring>mMailboxType</cstring>
+ </property>
+ </widget>
+ <widget class="QLabel" row="0" column="0">
+ <property name="name">
+ <cstring>TextLabel1_2</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Storage format:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>mMailboxType</cstring>
+ </property>
+ </widget>
+ <widget class="QLabel" row="2" column="0">
+ <property name="name">
+ <cstring>TextLabel1_3</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;List displays:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>mListDisplays</cstring>
+ </property>
+ </widget>
+ <spacer row="0" column="2" rowspan="3" colspan="1">
+ <property name="name">
+ <cstring>Spacer7</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>120</width>
+ <height>80</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QComboBox" row="2" column="1">
+ <item>
+ <property name="text">
+ <string>Sender</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Receiver</string>
+ </property>
+ </item>
+ <property name="name">
+ <cstring>mListDisplays</cstring>
+ </property>
+ </widget>
+ </grid>
+ </widget>
+ </vbox>
+</widget>
+<connections>
+ <connection>
+ <sender>mIconsCheckBox</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>TextLabel3</receiver>
+ <slot>setEnabled(bool)</slot>
+ </connection>
+ <connection>
+ <sender>mIconsCheckBox</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>TextLabel4</receiver>
+ <slot>setEnabled(bool)</slot>
+ </connection>
+ <connection>
+ <sender>mHoldsMailingList</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>mMailingListPostAddress</receiver>
+ <slot>setEnabled(bool)</slot>
+ </connection>
+ <connection>
+ <sender>mHoldsMailingList</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>TextLabel2_2</receiver>
+ <slot>setEnabled(bool)</slot>
+ </connection>
+ <connection>
+ <sender>mIconsCheckBox</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>mNormalIconButton</receiver>
+ <slot>setEnabled(bool)</slot>
+ </connection>
+ <connection>
+ <sender>mIconsCheckBox</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>mUnreadIconButton</receiver>
+ <slot>setEnabled(bool)</slot>
+ </connection>
+ <connection>
+ <sender>mExpireRead</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>TextLabel2_3</receiver>
+ <slot>setEnabled(bool)</slot>
+ </connection>
+ <connection>
+ <sender>mExpireRead</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>mExpireReadNum</receiver>
+ <slot>setEnabled(bool)</slot>
+ </connection>
+ <connection>
+ <sender>mExpireRead</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>mExpireReadUnits</receiver>
+ <slot>setEnabled(bool)</slot>
+ </connection>
+ <connection>
+ <sender>mExpireUnread</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>TextLabel2_3_2</receiver>
+ <slot>setEnabled(bool)</slot>
+ </connection>
+ <connection>
+ <sender>mExpireUnread</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>mExpireUnreadNum</receiver>
+ <slot>setEnabled(bool)</slot>
+ </connection>
+ <connection>
+ <sender>mExpireUnread</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>mExpireUnreadUnits</receiver>
+ <slot>setEnabled(bool)</slot>
+ </connection>
+ <connection>
+ <sender>mNormalIconButton</sender>
+ <signal>iconChanged(QString)</signal>
+ <receiver>KMFolderDialogUI</receiver>
+ <slot>slotChangeIcon(QString)</slot>
+ </connection>
+</connections>
+<includes>
+ <include location="global" impldecl="in declaration">qvaluelist.h</include>
+ <include location="local" impldecl="in implementation">kmfolderdir.h</include>
+ <include location="local" impldecl="in implementation">kmfolder.h</include>
+ <include location="global" impldecl="in implementation">kdialog.h</include>
+ <include location="local" impldecl="in implementation">folderpropertiesdialog.ui.h</include>
+</includes>
+<forwards>
+ <forward>class KMFolderDir;</forward>
+ <forward>template &lt;typename T&gt; class QGuardedPtr; </forward>
+ <forward>class KMFolder; </forward>
+</forwards>
+<variables>
+ <variable>KMFolder *mFolder;</variable>
+ <variable>QValueList&lt;QGuardedPtr&lt;KMFolder&gt; &gt; mFolders;</variable>
+ <variable>KMFolderDir *mFolderDIr;</variable>
+</variables>
+<slots>
+ <slot access="protected">slotChangeIcon( QString )</slot>
+</slots>
+<layoutdefaults spacing="6" margin="11"/>
+<layoutfunctions spacing="KDialog::spacingHint" margin="KDialog::marginHint"/>
+</UI>
diff --git a/kmail/folderpropertiesdialog.ui.h b/kmail/folderpropertiesdialog.ui.h
new file mode 100644
index 00000000..ef1bcf95
--- /dev/null
+++ b/kmail/folderpropertiesdialog.ui.h
@@ -0,0 +1,15 @@
+/****************************************************************************
+** ui.h extension file, included from the uic-generated form implementation.
+**
+** If you wish to add, delete or rename functions respectively slots use
+** Qt Designer which will update this file, preserving your code. Create an
+** init() function in place of a constructor, and a destroy() function in
+** place of a destructor.
+*****************************************************************************/
+
+void KMFolderDialogUI::slotChangeIcon( QString /* icon */ )
+{
+ //FIXME
+ //if ( mFolder && !mFolder->unreadIcon() )
+ //mUnreadIconButton->setIcon( icon );
+}
diff --git a/kmail/folderrequester.cpp b/kmail/folderrequester.cpp
new file mode 100644
index 00000000..e03ddba7
--- /dev/null
+++ b/kmail/folderrequester.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2004 Carsten Burghardt <burghardt@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#include "folderrequester.h"
+#include "kmfolder.h"
+#include "kmfoldertree.h"
+#include "kmfolderseldlg.h"
+
+#include <kdebug.h>
+#include <klineedit.h>
+#include <kiconloader.h>
+#include <kdialog.h>
+
+#include <qlayout.h>
+#include <qtoolbutton.h>
+
+namespace KMail {
+
+FolderRequester::FolderRequester( QWidget *parent, KMFolderTree *tree )
+ : QWidget( parent ), mFolder( 0 ), mFolderTree( tree ),
+ mMustBeReadWrite( true ), mShowOutbox( true ), mShowImapFolders( true )
+{
+ QHBoxLayout * hlay = new QHBoxLayout( this, 0, KDialog::spacingHint() );
+ hlay->setAutoAdd( true );
+
+ edit = new KLineEdit( this );
+ edit->setReadOnly( true );
+
+ QToolButton* button = new QToolButton( this );
+ button->setIconSet( KGlobal::iconLoader()->loadIconSet( "folder", KIcon::Small, 0 ) );
+ connect( button, SIGNAL(clicked()), this, SLOT(slotOpenDialog()) );
+
+ setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding,
+ QSizePolicy::Fixed ) );
+ setFocusPolicy( QWidget::StrongFocus );
+}
+
+//-----------------------------------------------------------------------------
+void FolderRequester::slotOpenDialog()
+{
+ KMFolderSelDlg dlg( this, mFolderTree, i18n("Select Folder"),
+ mMustBeReadWrite, false );
+ dlg.setFlags( mMustBeReadWrite, mShowOutbox, mShowImapFolders );
+ dlg.setFolder( mFolder );
+
+ if (!dlg.exec()) return;
+ setFolder( dlg.folder() );
+}
+
+//-----------------------------------------------------------------------------
+FolderRequester::~FolderRequester()
+{
+}
+
+//-----------------------------------------------------------------------------
+KMFolder * FolderRequester::folder( void ) const
+{
+ return mFolder;
+}
+
+//-----------------------------------------------------------------------------
+void FolderRequester::setFolder( KMFolder *folder )
+{
+ mFolder = folder;
+ if ( mFolder ) {
+ edit->setText( mFolder->prettyURL() );
+ mFolderId = mFolder->idString();
+ }
+ else if ( !mMustBeReadWrite ) // the Local Folders root node was selected
+ edit->setText( i18n("Local Folders") );
+ emit folderChanged( folder );
+}
+
+//-----------------------------------------------------------------------------
+void FolderRequester::setFolder( const QString &idString )
+{
+ KMFolder *folder = kmkernel->findFolderById( idString );
+ if ( folder ) {
+ setFolder( folder );
+ } else {
+ if ( !idString.isEmpty() ) {
+ edit->setText( i18n( "Unknown folder '%1'" ).arg( idString ) );
+ } else {
+ edit->setText( i18n( "Please select a folder" ) );
+ }
+ mFolder = 0;
+ }
+ mFolderId = idString;
+}
+
+//-----------------------------------------------------------------------------
+void FolderRequester::keyPressEvent( QKeyEvent * e )
+{
+ if ( e->key() == Qt::Key_Space )
+ slotOpenDialog();
+ else
+ e->ignore();
+}
+
+} // namespace KMail
+
+#include "folderrequester.moc"
diff --git a/kmail/folderrequester.h b/kmail/folderrequester.h
new file mode 100644
index 00000000..e404456c
--- /dev/null
+++ b/kmail/folderrequester.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2004 Carsten Burghardt <burghardt@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#ifndef folderrequester_h
+#define folderrequester_h
+
+#include <qwidget.h>
+#include <klineedit.h>
+
+class KMFolder;
+class KMFolderTree;
+
+namespace KMail {
+
+ /**
+ * A widget that contains a KLineEdit which shows the current folder
+ * and a button that fires a KMFolderSelDlg
+ * The dialog is set to disable readonly folders by default
+ * Search folders are excluded
+ */
+ class FolderRequester: public QWidget
+ {
+ Q_OBJECT
+
+ public:
+ /**
+ * Constructor
+ * @param parent the parent widget
+ * @param tree the KMFolderTree to get the folders
+ */
+ FolderRequester( QWidget *parent, KMFolderTree* tree );
+ virtual ~FolderRequester();
+
+ /** Returns selected folder */
+ KMFolder* folder( void ) const;
+
+ /** Returns the folder id */
+ QString folderId() const { return mFolderId; }
+
+ /** Returns current text */
+ QString text() const { return edit->originalText(); }
+
+ /** Preset the folder */
+ void setFolder( KMFolder* );
+ void setFolder( const QString& idString );
+
+ /**
+ * Set if readonly folders should be disabled
+ * Be aware that if you disable this the user can also select the
+ * 'Local Folders' folder which has no valid folder associated
+ */
+ void setMustBeReadWrite( bool readwrite )
+ { mMustBeReadWrite = readwrite; }
+
+ /** Set if the outbox should be shown */
+ void setShowOutbox( bool show )
+ { mShowOutbox = show; }
+
+ /** Set if the imap folders should be shown */
+ void setShowImapFolders( bool show )
+ { mShowImapFolders = show; }
+
+ protected slots:
+ /** Open the folder dialog */
+ void slotOpenDialog();
+
+ signals:
+ /** Emitted when the folder changed */
+ void folderChanged( KMFolder* );
+
+ protected:
+ /** Capture space key to open the dialog */
+ virtual void keyPressEvent( QKeyEvent * e );
+
+ protected:
+ KLineEdit* edit;
+ KMFolder* mFolder;
+ KMFolderTree* mFolderTree;
+ QString mFolderId;
+ bool mMustBeReadWrite;
+ bool mShowOutbox;
+ bool mShowImapFolders;
+ };
+
+} // namespace KMail
+
+#endif /*folderrequester_h*/
diff --git a/kmail/foldershortcutdialog.cpp b/kmail/foldershortcutdialog.cpp
new file mode 100644
index 00000000..f6b1cf92
--- /dev/null
+++ b/kmail/foldershortcutdialog.cpp
@@ -0,0 +1,108 @@
+/*******************************************************************************
+**
+** Filename : foldershortcutdialog.cpp
+** Created on : 09 October, 2004
+** Copyright : (c) 2004 Till Adam
+** Email : adam@kde.org
+**
+*******************************************************************************/
+
+/*******************************************************************************
+**
+** 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.
+**
+** In addition, as a special exception, the copyright holders give
+** permission to link the code of this program with any edition of
+** the Qt library by Trolltech AS, Norway (or with modified versions
+** of Qt that use the same license as Qt), and distribute linked
+** combinations including the two. You must obey the GNU General
+** Public License in all respects for all of the code used other than
+** Qt. If you modify this file, you may extend this exception to
+** your version of the file, but you are not obligated to do so. If
+** you do not wish to do so, delete this exception statement from
+** your version.
+**
+*******************************************************************************/
+
+#include <qlabel.h>
+#include <qvbox.h>
+#include <qvgroupbox.h>
+#include <qwhatsthis.h>
+
+#include <kkeybutton.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+
+#include "kmmainwidget.h"
+#include "foldershortcutdialog.h"
+#include "kmfolder.h"
+
+using namespace KMail;
+
+FolderShortcutDialog::FolderShortcutDialog( KMFolder *folder,
+ KMMainWidget *mainwidget,
+ QWidget *parent,
+ const char *name )
+: KDialogBase( parent, name, true,
+ i18n( "Shortcut for Folder %1" ).arg( folder->label() ),
+ KDialogBase::Ok | KDialogBase::Cancel ),
+ mFolder( folder ), mMainWidget( mainwidget )
+{
+ QVBox *box = makeVBoxMainWidget();
+ QVGroupBox *gb = new QVGroupBox( i18n("Select Shortcut for Folder"), box );
+ QWhatsThis::add( gb, i18n( "<qt>To choose a key or a combination "
+ "of keys which select the current folder, "
+ "click the button below and then press the key(s) "
+ "you wish to associate with this folder.</qt>" ) );
+ QHBox *hb = new QHBox( gb );
+ new QWidget(hb);
+ mKeyButton = new KKeyButton( hb, "FolderShortcutSelector" );
+ new QWidget(hb);
+
+ connect( mKeyButton, SIGNAL( capturedShortcut( const KShortcut& ) ),
+ this, SLOT( slotCapturedShortcut( const KShortcut& ) ) );
+ mKeyButton->setShortcut( folder->shortcut(), false );
+}
+
+FolderShortcutDialog::~FolderShortcutDialog()
+{
+}
+
+void FolderShortcutDialog::slotCapturedShortcut( const KShortcut& sc )
+{
+ if ( sc == mKeyButton->shortcut() ) return;
+ if ( sc.toString().isNull() ) {
+ // null is fine, that's reset, but sc.іsNull() will be false :/
+ mKeyButton->setShortcut( KShortcut::null(), false );
+ } else {
+ if( !mMainWidget->shortcutIsValid( sc ) ) {
+ QString msg( i18n( "The selected shortcut is already used, "
+ "please select a different one." ) );
+ KMessageBox::sorry( mMainWidget, msg );
+ } else {
+ mKeyButton->setShortcut( sc, false );
+ }
+ }
+}
+
+void FolderShortcutDialog::slotOk()
+{
+ mFolder->setShortcut( mKeyButton->shortcut() );
+ KDialogBase::slotOk();
+}
+
+#include "foldershortcutdialog.moc"
+
+
diff --git a/kmail/foldershortcutdialog.h b/kmail/foldershortcutdialog.h
new file mode 100644
index 00000000..bf959699
--- /dev/null
+++ b/kmail/foldershortcutdialog.h
@@ -0,0 +1,74 @@
+/*******************************************************************************
+**
+** Filename : foldershortcutdialog.h
+** Created on : 09 October, 2004
+** Copyright : (c) 2004 Till Adam
+** Email : adam@kde.org
+**
+*******************************************************************************/
+
+/*******************************************************************************
+**
+** 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.
+**
+** In addition, as a special exception, the copyright holders give
+** permission to link the code of this program with any edition of
+** the Qt library by Trolltech AS, Norway (or with modified versions
+** of Qt that use the same license as Qt), and distribute linked
+** combinations including the two. You must obey the GNU General
+** Public License in all respects for all of the code used other than
+** Qt. If you modify this file, you may extend this exception to
+** your version of the file, but you are not obligated to do so. If
+** you do not wish to do so, delete this exception statement from
+** your version.
+**
+*******************************************************************************/
+
+#ifndef FOLDERSHORTCUTDIALOG_H
+#define FOLDERSHORTCUTDIALOG_H
+
+#include <kdialogbase.h> // include for the base class
+class KShortcut;
+class KMFolder;
+class KKeyButton;
+class KMMainWidget;
+
+namespace KMail
+{
+
+class FolderShortcutDialog : public KDialogBase
+{
+Q_OBJECT
+public:
+ FolderShortcutDialog( KMFolder *folder, KMMainWidget *mw,
+ QWidget *parent=0, const char *name=0 );
+ ~FolderShortcutDialog();
+
+
+protected slots:
+ void slotOk();
+ void slotCapturedShortcut( const KShortcut& );
+
+private:
+ KMFolder *mFolder;
+ KMMainWidget *mMainWidget;
+ KKeyButton *mKeyButton;
+
+}; // End of class FolderShortcutDialog
+
+} // End of namespace KMail
+
+
+#endif // FOLDERSHORTCUTDIALOG_H
diff --git a/kmail/folderstorage.cpp b/kmail/folderstorage.cpp
new file mode 100644
index 00000000..89fc20db
--- /dev/null
+++ b/kmail/folderstorage.cpp
@@ -0,0 +1,1176 @@
+/*
+ Virtual base class for mail storage.
+
+ This file is part of KMail.
+
+ Copyright (c) 2004 Bo Thorsen <bo@sonofthor.dk>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include <config.h>
+
+#include "folderstorage.h"
+#include "kmfolder.h"
+#include "kmkernel.h"
+
+#include "kmfolderimap.h" //for the nasty imap hacks, FIXME
+#include "undostack.h"
+#include "kmmsgdict.h"
+#include "kmfoldermgr.h"
+#include "kmcommands.h"
+#include "listjob.h"
+using KMail::ListJob;
+#include "kmsearchpattern.h"
+#include "globalsettings.h"
+
+#include <klocale.h>
+#include <kconfig.h>
+#include <kdebug.h>
+
+#include <qfile.h>
+#include <qregexp.h>
+
+#include <mimelib/mimepp.h>
+#include <errno.h>
+
+//-----------------------------------------------------------------------------
+
+FolderStorage::FolderStorage( KMFolder* folder, const char* aName )
+ : QObject( folder, aName ), mFolder( folder ), mEmitChangedTimer( 0L )
+{
+ mOpenCount = 0;
+ mQuiet = 0;
+ mChanged = false;
+ mAutoCreateIndex = true;
+ mExportsSernums = false;
+ mDirty = false;
+ mUnreadMsgs = -1;
+ mGuessedUnreadMsgs = -1;
+ mTotalMsgs = -1;
+ mSize = -1;
+ needsCompact = false;
+ mConvertToUtf8 = false;
+ mCompactable = true;
+ mNoContent = false;
+ mNoChildren = false;
+ mRDict = 0;
+ mDirtyTimer = new QTimer(this, "mDirtyTimer");
+ connect(mDirtyTimer, SIGNAL(timeout()),
+ this, SLOT(updateIndex()));
+
+ mHasChildren = HasNoChildren;
+ mContentsType = KMail::ContentsTypeMail;
+
+ connect(this, SIGNAL(closed(KMFolder*)), mFolder, SIGNAL(closed()));
+}
+
+//-----------------------------------------------------------------------------
+FolderStorage::~FolderStorage()
+{
+ mJobList.setAutoDelete( true );
+ QObject::disconnect( SIGNAL(destroyed(QObject*)), this, 0 );
+ mJobList.clear();
+ KMMsgDict::deleteRentry(mRDict);
+}
+
+
+void FolderStorage::close( const char* owner, bool aForced )
+{
+ if (mOpenCount <= 0) return;
+ if (mOpenCount > 0) mOpenCount--;
+ if (mOpenCount > 0 && !aForced) return;
+
+ // kdWarning() << "Really closing: " << folder()->prettyURL() << kdBacktrace() << endl;
+ reallyDoClose(owner);
+}
+
+//-----------------------------------------------------------------------------
+QString FolderStorage::dotEscape(const QString& aStr)
+{
+ if (aStr[0] != '.') return aStr;
+ return aStr.left(aStr.find(QRegExp("[^\\.]"))) + aStr;
+}
+
+void FolderStorage::addJob( FolderJob* job ) const
+{
+ QObject::connect( job, SIGNAL(destroyed(QObject*)),
+ SLOT(removeJob(QObject*)) );
+ mJobList.append( job );
+}
+
+void FolderStorage::removeJob( QObject* job )
+{
+ mJobList.remove( static_cast<FolderJob*>( job ) );
+}
+
+
+//-----------------------------------------------------------------------------
+QString FolderStorage::location() const
+{
+ QString sLocation(const_cast<FolderStorage*>(this)->folder()->path());
+
+ if (!sLocation.isEmpty()) sLocation += '/';
+ sLocation += dotEscape(fileName());
+
+ return sLocation;
+}
+
+QString FolderStorage::fileName() const
+{
+ return mFolder->name();
+}
+
+
+
+//-----------------------------------------------------------------------------
+void FolderStorage::setAutoCreateIndex(bool autoIndex)
+{
+ mAutoCreateIndex = autoIndex;
+}
+
+//-----------------------------------------------------------------------------
+void FolderStorage::setDirty(bool f)
+{
+ mDirty = f;
+ if (mDirty && mAutoCreateIndex)
+ mDirtyTimer->changeInterval( mDirtyTimerInterval );
+ else
+ mDirtyTimer->stop();
+}
+
+//-----------------------------------------------------------------------------
+void FolderStorage::markNewAsUnread()
+{
+ KMMsgBase* msgBase;
+ int i;
+
+ for (i=0; i< count(); ++i)
+ {
+ if (!(msgBase = getMsgBase(i))) continue;
+ if (msgBase->isNew())
+ {
+ msgBase->setStatus(KMMsgStatusUnread);
+ msgBase->setDirty(true);
+ }
+ }
+}
+
+void FolderStorage::markUnreadAsRead()
+{
+ KMMsgBase* msgBase;
+ SerNumList serNums;
+
+ for (int i=count()-1; i>=0; --i)
+ {
+ msgBase = getMsgBase(i);
+ assert(msgBase);
+ if (msgBase->isNew() || msgBase->isUnread())
+ {
+ serNums.append( msgBase->getMsgSerNum() );
+ }
+ }
+ if (serNums.empty())
+ return;
+
+ KMCommand *command = new KMSetStatusCommand( KMMsgStatusRead, serNums );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void FolderStorage::quiet(bool beQuiet)
+{
+
+ if (beQuiet)
+ {
+ /* Allocate the timer here to don't have one timer for each folder. BTW,
+ * a timer is created when a folder is checked
+ */
+ if ( !mEmitChangedTimer) {
+ mEmitChangedTimer= new QTimer( this, "mEmitChangedTimer" );
+ connect( mEmitChangedTimer, SIGNAL( timeout() ),
+ this, SLOT( slotEmitChangedTimer() ) );
+ }
+ mQuiet++;
+ } else {
+ mQuiet--;
+ if (mQuiet <= 0)
+ {
+ delete mEmitChangedTimer;
+ mEmitChangedTimer=0L;
+
+ mQuiet = 0;
+ if (mChanged) {
+ emit changed();
+ // Don't hurt emit this if the mUnreadMsg really don't change
+ // We emit it here, because this signal is delayed if mQuiet >0
+ emit numUnreadMsgsChanged( folder() );
+ }
+ mChanged = false;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+/** Compare message's date. This is useful for message sorting */
+int operator<( KMMsgBase & m1, KMMsgBase & m2 )
+{
+ return (m1.date() < m2.date());
+}
+
+/** Compare message's date. This is useful for message sorting */
+int operator==( KMMsgBase & m1, KMMsgBase & m2 )
+{
+ return (m1.date() == m2.date());
+}
+
+
+//-----------------------------------------------------------------------------
+int FolderStorage::expungeOldMsg(int days)
+{
+ int i, msgnb=0;
+ time_t msgTime, maxTime;
+ const KMMsgBase* mb;
+ QValueList<int> rmvMsgList;
+
+ maxTime = time(0) - days * 3600 * 24;
+
+ for (i=count()-1; i>=0; i--) {
+ mb = getMsgBase(i);
+ assert(mb);
+ msgTime = mb->date();
+
+ if (msgTime < maxTime) {
+ //kdDebug(5006) << "deleting msg " << i << " : " << mb->subject() << " - " << mb->dateStr(); // << endl;
+ removeMsg( i );
+ msgnb++;
+ }
+ }
+ return msgnb;
+}
+
+//------------------------------------------
+void FolderStorage::slotEmitChangedTimer()
+{
+ emit changed();
+ mChanged=false;
+}
+//-----------------------------------------------------------------------------
+void FolderStorage::emitMsgAddedSignals(int idx)
+{
+ Q_UINT32 serNum = KMMsgDict::instance()->getMsgSerNum( folder() , idx );
+ if (!mQuiet) {
+ emit msgAdded(idx);
+ } else {
+ /** Restart always the timer or not. BTW we get a kmheaders refresh
+ * each 3 seg.?*/
+ if ( !mEmitChangedTimer->isActive() ) {
+ mEmitChangedTimer->start( 3000 );
+ }
+ mChanged=true;
+ }
+ emit msgAdded( folder(), serNum );
+}
+
+//-----------------------------------------------------------------------------
+bool FolderStorage::canAddMsgNow(KMMessage* aMsg, int* aIndex_ret)
+{
+ if (aIndex_ret) *aIndex_ret = -1;
+ KMFolder *msgParent = aMsg->parent();
+ // If the message has a parent and is in transfer, bail out. If it does not
+ // have a parent we want to be able to add it even if it is in transfer.
+ if (aMsg->transferInProgress() && msgParent)
+ return false;
+ if (!aMsg->isComplete() && msgParent && msgParent->folderType() == KMFolderTypeImap)
+ {
+ FolderJob *job = msgParent->createJob(aMsg);
+ connect(job, SIGNAL(messageRetrieved(KMMessage*)),
+ SLOT(reallyAddMsg(KMMessage*)));
+ job->start();
+ aMsg->setTransferInProgress( true );
+ return false;
+ }
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+void FolderStorage::reallyAddMsg(KMMessage* aMsg)
+{
+ if (!aMsg) // the signal that is connected can call with aMsg=0
+ return;
+ aMsg->setTransferInProgress( false );
+ aMsg->setComplete( true );
+ KMFolder *aFolder = aMsg->parent();
+ int index;
+ ulong serNum = aMsg->getMsgSerNum();
+ bool undo = aMsg->enableUndo();
+ addMsg(aMsg, &index);
+ if (index < 0) return;
+ unGetMsg(index);
+ if (undo)
+ {
+ kmkernel->undoStack()->pushSingleAction( serNum, aFolder, folder() );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void FolderStorage::reallyAddCopyOfMsg(KMMessage* aMsg)
+{
+ if ( !aMsg ) return; // messageRetrieved(0) is always possible
+ aMsg->setParent( 0 );
+ aMsg->setTransferInProgress( false );
+ addMsg( aMsg );
+ unGetMsg( count() - 1 );
+}
+
+int FolderStorage::find( const KMMessage * msg ) const {
+ return find( &msg->toMsgBase() );
+}
+
+//-----------------------------------------------------------------------------
+void FolderStorage::removeMsg(const QPtrList<KMMsgBase>& msgList, bool imapQuiet)
+{
+ for( QPtrListIterator<KMMsgBase> it( msgList ); *it; ++it )
+ {
+ int idx = find(it.current());
+ assert( idx != -1);
+ removeMsg(idx, imapQuiet);
+ }
+}
+
+//-----------------------------------------------------------------------------
+void FolderStorage::removeMsg(const QPtrList<KMMessage>& msgList, bool imapQuiet)
+{
+ for( QPtrListIterator<KMMessage> it( msgList ); *it; ++it )
+ {
+ int idx = find(it.current());
+ assert( idx != -1);
+ removeMsg(idx, imapQuiet);
+ }
+}
+
+//-----------------------------------------------------------------------------
+void FolderStorage::removeMsg(int idx, bool)
+{
+ //assert(idx>=0);
+ if(idx < 0)
+ {
+ kdDebug(5006) << "FolderStorage::removeMsg() : idx < 0\n" << endl;
+ return;
+ }
+
+ KMMsgBase* mb = getMsgBase(idx);
+
+ Q_UINT32 serNum = KMMsgDict::instance()->getMsgSerNum( folder(), idx );
+ if (serNum != 0)
+ emit msgRemoved( folder(), serNum );
+ mb = takeIndexEntry( idx );
+
+ setDirty( true );
+ needsCompact=true; // message is taken from here - needs to be compacted
+
+ if (mb->isUnread() || mb->isNew() ||
+ (folder() == kmkernel->outboxFolder())) {
+ --mUnreadMsgs;
+ if ( !mQuiet ) {
+// kdDebug( 5006 ) << "FolderStorage::msgStatusChanged" << endl;
+ emit numUnreadMsgsChanged( folder() );
+ }else{
+ if ( !mEmitChangedTimer->isActive() ) {
+// kdDebug( 5006 )<< "EmitChangedTimer started" << endl;
+ mEmitChangedTimer->start( 3000 );
+ }
+ mChanged = true;
+ }
+ }
+ --mTotalMsgs;
+
+ mSize = -1;
+ QString msgIdMD5 = mb->msgIdMD5();
+ emit msgRemoved( idx, msgIdMD5 );
+ emit msgRemoved( folder() );
+}
+
+
+//-----------------------------------------------------------------------------
+KMMessage* FolderStorage::take(int idx)
+{
+ KMMsgBase* mb;
+ KMMessage* msg;
+
+ assert(idx>=0 && idx<=count());
+
+ mb = getMsgBase(idx);
+ if (!mb) return 0;
+ if (!mb->isMessage()) readMsg(idx);
+ Q_UINT32 serNum = KMMsgDict::instance()->getMsgSerNum( folder(), idx );
+ emit msgRemoved( folder(), serNum );
+
+ msg = (KMMessage*)takeIndexEntry(idx);
+
+ if (msg->isUnread() || msg->isNew() ||
+ ( folder() == kmkernel->outboxFolder() )) {
+ --mUnreadMsgs;
+ if ( !mQuiet ) {
+ emit numUnreadMsgsChanged( folder() );
+ }else{
+ if ( !mEmitChangedTimer->isActive() ) {
+ mEmitChangedTimer->start( 3000 );
+ }
+ mChanged = true;
+ }
+ }
+ --mTotalMsgs;
+ msg->setParent(0);
+ setDirty( true );
+ mSize = -1;
+ needsCompact=true; // message is taken from here - needs to be compacted
+ QString msgIdMD5 = msg->msgIdMD5();
+ emit msgRemoved( idx, msgIdMD5 );
+ emit msgRemoved( folder() );
+
+ return msg;
+}
+
+void FolderStorage::take(QPtrList<KMMessage> msgList)
+{
+ for ( KMMessage* msg = msgList.first(); msg; msg = msgList.next() )
+ {
+ if (msg->parent())
+ {
+ int idx = msg->parent()->find(msg);
+ if ( idx >= 0 )
+ take(idx);
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+KMMessage* FolderStorage::getMsg(int idx)
+{
+ if ( mOpenCount <= 0 ) {
+ 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;
+ return 0;
+ }
+
+ KMMsgBase* mb = getMsgBase(idx);
+ if (!mb) {
+ kdWarning(5006) << "FolderStorage::getMsg, getMsgBase failed for index: " << idx << endl;
+ return 0;
+ }
+
+ KMMessage *msg = 0;
+ bool undo = mb->enableUndo();
+ if (mb->isMessage()) {
+ msg = ((KMMessage*)mb);
+ } else {
+ QString mbSubject = mb->subject();
+ msg = readMsg(idx);
+ // sanity check
+ if (mCompactable && (!msg || (msg->subject().isEmpty() != mbSubject.isEmpty()))) {
+ kdDebug(5006) << "Error: " << location() <<
+ " Index file is inconsistent with folder file. This should never happen." << endl;
+ mCompactable = false; // Don't compact
+ writeConfig();
+ }
+
+ }
+ // Either isMessage and we had a sernum, or readMsg gives us one
+ // (via insertion into mMsgList). sernum == 0 may still occur due to
+ // an outdated or corrupt IMAP cache.
+ if ( msg->getMsgSerNum() == 0 ) {
+ kdWarning(5006) << "FolderStorage::getMsg, message has no sernum, index: " << idx << endl;
+ return 0;
+ }
+ msg->setEnableUndo(undo);
+ msg->setComplete( true );
+ return msg;
+}
+
+//-----------------------------------------------------------------------------
+KMMessage* FolderStorage::readTemporaryMsg(int idx)
+{
+ if(!(idx >= 0 && idx <= count()))
+ return 0;
+
+ KMMsgBase* mb = getMsgBase(idx);
+ if (!mb) return 0;
+
+ unsigned long sernum = mb->getMsgSerNum();
+
+ KMMessage *msg = 0;
+ bool undo = mb->enableUndo();
+ if (mb->isMessage()) {
+ // the caller will delete it, so we must make a copy it
+ msg = new KMMessage(*(KMMessage*)mb);
+ msg->setMsgSerNum(sernum);
+ msg->setComplete( true );
+ } else {
+ // ## Those two lines need to be moved to a virtual method for KMFolderSearch, like readMsg
+ msg = new KMMessage(*(KMMsgInfo*)mb);
+ msg->setMsgSerNum(sernum); // before fromDwString so that readyToShow uses the right sernum
+ msg->setComplete( true );
+ msg->fromDwString(getDwString(idx));
+ }
+ msg->setEnableUndo(undo);
+ return msg;
+}
+
+
+//-----------------------------------------------------------------------------
+KMMsgInfo* FolderStorage::unGetMsg(int idx)
+{
+ KMMsgBase* mb;
+
+ if(!(idx >= 0 && idx <= count()))
+ return 0;
+
+ mb = getMsgBase(idx);
+ if (!mb) return 0;
+
+
+ if (mb->isMessage()) {
+ // Remove this message from all jobs' list it might still be on.
+ // setIndexEntry deletes the message.
+ KMMessage *msg = static_cast<KMMessage*>(mb);
+ if ( msg->transferInProgress() ) return 0;
+ ignoreJobsForMessage( msg );
+ return setIndexEntry( idx, msg );
+ }
+
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+bool FolderStorage::isMessage(int idx)
+{
+ KMMsgBase* mb;
+ if (!(idx >= 0 && idx <= count())) return false;
+ mb = getMsgBase(idx);
+ return (mb && mb->isMessage());
+}
+
+//-----------------------------------------------------------------------------
+FolderJob* FolderStorage::createJob( KMMessage *msg, FolderJob::JobType jt,
+ KMFolder *folder, QString partSpecifier,
+ const AttachmentStrategy *as ) const
+{
+ FolderJob * job = doCreateJob( msg, jt, folder, partSpecifier, as );
+ if ( job )
+ addJob( job );
+ return job;
+}
+
+//-----------------------------------------------------------------------------
+FolderJob* FolderStorage::createJob( QPtrList<KMMessage>& msgList, const QString& sets,
+ FolderJob::JobType jt, KMFolder *folder ) const
+{
+ FolderJob * job = doCreateJob( msgList, sets, jt, folder );
+ if ( job )
+ addJob( job );
+ return job;
+}
+
+//-----------------------------------------------------------------------------
+int FolderStorage::moveMsg(KMMessage* aMsg, int* aIndex_ret)
+{
+ assert(aMsg != 0);
+ KMFolder* msgParent = aMsg->parent();
+
+ if (msgParent)
+ msgParent->open("moveMsgSrc");
+
+ open("moveMsgDest");
+ int rc = addMsg(aMsg, aIndex_ret);
+ close("moveMsgDest");
+
+ if (msgParent)
+ msgParent->close("moveMsgSrc");
+
+ return rc;
+}
+
+//-----------------------------------------------------------------------------
+int FolderStorage::moveMsg(QPtrList<KMMessage> msglist, int* aIndex_ret)
+{
+ KMMessage* aMsg = msglist.first();
+ assert(aMsg != 0);
+ KMFolder* msgParent = aMsg->parent();
+
+ if (msgParent)
+ msgParent->open("foldermovemsg");
+
+ QValueList<int> index;
+ open("moveMsg");
+ int rc = addMsg(msglist, index);
+ close("moveMsg");
+ // FIXME: we want to have a QValueList to pass it back, so change this method
+ if ( !index.isEmpty() )
+ aIndex_ret = &index.first();
+
+ if (msgParent)
+ msgParent->close("foldermovemsg");
+
+ return rc;
+}
+
+
+//-----------------------------------------------------------------------------
+int FolderStorage::rename(const QString& newName, KMFolderDir *newParent)
+{
+ QString oldLoc, oldIndexLoc, oldIdsLoc, newLoc, newIndexLoc, newIdsLoc;
+ QString oldSubDirLoc, newSubDirLoc;
+ QString oldName;
+ int rc=0;
+ KMFolderDir *oldParent;
+
+ assert(!newName.isEmpty());
+
+ oldLoc = location();
+ oldIndexLoc = indexLocation();
+ oldSubDirLoc = folder()->subdirLocation();
+ oldIdsLoc = KMMsgDict::instance()->getFolderIdsLocation( *this );
+ QString oldConfigString = "Folder-" + folder()->idString();
+
+ close("rename", true);
+
+ oldName = folder()->fileName();
+ oldParent = folder()->parent();
+ if (newParent)
+ folder()->setParent( newParent );
+
+ folder()->setName(newName);
+ newLoc = location();
+ newIndexLoc = indexLocation();
+ newSubDirLoc = folder()->subdirLocation();
+ newIdsLoc = KMMsgDict::instance()->getFolderIdsLocation( *this );
+
+ if (::rename(QFile::encodeName(oldLoc), QFile::encodeName(newLoc))) {
+ folder()->setName(oldName);
+ folder()->setParent(oldParent);
+ rc = errno;
+ }
+ else {
+ // rename/move index file and index.sorted file
+ if (!oldIndexLoc.isEmpty()) {
+ ::rename(QFile::encodeName(oldIndexLoc), QFile::encodeName(newIndexLoc));
+ ::rename(QFile::encodeName(oldIndexLoc) + ".sorted",
+ QFile::encodeName(newIndexLoc) + ".sorted");
+ }
+
+ // rename/move serial number file
+ if (!oldIdsLoc.isEmpty())
+ ::rename(QFile::encodeName(oldIdsLoc), QFile::encodeName(newIdsLoc));
+
+ // rename/move the subfolder directory
+ KMFolderDir* child = 0;
+ if( folder() )
+ child = folder()->child();
+
+ if (!::rename(QFile::encodeName(oldSubDirLoc), QFile::encodeName(newSubDirLoc) )) {
+ // now that the subfolder directory has been renamed and/or moved also
+ // change the name that is stored in the corresponding KMFolderNode
+ // (provide that the name actually changed)
+ if( child && ( oldName != newName ) ) {
+ child->setName( "." + QFile::encodeName(newName) + ".directory" );
+ }
+ }
+
+ // if the folder is being moved then move its node and, if necessary, also
+ // the associated subfolder directory node to the new parent
+ if (newParent) {
+ if (oldParent->findRef( folder() ) != -1)
+ oldParent->take();
+ newParent->inSort( folder() );
+ if ( child ) {
+ if ( child->parent()->findRef( child ) != -1 )
+ child->parent()->take();
+ newParent->inSort( child );
+ child->setParent( newParent );
+ }
+ }
+ }
+
+ writeConfig();
+
+ // delete the old entry as we get two entries with the same ID otherwise
+ if ( oldConfigString != "Folder-" + folder()->idString() )
+ KMKernel::config()->deleteGroup( oldConfigString );
+
+ emit locationChanged( oldLoc, newLoc );
+ emit nameChanged();
+ kmkernel->folderMgr()->contentsChanged();
+ emit closed(folder()); // let the ticket owners regain
+ return rc;
+}
+
+
+//-----------------------------------------------------------------------------
+void FolderStorage::remove()
+{
+ assert(!folder()->name().isEmpty());
+
+ clearIndex( true, mExportsSernums ); // delete and remove from dict if necessary
+ close("remove", true);
+
+ if ( mExportsSernums ) {
+ KMMsgDict::mutableInstance()->removeFolderIds( *this );
+ mExportsSernums = false; // do not writeFolderIds after removal
+ }
+ unlink(QFile::encodeName(indexLocation()) + ".sorted");
+ unlink(QFile::encodeName(indexLocation()));
+
+ int rc = removeContents();
+
+ needsCompact = false; //we are dead - no need to compact us
+
+ // Erase settings, otherwise they might interfer when recreating the folder
+ KConfig* config = KMKernel::config();
+ config->deleteGroup( "Folder-" + folder()->idString() );
+
+ emit closed(folder());
+ emit removed(folder(), (rc ? false : true));
+}
+
+
+//-----------------------------------------------------------------------------
+int FolderStorage::expunge()
+{
+ assert(!folder()->name().isEmpty());
+
+ clearIndex( true, mExportsSernums ); // delete and remove from dict, if needed
+ close( "expunge", true );
+
+ if ( mExportsSernums )
+ KMMsgDict::mutableInstance()->removeFolderIds( *this );
+ if ( mAutoCreateIndex )
+ truncateIndex();
+ else unlink(QFile::encodeName(indexLocation()));
+
+ int rc = expungeContents();
+ if (rc) return rc;
+
+ mDirty = false;
+ needsCompact = false; //we're cleared and truncated no need to compact
+
+ mUnreadMsgs = 0;
+ mTotalMsgs = 0;
+ mSize = 0;
+ emit numUnreadMsgsChanged( folder() );
+ if ( mAutoCreateIndex ) // FIXME Heh? - Till
+ writeConfig();
+ emit changed();
+ emit expunged( folder() );
+
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+QString FolderStorage::label() const
+{
+ return folder()->label();
+}
+
+int FolderStorage::count(bool cache) const
+{
+ if (cache && mTotalMsgs != -1)
+ return mTotalMsgs;
+ else
+ return -1;
+}
+
+//-----------------------------------------------------------------------------
+int FolderStorage::countUnread()
+{
+ if (mGuessedUnreadMsgs > -1)
+ return mGuessedUnreadMsgs;
+ if (mUnreadMsgs > -1)
+ return mUnreadMsgs;
+
+ readConfig();
+
+ if (mUnreadMsgs > -1)
+ return mUnreadMsgs;
+
+ open("countunread"); // will update unreadMsgs
+ int unread = mUnreadMsgs;
+ close("countunread");
+ return (unread > 0) ? unread : 0;
+}
+
+Q_INT64 FolderStorage::folderSize() const
+{
+ if ( mSize != -1 ) {
+ return mSize;
+ } else {
+ return doFolderSize();
+ }
+}
+
+
+/*virtual*/
+bool FolderStorage::isCloseToQuota() const
+{
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+void FolderStorage::msgStatusChanged(const KMMsgStatus oldStatus,
+ const KMMsgStatus newStatus, int idx)
+{
+ int oldUnread = 0;
+ int newUnread = 0;
+
+ if (((oldStatus & KMMsgStatusUnread || oldStatus & KMMsgStatusNew) &&
+ !(oldStatus & KMMsgStatusIgnored)) ||
+ (folder() == kmkernel->outboxFolder()))
+ oldUnread = 1;
+ if (((newStatus & KMMsgStatusUnread || newStatus & KMMsgStatusNew) &&
+ !(newStatus & KMMsgStatusIgnored)) ||
+ (folder() == kmkernel->outboxFolder()))
+ newUnread = 1;
+ int deltaUnread = newUnread - oldUnread;
+
+ mDirtyTimer->changeInterval(mDirtyTimerInterval);
+ if (deltaUnread != 0) {
+ if (mUnreadMsgs < 0) mUnreadMsgs = 0;
+ mUnreadMsgs += deltaUnread;
+ if ( !mQuiet ) {
+ emit numUnreadMsgsChanged( folder() );
+ }else{
+ if ( !mEmitChangedTimer->isActive() ) {
+ mEmitChangedTimer->start( 3000 );
+ }
+ mChanged = true;
+ }
+ Q_UINT32 serNum = KMMsgDict::instance()->getMsgSerNum(folder(), idx);
+ emit msgChanged( folder(), serNum, deltaUnread );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void FolderStorage::headerOfMsgChanged(const KMMsgBase* aMsg, int idx)
+{
+ if (idx < 0)
+ idx = aMsg->parent()->find( aMsg );
+
+ if (idx >= 0 )
+ {
+ if ( !mQuiet )
+ emit msgHeaderChanged(folder(), idx);
+ else{
+ if ( !mEmitChangedTimer->isActive() ) {
+ mEmitChangedTimer->start( 3000 );
+ }
+ mChanged = true;
+ }
+ } else
+ mChanged = true;
+}
+
+//-----------------------------------------------------------------------------
+void FolderStorage::readConfig()
+{
+ //kdDebug(5006)<<"#### READING CONFIG = "<< name() <<endl;
+ KConfig* config = KMKernel::config();
+ KConfigGroupSaver saver(config, "Folder-" + folder()->idString());
+ if (mUnreadMsgs == -1)
+ mUnreadMsgs = config->readNumEntry("UnreadMsgs", -1);
+ if (mTotalMsgs == -1)
+ mTotalMsgs = config->readNumEntry("TotalMsgs", -1);
+ 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<KMail::FolderContentsType>( type ) );
+
+ if( folder() ) folder()->readConfig( config );
+}
+
+//-----------------------------------------------------------------------------
+void FolderStorage::writeConfig()
+{
+ KConfig* config = KMKernel::config();
+ KConfigGroupSaver saver(config, "Folder-" + folder()->idString());
+ config->writeEntry("UnreadMsgs",
+ mGuessedUnreadMsgs == -1 ? mUnreadMsgs : mGuessedUnreadMsgs);
+ config->writeEntry("TotalMsgs", mTotalMsgs);
+ config->writeEntry("Compactable", mCompactable);
+ config->writeEntry("ContentsType", mContentsType);
+ config->writeEntry("FolderSize", mSize);
+
+ // Write the KMFolder parts
+ if( folder() ) folder()->writeConfig( config );
+
+ GlobalSettings::self()->requestSync();
+}
+
+//-----------------------------------------------------------------------------
+void FolderStorage::correctUnreadMsgsCount()
+{
+ open("countunreadmsg");
+ close("countunreadmsg");
+ emit numUnreadMsgsChanged( folder() );
+}
+
+void FolderStorage::registerWithMessageDict()
+{
+ mExportsSernums = true;
+ readFolderIdsFile();
+}
+
+void FolderStorage::deregisterFromMessageDict()
+{
+ writeFolderIdsFile();
+ mExportsSernums = false;
+}
+
+void FolderStorage::readFolderIdsFile()
+{
+ if ( !mExportsSernums ) return;
+ if ( KMMsgDict::mutableInstance()->readFolderIds( *this ) == -1 ) {
+ invalidateFolder();
+ }
+ if ( !KMMsgDict::mutableInstance()->hasFolderIds( *this ) ) {
+ invalidateFolder();
+ }
+}
+
+void FolderStorage::invalidateFolder()
+{
+ if ( !mExportsSernums ) return;
+ unlink(QFile::encodeName( indexLocation()) + ".sorted");
+ unlink(QFile::encodeName( indexLocation()) + ".ids");
+ fillMessageDict();
+ KMMsgDict::mutableInstance()->writeFolderIds( *this );
+ emit invalidated( folder() );
+}
+
+
+//-----------------------------------------------------------------------------
+int FolderStorage::writeFolderIdsFile() const
+{
+ if ( !mExportsSernums ) return -1;
+ return KMMsgDict::mutableInstance()->writeFolderIds( *this );
+}
+
+//-----------------------------------------------------------------------------
+int FolderStorage::touchFolderIdsFile()
+{
+ if ( !mExportsSernums ) return -1;
+ return KMMsgDict::mutableInstance()->touchFolderIds( *this );
+}
+
+//-----------------------------------------------------------------------------
+int FolderStorage::appendToFolderIdsFile( int idx )
+{
+ if ( !mExportsSernums ) return -1;
+ int ret = 0;
+ if ( count() == 1 ) {
+ ret = KMMsgDict::mutableInstance()->writeFolderIds( *this );
+ } else {
+ ret = KMMsgDict::mutableInstance()->appendToFolderIds( *this, idx );
+ }
+ return ret;
+}
+
+void FolderStorage::replaceMsgSerNum( unsigned long sernum, KMMsgBase* msg, int idx )
+{
+ if ( !mExportsSernums ) return;
+ KMMsgDict::mutableInstance()->replace( sernum, msg, idx );
+}
+
+void FolderStorage::setRDict( KMMsgDictREntry *rentry ) const
+{
+ if ( ! mExportsSernums )
+ kdDebug(5006) << "WTF, this FolderStorage should be invisible to the msgdict, who is calling us?" << kdBacktrace() << endl;
+ assert( mExportsSernums ); // otherwise things are very wrong
+ if ( rentry == mRDict )
+ return;
+ KMMsgDict::deleteRentry( mRDict );
+ mRDict = rentry;
+}
+
+//-----------------------------------------------------------------------------
+void FolderStorage::setStatus(int idx, KMMsgStatus status, bool toggle)
+{
+ KMMsgBase *msg = getMsgBase(idx);
+ if ( msg ) {
+ if (toggle)
+ msg->toggleStatus(status, idx);
+ else
+ msg->setStatus(status, idx);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void FolderStorage::setStatus(QValueList<int>& ids, KMMsgStatus status, bool toggle)
+{
+ for ( QValueList<int>::Iterator it = ids.begin(); it != ids.end(); ++it )
+ {
+ FolderStorage::setStatus(*it, status, toggle);
+ }
+}
+
+void FolderStorage::ignoreJobsForMessage( KMMessage *msg )
+{
+ if ( !msg || msg->transferInProgress() )
+ return;
+
+ QPtrListIterator<FolderJob> it( mJobList );
+ while ( it.current() )
+ {
+ //FIXME: the questions is : should we iterate through all
+ //messages in jobs? I don't think so, because it would
+ //mean canceling the jobs that work with other messages
+ if ( it.current()->msgList().first() == msg )
+ {
+ FolderJob* job = it.current();
+ mJobList.remove( job );
+ delete job;
+ } else
+ ++it;
+ }
+}
+
+//-----------------------------------------------------------------------------
+void FolderStorage::removeJobs()
+{
+ mJobList.setAutoDelete( true );
+ mJobList.clear();
+ mJobList.setAutoDelete( false );
+}
+
+
+
+//-----------------------------------------------------------------------------
+void FolderStorage::updateChildrenState()
+{
+ if ( folder() && folder()->child() )
+ {
+ if ( kmkernel->folderMgr()->folderCount( folder()->child() ) > 0 )
+ setHasChildren( HasChildren );
+ else
+ setHasChildren( HasNoChildren );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void FolderStorage::setNoChildren( bool aNoChildren )
+{
+ mNoChildren = aNoChildren;
+ if ( aNoChildren )
+ setHasChildren( HasNoChildren );
+}
+
+//-----------------------------------------------------------------------------
+void FolderStorage::setContentsType( KMail::FolderContentsType type, bool quiet )
+{
+ if ( type != mContentsType ) {
+ mContentsType = type;
+ if ( !quiet )
+ emit contentsTypeChanged( type );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void FolderStorage::search( const KMSearchPattern* pattern )
+{
+ mSearchPattern = pattern;
+ mCurrentSearchedMsg = 0;
+ if ( pattern )
+ slotProcessNextSearchBatch();
+}
+
+void FolderStorage::slotProcessNextSearchBatch()
+{
+ if ( !mSearchPattern )
+ return;
+ QValueList<Q_UINT32> matchingSerNums;
+ const int end = QMIN( mCurrentSearchedMsg + 15, count() );
+ for ( int i = mCurrentSearchedMsg; i < end; ++i )
+ {
+ Q_UINT32 serNum = KMMsgDict::instance()->getMsgSerNum( folder(), i );
+ if ( mSearchPattern->matches( serNum ) )
+ matchingSerNums.append( serNum );
+ }
+ mCurrentSearchedMsg = end;
+ bool complete = ( end >= count() );
+ emit searchResult( folder(), matchingSerNums, mSearchPattern, complete );
+ if ( !complete )
+ QTimer::singleShot( 0, this, SLOT(slotProcessNextSearchBatch()) );
+}
+
+//-----------------------------------------------------------------------------
+void FolderStorage::search( const KMSearchPattern* pattern, Q_UINT32 serNum )
+{
+ bool matches = pattern && pattern->matches( serNum );
+
+ emit searchDone( folder(), serNum, pattern, matches );
+}
+
+//-----------------------------------------------------------------------------
+int FolderStorage::addMsg( QPtrList<KMMessage>& msgList, QValueList<int>& index_ret )
+{
+ int ret = 0;
+ int index;
+ for ( QPtrListIterator<KMMessage> it( msgList ); *it; ++it )
+ {
+ int aret = addMsg( *it, &index );
+ index_ret << index;
+ if ( aret != 0 ) // error condition
+ ret = aret;
+ }
+ return ret;
+}
+
+//-----------------------------------------------------------------------------
+bool FolderStorage::isMoveable() const
+{
+ return ( folder()->isSystemFolder() ) ? false : true;
+}
+
+
+/*virtual*/
+KMAccount* FolderStorage::account() const
+{
+ return 0;
+}
+
+#include "folderstorage.moc"
diff --git a/kmail/folderstorage.h b/kmail/folderstorage.h
new file mode 100644
index 00000000..9b71f28a
--- /dev/null
+++ b/kmail/folderstorage.h
@@ -0,0 +1,645 @@
+/*
+ Virtual base class for mail storage.
+
+ This file is part of KMail.
+
+ Copyright (c) 2004 Bo Thorsen <bo@sonofthor.dk>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef FOLDERSTORAGE_H
+#define FOLDERSTORAGE_H
+
+// for large file support
+#include <config.h>
+
+#include "kmfoldernode.h"
+#include "kmfoldertype.h"
+#include "kmmsginfo.h"
+#include "kmglobal.h"
+#include "folderjob.h"
+using KMail::FolderJob;
+
+#include "mimelib/string.h"
+
+#include <sys/types.h>
+#include <stdio.h>
+
+class KMMessage;
+class KMAccount;
+class KMFolderDir;
+class KMMsgDict; // for the rDict manipulations
+class KMMsgDictREntry;
+class QTimer;
+class KMSearchPattern;
+
+namespace KMail {
+ class AttachmentStrategy;
+}
+using KMail::AttachmentStrategy;
+
+typedef QValueList<Q_UINT32> SerNumList;
+
+/**
+ * @short The FolderStorage class is the bass class for the storage related
+ * aspects of a collection of mail (a folder).
+ *
+ * @section Accounts
+ * The accounts (of KMail) that are fed into the folder are
+ * represented as the children of the folder. They are only stored here
+ * during runtime to have a reference for which accounts point to a
+ * specific folder.
+ */
+
+class FolderStorage : public QObject
+{
+ Q_OBJECT
+
+public:
+
+
+ /** Usually a parent is given. But in some cases there is no
+ fitting parent object available. Then the name of the folder
+ is used as the absolute path to the folder file. */
+ FolderStorage( KMFolder* folder, const char* name=0 );
+ virtual ~FolderStorage();
+
+ KMFolder* folder() const { return mFolder; }
+
+ /** Returns the type of this folder */
+ virtual KMFolderType folderType() const { return KMFolderTypeUnknown; }
+
+ /** Returns the filename of the folder (reimplemented in KMFolderImap) */
+ virtual QString fileName() const;
+ /** Returns full path to folder file */
+ QString location() const;
+
+ /** Returns full path to index file */
+ virtual QString indexLocation() const = 0;
+
+ /** Returns, if the folder can't contain mails, but only subfolder */
+ virtual bool noContent() const { return mNoContent; }
+
+ /** Specify, that the folder can't contain mails. */
+ virtual void setNoContent(bool aNoContent)
+ { mNoContent = aNoContent; }
+
+ /** Returns, if the folder can't have children */
+ virtual bool noChildren() const { return mNoChildren; }
+
+ /** Specify, that the folder can't have children */
+ virtual void setNoChildren( bool aNoChildren );
+
+ enum ChildrenState {
+ HasChildren,
+ HasNoChildren,
+ ChildrenUnknown
+ };
+ /** Returns if the folder has children,
+ * has no children or we don't know */
+ virtual ChildrenState hasChildren() const { return mHasChildren; }
+
+ /** Specify if the folder has children */
+ virtual void setHasChildren( ChildrenState state )
+ { mHasChildren = state; }
+
+ /** Updates the hasChildren() state */
+ virtual void updateChildrenState();
+
+ /** Read message at given index. Indexing starts at zero */
+ virtual KMMessage* getMsg(int idx);
+
+ /** Replace KMMessage with KMMsgInfo and delete KMMessage */
+ virtual KMMsgInfo* unGetMsg(int idx);
+
+ /** Checks if the message is already "gotten" with getMsg */
+ virtual bool isMessage(int idx);
+
+ /** Load message from file and do NOT store it, only return it.
+ This is equivalent to, but faster than, getMsg+unGetMsg
+ WARNING: the caller has to delete the returned value!
+ */
+ virtual KMMessage* readTemporaryMsg(int idx);
+
+ /** Read a message and returns a DwString */
+ virtual DwString getDwString(int idx) = 0;
+
+ /**
+ * Removes and deletes all jobs associated with the particular message
+ */
+ virtual void ignoreJobsForMessage( KMMessage* );
+
+ /**
+ * These methods create respective FolderJob (You should derive FolderJob
+ * for each derived KMFolder).
+ */
+ virtual FolderJob* createJob( KMMessage *msg, FolderJob::JobType jt = FolderJob::tGetMessage,
+ KMFolder *folder = 0, QString partSpecifier = QString::null,
+ const AttachmentStrategy *as = 0 ) const;
+ virtual FolderJob* createJob( QPtrList<KMMessage>& msgList, const QString& sets,
+ FolderJob::JobType jt = FolderJob::tGetMessage,
+ KMFolder *folder = 0 ) const;
+
+ /** Provides access to the basic message fields that are also stored
+ in the index. Whenever you only need subject, from, date, status
+ you should use this method instead of getMsg() because getMsg()
+ will load the message if necessary and this method does not. */
+ virtual const KMMsgBase* getMsgBase(int idx) const = 0;
+ virtual KMMsgBase* getMsgBase(int idx) = 0;
+
+ /** Same as getMsgBase(int). */
+ virtual const KMMsgBase* operator[](int idx) const { return getMsgBase(idx); }
+
+ /** Same as getMsgBase(int). This time non-const. */
+ virtual KMMsgBase* operator[](int idx) { return getMsgBase(idx); }
+
+ /** Detach message from this folder. Usable to call addMsg() afterwards.
+ Loads the message if it is not loaded up to now. */
+ virtual KMMessage* take(int idx);
+ virtual void take(QPtrList<KMMessage> msgList);
+
+ /** Add the given message to the folder. Usually the message
+ is added at the end of the folder. Returns zero on success and
+ an errno error code on failure. The index of the new message
+ is stored in index_return if given.
+ Please note that the message is added as is to the folder and the folder
+ takes ownership of the message (deleting it in the destructor).*/
+ virtual int addMsg(KMMessage* msg, int* index_return = 0) = 0;
+
+ /** (Note(bo): This needs to be fixed better at a later point.)
+ This is overridden by dIMAP because addMsg strips the X-UID
+ header from the mail. */
+ virtual int addMsgKeepUID(KMMessage* msg, int* index_return = 0) {
+ return addMsg(msg, index_return);
+ }
+
+ /**
+ * Adds the given messages to the folder. Behaviour is identical
+ * to addMsg(msg)
+ */
+ virtual int addMsg( QPtrList<KMMessage>&, QValueList<int>& index_return );
+
+ /** Called by derived classes implementation of addMsg.
+ Emits msgAdded signals */
+ void emitMsgAddedSignals(int idx);
+
+ /** Returns FALSE, if the message has to be retrieved from an IMAP account
+ * first. In this case this function does this and cares for the rest */
+ virtual bool canAddMsgNow(KMMessage* aMsg, int* aIndex_ret);
+
+ /** Remove (first occurrence of) given message from the folder. */
+ virtual void removeMsg(int i, bool imapQuiet = false);
+ virtual void removeMsg(const QPtrList<KMMsgBase>& msgList, bool imapQuiet = false);
+ virtual void removeMsg(const QPtrList<KMMessage>& msgList, bool imapQuiet = false);
+
+ /** Delete messages in the folder that are older than days. Return the
+ * number of deleted messages. */
+ virtual int expungeOldMsg(int days);
+
+ /** Detaches the given message from it's current folder and
+ adds it to this folder. Returns zero on success and an errno error
+ code on failure. The index of the new message is stored in index_return
+ if given. */
+ virtual int moveMsg(KMMessage* msg, int* index_return = 0);
+ virtual int moveMsg(QPtrList<KMMessage>, int* index_return = 0);
+
+ /** Returns the index of the given message or -1 if not found. */
+ virtual int find(const KMMsgBase* msg) const = 0;
+ int find( const KMMessage * msg ) const;
+
+ /** Number of messages in this folder. */
+ virtual int count(bool cache = false) const;
+
+ /** Number of new or unread messages in this folder. */
+ virtual int countUnread();
+
+ /** Total size of the contents of this folder. */
+ Q_INT64 folderSize() const;
+
+ /** Return whether the folder is close to its quota limit, which can
+ * be reflected in the UI. */
+ virtual bool isCloseToQuota() const;
+
+ /** Called by KMMsgBase::setStatus when status of a message has changed
+ required to keep the number unread messages variable current. */
+ virtual void msgStatusChanged( const KMMsgStatus oldStatus,
+ const KMMsgStatus newStatus,
+ int idx);
+
+ /** Open folder for access.
+ open() and close() use reference counting.
+ Returns zero on success and an error code equal to the c-library
+ fopen call otherwise (errno).
+ @see KMFolderOpener */
+ virtual int open(const char* owner) = 0;
+
+ /** Check folder for permissions
+ Returns zero if readable and writable. */
+ virtual int canAccess() = 0;
+
+ /** Close folder. open() and close() use reference counting.
+ If @p force is true the files are closed regardless of reference count,
+ and the reference count will be set to zero. */
+ void close(const char* owner, bool force=false);
+ virtual void reallyDoClose(const char* owner) = 0;
+
+ /** Try releasing @p folder if possible, something is attempting an exclusive access to it.
+ Currently used for KMFolderSearch and the background tasks like expiry. */
+ virtual void tryReleasingFolder(KMFolder*) {}
+
+ /** fsync buffers to disk */
+ virtual void sync() = 0;
+
+ /** Test if folder is opened, i.e. its reference count is greater than zero. */
+ bool isOpened() const { return (mOpenCount>0); }
+
+ /** Mark all new messages as unread. */
+ virtual void markNewAsUnread();
+
+ /** Mark all new and unread messages as read. */
+ virtual void markUnreadAsRead();
+
+ /** Create a new folder with the name of this object and open it.
+ Returns zero on success and an error code equal to the
+ c-library fopen call otherwise. */
+ virtual int create() = 0;
+
+ /** Removes the folder physically from disk and empties the contents
+ of the folder in memory. Note that the folder is closed during this
+ process, whether there are others using it or not.
+ @see KMFolder::removeContents */
+ virtual void remove();
+
+ /** Delete entire folder. Forces a close *but* opens the
+ folder again afterwards. Returns errno(3) error code or zero on
+ success. see KMFolder::expungeContents */
+ virtual int expunge();
+
+ /** Remove deleted messages from the folder. Returns zero on success
+ and an errno on failure.
+ A statusbar message will inform the user that the compaction worked,
+ unless @p silent is set. */
+ virtual int compact( bool silent ) = 0;
+
+ /** Physically rename the folder. Returns zero on success and an errno
+ on failure. */
+ virtual int rename(const QString& newName, KMFolderDir *aParent = 0);
+
+ /** Returns TRUE if a table of contents file is automatically created. */
+ bool autoCreateIndex() const { return mAutoCreateIndex; }
+
+ /** Allow/disallow automatic creation of a table of contents file.
+ Default is TRUE. */
+ virtual void setAutoCreateIndex(bool);
+
+ /** Returns TRUE if the table of contents is dirty. This happens when
+ a message is deleted from the folder. The toc will then be re-created
+ when the folder is closed. */
+ bool dirty() const { return mDirty; }
+
+ /** Change the dirty flag. */
+ void setDirty(bool f);
+
+ /** Returns TRUE if the folder contains deleted messages */
+ bool needsCompacting() const { return needsCompact; }
+ virtual void setNeedsCompacting(bool f) { needsCompact = f; }
+
+ /** If set to quiet the folder will not emit msgAdded(idx) signal.
+ This is necessary because adding the messages to the listview
+ one by one as they come in ( as happens on msgAdded(idx) ) is
+ very slow for large ( >10000 ) folders. For pop, where whole
+ bodies are downloaded this is not an issue, but for imap, where
+ we only download headers it becomes a bottleneck. We therefore
+ set the folder quiet() and rebuild the listview completely once
+ the complete folder has been checked. */
+ virtual void quiet(bool beQuiet);
+
+ /** Is the folder read-only? */
+ virtual bool isReadOnly() const = 0;
+
+ /** Returns the label of the folder for visualization. */
+ QString label() const;
+
+ /** A cludge to help make sure the count of unread messges is kept in sync */
+ virtual void correctUnreadMsgsCount();
+
+ /** Write index to index-file. Returns 0 on success and errno error on
+ failure. */
+ virtual int writeIndex( bool createEmptyIndex = false ) = 0;
+
+ /** Triggers registration with the message dict, which will cause the dict
+ * to ask the FolderStorage to fill itself into it. */
+ void registerWithMessageDict();
+
+ /** Triggers deregistration from the message dict, which will cause the dict
+ * to ask the FolderStorage to write the relevant data structures to disk.. */
+ void deregisterFromMessageDict();
+
+ /** Set the status of the message at index @p idx to @p status. */
+ virtual void setStatus(int idx, KMMsgStatus status, bool toggle=false);
+
+ /** Set the status of the message(s) in the QValueList @p ids to @p status. */
+ virtual void setStatus(QValueList<int>& ids, KMMsgStatus status, bool toggle=false);
+
+ void removeJobs();
+
+ /** Escape a leading dot */
+ static QString dotEscape(const QString&);
+
+ /** Read the config file */
+ virtual void readConfig();
+
+ /** Write the config file */
+ virtual void writeConfig();
+
+ /**
+ * If this folder has a special trash folder set, return it. Otherwise
+ * return 0.
+ */
+ virtual KMFolder* trashFolder() const { return 0; }
+
+ /**
+ * Add job for this folder. This is done automatically by createJob.
+ * This method is public only for other kind of FolderJob like ExpireJob.
+ */
+ void addJob( FolderJob* ) const;
+
+ /** false if index file is out of sync with mbox file */
+ bool compactable() const { return mCompactable; }
+
+ /// Set the type of contents held in this folder (mail, calendar, etc.)
+ // If quiet is true, the KMailIcalIface is not informed of the changed. That's usefull
+ // for folder that are being copied around, should retain their type, but not cause
+ // conflicts on copy because events are identical in two folders.
+ virtual void setContentsType( KMail::FolderContentsType type, bool quiet = false );
+ /// @return the type of contents held in this folder (mail, calendar, etc.)
+ KMail::FolderContentsType contentsType() const { return mContentsType; }
+
+ /**
+ * Search for messages
+ * The end is signaled with searchDone()
+ */
+ virtual void search( const KMSearchPattern* );
+
+ /**
+ * Check if the message matches the search criteria
+ * The end is signaled with searchDone()
+ */
+ virtual void search( const KMSearchPattern*, Q_UINT32 serNum );
+
+ /** Returns true if this folder can be moved */
+ virtual bool isMoveable() const;
+
+ virtual KMAccount* account() const;
+
+signals:
+ /** Emitted when the status, name, or associated accounts of this
+ folder changed. */
+ void changed();
+
+ /** Emitted when the contents of a folder have been cleared
+ (new search in a search folder, for example) */
+ void cleared();
+
+ /** Emitted after an expunge. If not quiet, changed() will be
+ emmitted first. */
+ void expunged( KMFolder* );
+
+ /** 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 * );
+
+ /** Emitted when the name of the folder changes. */
+ void nameChanged();
+
+ /** Emitted when the location on disk of the folder changes. This
+ * is used by all code which uses the locatio on disk of the folder storage
+ * ( or the cache storage ) as an identifier for the folder. */
+ void locationChanged( const QString &, const QString & );
+
+ /** Emitted when the contents type (calendar, mail, tasks, ..) of the
+ * folderstorage changes. */
+ void contentsTypeChanged( KMail::FolderContentsType type );
+
+ /** Emitted when the readonly status of the folder changes. */
+ void readOnlyChanged(KMFolder*);
+
+ /** Emitted before a message is removed from the folder. */
+ void msgRemoved(KMFolder*, Q_UINT32 sernum);
+
+ /** Emitted after a message is removed from the folder. */
+ void msgRemoved( int idx, QString msgIdMD5 );
+ void msgRemoved( KMFolder* );
+
+ /** Emitted when a message is added from the folder. */
+ void msgAdded(int idx);
+ void msgAdded(KMFolder*, Q_UINT32 sernum);
+
+ /** Emitted, when the status of a message is changed */
+ void msgChanged(KMFolder*, Q_UINT32 sernum, int delta);
+
+ /** Emitted when a field of the header of a specific message changed. */
+ void msgHeaderChanged(KMFolder*, int);
+
+ /** Emmited to display a message somewhere in a status line. */
+ void statusMsg(const QString&);
+
+ /** Emitted when number of unread messages has changed. */
+ void numUnreadMsgsChanged( KMFolder* );
+
+ /** Emitted when a folder was removed */
+ void removed(KMFolder*, bool);
+
+ /**
+ * Emitted when a search delivers results
+ * The matching serial numbers are included
+ * If @p complete is true the search is done
+ */
+ void searchResult( KMFolder*, QValueList<Q_UINT32>,
+ const KMSearchPattern*, bool complete );
+
+ /**
+ * Emitted when a search for a single message is completed
+ * The serial number and a bool matching yes/no is included
+ */
+ void searchDone( KMFolder*, Q_UINT32, const KMSearchPattern*, bool );
+
+ /** Emitted when the folder's size changes. */
+ void folderSizeChanged();
+
+
+public slots:
+ /** Incrementally update the index if possible else call writeIndex */
+ virtual int updateIndex() = 0;
+
+ /** Add the message to the folder after it has been retrieved from an IMAP
+ server */
+ virtual void reallyAddMsg(KMMessage* aMsg);
+
+ /** Add a copy of the message to the folder after it has been retrieved
+ from an IMAP server */
+ virtual void reallyAddCopyOfMsg(KMMessage* aMsg);
+
+ /** Emit changed signal if mQuite <=0 */
+ void slotEmitChangedTimer();
+
+protected slots:
+ virtual void removeJob( QObject* );
+
+ /** Process the next search batch */
+ void slotProcessNextSearchBatch();
+
+protected:
+
+ /**
+ * These two methods actually create the jobs. They have to be implemented
+ * in all folders.
+ * @see createJob
+ */
+ virtual FolderJob* doCreateJob( KMMessage *msg, FolderJob::JobType jt, KMFolder *folder,
+ QString partSpecifier, const AttachmentStrategy *as ) const = 0;
+ virtual FolderJob* doCreateJob( QPtrList<KMMessage>& msgList, const QString& sets,
+ FolderJob::JobType jt, KMFolder *folder ) const = 0;
+
+ /** Tell the folder that a header field that is usually used for
+ the index (subject, from, ...) has changed of given message.
+ This method is usually called from within KMMessage::setSubject/set... */
+ void headerOfMsgChanged(const KMMsgBase*, int idx);
+
+ /** Load message from file and store it at given index. Returns 0
+ on failure. */
+ virtual KMMessage* readMsg(int idx) = 0;
+
+ //--------- Message Dict manipulation
+friend class KMMsgDict;
+ /** Inserts messages into the message dictionary. The messages will get
+ * new serial numbers. This is only used on newly appeared folders, where
+ * there is no .ids file yet, or when that has been invalidated. */
+ virtual void fillMessageDict() {}
+
+ /** Read the on-disk cache of serial numbers of messages in this store
+ * and fill those into the global message dict, such that the messages
+ * we hold can be looked up there. */
+ void readFolderIdsFile();
+
+ /** Writes the message serial number file. */
+ int writeFolderIdsFile() const;
+
+ /** Touches the message serial number file. */
+ int touchFolderIdsFile();
+
+ /** Append message to end of message serial number file. */
+ int appendToFolderIdsFile( int idx = -1 );
+
+ /** Sets the reverse-dictionary for this folder. const, because the mRDict
+ * is mutable, since it is not part of the (conceptually) const-relevant state
+ * of the object. */
+ void setRDict(KMMsgDictREntry *rentry) const;
+
+ /** Returns the reverse-dictionary for this folder. */
+ KMMsgDictREntry *rDict() const { return mRDict; }
+
+
+ /** Replaces the serial number for the message @p msg at index @p idx with
+ * @p sernum */
+ void replaceMsgSerNum( unsigned long sernum, KMMsgBase* msg, int idx );
+
+ /** Called when serial numbers for a folder are invalidated,
+ invalidates/recreates data structures dependent on the
+ serial numbers for this folder */
+ void invalidateFolder();
+
+ /** Called by KMFolder::remove() to delete the actual contents.
+ At the time of the call the folder has already been closed, and
+ the various index files deleted. Returns 0 on success. */
+ virtual int removeContents() = 0;
+
+ /** Called by KMFolder::expunge() to delete the actual contents.
+ At the time of the call the folder has already been closed, and
+ the various index files deleted. Returns 0 on success. */
+ virtual int expungeContents() = 0;
+
+ /** Read index file and fill the message-info list mMsgList. */
+ virtual bool readIndex() = 0;
+ virtual KMMsgBase* takeIndexEntry( int idx ) = 0;
+ virtual KMMsgInfo* setIndexEntry( int idx, KMMessage *msg ) = 0;
+ virtual void clearIndex(bool autoDelete=true, bool syncDict = false) = 0;
+ virtual void truncateIndex() = 0;
+
+ virtual Q_INT64 doFolderSize() const { return 0; };
+
+ int mOpenCount;
+ int mQuiet;
+ bool mChanged :1;
+ /** is the automatic creation of a index file allowed ? */
+ bool mAutoCreateIndex :1;
+ /** Has this storage exported its serial numbers to the global message
+ * dict for lookup? */
+ bool mExportsSernums :1;
+ /** if the index is dirty it will be recreated upon close() */
+ bool mDirty :1;
+ /** TRUE if the files of the folder are locked (writable) */
+ bool mFilesLocked :1;
+
+ /** number of unread messages, -1 if not yet set */
+ int mUnreadMsgs, mGuessedUnreadMsgs;
+ int mTotalMsgs;
+ Q_INT64 mSize;
+ bool mWriteConfigEnabled :1;
+ /** sven: true if on destruct folder needs to be compacted. */
+ bool needsCompact :1;
+ /** false if index file is out of sync with mbox file */
+ bool mCompactable :1;
+ bool mNoContent :1;
+ bool mNoChildren :1;
+ bool mConvertToUtf8 :1;
+
+ /** Points at the reverse dictionary for this folder. */
+ mutable KMMsgDictREntry *mRDict;
+ /** List of jobs created by this folder. */
+ mutable QPtrList<FolderJob> mJobList;
+
+ QTimer *mDirtyTimer;
+ enum { mDirtyTimerInterval = 600000 }; // 10 minutes
+
+ ChildrenState mHasChildren;
+
+ /** Type of contents in this folder. */
+ KMail::FolderContentsType mContentsType;
+
+ KMFolder* mFolder;
+
+ QTimer * mEmitChangedTimer;
+
+ int mCurrentSearchedMsg;
+ const KMSearchPattern* mSearchPattern;
+};
+
+#endif // FOLDERSTORAGE_H
diff --git a/kmail/foldertreebase.cpp b/kmail/foldertreebase.cpp
new file mode 100644
index 00000000..8f3b8c85
--- /dev/null
+++ b/kmail/foldertreebase.cpp
@@ -0,0 +1,243 @@
+/*
+ Copyright (c) 2007 Volker Krause <vkrause@kde.org>
+
+ 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 "foldertreebase.h"
+
+#include "globalsettings.h"
+#include "kmfolder.h"
+#include "kmfolderdir.h"
+#include "kmfoldertree.h"
+#include "kmheaders.h"
+#include "kmmainwidget.h"
+#include "messagecopyhelper.h"
+#include "folderstorage.h"
+
+#include <libkdepim/maillistdrag.h>
+using KPIM::MailList;
+using KPIM::MailListDrag;
+
+#include <kconfig.h>
+#include <kiconloader.h>
+#include <kpopupmenu.h>
+
+#include <qcursor.h>
+
+using namespace KMail;
+
+FolderTreeBase::FolderTreeBase(KMMainWidget *mainWidget, QWidget * parent, const char * name) :
+ KFolderTree( parent, name ),
+ mMainWidget( mainWidget )
+{
+ addAcceptableDropMimetype(MailListDrag::format(), false);
+}
+
+void FolderTreeBase::contentsDropEvent(QDropEvent * e)
+{
+ QListViewItem *item = itemAt( contentsToViewport(e->pos()) );
+ KMFolderTreeItem *fti = static_cast<KMFolderTreeItem*>(item);
+ if ( fti && fti->folder() && e->provides( MailListDrag::format() ) ) {
+ if ( e->source() == mMainWidget->headers()->viewport() ) {
+ int action;
+ if ( mMainWidget->headers()->folder() && mMainWidget->headers()->folder()->isReadOnly() )
+ action = DRAG_COPY;
+ else
+ action = dndMode();
+ // KMHeaders does copy/move itself
+ if ( action == DRAG_MOVE && fti->folder() )
+ emit folderDrop( fti->folder() );
+ else if ( action == DRAG_COPY && fti->folder() )
+ emit folderDropCopy( fti->folder() );
+ } else {
+ handleMailListDrop( e, fti->folder() );
+ }
+ e->accept( true );
+ } else {
+ KFolderTree::contentsDropEvent( e );
+ }
+}
+
+int FolderTreeBase::dndMode(bool alwaysAsk)
+{
+ int action = -1;
+ int keybstate = kapp->keyboardModifiers();
+ if ( keybstate & KApplication::ControlModifier ) {
+ action = DRAG_COPY;
+ } else if ( keybstate & KApplication::ShiftModifier ) {
+ 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( QCursor::pos(), 0 );
+ }
+ else
+ action = DRAG_MOVE;
+ }
+ return action;
+}
+
+bool FolderTreeBase::event(QEvent * e)
+{
+ if (e->type() == QEvent::ApplicationPaletteChange) {
+ readColorConfig();
+ return true;
+ }
+ return KFolderTree::event(e);
+}
+
+void FolderTreeBase::readColorConfig()
+{
+ KConfig* conf = KMKernel::config();
+ // Custom/System color support
+ KConfigGroupSaver saver(conf, "Reader");
+ QColor c1=QColor(kapp->palette().active().text());
+ QColor c2=QColor("blue");
+ QColor c4=QColor(kapp->palette().active().base());
+ QColor c5=QColor("red");
+
+ if (!conf->readBoolEntry("defaultColors",true)) {
+ mPaintInfo.colFore = conf->readColorEntry("ForegroundColor",&c1);
+ mPaintInfo.colUnread = conf->readColorEntry("UnreadMessage",&c2);
+ mPaintInfo.colBack = conf->readColorEntry("BackgroundColor",&c4);
+ mPaintInfo.colCloseToQuota = conf->readColorEntry("CloseToQuotaColor",&c5);
+ }
+ else {
+ mPaintInfo.colFore = c1;
+ mPaintInfo.colUnread = c2;
+ mPaintInfo.colBack = c4;
+ mPaintInfo.colCloseToQuota = c5;
+ }
+ QPalette newPal = kapp->palette();
+ newPal.setColor( QColorGroup::Base, mPaintInfo.colBack );
+ newPal.setColor( QColorGroup::Text, mPaintInfo.colFore );
+ setPalette( newPal );
+}
+
+bool FolderTreeBase::hideLocalInbox() const
+{
+ if ( !GlobalSettings::self()->hideLocalInbox() )
+ return false;
+ KMFolder *localInbox = kmkernel->inboxFolder();
+ assert( localInbox );
+ // check if it is empty
+ localInbox->open( "foldertreebase" );
+ if ( localInbox->count() > 0 ) {
+ localInbox->close( "foldertreebase" );
+ return false;
+ }
+ localInbox->close( "foldertreebase" );
+ // check if it has subfolders
+ if ( localInbox->child() && !localInbox->child()->isEmpty() )
+ return false;
+ // check if it is an account target
+ if ( localInbox->hasAccounts() )
+ return false;
+ return true;
+}
+
+
+void FolderTreeBase::slotUpdateCounts(KMFolder * folder, bool force /* = false*/)
+{
+ // kdDebug(5006) << "KMFolderTree::slotUpdateCounts()" << endl;
+ QListViewItem * current;
+ if (folder)
+ current = indexOfFolder(folder);
+ else
+ current = currentItem();
+
+ KMFolderTreeItem* fti = static_cast<KMFolderTreeItem*>(current);
+
+ // sanity check
+ if (!fti) return;
+ if (!fti->folder()) fti->setTotalCount(-1);
+
+ // get the unread count
+ int count = 0;
+ if (folder && folder->noContent()) // always empty
+ count = -1;
+ else {
+ if ( fti->folder() )
+ count = fti->folder()->countUnread();
+ }
+
+ // set it
+ bool repaint = false;
+ if (fti->unreadCount() != count) {
+ fti->adjustUnreadCount( count );
+ repaint = true;
+ }
+ if (isTotalActive() || force)
+ {
+ // get the total-count
+ if (fti->folder()->noContent())
+ count = -1;
+ else {
+ // get the cached count if the folder is not open
+ count = fti->folder()->count( !fti->folder()->isOpened() );
+ }
+ // set it
+ if ( count != fti->totalCount() ) {
+ fti->setTotalCount(count);
+ repaint = true;
+ }
+ }
+ if ( isSizeActive() || force ) {
+ if ( !fti->folder()->noContent()) {
+ Q_INT64 size = folder->storage()->folderSize();
+ if ( size != fti->folderSize() ) {
+ fti->setFolderSize( size );
+ repaint = true;
+ }
+ }
+ }
+ if ( fti->folderIsCloseToQuota() != folder->storage()->isCloseToQuota() ) {
+ fti->setFolderIsCloseToQuota( folder->storage()->isCloseToQuota() );
+ }
+
+ if (fti->parent() && !fti->parent()->isOpen())
+ repaint = false; // we're not visible
+ if (repaint) {
+ fti->setNeedsRepaint( true );
+ emit triggerRefresh();
+ }
+ // tell the kernel that one of the counts has changed
+ kmkernel->messageCountChanged();
+}
+
+void FolderTreeBase::handleMailListDrop(QDropEvent * event, KMFolder *destination )
+{
+ MailList list;
+ if ( !MailListDrag::decode( event, list ) ) {
+ kdWarning() << k_funcinfo << "Could not decode drag data!" << endl;
+ } else {
+ QValueList<Q_UINT32> serNums = MessageCopyHelper::serNumListFromMailList( list );
+ int action;
+ if ( MessageCopyHelper::inReadOnlyFolder( serNums ) )
+ action = DRAG_COPY;
+ else
+ action = dndMode();
+ if ( action == DRAG_COPY || action == DRAG_MOVE ) {
+ new MessageCopyHelper( serNums, destination, action == DRAG_MOVE, this );
+ }
+ }
+}
+
+#include "foldertreebase.moc"
diff --git a/kmail/foldertreebase.h b/kmail/foldertreebase.h
new file mode 100644
index 00000000..4e14a5a2
--- /dev/null
+++ b/kmail/foldertreebase.h
@@ -0,0 +1,98 @@
+/*
+ Copyright (c) 2007 Volker Krause <vkrause@kde.org>
+
+ 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_FOLDERTREEBASE_H
+#define KMAIL_FOLDERTREEBASE_H
+
+#include <libkdepim/kfoldertree.h>
+
+class KMFolder;
+class KMMainWidget;
+
+namespace KMail {
+
+class FolderTreeBase : public KFolderTree
+{
+ Q_OBJECT
+ public:
+ FolderTreeBase( KMMainWidget *mainWidget, QWidget *parent = 0, const char *name = 0 );
+
+ /** Returns the main widget that this widget is a child of. */
+ KMMainWidget* mainWidget() const { return mMainWidget; }
+
+ /** Find index of given folder. Returns 0 if not found */
+ virtual QListViewItem* indexOfFolder( const KMFolder* folder ) const
+ {
+ if ( mFolderToItem.contains( folder ) )
+ return mFolderToItem[ folder ];
+ else
+ return 0;
+ }
+
+ void insertIntoFolderToItemMap( const KMFolder *folder, QListViewItem* item )
+ {
+ mFolderToItem.insert( folder, item );
+ }
+
+ void removeFromFolderToItemMap( const KMFolder *folder )
+ {
+ mFolderToItem.remove( folder );
+ }
+
+ signals:
+ /** Messages have been dropped onto a folder */
+ void folderDrop(KMFolder*);
+
+ /** Messages have been dropped onto a folder with Ctrl */
+ void folderDropCopy(KMFolder*);
+
+ void triggerRefresh();
+
+ public slots:
+ /** Update the total and unread columns (if available, or if forced) */
+ void slotUpdateCounts(KMFolder * folder, bool force = false );
+
+ protected:
+ enum {
+ DRAG_COPY = 0,
+ DRAG_MOVE = 1,
+ DRAG_CANCEL = 2
+ };
+ int dndMode( bool alwaysAsk = false );
+ void contentsDropEvent( QDropEvent *e );
+
+ /** Catch palette changes */
+ virtual bool event(QEvent *e);
+
+ /** Read color options and set palette. */
+ virtual void readColorConfig();
+
+ /** Checks if the local inbox should be hidden. */
+ bool hideLocalInbox() const;
+
+ /** Handle drop of a MailList object. */
+ void handleMailListDrop( QDropEvent *event, KMFolder *destination );
+
+ protected:
+ KMMainWidget *mMainWidget;
+ QMap<const KMFolder*, QListViewItem*> mFolderToItem;
+};
+
+}
+
+#endif
diff --git a/kmail/folderviewtooltip.h b/kmail/folderviewtooltip.h
new file mode 100644
index 00000000..55afa1a0
--- /dev/null
+++ b/kmail/folderviewtooltip.h
@@ -0,0 +1,55 @@
+#ifndef __FOLDERVIEWTOOLTIP_H__
+#define __FOLDERVIEWTOOLTIP_H__
+
+#include <kmfoldercachedimap.h>
+
+#include <qtooltip.h>
+
+namespace KMail {
+
+class FolderViewToolTip : public QToolTip
+{
+ public:
+ FolderViewToolTip( QListView* parent ) :
+ QToolTip( parent->viewport() ),
+ mListView( parent ) {}
+
+ protected:
+ void maybeTip( const QPoint &point )
+ {
+ KMFolderTreeItem *item = dynamic_cast<KMFolderTreeItem*>( mListView->itemAt( point ) );
+ if ( !item )
+ return;
+ const QRect itemRect = mListView->itemRect( item );
+ if ( !itemRect.isValid() )
+ return;
+ const QRect headerRect = mListView->header()->sectionRect( 0 );
+ if ( !headerRect.isValid() )
+ return;
+
+ if ( !item->folder() || item->folder()->noContent() )
+ return;
+
+ item->updateCount();
+ QString tipText = i18n("<qt><b>%1</b><br>Total: %2<br>Unread: %3<br>Size: %4" )
+ .arg( item->folder()->prettyURL().replace( " ", "&nbsp;" ) )
+ .arg( item->totalCount() < 0 ? "-" : QString::number( item->totalCount() ) )
+ .arg( item->unreadCount() < 0 ? "-" : QString::number( item->unreadCount() ) )
+ .arg( KIO::convertSize( item->folderSize() ) );
+
+ if ( KMFolderCachedImap* imap = dynamic_cast<KMFolderCachedImap*>( item->folder()->storage() ) ) {
+ QuotaInfo info = imap->quotaInfo();
+ if ( info.isValid() && !info.isEmpty() )
+ tipText += i18n("<br>Quota: %1").arg( info.toString() );
+ }
+
+ tip( QRect( headerRect.left(), itemRect.top(), headerRect.width(), itemRect.height() ), tipText );
+ }
+
+ private:
+ QListView *mListView;
+};
+
+}
+
+#endif /* __FOLDERVIEWTOOLTIP_H__ */
diff --git a/kmail/globalsettings.cpp b/kmail/globalsettings.cpp
new file mode 100644
index 00000000..1386338c
--- /dev/null
+++ b/kmail/globalsettings.cpp
@@ -0,0 +1,62 @@
+/*
+ This file is part of KMail.
+
+ Copyright (c) 2005 David Faure <faure@kde.org>
+
+ This program 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.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ As a special exception, permission is given to link this program
+ with any edition of Qt, and distribute the resulting executable,
+ without including the source code for Qt in the source distribution.
+*/
+
+#include "globalsettings.h"
+#include <kstaticdeleter.h>
+#include <qtimer.h>
+
+GlobalSettings *GlobalSettings::mSelf = 0;
+static KStaticDeleter<GlobalSettings> staticGlobalSettingsDeleter;
+
+GlobalSettings *GlobalSettings::self()
+{
+ if ( !mSelf ) {
+ staticGlobalSettingsDeleter.setObject( mSelf, new GlobalSettings() );
+ mSelf->readConfig();
+ }
+
+ return mSelf;
+}
+
+GlobalSettings::GlobalSettings()
+{
+ mConfigSyncTimer = new QTimer( this, "mConfigSyncTimer" );
+ connect( mConfigSyncTimer, SIGNAL( timeout() ), this, SLOT( slotSyncNow() ) );
+}
+
+void GlobalSettings::requestSync()
+{
+ if ( !mConfigSyncTimer->isActive() )
+ mConfigSyncTimer->start( 0, true /*single shot*/ );
+}
+
+void GlobalSettings::slotSyncNow()
+{
+ config()->sync();
+}
+
+GlobalSettings::~GlobalSettings()
+{
+}
+
+#include "globalsettings.moc"
diff --git a/kmail/globalsettings.h b/kmail/globalsettings.h
new file mode 100644
index 00000000..68789415
--- /dev/null
+++ b/kmail/globalsettings.h
@@ -0,0 +1,59 @@
+/*
+ This file is part of KMail.
+
+ Copyright (c) 2005 David Faure <faure@kde.org>
+
+ This program 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.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ As a special exception, permission is given to link this program
+ with any edition of Qt, and distribute the resulting executable,
+ without including the source code for Qt in the source distribution.
+*/
+
+#ifndef KMAIL_GLOBALSETTINGS_H
+#define KMAIL_GLOBALSETTINGS_H
+
+#include "globalsettings_base.h"
+
+class QTimer;
+
+class GlobalSettings : public QObject, public GlobalSettingsBase
+{
+ Q_OBJECT
+public:
+ virtual ~GlobalSettings();
+
+ static GlobalSettings *self();
+
+ /** Call this slot instead of directly @ref KConfig::sync() to
+ minimize the overall config writes. Calling this slot will
+ schedule a sync of the application config file using a timer, so
+ that many consecutive calls can be condensed into a single
+ sync, which is more efficient. */
+ void requestSync();
+
+private slots:
+ void slotSyncNow();
+
+private:
+ GlobalSettings();
+ static GlobalSettings *mSelf;
+
+ QTimer *mConfigSyncTimer;
+
+};
+
+const int defaultmailcheckintervalmin=5; // check for mail every X minutes
+
+#endif /* KMAIL_GLOBALSETTINGS_H */
diff --git a/kmail/globalsettings_base.kcfgc b/kmail/globalsettings_base.kcfgc
new file mode 100644
index 00000000..7fbfbaf8
--- /dev/null
+++ b/kmail/globalsettings_base.kcfgc
@@ -0,0 +1,7 @@
+File=kmail.kcfg
+ClassName=GlobalSettingsBase
+Mutators=true
+Singleton=true
+ItemAccessors=true
+SetUserTexts=true
+IncludeFiles=templatesconfiguration.h,kmglobal.h,templatesconfiguration_base.h
diff --git a/kmail/headeritem.cpp b/kmail/headeritem.cpp
new file mode 100644
index 00000000..ab9b8329
--- /dev/null
+++ b/kmail/headeritem.cpp
@@ -0,0 +1,585 @@
+/*******************************************************************************
+**
+** Filename : headeritem.cpp
+** Created on : 28 November, 2004
+** Copyright : (c) 2004 Till Adam
+** Email : adam@kde.org
+**
+*******************************************************************************/
+
+/*******************************************************************************
+**
+** 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.
+**
+** In addition, as a special exception, the copyright holders give
+** permission to link the code of this program with any edition of
+** the Qt library by Trolltech AS, Norway (or with modified versions
+** of Qt that use the same license as Qt), and distribute linked
+** combinations including the two. You must obey the GNU General
+** Public License in all respects for all of the code used other than
+** Qt. If you modify this file, you may extend this exception to
+** your version of the file, but you are not obligated to do so. If
+** you do not wish to do so, delete this exception statement from
+** your version.
+**
+*******************************************************************************/
+#include <klocale.h>
+#include <qapplication.h>
+#include <qregexp.h>
+#include <qbitmap.h>
+#include <qpainter.h>
+
+#include <kio/netaccess.h>
+
+#include "headeritem.h"
+#include "kmheaders.h"
+
+#include "kmfolder.h"
+
+using namespace KMail;
+
+// Constuction a new list view item with the given colors and pixmap
+HeaderItem::HeaderItem( QListView* parent, int msgId, const QString& key )
+ : KListViewItem( parent ),
+ mMsgId( msgId ),
+ mKey( key ),
+ mAboutToBeDeleted( false ),
+ mSortCacheItem( 0 )
+{
+ irefresh();
+}
+
+// Constuction a new list view item with the given parent, colors, & pixmap
+HeaderItem::HeaderItem( QListViewItem* parent, int msgId, const QString& key )
+ : KListViewItem( parent ),
+ mMsgId( msgId ),
+ mKey( key ),
+ mAboutToBeDeleted( false ),
+ mSortCacheItem( 0 )
+{
+ irefresh();
+}
+
+HeaderItem::~HeaderItem ()
+{
+ delete mSortCacheItem;
+}
+
+// Update the msgId this item corresponds to.
+void HeaderItem::setMsgId( int aMsgId )
+{
+ mMsgId = aMsgId;
+}
+
+// Profiling note: About 30% of the time taken to initialize the
+// listview is spent in this function. About 60% is spent in operator
+// new and QListViewItem::QListViewItem.
+void HeaderItem::irefresh()
+{
+ KMHeaders *headers = static_cast<KMHeaders*>(listView());
+ NestingPolicy threadingPolicy = headers->getNestingPolicy();
+ if ((threadingPolicy == AlwaysOpen) ||
+ (threadingPolicy == DefaultOpen)) {
+ //Avoid opening items as QListView is currently slow to do so.
+ setOpen(true);
+ return;
+
+ }
+ if (threadingPolicy == DefaultClosed)
+ return; //default to closed
+
+ // otherwise threadingPolicy == OpenUnread
+ if (parent() && parent()->isOpen()) {
+ setOpen(true);
+ return;
+ }
+
+ KMMsgBase *mMsgBase = headers->folder()->getMsgBase( mMsgId );
+ mSerNum = mMsgBase->getMsgSerNum();
+ if (mMsgBase->isNew() || mMsgBase->isUnread()
+ || mMsgBase->isImportant() || mMsgBase->isTodo() || mMsgBase->isWatched() ) {
+ setOpen(true);
+ HeaderItem * topOfThread = this;
+ while(topOfThread->parent())
+ topOfThread = (HeaderItem*)topOfThread->parent();
+ topOfThread->setOpenRecursive(true);
+ }
+}
+
+// Return the msgId of the message associated with this item
+int HeaderItem::msgId() const
+{
+ return mMsgId;
+}
+
+// Return the serial number
+Q_UINT32 HeaderItem::msgSerNum() const
+{
+ return mSerNum;
+}
+
+// Update this item to summarise a new folder and message
+
+//Opens all children in the thread
+void HeaderItem::setOpenRecursive( bool open )
+{
+ if (open){
+ QListViewItem * lvchild;
+ lvchild = firstChild();
+ while (lvchild){
+ ((HeaderItem*)lvchild)->setOpenRecursive( true );
+ lvchild = lvchild->nextSibling();
+ }
+ setOpen( true );
+ } else {
+ setOpen( false );
+ }
+}
+
+QString HeaderItem::text( int col) const
+{
+ KMHeaders *headers = static_cast<KMHeaders*>(listView());
+ KMMsgBase *mMsgBase = headers->folder()->getMsgBase( mMsgId );
+ QString tmp;
+
+ if ( !mMsgBase )
+ return QString();
+
+ if ( col == headers->paintInfo()->senderCol ) {
+ if ( (headers->folder()->whoField().lower() == "to") && !headers->paintInfo()->showReceiver )
+ tmp = mMsgBase->toStrip();
+ else
+ tmp = mMsgBase->fromStrip();
+ if (tmp.isEmpty())
+ tmp = i18n("Unknown");
+ else
+ tmp = tmp.simplifyWhiteSpace();
+
+ } else if ( col == headers->paintInfo()->receiverCol ) {
+ tmp = mMsgBase->toStrip();
+ if (tmp.isEmpty())
+ tmp = i18n("Unknown");
+ else
+ tmp = tmp.simplifyWhiteSpace();
+
+ } else if(col == headers->paintInfo()->subCol) {
+ tmp = mMsgBase->subject();
+ if (tmp.isEmpty())
+ tmp = i18n("No Subject");
+ else
+ tmp.remove(QRegExp("[\r\n]"));
+
+ } else if(col == headers->paintInfo()->dateCol) {
+ tmp = headers->mDate.dateString( mMsgBase->date() );
+ } else if(col == headers->paintInfo()->sizeCol
+ && headers->paintInfo()->showSize) {
+ if ( mMsgBase->parent()->folderType() == KMFolderTypeImap ) {
+ tmp = KIO::convertSize( mMsgBase->msgSizeServer() );
+ } else {
+ tmp = KIO::convertSize( mMsgBase->msgSize() );
+ }
+ }
+ return tmp;
+}
+
+void HeaderItem::setup()
+{
+ widthChanged();
+ const int ph = KMHeaders::pixNew->height();
+ QListView *v = listView();
+ int h = QMAX( v->fontMetrics().height(), ph ) + 2*v->itemMargin();
+ h = QMAX( h, QApplication::globalStrut().height());
+ if ( h % 2 > 0 )
+ h++;
+ setHeight( h );
+}
+
+typedef QValueList<QPixmap> PixmapList;
+
+QPixmap HeaderItem::pixmapMerge( PixmapList pixmaps ) const
+{
+ int width = 0;
+ int height = 0;
+ for ( PixmapList::ConstIterator it = pixmaps.begin();
+ it != pixmaps.end(); ++it ) {
+ width += (*it).width();
+ height = QMAX( height, (*it).height() );
+ }
+
+ QPixmap res( width, height );
+ QBitmap mask( width, height, true );
+
+ int x = 0;
+ for ( PixmapList::ConstIterator it = pixmaps.begin();
+ it != pixmaps.end(); ++it ) {
+ bitBlt( &res, x, (height - (*it).height()) / 2, &(*it) );
+ bitBlt( &mask, x, (height - (*it).height()) / 2, (*it).mask() );
+ x += (*it).width();
+ }
+
+ res.setMask( mask );
+ return res;
+}
+
+const QPixmap *HeaderItem::cryptoIcon(KMMsgBase *msgBase) const
+{
+ switch ( msgBase->encryptionState() )
+ {
+ case KMMsgFullyEncrypted : return KMHeaders::pixFullyEncrypted;
+ case KMMsgPartiallyEncrypted : return KMHeaders::pixPartiallyEncrypted;
+ case KMMsgEncryptionStateUnknown: return KMHeaders::pixUndefinedEncrypted;
+ case KMMsgEncryptionProblematic : return KMHeaders::pixEncryptionProblematic;
+ default : return 0;
+ }
+}
+
+const QPixmap *HeaderItem::signatureIcon(KMMsgBase *msgBase) const
+{
+ switch ( msgBase->signatureState() )
+ {
+ case KMMsgFullySigned : return KMHeaders::pixFullySigned;
+ case KMMsgPartiallySigned : return KMHeaders::pixPartiallySigned;
+ case KMMsgSignatureStateUnknown: return KMHeaders::pixUndefinedSigned;
+ case KMMsgSignatureProblematic : return KMHeaders::pixSignatureProblematic;
+ default : return 0;
+ }
+}
+
+const QPixmap *HeaderItem::statusIcon(KMMsgBase *msgBase) const
+{
+ // forwarded, replied have precedence over the other states
+ if ( msgBase->isForwarded() && !msgBase->isReplied() ) return KMHeaders::pixReadFwd;
+ if ( !msgBase->isForwarded() && msgBase->isReplied() ) return KMHeaders::pixReadReplied;
+ if ( msgBase->isForwarded() && msgBase->isReplied() ) return KMHeaders::pixReadFwdReplied;
+
+ // a queued or sent mail is usually also read
+ if ( msgBase->isQueued() ) return KMHeaders::pixQueued;
+ if ( msgBase->isSent() ) return KMHeaders::pixSent;
+
+ if ( msgBase->isNew() ) return KMHeaders::pixNew;
+ if ( msgBase->isRead() || msgBase->isOld() ) return KMHeaders::pixRead;
+ if ( msgBase->isUnread() ) return KMHeaders::pixUns;
+ if ( msgBase->isDeleted() ) return KMHeaders::pixDel;
+
+ return 0;
+}
+
+const QPixmap *HeaderItem::pixmap(int col) const
+{
+ KMHeaders *headers = static_cast<KMHeaders*>(listView());
+ KMMsgBase *msgBase = headers->folder()->getMsgBase( mMsgId );
+
+ if ( col == headers->paintInfo()->subCol ) {
+
+ PixmapList pixmaps;
+
+ if ( !headers->mPaintInfo.showSpamHam ) {
+ // Have the spam/ham and watched/ignored icons first, I guess.
+ if ( msgBase->isSpam() ) pixmaps << *KMHeaders::pixSpam;
+ if ( msgBase->isHam() ) pixmaps << *KMHeaders::pixHam;
+ }
+
+ if ( !headers->mPaintInfo.showWatchedIgnored ) {
+ if ( msgBase->isIgnored() ) pixmaps << *KMHeaders::pixIgnored;
+ if ( msgBase->isWatched() ) pixmaps << *KMHeaders::pixWatched;
+ }
+
+ if ( !headers->mPaintInfo.showStatus ) {
+ const QPixmap *pix = statusIcon(msgBase);
+ if ( pix ) pixmaps << *pix;
+ }
+
+ // Only merge the attachment icon in if that is configured.
+ if ( headers->paintInfo()->showAttachmentIcon &&
+ !headers->paintInfo()->showAttachment &&
+ msgBase->attachmentState() == KMMsgHasAttachment )
+ pixmaps << *KMHeaders::pixAttachment;
+
+ // Only merge the crypto icons in if that is configured.
+ if ( headers->paintInfo()->showCryptoIcons ) {
+ const QPixmap *pix;
+
+ if ( !headers->paintInfo()->showCrypto )
+ if ( (pix = cryptoIcon(msgBase)) ) pixmaps << *pix;
+
+ if ( !headers->paintInfo()->showSigned )
+ if ( (pix = signatureIcon(msgBase)) ) pixmaps << *pix;
+ }
+
+ if ( !headers->mPaintInfo.showImportant )
+ if ( msgBase->isImportant() ) pixmaps << *KMHeaders::pixFlag;
+
+ if ( !headers->mPaintInfo.showTodo )
+ if ( msgBase->isTodo() ) pixmaps << *KMHeaders::pixTodo;
+
+ static QPixmap mergedpix;
+ mergedpix = pixmapMerge( pixmaps );
+ return &mergedpix;
+ }
+ else if ( col == headers->paintInfo()->statusCol ) {
+ return statusIcon(msgBase);
+ }
+ else if ( col == headers->paintInfo()->attachmentCol ) {
+ if ( msgBase->attachmentState() == KMMsgHasAttachment )
+ return KMHeaders::pixAttachment;
+ }
+ else if ( col == headers->paintInfo()->importantCol ) {
+ if ( msgBase->isImportant() )
+ return KMHeaders::pixFlag;
+ }
+ else if ( col == headers->paintInfo()->todoCol ) {
+ if ( msgBase->isTodo() )
+ return KMHeaders::pixTodo;
+ }
+ else if ( col == headers->paintInfo()->spamHamCol ) {
+ if ( msgBase->isSpam() ) return KMHeaders::pixSpam;
+ if ( msgBase->isHam() ) return KMHeaders::pixHam;
+ }
+ else if ( col == headers->paintInfo()->watchedIgnoredCol ) {
+ if ( msgBase->isWatched() ) return KMHeaders::pixWatched;
+ if ( msgBase->isIgnored() ) return KMHeaders::pixIgnored;
+ }
+ else if ( col == headers->paintInfo()->signedCol ) {
+ return signatureIcon(msgBase);
+ }
+ else if ( col == headers->paintInfo()->cryptoCol ) {
+ return cryptoIcon(msgBase);
+ }
+ return 0;
+}
+
+void HeaderItem::paintCell( QPainter * p, const QColorGroup & cg,
+ int column, int width, int align )
+{
+ KMHeaders *headers = static_cast<KMHeaders*>(listView());
+ if (headers->noRepaint) return;
+ if (!headers->folder()) return;
+ KMMsgBase *mMsgBase = headers->folder()->getMsgBase( mMsgId );
+ if (!mMsgBase) return;
+
+ QColorGroup _cg( cg );
+ QColor c = _cg.text();
+ QColor *color = const_cast<QColor *>( &headers->paintInfo()->colFore );
+ QFont font = p->font();
+ int weight = font.weight();
+
+ // for color and font family "important" overrides "new" overrides "unread"
+ // overrides "todo" for the weight we use the maximal weight
+ if ( mMsgBase->isTodo() ) {
+ color = const_cast<QColor*>( &headers->paintInfo()->colTodo );
+ font = headers->todoFont();
+ weight = QMAX( weight, font.weight() );
+ }
+ if ( mMsgBase->isUnread() ) {
+ color = const_cast<QColor*>( &headers->paintInfo()->colUnread );
+ font = headers->unreadFont();
+ weight = QMAX( weight, font.weight() );
+ }
+ if ( mMsgBase->isNew() ) {
+ color = const_cast<QColor*>( &headers->paintInfo()->colNew );
+ font = headers->newFont();
+ weight = QMAX( weight, font.weight() );
+ }
+
+ if ( mMsgBase->isImportant() ) {
+ color = const_cast<QColor*>( &headers->paintInfo()->colFlag );
+ font = headers->importantFont();
+ weight = QMAX( weight, font.weight() );
+ }
+ if ( column == headers->paintInfo()->dateCol ) {
+ font = headers->dateFont();
+ }
+
+ QColor cdisabled = KGlobalSettings::inactiveTextColor();
+ if ( headers->isMessageCut( msgSerNum() ) ) {
+ font.setItalic( true );
+ color = &cdisabled;
+ }
+
+ // set color and font
+ _cg.setColor( QColorGroup::Text, *color );
+ font.setWeight( weight );
+ p->setFont( font );
+
+ KListViewItem::paintCell( p, _cg, column, width, align );
+
+ if (aboutToBeDeleted()) {
+ // strike through
+ p->drawLine( 0, height()/2, width, height()/2);
+ }
+
+ // reset color
+ _cg.setColor( QColorGroup::Text, c );
+}
+
+QString HeaderItem::generate_key( KMHeaders *headers,
+ KMMsgBase *msg,
+ const KPaintInfo *paintInfo,
+ int sortOrder )
+{
+ // It appears, that QListView in Qt-3.0 asks for the key
+ // in QListView::clear(), which is called from
+ // readSortOrder()
+ if (!msg) return QString::null;
+
+ int column = sortOrder & ((1 << 5) - 1);
+ QString ret = QChar( (char)sortOrder );
+ QString sortArrival = QString( "%1" ).arg( msg->getMsgSerNum(), 0, 36 );
+ while (sortArrival.length() < 7) sortArrival = '0' + sortArrival;
+
+ if (column == paintInfo->dateCol) {
+ if (paintInfo->orderOfArrival)
+ return ret + sortArrival;
+ else {
+ QString d = QString::number(msg->date());
+ while (d.length() <= 10) d = '0' + d;
+ return ret + d + sortArrival;
+ }
+ } else if (column == paintInfo->senderCol) {
+ QString tmp;
+ if ( (headers->folder()->whoField().lower() == "to") && !headers->paintInfo()->showReceiver )
+ tmp = msg->toStrip();
+ else
+ tmp = msg->fromStrip();
+ return ret + tmp.lower() + ' ' + sortArrival;
+ } else if (column == paintInfo->receiverCol) {
+ QString tmp = msg->toStrip();
+ return ret + tmp.lower() + ' ' + sortArrival;
+ } else if (column == paintInfo->subCol) {
+ QString tmp;
+ tmp = ret;
+ if (paintInfo->status) {
+ tmp += msg->statusToSortRank() + ' ';
+ }
+ tmp += KMMessage::stripOffPrefixes( msg->subject().lower() ) + ' ' + sortArrival;
+ return tmp;
+ }
+ else if (column == paintInfo->sizeCol) {
+ QString len;
+ if ( msg->parent()->folderType() == KMFolderTypeImap )
+ {
+ len = QString::number( msg->msgSizeServer() );
+ } else {
+ len = QString::number( msg->msgSize() );
+ }
+ while (len.length() < 9) len = '0' + len;
+ return ret + len + sortArrival;
+ }
+ else if (column == paintInfo->statusCol) {
+ QString s;
+ if ( msg->isNew() ) s = "1";
+ else if ( msg->isUnread() ) s = "2";
+ else if (!msg->isForwarded() && msg->isReplied() ) s = "3";
+ else if ( msg->isForwarded() && msg->isReplied() ) s = "4";
+ else if ( msg->isForwarded() && !msg->isReplied() ) s = "5";
+ else if ( msg->isRead() || msg->isOld() ) s = "6";
+ else if ( msg->isQueued() ) s = "7";
+ else if ( msg->isSent() ) s = "8";
+ else if ( msg->isDeleted() ) s = "9";
+ return ret + s + sortArrival;
+ }
+ else if (column == paintInfo->attachmentCol) {
+ QString s(msg->attachmentState() == KMMsgHasAttachment ? "1" : "0");
+ return ret + s + sortArrival;
+ }
+ else if (column == paintInfo->importantCol) {
+ QString s(msg->isImportant() ? "1" : "0");
+ return ret + s + sortArrival;
+ }
+ else if ( column == paintInfo->todoCol ) {
+ QString s( msg->isTodo() ? "1": "0" );
+ return ret + s + sortArrival;
+ }
+ else if (column == paintInfo->spamHamCol) {
+ QString s((msg->isSpam() || msg->isHam()) ? "1" : "0");
+ return ret + s + sortArrival;
+ }
+ else if (column == paintInfo->watchedIgnoredCol) {
+ QString s((msg->isWatched() || msg->isIgnored()) ? "1" : "0");
+ return ret + s + sortArrival;
+ }
+ else if (column == paintInfo->signedCol) {
+ QString s;
+ switch ( msg->signatureState() )
+ {
+ case KMMsgFullySigned : s = "1"; break;
+ case KMMsgPartiallySigned : s = "2"; break;
+ case KMMsgSignatureStateUnknown: s = "3"; break;
+ case KMMsgSignatureProblematic : s = "4"; break;
+ default : s = "5"; break;
+ }
+ return ret + s + sortArrival;
+ }
+ else if (column == paintInfo->cryptoCol) {
+ QString s;
+ switch ( msg->encryptionState() )
+ {
+ case KMMsgFullyEncrypted : s = "1"; break;
+ case KMMsgPartiallyEncrypted : s = "2"; break;
+ case KMMsgEncryptionStateUnknown: s = "3"; break;
+ case KMMsgEncryptionProblematic : s = "4"; break;
+ default : s = "5"; break;
+ }
+ return ret + s + sortArrival;
+ }
+ return ret + "missing key"; //you forgot something!!
+}
+
+QString HeaderItem::key( int column, bool /*ascending*/ ) const
+{
+ KMHeaders *headers = static_cast<KMHeaders*>(listView());
+ int sortOrder = column;
+ if (headers->mPaintInfo.orderOfArrival)
+ sortOrder |= (1 << 6);
+ if (headers->mPaintInfo.status)
+ sortOrder |= (1 << 5);
+ //This code should stay pretty much like this, if you are adding new
+ //columns put them in generate_key
+ if(mKey.isEmpty() || mKey[0] != (char)sortOrder) {
+ KMHeaders *headers = static_cast<KMHeaders*>(listView());
+ KMMsgBase *msgBase = headers->folder()->getMsgBase( mMsgId );
+ return ((HeaderItem *)this)->mKey =
+ generate_key( headers, msgBase, headers->paintInfo(), sortOrder );
+ }
+ return mKey;
+}
+
+void HeaderItem::setTempKey( QString key ) {
+ mKey = key;
+}
+
+int HeaderItem::compare( QListViewItem *i, int col, bool ascending ) const
+{
+ int res = 0;
+ KMHeaders *headers = static_cast<KMHeaders*>(listView());
+ if ( ( col == headers->paintInfo()->statusCol ) ||
+ ( col == headers->paintInfo()->sizeCol ) ||
+ ( col == headers->paintInfo()->attachmentCol ) ||
+ ( col == headers->paintInfo()->importantCol ) ||
+ ( col == headers->paintInfo()->todoCol ) ||
+ ( col == headers->paintInfo()->spamHamCol ) ||
+ ( col == headers->paintInfo()->signedCol ) ||
+ ( col == headers->paintInfo()->cryptoCol ) ||
+ ( col == headers->paintInfo()->watchedIgnoredCol ) ) {
+ res = key( col, ascending ).compare( i->key( col, ascending ) );
+ } else if ( col == headers->paintInfo()->dateCol ) {
+ res = key( col, ascending ).compare( i->key( col, ascending ) );
+ if (i->parent() && !ascending)
+ res = -res;
+ } else if ( col == headers->paintInfo()->subCol ||
+ col == headers->paintInfo()->senderCol ||
+ col == headers->paintInfo()->receiverCol ) {
+ res = key( col, ascending ).localeAwareCompare( i->key( col, ascending ) );
+ }
+ return res;
+}
+
+QListViewItem* HeaderItem::firstChildNonConst() /* Non const! */
+{
+ enforceSortOrder(); // Try not to rely on QListView implementation details
+ return firstChild();
+}
+
diff --git a/kmail/headeritem.h b/kmail/headeritem.h
new file mode 100644
index 00000000..66b75cc2
--- /dev/null
+++ b/kmail/headeritem.h
@@ -0,0 +1,241 @@
+/*******************************************************************************
+**
+** Filename : headeritem.h
+** Created on : 28 November, 2004
+** Copyright : (c) 2004 Till Adam
+** Email : adam@kde.org
+**
+*******************************************************************************/
+
+/*******************************************************************************
+**
+** 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.
+**
+** In addition, as a special exception, the copyright holders give
+** permission to link the code of this program with any edition of
+** the Qt library by Trolltech AS, Norway (or with modified versions
+** of Qt that use the same license as Qt), and distribute linked
+** combinations including the two. You must obey the GNU General
+** Public License in all respects for all of the code used other than
+** Qt. If you modify this file, you may extend this exception to
+** your version of the file, but you are not obligated to do so. If
+** you do not wish to do so, delete this exception statement from
+** your version.
+**
+*******************************************************************************/
+#ifndef HEADERITEM_H
+#define HEADERITEM_H
+
+#include <stdlib.h>
+
+#include <klistview.h> // include for the base class
+
+class KMMsgBase;
+class KPaintInfo;
+class KMFolder;
+class KMHeaders;
+
+namespace KMail
+{
+class HeaderItem; // forward declaration
+
+/**
+ * @short Represents an item in the set of mails to be displayed but only as far
+ * as sorting, threading and reading/writing of the current sort order to
+ * a disk cache is concerned.
+ *
+ * Each such item is paired with a KMail::HeaderItem, which holds the graphical
+ * representation of each item (mail). This is what the threading trees are
+ * built of.
+ */
+class SortCacheItem {
+
+public:
+ SortCacheItem() : mItem(0), mParent(0), mId(-1), mSortOffset(-1),
+ mUnsortedCount(0), mUnsortedSize(0), mUnsortedChildren(0),
+ mImperfectlyThreaded (true), mSubjThreadingList(0) { }
+ SortCacheItem(int i, QString k, int o=-1)
+ : mItem(0), mParent(0), mId(i), mSortOffset(o), mKey(k),
+ mUnsortedCount(0), mUnsortedSize(0), mUnsortedChildren(0),
+ mImperfectlyThreaded (true), mSubjThreadingList(0) { }
+ ~SortCacheItem() { if(mUnsortedChildren) free(mUnsortedChildren); }
+
+ /** The parent node of the item in the threading hierarchy. 0 if the item
+ * is at top level, which is the default. Can only be set by parents. */
+ SortCacheItem *parent() const { return mParent; }
+ /**
+ * Returs whether the item is so far imperfectly threaded.
+ * If an item is imperfectly threaded (by References or subject, not by
+ * In-Reply-To) it will be reevalutated when a new mail comes in. It could be
+ * the perfect parent. */
+ bool isImperfectlyThreaded() const
+ { return mImperfectlyThreaded; }
+ /** Set whether the item is currently imperfectly threaded (by References
+ * or Subject, not by In-Reply-To). */
+ void setImperfectlyThreaded (bool val)
+ { mImperfectlyThreaded = val; }
+ /** Returns whether the item has other items below it. */
+ bool hasChildren() const
+ { return mSortedChildren.count() || mUnsortedCount; }
+ /** The sorted children are an array of sortcache items we know are below the
+ * current one and are already properly sorted (as read from the cache ) */
+ const QPtrList<SortCacheItem> *sortedChildren() const
+ { return &mSortedChildren; }
+ /** The unsorted children are an array of sortcache items we know are below the
+ * current one, but are yet to be threaded and sorted properly. */
+ SortCacheItem **unsortedChildren(int &count) const
+ { count = mUnsortedCount; return mUnsortedChildren; }
+ /** Add an item to this itme's list of already sorted children. */
+ void addSortedChild(SortCacheItem *i) {
+ i->mParent = this;
+ mSortedChildren.append(i);
+ }
+ /** Add an item to this itme's list of unsorted children. */
+ void addUnsortedChild(SortCacheItem *i) {
+ i->mParent = this;
+ if(!mUnsortedChildren)
+ mUnsortedChildren = (SortCacheItem **)malloc((mUnsortedSize = 25) * sizeof(SortCacheItem *));
+ else if(mUnsortedCount >= mUnsortedSize)
+ mUnsortedChildren = (SortCacheItem **)realloc(mUnsortedChildren,
+ (mUnsortedSize *= 2) * sizeof(SortCacheItem *));
+ mUnsortedChildren[mUnsortedCount++] = i;
+ }
+
+ /** Clear the sorted and unsorted children datastructures. */
+ void clearChildren() {
+ mSortedChildren.clear();
+ free( mUnsortedChildren );
+ mUnsortedChildren = 0;
+ mUnsortedCount = mUnsortedSize = 0;
+ }
+
+ /** The corresponding KMail::HeaderItem */
+ HeaderItem *item() const { return mItem; }
+ /** Set the corresponding KMail::HeaderItem */
+ void setItem(HeaderItem *i) { Q_ASSERT(!mItem); mItem = i; }
+
+ /** sort key as used by the listview */
+ const QString &key() const { return mKey; }
+ /** Set the sort key used by the list view. */
+ void setKey(const QString &key) { mKey = key; }
+
+ int id() const { return mId; }
+ void setId(int id) { mId = id; }
+
+ /** offset in the cache file stream */
+ int offset() const { return mSortOffset; }
+ void setOffset(int x) { mSortOffset = x; }
+
+ void updateSortFile( FILE *sortStream, KMFolder *folder,
+ bool waiting_for_parent = false,
+ bool update_discovered_count = false);
+
+ /** Set the list of mails with a certain subject that this item is on.
+ * Used to remove the item from that list on deletion. */
+ void setSubjectThreadingList( QPtrList<SortCacheItem> *list ) { mSubjThreadingList = list; }
+ /** The list of mails with a certain subject that this item is on. */
+ QPtrList<SortCacheItem>* subjectThreadingList() const { return mSubjThreadingList; }
+
+private:
+ HeaderItem *mItem;
+ SortCacheItem *mParent;
+ int mId, mSortOffset;
+ QString mKey;
+
+ QPtrList<SortCacheItem> mSortedChildren;
+ int mUnsortedCount, mUnsortedSize;
+ SortCacheItem **mUnsortedChildren;
+ bool mImperfectlyThreaded;
+ // pointer to the list it might be on so it can be remove from it
+ // when the item goes away.
+ QPtrList<SortCacheItem>* mSubjThreadingList;
+};
+
+
+/**
+ * Visual representation of a member of the set of displayables (mails in
+ * the current folder). Each item is paired with a KMail::SortCacheItem. See there as to
+ * how they are meant to cooperate. This should be about the visual aspects of
+ * displaying an entry only. */
+class HeaderItem : public KListViewItem
+{
+public:
+ HeaderItem( QListView* parent, int msgId, const QString& key = QString::null );
+ HeaderItem( QListViewItem* parent, int msgId, const QString& key = QString::null );
+ ~HeaderItem ();
+
+ /** Set the message id of this item, which is the offset/index in the folder
+ * currently displayed by the KMHeaders list view. */
+ void setMsgId( int aMsgId );
+
+ // Profiling note: About 30% of the time taken to initialize the
+ // listview is spent in this function. About 60% is spent in operator
+ // new and QListViewItem::QListViewItem.
+ void irefresh();
+
+ /** Return the msgId of the message associated with this item. */
+ int msgId() const;
+
+ // Return the serial number of the message associated with this item;
+ Q_UINT32 msgSerNum() const;
+
+ /** Expands all children of the list view item. */
+ void setOpenRecursive( bool open );
+
+ /** Returns the text of the list view item. */
+ QString text( int col) const;
+
+ void setup();
+
+ typedef QValueList<QPixmap> PixmapList;
+
+ QPixmap pixmapMerge( PixmapList pixmaps ) const;
+
+ const QPixmap *cryptoIcon(KMMsgBase *msgBase) const;
+ const QPixmap *signatureIcon(KMMsgBase *msgBase) const;
+ const QPixmap *statusIcon(KMMsgBase *msgBase) const;
+
+ const QPixmap *pixmap(int col) const;
+
+ void paintCell( QPainter * p, const QColorGroup & cg,
+ int column, int width, int align );
+
+ static QString generate_key( KMHeaders *headers, KMMsgBase *msg, const KPaintInfo *paintInfo, int sortOrder );
+
+ virtual QString key( int column, bool /*ascending*/ ) const;
+
+ void setTempKey( QString key );
+
+ int compare( QListViewItem *i, int col, bool ascending ) const;
+
+ QListViewItem* firstChildNonConst(); /* Non const! */
+
+ /** Returns whether the item is about to be removed from the list view as a
+ * result of some user action. Such items are not selectable and painted with
+ * a strike-through decoration. */
+ bool aboutToBeDeleted() const { return mAboutToBeDeleted; }
+ /** Set the item to be in about-to-be-deleted state, which means it
+ * cannot be selected and will be painted with a strike-through decoration. */
+ void setAboutToBeDeleted( bool val ) { mAboutToBeDeleted = val; }
+
+ /** Associate a KMail::SortCacheItem with this item. This is the structure used to
+ * represent the mail during sorting and threading calculation. */
+ void setSortCacheItem( SortCacheItem *item ) { mSortCacheItem = item; }
+ /** Returns the KMail::SortCacheItem associated with this display item. */
+ SortCacheItem* sortCacheItem() const { return mSortCacheItem; }
+
+private:
+ int mMsgId;
+ Q_UINT32 mSerNum;
+ QString mKey;
+ bool mAboutToBeDeleted;
+ SortCacheItem *mSortCacheItem;
+}; // End of class HeaderItem
+
+} // End of namespace KMail
+
+
+#endif // HEADERITEM_H
diff --git a/kmail/headerlistquicksearch.cpp b/kmail/headerlistquicksearch.cpp
new file mode 100644
index 00000000..d735386d
--- /dev/null
+++ b/kmail/headerlistquicksearch.cpp
@@ -0,0 +1,193 @@
+/*
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2004 Till Adam <adam@kde.org>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+#include "headerlistquicksearch.h"
+
+#include <qapplication.h>
+#include <qlabel.h>
+#include <qcombobox.h>
+#include <qvaluevector.h>
+#include <qtimer.h>
+
+#include <kaction.h>
+#include <kiconloader.h>
+#include <klistview.h>
+#include <klocale.h>
+#include <ktoolbarbutton.h>
+
+#include "kmheaders.h"
+#include "kmsearchpattern.h"
+#include "kmmainwidget.h"
+
+namespace KMail {
+
+HeaderListQuickSearch::HeaderListQuickSearch( QWidget *parent,
+ KListView *listView,
+ KActionCollection *actionCollection,
+ const char *name )
+ : KListViewSearchLine(parent, listView, name), mStatusCombo(0), mStatus(0), statusList()
+{
+ KAction *resetQuickSearch = new KAction( i18n( "Reset Quick Search" ),
+ QApplication::reverseLayout()
+ ? "clear_left"
+ : "locationbar_erase",
+ 0, this,
+ SLOT( reset() ),
+ actionCollection,
+ "reset_quicksearch" );
+ resetQuickSearch->plug( parent );
+ resetQuickSearch->setWhatsThis( i18n( "Reset Quick Search\n"
+ "Resets the quick search so that "
+ "all messages are shown again." ) );
+
+ QLabel *label = new QLabel( i18n("Stat&us:"), parent, "kde toolbar widget" );
+
+ mStatusCombo = new QComboBox( parent, "quick search status combo box" );
+ mStatusCombo->insertItem( SmallIcon( "run" ), i18n("Any Status") );
+
+ insertStatus( StatusUnread );
+ insertStatus( StatusNew );
+ insertStatus( StatusImportant );
+ insertStatus( StatusReplied );
+ insertStatus( StatusForwarded );
+ insertStatus( StatusToDo );
+ insertStatus( StatusHasAttachment );
+ insertStatus( StatusWatched );
+ insertStatus( StatusIgnored );
+ mStatusCombo->setCurrentItem( 0 );
+ mStatusCombo->installEventFilter( this );
+ connect( mStatusCombo, SIGNAL ( activated( int ) ),
+ this, SLOT( slotStatusChanged( int ) ) );
+
+ label->setBuddy( mStatusCombo );
+
+ KToolBarButton * btn = new KToolBarButton( "mail_find", 0, parent,
+ 0, i18n( "Open Full Search" ) );
+ connect( btn, SIGNAL( clicked() ), SIGNAL( requestFullSearch() ) );
+
+ /* 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. */
+ disconnect(listView, SIGNAL(itemAdded(QListViewItem *)),
+ this, SLOT(itemAdded(QListViewItem *)));
+ KMHeaders *headers = static_cast<KMHeaders*>( listView );
+ connect( headers, SIGNAL( msgAddedToListView( QListViewItem* ) ),
+ this, SLOT( itemAdded( QListViewItem* ) ) );
+
+}
+
+HeaderListQuickSearch::~HeaderListQuickSearch()
+{
+}
+
+
+bool HeaderListQuickSearch::eventFilter( QObject *watched, QEvent *event )
+{
+ if ( watched == mStatusCombo ) {
+ KMMainWidget *mainWidget = 0;
+
+ // Travel up the parents list until we find the main widget
+ for ( QWidget *curWidget = parentWidget(); curWidget; curWidget = curWidget->parentWidget() ) {
+ mainWidget = ::qt_cast<KMMainWidget *>( curWidget );
+ if ( mainWidget )
+ break;
+ }
+
+ if ( mainWidget ) {
+ switch ( event->type() ) {
+ case QEvent::FocusIn:
+ mainWidget->setAccelsEnabled( false );
+ break;
+ case QEvent::FocusOut:
+ mainWidget->setAccelsEnabled( true );
+ break;
+ default:
+ // Avoid compiler warnings
+ break;
+ }
+ }
+ }
+
+ // In either case, always return false, we NEVER want to eat the event
+ return false;
+}
+
+
+bool HeaderListQuickSearch::itemMatches(const QListViewItem *item, const QString &s) const
+{
+ mCurrentSearchTerm = s; // bit of a hack, but works
+ if ( mStatus != 0 ) {
+ KMHeaders *headers = static_cast<KMHeaders*>( item->listView() );
+ const KMMsgBase *msg = headers->getMsgBaseForItem( item );
+ if ( !msg || ! ( msg->status() & mStatus ) )
+ return false;
+ }
+ return KListViewSearchLine::itemMatches(item, s);
+}
+
+//-----------------------------------------------------------------------------
+void HeaderListQuickSearch::reset()
+{
+ clear();
+ mStatusCombo->setCurrentItem( 0 );
+ slotStatusChanged( 0 );
+}
+
+void HeaderListQuickSearch::slotStatusChanged( int index )
+{
+ if ( index == 0 )
+ mStatus = 0;
+ else
+ mStatus = KMSearchRuleStatus::statusFromEnglishName( statusList[index - 1] );
+ updateSearch();
+}
+
+void HeaderListQuickSearch::insertStatus(KMail::StatusValueTypes which)
+{
+ mStatusCombo->insertItem( SmallIcon( KMail::StatusValues[which].icon ),
+ i18n( KMail::StatusValues[ which ].text ) );
+ statusList.append( KMail::StatusValues[ which ].text );
+}
+
+
+QString HeaderListQuickSearch::currentSearchTerm() const
+{
+ return mCurrentSearchTerm;
+}
+
+
+int HeaderListQuickSearch::currentStatus() const
+{
+ return mStatus;
+}
+
+} // namespace KMail
+
+#include "headerlistquicksearch.moc"
diff --git a/kmail/headerlistquicksearch.h b/kmail/headerlistquicksearch.h
new file mode 100644
index 00000000..cc2e728a
--- /dev/null
+++ b/kmail/headerlistquicksearch.h
@@ -0,0 +1,102 @@
+/* -*- mode: C++ -*-
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2004 Till Adam <adam@kde.org>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef KMAILHEADERLISTQUICKSEARCH_H
+#define KMAILHEADERLISTQUICKSEARCH_H
+
+#include "kmmsgbase.h" // for KMMsgStatus
+#include "kmsearchpattern.h"
+#include <klistviewsearchline.h>
+#include <qvaluevector.h>
+class QComboBox;
+class QLabel;
+class KListView;
+class KActionCollection;
+
+namespace KMail {
+
+class HeaderListQuickSearch : public KListViewSearchLine
+{
+Q_OBJECT
+public:
+ HeaderListQuickSearch( QWidget *parent,
+ KListView *listView,
+ KActionCollection *actionCollection,
+ const char *name = 0 );
+ virtual ~HeaderListQuickSearch();
+
+ /**
+ * Used to disable the main window's accelerators when the search widget's
+ * combo has focus
+ */
+ bool eventFilter( QObject *watched, QEvent *event );
+
+ /**
+ * Returns the currently entered search text.
+ */
+ QString currentSearchTerm() const;
+
+ /**
+ * Returns the currently selected status filter.
+ */
+ int currentStatus() const;
+
+public slots:
+ void reset();
+
+signals:
+ void requestFullSearch();
+
+protected:
+ /**
+ * checks whether @param item contains the search string and has the status
+ * currently in mStatus
+ */
+ virtual bool itemMatches(const QListViewItem *item, const QString &s) const;
+
+private slots:
+ /**
+ * cache the status in mStatus so as to avoid having to do the comparatively
+ * expensive string comparison for each item in itemMatches
+ */
+ void slotStatusChanged( int index );
+
+private:
+ /** Helper method for the filling of the status combo. */
+ void insertStatus(KMail::StatusValueTypes which);
+ QComboBox *mStatusCombo;
+ KMMsgStatus mStatus;
+ QValueVector<QString> statusList;
+ mutable QString mCurrentSearchTerm;
+};
+
+}
+
+#endif
diff --git a/kmail/headerstrategy.cpp b/kmail/headerstrategy.cpp
new file mode 100644
index 00000000..1c73532c
--- /dev/null
+++ b/kmail/headerstrategy.cpp
@@ -0,0 +1,312 @@
+/* -*- c++ -*-
+ headerstrategy.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "headerstrategy.h"
+
+#include "kmkernel.h"
+
+#include <kdebug.h>
+#include <kconfig.h>
+
+namespace KMail {
+
+ //
+ // Header tables:
+ //
+
+ static const char * briefHeaders[] = {
+ "subject", "from", "cc", "bcc", "date"
+ };
+ static const int numBriefHeaders = sizeof briefHeaders / sizeof *briefHeaders;
+
+
+ static const char * standardHeaders[] = {
+ "subject", "from", "cc", "bcc", "to"
+ };
+ static const int numStandardHeaders = sizeof standardHeaders / sizeof *standardHeaders;
+
+
+ static const char * richHeaders[] = {
+ "subject", "date", "from", "cc", "bcc", "to",
+ "organization", "organisation", "reply-to",
+ "user-agent", "x-mailer"
+ };
+ static const int numRichHeaders = sizeof richHeaders / sizeof *richHeaders;
+
+ //
+ // Convenience function
+ //
+
+ static QStringList stringList( const char * headers[], int numHeaders ) {
+ QStringList sl;
+ for ( int i = 0 ; i < numHeaders ; ++i )
+ sl.push_back( headers[i] );
+ return sl;
+ }
+
+ //
+ // AllHeaderStrategy:
+ // show everything
+ //
+
+ class AllHeaderStrategy : public HeaderStrategy {
+ friend class ::KMail::HeaderStrategy;
+ protected:
+ AllHeaderStrategy() : HeaderStrategy() {}
+ virtual ~AllHeaderStrategy() {}
+
+ public:
+ const char * name() const { return "all"; }
+ const HeaderStrategy * next() const { return rich(); }
+ const HeaderStrategy * prev() const { return custom(); }
+
+ DefaultPolicy defaultPolicy() const { return Display; }
+
+ bool showHeader( const QString & ) const {
+ return true; // more efficient than default impl
+ }
+ };
+
+ //
+ // RichHeaderStrategy:
+ // Date, Subject, From, To, CC, ### what exactly?
+ //
+
+ class RichHeaderStrategy : public HeaderStrategy {
+ friend class ::KMail::HeaderStrategy;
+ protected:
+ RichHeaderStrategy()
+ : HeaderStrategy(),
+ mHeadersToDisplay( stringList( richHeaders, numRichHeaders ) ) {}
+ virtual ~RichHeaderStrategy() {}
+
+ public:
+ const char * name() const { return "rich"; }
+ const HeaderStrategy * next() const { return standard(); }
+ const HeaderStrategy * prev() const { return all(); }
+
+ QStringList headersToDisplay() const { return mHeadersToDisplay; }
+ DefaultPolicy defaultPolicy() const { return Hide; }
+
+ private:
+ const QStringList mHeadersToDisplay;
+ };
+
+ //
+ // StandardHeaderStrategy:
+ // BCC, CC, Date, From, Subject, To
+ //
+
+ class StandardHeaderStrategy : public HeaderStrategy {
+ friend class ::KMail::HeaderStrategy;
+ protected:
+ StandardHeaderStrategy()
+ : HeaderStrategy(),
+ mHeadersToDisplay( stringList( standardHeaders, numStandardHeaders) ) {}
+ virtual ~StandardHeaderStrategy() {}
+
+ public:
+ const char * name() const { return "standard"; }
+ const HeaderStrategy * next() const { return brief(); }
+ const HeaderStrategy * prev() const { return rich(); }
+
+ QStringList headersToDisplay() const { return mHeadersToDisplay; }
+ DefaultPolicy defaultPolicy() const { return Hide; }
+
+ private:
+ const QStringList mHeadersToDisplay;
+ };
+
+ //
+ // BriefHeaderStrategy
+ // From, Subject, Date
+ //
+
+ class BriefHeaderStrategy : public HeaderStrategy {
+ friend class ::KMail::HeaderStrategy;
+ protected:
+ BriefHeaderStrategy()
+ : HeaderStrategy(),
+ mHeadersToDisplay( stringList( briefHeaders, numBriefHeaders ) ) {}
+ virtual ~BriefHeaderStrategy() {}
+
+ public:
+ const char * name() const { return "brief"; }
+ const HeaderStrategy * next() const { return custom(); }
+ const HeaderStrategy * prev() const { return standard(); }
+
+ QStringList headersToDisplay() const { return mHeadersToDisplay; }
+ DefaultPolicy defaultPolicy() const { return Hide; }
+
+ private:
+ const QStringList mHeadersToDisplay;
+ };
+
+
+ //
+ // CustomHeaderStrategy
+ // Determined by user
+ //
+
+ class CustomHeaderStrategy : public HeaderStrategy {
+ friend class ::KMail::HeaderStrategy;
+ protected:
+ CustomHeaderStrategy();
+ virtual ~CustomHeaderStrategy() {}
+
+ public:
+ const char * name() const { return "custom"; }
+ const HeaderStrategy * next() const { return all(); }
+ const HeaderStrategy * prev() const { return brief(); }
+
+ QStringList headersToDisplay() const { return mHeadersToDisplay; }
+ QStringList headersToHide() const { return mHeadersToHide; }
+ DefaultPolicy defaultPolicy() const { return mDefaultPolicy; }
+
+ private:
+ QStringList mHeadersToDisplay;
+ QStringList mHeadersToHide;
+ DefaultPolicy mDefaultPolicy;
+ };
+
+
+ CustomHeaderStrategy::CustomHeaderStrategy()
+ : HeaderStrategy()
+ {
+ KConfigGroup customHeader( KMKernel::config(), "Custom Headers" );
+ if ( customHeader.hasKey( "headers to display" ) ) {
+ mHeadersToDisplay = customHeader.readListEntry( "headers to display" );
+ for ( QStringList::iterator it = mHeadersToDisplay.begin() ; it != mHeadersToDisplay.end() ; ++ it )
+ *it = (*it).lower();
+ } else
+ mHeadersToDisplay = stringList( standardHeaders, numStandardHeaders );
+
+ if ( customHeader.hasKey( "headers to hide" ) ) {
+ mHeadersToHide = customHeader.readListEntry( "headers to hide" );
+ for ( QStringList::iterator it = mHeadersToHide.begin() ; it != mHeadersToHide.end() ; ++ it )
+ *it = (*it).lower();
+ }
+
+ mDefaultPolicy = customHeader.readEntry( "default policy", "hide" ) == "display" ? Display : Hide ;
+ }
+
+ //
+ // HeaderStrategy abstract base:
+ //
+
+ HeaderStrategy::HeaderStrategy() {
+
+ }
+
+ HeaderStrategy::~HeaderStrategy() {
+
+ }
+
+ QStringList HeaderStrategy::headersToDisplay() const {
+ return QStringList();
+ }
+
+ QStringList HeaderStrategy::headersToHide() const {
+ return QStringList();
+ }
+
+ bool HeaderStrategy::showHeader( const QString & header ) const {
+ if ( headersToDisplay().contains( header.lower() ) ) return true;
+ if ( headersToHide().contains( header.lower() ) ) return false;
+ return defaultPolicy() == Display;
+ }
+
+ const HeaderStrategy * HeaderStrategy::create( Type type ) {
+ switch ( type ) {
+ case All: return all();
+ case Rich: return rich();
+ case Standard: return standard();
+ case Brief: return brief();
+ case Custom: return custom();
+ }
+ kdFatal( 5006 ) << "HeaderStrategy::create(): Unknown header strategy ( type == "
+ << (int)type << " ) requested!" << endl;
+ return 0; // make compiler happy
+ }
+
+ const HeaderStrategy * HeaderStrategy::create( const QString & type ) {
+ QString lowerType = type.lower();
+ if ( lowerType == "all" ) return all();
+ if ( lowerType == "rich" ) return HeaderStrategy::rich();
+ //if ( lowerType == "standard" ) return standard(); // not needed, see below
+ if ( lowerType == "brief" ) return brief();
+ if ( lowerType == "custom" ) return custom();
+ // don't kdFatal here, b/c the strings are user-provided
+ // (KConfig), so fail gracefully to the default:
+ return standard();
+ }
+
+ static const HeaderStrategy * allStrategy = 0;
+ static const HeaderStrategy * richStrategy = 0;
+ static const HeaderStrategy * standardStrategy = 0;
+ static const HeaderStrategy * briefStrategy = 0;
+ static const HeaderStrategy * customStrategy = 0;
+
+ const HeaderStrategy * HeaderStrategy::all() {
+ if ( !allStrategy )
+ allStrategy = new AllHeaderStrategy();
+ return allStrategy;
+ }
+
+ const HeaderStrategy * HeaderStrategy::rich() {
+ if ( !richStrategy )
+ richStrategy = new RichHeaderStrategy();
+ return richStrategy;
+ }
+
+ const HeaderStrategy * HeaderStrategy::standard() {
+ if ( !standardStrategy )
+ standardStrategy = new StandardHeaderStrategy();
+ return standardStrategy;
+ }
+
+ const HeaderStrategy * HeaderStrategy::brief() {
+ if ( !briefStrategy )
+ briefStrategy = new BriefHeaderStrategy();
+ return briefStrategy;
+ }
+
+ const HeaderStrategy * HeaderStrategy::custom() {
+ if ( !customStrategy )
+ customStrategy = new CustomHeaderStrategy();
+ return customStrategy;
+ }
+
+} // namespace KMail
diff --git a/kmail/headerstrategy.h b/kmail/headerstrategy.h
new file mode 100644
index 00000000..b5bdce35
--- /dev/null
+++ b/kmail/headerstrategy.h
@@ -0,0 +1,80 @@
+/* -*- c++ -*-
+ headerstrategy.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_HEADERSTRATEGY_H__
+#define __KMAIL_HEADERSTRATEGY_H__
+
+class QString;
+class QStringList;
+
+namespace KMail {
+
+ class HeaderStrategy {
+ protected:
+ HeaderStrategy();
+ virtual ~HeaderStrategy();
+
+ public:
+ //
+ // Factory methods:
+ //
+ enum Type { All, Rich, Standard, Brief, Custom };
+
+ static const HeaderStrategy * create( Type type );
+ static const HeaderStrategy * create( const QString & type );
+
+ static const HeaderStrategy * all();
+ static const HeaderStrategy * rich();
+ static const HeaderStrategy * standard();
+ static const HeaderStrategy * brief();
+ static const HeaderStrategy * custom();
+
+ //
+ // Methods for handling the strategies:
+ //
+ virtual const char * name() const = 0;
+ virtual const HeaderStrategy * next() const = 0;
+ virtual const HeaderStrategy * prev() const = 0;
+
+ //
+ // HeaderStrategy interface:
+ //
+ enum DefaultPolicy { Display, Hide };
+
+ virtual QStringList headersToDisplay() const;
+ virtual QStringList headersToHide() const;
+ virtual DefaultPolicy defaultPolicy() const = 0;
+ virtual bool showHeader( const QString & header ) const;
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_HEADERSTRATEGY_H__
diff --git a/kmail/headerstyle.cpp b/kmail/headerstyle.cpp
new file mode 100644
index 00000000..26e615d9
--- /dev/null
+++ b/kmail/headerstyle.cpp
@@ -0,0 +1,983 @@
+/* -*- c++ -*-
+ headerstyle.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "headerstyle.h"
+
+#include "headerstrategy.h"
+#include "kmkernel.h"
+#include "linklocator.h"
+#include "kmmessage.h"
+#include "spamheaderanalyzer.h"
+#include "globalsettings.h"
+
+#include <libemailfunctions/email.h>
+#include <libkdepim/kxface.h>
+using namespace KPIM;
+
+#include <mimelib/string.h>
+#include <mimelib/field.h>
+#include <mimelib/headers.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kglobal.h>
+#include <kimproxy.h>
+#include <kabc/stdaddressbook.h>
+#include <kabc/addresseelist.h>
+#include <kmdcodec.h>
+#include <qdatetime.h>
+#include <qbuffer.h>
+#include <qbitmap.h>
+#include <qimage.h>
+#include <qapplication.h>
+#include <qregexp.h>
+
+#include <kstandarddirs.h>
+
+namespace KMail {
+
+ //
+ // Convenience functions:
+ //
+ static inline QString directionOf( const QString & str ) {
+ return str.isRightToLeft() ? "rtl" : "ltr" ;
+ }
+
+#if 0
+ // Converts to html. Changes URLs into href's, escapes HTML special
+ // chars and inserts the result into an <div> or <span> tag with
+ // "dir" set to "rtl" or "ltr" depending on the direction of @p str.
+ static QString convertToHtmlBlock( const QString & str, bool useSpan=false ) {
+ QString dir = directionOf( str );
+ QString format = "<%1 dir=\"%3\">%4</%2>";
+ return format.arg( useSpan ? "span" : "div" )
+ .arg( useSpan ? "span" : "div" )
+ .arg( dir )
+ .arg( LinkLocator::convertToHtml( str ) );
+ }
+#endif
+
+ // ### tmp wrapper to make kmreaderwin code working:
+ static QString strToHtml( const QString & str,
+ int flags = LinkLocator::PreserveSpaces ) {
+ return LinkLocator::convertToHtml( str, flags );
+ }
+
+ //
+ // BriefHeaderStyle
+ // Show everything in a single line, don't show header field names.
+ //
+
+ class BriefHeaderStyle : public HeaderStyle {
+ friend class ::KMail::HeaderStyle;
+ protected:
+ BriefHeaderStyle() : HeaderStyle() {}
+ virtual ~BriefHeaderStyle() {}
+
+ public:
+ const char * name() const { return "brief"; }
+ const HeaderStyle * next() const { return plain(); }
+ const HeaderStyle * prev() const { return fancy(); }
+
+ QString format( const KMMessage * message, const HeaderStrategy * strategy,
+ const QString & vCardName, bool printing, bool topLevel ) const;
+ };
+
+ QString BriefHeaderStyle::format( const KMMessage * message,
+ const HeaderStrategy * strategy,
+ const QString & vCardName, bool printing, bool topLevel ) const {
+ if ( !message ) return QString::null;
+ if ( !strategy )
+ strategy = HeaderStrategy::brief();
+
+ // The direction of the header is determined according to the direction
+ // of the application layout.
+
+ QString dir = QApplication::reverseLayout() ? "rtl" : "ltr" ;
+
+ // However, the direction of the message subject within the header is
+ // determined according to the contents of the subject itself. Since
+ // the "Re:" and "Fwd:" prefixes would always cause the subject to be
+ // considered left-to-right, they are ignored when determining its
+ // direction.
+
+ QString subjectDir;
+ if (!message->subject().isEmpty())
+ subjectDir = directionOf( message->cleanSubject() );
+ else
+ subjectDir = directionOf( i18n("No Subject") );
+
+ // Prepare the date string (when printing always use the localized date)
+ QString dateString;
+ if( printing ) {
+ QDateTime dateTime;
+ KLocale * locale = KGlobal::locale();
+ dateTime.setTime_t( message->date() );
+ dateString = locale->formatDateTime( dateTime );
+ } else {
+ dateString = message->dateStr();
+ }
+
+ QString headerStr = "<div class=\"header\" dir=\"" + dir + "\">\n";
+
+ if ( strategy->showHeader( "subject" ) )
+ headerStr += "<div dir=\"" + subjectDir + "\">\n"
+ "<b style=\"font-size:130%\">" +
+ strToHtml( message->subject() ) +
+ "</b></div>\n";
+
+ QStringList headerParts;
+
+ if ( strategy->showHeader( "from" ) ) {
+ QString fromStr = message->from();
+ if ( fromStr.isEmpty() ) // no valid email in from, maybe just a name
+ fromStr = message->fromStrip(); // let's use that
+ QString fromPart = KMMessage::emailAddrAsAnchor( fromStr, true );
+ if ( !vCardName.isEmpty() )
+ fromPart += "&nbsp;&nbsp;<a href=\"" + vCardName + "\">" + i18n("[vCard]") + "</a>";
+ headerParts << fromPart;
+ }
+
+ if ( strategy->showHeader( "cc" ) && !message->cc().isEmpty() )
+ headerParts << i18n("CC: ") + KMMessage::emailAddrAsAnchor( message->cc(), true );
+
+ if ( strategy->showHeader( "bcc" ) && !message->bcc().isEmpty() )
+ headerParts << i18n("BCC: ") + KMMessage::emailAddrAsAnchor( message->bcc(), true );
+
+ if ( strategy->showHeader( "date" ) )
+ headerParts << strToHtml(message->dateShortStr());
+
+ // remove all empty (modulo whitespace) entries and joins them via ", \n"
+ headerStr += " (" + headerParts.grep( QRegExp( "\\S" ) ).join( ",\n" ) + ')';
+
+ headerStr += "</div>\n";
+
+ // ### iterate over the rest of strategy->headerToDisplay() (or
+ // ### all headers if DefaultPolicy == Display) (elsewhere, too)
+ return headerStr;
+ }
+
+ //
+ // PlainHeaderStyle:
+ // show every header field on a line by itself,
+ // show subject larger
+ //
+
+ class PlainHeaderStyle : public HeaderStyle {
+ friend class ::KMail::HeaderStyle;
+ protected:
+ PlainHeaderStyle() : HeaderStyle() {}
+ virtual ~PlainHeaderStyle() {}
+
+ public:
+ const char * name() const { return "plain"; }
+ const HeaderStyle * next() const { return fancy(); }
+ const HeaderStyle * prev() const { return brief(); }
+
+ QString format( const KMMessage * message, const HeaderStrategy * strategy,
+ const QString & vCardName, bool printing, bool topLevel ) const;
+
+ private:
+ QString formatAllMessageHeaders( const KMMessage * message ) const;
+ };
+
+ QString PlainHeaderStyle::format( const KMMessage * message,
+ const HeaderStrategy * strategy,
+ const QString & vCardName, bool printing, bool topLevel ) const {
+ if ( !message ) return QString::null;
+ if ( !strategy )
+ strategy = HeaderStrategy::rich();
+
+ // The direction of the header is determined according to the direction
+ // of the application layout.
+
+ QString dir = ( QApplication::reverseLayout() ? "rtl" : "ltr" );
+
+ // However, the direction of the message subject within the header is
+ // determined according to the contents of the subject itself. Since
+ // the "Re:" and "Fwd:" prefixes would always cause the subject to be
+ // considered left-to-right, they are ignored when determining its
+ // direction.
+
+ QString subjectDir;
+ if (!message->subject().isEmpty())
+ subjectDir = directionOf( message->cleanSubject() );
+ else
+ subjectDir = directionOf( i18n("No Subject") );
+
+ // Prepare the date string (when printing always use the localized date)
+ QString dateString;
+ if( printing ) {
+ QDateTime dateTime;
+ KLocale* locale = KGlobal::locale();
+ dateTime.setTime_t( message->date() );
+ dateString = locale->formatDateTime( dateTime );
+ }
+ else {
+ dateString = message->dateStr();
+ }
+
+ QString headerStr;
+
+ if ( strategy->headersToDisplay().isEmpty()
+ && strategy->defaultPolicy() == HeaderStrategy::Display ) {
+ // crude way to emulate "all" headers - Note: no strings have
+ // i18n(), so direction should always be ltr.
+ headerStr= QString("<div class=\"header\" dir=\"ltr\">");
+ headerStr += formatAllMessageHeaders( message );
+ return headerStr + "</div>";
+ }
+
+ headerStr = QString("<div class=\"header\" dir=\"%1\">").arg(dir);
+
+ //case HdrLong:
+ if ( strategy->showHeader( "subject" ) )
+ headerStr += QString("<div dir=\"%1\"><b style=\"font-size:130%\">" +
+ strToHtml(message->subject()) + "</b></div>\n")
+ .arg(subjectDir);
+
+ if ( strategy->showHeader( "date" ) )
+ headerStr.append(i18n("Date: ") + strToHtml(dateString)+"<br>\n");
+
+#if 0
+ // Get Instant Messaging presence
+ QString presence;
+ QString kabcUid;
+ if ( strategy->showHeader( "status" ) )
+ {
+ KABC::AddressBook *addressBook = KABC::StdAddressBook::self( true );
+ KABC::AddresseeList addresses = addressBook->findByEmail( KPIM::getFirstEmailAddress( message->from() ) );
+ ::KIMProxy *imProxy = KMKernel::self()->imProxy();
+ kabcUid = addresses[0].uid();
+ presence = imProxy->presenceString( kabcUid );
+ }
+#endif
+
+ if ( strategy->showHeader( "from" ) ) {
+ QString fromStr = message->from();
+ if ( fromStr.isEmpty() ) // no valid email in from, maybe just a name
+ fromStr = message->fromStrip(); // let's use that
+ headerStr.append(i18n("From: ") +
+ KMMessage::emailAddrAsAnchor( fromStr, false, "", true ) );
+ if ( !vCardName.isEmpty() )
+ headerStr.append("&nbsp;&nbsp;<a href=\"" + vCardName +
+ "\">" + i18n("[vCard]") + "</a>" );
+#if 0
+ if ( !presence.isEmpty() && strategy->showHeader( "status" ) )
+ headerStr.append("&nbsp;&nbsp;(<span name=\"presence-" + kabcUid + "\">" + presence + "</span>)" );
+#endif
+
+ if ( strategy->showHeader( "organization" )
+ && !message->headerField("Organization").isEmpty())
+ headerStr.append("&nbsp;&nbsp;(" +
+ strToHtml(message->headerField("Organization")) + ")");
+ headerStr.append("<br>\n");
+ }
+
+ if ( strategy->showHeader( "to" ) )
+ headerStr.append(i18n("To: ")+
+ KMMessage::emailAddrAsAnchor(message->to(),false) + "<br>\n");
+
+ if ( strategy->showHeader( "cc" ) && !message->cc().isEmpty() )
+ headerStr.append(i18n("CC: ")+
+ KMMessage::emailAddrAsAnchor(message->cc(),false) + "<br>\n");
+
+ if ( strategy->showHeader( "bcc" ) && !message->bcc().isEmpty() )
+ headerStr.append(i18n("BCC: ")+
+ KMMessage::emailAddrAsAnchor(message->bcc(),false) + "<br>\n");
+
+ if ( strategy->showHeader( "reply-to" ) && !message->replyTo().isEmpty())
+ headerStr.append(i18n("Reply to: ")+
+ KMMessage::emailAddrAsAnchor(message->replyTo(),false) + "<br>\n");
+
+ headerStr += "</div>\n";
+
+ return headerStr;
+ }
+
+ QString PlainHeaderStyle::formatAllMessageHeaders( const KMMessage * message ) const {
+ const DwHeaders & headers = message->headers();
+ QString result;
+
+ for ( const DwField * field = headers.FirstField() ; field ; field = field->Next() ) {
+ result += ( field->FieldNameStr() + ": " ).c_str();
+ result += strToHtml( field->FieldBodyStr().c_str() );
+ result += "<br>\n";
+ }
+
+ return result;
+ }
+
+ //
+ // FancyHeaderStyle:
+ // Like PlainHeaderStyle, but with slick frames and background colours.
+ //
+
+ class FancyHeaderStyle : public HeaderStyle {
+ friend class ::KMail::HeaderStyle;
+ protected:
+ FancyHeaderStyle() : HeaderStyle() {}
+ virtual ~FancyHeaderStyle() {}
+
+ public:
+ const char * name() const { return "fancy"; }
+ const HeaderStyle * next() const { return enterprise(); }
+ const HeaderStyle * prev() const { return plain(); }
+
+ QString format( const KMMessage * message, const HeaderStrategy * strategy,
+ const QString & vCardName, bool printing, bool topLevel ) const;
+ static QString imgToDataUrl( const QImage & image,
+ const char *fmt = "PNG" );
+
+ private:
+ static QString drawSpamMeter( double percent, const QString & filterHeader );
+
+ };
+
+ QString FancyHeaderStyle::drawSpamMeter( double percent,
+ const QString & filterHeader )
+ {
+ QImage meterBar( 20, 1, 8, 24 );
+ const unsigned short gradient[20][3] = {
+ { 0, 255, 0 },
+ { 27, 254, 0 },
+ { 54, 252, 0 },
+ { 80, 250, 0 },
+ { 107, 249, 0 },
+ { 135, 247, 0 },
+ { 161, 246, 0 },
+ { 187, 244, 0 },
+ { 214, 242, 0 },
+ { 241, 241, 0 },
+ { 255, 228, 0 },
+ { 255, 202, 0 },
+ { 255, 177, 0 },
+ { 255, 151, 0 },
+ { 255, 126, 0 },
+ { 255, 101, 0 },
+ { 255, 76, 0 },
+ { 255, 51, 0 },
+ { 255, 25, 0 },
+ { 255, 0, 0 }
+ };
+ meterBar.setColor( 21, qRgb( 255, 255, 255 ) );
+ meterBar.setColor( 22, qRgb( 170, 170, 170 ) );
+ if ( percent < 0 ) // grey is for errors
+ meterBar.fill( 22 );
+ else {
+ meterBar.fill( 21 );
+ int max = QMIN( 20, static_cast<int>( percent ) / 5 );
+ for ( int i = 0; i < max; ++i ) {
+ meterBar.setColor( i+1, qRgb( gradient[i][0], gradient[i][1],
+ gradient[i][2] ) );
+ meterBar.setPixel( i, 0, i+1 );
+ }
+ }
+ QString titleText = i18n("%1% probability of being spam.\n\nFull report:\n%2")
+ .arg( QString::number( percent ), filterHeader );
+ return QString("<img src=\"%1\" width=\"%2\" height=\"%3\" style=\"border: 1px solid black;\" title=\"%4\"> &nbsp;")
+ .arg( imgToDataUrl( meterBar, "PPM" ), QString::number( 20 ),
+ QString::number( 5 ), titleText );
+ }
+
+
+ QString FancyHeaderStyle::format( const KMMessage * message,
+ const HeaderStrategy * strategy,
+ const QString & vCardName, bool printing, bool topLevel ) const {
+ if ( !message ) return QString::null;
+ if ( !strategy )
+ strategy = HeaderStrategy::rich();
+
+ KConfigGroup configReader( KMKernel::config(), "Reader" );
+
+ // ### from kmreaderwin begin
+ // The direction of the header is determined according to the direction
+ // of the application layout.
+
+ QString dir = ( QApplication::reverseLayout() ? "rtl" : "ltr" );
+ QString headerStr = QString("<div class=\"fancy header\" dir=\"%1\">\n").arg(dir);
+
+ // However, the direction of the message subject within the header is
+ // determined according to the contents of the subject itself. Since
+ // the "Re:" and "Fwd:" prefixes would always cause the subject to be
+ // considered left-to-right, they are ignored when determining its
+ // direction.
+
+ QString subjectDir;
+ if ( !message->subject().isEmpty() )
+ subjectDir = directionOf( message->cleanSubject() );
+ else
+ subjectDir = directionOf( i18n("No Subject") );
+
+ // Prepare the date string (when printing always use the localized date)
+ QString dateString;
+ if( printing ) {
+ QDateTime dateTime;
+ KLocale* locale = KGlobal::locale();
+ dateTime.setTime_t( message->date() );
+ dateString = locale->formatDateTime( dateTime );
+ }
+ else {
+ dateString = message->dateStr();
+ }
+
+ // Spam header display.
+ // If the spamSpamStatus config value is true then we look for headers
+ // from a few spam filters and try to create visually meaningful graphics
+ // out of the spam scores.
+
+ QString spamHTML;
+
+ if ( configReader.readBoolEntry( "showSpamStatus", true ) ) {
+ SpamScores scores = SpamHeaderAnalyzer::getSpamScores( message );
+ for ( SpamScoresIterator it = scores.begin(); it != scores.end(); ++it )
+ spamHTML += (*it).agent() + " " +
+ drawSpamMeter( (*it).score(), (*it).spamHeader() );
+ }
+
+ QString userHTML;
+ QString presence;
+
+ // IM presence and kabc photo
+
+ ::KIMProxy *imProxy = KMKernel::self()->imProxy();
+ QString kabcUid;
+ KABC::AddressBook *addressBook = KABC::StdAddressBook::self( true );
+ KABC::AddresseeList addresses = addressBook->findByEmail( KPIM::getFirstEmailAddress( message->from() ) );
+
+ QString photoURL;
+ int photoWidth = 60;
+ int photoHeight = 60;
+ if( addresses.count() == 1 )
+ {
+ // kabcUid is embedded in im: URIs to indicate which IM contact to message
+ kabcUid = addresses[0].uid();
+
+ if ( imProxy->initialize() ) {
+ // im status
+ presence = imProxy->presenceString( kabcUid );
+ if ( !presence.isEmpty() )
+ {
+ QString presenceIcon = QString::fromLatin1( " <img src=\"%1\"/>" )
+ .arg( imgToDataUrl( imProxy->presenceIcon( kabcUid ).convertToImage() ) );
+ presence += presenceIcon;
+ }
+ }
+ // picture
+ if ( addresses[0].photo().isIntern() )
+ {
+ // get photo data and convert to data: url
+ //kdDebug( 5006 ) << "INTERNAL photo found" << endl;
+ QImage photo = addresses[0].photo().data();
+ if ( !photo.isNull() )
+ {
+ photoWidth = photo.width();
+ photoHeight = photo.height();
+ // scale below 60, otherwise it can get way too large
+ if ( photoHeight > 60 ) {
+ double ratio = ( double )photoHeight / ( double )photoWidth;
+ photoHeight = 60;
+ photoWidth = (int)( 60 / ratio );
+ photo = photo.smoothScale( photoWidth, photoHeight );
+ }
+ photoURL = imgToDataUrl( photo );
+ }
+ }
+ else
+ {
+ //kdDebug( 5006 ) << "URL found" << endl;
+ photoURL = addresses[0].photo().url();
+ if ( photoURL.startsWith("/") )
+ photoURL.prepend( "file:" );
+ }
+ }
+ else // TODO: find a usable one
+ {
+ kdDebug( 5006 ) << "Multiple / No addressees matched email address; Count is " << addresses.count() << endl;
+ userHTML = "&nbsp;";
+ }
+
+ if( photoURL.isEmpty() ) {
+ // no photo, look for a Face header
+ QString faceheader = message->headerField( "Face" );
+ if ( !faceheader.isEmpty() ) {
+ QImage faceimage;
+
+ kdDebug( 5006 ) << "Found Face: header" << endl;
+
+ QCString facestring = faceheader.utf8();
+ // Spec says header should be less than 998 bytes
+ // Face: is 5 characters
+ if ( facestring.length() < 993 ) {
+ QByteArray facearray;
+ KCodecs::base64Decode(facestring, facearray);
+
+ QImage faceimage;
+ if ( faceimage.loadFromData( facearray, "png" ) ) {
+ // Spec says image must be 48x48 pixels
+ if ( (48 == faceimage.width()) && (48 == faceimage.height()) ) {
+ photoURL = imgToDataUrl( faceimage );
+ photoWidth = 48;
+ photoHeight = 48;
+ } else {
+ kdDebug( 5006 ) << "Face: header image is" << faceimage.width() << "by" << faceimage.height() <<"not 48x48 Pixels" << endl;
+ }
+ } else {
+ kdDebug( 5006 ) << "Failed to load decoded png from Face: header" << endl;
+ }
+ } else {
+ kdDebug( 5006 ) << "Face: header too long at " << facestring.length() << endl;
+ }
+ }
+ }
+
+ if( photoURL.isEmpty() )
+ {
+ // no photo, look for a X-Face header
+ QString xfaceURL;
+ QString xfhead = message->headerField( "X-Face" );
+ if ( !xfhead.isEmpty() )
+ {
+ KXFace xf;
+ photoURL = imgToDataUrl( xf.toImage( xfhead ) );
+ photoWidth = 48;
+ photoHeight = 48;
+
+ }
+ }
+
+ if( !photoURL.isEmpty() )
+ {
+ //kdDebug( 5006 ) << "Got a photo: " << photoURL << endl;
+ userHTML = QString("<img src=\"%1\" width=\"%2\" height=\"%3\">")
+ .arg( photoURL ).arg( photoWidth ).arg( photoHeight );
+ if ( presence.isEmpty() ) {
+ userHTML = QString("<div class=\"senderpic\">") + userHTML + "</div>";
+ } else {
+ userHTML = QString( "<div class=\"senderpic\">"
+ "<a href=\"im:%1\">%2<div class=\"senderstatus\">"
+ "<span name=\"presence-%3\">%4</span></div></a>"
+ "</div>" ).arg( kabcUid )
+ .arg( userHTML )
+ .arg( kabcUid )
+ .arg( presence );
+ }
+ } else {
+ // we don't have a photo, just show presence, if we have it
+ if ( !presence.isEmpty() )
+ userHTML = QString( "<a href=\"im:%1\"><div class=\"senderstatus\">"
+ "<span name=\"presence-%2\">%3</span></div></a>" )
+ .arg( kabcUid )
+ .arg( kabcUid )
+ .arg( presence );
+ }
+#if 0
+ // Disabled 'Launch IM' link in headers - Will
+ if ( imProxy->imAppsAvailable() )
+ presence = "<a name=\"launchim\" href=\"kmail:startIMApp\">" + i18n("Launch IM") + "</a></span>";
+ // do nothing - no im apps available, leave presence empty
+ //presence = i18n( "DCOP/InstantMessenger not installed" );
+ kdDebug( 5006 ) << "final presence: '" << presence << "'" << endl;
+#endif
+
+
+ //case HdrFancy:
+ // the subject line and box below for details
+ if ( strategy->showHeader( "subject" ) ) {
+ const int flags = LinkLocator::PreserveSpaces |
+ ( GlobalSettings::self()->showEmoticons() ?
+ LinkLocator::ReplaceSmileys : 0 );
+ headerStr += QString("<div dir=\"%1\">%2</div>\n")
+ .arg(subjectDir)
+ .arg(message->subject().isEmpty()?
+ i18n("No Subject") :
+ strToHtml( message->subject(), flags ));
+ }
+ headerStr += "<table class=\"outer\"><tr><td width=\"100%\"><table>\n";
+ //headerStr += "<table>\n";
+ // from line
+ // the mailto: URLs can contain %3 etc., therefore usage of multiple
+ // QString::arg is not possible
+ if ( strategy->showHeader( "from" ) ) {
+ QString fromStr = message->from();
+ if ( fromStr.isEmpty() ) // no valid email in from, maybe just a name
+ fromStr = message->fromStrip(); // let's use that
+ headerStr += QString("<tr><th>%1</th>\n"
+ "<td>")
+ .arg(i18n("From: "))
+ + KMMessage::emailAddrAsAnchor( fromStr, false )
+ + ( !message->headerField( "Resent-From" ).isEmpty() ? "&nbsp;"
+ + i18n("(resent from %1)")
+ .arg( KMMessage::emailAddrAsAnchor(
+ message->headerField( "Resent-From" ),false) )
+ : QString("") )
+ + ( !vCardName.isEmpty() ? "&nbsp;&nbsp;<a href=\"" + vCardName + "\">"
+ + i18n("[vCard]") + "</a>"
+ : QString("") )
+#if 0
+ + ( ( !presence.isEmpty() )
+ ? "&nbsp;&nbsp;(<span name=\"presence-" + kabcUid + "\">" + presence + "</span>)"
+ : QString("") )
+#endif
+ + ( message->headerField("Organization").isEmpty()
+ ? QString("")
+ : "&nbsp;&nbsp;("
+ + strToHtml(message->headerField("Organization"))
+ + ")")
+ + "</td></tr>\n";
+ }
+ // to line
+ if ( strategy->showHeader( "to" ) )
+ headerStr.append(QString("<tr><th>%1</th>\n"
+ "<td>%2</td></tr>\n")
+ .arg(i18n("To: "))
+ .arg(KMMessage::emailAddrAsAnchor(message->to(),false)));
+
+ // cc line, if any
+ if ( strategy->showHeader( "cc" ) && !message->cc().isEmpty())
+ headerStr.append(QString("<tr><th>%1</th>\n"
+ "<td>%2</td></tr>\n")
+ .arg(i18n("CC: "))
+ .arg(KMMessage::emailAddrAsAnchor(message->cc(),false)));
+
+ // Bcc line, if any
+ if ( strategy->showHeader( "bcc" ) && !message->bcc().isEmpty())
+ headerStr.append(QString("<tr><th>%1</th>\n"
+ "<td>%2</td></tr>\n")
+ .arg(i18n("BCC: "))
+ .arg(KMMessage::emailAddrAsAnchor(message->bcc(),false)));
+
+ if ( strategy->showHeader( "date" ) )
+ headerStr.append(QString("<tr><th>%1</th>\n"
+ "<td dir=\"%2\">%3</td></tr>\n")
+ .arg(i18n("Date: "))
+ .arg( directionOf( message->dateStr() ) )
+ .arg(strToHtml(dateString)));
+
+ if ( GlobalSettings::self()->showUserAgent() ) {
+ if ( strategy->showHeader( "user-agent" ) ) {
+ if ( !message->headerField("User-Agent").isEmpty() ) {
+ headerStr.append(QString("<tr><th>%1</th>\n"
+ "<td>%2</td></tr>\n")
+ .arg(i18n("User-Agent: "))
+ .arg( strToHtml( message->headerField("User-Agent") ) ) );
+ }
+ }
+
+ if ( strategy->showHeader( "x-mailer" ) ) {
+ if ( !message->headerField("X-Mailer").isEmpty() ) {
+ headerStr.append(QString("<tr><th>%1</th>\n"
+ "<td>%2</td></tr>\n")
+ .arg(i18n("X-Mailer: "))
+ .arg( strToHtml( message->headerField("X-Mailer") ) ) );
+ }
+ }
+ }
+
+ // FIXME: Show status in synthetic header style field. Decide whether this or current in brackets style is best and remove one.
+ /* if( strategy->showHeader( "status" ) )
+ headerStr.append( QString( "<tr><th>%1</th>\n"
+ "<td dir=\"%2\">%3</td></tr>\n")
+ .arg(i18n("Sender status: "))
+ .arg( directionOf( onlineStatus ) )
+ .arg(onlineStatus));
+ */
+ headerStr.append( QString("<tr><td colspan=\"2\"><div id=\"attachmentInjectionPoint\"></div></td></tr>" ) );
+ headerStr.append(
+ QString( "</table></td><td align=\"center\">%1</td></tr></table>\n" ).arg(userHTML) );
+
+ if ( !spamHTML.isEmpty() )
+ headerStr.append( QString( "<div class=\"spamheader\" dir=\"%1\"><b>%2</b>&nbsp;<span style=\"padding-left: 20px;\">%3</span></div>\n")
+ .arg( subjectDir, i18n("Spam Status:"), spamHTML ) );
+
+ headerStr += "</div>\n\n";
+ return headerStr;
+ }
+
+ QString FancyHeaderStyle::imgToDataUrl( const QImage &image, const char* fmt )
+ {
+ QByteArray ba;
+ QBuffer buffer( ba );
+ buffer.open( IO_WriteOnly );
+ image.save( &buffer, fmt );
+ return QString::fromLatin1("data:image/%1;base64,%2")
+ .arg( fmt, KCodecs::base64Encode( ba ) );
+ }
+
+// #####################
+
+ class EnterpriseHeaderStyle : public HeaderStyle {
+ friend class ::KMail::HeaderStyle;
+ protected:
+ EnterpriseHeaderStyle() : HeaderStyle() {}
+ virtual ~EnterpriseHeaderStyle() {}
+
+ public:
+ const char * name() const { return "enterprise"; }
+ const HeaderStyle * next() const { return brief(); }
+ const HeaderStyle * prev() const { return fancy(); }
+
+ QString format( const KMMessage * message, const HeaderStrategy * strategy,
+ const QString & vCardName, bool printing, bool topLevel ) const;
+ };
+
+ QString EnterpriseHeaderStyle::format( const KMMessage * message,
+ const HeaderStrategy * strategy,
+ const QString & vCardName, bool printing, bool topLevel ) const {
+ if ( !message ) return QString::null;
+ if ( !strategy )
+ strategy = HeaderStrategy::brief();
+
+ // The direction of the header is determined according to the direction
+ // of the application layout.
+
+ QString dir = QApplication::reverseLayout() ? "rtl" : "ltr" ;
+
+ // However, the direction of the message subject within the header is
+ // determined according to the contents of the subject itself. Since
+ // the "Re:" and "Fwd:" prefixes would always cause the subject to be
+ // considered left-to-right, they are ignored when determining its
+ // direction.
+
+ QString subjectDir;
+ if (!message->subject().isEmpty())
+ subjectDir = directionOf( message->cleanSubject() );
+ else
+ subjectDir = directionOf( i18n("No Subject") );
+
+ // colors depend on if its encapsulated or not
+ QColor fontColor(Qt::white);
+ QString linkColor = "class =\"white\"";
+ const QColor activeColor = qApp->palette().active().highlight();
+ QColor activeColorDark = activeColor.dark(130);
+ // reverse colors for encapsulated
+ if( !topLevel ){
+ activeColorDark = activeColor.dark(50);
+ fontColor = qApp->palette().active().text();
+ linkColor = "";
+ }
+
+ QStringList 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"
+ QString headerPart = " " + headerParts.grep( QRegExp( "\\S" ) ).join( ", " );
+
+ // Prepare the date string (when printing always use the localized date)
+ QString dateString;
+ if( printing ) {
+ QDateTime dateTime;
+ KLocale * locale = KGlobal::locale();
+ dateTime.setTime_t( message->date() );
+ dateString = locale->formatDateTime( dateTime );
+ } else {
+ dateString = message->dateStr();
+ }
+
+ QString imgpath(locate("data","kmail/pics/"));
+ imgpath.append("enterprise_");
+ const QString borderSettings(" padding-top: 0px; padding-bottom: 0px; border-width: 0px ");
+ QString headerStr ("");
+
+ // 3D borders
+ if(topLevel)
+ headerStr +=
+ "<div style=\"position: fixed; top: 0px; left: 0px; background-color: #606060; "
+ "background-image: url("+imgpath+"shadow_left.png); width: 10px; min-height: 100%;\">&nbsp;</div>"
+ "<div style=\"position: fixed; top: 0px; right: 0px; background-color: #606060; "
+ "background-image: url("+imgpath+"shadow_right.png); width: 10px; min-height: 100%;\">&nbsp;</div>";
+
+ headerStr += ""
+ "<div style=\"margin-left: 8px; top: 0px;\"><span style=\"font-size: 10px; font-weight: bold;\">"+dateString+"</span></div>"
+ // #0057ae
+ "<table style=\"background: "+activeColorDark.name()+"; border-collapse:collapse; top: 14px; min-width: 200px; \" cellpadding=0> \n"
+ " <tr> \n"
+ " <td style=\"min-width: 6px; background-image: url("+imgpath+"top_left.png); \"></td> \n"
+ " <td style=\"height: 6px; width: 100%; background: url("+imgpath+"top.png); \"></td> \n"
+ " <td style=\"min-width: 6px; background: url("+imgpath+"top_right.png); \"></td> </tr> \n"
+ " <tr> \n"
+ " <td style=\"min-width: 6px; max-width: 6px; background: url("+imgpath+"left.png); \"></td> \n"
+ " <td style=\"\"> \n"
+ " <table style=\"color: "+fontColor.name()+" ! important; margin: 1px; border-spacing: 0px;\" cellpadding=0> \n";
+
+ // subject
+ //strToHtml( message->subject() )
+ if ( strategy->showHeader( "subject" ) ){
+ headerStr +=
+ " <tr> \n"
+ " <td style=\"font-size: 6px; text-align: right; padding-left: 5px; padding-right: 24px; "+borderSettings+"\"></td> \n"
+ " <td style=\"font-weight: bolder; font-size: 120%;"+borderSettings+"\">"+message->subject()+"</td> \n"
+ " </tr> \n";
+ }
+
+ // from
+ if ( strategy->showHeader( "from" ) ){
+ QString fromStr = message->from();
+ if ( fromStr.isEmpty() ) // no valid email in from, maybe just a name
+ fromStr = message->fromStrip(); // let's use that
+ // TODO vcard
+ QString fromPart = KMMessage::emailAddrAsAnchor( fromStr, true, linkColor );
+ if ( !vCardName.isEmpty() )
+ fromPart += "&nbsp;&nbsp;<a href=\"" + vCardName + "\" "+linkColor+">" + i18n("[vCard]") + "</a>";
+ //TDDO strategy date
+ //if ( strategy->showHeader( "date" ) )
+ headerStr +=
+ " <tr> \n"
+ " <td style=\"font-size: 6px; padding-left: 5px; padding-right: 24px; text-align: right; "+borderSettings+"\">"+i18n("From: ")+"</td> \n"
+ " <td style=\""+borderSettings+"\">"+ fromPart +"</td> "
+ " </tr> ";
+ }
+
+ // to, cc, bcc
+ headerStr +=
+ " <tr> "
+ " <td style=\"font-size: 6px; text-align: right; padding-left: 5px; padding-right: 24px; "+borderSettings+"\">"+i18n("To: ")+"</td> "
+ " <td style=\""+borderSettings+"\">"
+ +headerPart+
+ " </td> "
+ " </tr> ";
+
+ // header-bottom
+ headerStr +=
+ " </table> \n"
+ " </td> \n"
+ " <td style=\"min-width: 6px; max-height: 15px; background: url("+imgpath+"right.png); \"></td> \n"
+ " </tr> \n"
+ " <tr> \n"
+ " <td style=\"min-width: 6px; background: url("+imgpath+"s_left.png); \"></td> \n"
+ " <td style=\"height: 35px; width: 80%; background: url("+imgpath+"sbar.png);\"> \n"
+ " <img src=\""+imgpath+"sw.png\" style=\"margin: 0px; height: 30px; overflow:hidden; \"> \n"
+ " <img src=\""+imgpath+"sp_right.png\" style=\"float: right; \"> </td> \n"
+ " <td style=\"min-width: 6px; background: url("+imgpath+"s_right.png); \"></td> \n"
+ " </tr> \n"
+ " </table> \n";
+
+ // kmail icon
+ if(topLevel) {
+ headerStr +=
+ "<div class=\"noprint\" style=\"position: absolute; top: -14px; right: 30px; width: 91px; height: 91px;\">\n"
+ "<img style=\"float: right;\" src=\""+imgpath+"icon.png\">\n"
+ "</div>\n";
+
+ // attachments
+ headerStr +=
+ "<div class=\"noprint\" style=\"position: fixed; top: 60px; right: 20px; width: 91px; height: 200px;\">"
+ "<div id=\"attachmentInjectionPoint\"></div>"
+ "</div>\n";
+ }
+
+ headerStr += "<div style=\"padding: 6px;\">";
+
+ // TODO
+ // spam status
+ // ### iterate over the rest of strategy->headerToDisplay() (or
+ // ### all headers if DefaultPolicy == Display) (elsewhere, too)
+ return headerStr;
+ }
+
+// #####################
+
+ //
+ // HeaderStyle abstract base:
+ //
+
+ HeaderStyle::HeaderStyle() {
+
+ }
+
+ HeaderStyle::~HeaderStyle() {
+
+ }
+
+ const HeaderStyle * HeaderStyle::create( Type type ) {
+ switch ( type ) {
+ case Brief: return brief();
+ case Plain: return plain();
+ case Fancy: return fancy();
+ case Enterprise: return enterprise();
+ }
+ kdFatal( 5006 ) << "HeaderStyle::create(): Unknown header style ( type == "
+ << (int)type << " ) requested!" << endl;
+ return 0; // make compiler happy
+ }
+
+ const HeaderStyle * HeaderStyle::create( const QString & type ) {
+ QString lowerType = type.lower();
+ if ( lowerType == "brief" ) return brief();
+ if ( lowerType == "plain" ) return plain();
+ if ( lowerType == "enterprise" ) return enterprise();
+ //if ( lowerType == "fancy" ) return fancy(); // not needed, see below
+ // don't kdFatal here, b/c the strings are user-provided
+ // (KConfig), so fail gracefully to the default:
+ return fancy();
+ }
+
+ static const HeaderStyle * briefStyle = 0;
+ static const HeaderStyle * plainStyle = 0;
+ static const HeaderStyle * fancyStyle = 0;
+ static const HeaderStyle * enterpriseStyle = 0;
+
+ const HeaderStyle * HeaderStyle::brief() {
+ if ( !briefStyle )
+ briefStyle = new BriefHeaderStyle();
+ return briefStyle;
+ }
+
+ const HeaderStyle * HeaderStyle::plain() {
+ if ( !plainStyle )
+ plainStyle = new PlainHeaderStyle();
+ return plainStyle;
+ }
+
+ const HeaderStyle * HeaderStyle::fancy() {
+ if ( !fancyStyle )
+ fancyStyle = new FancyHeaderStyle();
+ return fancyStyle;
+ }
+
+ const HeaderStyle * HeaderStyle::enterprise() {
+ if ( !enterpriseStyle )
+ enterpriseStyle = new EnterpriseHeaderStyle();
+ return enterpriseStyle;
+ }
+
+} // namespace KMail
diff --git a/kmail/headerstyle.h b/kmail/headerstyle.h
new file mode 100644
index 00000000..8329f3fd
--- /dev/null
+++ b/kmail/headerstyle.h
@@ -0,0 +1,88 @@
+/* -*- c++ -*-
+ headerstyle.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_HEADERSTYLE_H__
+#define __KMAIL_HEADERSTYLE_H__
+
+class QString;
+class KMMessage;
+
+namespace KMail {
+
+ class HeaderStrategy;
+
+ /** This class encapsulates the visual appearance of message
+ headers. Together with HeaderStrategy, which determines
+ which of the headers present in the message be shown, it is
+ responsible for the formatting of message headers.
+
+ @short Encapsulates visual appearance of message headers.
+ @author Marc Mutz <mutz@kde.org>
+ @see HeaderStrategy
+ **/
+ class HeaderStyle {
+ protected:
+ HeaderStyle();
+ virtual ~HeaderStyle();
+
+ public:
+ //
+ // Factory methods:
+ //
+ enum Type { Brief, Plain, Fancy, Enterprise };
+
+ static const HeaderStyle * create( Type type );
+ static const HeaderStyle * create( const QString & type );
+
+ static const HeaderStyle * brief();
+ static const HeaderStyle * plain();
+ static const HeaderStyle * fancy();
+ static const HeaderStyle * enterprise();
+
+ //
+ // Methods for handling the styles:
+ //
+ virtual const char * name() const = 0;
+ virtual const HeaderStyle * next() const = 0;
+ virtual const HeaderStyle * prev() const = 0;
+
+ //
+ // HeaderStyle interface:
+ //
+ virtual QString format( const KMMessage * message,
+ const KMail::HeaderStrategy * strategy,
+ const QString & vCardName,
+ bool printing=false, bool topLevel = false ) const = 0;
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_HEADERSTYLE_H__
diff --git a/kmail/hi128-app-kmail.png b/kmail/hi128-app-kmail.png
new file mode 100644
index 00000000..e7cc40aa
--- /dev/null
+++ b/kmail/hi128-app-kmail.png
Binary files differ
diff --git a/kmail/hi16-app-kmail.png b/kmail/hi16-app-kmail.png
new file mode 100644
index 00000000..168c136c
--- /dev/null
+++ b/kmail/hi16-app-kmail.png
Binary files differ
diff --git a/kmail/hi22-app-kmail.png b/kmail/hi22-app-kmail.png
new file mode 100644
index 00000000..1d3d6cd6
--- /dev/null
+++ b/kmail/hi22-app-kmail.png
Binary files differ
diff --git a/kmail/hi32-app-kmail.png b/kmail/hi32-app-kmail.png
new file mode 100644
index 00000000..842f672f
--- /dev/null
+++ b/kmail/hi32-app-kmail.png
Binary files differ
diff --git a/kmail/hi48-app-kmail.png b/kmail/hi48-app-kmail.png
new file mode 100644
index 00000000..deee685e
--- /dev/null
+++ b/kmail/hi48-app-kmail.png
Binary files differ
diff --git a/kmail/hi64-app-kmail.png b/kmail/hi64-app-kmail.png
new file mode 100644
index 00000000..287686dc
--- /dev/null
+++ b/kmail/hi64-app-kmail.png
Binary files differ
diff --git a/kmail/hisc-app-kmail.svgz b/kmail/hisc-app-kmail.svgz
new file mode 100644
index 00000000..a7f4e627
--- /dev/null
+++ b/kmail/hisc-app-kmail.svgz
Binary files differ
diff --git a/kmail/htmlstatusbar.cpp b/kmail/htmlstatusbar.cpp
new file mode 100644
index 00000000..831b3665
--- /dev/null
+++ b/kmail/htmlstatusbar.cpp
@@ -0,0 +1,140 @@
+/* -*- c++ -*-
+ htmlstatusbar.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2002 Ingo Kloecker <kloecker@kde.org>
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "htmlstatusbar.h"
+
+#ifndef KMAIL_TESTING
+#include "kmkernel.h"
+#else
+#include <kapplication.h>
+#endif
+
+#include <klocale.h>
+#include <kconfig.h>
+
+#include <qcolor.h>
+#include <qstring.h>
+
+KMail::HtmlStatusBar::HtmlStatusBar( QWidget * parent, const char * name, WFlags f )
+ : QLabel( parent, name, f ),
+ mMode( Normal )
+{
+ setAlignment( AlignHCenter|AlignTop );
+ // Don't force a minimum height to the reader widget
+ setSizePolicy( QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Ignored ) );
+ upd();
+}
+
+KMail::HtmlStatusBar::~HtmlStatusBar() {}
+
+void KMail::HtmlStatusBar::upd() {
+ setEraseColor( bgColor() );
+ setPaletteForegroundColor( fgColor() );
+ setText( message() );
+}
+
+void KMail::HtmlStatusBar::setNormalMode() {
+ setMode( Normal );
+}
+
+void KMail::HtmlStatusBar::setHtmlMode() {
+ setMode( Html );
+}
+
+void KMail::HtmlStatusBar::setNeutralMode() {
+ setMode( Neutral );
+}
+
+void KMail::HtmlStatusBar::setMode( Mode m ) {
+ if ( m == mode() )
+ return;
+ mMode = m;
+ upd();
+}
+
+QString KMail::HtmlStatusBar::message() const {
+ switch ( mode() ) {
+ case Html: // bold: "HTML Message"
+ return i18n( "<qt><b><br>H<br>T<br>M<br>L<br> "
+ "<br>M<br>e<br>s<br>s<br>a<br>g<br>e</b></qt>" );
+ case Normal: // normal: "No HTML Message"
+ return i18n( "<qt><br>N<br>o<br> "
+ "<br>H<br>T<br>M<br>L<br> "
+ "<br>M<br>e<br>s<br>s<br>a<br>g<br>e</qt>" );
+ default:
+ case Neutral:
+ return QString::null;
+ }
+}
+
+namespace {
+ inline KConfig * config() {
+#ifndef KMAIL_TESTING
+ return KMKernel::config();
+#else
+ return kApp->config();
+#endif
+ }
+}
+
+QColor KMail::HtmlStatusBar::fgColor() const {
+ KConfigGroup conf( config(), "Reader" );
+ switch ( mode() ) {
+ case Html:
+ return conf.readColorEntry( "ColorbarForegroundHTML", &Qt::white );
+ case Normal:
+ return conf.readColorEntry( "ColorbarForegroundPlain", &Qt::black );
+ default:
+ case Neutral:
+ return Qt::black;
+ }
+}
+
+QColor KMail::HtmlStatusBar::bgColor() const {
+ KConfigGroup conf( config(), "Reader" );
+
+ switch ( mode() ) {
+ case Html:
+ return conf.readColorEntry( "ColorbarBackgroundHTML", &Qt::black );
+ case Normal:
+ return conf.readColorEntry( "ColorbarBackgroundPlain", &Qt::lightGray );
+ default:
+ case Neutral:
+ return Qt::white;
+ }
+}
+
+#include "htmlstatusbar.moc"
diff --git a/kmail/htmlstatusbar.h b/kmail/htmlstatusbar.h
new file mode 100644
index 00000000..103f053a
--- /dev/null
+++ b/kmail/htmlstatusbar.h
@@ -0,0 +1,100 @@
+/* -*- c++ -*-
+ htmlstatusbar.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2002 Ingo Kloecker <kloecker@kde.org>
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+#ifndef _KMAIL_HTMLSTATUSBAR_H_
+#define _KMAIL_HTMLSTATUSBAR_H_
+
+#include <qlabel.h>
+
+class QString;
+class QColor;
+
+namespace KMail {
+
+ /**
+ * @short The HTML statusbar widget for use with the reader.
+ *
+ * The HTML status bar is a small widget that acts as an indicator
+ * for the message content. It can be in one of two modes:
+ *
+ * <dl>
+ * <dt><code>Normal</code></dt>
+ * <dd>Default. No HTML.</dd>
+ * <dt><code>Neutral</code></dt>
+ * <dd>Temporary value. Used while the real mode is undetermined.</dd>
+ * <dt><code>Html</code></dt>
+ * <dd>HTML content is being shown. Since HTML mails can mimic all sorts
+ * of KMail markup in the reader, this provides out-of-band information
+ * about the presence of (rendered) HTML.</dd>
+ * </dl>
+ *
+ * @author Ingo Kloecker <kloecker@kde.org>, Marc Mutz <mutz@kde.org>
+ **/
+ class HtmlStatusBar : public QLabel {
+ Q_OBJECT
+ public:
+ HtmlStatusBar( QWidget * parent=0, const char * name=0, WFlags f=0 );
+ virtual ~HtmlStatusBar();
+
+ enum Mode {
+ Normal,
+ Html,
+ Neutral
+ };
+
+ /** @return current mode. */
+ Mode mode() const { return mMode ; }
+ bool isHtml() const { return mode() == Html ; }
+ bool isNormal() const { return mode() == Normal ; }
+ bool isNeutral() const { return mode() == Neutral ; }
+
+ public slots:
+ /** Switch to "html mode". */
+ void setHtmlMode();
+ /** Switch to "normal mode". */
+ void setNormalMode();
+ /** Switch to "neutral" mode (currently == normal mode). */
+ void setNeutralMode();
+ /** Switch to mode @p m */
+ void setMode( Mode m );
+
+ private:
+ void upd();
+ QString message() const;
+ QColor bgColor() const;
+ QColor fgColor() const;
+
+ Mode mMode;
+ };
+
+} // namespace KMail
+
+#endif // _KMAIL_HTMLSTATUSBAR_H_
diff --git a/kmail/identitydialog.cpp b/kmail/identitydialog.cpp
new file mode 100644
index 00000000..1283047e
--- /dev/null
+++ b/kmail/identitydialog.cpp
@@ -0,0 +1,700 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ identitydialog.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2002 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "identitydialog.h"
+
+// other KMail headers:
+#include "signatureconfigurator.h"
+#include "xfaceconfigurator.h"
+#include "folderrequester.h"
+using KMail::FolderRequester;
+#include "kmfoldermgr.h"
+#include "transportmanager.h"
+#include "dictionarycombobox.h"
+#include "kleo_util.h"
+#include "kmmainwidget.h"
+#include "kmfolder.h"
+#include "templatesconfiguration.h"
+#include "templatesconfiguration_kfg.h"
+
+// other kdepim headers:
+// libkdepim
+#include <libkpimidentities/identity.h>
+#include <libkdepim/addresseelineedit.h>
+// libkleopatra:
+#include <ui/keyrequester.h>
+#include <kleo/cryptobackendfactory.h>
+
+#include <libemailfunctions/email.h>
+
+// other KDE headers:
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kconfig.h>
+#include <kfileitem.h>
+#include <kurl.h>
+#include <kdebug.h>
+#include <kpushbutton.h>
+
+// Qt headers:
+#include <qtabwidget.h>
+#include <qlabel.h>
+#include <qwhatsthis.h>
+#include <qlayout.h>
+#include <qpushbutton.h>
+#include <qcheckbox.h>
+#include <qcombobox.h>
+
+// other headers:
+#include <gpgmepp/key.h>
+#include <iterator>
+#include <algorithm>
+
+using namespace KPIM;
+
+namespace KMail {
+
+ IdentityDialog::IdentityDialog( QWidget * parent, const char * name )
+ : KDialogBase( Plain, i18n("Edit Identity"), Ok|Cancel|Help, Ok,
+ parent, name )
+ {
+ // tmp. vars:
+ QWidget * tab;
+ QLabel * label;
+ int row;
+ QGridLayout * glay;
+ QString msg;
+
+ //
+ // Tab Widget: General
+ //
+ row = -1;
+ QVBoxLayout * vlay = new QVBoxLayout( plainPage(), 0, spacingHint() );
+ QTabWidget *tabWidget = new QTabWidget( plainPage(), "config-identity-tab" );
+ vlay->addWidget( tabWidget );
+
+ tab = new QWidget( tabWidget );
+ tabWidget->addTab( tab, i18n("&General") );
+ glay = new QGridLayout( tab, 4, 2, marginHint(), spacingHint() );
+ glay->setRowStretch( 3, 1 );
+ glay->setColStretch( 1, 1 );
+
+ // "Name" line edit and label:
+ ++row;
+ mNameEdit = new KLineEdit( tab );
+ glay->addWidget( mNameEdit, row, 1 );
+ label = new QLabel( mNameEdit, i18n("&Your name:"), tab );
+ glay->addWidget( label, row, 0 );
+ msg = i18n("<qt><h3>Your name</h3>"
+ "<p>This field should contain your name as you would like "
+ "it to appear in the email header that is sent out;</p>"
+ "<p>if you leave this blank your real name will not "
+ "appear, only the email address.</p></qt>");
+ QWhatsThis::add( label, msg );
+ QWhatsThis::add( mNameEdit, msg );
+
+ // "Organization" line edit and label:
+ ++row;
+ mOrganizationEdit = new KLineEdit( tab );
+ glay->addWidget( mOrganizationEdit, row, 1 );
+ label = new QLabel( mOrganizationEdit, i18n("Organi&zation:"), tab );
+ glay->addWidget( label, row, 0 );
+ msg = i18n("<qt><h3>Organization</h3>"
+ "<p>This field should have the name of your organization "
+ "if you'd like it to be shown in the email header that "
+ "is sent out.</p>"
+ "<p>It is safe (and normal) to leave this blank.</p></qt>");
+ QWhatsThis::add( label, msg );
+ QWhatsThis::add( mOrganizationEdit, msg );
+
+ // "Email Address" line edit and label:
+ // (row 3: spacer)
+ ++row;
+ mEmailEdit = new KLineEdit( tab );
+ glay->addWidget( mEmailEdit, row, 1 );
+ label = new QLabel( mEmailEdit, i18n("&Email address:"), tab );
+ glay->addWidget( label, row, 0 );
+ msg = i18n("<qt><h3>Email address</h3>"
+ "<p>This field should have your full email address.</p>"
+ "<p>If you leave this blank, or get it wrong, people "
+ "will have trouble replying to you.</p></qt>");
+ QWhatsThis::add( label, msg );
+ QWhatsThis::add( mEmailEdit, msg );
+
+ //
+ // Tab Widget: Cryptography
+ //
+ row = -1;
+ mCryptographyTab = tab = new QWidget( tabWidget );
+ tabWidget->addTab( tab, i18n("Cryptograph&y") );
+ glay = new QGridLayout( tab, 6, 2, marginHint(), spacingHint() );
+ glay->setColStretch( 1, 1 );
+
+ // "OpenPGP Signature Key" requester and label:
+ ++row;
+ mPGPSigningKeyRequester = new Kleo::SigningKeyRequester( false, Kleo::SigningKeyRequester::OpenPGP, tab );
+ mPGPSigningKeyRequester->dialogButton()->setText( i18n("Chang&e...") );
+ mPGPSigningKeyRequester->setDialogCaption( i18n("Your OpenPGP Signature Key") );
+ msg = i18n("Select the OpenPGP key which should be used to "
+ "digitally sign your messages.");
+ mPGPSigningKeyRequester->setDialogMessage( msg );
+
+ msg = i18n("<qt><p>The OpenPGP key you choose here will be used "
+ "to digitally sign messages. You can also use GnuPG keys.</p>"
+ "<p>You can leave this blank, but KMail will not be able "
+ "to digitally sign emails using OpenPGP; "
+ "normal mail functions will not be affected.</p>"
+ "<p>You can find out more about keys at <a>http://www.gnupg.org</a></p></qt>");
+
+ label = new QLabel( mPGPSigningKeyRequester, i18n("OpenPGP signing key:"), tab );
+ QWhatsThis::add( mPGPSigningKeyRequester, msg );
+ QWhatsThis::add( label, msg );
+
+ glay->addWidget( label, row, 0 );
+ glay->addWidget( mPGPSigningKeyRequester, row, 1 );
+
+
+ // "OpenPGP Encryption Key" requester and label:
+ ++row;
+ mPGPEncryptionKeyRequester = new Kleo::EncryptionKeyRequester( false, Kleo::EncryptionKeyRequester::OpenPGP, tab );
+ mPGPEncryptionKeyRequester->dialogButton()->setText( i18n("Chang&e...") );
+ mPGPEncryptionKeyRequester->setDialogCaption( i18n("Your OpenPGP Encryption Key") );
+ msg = i18n("Select the OpenPGP key which should be used when encrypting "
+ "to yourself and for the \"Attach My Public Key\" "
+ "feature in the composer.");
+ mPGPEncryptionKeyRequester->setDialogMessage( msg );
+
+ msg = i18n("<qt><p>The OpenPGP key you choose here will be used "
+ "to encrypt messages to yourself and for the \"Attach My Public Key\" "
+ "feature in the composer. You can also use GnuPG keys.</p>"
+ "<p>You can leave this blank, but KMail will not be able "
+ "to encrypt copies of outgoing messages to you using OpenPGP; "
+ "normal mail functions will not be affected.</p>"
+ "<p>You can find out more about keys at <a>http://www.gnupg.org</a></qt>");
+ label = new QLabel( mPGPEncryptionKeyRequester, i18n("OpenPGP encryption key:"), tab );
+ QWhatsThis::add( mPGPEncryptionKeyRequester, msg );
+ QWhatsThis::add( label, msg );
+
+ glay->addWidget( label, row, 0 );
+ glay->addWidget( mPGPEncryptionKeyRequester, row, 1 );
+
+
+ // "S/MIME Signature Key" requester and label:
+ ++row;
+ mSMIMESigningKeyRequester = new Kleo::SigningKeyRequester( false, Kleo::SigningKeyRequester::SMIME, tab );
+ mSMIMESigningKeyRequester->dialogButton()->setText( i18n("Chang&e...") );
+ mSMIMESigningKeyRequester->setDialogCaption( i18n("Your S/MIME Signature Certificate") );
+ msg = i18n("Select the S/MIME certificate which should be used to "
+ "digitally sign your messages.");
+ mSMIMESigningKeyRequester->setDialogMessage( msg );
+
+ msg = i18n("<qt><p>The S/MIME (X.509) certificate you choose here will be used "
+ "to digitally sign messages.</p>"
+ "<p>You can leave this blank, but KMail will not be able "
+ "to digitally sign emails using S/MIME; "
+ "normal mail functions will not be affected.</p></qt>");
+ label = new QLabel( mSMIMESigningKeyRequester, i18n("S/MIME signing certificate:"), tab );
+ QWhatsThis::add( mSMIMESigningKeyRequester, msg );
+ QWhatsThis::add( label, msg );
+ glay->addWidget( label, row, 0 );
+ glay->addWidget( mSMIMESigningKeyRequester, row, 1 );
+
+ const Kleo::CryptoBackend::Protocol * smimeProtocol
+ = Kleo::CryptoBackendFactory::instance()->smime();
+
+ label->setEnabled( smimeProtocol );
+ mSMIMESigningKeyRequester->setEnabled( smimeProtocol );
+
+ // "S/MIME Encryption Key" requester and label:
+ ++row;
+ mSMIMEEncryptionKeyRequester = new Kleo::EncryptionKeyRequester( false, Kleo::EncryptionKeyRequester::SMIME, tab );
+ mSMIMEEncryptionKeyRequester->dialogButton()->setText( i18n("Chang&e...") );
+ mSMIMEEncryptionKeyRequester->setDialogCaption( i18n("Your S/MIME Encryption Certificate") );
+ msg = i18n("Select the S/MIME certificate which should be used when encrypting "
+ "to yourself and for the \"Attach My Certificate\" "
+ "feature in the composer.");
+ mSMIMEEncryptionKeyRequester->setDialogMessage( msg );
+
+ msg = i18n("<qt><p>The S/MIME certificate you choose here will be used "
+ "to encrypt messages to yourself and for the \"Attach My Certificate\" "
+ "feature in the composer.</p>"
+ "<p>You can leave this blank, but KMail will not be able "
+ "to encrypt copies of outgoing messages to you using S/MIME; "
+ "normal mail functions will not be affected.</p></qt>");
+ label = new QLabel( mSMIMEEncryptionKeyRequester, i18n("S/MIME encryption certificate:"), tab );
+ QWhatsThis::add( mSMIMEEncryptionKeyRequester, msg );
+ QWhatsThis::add( label, msg );
+
+ glay->addWidget( label, row, 0 );
+ glay->addWidget( mSMIMEEncryptionKeyRequester, row, 1 );
+
+ label->setEnabled( smimeProtocol );
+ mSMIMEEncryptionKeyRequester->setEnabled( smimeProtocol );
+
+ // "Preferred Crypto Message Format" combobox and label:
+ ++row;
+ mPreferredCryptoMessageFormat = new QComboBox( false, tab );
+ QStringList l;
+ l << Kleo::cryptoMessageFormatToLabel( Kleo::AutoFormat )
+ << Kleo::cryptoMessageFormatToLabel( Kleo::InlineOpenPGPFormat )
+ << Kleo::cryptoMessageFormatToLabel( Kleo::OpenPGPMIMEFormat )
+ << Kleo::cryptoMessageFormatToLabel( Kleo::SMIMEFormat )
+ << Kleo::cryptoMessageFormatToLabel( Kleo::SMIMEOpaqueFormat );
+ mPreferredCryptoMessageFormat->insertStringList( l );
+ label = new QLabel( mPreferredCryptoMessageFormat,
+ i18n("Preferred crypto message format:"), tab );
+
+ glay->addWidget( label, row, 0 );
+ glay->addWidget( mPreferredCryptoMessageFormat, row, 1 );
+
+ ++row;
+ glay->setRowStretch( row, 1 );
+
+ //
+ // Tab Widget: Advanced
+ //
+ row = -1;
+ tab = new QWidget( tabWidget );
+ tabWidget->addTab( tab, i18n("&Advanced") );
+ glay = new QGridLayout( tab, 8, 2, marginHint(), spacingHint() );
+ // the last (empty) row takes all the remaining space
+ glay->setRowStretch( 8-1, 1 );
+ glay->setColStretch( 1, 1 );
+
+ // "Reply-To Address" line edit and label:
+ ++row;
+ mReplyToEdit = new KPIM::AddresseeLineEdit( tab, true, "mReplyToEdit" );
+ glay->addWidget( mReplyToEdit, row, 1 );
+ label = new QLabel ( mReplyToEdit, i18n("&Reply-To address:"), tab);
+ glay->addWidget( label , row, 0 );
+ msg = i18n("<qt><h3>Reply-To addresses</h3>"
+ "<p>This sets the <tt>Reply-to:</tt> header to contain a "
+ "different email address to the normal <tt>From:</tt> "
+ "address.</p>"
+ "<p>This can be useful when you have a group of people "
+ "working together in similar roles. For example, you "
+ "might want any emails sent to have your email in the "
+ "<tt>From:</tt> field, but any responses to go to "
+ "a group address.</p>"
+ "<p>If in doubt, leave this field blank.</p></qt>");
+ QWhatsThis::add( label, msg );
+ QWhatsThis::add( mReplyToEdit, msg );
+
+ // "BCC addresses" line edit and label:
+ ++row;
+ mBccEdit = new KPIM::AddresseeLineEdit( tab, true, "mBccEdit" );
+ glay->addWidget( mBccEdit, row, 1 );
+ label = new QLabel( mBccEdit, i18n("&BCC addresses:"), tab );
+ glay->addWidget( label, row, 0 );
+ msg = i18n("<qt><h3>BCC (Blind Carbon Copy) addresses</h3>"
+ "<p>The addresses that you enter here will be added to each "
+ "outgoing mail that is sent with this identity. They will not "
+ "be visible to other recipients.</p>"
+ "<p>This is commonly used to send a copy of each sent message to "
+ "another account of yours.</p>"
+ "<p>To specify more than one address, use commas to separate "
+ "the list of BCC recipients.</p>"
+ "<p>If in doubt, leave this field blank.</p></qt>");
+ QWhatsThis::add( label, msg );
+ QWhatsThis::add( mBccEdit, msg );
+
+ // "Dictionary" combo box and label:
+ ++row;
+ mDictionaryCombo = new DictionaryComboBox( tab );
+ glay->addWidget( mDictionaryCombo, row, 1 );
+ glay->addWidget( new QLabel( mDictionaryCombo, i18n("D&ictionary:"), tab ),
+ row, 0 );
+
+ // "Sent-mail Folder" combo box and label:
+ ++row;
+ mFccCombo = new FolderRequester( tab,
+ kmkernel->getKMMainWidget()->folderTree() );
+ mFccCombo->setShowOutbox( false );
+ glay->addWidget( mFccCombo, row, 1 );
+ glay->addWidget( new QLabel( mFccCombo, i18n("Sent-mail &folder:"), tab ),
+ row, 0 );
+
+ // "Drafts Folder" combo box and label:
+ ++row;
+ mDraftsCombo = new FolderRequester( tab,
+ kmkernel->getKMMainWidget()->folderTree() );
+ mDraftsCombo->setShowOutbox( false );
+ glay->addWidget( mDraftsCombo, row, 1 );
+ glay->addWidget( new QLabel( mDraftsCombo, i18n("&Drafts folder:"), tab ),
+ row, 0 );
+
+ // "Templates Folder" combo box and label:
+ ++row;
+ mTemplatesCombo = new FolderRequester( tab,
+ kmkernel->getKMMainWidget()->folderTree() );
+ mTemplatesCombo->setShowOutbox( false );
+ glay->addWidget( mTemplatesCombo, row, 1 );
+ glay->addWidget( new QLabel( mTemplatesCombo, i18n("&Templates folder:"), tab ),
+ row, 0 );
+
+ // "Special transport" combobox and label:
+ ++row;
+ mTransportCheck = new QCheckBox( i18n("Special &transport:"), tab );
+ glay->addWidget( mTransportCheck, row, 0 );
+ mTransportCombo = new QComboBox( true, tab );
+ mTransportCombo->setEnabled( false ); // since !mTransportCheck->isChecked()
+ mTransportCombo->insertStringList( KMail::TransportManager::transportNames() );
+ glay->addWidget( mTransportCombo, row, 1 );
+ connect( mTransportCheck, SIGNAL(toggled(bool)),
+ mTransportCombo, SLOT(setEnabled(bool)) );
+
+ // the last row is a spacer
+
+ //
+ // Tab Widget: Templates
+ //
+ tab = new QWidget( tabWidget );
+ tabWidget->addTab( tab, i18n("&Templates") );
+ vlay = new QVBoxLayout( tab, marginHint(), spacingHint() );
+ mCustom = new QCheckBox( i18n("&Use custom message templates"), tab );
+ vlay->addWidget( mCustom );
+ mWidget = new TemplatesConfiguration( tab , "identity-templates" );
+ mWidget->setEnabled( false );
+ vlay->addWidget( mWidget );
+ QHBoxLayout *btns = new QHBoxLayout( vlay, spacingHint() );
+ mCopyGlobal = new KPushButton( i18n("&Copy global templates"), tab );
+ mCopyGlobal->setEnabled( false );
+ btns->addWidget( mCopyGlobal );
+ connect( mCustom, SIGNAL( toggled( bool ) ),
+ mWidget, SLOT( setEnabled( bool ) ) );
+ connect( mCustom, SIGNAL( toggled( bool ) ),
+ mCopyGlobal, SLOT( setEnabled( bool ) ) );
+ connect( mCopyGlobal, SIGNAL(clicked()),
+ this, SLOT(slotCopyGlobal()) );
+
+ //
+ // Tab Widget: Signature
+ //
+ mSignatureConfigurator = new SignatureConfigurator( tabWidget );
+ mSignatureConfigurator->layout()->setMargin( KDialog::marginHint() );
+ tabWidget->addTab( mSignatureConfigurator, i18n("&Signature") );
+
+ mXFaceConfigurator = new XFaceConfigurator( tabWidget );
+ mXFaceConfigurator->layout()->setMargin( KDialog::marginHint() );
+ tabWidget->addTab( mXFaceConfigurator, i18n("&Picture") );
+
+ KConfigGroup geometry( KMKernel::config(), "Geometry" );
+ if ( geometry.hasKey( "Identity Dialog size" ) )
+ resize( geometry.readSizeEntry( "Identity Dialog size" ) );
+ mNameEdit->setFocus();
+
+ connect( tabWidget, SIGNAL(currentChanged(QWidget*)),
+ SLOT(slotAboutToShow(QWidget*)) );
+ }
+
+ IdentityDialog::~IdentityDialog() {
+ KConfigGroup geometry( KMKernel::config(), "Geometry" );
+ geometry.writeEntry( "Identity Dialog size", size() );
+ }
+
+ void IdentityDialog::slotAboutToShow( QWidget * w ) {
+ if ( w == mCryptographyTab ) {
+ // set the configured email address as inital query of the key
+ // requesters:
+ const QString email = mEmailEdit->text().stripWhiteSpace();
+ mPGPEncryptionKeyRequester->setInitialQuery( email );
+ mPGPSigningKeyRequester->setInitialQuery( email );
+ mSMIMEEncryptionKeyRequester->setInitialQuery( email );
+ mSMIMESigningKeyRequester->setInitialQuery( email );
+ }
+ }
+
+ void IdentityDialog::slotCopyGlobal() {
+ mWidget->loadFromGlobal();
+ }
+
+ namespace {
+ struct DoesntMatchEMailAddress {
+ explicit DoesntMatchEMailAddress( const QString & s )
+ : email( s.stripWhiteSpace().lower() ) {}
+ bool operator()( const GpgME::Key & key ) const;
+ private:
+ bool checkForEmail( const char * email ) const;
+ static QString extractEmail( const char * email );
+ const QString email;
+ };
+
+ bool DoesntMatchEMailAddress::operator()( const GpgME::Key & key ) const {
+ const std::vector<GpgME::UserID> uids = key.userIDs();
+ for ( std::vector<GpgME::UserID>::const_iterator it = uids.begin() ; it != uids.end() ; ++it )
+ if ( checkForEmail( it->email() ? it->email() : it->id() ) )
+ return false;
+ return true; // note the negation!
+ }
+
+ bool DoesntMatchEMailAddress::checkForEmail( const char * e ) const {
+ const QString em = extractEmail( e );
+ return !em.isEmpty() && email == em;
+ }
+
+ QString DoesntMatchEMailAddress::extractEmail( const char * e ) {
+ if ( !e || !*e )
+ return QString::null;
+ const QString em = QString::fromUtf8( e );
+ if ( e[0] == '<' )
+ return em.mid( 1, em.length() - 2 );
+ else
+ return em;
+ }
+ }
+
+bool IdentityDialog::validateAddresses( const QString & addresses )
+{
+ QString brokenAddress;
+ KPIM::EmailParseResult errorCode = KMMessage::isValidEmailAddressList( KMMessage::expandAliases( addresses ), brokenAddress );
+ if ( !( errorCode == KPIM::AddressOk || errorCode == KPIM::AddressEmpty ) ) {
+ QString errorMsg( "<qt><p><b>" + brokenAddress +
+ "</b></p><p>" + KPIM::emailParseResultToString( errorCode ) +
+ "</p></qt>" );
+ KMessageBox::sorry( this, errorMsg, i18n("Invalid Email Address") );
+ return false;
+ }
+ return true;
+}
+
+void IdentityDialog::slotOk() {
+ const QString email = mEmailEdit->text().stripWhiteSpace();
+
+ // Validate email addresses
+ if ( !isValidSimpleEmailAddress( email )) {
+ QString errorMsg( simpleEmailAddressErrorMsg());
+ KMessageBox::sorry( this, errorMsg, i18n("Invalid Email Address") );
+ return;
+ }
+
+ if ( !validateAddresses( mReplyToEdit->text().stripWhiteSpace() ) ) {
+ return;
+ }
+
+ if ( !validateAddresses( mBccEdit->text().stripWhiteSpace() ) ) {
+ return;
+ }
+
+ const std::vector<GpgME::Key> & pgpSigningKeys = mPGPSigningKeyRequester->keys();
+ const std::vector<GpgME::Key> & pgpEncryptionKeys = mPGPEncryptionKeyRequester->keys();
+ const std::vector<GpgME::Key> & smimeSigningKeys = mSMIMESigningKeyRequester->keys();
+ const std::vector<GpgME::Key> & smimeEncryptionKeys = mSMIMEEncryptionKeyRequester->keys();
+ QString msg;
+ bool err = false;
+ if ( std::find_if( pgpSigningKeys.begin(), pgpSigningKeys.end(),
+ DoesntMatchEMailAddress( email ) ) != pgpSigningKeys.end() ) {
+ msg = i18n("One of the configured OpenPGP signing keys does not contain "
+ "any user ID with the configured email address for this "
+ "identity (%1).\n"
+ "This might result in warning messages on the receiving side "
+ "when trying to verify signatures made with this configuration.");
+ err = true;
+ }
+ else if ( std::find_if( pgpEncryptionKeys.begin(), pgpEncryptionKeys.end(),
+ DoesntMatchEMailAddress( email ) ) != pgpEncryptionKeys.end() ) {
+ msg = i18n("One of the configured OpenPGP encryption keys does not contain "
+ "any user ID with the configured email address for this "
+ "identity (%1).");
+ err = true;
+ }
+ else if ( std::find_if( smimeSigningKeys.begin(), smimeSigningKeys.end(),
+ DoesntMatchEMailAddress( email ) ) != smimeSigningKeys.end() ) {
+ msg = i18n("One of the configured S/MIME signing certificates does not contain "
+ "the configured email address for this "
+ "identity (%1).\n"
+ "This might result in warning messages on the receiving side "
+ "when trying to verify signatures made with this configuration.");
+ err = true;
+ }
+ else if ( std::find_if( smimeEncryptionKeys.begin(), smimeEncryptionKeys.end(),
+ DoesntMatchEMailAddress( email ) ) != smimeEncryptionKeys.end() ) {
+ msg = i18n("One of the configured S/MIME encryption certificates does not contain "
+ "the configured email address for this "
+ "identity (%1).");
+ err = true;
+ }
+
+ if ( err )
+ if ( KMessageBox::warningContinueCancel( this, msg.arg( email ),
+ i18n("Email Address Not Found in Key/Certificates"),
+ KStdGuiItem::cont(), "warn_email_not_in_certificate" )
+ != KMessageBox::Continue)
+ return;
+
+
+ if ( mSignatureConfigurator->isSignatureEnabled() &&
+ mSignatureConfigurator->signatureType()==Signature::FromFile ) {
+ KURL url( mSignatureConfigurator->fileURL() );
+ KFileItem signatureFile( KFileItem::Unknown, KFileItem::Unknown, url );
+ if ( !signatureFile.isFile() || !signatureFile.isReadable() || !signatureFile.isLocalFile() ) {
+ KMessageBox::error( this, i18n( "The signature file is not valid" ) );
+ return;
+ }
+ }
+
+ return KDialogBase::slotOk();
+ }
+
+ bool IdentityDialog::checkFolderExists( const QString & folderID,
+ const QString & msg ) {
+ KMFolder * folder = kmkernel->findFolderById( folderID );
+ if ( !folder ) {
+ KMessageBox::sorry( this, msg );
+ return false;
+ }
+ return true;
+ }
+
+ void IdentityDialog::setIdentity( KPIM::Identity & ident ) {
+
+ setCaption( i18n("Edit Identity \"%1\"").arg( ident.identityName() ) );
+
+ // "General" tab:
+ mNameEdit->setText( ident.fullName() );
+ mOrganizationEdit->setText( ident.organization() );
+ mEmailEdit->setText( ident.emailAddr() );
+
+ // "Cryptography" tab:
+ mPGPSigningKeyRequester->setFingerprint( ident.pgpSigningKey() );
+ mPGPEncryptionKeyRequester->setFingerprint( ident.pgpEncryptionKey() );
+ mSMIMESigningKeyRequester->setFingerprint( ident.smimeSigningKey() );
+ mSMIMEEncryptionKeyRequester->setFingerprint( ident.smimeEncryptionKey() );
+ mPreferredCryptoMessageFormat->setCurrentItem( format2cb( ident.preferredCryptoMessageFormat() ) );
+
+ // "Advanced" tab:
+ mReplyToEdit->setText( ident.replyToAddr() );
+ mBccEdit->setText( ident.bcc() );
+ mTransportCheck->setChecked( !ident.transport().isEmpty() );
+ mTransportCombo->setEditText( ident.transport() );
+ mTransportCombo->setEnabled( !ident.transport().isEmpty() );
+ mDictionaryCombo->setCurrentByDictionary( ident.dictionary() );
+
+ if ( ident.fcc().isEmpty() ||
+ !checkFolderExists( ident.fcc(),
+ i18n("The custom sent-mail folder for identity "
+ "\"%1\" does not exist (anymore); "
+ "therefore, the default sent-mail folder "
+ "will be used.")
+ .arg( ident.identityName() ) ) )
+ mFccCombo->setFolder( kmkernel->sentFolder() );
+ else
+ mFccCombo->setFolder( ident.fcc() );
+
+ if ( ident.drafts().isEmpty() ||
+ !checkFolderExists( ident.drafts(),
+ i18n("The custom drafts folder for identity "
+ "\"%1\" does not exist (anymore); "
+ "therefore, the default drafts folder "
+ "will be used.")
+ .arg( ident.identityName() ) ) )
+ mDraftsCombo->setFolder( kmkernel->draftsFolder() );
+ else
+ mDraftsCombo->setFolder( ident.drafts() );
+
+ if ( ident.templates().isEmpty() ||
+ !checkFolderExists( ident.templates(),
+ i18n("The custom templates folder for identity "
+ "\"%1\" does not exist (anymore); "
+ "therefore, the default templates folder "
+ "will be used.")
+ .arg( ident.identityName() ) ) )
+ mTemplatesCombo->setFolder( kmkernel->templatesFolder() );
+ else
+ mTemplatesCombo->setFolder( ident.templates() );
+
+ // "Templates" tab:
+ uint identity = ident.uoid();
+ QString iid = QString("IDENTITY_%1").arg( identity );
+ Templates t( iid );
+ mCustom->setChecked(t.useCustomTemplates());
+ mWidget->loadFromIdentity( identity );
+
+ // "Signature" tab:
+ mSignatureConfigurator->setSignature( ident.signature() );
+ mXFaceConfigurator->setXFace( ident.xface() );
+ mXFaceConfigurator->setXFaceEnabled( ident.isXFaceEnabled() );
+ }
+
+ void IdentityDialog::updateIdentity( KPIM::Identity & ident ) {
+ // "General" tab:
+ ident.setFullName( mNameEdit->text() );
+ ident.setOrganization( mOrganizationEdit->text() );
+ QString email = mEmailEdit->text();
+ ident.setEmailAddr( email );
+ // "Cryptography" tab:
+ ident.setPGPSigningKey( mPGPSigningKeyRequester->fingerprint().latin1() );
+ ident.setPGPEncryptionKey( mPGPEncryptionKeyRequester->fingerprint().latin1() );
+ ident.setSMIMESigningKey( mSMIMESigningKeyRequester->fingerprint().latin1() );
+ ident.setSMIMEEncryptionKey( mSMIMEEncryptionKeyRequester->fingerprint().latin1() );
+ ident.setPreferredCryptoMessageFormat( cb2format( mPreferredCryptoMessageFormat->currentItem() ) );
+ // "Advanced" tab:
+ ident.setReplyToAddr( mReplyToEdit->text() );
+ ident.setBcc( mBccEdit->text() );
+ ident.setTransport( ( mTransportCheck->isChecked() ) ?
+ mTransportCombo->currentText() : QString::null );
+ ident.setDictionary( mDictionaryCombo->currentDictionary() );
+ ident.setFcc( mFccCombo->folder() ?
+ mFccCombo->folder()->idString() : QString::null );
+ ident.setDrafts( mDraftsCombo->folder() ?
+ mDraftsCombo->folder()->idString() : QString::null );
+ ident.setTemplates( mTemplatesCombo->folder() ?
+ mTemplatesCombo->folder()->idString() : QString::null );
+ // "Templates" tab:
+ uint identity = ident.uoid();
+ QString iid = QString("IDENTITY_%1").arg( identity );
+ Templates t( iid );
+ kdDebug() << "use custom templates for identity " << identity << ": " << mCustom->isChecked() << endl;
+ t.setUseCustomTemplates(mCustom->isChecked());
+ t.writeConfig();
+ mWidget->saveToIdentity( identity );
+ // "Signature" tab:
+ ident.setSignature( mSignatureConfigurator->signature() );
+ ident.setXFace( mXFaceConfigurator->xface() );
+ ident.setXFaceEnabled( mXFaceConfigurator->isXFaceEnabled() );
+ }
+
+ void IdentityDialog::slotUpdateTransportCombo( const QStringList & sl ) {
+ // save old setting:
+ QString content = mTransportCombo->currentText();
+ // update combo box:
+ mTransportCombo->clear();
+ mTransportCombo->insertStringList( sl );
+ // restore saved setting:
+ mTransportCombo->setEditText( content );
+ }
+
+}
+
+#include "identitydialog.moc"
diff --git a/kmail/identitydialog.h b/kmail/identitydialog.h
new file mode 100644
index 00000000..b43f3ebe
--- /dev/null
+++ b/kmail/identitydialog.h
@@ -0,0 +1,118 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ identitydialog.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2002 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_IDENTITYDIALOG_H__
+#define __KMAIL_IDENTITYDIALOG_H__
+
+#include <kdialogbase.h>
+
+
+class QLineEdit;
+class QCheckBox;
+class QComboBox;
+class QString;
+class QStringList;
+class TemplatesConfiguration;
+class KPushButton;
+namespace Kleo {
+ class EncryptionKeyRequester;
+ class SigningKeyRequester;
+}
+namespace KPIM {
+ class Identity;
+}
+namespace KMail {
+ class SignatureConfigurator;
+ class XFaceConfigurator;
+ class DictionaryComboBox;
+ class FolderRequester;
+}
+
+namespace KMail {
+
+ class IdentityDialog : public KDialogBase {
+ Q_OBJECT
+ public:
+ IdentityDialog( QWidget * parent=0, const char * name = 0 );
+ virtual ~IdentityDialog();
+
+ void setIdentity( /*_not_ const*/ KPIM::Identity & ident );
+
+ void updateIdentity( KPIM::Identity & ident );
+
+ public slots:
+ void slotUpdateTransportCombo( const QStringList & sl );
+
+ protected slots:
+ void slotAboutToShow( QWidget * w );
+ /*! \reimp */
+ void slotOk();
+ // copy default templates to identity templates
+ void slotCopyGlobal();
+
+ private:
+ bool checkFolderExists( const QString & folder, const QString & msg );
+ bool validateAddresses( const QString & addresses );
+
+ protected:
+ // "general" tab:
+ QLineEdit *mNameEdit;
+ QLineEdit *mOrganizationEdit;
+ QLineEdit *mEmailEdit;
+ // "cryptography" tab:
+ QWidget *mCryptographyTab;
+ Kleo::SigningKeyRequester *mPGPSigningKeyRequester;
+ Kleo::EncryptionKeyRequester *mPGPEncryptionKeyRequester;
+ Kleo::SigningKeyRequester *mSMIMESigningKeyRequester;
+ Kleo::EncryptionKeyRequester *mSMIMEEncryptionKeyRequester;
+ QComboBox *mPreferredCryptoMessageFormat;
+ // "advanced" tab:
+ QLineEdit *mReplyToEdit;
+ QLineEdit *mBccEdit;
+ KMail::DictionaryComboBox *mDictionaryCombo;
+ FolderRequester *mFccCombo;
+ FolderRequester *mDraftsCombo;
+ FolderRequester *mTemplatesCombo;
+ QCheckBox *mTransportCheck;
+ QComboBox *mTransportCombo; // should be a KMTransportCombo...
+ // "templates" tab:
+ TemplatesConfiguration *mWidget;
+ QCheckBox *mCustom;
+ KPushButton *mCopyGlobal;
+ // "signature" tab:
+ KMail::SignatureConfigurator *mSignatureConfigurator;
+ // "X-Face" tab:
+ KMail::XFaceConfigurator *mXFaceConfigurator;
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_IDENTITYDIALOG_H__
diff --git a/kmail/identitydrag.cpp b/kmail/identitydrag.cpp
new file mode 100644
index 00000000..5116dd44
--- /dev/null
+++ b/kmail/identitydrag.cpp
@@ -0,0 +1,86 @@
+/* -*- c++ -*-
+ identitydrag.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2002 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "identitydrag.h"
+
+
+namespace KMail {
+
+ static const char kmailIdentityMimeType[] = "application/x-kmail-identity-drag";
+
+ IdentityDrag::IdentityDrag( const KPIM::Identity & ident,
+ QWidget * dragSource, const char * name )
+ : QDragObject( dragSource, name ), mIdent( ident )
+ {
+
+ }
+
+ const char * IdentityDrag::format( int i ) const {
+ if ( i == 0 )
+ return kmailIdentityMimeType;
+ else
+ return 0;
+ }
+
+ QByteArray IdentityDrag::encodedData( const char * mimetype ) const {
+ QByteArray a;
+
+ if ( !qstrcmp( mimetype, kmailIdentityMimeType ) ) {
+ QDataStream s( a, IO_WriteOnly );
+ s << mIdent;
+ }
+
+ return a;
+ }
+
+ bool IdentityDrag::canDecode( const QMimeSource * e ) {
+ // ### feel free to add vCard and other stuff here and in decode...
+ return e->provides( kmailIdentityMimeType );
+ }
+
+ bool IdentityDrag::decode( const QMimeSource * e, KPIM::Identity & i ) {
+
+ if ( e->provides( kmailIdentityMimeType ) ) {
+ QDataStream s( e->encodedData( kmailIdentityMimeType ), IO_ReadOnly );
+ s >> i;
+ return true;
+ }
+
+ return false;
+ }
+
+}
+
+#include "identitydrag.moc"
diff --git a/kmail/identitydrag.h b/kmail/identitydrag.h
new file mode 100644
index 00000000..b9b8b831
--- /dev/null
+++ b/kmail/identitydrag.h
@@ -0,0 +1,65 @@
+/* -*- c++ -*-
+ identitydrag.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2002 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_IDENTITYDRAG_H__
+#define __KMAIL_IDENTITYDRAG_H__
+
+#include <libkpimidentities/identity.h>
+
+#include <qdragobject.h> // is a qobject and a qmimesource
+
+namespace KMail {
+
+ /** @short A QDragObject for KPIM::Identity
+ @author Marc Mutz <mutz@kde.org>
+ **/
+ class IdentityDrag : public QDragObject {
+ Q_OBJECT
+ public:
+ IdentityDrag( const KPIM::Identity & ident,
+ QWidget * dragSource=0, const char * name=0 );
+
+ public:
+ virtual ~IdentityDrag() {}
+
+ const char * format( int i ) const; // reimp. QMimeSource
+ QByteArray encodedData( const char * mimetype ) const; // dto.
+
+ static bool canDecode( const QMimeSource * e );
+ static bool decode( const QMimeSource * e, KPIM::Identity & ident );
+
+ protected:
+ KPIM::Identity mIdent;
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_IDENTITYDRAG_H__
diff --git a/kmail/identitylistview.cpp b/kmail/identitylistview.cpp
new file mode 100644
index 00000000..9666a38d
--- /dev/null
+++ b/kmail/identitylistview.cpp
@@ -0,0 +1,146 @@
+/* -*- c++ -*-
+ identitylistview.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2002 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "identitylistview.h"
+
+#include "identitydrag.h"
+#include <libkpimidentities/identitymanager.h>
+#include "kmkernel.h"
+
+#include <klocale.h> // i18n
+#include <kiconloader.h> // SmallIcon
+
+#include <cassert>
+
+namespace KMail {
+
+ //
+ //
+ // IdentityListViewItem
+ //
+ //
+
+ IdentityListViewItem::IdentityListViewItem( IdentityListView * parent, const KPIM::Identity & ident )
+ : KListViewItem( parent ), mUOID( ident.uoid() ) {
+ init( ident );
+ }
+
+ IdentityListViewItem::IdentityListViewItem( IdentityListView * parent, QListViewItem * after, const KPIM::Identity & ident )
+ : KListViewItem( parent, after ), mUOID( ident.uoid() ) {
+ init( ident );
+ }
+
+ KPIM::Identity & IdentityListViewItem::identity() const {
+ KPIM::IdentityManager * im = kmkernel->identityManager();
+ assert( im );
+ return im->modifyIdentityForUoid( uoid() );
+ }
+
+ void IdentityListViewItem::setIdentity( const KPIM::Identity & ident ) {
+ mUOID = ident.uoid();
+ init( ident );
+ }
+
+ void IdentityListViewItem::redisplay() {
+ init( identity() );
+ }
+
+ void IdentityListViewItem::init( const KPIM::Identity & ident ) {
+ if ( ident.isDefault() )
+ // Add "(Default)" to the end of the default identity's name:
+ setText( 0, i18n("%1: identity name. Used in the config "
+ "dialog, section Identity, to indicate the "
+ "default identity", "%1 (Default)")
+ .arg( ident.identityName() ) );
+ else
+ setText( 0, ident.identityName() );
+ setText( 1, ident.fullEmailAddr() );
+ }
+
+ //
+ //
+ // IdentityListView
+ //
+ //
+
+ IdentityListView::IdentityListView( QWidget * parent, const char * name )
+ : KListView( parent, name )
+ {
+ setFullWidth( true );
+ setDragEnabled( true );
+ setAcceptDrops( true );
+ setDropVisualizer( true );
+ addColumn( i18n("Identity Name") );
+ addColumn( i18n("Email Address") );
+ setRootIsDecorated( false );
+ setRenameable( 0 );
+ setItemsRenameable( true );
+ // setShowToolTips( true );
+ setItemsMovable( false );
+ setAllColumnsShowFocus( true );
+ setSorting( -1 ); // disabled
+ setSelectionModeExt( Single ); // ### Extended would be nicer...
+ }
+
+ void IdentityListView::rename( QListViewItem * i, int col ) {
+ if ( col == 0 && isRenameable( col ) ) {
+ IdentityListViewItem * item = dynamic_cast<IdentityListViewItem*>( i );
+ if ( item ) {
+ KPIM::Identity & ident = item->identity();
+ if ( ident.isDefault() )
+ item->setText( 0, ident.identityName() );
+ }
+ }
+ KListView::rename( i, col );
+ }
+
+ bool IdentityListView::acceptDrag( QDropEvent * e ) const {
+ // disallow moving:
+ return e->source() != viewport() && IdentityDrag::canDecode( e );
+ }
+
+ QDragObject * IdentityListView::dragObject() {
+ IdentityListViewItem * item = dynamic_cast<IdentityListViewItem*>( currentItem() );
+ if ( !item ) return 0;
+
+ IdentityDrag * drag = new IdentityDrag( item->identity(), viewport() );
+ drag->setPixmap( SmallIcon("identity") );
+ return drag;
+ }
+
+} // namespace KMail
+
+
+#include "identitylistview.moc"
diff --git a/kmail/identitylistview.h b/kmail/identitylistview.h
new file mode 100644
index 00000000..7269237e
--- /dev/null
+++ b/kmail/identitylistview.h
@@ -0,0 +1,86 @@
+/* -*- c++ -*-
+ identitylistview.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2002 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_IDENTITYLIST_H__
+#define __KMAIL_IDENTITYLIST_H__
+
+#include <klistview.h>
+
+namespace KPIM { class Identity; }
+class QDropEvent;
+class QDragEvent;
+
+namespace KMail {
+
+ class IdentityListView;
+
+ /** @short A QListViewItem for use in IdentityListView
+ @author Marc Mutz <mutz@kde.org>
+ **/
+ class IdentityListViewItem : public KListViewItem {
+ public:
+ IdentityListViewItem( IdentityListView * parent,
+ const KPIM::Identity & ident );
+ IdentityListViewItem( IdentityListView * parent, QListViewItem * after,
+ const KPIM::Identity & ident );
+
+ uint uoid() const { return mUOID; }
+ KPIM::Identity & identity() const;
+ virtual void setIdentity( const KPIM::Identity & ident );
+ void redisplay();
+ private:
+ void init( const KPIM::Identity & ident );
+
+ protected:
+ uint mUOID;
+ };
+
+ /** @short A listview for KPIM::Identity
+ @author Marc Mutz <mutz@kde.org>
+ **/
+ class IdentityListView : public KListView {
+ Q_OBJECT
+ public:
+ IdentityListView( QWidget * parent=0, const char * name=0 );
+ virtual ~IdentityListView() {}
+
+ public slots:
+ void rename( QListViewItem *, int );
+
+ protected:
+ bool acceptDrag( QDropEvent * ) const;
+ QDragObject * dragObject();
+ };
+
+
+} // namespace KMail
+
+#endif // __KMAIL_IDENTITYLISTVIEW_H__
diff --git a/kmail/imapaccountbase.cpp b/kmail/imapaccountbase.cpp
new file mode 100644
index 00000000..9a04c3fd
--- /dev/null
+++ b/kmail/imapaccountbase.cpp
@@ -0,0 +1,1446 @@
+/** -*- c++ -*-
+ * imapaccountbase.cpp
+ *
+ * Copyright (c) 2000-2002 Michael Haeckel <haeckel@kde.org>
+ * Copyright (c) 2002 Marc Mutz <mutz@kde.org>
+ *
+ * This file is based on work on pop3 and imap account implementations
+ * by Don Sanders <sanders@kde.org> and Michael Haeckel <haeckel@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "imapaccountbase.h"
+using KMail::SieveConfig;
+
+#include "accountmanager.h"
+using KMail::AccountManager;
+#include "kmfolder.h"
+#include "broadcaststatus.h"
+using KPIM::BroadcastStatus;
+#include "kmmainwin.h"
+#include "kmfolderimap.h"
+#include "kmmainwidget.h"
+#include "kmmainwin.h"
+#include "kmmsgpart.h"
+#include "acljobs.h"
+#include "kmfoldercachedimap.h"
+#include "bodyvisitor.h"
+using KMail::BodyVisitor;
+#include "imapjob.h"
+using KMail::ImapJob;
+#include "protocols.h"
+#include "progressmanager.h"
+using KPIM::ProgressManager;
+#include "kmfoldermgr.h"
+#include "listjob.h"
+
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kconfig.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+using KIO::MetaData;
+#include <kio/passdlg.h>
+using KIO::PasswordDialog;
+#include <kio/scheduler.h>
+#include <kio/slave.h>
+#include <mimelib/bodypart.h>
+#include <mimelib/body.h>
+#include <mimelib/headers.h>
+#include <mimelib/message.h>
+//using KIO::Scheduler; // use FQN below
+
+#include <qregexp.h>
+#include <qstylesheet.h>
+
+namespace KMail {
+
+ static const unsigned short int imapDefaultPort = 143;
+
+ //
+ //
+ // Ctor and Dtor
+ //
+ //
+
+ ImapAccountBase::ImapAccountBase( AccountManager * parent, const QString & name, uint id )
+ : NetworkAccount( parent, name, id ),
+ mIdleTimer( 0, "mIdleTimer" ),
+ mNoopTimer( 0, "mNoopTimer" ),
+ mTotal( 0 ),
+ mCountUnread( 0 ),
+ mCountLastUnread( 0 ),
+ mAutoExpunge( true ),
+ mHiddenFolders( false ),
+ mOnlySubscribedFolders( false ),
+ mOnlyLocallySubscribedFolders( false ),
+ mLoadOnDemand( true ),
+ mListOnlyOpenFolders( false ),
+ mProgressEnabled( false ),
+ mErrorDialogIsActive( false ),
+ mPasswordDialogIsActive( false ),
+ mACLSupport( true ),
+ mAnnotationSupport( true ),
+ mQuotaSupport( true ),
+ mSlaveConnected( false ),
+ mSlaveConnectionError( false ),
+ mCheckingSingleFolder( false ),
+ mListDirProgressItem( 0 )
+ {
+ mPort = imapDefaultPort;
+ mBodyPartList.setAutoDelete(true);
+ KIO::Scheduler::connect(SIGNAL(slaveError(KIO::Slave *, int, const QString &)),
+ this, SLOT(slotSchedulerSlaveError(KIO::Slave *, int, const QString &)));
+ KIO::Scheduler::connect(SIGNAL(slaveConnected(KIO::Slave *)),
+ this, SLOT(slotSchedulerSlaveConnected(KIO::Slave *)));
+ connect(&mNoopTimer, SIGNAL(timeout()), SLOT(slotNoopTimeout()));
+ connect(&mIdleTimer, SIGNAL(timeout()), SLOT(slotIdleTimeout()));
+ }
+
+ ImapAccountBase::~ImapAccountBase() {
+ kdWarning( mSlave, 5006 )
+ << "slave should have been destroyed by subclass!" << endl;
+ }
+
+ void ImapAccountBase::init() {
+ mAutoExpunge = true;
+ mHiddenFolders = false;
+ mOnlySubscribedFolders = false;
+ mOnlyLocallySubscribedFolders = false;
+ mLoadOnDemand = true;
+ mListOnlyOpenFolders = false;
+ mProgressEnabled = false;
+ }
+
+ void ImapAccountBase::pseudoAssign( const KMAccount * a ) {
+ NetworkAccount::pseudoAssign( a );
+
+ const ImapAccountBase * i = dynamic_cast<const ImapAccountBase*>( a );
+ if ( !i ) return;
+
+ setAutoExpunge( i->autoExpunge() );
+ setHiddenFolders( i->hiddenFolders() );
+ setOnlySubscribedFolders( i->onlySubscribedFolders() );
+ setOnlyLocallySubscribedFolders( i->onlyLocallySubscribedFolders() );
+ setLoadOnDemand( i->loadOnDemand() );
+ setListOnlyOpenFolders( i->listOnlyOpenFolders() );
+ setNamespaces( i->namespaces() );
+ setNamespaceToDelimiter( i->namespaceToDelimiter() );
+ localBlacklistFromStringList( i->locallyBlacklistedFolders() );
+ }
+
+ unsigned short int ImapAccountBase::defaultPort() const {
+ return imapDefaultPort;
+ }
+
+ QString ImapAccountBase::protocol() const {
+ return useSSL() ? IMAP_SSL_PROTOCOL : IMAP_PROTOCOL;
+ }
+
+ //
+ //
+ // Getters and Setters
+ //
+ //
+
+ void ImapAccountBase::setAutoExpunge( bool expunge ) {
+ mAutoExpunge = expunge;
+ }
+
+ void ImapAccountBase::setHiddenFolders( bool show ) {
+ mHiddenFolders = show;
+ }
+
+ void ImapAccountBase::setOnlySubscribedFolders( bool show ) {
+ mOnlySubscribedFolders = show;
+ }
+
+ void ImapAccountBase::setOnlyLocallySubscribedFolders( bool show ) {
+ mOnlyLocallySubscribedFolders = show;
+ }
+
+ void ImapAccountBase::setLoadOnDemand( bool load ) {
+ mLoadOnDemand = load;
+ }
+
+ void ImapAccountBase::setListOnlyOpenFolders( bool only ) {
+ mListOnlyOpenFolders = only;
+ }
+
+ //
+ //
+ // read/write config
+ //
+ //
+
+ void ImapAccountBase::readConfig( /*const*/ KConfig/*Base*/ & config ) {
+ NetworkAccount::readConfig( config );
+
+ setAutoExpunge( config.readBoolEntry( "auto-expunge", false ) );
+ setHiddenFolders( config.readBoolEntry( "hidden-folders", false ) );
+ setOnlySubscribedFolders( config.readBoolEntry( "subscribed-folders", false ) );
+ setOnlyLocallySubscribedFolders( config.readBoolEntry( "locally-subscribed-folders", false ) );
+ setLoadOnDemand( config.readBoolEntry( "loadondemand", false ) );
+ setListOnlyOpenFolders( config.readBoolEntry( "listOnlyOpenFolders", false ) );
+ // read namespaces
+ nsMap map;
+ QStringList list = config.readListEntry( QString::number( PersonalNS ) );
+ if ( !list.isEmpty() )
+ map[PersonalNS] = list.gres( "\"", "" );
+ list = config.readListEntry( QString::number( OtherUsersNS ) );
+ if ( !list.isEmpty() )
+ map[OtherUsersNS] = list.gres( "\"", "" );
+ list = config.readListEntry( QString::number( SharedNS ) );
+ if ( !list.isEmpty() )
+ map[SharedNS] = list.gres( "\"", "" );
+ setNamespaces( map );
+ // read namespace - delimiter
+ namespaceDelim entries = config.entryMap( config.group() );
+ namespaceDelim namespaceToDelimiter;
+ for ( namespaceDelim::ConstIterator it = entries.begin();
+ it != entries.end(); ++it ) {
+ if ( it.key().startsWith( "Namespace:" ) ) {
+ QString key = it.key().right( it.key().length() - 10 );
+ namespaceToDelimiter[key] = it.data();
+ }
+ }
+ setNamespaceToDelimiter( namespaceToDelimiter );
+ mOldPrefix = config.readEntry( "prefix" );
+ if ( !mOldPrefix.isEmpty() ) {
+ makeConnection();
+ }
+ localBlacklistFromStringList( config.readListEntry( "locallyUnsubscribedFolders" ) );
+ }
+
+ void ImapAccountBase::writeConfig( KConfig/*Base*/ & config ) /*const*/ {
+ NetworkAccount::writeConfig( config );
+
+ config.writeEntry( "auto-expunge", autoExpunge() );
+ config.writeEntry( "hidden-folders", hiddenFolders() );
+ config.writeEntry( "subscribed-folders", onlySubscribedFolders() );
+ config.writeEntry( "locally-subscribed-folders", onlyLocallySubscribedFolders() );
+ config.writeEntry( "loadondemand", loadOnDemand() );
+ config.writeEntry( "listOnlyOpenFolders", listOnlyOpenFolders() );
+ QString data;
+ for ( nsMap::Iterator it = mNamespaces.begin(); it != mNamespaces.end(); ++it ) {
+ if ( !it.data().isEmpty() ) {
+ data = "\"" + it.data().join("\",\"") + "\"";
+ config.writeEntry( QString::number( it.key() ), data );
+ }
+ }
+ QString key;
+ for ( namespaceDelim::ConstIterator it = mNamespaceToDelimiter.begin();
+ it != mNamespaceToDelimiter.end(); ++it ) {
+ key = "Namespace:" + it.key();
+ config.writeEntry( key, it.data() );
+ }
+ config.writeEntry( "locallyUnsubscribedFolders", locallyBlacklistedFolders() );
+ }
+
+ //
+ //
+ // Network processing
+ //
+ //
+
+ MetaData ImapAccountBase::slaveConfig() const {
+ MetaData m = NetworkAccount::slaveConfig();
+
+ m.insert( "auth", auth() );
+ if ( autoExpunge() )
+ m.insert( "expunge", "auto" );
+
+ return m;
+ }
+
+ ImapAccountBase::ConnectionState ImapAccountBase::makeConnection()
+ {
+ if ( mSlave && mSlaveConnected ) {
+ return Connected;
+ }
+ if ( mPasswordDialogIsActive ) return Connecting;
+
+ if( mAskAgain || ( ( passwd().isEmpty() || login().isEmpty() ) &&
+ auth() != "GSSAPI" ) ) {
+
+ Q_ASSERT( !mSlave ); // disconnected on 'wrong login' error already, or first try
+ QString log = login();
+ QString pass = passwd();
+ // We init "store" to true to indicate that we want to have the
+ // "keep password" checkbox. Then, we set [Passwords]Keep to
+ // storePasswd(), so that the checkbox in the dialog will be
+ // init'ed correctly:
+ KConfigGroup passwords( KGlobal::config(), "Passwords" );
+ passwords.writeEntry( "Keep", storePasswd() );
+ QString msg = i18n("You need to supply a username and a password to "
+ "access this mailbox.");
+ mPasswordDialogIsActive = true;
+
+ PasswordDialog dlg( msg, log, true /* store pw */, true, KMKernel::self()->mainWin() );
+ dlg.setPlainCaption( i18n("Authorization Dialog") );
+ dlg.addCommentLine( i18n("Account:"), name() );
+ int ret = dlg.exec();
+ if (ret != QDialog::Accepted ) {
+ mPasswordDialogIsActive = false;
+ mAskAgain = false;
+ emit connectionResult( KIO::ERR_USER_CANCELED, QString::null );
+ return Error;
+ }
+ mPasswordDialogIsActive = false;
+ // The user has been given the chance to change login and
+ // password, so copy both from the dialog:
+ setPasswd( dlg.password(), dlg.keepPassword() );
+ setLogin( dlg.username() );
+ mAskAgain = false;
+ }
+ // already waiting for a connection?
+ if ( mSlave && !mSlaveConnected ) return Connecting;
+
+ mSlaveConnected = false;
+ mSlave = KIO::Scheduler::getConnectedSlave( getUrl(), slaveConfig() );
+ if ( !mSlave ) {
+ KMessageBox::error(0, i18n("Could not start process for %1.")
+ .arg( getUrl().protocol() ) );
+ return Error;
+ }
+ if ( mSlave->isConnected() ) {
+ slotSchedulerSlaveConnected( mSlave );
+ return Connected;
+ }
+
+ return Connecting;
+ }
+
+ bool ImapAccountBase::handleJobError( KIO::Job *job, const QString& context, bool abortSync )
+ {
+ JobIterator it = findJob( job );
+ if ( it != jobsEnd() && (*it).progressItem )
+ {
+ (*it).progressItem->setComplete();
+ (*it).progressItem = 0;
+ }
+ return handleError( job->error(), job->errorText(), job, context, abortSync );
+ }
+
+ // Called when we're really all done.
+ void ImapAccountBase::postProcessNewMail( bool showStatusMsg ) {
+ setCheckingMail(false);
+ int newMails = 0;
+ if ( mCountUnread > 0 && mCountUnread > mCountLastUnread ) {
+ newMails = mCountUnread - mCountLastUnread;
+ mCountLastUnread = mCountUnread;
+ mCountUnread = 0;
+ checkDone( true, CheckOK );
+ } else {
+ mCountUnread = 0;
+ checkDone( false, CheckOK );
+ }
+ if ( showStatusMsg )
+ BroadcastStatus::instance()->setStatusMsgTransmissionCompleted(
+ name(), newMails);
+ }
+
+ //-----------------------------------------------------------------------------
+ void ImapAccountBase::changeSubscription( bool subscribe, const QString& imapPath )
+ {
+ // change the subscription of the folder
+ KURL url = getUrl();
+ url.setPath(imapPath);
+
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly);
+
+ if (subscribe)
+ stream << (int) 'u' << url;
+ else
+ stream << (int) 'U' << url;
+
+ // create the KIO-job
+ if ( makeConnection() != Connected )
+ return;// ## doesn't handle Connecting
+ KIO::SimpleJob *job = KIO::special(url, packedArgs, false);
+ KIO::Scheduler::assignJobToSlave(mSlave, job);
+ jobData jd( url.url(), NULL );
+ // a bit of a hack to save one slot
+ if (subscribe) jd.onlySubscribed = true;
+ else jd.onlySubscribed = false;
+ insertJob(job, jd);
+
+ connect(job, SIGNAL(result(KIO::Job *)),
+ SLOT(slotSubscriptionResult(KIO::Job *)));
+ }
+
+ //-----------------------------------------------------------------------------
+ void ImapAccountBase::slotSubscriptionResult( KIO::Job * job )
+ {
+ // result of a subscription-job
+ JobIterator it = findJob( job );
+ if ( it == jobsEnd() ) return;
+ bool onlySubscribed = (*it).onlySubscribed;
+ QString path = static_cast<KIO::SimpleJob*>(job)->url().path();
+ if (job->error())
+ {
+ handleJobError( job, i18n( "Error while trying to subscribe to %1:" ).arg( path ) + '\n' );
+ // ## emit subscriptionChanged here in case anyone needs it to support continue/cancel
+ }
+ else
+ {
+ emit subscriptionChanged( path, onlySubscribed );
+ if (mSlave) removeJob(job);
+ }
+ }
+
+ //-----------------------------------------------------------------------------
+ // TODO imapPath can be removed once parent can be a KMFolderImapBase or whatever
+ void ImapAccountBase::getUserRights( KMFolder* parent, const QString& imapPath )
+ {
+ // There isn't much point in asking the server about a user's rights on his own inbox,
+ // it might not be the effective permissions (at least with Cyrus, one can admin his own inbox,
+ // even after a SETACL that removes the admin permissions. Other imap servers apparently
+ // 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<KMFolderImap*>( parent->storage() )->setUserRights( ACLJobs::All );
+ else if ( parent->folderType() == KMFolderTypeCachedImap )
+ static_cast<KMFolderCachedImap*>( parent->storage() )->setUserRights( ACLJobs::All );
+ emit receivedUserRights( parent ); // warning, you need to connect first to get that one
+ return;
+ }
+
+ KURL url = getUrl();
+ url.setPath(imapPath);
+
+ ACLJobs::GetUserRightsJob* job = ACLJobs::getUserRights( mSlave, url );
+
+ jobData jd( url.url(), parent );
+ jd.cancellable = true;
+ insertJob(job, jd);
+
+ connect(job, SIGNAL(result(KIO::Job *)),
+ SLOT(slotGetUserRightsResult(KIO::Job *)));
+ }
+
+ void ImapAccountBase::slotGetUserRightsResult( KIO::Job* _job )
+ {
+ ACLJobs::GetUserRightsJob* job = static_cast<ACLJobs::GetUserRightsJob *>( _job );
+ JobIterator it = findJob( job );
+ if ( it == jobsEnd() ) return;
+
+ KMFolder* folder = (*it).parent;
+ if ( job->error() ) {
+ if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION ) // that's when the imap server doesn't support ACLs
+ mACLSupport = false;
+ else
+ kdWarning(5006) << "slotGetUserRightsResult: " << job->errorString() << endl;
+ } else {
+#ifndef NDEBUG
+ //kdDebug(5006) << "User Rights: " << ACLJobs::permissionsToString( job->permissions() ) << endl;
+#endif
+ // Store the permissions
+ if ( folder->folderType() == KMFolderTypeImap )
+ static_cast<KMFolderImap*>( folder->storage() )->setUserRights( job->permissions() );
+ else if ( folder->folderType() == KMFolderTypeCachedImap )
+ static_cast<KMFolderCachedImap*>( folder->storage() )->setUserRights( job->permissions() );
+ }
+ if (mSlave) removeJob(job);
+ emit receivedUserRights( folder );
+ }
+
+ //-----------------------------------------------------------------------------
+ void ImapAccountBase::getACL( KMFolder* parent, const QString& imapPath )
+ {
+ KURL url = getUrl();
+ url.setPath(imapPath);
+
+ ACLJobs::GetACLJob* job = ACLJobs::getACL( mSlave, url );
+ jobData jd( url.url(), parent );
+ jd.cancellable = true;
+ insertJob(job, jd);
+
+ connect(job, SIGNAL(result(KIO::Job *)),
+ SLOT(slotGetACLResult(KIO::Job *)));
+ }
+
+ void ImapAccountBase::slotGetACLResult( KIO::Job* _job )
+ {
+ ACLJobs::GetACLJob* job = static_cast<ACLJobs::GetACLJob *>( _job );
+ JobIterator it = findJob( job );
+ if ( it == jobsEnd() ) return;
+
+ KMFolder* folder = (*it).parent;
+ emit receivedACL( folder, job, job->entries() );
+ if (mSlave) removeJob(job);
+ }
+
+ //-----------------------------------------------------------------------------
+ // Do not remove imapPath, FolderDiaQuotaTab needs to call this with parent==0.
+ void ImapAccountBase::getStorageQuotaInfo( KMFolder* parent, const QString& imapPath )
+ {
+ if ( !mSlave ) return;
+ KURL url = getUrl();
+ url.setPath(imapPath);
+
+ QuotaJobs::GetStorageQuotaJob* job = QuotaJobs::getStorageQuota( mSlave, url );
+ jobData jd( url.url(), parent );
+ jd.cancellable = true;
+ insertJob(job, jd);
+
+ connect(job, SIGNAL(result(KIO::Job *)),
+ SLOT(slotGetStorageQuotaInfoResult(KIO::Job *)));
+ }
+
+ void ImapAccountBase::slotGetStorageQuotaInfoResult( KIO::Job* _job )
+ {
+ QuotaJobs::GetStorageQuotaJob* job = static_cast<QuotaJobs::GetStorageQuotaJob *>( _job );
+ JobIterator it = findJob( job );
+ if ( it == jobsEnd() ) return;
+ if ( job->error() && job->error() == KIO::ERR_UNSUPPORTED_ACTION )
+ setHasNoQuotaSupport();
+
+ KMFolder* folder = (*it).parent; // can be 0
+ emit receivedStorageQuotaInfo( folder, job, job->storageQuotaInfo() );
+ if (mSlave) removeJob(job);
+ }
+
+ void ImapAccountBase::slotNoopTimeout()
+ {
+ if ( mSlave ) {
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+
+ stream << ( int ) 'N';
+
+ KIO::SimpleJob *job = KIO::special( getUrl(), packedArgs, false );
+ KIO::Scheduler::assignJobToSlave(mSlave, job);
+ connect( job, SIGNAL(result( KIO::Job * ) ),
+ this, SLOT( slotSimpleResult( KIO::Job * ) ) );
+ } else {
+ /* Stop the timer, we have disconnected. We have to make sure it is
+ started again when a new slave appears. */
+ mNoopTimer.stop();
+ }
+ }
+
+ void ImapAccountBase::slotIdleTimeout()
+ {
+ if ( mSlave ) {
+ KIO::Scheduler::disconnectSlave(mSlave);
+ mSlave = 0;
+ mSlaveConnected = false;
+ /* As for the noop timer, we need to make sure this one is started
+ again when a new slave goes up. */
+ mIdleTimer.stop();
+ }
+ }
+
+ void ImapAccountBase::slotAbortRequested( KPIM::ProgressItem* item )
+ {
+ if ( item )
+ item->setComplete();
+ killAllJobs();
+ }
+
+
+ //-----------------------------------------------------------------------------
+ void ImapAccountBase::slotSchedulerSlaveError(KIO::Slave *aSlave, int errorCode,
+ const QString &errorMsg)
+ {
+ if (aSlave != mSlave) return;
+ handleError( errorCode, errorMsg, 0, QString::null, true );
+ if ( mAskAgain )
+ if ( makeConnection() != ImapAccountBase::Error )
+ return;
+
+ if ( !mSlaveConnected ) {
+ mSlaveConnectionError = true;
+ resetConnectionList( this );
+ if ( mSlave )
+ {
+ KIO::Scheduler::disconnectSlave( slave() );
+ mSlave = 0;
+ }
+ }
+ emit connectionResult( errorCode, errorMsg );
+ }
+
+ //-----------------------------------------------------------------------------
+ void ImapAccountBase::slotSchedulerSlaveConnected(KIO::Slave *aSlave)
+ {
+ if (aSlave != mSlave) return;
+ mSlaveConnected = true;
+ mNoopTimer.start( 60000 ); // make sure we start sending noops
+ emit connectionResult( 0, QString::null ); // success
+
+ if ( mNamespaces.isEmpty() || mNamespaceToDelimiter.isEmpty() ) {
+ connect( this, SIGNAL( namespacesFetched( const ImapAccountBase::nsDelimMap& ) ),
+ this, SLOT( slotSaveNamespaces( const ImapAccountBase::nsDelimMap& ) ) );
+ getNamespaces();
+ }
+
+ // get capabilities
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly);
+ stream << (int) 'c';
+ KIO::SimpleJob *job = KIO::special( getUrl(), packedArgs, false );
+ KIO::Scheduler::assignJobToSlave( mSlave, job );
+ connect( job, SIGNAL(infoMessage(KIO::Job*, const QString&)),
+ SLOT(slotCapabilitiesResult(KIO::Job*, const QString&)) );
+ }
+
+ //-----------------------------------------------------------------------------
+ void ImapAccountBase::slotCapabilitiesResult( KIO::Job*, const QString& result )
+ {
+ mCapabilities = QStringList::split(' ', result.lower() );
+ kdDebug(5006) << "capabilities:" << mCapabilities << endl;
+ }
+
+ //-----------------------------------------------------------------------------
+ void ImapAccountBase::getNamespaces()
+ {
+ disconnect( this, SIGNAL( connectionResult(int, const QString&) ),
+ this, SLOT( getNamespaces() ) );
+ if ( makeConnection() != Connected || !mSlave ) {
+ kdDebug(5006) << "getNamespaces - wait for connection" << endl;
+ if ( mNamespaces.isEmpty() || mNamespaceToDelimiter.isEmpty() ) {
+ // when the connection is established slotSchedulerSlaveConnected notifies us
+ } else {
+ // getNamespaces was called by someone else
+ connect( this, SIGNAL( connectionResult(int, const QString&) ),
+ this, SLOT( getNamespaces() ) );
+ }
+ return;
+ }
+
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly);
+ stream << (int) 'n';
+ jobData jd;
+ jd.total = 1; jd.done = 0; jd.cancellable = true;
+ jd.progressItem = ProgressManager::createProgressItem(
+ ProgressManager::getUniqueID(),
+ i18n("Retrieving Namespaces"),
+ QString::null, true, useSSL() || useTLS() );
+ jd.progressItem->setTotalItems( 1 );
+ connect ( jd.progressItem,
+ SIGNAL( progressItemCanceled( KPIM::ProgressItem* ) ),
+ this,
+ SLOT( slotAbortRequested( KPIM::ProgressItem* ) ) );
+ KIO::SimpleJob *job = KIO::special( getUrl(), packedArgs, false );
+ KIO::Scheduler::assignJobToSlave( mSlave, job );
+ insertJob( job, jd );
+ connect( job, SIGNAL( infoMessage(KIO::Job*, const QString&) ),
+ SLOT( slotNamespaceResult(KIO::Job*, const QString&) ) );
+ }
+
+ //-----------------------------------------------------------------------------
+ void ImapAccountBase::slotNamespaceResult( KIO::Job* job, const QString& str )
+ {
+ JobIterator it = findJob( job );
+ if ( it == jobsEnd() ) return;
+
+ nsDelimMap map;
+ namespaceDelim nsDelim;
+ QStringList ns = QStringList::split( ",", str );
+ for ( QStringList::Iterator it = ns.begin(); it != ns.end(); ++it ) {
+ // split, allow empty parts as we can get empty namespaces
+ QStringList parts = QStringList::split( "=", *it, true );
+ imapNamespace section = imapNamespace( parts[0].toInt() );
+ if ( map.contains( section ) ) {
+ nsDelim = map[section];
+ } else {
+ nsDelim.clear();
+ }
+ // map namespace to delimiter
+ nsDelim[parts[1]] = parts[2];
+ map[section] = nsDelim;
+ }
+ removeJob(it);
+
+ kdDebug(5006) << "namespaces fetched" << endl;
+ emit namespacesFetched( map );
+ }
+
+ //-----------------------------------------------------------------------------
+ void ImapAccountBase::slotSaveNamespaces( const ImapAccountBase::nsDelimMap& map )
+ {
+ kdDebug(5006) << "slotSaveNamespaces " << name() << endl;
+ // extract the needed information
+ mNamespaces.clear();
+ mNamespaceToDelimiter.clear();
+ for ( uint i = 0; i < 3; ++i ) {
+ imapNamespace section = imapNamespace( i );
+ namespaceDelim ns = map[ section ];
+ namespaceDelim::ConstIterator it;
+ QStringList list;
+ for ( it = ns.begin(); it != ns.end(); ++it ) {
+ list += it.key();
+ mNamespaceToDelimiter[ it.key() ] = it.data();
+ }
+ if ( !list.isEmpty() ) {
+ mNamespaces[section] = list;
+ }
+ }
+ // see if we need to migrate an old prefix
+ if ( !mOldPrefix.isEmpty() ) {
+ migratePrefix();
+ }
+ emit namespacesFetched();
+ }
+
+ //-----------------------------------------------------------------------------
+ void ImapAccountBase::migratePrefix()
+ {
+ if ( !mOldPrefix.isEmpty() && mOldPrefix != "/" ) {
+ // strip /
+ if ( mOldPrefix.startsWith("/") ) {
+ mOldPrefix = mOldPrefix.right( mOldPrefix.length()-1 );
+ }
+ if ( mOldPrefix.endsWith("/") ) {
+ mOldPrefix = mOldPrefix.left( mOldPrefix.length()-1 );
+ }
+ QStringList list = mNamespaces[PersonalNS];
+ bool done = false;
+ for ( QStringList::Iterator it = list.begin(); it != list.end(); ++it ) {
+ if ( (*it).startsWith( mOldPrefix ) ) {
+ // should be ok
+ done = true;
+ kdDebug(5006) << "migratePrefix - no migration needed" << endl;
+ break;
+ }
+ }
+ if ( !done ) {
+ QString msg = i18n("KMail has detected a prefix entry in the "
+ "configuration of the account \"%1\" which is obsolete with the "
+ "support of IMAP namespaces.").arg( name() );
+ if ( list.contains( "" ) ) {
+ // replace empty entry with the old prefix
+ list.remove( "" );
+ list += mOldPrefix;
+ mNamespaces[PersonalNS] = list;
+ if ( mNamespaceToDelimiter.contains( "" ) ) {
+ QString delim = mNamespaceToDelimiter[""];
+ mNamespaceToDelimiter.remove( "" );
+ mNamespaceToDelimiter[mOldPrefix] = delim;
+ }
+ kdDebug(5006) << "migratePrefix - replaced empty with " << mOldPrefix << endl;
+ msg += i18n("The configuration was automatically migrated but you should check "
+ "your account configuration.");
+ } else if ( list.count() == 1 ) {
+ // only one entry in the personal namespace so replace it
+ QString old = list.first();
+ list.clear();
+ list += mOldPrefix;
+ mNamespaces[PersonalNS] = list;
+ if ( mNamespaceToDelimiter.contains( old ) ) {
+ QString delim = mNamespaceToDelimiter[old];
+ mNamespaceToDelimiter.remove( old );
+ mNamespaceToDelimiter[mOldPrefix] = delim;
+ }
+ kdDebug(5006) << "migratePrefix - replaced single with " << mOldPrefix << endl;
+ msg += i18n("The configuration was automatically migrated but you should check "
+ "your account configuration.");
+ } else {
+ kdDebug(5006) << "migratePrefix - migration failed" << endl;
+ msg += i18n("It was not possible to migrate your configuration automatically "
+ "so please check your account configuration.");
+ }
+ KMessageBox::information( kmkernel->getKMMainWidget(), msg );
+ }
+ } else
+ {
+ kdDebug(5006) << "migratePrefix - no migration needed" << endl;
+ }
+ mOldPrefix = "";
+ }
+
+ //-----------------------------------------------------------------------------
+ QString ImapAccountBase::namespaceForFolder( FolderStorage* storage )
+ {
+ QString path;
+ if ( storage->folderType() == KMFolderTypeImap ) {
+ path = static_cast<KMFolderImap*>( storage )->imapPath();
+ } else if ( storage->folderType() == KMFolderTypeCachedImap ) {
+ path = static_cast<KMFolderCachedImap*>( storage )->imapPath();
+ }
+
+ nsMap::Iterator it;
+ for ( it = mNamespaces.begin(); it != mNamespaces.end(); ++it )
+ {
+ QStringList::Iterator strit;
+ for ( strit = it.data().begin(); strit != it.data().end(); ++strit )
+ {
+ QString ns = *strit;
+ if ( ns.endsWith("/") || ns.endsWith(".") ) {
+ // strip delimiter for the comparison
+ ns = ns.left( ns.length()-1 );
+ }
+ // first ignore an empty prefix as it would match always
+ if ( !ns.isEmpty() && path.find( ns ) != -1 ) {
+ return (*strit);
+ }
+ }
+ }
+ return QString::null;
+ }
+
+ //-----------------------------------------------------------------------------
+ QString ImapAccountBase::delimiterForNamespace( const QString& prefix )
+ {
+ kdDebug(5006) << "delimiterForNamespace " << prefix << endl;
+ // try to match exactly
+ if ( mNamespaceToDelimiter.contains(prefix) ) {
+ return mNamespaceToDelimiter[prefix];
+ }
+
+ // then try if the prefix is part of a namespace
+ // exclude empty namespace
+ for ( namespaceDelim::ConstIterator it = mNamespaceToDelimiter.begin();
+ it != mNamespaceToDelimiter.end(); ++it ) {
+ // the namespace definition sometimes contains the delimiter
+ // make sure we also match this version
+ QString stripped = it.key().left( it.key().length() - 1 );
+ if ( !it.key().isEmpty() &&
+ ( prefix.contains( it.key() ) || prefix.contains( stripped ) ) ) {
+ return it.data();
+ }
+ }
+ // see if we have an empty namespace
+ // this should always be the case
+ if ( mNamespaceToDelimiter.contains( "" ) ) {
+ return mNamespaceToDelimiter[""];
+ }
+ // well, we tried
+ kdDebug(5006) << "delimiterForNamespace - not found" << endl;
+ return QString::null;
+ }
+
+ //-----------------------------------------------------------------------------
+ QString ImapAccountBase::delimiterForFolder( FolderStorage* storage )
+ {
+ QString prefix = namespaceForFolder( storage );
+ QString delim = delimiterForNamespace( prefix );
+ return delim;
+ }
+
+ //-----------------------------------------------------------------------------
+ void ImapAccountBase::slotSimpleResult(KIO::Job * job)
+ {
+ JobIterator it = findJob( job );
+ bool quiet = false;
+ if (it != mapJobData.end()) {
+ quiet = (*it).quiet;
+ if ( !(job->error() && !quiet) ) // the error handler removes in that case
+ removeJob(it);
+ }
+ if (job->error()) {
+ if (!quiet)
+ handleJobError(job, QString::null );
+ else {
+ if ( job->error() == KIO::ERR_CONNECTION_BROKEN && slave() ) {
+ // make sure ERR_CONNECTION_BROKEN is properly handled and the slave
+ // disconnected even when quiet()
+ KIO::Scheduler::disconnectSlave( slave() );
+ mSlave = 0;
+ }
+ if (job->error() == KIO::ERR_SLAVE_DIED)
+ slaveDied();
+ }
+ }
+ }
+
+ //-----------------------------------------------------------------------------
+ bool ImapAccountBase::handlePutError( KIO::Job* job, jobData& jd, KMFolder* folder )
+ {
+ Q_ASSERT( !jd.msgList.isEmpty() );
+ KMMessage* msg = jd.msgList.first();
+ // Use double-quotes around the subject to keep the sentence readable,
+ // but don't use double quotes around the sender since from() might return a double-quoted name already
+ const QString subject = msg->subject().isEmpty() ? i18n( "<unknown>" ) : QString("\"%1\"").arg( msg->subject() );
+ const QString from = msg->from().isEmpty() ? i18n( "<unknown>" ) : msg->from();
+ QString myError = "<p><b>" + i18n("Error while uploading message")
+ + "</b></p><p>"
+ + i18n("Could not upload the message dated %1 from <i>%2</i> with subject <i>%3</i> to the server.").arg( msg->dateStr(), QStyleSheet::escape( from ), QStyleSheet::escape( subject ) )
+ + "</p><p>"
+ + i18n("The destination folder was: <b>%1</b>.").arg( QStyleSheet::escape( folder->prettyURL() ) )
+ + "</p><p>"
+ + i18n("The server reported:") + "</p>";
+ return handleJobError( job, myError );
+ }
+
+ QString ImapAccountBase::prettifyQuotaError( const QString& _error, KIO::Job * job )
+ {
+ QString error = _error;
+ if ( error.find( "quota", 0, false ) == -1 ) return error;
+ // this is a quota error, prettify it a bit
+ JobIterator it = findJob( job );
+ QString quotaAsString( i18n("No detailed quota information available.") );
+ bool readOnly = false;
+ if (it != mapJobData.end()) {
+ const KMFolder * const folder = (*it).parent;
+ assert(folder);
+ const KMFolderCachedImap * const imap = dynamic_cast<const KMFolderCachedImap*>( folder->storage() );
+ if ( imap ) {
+ 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.");
+ }
+ return error;
+ }
+
+ //-----------------------------------------------------------------------------
+ bool ImapAccountBase::handleError( int errorCode, const QString &errorMsg, KIO::Job* job, const QString& context, bool abortSync )
+ {
+ // Copy job's data before a possible killAllJobs
+ QStringList errors;
+ if ( job && job->error() != KIO::ERR_SLAVE_DEFINED /*workaround for kdelibs-3.2*/)
+ errors = job->detailedErrorStrings();
+
+ bool jobsKilled = true;
+ switch( errorCode ) {
+ case KIO::ERR_SLAVE_DIED: slaveDied(); killAllJobs( true ); break;
+ case KIO::ERR_COULD_NOT_AUTHENTICATE: // bad password
+ mAskAgain = true;
+ // fallthrough intended
+ case KIO::ERR_CONNECTION_BROKEN:
+ case KIO::ERR_COULD_NOT_CONNECT:
+ case KIO::ERR_SERVER_TIMEOUT:
+ // These mean that we'll have to reconnect on the next attempt, so disconnect and set mSlave to 0.
+ killAllJobs( true );
+ break;
+ case KIO::ERR_COULD_NOT_LOGIN:
+ case KIO::ERR_USER_CANCELED:
+ killAllJobs( false );
+ break;
+ default:
+ if ( abortSync )
+ killAllJobs( false );
+ else
+ jobsKilled = false;
+ break;
+ }
+
+ // check if we still display an error
+ if ( !mErrorDialogIsActive && errorCode != KIO::ERR_USER_CANCELED ) {
+ mErrorDialogIsActive = true;
+ QString msg = context + '\n' + prettifyQuotaError( KIO::buildErrorString( errorCode, errorMsg ), job );
+ QString caption = i18n("Error");
+
+ if ( jobsKilled || errorCode == KIO::ERR_COULD_NOT_LOGIN ) {
+ if ( errorCode == KIO::ERR_SERVER_TIMEOUT || errorCode == KIO::ERR_CONNECTION_BROKEN ) {
+ msg = i18n("The connection to the server %1 was unexpectedly closed or timed out. It will be re-established automatically if possible.").
+ arg( name() );
+ KMessageBox::information( kapp->activeWindow(), msg, caption, "kmailConnectionBrokenErrorDialog" );
+ // Show it in the status bar, in case the user has ticked "don't show again"
+ if ( errorCode == KIO::ERR_CONNECTION_BROKEN )
+ KPIM::BroadcastStatus::instance()->setStatusMsg(
+ i18n( "The connection to account %1 was broken." ).arg( name() ) );
+ else if ( errorCode == KIO::ERR_SERVER_TIMEOUT )
+ KPIM::BroadcastStatus::instance()->setStatusMsg(
+ i18n( "The connection to account %1 timed out." ).arg( name() ) );
+ } else {
+ if ( !errors.isEmpty() )
+ KMessageBox::detailedError( kapp->activeWindow(), msg, errors.join("\n").prepend("<qt>"), caption );
+ else
+ KMessageBox::error( kapp->activeWindow(), msg, caption );
+ }
+ } else { // i.e. we have a chance to continue, ask the user about it
+ if ( errors.count() >= 3 ) { // there is no detailedWarningContinueCancel... (#86517)
+ QString error = prettifyQuotaError( errors[1], job );
+ msg = QString( "<qt>") + context + error + '\n' + errors[2];
+ caption = errors[0];
+ }
+ int ret = KMessageBox::warningContinueCancel( kapp->activeWindow(), msg, caption );
+ if ( ret == KMessageBox::Cancel ) {
+ jobsKilled = true;
+ killAllJobs( false );
+ }
+ }
+ mErrorDialogIsActive = false;
+ } else {
+ if ( mErrorDialogIsActive )
+ kdDebug(5006) << "suppressing error:" << errorMsg << endl;
+ }
+ if ( job && !jobsKilled )
+ removeJob( job );
+ return !jobsKilled; // jobsKilled==false -> continue==true
+ }
+
+ //-----------------------------------------------------------------------------
+ void ImapAccountBase::cancelMailCheck()
+ {
+ QMap<KIO::Job*, jobData>::Iterator it = mapJobData.begin();
+ while ( it != mapJobData.end() ) {
+ kdDebug(5006) << "cancelMailCheck: job is cancellable: " << (*it).cancellable << endl;
+ if ( (*it).cancellable ) {
+ it.key()->kill();
+ QMap<KIO::Job*, jobData>::Iterator rmit = it;
+ ++it;
+ mapJobData.remove( rmit );
+ // We killed a job -> this kills the slave
+ mSlave = 0;
+ } else
+ ++it;
+ }
+
+ for( QPtrListIterator<FolderJob> it( mJobList ); it.current(); ++it ) {
+ if ( it.current()->isCancellable() ) {
+ FolderJob* job = it.current();
+ job->setPassiveDestructor( true );
+ mJobList.remove( job );
+ delete job;
+ } else
+ ++it;
+ }
+ }
+
+ //-----------------------------------------------------------------------------
+ void ImapAccountBase::processNewMailSingleFolder(KMFolder* folder)
+ {
+ if ( mFoldersQueuedForChecking.contains( folder ) )
+ return;
+ mFoldersQueuedForChecking.append(folder);
+ mCheckingSingleFolder = true;
+ if ( checkingMail() )
+ {
+ disconnect( this, SIGNAL( finishedCheck( bool, CheckStatus ) ),
+ this, SLOT( slotCheckQueuedFolders() ) );
+ connect( this, SIGNAL( finishedCheck( bool, CheckStatus ) ),
+ this, SLOT( slotCheckQueuedFolders() ) );
+ } else {
+ slotCheckQueuedFolders();
+ }
+ }
+
+ //-----------------------------------------------------------------------------
+ void ImapAccountBase::slotCheckQueuedFolders()
+ {
+ disconnect( this, SIGNAL( finishedCheck( bool, CheckStatus ) ),
+ this, SLOT( slotCheckQueuedFolders() ) );
+
+ QValueList<QGuardedPtr<KMFolder> > mSaveList = mMailCheckFolders;
+ mMailCheckFolders = mFoldersQueuedForChecking;
+ if ( kmkernel->acctMgr() )
+ kmkernel->acctMgr()->singleCheckMail(this, true);
+ mMailCheckFolders = mSaveList;
+ mFoldersQueuedForChecking.clear();
+ }
+
+ //-----------------------------------------------------------------------------
+ bool ImapAccountBase::checkingMail( KMFolder *folder )
+ {
+ if (checkingMail() && mFoldersQueuedForChecking.contains(folder))
+ return true;
+ return false;
+ }
+
+ //-----------------------------------------------------------------------------
+ void ImapAccountBase::handleBodyStructure( QDataStream & stream, KMMessage * msg,
+ const AttachmentStrategy *as )
+ {
+ mBodyPartList.clear();
+ mCurrentMsg = msg;
+ // first delete old parts as we construct our own
+ msg->deleteBodyParts();
+ // make the parts and fill the mBodyPartList
+ constructParts( stream, 1, 0, 0, msg->asDwMessage() );
+ if ( mBodyPartList.count() == 1 ) // we directly set the body later, at partsToLoad below
+ msg->deleteBodyParts();
+
+ if ( !as )
+ {
+ kdWarning(5006) << k_funcinfo << " - found no attachment strategy!" << endl;
+ return;
+ }
+
+ // see what parts have to loaded according to attachmentstrategy
+ BodyVisitor *visitor = BodyVisitorFactory::getVisitor( as );
+ visitor->visit( mBodyPartList );
+ QPtrList<KMMessagePart> parts = visitor->partsToLoad();
+ delete visitor;
+ QPtrListIterator<KMMessagePart> it( parts );
+ KMMessagePart *part;
+ int partsToLoad = 0;
+ // check how many parts we have to load
+ while ( (part = it.current()) != 0 )
+ {
+ ++it;
+ if ( part->loadPart() )
+ {
+ ++partsToLoad;
+ }
+ }
+ // if the only body part is not text, part->loadPart() would return false
+ // and that part is never loaded, so make sure it loads.
+ // it seems that TEXT does load the single body part even if it is not text/*
+ if ( mBodyPartList.count() == 1 && partsToLoad == 0 )
+ partsToLoad = 1; // this causes the next test to succeed, and loads the whole message
+
+ if ( (mBodyPartList.count() * 0.5) < partsToLoad )
+ {
+ // more than 50% of the parts have to be loaded anyway so it is faster
+ // to load the message completely
+ kdDebug(5006) << "Falling back to normal mode" << endl;
+ FolderJob *job = msg->parent()->createJob(
+ msg, FolderJob::tGetMessage, 0, "TEXT" );
+ job->start();
+ return;
+ }
+ it.toFirst();
+ while ( (part = it.current()) != 0 )
+ {
+ ++it;
+ kdDebug(5006) << "ImapAccountBase::handleBodyStructure - load " << part->partSpecifier()
+ << " (" << part->originalContentTypeStr() << ")" << endl;
+ if ( part->loadHeaders() )
+ {
+ kdDebug(5006) << "load HEADER" << endl;
+ FolderJob *job = msg->parent()->createJob(
+ msg, FolderJob::tGetMessage, 0, part->partSpecifier()+".MIME" );
+ job->start();
+ }
+ if ( part->loadPart() )
+ {
+ kdDebug(5006) << "load Part" << endl;
+ FolderJob *job = msg->parent()->createJob(
+ msg, FolderJob::tGetMessage, 0, part->partSpecifier() );
+ job->start();
+ }
+ }
+ }
+
+ //-----------------------------------------------------------------------------
+ void ImapAccountBase::constructParts( QDataStream & stream, int count, KMMessagePart* parentKMPart,
+ DwBodyPart * parent, const DwMessage * dwmsg )
+ {
+ int children;
+ for (int i = 0; i < count; i++)
+ {
+ stream >> children;
+ KMMessagePart* part = new KMMessagePart( stream );
+ part->setParent( parentKMPart );
+ mBodyPartList.append( part );
+ kdDebug(5006) << "ImapAccountBase::constructParts - created id " << part->partSpecifier()
+ << " of type " << part->originalContentTypeStr() << endl;
+ DwBodyPart *dwpart = mCurrentMsg->createDWBodyPart( part );
+
+ if ( parent )
+ {
+ // add to parent body
+ parent->Body().AddBodyPart( dwpart );
+ dwpart->Parse();
+// kdDebug(5006) << "constructed dwpart " << dwpart << ",dwmsg " << dwmsg << ",parent " << parent
+// << ",dwparts msg " << dwpart->Body().Message() <<",id "<<dwpart->ObjectId() << endl;
+ } else if ( part->partSpecifier() != "0" &&
+ !part->partSpecifier().endsWith(".HEADER") )
+ {
+ // add to message
+ dwmsg->Body().AddBodyPart( dwpart );
+ dwpart->Parse();
+// kdDebug(5006) << "constructed dwpart " << dwpart << ",dwmsg " << dwmsg << ",parent " << parent
+// << ",dwparts msg " << dwpart->Body().Message() <<",id "<<dwpart->ObjectId() << endl;
+ } else
+ dwpart = 0;
+
+ if ( !parentKMPart )
+ parentKMPart = part;
+
+ if (children > 0)
+ {
+ DwBodyPart* newparent = dwpart;
+ const DwMessage* newmsg = dwmsg;
+ if ( part->originalContentTypeStr() == "MESSAGE/RFC822" && dwpart &&
+ dwpart->Body().Message() )
+ {
+ // set the encapsulated message as the new message
+ newparent = 0;
+ newmsg = dwpart->Body().Message();
+ }
+ KMMessagePart* newParentKMPart = part;
+ if ( part->partSpecifier().endsWith(".HEADER") ) // we don't want headers as parent
+ newParentKMPart = parentKMPart;
+
+ constructParts( stream, children, newParentKMPart, newparent, newmsg );
+ }
+ }
+ }
+
+ //-----------------------------------------------------------------------------
+ void ImapAccountBase::setImapStatus( KMFolder* folder, const QString& path, const QCString& flags )
+ {
+ // set the status on the server, the uids are integrated in the path
+ kdDebug(5006) << "setImapStatus path=" << path << " to: " << flags << endl;
+ KURL url = getUrl();
+ url.setPath(path);
+
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly);
+
+ stream << (int) 'S' << url << flags;
+
+ if ( makeConnection() != Connected )
+ return; // can't happen with dimap
+
+ KIO::SimpleJob *job = KIO::special(url, packedArgs, false);
+ KIO::Scheduler::assignJobToSlave(slave(), job);
+ ImapAccountBase::jobData jd( url.url(), folder );
+ jd.path = path;
+ insertJob(job, jd);
+ connect(job, SIGNAL(result(KIO::Job *)),
+ SLOT(slotSetStatusResult(KIO::Job *)));
+ }
+
+ void ImapAccountBase::setImapSeenStatus(KMFolder * folder, const QString & path, bool seen)
+ {
+ KURL url = getUrl();
+ url.setPath(path);
+
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly);
+
+ stream << (int) 's' << url << seen;
+
+ if ( makeConnection() != Connected )
+ return; // can't happen with dimap
+
+ KIO::SimpleJob *job = KIO::special(url, packedArgs, false);
+ KIO::Scheduler::assignJobToSlave(slave(), job);
+ ImapAccountBase::jobData jd( url.url(), folder );
+ jd.path = path;
+ insertJob(job, jd);
+ connect(job, SIGNAL(result(KIO::Job *)),
+ SLOT(slotSetStatusResult(KIO::Job *)));
+ }
+
+ //-----------------------------------------------------------------------------
+ void ImapAccountBase::slotSetStatusResult(KIO::Job * job)
+ {
+ ImapAccountBase::JobIterator it = findJob(job);
+ if ( it == jobsEnd() ) return;
+ int errorCode = job->error();
+ KMFolder * const parent = (*it).parent;
+ const QString path = (*it).path;
+ if (errorCode && errorCode != KIO::ERR_CANNOT_OPEN_FOR_WRITING)
+ {
+ bool cont = handleJobError( job, i18n( "Error while uploading status of messages to server: " ) + '\n' );
+ emit imapStatusChanged( parent, path, cont );
+ }
+ else
+ {
+ emit imapStatusChanged( parent, path, true );
+ removeJob(it);
+ }
+ }
+
+ //-----------------------------------------------------------------------------
+ void ImapAccountBase::setFolder(KMFolder* folder, bool addAccount)
+ {
+ if (folder)
+ {
+ folder->setSystemLabel(name());
+ folder->setId(id());
+ }
+ NetworkAccount::setFolder(folder, addAccount);
+ }
+
+ //-----------------------------------------------------------------------------
+ void ImapAccountBase::removeJob( JobIterator& it )
+ {
+ if( (*it).progressItem ) {
+ (*it).progressItem->setComplete();
+ (*it).progressItem = 0;
+ }
+ mapJobData.remove( it );
+ }
+
+ //-----------------------------------------------------------------------------
+ void KMail::ImapAccountBase::removeJob( KIO::Job* job )
+ {
+ mapJobData.remove( job );
+ }
+
+ //-----------------------------------------------------------------------------
+ KPIM::ProgressItem* ImapAccountBase::listDirProgressItem()
+ {
+ if ( !mListDirProgressItem )
+ {
+ mListDirProgressItem = ProgressManager::createProgressItem(
+ "ListDir" + name(),
+ QStyleSheet::escape( name() ),
+ i18n("retrieving folders"),
+ true,
+ useSSL() || useTLS() );
+ connect ( mListDirProgressItem,
+ SIGNAL( progressItemCanceled( KPIM::ProgressItem* ) ),
+ this,
+ SLOT( slotAbortRequested( KPIM::ProgressItem* ) ) );
+ // Start with a guessed value of the old folder count plus 5%. As long
+ // as the list of folders doesn't constantly change, that should be good
+ // enough.
+ unsigned int count = folderCount();
+ mListDirProgressItem->setTotalItems( count + (unsigned int)(count*0.05) );
+ }
+ return mListDirProgressItem;
+ }
+
+ //-----------------------------------------------------------------------------
+ unsigned int ImapAccountBase::folderCount() const
+ {
+ if ( !rootFolder() || !rootFolder()->folder() || !rootFolder()->folder()->child() )
+ return 0;
+ return kmkernel->imapFolderMgr()->folderCount( rootFolder()->folder()->child() );
+ }
+
+ //------------------------------------------------------------------------------
+ QString ImapAccountBase::addPathToNamespace( const QString& prefix )
+ {
+ QString myPrefix = prefix;
+ if ( !myPrefix.startsWith( "/" ) ) {
+ myPrefix = "/" + myPrefix;
+ }
+ if ( !myPrefix.endsWith( "/" ) ) {
+ myPrefix += "/";
+ }
+
+ return myPrefix;
+ }
+
+ //------------------------------------------------------------------------------
+ bool ImapAccountBase::isNamespaceFolder( QString& name )
+ {
+ QStringList ns = mNamespaces[OtherUsersNS];
+ ns += mNamespaces[SharedNS];
+ ns += mNamespaces[PersonalNS];
+ QString nameWithDelimiter;
+ for ( QStringList::Iterator it = ns.begin(); it != ns.end(); ++it )
+ {
+ nameWithDelimiter = name + delimiterForNamespace( *it );
+ if ( *it == name || *it == nameWithDelimiter )
+ return true;
+ }
+ return false;
+ }
+
+ //------------------------------------------------------------------------------
+ ImapAccountBase::nsDelimMap ImapAccountBase::namespacesWithDelimiter()
+ {
+ nsDelimMap map;
+ nsMap::ConstIterator it;
+ for ( uint i = 0; i < 3; ++i )
+ {
+ imapNamespace section = imapNamespace( i );
+ QStringList namespaces = mNamespaces[section];
+ namespaceDelim nsDelim;
+ QStringList::Iterator lit;
+ for ( lit = namespaces.begin(); lit != namespaces.end(); ++lit )
+ {
+ nsDelim[*lit] = delimiterForNamespace( *lit );
+ }
+ map[section] = nsDelim;
+ }
+ return map;
+ }
+
+ //------------------------------------------------------------------------------
+ QString ImapAccountBase::createImapPath( const QString& parent,
+ const QString& folderName )
+ {
+ kdDebug(5006) << "createImapPath parent="<<parent<<", folderName="<<folderName<<endl;
+ QString newName = parent;
+ // strip / at the end
+ if ( newName.endsWith("/") ) {
+ newName = newName.left( newName.length() - 1 );
+ }
+ // add correct delimiter
+ QString delim = delimiterForNamespace( newName );
+ // should not happen...
+ if ( delim.isEmpty() ) {
+ delim = "/";
+ }
+ if ( !newName.isEmpty() &&
+ !newName.endsWith( delim ) && !folderName.startsWith( delim ) ) {
+ newName = newName + delim;
+ }
+ newName = newName + folderName;
+ // add / at the end
+ if ( !newName.endsWith("/") ) {
+ newName = newName + "/";
+ }
+
+ return newName;
+ }
+
+ //------------------------------------------------------------------------------
+ QString ImapAccountBase::createImapPath( FolderStorage* parent,
+ const QString& folderName )
+ {
+ QString path;
+ if ( parent->folderType() == KMFolderTypeImap ) {
+ path = static_cast<KMFolderImap*>( parent )->imapPath();
+ } else if ( parent->folderType() == KMFolderTypeCachedImap ) {
+ path = static_cast<KMFolderCachedImap*>( parent )->imapPath();
+ } else {
+ // error
+ return path;
+ }
+
+ return createImapPath( path, folderName );
+ }
+
+
+ bool ImapAccountBase::locallySubscribedTo( const QString& imapPath )
+ {
+ return mLocalSubscriptionBlackList.find( imapPath ) == mLocalSubscriptionBlackList.end();
+ }
+
+ void ImapAccountBase::changeLocalSubscription( const QString& imapPath, bool subscribe )
+ {
+ if ( subscribe ) {
+ // find in blacklist and remove from it
+ mLocalSubscriptionBlackList.erase( imapPath );
+ } else {
+ // blacklist
+ mLocalSubscriptionBlackList.insert( imapPath );
+ }
+ }
+
+
+ QStringList ImapAccountBase::locallyBlacklistedFolders() const
+ {
+ QStringList list;
+ std::set<QString>::const_iterator it = mLocalSubscriptionBlackList.begin();
+ std::set<QString>::const_iterator end = mLocalSubscriptionBlackList.end();
+ for ( ; it != end ; ++it )
+ list.append( *it );
+ return list;
+ }
+
+ void ImapAccountBase::localBlacklistFromStringList( const QStringList &list )
+ {
+ for( QStringList::ConstIterator it = list.constBegin( ); it != list.constEnd( ); ++it )
+ mLocalSubscriptionBlackList.insert( *it );
+ }
+
+} // namespace KMail
+
+#include "imapaccountbase.moc"
diff --git a/kmail/imapaccountbase.h b/kmail/imapaccountbase.h
new file mode 100644
index 00000000..57147330
--- /dev/null
+++ b/kmail/imapaccountbase.h
@@ -0,0 +1,635 @@
+/* -*- c++ -*-
+ * imapaccountbase.h
+ *
+ * Copyright (c) 2000-2002 Michael Haeckel <haeckel@kde.org>
+ * Copyright (c) 2002 Marc Mutz <mutz@kde.org>
+ *
+ * This file is based on work on pop3 and imap account implementations
+ * by Don Sanders <sanders@kde.org> and Michael Haeckel <haeckel@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __KMAIL_IMAPACCOUNTBASE_H__
+#define __KMAIL_IMAPACCOUNTBASE_H__
+
+#include <set>
+
+#include "networkaccount.h"
+
+#include <qtimer.h>
+#include <qguardedptr.h>
+#include <kio/global.h>
+
+class AccountManager;
+class KMFolder;
+class KConfig/*Base*/;
+class KMMessagePart;
+class DwBodyPart;
+class DwMessage;
+class FolderStorage;
+template <typename T> class QValueVector;
+
+namespace KIO {
+ class Job;
+}
+
+namespace KPIM {
+ class ProgressItem;
+}
+
+namespace KMail {
+ struct ACLListEntry;
+ struct QuotaInfo;
+ typedef QValueVector<KMail::ACLListEntry> ACLList;
+
+ class AttachmentStrategy;
+
+ class ImapAccountBase : public KMail::NetworkAccount {
+ Q_OBJECT
+ protected:
+ ImapAccountBase( AccountManager * parent, const QString & name, uint id );
+ public:
+ virtual ~ImapAccountBase();
+
+ /** Set the config options to a decent state */
+ virtual void init();
+
+ /** A weak assignment operator */
+ virtual void pseudoAssign( const KMAccount * a );
+
+ /** @return whether to automatically expunge deleted messages when
+ leaving the folder */
+ bool autoExpunge() const { return mAutoExpunge; }
+ virtual void setAutoExpunge( bool expunge );
+
+ /** @return whether to show hidden files on the server */
+ bool hiddenFolders() const { return mHiddenFolders; }
+ virtual void setHiddenFolders( bool show );
+
+ /** @return whether to show only subscribed folders */
+ bool onlySubscribedFolders() const { return mOnlySubscribedFolders; }
+ virtual void setOnlySubscribedFolders( bool show );
+
+ /** @return whether to show only locally subscribed folders */
+ bool onlyLocallySubscribedFolders() const { return mOnlyLocallySubscribedFolders; }
+ virtual void setOnlyLocallySubscribedFolders( bool show );
+
+
+ /** @return whether to load attachments on demand */
+ bool loadOnDemand() const { return mLoadOnDemand; }
+ virtual void setLoadOnDemand( bool load );
+
+ /** @return whether to list only open folders */
+ bool listOnlyOpenFolders() const { return mListOnlyOpenFolders; }
+ virtual void setListOnlyOpenFolders( bool only );
+
+ /** Configure the slave by adding to the meta data map */
+ virtual KIO::MetaData slaveConfig() const;
+
+ virtual void readConfig( KConfig& config );
+ virtual void writeConfig( KConfig& config );
+
+ /**
+ * The state of the kioslave connection
+ */
+ enum ConnectionState { Error = 0, Connected, Connecting };
+
+ // possible list types
+ enum ListType {
+ List,
+ ListSubscribed,
+ ListSubscribedNoCheck,
+ ListFolderOnly,
+ ListFolderOnlySubscribed
+ };
+
+ /**
+ * Connect to the server, if no connection is active
+ * Returns Connected (ok), Error (ko) or Connecting - which means
+ * that one should wait for the slaveConnected signal from KIO::Scheduler
+ * before proceeding.
+ */
+ ConnectionState makeConnection();
+
+ // namespace defines
+ enum imapNamespace { PersonalNS=0, OtherUsersNS=1, SharedNS=2 };
+
+ // map a namespace type to a list of namespaces
+ typedef QMap<imapNamespace, QStringList> nsMap;
+
+ // map a namespace to a delimiter
+ typedef QMap<QString, QString> namespaceDelim;
+
+ // map a namespace type to a map with the namespace and the delimiter
+ typedef QMap<imapNamespace, namespaceDelim> nsDelimMap;
+
+ /**
+ * Info Data for the Job
+ */
+ struct jobData
+ {
+ // Needed by QMap, don't use
+ jobData() : url(QString::null), parent(0), current(0), total(1), done(0), offset(0), progressItem(0),
+ onlySubscribed(false), quiet(false), cancellable(false) {}
+ // Real constructor
+ jobData( const QString& _url, KMFolder *_parent = 0,
+ int _total = 1, int _done = 0, bool _quiet = false,
+ bool _cancelable = false )
+ : url(_url), parent(_parent), current(0), total(_total), done(_done), offset(0),
+ progressItem(0), quiet(_quiet), cancellable(_cancelable) {}
+
+ QString path;
+ QString url;
+ QString curNamespace;
+ QByteArray data;
+ QCString cdata;
+ QStringList items;
+ KMFolder *parent, *current;
+ QPtrList<KMMessage> msgList;
+ int total, done, offset;
+ KPIM::ProgressItem *progressItem;
+ bool onlySubscribed, quiet, cancellable;
+ };
+
+ typedef QMap<KIO::Job *, jobData>::Iterator JobIterator;
+ /**
+ * Call this when starting a new job
+ */
+ void insertJob( KIO::Job* job, const jobData& data ) {
+ mapJobData.insert( job, data );
+ }
+ /**
+ * Look for the jobData related to a given job. Compare with end()
+ */
+ JobIterator findJob( KIO::Job* job ) { return mapJobData.find( job ); }
+ JobIterator jobsEnd() { return mapJobData.end(); }
+ /**
+ * Call this when a job is finished.
+ * Don't use @p *it afterwards!
+ */
+ void removeJob( JobIterator& it );
+
+ void removeJob( KIO::Job* job );
+
+ /**
+ * Subscribe (@p subscribe = TRUE) / Unsubscribe the folder
+ * identified by @p imapPath.
+ * Emits subscriptionChanged signal on success.
+ */
+ void changeSubscription(bool subscribe, const QString& imapPath);
+
+ /**
+ * Returns whether the account is locally subscribed to the
+ * folder @param imapPath. No relation to server side subscription above.
+ */
+ bool locallySubscribedTo( const QString& imapPath );
+
+ /**
+ * Locally subscribe (@p subscribe = TRUE) / Unsubscribe the folder
+ * identified by @p imapPath.
+ */
+ void changeLocalSubscription( const QString& imapPath, bool subscribe );
+
+
+ /**
+ * Retrieve the users' right on the folder
+ * identified by @p folder and @p imapPath.
+ * Emits receivedUserRights signal on success/error.
+ */
+ void getUserRights( KMFolder* folder, const QString& imapPath );
+
+ /**
+ * Retrieve the complete list of ACLs on the folder
+ * identified by @p imapPath.
+ * Emits receivedACL signal on success/error.
+ */
+ void getACL( KMFolder* folder, const QString& imapPath );
+
+ /**
+ * Retrieve the the quota inforamiton on the folder
+ * identified by @p imapPath.
+ * Emits receivedQuotaInfo signal on success/error.
+ */
+ void getStorageQuotaInfo( KMFolder* folder, const QString& imapPath );
+
+ /**
+ * Set the status on the server
+ * Emits imapStatusChanged signal on success/error.
+ */
+ void setImapStatus( KMFolder* folder, const QString& path, const QCString& flags );
+
+ /**
+ * Set seen status on the server.
+ * Emits imapStatusChanged signal on success/error.
+ */
+ void setImapSeenStatus( KMFolder* folder, const QString& path, bool seen );
+
+ /**
+ * The KIO-Slave died
+ */
+ void slaveDied() { mSlave = 0; killAllJobs(); }
+
+ /**
+ * Kill the slave if any jobs are active
+ */
+ void killAllJobs( bool disconnectSlave=false ) = 0;
+
+ /**
+ * Abort all running mail checks. Used when exiting.
+ */
+ virtual void cancelMailCheck();
+
+ /**
+ * Init a new-mail-check for a single folder
+ */
+ void processNewMailSingleFolder(KMFolder* folder);
+
+ /**
+ * Return true if we are processing a mailcheck for a single folder
+ */
+ bool checkingSingleFolder() { return mCheckingSingleFolder; }
+
+ /**
+ * Called when we're completely done checking mail for this account
+ * When @p setStatusMsg is true a status msg is shown
+ */
+ void postProcessNewMail( bool setStatusMsg = true );
+
+ /**
+ * Check whether we're checking for new mail
+ * and the folder is included
+ */
+ bool checkingMail( KMFolder *folder );
+
+ bool checkingMail() { return NetworkAccount::checkingMail(); }
+
+ /**
+ * Handles the result from a BODYSTRUCTURE fetch
+ */
+ void handleBodyStructure( QDataStream & stream, KMMessage * msg,
+ const AttachmentStrategy *as );
+
+ /**
+ * Reimplemented. Additionally set the folder label
+ */
+ virtual void setFolder(KMFolder*, bool addAccount = false);
+
+ /**
+ * Returns false if the IMAP server for this account doesn't support ACLs.
+ * (and true if it does, or if we didn't try yet).
+ */
+ bool hasACLSupport() const { return mACLSupport; }
+
+ /**
+ * Returns false if the IMAP server for this account doesn't support annotations.
+ * (and true if it does, or if we didn't try yet).
+ */
+ bool hasAnnotationSupport() const { return mAnnotationSupport; }
+
+ /**
+ * Called if the annotation command failed due to 'unsupported'
+ */
+ void setHasNoAnnotationSupport() { mAnnotationSupport = false; }
+
+ /**
+ * Returns false if the IMAP server for this account doesn't support quotas.
+ * (and true if it does, or if we didn't try yet).
+ */
+ bool hasQuotaSupport() const { return mQuotaSupport; }
+
+ /**
+ * Called if the quota command failed due to 'unsupported'
+ */
+ void setHasNoQuotaSupport() { mQuotaSupport = false; }
+
+ /**
+ * React to an error from the job. Uses job->error and job->errorString and calls
+ * the protected virtual handleJobError with them. See handleError below for details.
+ */
+ bool handleJobError( KIO::Job* job, const QString& context, bool abortSync = false );
+
+ /**
+ * Returns the root folder of this account
+ */
+ virtual FolderStorage* const rootFolder() const = 0;
+
+ /**
+ * Progress item for listDir
+ */
+ KPIM::ProgressItem* listDirProgressItem();
+
+ /**
+ * @return the number of (subscribed, if applicable) folders in this
+ * account.
+ */
+ virtual unsigned int folderCount() const;
+
+ /**
+ * @return defined namespaces
+ */
+ nsMap namespaces() const { return mNamespaces; }
+
+ /**
+ * Set defined namespaces
+ */
+ virtual void setNamespaces( nsMap map )
+ { mNamespaces = map; }
+
+ /**
+ * Full blown section - namespace - delimiter map
+ * Do not call this very often as the map is constructed on the fly
+ */
+ nsDelimMap namespacesWithDelimiter();
+
+ /**
+ * @return the namespace for the @p folder
+ */
+ QString namespaceForFolder( FolderStorage* );
+
+ /**
+ * Adds "/" as needed to the given namespace
+ */
+ QString addPathToNamespace( const QString& ns );
+
+ /**
+ * @return the delimiter for the @p namespace
+ */
+ QString delimiterForNamespace( const QString& prefix );
+
+ /**
+ * @return the delimiter for the @p folderstorage
+ */
+ QString delimiterForFolder( FolderStorage* );
+
+ /**
+ * @return the namespace - delimiter map
+ */
+ namespaceDelim namespaceToDelimiter() const
+ { return mNamespaceToDelimiter; }
+
+ /**
+ * Set the namespace - delimiter map
+ */
+ void setNamespaceToDelimiter( namespaceDelim map )
+ { mNamespaceToDelimiter = map; }
+
+ /**
+ * Returns true if the given string is a namespace
+ */
+ bool isNamespaceFolder( QString& name );
+
+ /**
+ * Returns true if the account has the given capability
+ */
+ bool hasCapability( const QString& capa ) {
+ return mCapabilities.contains( capa ); }
+
+ /**
+ * Create an IMAP path for a parent folder and a foldername
+ * Parent and folder are separated with the delimiter of the account
+ * The path starts and ends with '/'
+ */
+ QString createImapPath( FolderStorage* parent, const QString& folderName );
+
+ /**
+ * Create an IMAP path for a parent imapPath and a folderName
+ */
+ QString createImapPath( const QString& parent, const QString& folderName );
+
+
+ public slots:
+ /**
+ * Call this to get the namespaces
+ * You are notified by the signal namespacesFetched
+ */
+ void getNamespaces();
+
+ private slots:
+ /**
+ * is called when the changeSubscription has finished
+ * emits subscriptionChanged
+ */
+ void slotSubscriptionResult(KIO::Job * job);
+
+ protected slots:
+ virtual void slotCheckQueuedFolders();
+
+ /// Handle a message coming from the KIO scheduler saying that the slave is now connected
+ void slotSchedulerSlaveConnected(KIO::Slave *aSlave);
+ /// Handle an error coming from the KIO scheduler
+ void slotSchedulerSlaveError(KIO::Slave *aSlave, int, const QString &errorMsg);
+
+ /**
+ * Only delete information about the job and ignore write errors
+ */
+ void slotSetStatusResult(KIO::Job * job);
+
+ /// Result of getUserRights() job
+ void slotGetUserRightsResult( KIO::Job* _job );
+
+ /// Result of getACL() job
+ void slotGetACLResult( KIO::Job* _job );
+
+ /// Result of getStorageQuotaInfo() job
+ void slotGetStorageQuotaInfoResult( KIO::Job* _job );
+
+ /**
+ * Send a NOOP command regularly to keep the slave from disconnecting
+ */
+ void slotNoopTimeout();
+ /**
+ * Log out when idle
+ */
+ void slotIdleTimeout();
+
+ /**
+ * Kills all jobs
+ */
+ void slotAbortRequested( KPIM::ProgressItem* );
+
+ /**
+ * Only delete information about the job
+ */
+ void slotSimpleResult(KIO::Job * job);
+
+ /** Gets and parses the namespaces */
+ void slotNamespaceResult( KIO::Job*, const QString& str );
+
+ /**
+ * Saves the fetched namespaces
+ */
+ void slotSaveNamespaces( const ImapAccountBase::nsDelimMap& map );
+
+ /**
+ * Saves the capabilities list
+ */
+ void slotCapabilitiesResult( KIO::Job*, const QString& result );
+
+ protected:
+
+ /**
+ * Handle an error coming from a KIO job or from a KIO slave (via the scheduler)
+ * and abort everything (in all cases) if abortSync is true [this is for slotSchedulerSlaveError].
+ * Otherwise (abortSync==false), dimap will only abort in case of severe errors (connection broken),
+ * but on "normal" errors (no permission to delete, etc.) it will ask the user.
+ *
+ * @param error the error code, usually job->error())
+ * @param errorMsg the error message, usually job->errorText()
+ * @param job the kio job (can be 0). If set, removeJob will be called automatically.
+ * This is important! It means you should not call removeJob yourself in case of errors.
+ * We can't let the caller do that, since it should only be done afterwards, and only if we didn't abort.
+ *
+ * @param context a sentence that gives some context to the error, e.g. i18n("Error while uploading message [...]")
+ * @param abortSync if true, abort sync in all cases (see above). If false, ask the user (when possible).
+ * @return false when aborting, true when continuing
+ */
+ virtual bool handleError( int error, const QString &errorMsg, KIO::Job* job, const QString& context, bool abortSync = false );
+
+ /** Handle an error during KIO::put - helper method */
+ bool handlePutError( KIO::Job* job, jobData& jd, KMFolder* folder );
+
+ virtual QString protocol() const;
+ virtual unsigned short int defaultPort() const;
+
+ /**
+ * Build KMMessageParts and DwBodyParts from the bodystructure-stream
+ */
+ void constructParts( QDataStream & stream, int count, KMMessagePart* parentKMPart,
+ DwBodyPart * parent, const DwMessage * dwmsg );
+
+ /** Migrate the prefix */
+ void migratePrefix();
+
+ // used for writing the blacklist out to the config file
+ QStringList locallyBlacklistedFolders() const;
+ void localBlacklistFromStringList( const QStringList & );
+ QString prettifyQuotaError( const QString& _error, KIO::Job * job );
+
+ protected:
+ QPtrList<QGuardedPtr<KMFolder> > mOpenFolders;
+ QStringList mSubfolderNames, mSubfolderPaths,
+ mSubfolderMimeTypes, mSubfolderAttributes;
+ QMap<KIO::Job *, jobData> mapJobData;
+ /** used to detect when the slave has not been used for a while */
+ QTimer mIdleTimer;
+ /** used to send a noop to the slave in regular intervals to keep it from disonnecting */
+ QTimer mNoopTimer;
+ int mTotal, mCountUnread, mCountLastUnread;
+ QMap<QString, int> mUnreadBeforeCheck;
+ bool mAutoExpunge : 1;
+ bool mHiddenFolders : 1;
+ bool mOnlySubscribedFolders : 1;
+ bool mOnlyLocallySubscribedFolders : 1;
+ bool mLoadOnDemand : 1;
+ bool mListOnlyOpenFolders : 1;
+ bool mProgressEnabled : 1;
+
+ bool mErrorDialogIsActive : 1;
+ bool mPasswordDialogIsActive : 1;
+ bool mACLSupport : 1;
+ bool mAnnotationSupport : 1;
+ bool mQuotaSupport : 1;
+ bool mSlaveConnected : 1;
+ bool mSlaveConnectionError : 1;
+ bool mCheckingSingleFolder : 1;
+
+ // folders that should be checked for new mails
+ QValueList<QGuardedPtr<KMFolder> > mMailCheckFolders;
+ // folders that should be checked after the current check is done
+ QValueList<QGuardedPtr<KMFolder> > mFoldersQueuedForChecking;
+ // holds messageparts from the bodystructure
+ QPtrList<KMMessagePart> mBodyPartList;
+ // the current message for the bodystructure
+ KMMessage* mCurrentMsg;
+
+ QGuardedPtr<KPIM::ProgressItem> mListDirProgressItem;
+
+ // our namespaces in the form section=namespaceList
+ nsMap mNamespaces;
+
+ // namespace - delimiter map
+ namespaceDelim mNamespaceToDelimiter;
+
+ // old prefix for migration
+ QString mOldPrefix;
+
+ // capabilities
+ QStringList mCapabilities;
+
+ std::set<QString> mLocalSubscriptionBlackList;
+
+ signals:
+ /**
+ * Emitted when the slave managed or failed to connect
+ * This is always emitted at some point after makeConnection returned Connecting.
+ * @param errorCode 0 for success, != 0 in case of error
+ * @param errorMsg if errorCode is != 0, this goes with errorCode to call KIO::buildErrorString
+ */
+ void connectionResult( int errorCode, const QString& errorMsg );
+
+ /**
+ * Emitted when the subscription has changed,
+ * as a result of a changeSubscription call.
+ */
+ void subscriptionChanged(const QString& imapPath, bool subscribed);
+
+ /**
+ * Emitted upon completion of the job for setting the status for a group of UIDs,
+ * as a result of a setImapStatus call.
+ * On error, if the user chooses abort (not continue), cont is set to false.
+ */
+ void imapStatusChanged( KMFolder*, const QString& imapPath, bool cont );
+
+ /**
+ * 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.
+ */
+ void receivedUserRights( KMFolder* folder );
+
+ /**
+ * Emitted when the get-the-ACLs job is done,
+ * as a result of a getACL call.
+ * @param folder the folder for which we were listing the ACLs (can be 0)
+ * @param job the job that was used for doing so (can be used to display errors)
+ * @param entries the ACL list. Make your copy of it, it comes from the job.
+ */
+ void receivedACL( KMFolder* folder, KIO::Job* job, const KMail::ACLList& entries );
+
+ /**
+ * Emitted when the getQuotaInfo job is done,
+ * as a result of a getQuotaInfo call.
+ * @param folder The folder for which we were getting quota info (can be 0)
+ * @param job The job that was used for doing so (can be used to display errors)
+ * @param info The quota information for this folder. Make your copy of it,
+ * it comes from the job.
+ */
+ void receivedStorageQuotaInfo( KMFolder* folder, KIO::Job* job, const KMail::QuotaInfo& entries );
+
+ /**
+ * Emitted when we got the namespaces
+ */
+ void namespacesFetched( const ImapAccountBase::nsDelimMap& );
+
+ /**
+ * Emitted when we got the namespaces, and they were set on the object.
+ */
+ void namespacesFetched();
+ };
+
+
+} // namespace KMail
+
+#endif // __KMAIL_IMAPACCOUNTBASE_H__
diff --git a/kmail/imapjob.cpp b/kmail/imapjob.cpp
new file mode 100644
index 00000000..13d4baa8
--- /dev/null
+++ b/kmail/imapjob.cpp
@@ -0,0 +1,711 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ *
+ * This file is part of KMail, the KDE mail client.
+ * Copyright (c) 2002-2003 Zack Rusin <zack@kde.org>
+ * 2000-2002 Michael Haeckel <haeckel@kde.org>
+ *
+ * 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 Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "imapjob.h"
+#include "kmfolderimap.h"
+#include "kmfolder.h"
+#include "kmmsgpart.h"
+#include "progressmanager.h"
+using KPIM::ProgressManager;
+#include "util.h"
+
+#include <qstylesheet.h>
+#include <kio/scheduler.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <mimelib/body.h>
+#include <mimelib/bodypart.h>
+#include <mimelib/string.h>
+
+
+namespace KMail {
+
+//-----------------------------------------------------------------------------
+ImapJob::ImapJob( KMMessage *msg, JobType jt, KMFolderImap* folder,
+ QString partSpecifier, const AttachmentStrategy *as )
+ : FolderJob( msg, jt, folder? folder->folder() : 0, partSpecifier ),
+ mAttachmentStrategy( as ), mParentProgressItem(0)
+{
+}
+
+//-----------------------------------------------------------------------------
+ImapJob::ImapJob( QPtrList<KMMessage>& msgList, QString sets, JobType jt,
+ KMFolderImap* folder )
+ : FolderJob( msgList, sets, jt, folder? folder->folder() : 0 ),
+ mAttachmentStrategy ( 0 ), mParentProgressItem(0)
+{
+}
+
+void ImapJob::init( JobType jt, QString sets, KMFolderImap* folder,
+ QPtrList<KMMessage>& msgList )
+{
+ mJob = 0;
+
+ assert(jt == tGetMessage || folder);
+ KMMessage* msg = msgList.first();
+ // guard against empty list
+ if ( !msg ) {
+ deleteLater();
+ return;
+ }
+ mType = jt;
+ mDestFolder = folder? folder->folder() : 0;
+ // refcount++
+ if (folder) {
+ folder->open("imapjobdest");
+ }
+ KMFolder *msg_parent = msg->parent();
+ if (msg_parent) {
+ msg_parent->open("imapjobsrc");
+ }
+ mSrcFolder = msg_parent;
+ // If there is a destination folder, this is a copy, move or put to an
+ // imap folder, use its account for keeping track of the job. Otherwise,
+ // this is a get job and the src folder is an imap one. Use its account
+ // then.
+ KMAcctImap *account = 0;
+ if (folder) {
+ account = folder->account();
+ } else {
+ if ( msg_parent && msg_parent->storage() )
+ account = static_cast<KMFolderImap*>(msg_parent->storage())->account();
+ }
+ if ( !account ||
+ account->makeConnection() == ImapAccountBase::Error ) {
+ deleteLater();
+ return;
+ }
+ account->mJobList.append( this );
+ if ( jt == tPutMessage )
+ {
+ // transfers the complete message to the server
+ QPtrListIterator<KMMessage> it( msgList );
+ KMMessage* curMsg;
+ while ( ( curMsg = it.current() ) != 0 )
+ {
+ ++it;
+ if ( mSrcFolder && !curMsg->isMessage() )
+ {
+ int idx = mSrcFolder->find( curMsg );
+ curMsg = mSrcFolder->getMsg( idx );
+ }
+ KURL url = account->getUrl();
+ QString flags = KMFolderImap::statusToFlags( curMsg->status(), folder->permanentFlags() );
+ url.setPath( folder->imapPath() + ";SECTION=" + flags );
+ ImapAccountBase::jobData jd;
+ jd.parent = 0; jd.offset = 0; jd.done = 0;
+ jd.total = ( curMsg->msgSizeServer() > 0 ) ?
+ curMsg->msgSizeServer() : curMsg->msgSize();
+ jd.msgList.append( curMsg );
+ QCString cstr( curMsg->asString() );
+ int a = cstr.find("\nX-UID: ");
+ int b = cstr.find('\n', a);
+ if (a != -1 && b != -1 && cstr.find("\n\n") > a) cstr.remove(a, b-a);
+ jd.data.resize( cstr.length() + cstr.contains( "\n" ) - cstr.contains( "\r\n" ) );
+ unsigned int i = 0;
+ char prevChar = '\0';
+ // according to RFC 2060 we need CRLF
+ for ( char *ch = cstr.data(); *ch; ch++ )
+ {
+ if ( *ch == '\n' && (prevChar != '\r') ) {
+ jd.data.at( i ) = '\r';
+ i++;
+ }
+ jd.data.at( i ) = *ch;
+ prevChar = *ch;
+ i++;
+ }
+ jd.progressItem = ProgressManager::createProgressItem(
+ mParentProgressItem,
+ "ImapJobUploading"+ProgressManager::getUniqueID(),
+ i18n("Uploading message data"),
+ QStyleSheet::escape( curMsg->subject() ),
+ true,
+ account->useSSL() || account->useTLS() );
+ jd.progressItem->setTotalItems( jd.total );
+ connect ( jd.progressItem, SIGNAL( progressItemCanceled( KPIM::ProgressItem*)),
+ account, SLOT( slotAbortRequested( KPIM::ProgressItem* ) ) );
+ KIO::SimpleJob *job = KIO::put( url, 0, false, false, false );
+ KIO::Scheduler::assignJobToSlave( account->slave(), job );
+ account->insertJob( job, jd );
+ connect( job, SIGNAL(result(KIO::Job *)),
+ SLOT(slotPutMessageResult(KIO::Job *)) );
+ connect( job, SIGNAL(dataReq(KIO::Job *, QByteArray &)),
+ SLOT(slotPutMessageDataReq(KIO::Job *, QByteArray &)) );
+ connect( job, SIGNAL(infoMessage(KIO::Job *, const QString &)),
+ SLOT(slotPutMessageInfoData(KIO::Job *, const QString &)) );
+ connect( job, SIGNAL(processedSize(KIO::Job *, KIO::filesize_t)),
+ SLOT(slotProcessedSize(KIO::Job *, KIO::filesize_t)));
+ }
+ }
+ else if ( jt == tCopyMessage || jt == tMoveMessage )
+ {
+ KURL url = account->getUrl();
+ KURL destUrl = account->getUrl();
+ destUrl.setPath(folder->imapPath());
+ KMFolderImap *imapDestFolder = static_cast<KMFolderImap*>(msg_parent->storage());
+ url.setPath( imapDestFolder->imapPath() + ";UID=" + sets );
+ ImapAccountBase::jobData jd;
+ jd.parent = 0; jd.offset = 0;
+ jd.total = 1; jd.done = 0;
+ jd.msgList = msgList;
+
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+
+ stream << (int) 'C' << url << destUrl;
+ jd.progressItem = ProgressManager::createProgressItem(
+ mParentProgressItem,
+ "ImapJobCopyMove"+ProgressManager::getUniqueID(),
+ i18n("Server operation"),
+ i18n("Source folder: %1 - Destination folder: %2")
+ .arg( QStyleSheet::escape( msg_parent->prettyURL() ),
+ QStyleSheet::escape( mDestFolder->prettyURL() ) ),
+ true,
+ account->useSSL() || account->useTLS() );
+ jd.progressItem->setTotalItems( jd.total );
+ connect ( jd.progressItem, SIGNAL(progressItemCanceled(KPIM::ProgressItem*)),
+ account, SLOT( slotAbortRequested(KPIM::ProgressItem* ) ) );
+ KIO::SimpleJob *simpleJob = KIO::special( url, packedArgs, false );
+ KIO::Scheduler::assignJobToSlave( account->slave(), simpleJob );
+ mJob = simpleJob;
+ account->insertJob( mJob, jd );
+ connect( mJob, SIGNAL(result(KIO::Job *)),
+ SLOT(slotCopyMessageResult(KIO::Job *)) );
+ if ( jt == tMoveMessage )
+ {
+ connect( mJob, SIGNAL(infoMessage(KIO::Job *, const QString &)),
+ SLOT(slotCopyMessageInfoData(KIO::Job *, const QString &)) );
+ }
+ }
+ else {
+ slotGetNextMessage();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+ImapJob::~ImapJob()
+{
+ if ( mDestFolder )
+ {
+ KMAcctImap *account = static_cast<KMFolderImap*>(mDestFolder->storage())->account();
+ if ( account ) {
+ if ( mJob ) {
+ ImapAccountBase::JobIterator it = account->findJob( mJob );
+ if ( it != account->jobsEnd() ) {
+ if( (*it).progressItem ) {
+ (*it).progressItem->setComplete();
+ (*it).progressItem = 0;
+ }
+ if ( !(*it).msgList.isEmpty() ) {
+ for ( QPtrListIterator<KMMessage> mit( (*it).msgList ); mit.current(); ++mit )
+ mit.current()->setTransferInProgress( false );
+ }
+ }
+ account->removeJob( mJob );
+ }
+ account->mJobList.remove( this );
+ }
+ mDestFolder->close("imapjobdest");
+ }
+
+ if ( mSrcFolder ) {
+ if (!mDestFolder || mDestFolder != mSrcFolder) {
+ if (! (mSrcFolder->folderType() == KMFolderTypeImap) ) return;
+ KMAcctImap *account = static_cast<KMFolderImap*>(mSrcFolder->storage())->account();
+ if ( account ) {
+ if ( mJob ) {
+ ImapAccountBase::JobIterator it = account->findJob( mJob );
+ if ( it != account->jobsEnd() ) {
+ if( (*it).progressItem ) {
+ (*it).progressItem->setComplete();
+ (*it).progressItem = 0;
+ }
+ if ( !(*it).msgList.isEmpty() ) {
+ for ( QPtrListIterator<KMMessage> mit( (*it).msgList ); mit.current(); ++mit )
+ mit.current()->setTransferInProgress( false );
+ }
+ }
+ account->removeJob( mJob ); // remove the associated kio job
+ }
+ account->mJobList.remove( this ); // remove the folderjob
+ }
+ }
+ mSrcFolder->close("imapjobsrc");
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void ImapJob::slotGetNextMessage()
+{
+ KMMessage *msg = mMsgList.first();
+ KMFolderImap *msgParent = msg ? static_cast<KMFolderImap*>(msg->storage()) : 0;
+ if ( !msgParent || !msg || msg->UID() == 0 )
+ {
+ // broken message
+ emit messageRetrieved( 0 );
+ deleteLater();
+ return;
+ }
+ KMAcctImap *account = msgParent->account();
+ KURL url = account->getUrl();
+ QString path = msgParent->imapPath() + ";UID=" + QString::number(msg->UID());
+ ImapAccountBase::jobData jd;
+ jd.parent = 0; jd.offset = 0;
+ jd.total = 1; jd.done = 0;
+ jd.msgList.append( msg );
+ if ( !mPartSpecifier.isEmpty() )
+ {
+ if ( mPartSpecifier.find ("STRUCTURE", 0, false) != -1 ) {
+ path += ";SECTION=STRUCTURE";
+ } else if ( mPartSpecifier == "HEADER" ) {
+ path += ";SECTION=HEADER";
+ } else {
+ path += ";SECTION=BODY.PEEK[" + mPartSpecifier + "]";
+ DwBodyPart * part = msg->findDwBodyPart( msg->getFirstDwBodyPart(), mPartSpecifier );
+ if (part)
+ jd.total = part->BodySize();
+ }
+ } else {
+ path += ";SECTION=BODY.PEEK[]";
+ if (msg->msgSizeServer() > 0)
+ jd.total = msg->msgSizeServer();
+ }
+ url.setPath( path );
+// kdDebug(5006) << "ImapJob::slotGetNextMessage - retrieve " << url.path() << endl;
+ // protect the message, otherwise we'll get crashes afterwards
+ msg->setTransferInProgress( true );
+ jd.progressItem = ProgressManager::createProgressItem(
+ mParentProgressItem,
+ "ImapJobDownloading"+ProgressManager::getUniqueID(),
+ i18n("Downloading message data"),
+ i18n("Message with subject: ") +
+ QStyleSheet::escape( msg->subject() ),
+ true,
+ account->useSSL() || account->useTLS() );
+ connect ( jd.progressItem, SIGNAL( progressItemCanceled( KPIM::ProgressItem*)),
+ account, SLOT( slotAbortRequested( KPIM::ProgressItem* ) ) );
+ jd.progressItem->setTotalItems( jd.total );
+
+ KIO::SimpleJob *simpleJob = KIO::get( url, false, false );
+ KIO::Scheduler::assignJobToSlave( account->slave(), simpleJob );
+ mJob = simpleJob;
+ account->insertJob( mJob, jd );
+ if ( mPartSpecifier.find( "STRUCTURE", 0, false ) != -1 )
+ {
+ connect( mJob, SIGNAL(result(KIO::Job *)),
+ this, SLOT(slotGetBodyStructureResult(KIO::Job *)) );
+ } else {
+ connect( mJob, SIGNAL(result(KIO::Job *)),
+ this, SLOT(slotGetMessageResult(KIO::Job *)) );
+ }
+ connect( mJob, SIGNAL(data(KIO::Job *, const QByteArray &)),
+ msgParent, SLOT(slotSimpleData(KIO::Job *, const QByteArray &)) );
+ if ( jd.total > 1 )
+ {
+ connect(mJob, SIGNAL(processedSize(KIO::Job *, KIO::filesize_t)),
+ this, SLOT(slotProcessedSize(KIO::Job *, KIO::filesize_t)));
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void ImapJob::slotGetMessageResult( KIO::Job * job )
+{
+ KMMessage *msg = mMsgList.first();
+ if (!msg || !msg->parent() || !job) {
+ emit messageRetrieved( 0 );
+ deleteLater();
+ return;
+ }
+ KMFolderImap* parent = static_cast<KMFolderImap*>(msg->storage());
+ if (msg->transferInProgress())
+ msg->setTransferInProgress( false );
+ KMAcctImap *account = parent->account();
+ if ( !account ) {
+ emit messageRetrieved( 0 );
+ deleteLater();
+ return;
+ }
+ ImapAccountBase::JobIterator it = account->findJob( job );
+ if ( it == account->jobsEnd() ) return;
+
+ bool gotData = true;
+ if (job->error())
+ {
+ QString errorStr = i18n( "Error while retrieving messages from the server." );
+ if ( (*it).progressItem )
+ (*it).progressItem->setStatus( errorStr );
+ account->handleJobError( job, errorStr );
+ return;
+ } else {
+ if ((*it).data.size() > 0)
+ {
+ kdDebug(5006) << "ImapJob::slotGetMessageResult - retrieved part " << mPartSpecifier << endl;
+ if ( mPartSpecifier.isEmpty() ||
+ mPartSpecifier == "HEADER" )
+ {
+ uint size = msg->msgSizeServer();
+ if ( size > 0 && mPartSpecifier.isEmpty() )
+ (*it).done = size;
+ ulong uid = msg->UID();
+ // must set this first so that msg->fromByteArray sets the attachment status
+ if ( mPartSpecifier.isEmpty() )
+ msg->setComplete( true );
+ else
+ msg->setReadyToShow( false );
+
+ // Convert CR/LF to LF.
+ size_t dataSize = (*it).data.size();
+ dataSize = Util::crlf2lf( (*it).data.data(), dataSize ); // always <=
+ (*it).data.resize( dataSize );
+
+ // During the construction of the message from the byteArray it does
+ // not have a uid. Therefore we have to make sure that no connected
+ // slots are called, since they would operate on uid == 0.
+ msg->parent()->storage()->blockSignals( true );
+ msg->fromByteArray( (*it).data );
+ // now let others react
+ msg->parent()->storage()->blockSignals( false );
+ if ( size > 0 && msg->msgSizeServer() == 0 ) {
+ msg->setMsgSizeServer(size);
+ }
+ // reconstruct the UID as it gets overwritten above
+ msg->setUID(uid);
+
+ } else {
+ // Convert CR/LF to LF.
+ size_t dataSize = (*it).data.size();
+ dataSize = Util::crlf2lf( (*it).data.data(), dataSize ); // always <=
+ (*it).data.resize( dataSize );
+
+ // Update the body of the retrieved part (the message notifies all observers)
+ msg->updateBodyPart( mPartSpecifier, (*it).data );
+ msg->setReadyToShow( true );
+ // Update the attachment state, we have to do this for every part as we actually
+ // do not know if the message has no attachment or we simply did not load the header
+ if (msg->attachmentState() != KMMsgHasAttachment)
+ msg->updateAttachmentState();
+ }
+ } else {
+ kdDebug(5006) << "ImapJob::slotGetMessageResult - got no data for " << mPartSpecifier << endl;
+ gotData = false;
+ msg->setReadyToShow( true );
+ // nevertheless give visual feedback
+ msg->notify();
+ }
+ }
+ if (account->slave()) {
+ account->removeJob(it);
+ account->mJobList.remove(this);
+ }
+ /* This needs to be emitted last, so the slots that are hooked to it
+ * don't unGetMsg the msg before we have finished. */
+ if ( mPartSpecifier.isEmpty() ||
+ mPartSpecifier == "HEADER" )
+ {
+ if ( gotData )
+ emit messageRetrieved(msg);
+ else
+ {
+ /* we got an answer but not data
+ * this means that the msg is not on the server anymore so delete it */
+ emit messageRetrieved( 0 );
+ parent->ignoreJobsForMessage( msg );
+ int idx = parent->find( msg );
+ if (idx != -1) parent->removeMsg( idx, true );
+ // the removeMsg will unGet the message, which will delete all
+ // jobs, including this one
+ return;
+ }
+ } else {
+ emit messageUpdated(msg, mPartSpecifier);
+ }
+ deleteLater();
+}
+
+//-----------------------------------------------------------------------------
+void ImapJob::slotGetBodyStructureResult( KIO::Job * job )
+{
+ KMMessage *msg = mMsgList.first();
+ if (!msg || !msg->parent() || !job) {
+ deleteLater();
+ return;
+ }
+ KMFolderImap* parent = static_cast<KMFolderImap*>(msg->storage());
+ if (msg->transferInProgress())
+ msg->setTransferInProgress( false );
+ KMAcctImap *account = parent->account();
+ if ( !account ) {
+ deleteLater();
+ return;
+ }
+ ImapAccountBase::JobIterator it = account->findJob( job );
+ if ( it == account->jobsEnd() ) return;
+
+
+ if (job->error())
+ {
+ account->handleJobError( job, i18n( "Error while retrieving information on the structure of a message." ) );
+ return;
+ } else {
+ if ((*it).data.size() > 0)
+ {
+ QDataStream stream( (*it).data, IO_ReadOnly );
+ account->handleBodyStructure(stream, msg, mAttachmentStrategy);
+ }
+ }
+ if (account->slave()) {
+ account->removeJob(it);
+ account->mJobList.remove(this);
+ }
+ deleteLater();
+}
+
+//-----------------------------------------------------------------------------
+void ImapJob::slotPutMessageDataReq( KIO::Job *job, QByteArray &data )
+{
+ KMAcctImap *account = static_cast<KMFolderImap*>(mDestFolder->storage())->account();
+ if ( !account )
+ {
+ emit finished();
+ deleteLater();
+ return;
+ }
+ ImapAccountBase::JobIterator it = account->findJob( job );
+ if ( it == account->jobsEnd() ) return;
+
+ if ((*it).data.size() - (*it).offset > 0x8000)
+ {
+ data.duplicate((*it).data.data() + (*it).offset, 0x8000);
+ (*it).offset += 0x8000;
+ }
+ else if ((*it).data.size() - (*it).offset > 0)
+ {
+ data.duplicate((*it).data.data() + (*it).offset, (*it).data.size() - (*it).offset);
+ (*it).offset = (*it).data.size();
+ } else data.resize(0);
+}
+
+
+//-----------------------------------------------------------------------------
+void ImapJob::slotPutMessageResult( KIO::Job *job )
+{
+ KMAcctImap *account = static_cast<KMFolderImap*>(mDestFolder->storage())->account();
+ if ( !account )
+ {
+ emit finished();
+ deleteLater();
+ return;
+ }
+ ImapAccountBase::JobIterator it = account->findJob( job );
+ if ( it == account->jobsEnd() ) return;
+ bool deleteMe = false;
+ if (job->error())
+ {
+ if ( (*it).progressItem )
+ (*it).progressItem->setStatus( i18n("Uploading message data failed.") );
+ account->handlePutError( job, *it, mDestFolder );
+ return;
+ } else {
+ if ( (*it).progressItem )
+ (*it).progressItem->setStatus( i18n("Uploading message data completed.") );
+ if ( mParentProgressItem )
+ {
+ mParentProgressItem->incCompletedItems();
+ mParentProgressItem->updateProgress();
+ }
+ KMMessage *msg = (*it).msgList.first();
+ emit messageStored( msg );
+ if ( msg == mMsgList.getLast() )
+ {
+ emit messageCopied( mMsgList );
+ if (account->slave()) {
+ account->mJobList.remove( this );
+ }
+ deleteMe = true;
+ }
+ }
+ if (account->slave()) {
+ account->removeJob( it ); // also clears progressitem
+ }
+ if ( deleteMe )
+ deleteLater();
+}
+
+//-----------------------------------------------------------------------------
+void ImapJob::slotCopyMessageInfoData(KIO::Job * job, const QString & data)
+{
+ KMFolderImap * imapFolder = static_cast<KMFolderImap*>(mDestFolder->storage());
+ KMAcctImap *account = imapFolder->account();
+ if ( !account )
+ {
+ emit finished();
+ deleteLater();
+ return;
+ }
+ ImapAccountBase::JobIterator it = account->findJob( job );
+ if ( it == account->jobsEnd() ) return;
+
+ if (data.find("UID") != -1)
+ {
+ // split
+ QString oldUid = data.section(' ', 1, 1);
+ QString newUid = data.section(' ', 2, 2);
+
+ // get lists of uids
+ QValueList<ulong> olduids = KMFolderImap::splitSets(oldUid);
+ QValueList<ulong> newuids = KMFolderImap::splitSets(newUid);
+
+ int index = -1;
+ KMMessage * msg;
+ for ( msg = (*it).msgList.first(); msg; msg = (*it).msgList.next() )
+ {
+ ulong uid = msg->UID();
+ index = olduids.findIndex(uid);
+ if (index > -1)
+ {
+ // found, get the new uid
+ imapFolder->saveMsgMetaData( msg, newuids[index] );
+ }
+ }
+ }
+}
+
+//----------------------------------------------------------------------------
+void ImapJob::slotPutMessageInfoData(KIO::Job *job, const QString &data)
+{
+ KMFolderImap * imapFolder = static_cast<KMFolderImap*>(mDestFolder->storage());
+ KMAcctImap *account = imapFolder->account();
+ if ( !account )
+ {
+ emit finished();
+ deleteLater();
+ return;
+ }
+ ImapAccountBase::JobIterator it = account->findJob( job );
+ if ( it == account->jobsEnd() ) return;
+
+ if ( data.find("UID") != -1 )
+ {
+ ulong uid = ( data.right(data.length()-4) ).toInt();
+ if ( !(*it).msgList.isEmpty() )
+ {
+ imapFolder->saveMsgMetaData( (*it).msgList.first(), uid );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void ImapJob::slotCopyMessageResult( KIO::Job *job )
+{
+ KMAcctImap *account = static_cast<KMFolderImap*>(mDestFolder->storage())->account();
+ if ( !account )
+ {
+ emit finished();
+ deleteLater();
+ return;
+ }
+ ImapAccountBase::JobIterator it = account->findJob( job );
+ if ( it == account->jobsEnd() ) return;
+
+ if (job->error())
+ {
+ mErrorCode = job->error();
+ QString errStr = i18n("Error while copying messages.");
+ if ( (*it).progressItem )
+ (*it).progressItem->setStatus( errStr );
+ if ( account->handleJobError( job, errStr ) )
+ deleteLater();
+ return;
+ } else {
+ if ( !(*it).msgList.isEmpty() )
+ {
+ emit messageCopied((*it).msgList);
+ } else if (mMsgList.first()) {
+ emit messageCopied(mMsgList.first());
+ }
+ }
+ if (account->slave()) {
+ account->removeJob(it);
+ account->mJobList.remove(this);
+ }
+ deleteLater();
+}
+
+//-----------------------------------------------------------------------------
+void ImapJob::execute()
+{
+ init( mType, mSets, mDestFolder?
+ dynamic_cast<KMFolderImap*>( mDestFolder->storage() ):0, mMsgList );
+}
+
+//-----------------------------------------------------------------------------
+void ImapJob::setParentFolder( const KMFolderImap* parent )
+{
+ mParentFolder = const_cast<KMFolderImap*>( parent );
+}
+
+//-----------------------------------------------------------------------------
+void ImapJob::slotProcessedSize(KIO::Job * job, KIO::filesize_t processed)
+{
+ KMMessage *msg = mMsgList.first();
+ if (!msg || !job) {
+ return;
+ }
+ KMFolderImap* parent = 0;
+ if ( msg->parent() && msg->parent()->folderType() == KMFolderTypeImap )
+ parent = static_cast<KMFolderImap*>(msg->parent()->storage());
+ else if (mDestFolder) // put
+ parent = static_cast<KMFolderImap*>(mDestFolder->storage());
+ if (!parent) return;
+ KMAcctImap *account = parent->account();
+ if ( !account ) return;
+ ImapAccountBase::JobIterator it = account->findJob( job );
+ if ( it == account->jobsEnd() ) return;
+ (*it).done = processed;
+ if ( (*it).progressItem ) {
+ (*it).progressItem->setCompletedItems( processed );
+ (*it).progressItem->updateProgress();
+ }
+ emit progress( (*it).done, (*it).total );
+}
+
+}//namespace KMail
+
+#include "imapjob.moc"
diff --git a/kmail/imapjob.h b/kmail/imapjob.h
new file mode 100644
index 00000000..d12ac0f7
--- /dev/null
+++ b/kmail/imapjob.h
@@ -0,0 +1,97 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ *
+ * This file is part of KMail, the KDE mail client.
+ * Copyright (c) 2002-2003 Zack Rusin <zack@kde.org>
+ * 2000-2002 Michael Haeckel <haeckel@kde.org>
+ *
+ * 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 Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#ifndef IMAPJOB_H
+#define IMAPJOB_H
+
+#include <kio/job.h>
+#include <kio/global.h>
+
+#include "folderjob.h"
+
+#include <qcstring.h>
+
+class KMAcctImap;
+class KMMessage;
+class KMFolderTreeItem;
+class KMFolderImap;
+namespace KPIM {
+ class ProgressItem;
+}
+
+namespace KMail {
+
+class AttachmentStrategy;
+
+class ImapJob : public FolderJob
+{
+ Q_OBJECT
+ friend class ::KMAcctImap;
+
+public:
+ ImapJob( KMMessage *msg, JobType jt = tGetMessage, KMFolderImap *folder = 0,
+ QString partSpecifier = QString::null, const AttachmentStrategy *as = 0 );
+ ImapJob( QPtrList<KMMessage>& msgList, QString sets,
+ JobType jt = tGetMessage, KMFolderImap *folder = 0 );
+ virtual ~ImapJob();
+
+ void setParentFolder( const KMFolderImap* parent );
+ KPIM::ProgressItem* parentProgressItem() const { return mParentProgressItem; }
+ void setParentProgressItem( KPIM::ProgressItem *p ) { mParentProgressItem = p; }
+
+private slots:
+ void slotGetMessageResult( KIO::Job * job );
+ void slotGetBodyStructureResult( KIO::Job * job );
+ void slotGetNextMessage();
+ /** Feeds the message in pieces to the server */
+ void slotPutMessageDataReq( KIO::Job *job, QByteArray &data );
+ void slotPutMessageResult( KIO::Job *job );
+ void slotPutMessageInfoData(KIO::Job *, const QString &data);
+ /** result of a copy-operation */
+ void slotCopyMessageResult( KIO::Job *job );
+ void slotCopyMessageInfoData( KIO::Job *, const QString &data );
+ void slotProcessedSize( KIO::Job *, KIO::filesize_t processed );
+
+private:
+ void execute();
+ void init( JobType jt, QString sets, KMFolderImap *folder,
+ QPtrList<KMMessage>& msgList );
+ KIO::Job *mJob;
+ QByteArray mData;
+ const AttachmentStrategy *mAttachmentStrategy;
+ KMFolderImap *mParentFolder;
+ KPIM::ProgressItem *mParentProgressItem;
+};
+
+}
+
+
+#endif
diff --git a/kmail/index.cpp b/kmail/index.cpp
new file mode 100644
index 00000000..5b156a99
--- /dev/null
+++ b/kmail/index.cpp
@@ -0,0 +1,594 @@
+/* This file is part of KMail
+ * Copyright (C) 2005 Luís Pedro Coelho <luis@luispedro.org>
+ *
+ * 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 Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#include "index.h"
+
+#include "kmkernel.h"
+#include "kmfoldermgr.h"
+#include "kmmsgdict.h"
+#include "kmfolder.h"
+#include "kmsearchpattern.h"
+#include "kmfoldersearch.h"
+
+#include <kdebug.h>
+#include <kapplication.h>
+#include <qfile.h>
+#include <qtimer.h>
+#include <qvaluestack.h>
+#include <qptrlist.h>
+#include <qfileinfo.h>
+#ifdef HAVE_INDEXLIB
+#include <indexlib/create.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <iostream>
+#include <algorithm>
+#include <cstdlib>
+
+namespace {
+const unsigned int MaintenanceLimit = 1000;
+const char* const folderIndexDisabledKey = "fulltextIndexDisabled";
+}
+
+#ifdef HAVE_INDEXLIB
+static
+QValueList<int> vectorToQValueList( const std::vector<Q_UINT32>& input ) {
+ QValueList<int> res;
+ std::copy( input.begin(), input.end(), std::back_inserter( res ) );
+ return res;
+}
+
+static
+std::vector<Q_UINT32> QValueListToVector( const QValueList<int>& input ) {
+ std::vector<Q_UINT32> res;
+ // res.assign( input.begin(), input.end() ) doesn't work for some reason
+ for ( QValueList<int>::const_iterator first = input.begin(), past = input.end(); first != past; ++first ) {
+ res.push_back( *first );
+ }
+ return res;
+}
+#endif
+
+KMMsgIndex::KMMsgIndex( QObject* parent ):
+ QObject( parent, "index" ),
+ mState( s_idle ),
+#ifdef HAVE_INDEXLIB
+ mLockFile( std::string( static_cast<const char*>( QFile::encodeName( defaultPath() ) + "/lock" ) ) ),
+ mIndex( 0 ),
+#endif
+ mIndexPath( QFile::encodeName( defaultPath() ) ),
+ mTimer( new QTimer( this, "mTimer" ) ),
+ //mSyncState( ss_none ),
+ //mSyncTimer( new QTimer( this ) ),
+ mSlowDown( false ) {
+ kdDebug( 5006 ) << "KMMsgIndex::KMMsgIndex()" << endl;
+
+ connect( kmkernel->folderMgr(), SIGNAL( msgRemoved( KMFolder*, Q_UINT32 ) ), SLOT( slotRemoveMessage( Q_UINT32 ) ) );
+ connect( kmkernel->folderMgr(), SIGNAL( msgAdded( KMFolder*, Q_UINT32 ) ), SLOT( slotAddMessage( Q_UINT32 ) ) );
+ connect( kmkernel->dimapFolderMgr(), SIGNAL( msgRemoved( KMFolder*, Q_UINT32 ) ), SLOT( slotRemoveMessage( Q_UINT32 ) ) );
+ connect( kmkernel->dimapFolderMgr(), SIGNAL( msgAdded( KMFolder*, Q_UINT32 ) ), SLOT( slotAddMessage( Q_UINT32 ) ) );
+
+ connect( mTimer, SIGNAL( timeout() ), SLOT( act() ) );
+ //connect( mSyncTimer, SIGNAL( timeout() ), SLOT( syncIndex() ) );
+
+#ifdef HAVE_INDEXLIB
+ KConfigGroup cfg( KMKernel::config(), "text-index" );
+ if ( !cfg.readBoolEntry( "enabled", false ) ) {
+ indexlib::remove( mIndexPath );
+ mLockFile.force_unlock();
+ mState = s_disabled;
+ return;
+ }
+ if ( !mLockFile.trylock() ) {
+ indexlib::remove( mIndexPath );
+
+ mLockFile.force_unlock();
+ mLockFile.trylock();
+ } else {
+ mIndex = indexlib::open( mIndexPath, indexlib::open_flags::fail_if_nonexistant ).release();
+ }
+ if ( !mIndex ) {
+ QTimer::singleShot( 8000, this, SLOT( create() ) );
+ mState = s_willcreate;
+ } else {
+ if ( cfg.readBoolEntry( "creating" ) ) {
+ QTimer::singleShot( 8000, this, SLOT( continueCreation() ) );
+ mState = s_creating;
+ } else {
+ mPendingMsgs = QValueListToVector( cfg.readIntListEntry( "pending" ) );
+ mRemovedMsgs = QValueListToVector( cfg.readIntListEntry( "removed" ) );
+ }
+ }
+ mIndex = 0;
+#else
+ mState = s_error;
+#endif
+ //if ( mState == s_idle ) mSyncState = ss_synced;
+}
+
+
+KMMsgIndex::~KMMsgIndex() {
+ kdDebug( 5006 ) << "KMMsgIndex::~KMMsgIndex()" << endl;
+#ifdef HAVE_INDEXLIB
+ KConfigGroup cfg( KMKernel::config(), "text-index" );
+ cfg.writeEntry( "creating", mState == s_creating );
+ QValueList<int> pendingMsg;
+ if ( mState == s_processing ) {
+ Q_ASSERT( mAddedMsgs.empty() );
+ pendingMsg = vectorToQValueList( mPendingMsgs );
+ }
+ cfg.writeEntry( "pending", pendingMsg );
+ cfg.writeEntry( "removed", vectorToQValueList( mRemovedMsgs ) );
+ delete mIndex;
+#endif
+}
+
+bool KMMsgIndex::isIndexable( KMFolder* folder ) const {
+ if ( !folder || !folder->parent() ) return false;
+ const KMFolderMgr* manager = folder->parent()->manager();
+ return manager == kmkernel->folderMgr() || manager == kmkernel->dimapFolderMgr();
+}
+
+bool KMMsgIndex::isIndexed( KMFolder* folder ) const {
+ if ( !isIndexable( folder ) ) return false;
+ KConfig* config = KMKernel::config();
+ KConfigGroupSaver saver( config, "Folder-" + folder->idString() );
+ return !config->readBoolEntry( folderIndexDisabledKey, false );
+}
+
+void KMMsgIndex::setEnabled( bool e ) {
+ kdDebug( 5006 ) << "KMMsgIndex::setEnabled( " << e << " )" << endl;
+ KConfig* config = KMKernel::config();
+ KConfigGroupSaver saver( config, "text-index" );
+ if ( config->readBoolEntry( "enabled", !e ) == e ) return;
+ config->writeEntry( "enabled", e );
+ if ( e ) {
+ switch ( mState ) {
+ case s_idle:
+ case s_willcreate:
+ case s_creating:
+ case s_processing:
+ // nothing to do
+ return;
+ case s_error:
+ // nothing can be done, probably
+ return;
+ case s_disabled:
+ QTimer::singleShot( 8000, this, SLOT( create() ) );
+ mState = s_willcreate;
+ }
+ } else {
+ clear();
+ }
+}
+
+void KMMsgIndex::setIndexingEnabled( KMFolder* folder, bool e ) {
+ KConfig* config = KMKernel::config();
+ KConfigGroupSaver saver( config, "Folder-" + folder->idString() );
+ if ( config->readBoolEntry( folderIndexDisabledKey, e ) == e ) return; // nothing to do
+ config->writeEntry( folderIndexDisabledKey, e );
+
+ if ( e ) {
+ switch ( mState ) {
+ case s_idle:
+ case s_creating:
+ case s_processing:
+ mPendingFolders.push_back( folder );
+ scheduleAction();
+ break;
+ case s_willcreate:
+ // do nothing, create() will handle this
+ break;
+ case s_error:
+ case s_disabled:
+ // nothing can be done
+ break;
+ }
+
+ } else {
+ switch ( mState ) {
+ case s_willcreate:
+ // create() will notice that folder is disabled
+ break;
+ case s_creating:
+ if ( std::find( mPendingFolders.begin(), mPendingFolders.end(), folder ) != mPendingFolders.end() ) {
+ // easy:
+ mPendingFolders.erase( std::find( mPendingFolders.begin(), mPendingFolders.end(), folder ) );
+ break;
+ }
+ //else fall-through
+ case s_idle:
+ case s_processing:
+
+ case s_error:
+ case s_disabled:
+ // nothing can be done
+ break;
+ }
+ }
+}
+
+void KMMsgIndex::clear() {
+ kdDebug( 5006 ) << "KMMsgIndex::clear()" << endl;
+#ifdef HAVE_INDEXLIB
+ delete mIndex;
+ mLockFile.force_unlock();
+ mIndex = 0;
+ indexlib::remove( mIndexPath );
+ mPendingMsgs.clear();
+ mPendingFolders.clear();
+ mMaintenanceCount = 0;
+ mAddedMsgs.clear();
+ mRemovedMsgs.clear();
+ mExisting.clear();
+ mState = s_disabled;
+ for ( std::set<KMFolder*>::const_iterator first = mOpenedFolders.begin(), past = mOpenedFolders.end(); first != past; ++first ) {
+ ( *first )->close("msgindex");
+ }
+ mOpenedFolders.clear();
+ for ( std::vector<Search*>::const_iterator first = mSearches.begin(), past = mSearches.end(); first != past; ++first ) {
+ delete *first;
+ }
+ mSearches.clear();
+ mTimer->stop();
+#endif
+}
+
+void KMMsgIndex::maintenance() {
+#ifdef HAVE_INDEXLIB
+ if ( mState != s_idle || kapp->hasPendingEvents() ) {
+ QTimer::singleShot( 8000, this, SLOT( maintenance() ) );
+ return;
+ }
+ mIndex->maintenance();
+#endif
+}
+
+int KMMsgIndex::addMessage( Q_UINT32 serNum ) {
+ kdDebug( 5006 ) << "KMMsgIndex::addMessage( " << serNum << " )" << endl;
+ if ( mState == s_error ) return 0;
+#ifdef HAVE_INDEXLIB
+ assert( mIndex );
+ if ( !mExisting.empty() && std::binary_search( mExisting.begin(), mExisting.end(), serNum ) ) return 0;
+
+ int idx = -1;
+ KMFolder* folder = 0;
+ KMMsgDict::instance()->getLocation( serNum, &folder, &idx );
+ if ( !folder || idx == -1 ) return -1;
+ if ( !mOpenedFolders.count( folder ) ) {
+ mOpenedFolders.insert( folder );
+ folder->open("msgindex");
+ }
+ KMMessage* msg = folder->getMsg( idx );
+ /* I still don't know whether we should allow decryption or not.
+ * Setting to false which makes more sense.
+ * We keep signature to get the person's name
+ */
+ QString body = msg->asPlainText( false, false );
+ if ( !body.isEmpty() && static_cast<const char*>( body.latin1() ) ) {
+ mIndex->add( body.latin1(), QString::number( serNum ).latin1() );
+ } else {
+ kdDebug( 5006 ) << "Funny, no body" << endl;
+ }
+ folder->unGetMsg( idx );
+#endif
+ return 0;
+}
+
+void KMMsgIndex::act() {
+ kdDebug( 5006 ) << "KMMsgIndex::act()" << endl;
+ if ( kapp->hasPendingEvents() ) {
+ //nah, some other time..
+ mTimer->start( 500 );
+ mSlowDown = true;
+ return;
+ }
+ if ( mSlowDown ) {
+ mSlowDown = false;
+ mTimer->start( 0 );
+ }
+ if ( !mPendingMsgs.empty() ) {
+ addMessage( mPendingMsgs.back() );
+ mPendingMsgs.pop_back();
+ return;
+ }
+ if ( !mPendingFolders.empty() ) {
+ KMFolder *f = mPendingFolders.back();
+ mPendingFolders.pop_back();
+ if ( !mOpenedFolders.count( f ) ) {
+ mOpenedFolders.insert( f );
+ f->open("msgindex");
+ }
+ const KMMsgDict* dict = KMMsgDict::instance();
+ KConfig* config = KMKernel::config();
+ KConfigGroupSaver saver( config, "Folder-" + f->idString() );
+ if ( config->readBoolEntry( folderIndexDisabledKey, true ) ) {
+ for ( int i = 0; i < f->count(); ++i ) {
+ mPendingMsgs.push_back( dict->getMsgSerNum( f, i ) );
+ }
+ }
+ return;
+ }
+ if ( !mAddedMsgs.empty() ) {
+ std::swap( mAddedMsgs, mPendingMsgs );
+ mState = s_processing;
+ return;
+ }
+ for ( std::set<KMFolder*>::const_iterator first = mOpenedFolders.begin(), past = mOpenedFolders.end();
+ first != past;
+ ++first ) {
+ ( *first )->close("msgindex");
+ }
+ mOpenedFolders.clear();
+ mState = s_idle;
+ mTimer->stop();
+}
+
+void KMMsgIndex::continueCreation() {
+ kdDebug( 5006 ) << "KMMsgIndex::continueCreation()" << endl;
+#ifdef HAVE_INDEXLIB
+ create();
+ unsigned count = mIndex->ndocs();
+ mExisting.clear();
+ mExisting.reserve( count );
+ for ( unsigned i = 0; i != count; ++i ) {
+ mExisting.push_back( std::atoi( mIndex->lookup_docname( i ).c_str() ) );
+ }
+ std::sort( mExisting.begin(), mExisting.end() );
+#endif
+}
+
+void KMMsgIndex::create() {
+ kdDebug( 5006 ) << "KMMsgIndex::create()" << endl;
+
+#ifdef HAVE_INDEXLIB
+ if ( !QFileInfo( mIndexPath ).exists() ) {
+ ::mkdir( mIndexPath, S_IRWXU );
+ }
+ mState = s_creating;
+ if ( !mIndex ) mIndex = indexlib::create( mIndexPath ).release();
+ if ( !mIndex ) {
+ kdDebug( 5006 ) << "Error creating index" << endl;
+ mState = s_error;
+ return;
+ }
+ QValueStack<KMFolderDir*> folders;
+ folders.push(&(kmkernel->folderMgr()->dir()));
+ folders.push(&(kmkernel->dimapFolderMgr()->dir()));
+ while ( !folders.empty() ) {
+ KMFolderDir *dir = folders.pop();
+ for(KMFolderNode *child = dir->first(); child; child = dir->next()) {
+ if ( child->isDir() )
+ folders.push((KMFolderDir*)child);
+ else
+ mPendingFolders.push_back( (KMFolder*)child );
+ }
+ }
+ mTimer->start( 4000 ); // wait a couple of seconds before starting up...
+ mSlowDown = true;
+#endif
+}
+
+bool KMMsgIndex::startQuery( KMSearch* s ) {
+ kdDebug( 5006 ) << "KMMsgIndex::startQuery( . )" << endl;
+ if ( mState != s_idle ) return false;
+ if ( !isIndexed( s->root() ) || !canHandleQuery( s->searchPattern() ) ) return false;
+
+ kdDebug( 5006 ) << "KMMsgIndex::startQuery( . ) starting query" << endl;
+ Search* search = new Search( s );
+ connect( search, SIGNAL( finished( bool ) ), s, SIGNAL( finished( bool ) ) );
+ connect( search, SIGNAL( finished( bool ) ), s, SLOT( indexFinished() ) );
+ connect( search, SIGNAL( destroyed( QObject* ) ), SLOT( removeSearch( QObject* ) ) );
+ connect( search, SIGNAL( found( Q_UINT32 ) ), s, SIGNAL( found( Q_UINT32 ) ) );
+ mSearches.push_back( search );
+ return true;
+}
+
+
+//void KMMsgIndex::startSync() {
+// switch ( mSyncState ) {
+// case ss_none:
+// mIndex->start_sync();
+// mSyncState = ss_started;
+// mSyncTimer.start( 4000, true );
+// break;
+// case ss_started:
+// mIndex->sync_now();
+// mSyncState = ss_synced;
+// mLockFile.unlock();
+// break;
+// }
+//}
+//
+//void KMMsgIndex::finishSync() {
+//
+//}
+
+void KMMsgIndex::removeSearch( QObject* destroyed ) {
+ mSearches.erase( std::find( mSearches.begin(), mSearches.end(), destroyed ) );
+}
+
+
+bool KMMsgIndex::stopQuery( KMSearch* s ) {
+ kdDebug( 5006 ) << "KMMsgIndex::stopQuery( . )" << endl;
+ for ( std::vector<Search*>::iterator iter = mSearches.begin(), past = mSearches.end(); iter != past; ++iter ) {
+ if ( ( *iter )->search() == s ) {
+ delete *iter;
+ mSearches.erase( iter );
+ return true;
+ }
+ }
+ return false;
+}
+
+std::vector<Q_UINT32> KMMsgIndex::simpleSearch( QString s, bool* ok ) const {
+ kdDebug( 5006 ) << "KMMsgIndex::simpleSearch( -" << s.latin1() << "- )" << endl;
+ if ( mState == s_error || mState == s_disabled ) {
+ if ( ok ) *ok = false;
+ return std::vector<Q_UINT32>();
+ }
+ std::vector<Q_UINT32> res;
+#ifdef HAVE_INDEXLIB
+ assert( mIndex );
+ std::vector<unsigned> residx = mIndex->search( s.latin1() )->list();
+ res.reserve( residx.size() );
+ for ( std::vector<unsigned>::const_iterator first = residx.begin(), past = residx.end();first != past; ++first ) {
+ res.push_back( std::atoi( mIndex->lookup_docname( *first ).c_str() ) );
+ }
+ if ( ok ) *ok = true;
+#endif
+ return res;
+}
+
+bool KMMsgIndex::canHandleQuery( const KMSearchPattern* pat ) const {
+ kdDebug( 5006 ) << "KMMsgIndex::canHandleQuery( . )" << endl;
+ if ( !pat ) return false;
+ QPtrListIterator<KMSearchRule> it( *pat );
+ KMSearchRule* rule;
+ while ( (rule = it.current()) != 0 ) {
+ ++it;
+ if ( !rule->field().isEmpty() && !rule->contents().isEmpty() &&
+ rule->function() == KMSearchRule::FuncContains &&
+ rule->field() == "<body>" ) return true;
+ }
+ return false;
+}
+
+void KMMsgIndex::slotAddMessage( Q_UINT32 serNum ) {
+ kdDebug( 5006 ) << "KMMsgIndex::slotAddMessage( . , " << serNum << " )" << endl;
+ if ( mState == s_error || mState == s_disabled ) return;
+
+ if ( mState == s_creating ) mAddedMsgs.push_back( serNum );
+ else mPendingMsgs.push_back( serNum );
+
+ if ( mState == s_idle ) mState = s_processing;
+ scheduleAction();
+}
+
+void KMMsgIndex::slotRemoveMessage( Q_UINT32 serNum ) {
+ kdDebug( 5006 ) << "KMMsgIndex::slotRemoveMessage( . , " << serNum << " )" << endl;
+ if ( mState == s_error || mState == s_disabled ) return;
+
+ if ( mState == s_idle ) mState = s_processing;
+ mRemovedMsgs.push_back( serNum );
+ scheduleAction();
+}
+
+void KMMsgIndex::scheduleAction() {
+#ifdef HAVE_INDEXLIB
+ if ( mState == s_willcreate || !mIndex ) return;
+ if ( !mSlowDown ) mTimer->start( 0 );
+#endif
+}
+
+void KMMsgIndex::removeMessage( Q_UINT32 serNum ) {
+ kdDebug( 5006 ) << "KMMsgIndex::removeMessage( " << serNum << " )" << endl;
+ if ( mState == s_error || mState == s_disabled ) return;
+
+#ifdef HAVE_INDEXLIB
+ mIndex->remove_doc( QString::number( serNum ).latin1() );
+ ++mMaintenanceCount;
+ if ( mMaintenanceCount > MaintenanceLimit && mRemovedMsgs.empty() ) {
+ QTimer::singleShot( 100, this, SLOT( maintenance() ) );
+ }
+#endif
+}
+
+QString KMMsgIndex::defaultPath() {
+ return KMKernel::localDataPath() + "text-index";
+}
+
+bool KMMsgIndex::creating() const {
+ return !mPendingMsgs.empty() || !mPendingFolders.empty();
+}
+
+KMMsgIndex::Search::Search( KMSearch* s ):
+ mSearch( s ),
+ mTimer( new QTimer( this, "mTimer" ) ),
+ mResidual( new KMSearchPattern ),
+ mState( s_starting ) {
+ connect( mTimer, SIGNAL( timeout() ), SLOT( act() ) );
+ mTimer->start( 0 );
+}
+
+KMMsgIndex::Search::~Search() {
+ delete mTimer;
+}
+
+void KMMsgIndex::Search::act() {
+ switch ( mState ) {
+ case s_starting: {
+ KMSearchPattern* pat = mSearch->searchPattern();
+ QString terms;
+ for ( KMSearchRule* rule = pat->first(); rule; rule = pat->next() ) {
+ Q_ASSERT( rule->function() == KMSearchRule::FuncContains );
+ terms += QString::fromLatin1( " %1 " ).arg( rule->contents() );
+ }
+
+ mValues = kmkernel->msgIndex()->simpleSearch( terms, 0 );
+ break;
+ }
+ case s_emitstopped:
+ mTimer->start( 0 );
+ mState = s_emitting;
+ // fall throu
+ case s_emitting:
+ if ( kapp->hasPendingEvents() ) {
+ //nah, some other time..
+ mTimer->start( 250 );
+ mState = s_emitstopped;
+ return;
+ }
+ for ( int i = 0; i != 16 && !mValues.empty(); ++i ) {
+ KMFolder* folder;
+ int index;
+ KMMsgDict::instance()->getLocation( mValues.back(), &folder, &index );
+ if ( folder &&
+ mSearch->inScope( folder ) &&
+ ( !mResidual || mResidual->matches( mValues.back() ) ) ) {
+
+ emit found( mValues.back() );
+ }
+ mValues.pop_back();
+ }
+ if ( mValues.empty() ) {
+ emit finished( true );
+ mState = s_done;
+ mTimer->stop();
+ delete this;
+ }
+ break;
+ default:
+ Q_ASSERT( 0 );
+ }
+}
+#include "index.moc"
+
diff --git a/kmail/index.h b/kmail/index.h
new file mode 100644
index 00000000..731f407f
--- /dev/null
+++ b/kmail/index.h
@@ -0,0 +1,218 @@
+#ifndef LPC_INDEX_H1110724080_INCLUDE_GUARD_
+#define LPC_INDEX_H1110724080_INCLUDE_GUARD_
+
+/* This file is part of KMail
+ * Copyright (C) 2005 Lus Pedro Coelho <luis@luispedro.org>
+ * (based on the old kmmsgindex by Sam Magnuson)
+ *
+ * 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 Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+
+#include <qobject.h>
+#include <qcstring.h>
+#include <qvaluelist.h>
+#include <qtimer.h>
+#include <config.h>
+#ifdef HAVE_INDEXLIB
+#include <indexlib/index.h>
+#include <indexlib/lockfile.h>
+#endif
+#include <vector>
+#include <set>
+
+class KMFolder;
+
+class KMSearch;
+class KMSearchRule;
+class KMSearchPattern;
+
+class KMMsgIndex : public QObject {
+ Q_OBJECT
+ public:
+ explicit KMMsgIndex( QObject* parent );
+ ~KMMsgIndex();
+
+ public:
+
+ /**
+ * Starts a query.
+ * Results will be returned assyncronously by signals.
+ *
+ * @return false if the query cannot be handled
+ */
+ bool startQuery( KMSearch* );
+ /**
+ * Stops a query. nop if the query isn't running anymore.
+ *
+ * @return true if the query was found
+ */
+ bool stopQuery( KMSearch* );
+
+ /**
+ * Just return all the uids where the pattern exists
+ */
+ std::vector<Q_UINT32> simpleSearch( QString, bool* ) const;
+
+ /**
+ * Returns whether the folder is indexable. Only local and dimap
+ * folders are currently indexable.
+ *
+ * Note that a folder might be indexable and not indexed if the user has
+ * disabled it. @see isIndexed
+ */
+ bool isIndexable( KMFolder* folder ) const;
+
+ /**
+ * Returns whether the folder has indexing enabled.
+ *
+ * This returns true immediatelly after indexing has been enabled
+ * even though the folder is probably still being indexed in the background.
+ */
+ bool isIndexed( KMFolder* folder ) const;
+
+ /**
+ * Returns whether the index is enabled
+ */
+ bool isEnabled() const { return mState != s_disabled; }
+ public slots:
+ /**
+ * Either enable or disable indexing.
+ *
+ * Calling setEnabled( true ) will start building the whole index,
+ * which is an expensive operation (time and disk-space).
+ *
+ * Calling setEnabled( false ) will remove the index immediatelly,
+ * freeing up disk-space.
+ */
+ void setEnabled( bool );
+
+ /**
+ * Change the indexing override for a given folder
+ *
+ * If called with true, will start indexing all the messages in the folder
+ * If called with false will remove all the messages in the folder
+ */
+ void setIndexingEnabled( KMFolder*, bool );
+
+ private slots:
+ /**
+ * Removes the index.
+ *
+ * If the index is enabled, then creation will start on the next time
+ * KMMsgIndex is constructed.
+ */
+ void clear();
+ void create();
+ void maintenance();
+
+ void act();
+ void removeSearch( QObject* );
+
+ void continueCreation();
+
+ void slotAddMessage( Q_UINT32 message );
+ void slotRemoveMessage( Q_UINT32 message );
+ private:
+ static QString defaultPath();
+
+ bool canHandleQuery( const KMSearchPattern* ) const;
+ int addMessage( Q_UINT32 );
+ void removeMessage( Q_UINT32 );
+
+ void scheduleAction();
+ bool creating() const;
+
+ public:
+ /**
+ * @internal
+ * DO NOT USE THIS CLASS
+ *
+ * It is conceptually a private class.
+ * Just needs to be public because of moc limitations
+ */
+ class Search;
+ private:
+
+ std::vector<Q_UINT32> mPendingMsgs;
+ std::vector<KMFolder*> mPendingFolders;
+ std::vector<Q_UINT32> mAddedMsgs;
+ std::vector<Q_UINT32> mRemovedMsgs;
+ std::vector<Q_UINT32> mExisting;
+
+ enum e_state {
+ s_idle, // doing nothing, index waiting
+ s_willcreate, // just constructed, create() scheduled (mIndex == 0)
+ s_creating, // creating the index from the messages
+ s_processing, // has messages to process
+ s_error, // an error occurred
+ s_disabled // disabled: the index is not working
+ } mState;
+
+ unsigned mMaintenanceCount;
+
+#ifdef HAVE_INDEXLIB
+ /**
+ * The lock below should be moved down into libindex itself
+ * where things can be handled with a lot more granularity
+ */
+ indexlib::detail::lockfile mLockFile;
+ //enum e_syncState { ss_none, ss_started, ss_synced } mSyncState;
+ //QTimer* mSyncTimer;
+
+ indexlib::index* mIndex;
+#endif
+ std::set<KMFolder*> mOpenedFolders;
+ std::vector<Search*> mSearches;
+ QCString mIndexPath;
+ QTimer* mTimer;
+ bool mSlowDown;
+};
+
+
+class KMMsgIndex::Search : public QObject {
+ Q_OBJECT
+ public:
+ explicit Search( KMSearch* s );
+ ~Search();
+ KMSearch* search() const { return mSearch; }
+ signals:
+ void found( Q_UINT32 );
+ void finished( bool );
+ private slots:
+ void act();
+ private:
+ KMSearch* mSearch;
+ QTimer* mTimer;
+ /**
+ * mResidual is the part of the pattern which is not
+ * handled by the index
+ */
+ KMSearchPattern* mResidual;
+ std::vector<Q_UINT32> mValues;
+ enum { s_none = 0, s_starting, s_emitting, s_emitstopped, s_done } mState;
+};
+
+#endif /* LPC_INDEX_H1110724080_INCLUDE_GUARD_ */
diff --git a/kmail/interfaces/Makefile.am b/kmail/interfaces/Makefile.am
new file mode 100644
index 00000000..df3d32a7
--- /dev/null
+++ b/kmail/interfaces/Makefile.am
@@ -0,0 +1,8 @@
+kmailincludedir = $(includedir)/kmail/interfaces
+kmailinclude_HEADERS = \
+ observer.h \
+ observable.h \
+ htmlwriter.h \
+ bodypart.h \
+ bodypartformatter.h \
+ bodyparturlhandler.h
diff --git a/kmail/interfaces/bodypart.h b/kmail/interfaces/bodypart.h
new file mode 100644
index 00000000..0a69dd0c
--- /dev/null
+++ b/kmail/interfaces/bodypart.h
@@ -0,0 +1,163 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ bodypart.h
+
+ This file is part of KMail's plugin interface.
+ Copyright (c) 2004 Marc Mutz <mutz@kde.org>,
+ Ingo Kloecker <kloecker@kde.org>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_INTERFACES_BODYPART_H__
+#define __KMAIL_INTERFACES_BODYPART_H__
+
+template <typename T> class QMemArray;
+typedef QMemArray<char> QByteArray;
+class QString;
+
+namespace KMail {
+ namespace Interface {
+
+ class Observer;
+ class Observable;
+
+ /**
+ @short interface of classes that implement status for BodyPartFormatters.
+ */
+ class BodyPartMemento {
+ public:
+ virtual ~BodyPartMemento() {}
+
+ /** If your BodyPartMemento implementation also implements the
+ KMail::Observer interface, simply implement these as
+ <code>return this;</code>, else as <code>return
+ 0;</code>. This is needed to avoid forcing a dependency of
+ plugins on internal KMail classes.
+ */
+ virtual Observer * asObserver() = 0;
+
+ /** If your BodyPartMemento implementation also implements the
+ KMail::Observable interface, simply implement these as
+ <code>return this;</code>, else as <code>return
+ 0;</code>. This is needed to avoid forcing a dependency of
+ plugins on internal KMail classes.
+ */
+ virtual Observable * asObservable() = 0;
+ };
+
+ /**
+ @short interface of message body parts.
+ */
+ class BodyPart {
+ public:
+ virtual ~BodyPart() {}
+
+ /**
+ @return a string respresentation of an URL that can be used
+ to invoke a BodyPartURLHandler for this body part.
+ */
+ virtual QString makeLink( const QString & path ) const = 0;
+
+ /**
+ @return the decoded (CTE, canonicalisation, and charset
+ encoding undone) text contained in the body part, or
+ QString::null, it the body part is not of type "text".
+ */
+ virtual QString asText() const = 0;
+
+ /**
+ @return the decoded (CTE undone) content of the body part, or
+ a null array if this body part instance is of type text.
+ */
+ virtual QByteArray asBinary() const = 0;
+
+ /**
+ @return the value of the content-type header field parameter
+ with name \a parameter, or QString::null, if that that
+ parameter is not present in the body's content-type header
+ field. RFC 2231 encoding is removed first.
+
+ Note that this method will suppress queries to certain
+ standard parameters (most notably "charset") to keep plugins
+ decent.
+
+ Note2 that this method preserves the case of the parameter
+ value returned. So, if the parameter you want to use defines
+ the value to be case-insensitive (such as the smime-type
+ parameter), you need to make sure you do the casemap yourself
+ before comparing to a reference value.
+ */
+ virtual QString contentTypeParameter( const char * parameter ) const = 0;
+
+ /**
+ @return the content of the content-description header field,
+ or QString::null if that header is not present in this body
+ part. RFC 2047 encoding is decoded first.
+ */
+ virtual QString contentDescription() const = 0;
+
+ //virtual int contentDisposition() const = 0;
+ /**
+ @return the value of the content-disposition header field
+ parameter with name \a parameter, or QString::null if that
+ parameter is not present in the body's content-disposition
+ header field. RFC 2231 encoding is removed first.
+
+ The notes made for contentTypeParameter() above apply here as
+ well.
+ */
+ virtual QString contentDispositionParameter( const char * parameter ) const = 0;
+
+ /**
+ @return whether this part already has it's complete body
+ fetched e.g. from an IMAP server.
+ */
+ virtual bool hasCompleteBody() const = 0;
+
+ /**
+ @return the BodyPartMemento set for this part, or null, if
+ none is set.
+ */
+ virtual BodyPartMemento * memento() const = 0;
+
+ /**
+ @return register an implementation of the BodyPartMemento
+ interface as a status object with this part.
+ */
+ virtual void setBodyPartMemento( BodyPartMemento * ) = 0;
+
+ enum Display { None, AsIcon, Inline };
+ /**
+ @return whether this body part should be displayed iconic or inline
+ */
+ virtual Display defaultDisplay() const = 0;
+ };
+
+ } // namespace Interface
+
+} // namespace KMail
+
+#endif // __KMAIL_INTERFACES_BODYPART_H__
diff --git a/kmail/interfaces/bodypartformatter.h b/kmail/interfaces/bodypartformatter.h
new file mode 100644
index 00000000..b31cb26b
--- /dev/null
+++ b/kmail/interfaces/bodypartformatter.h
@@ -0,0 +1,95 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ bodypartformatter.h
+
+ This file is part of KMail's plugin interface.
+ Copyright (c) 2004 Marc Mutz <mutz@kde.org>,
+ Ingo Kloecker <kloecker@kde.org>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_INTERFACE_BODYPARTFORMATTER_H__
+#define __KMAIL_INTERFACE_BODYPARTFORMATTER_H__
+
+namespace KMail {
+
+ class HtmlWriter;
+
+ namespace Interface {
+
+ class BodyPart;
+ class BodyPartURLHandler;
+
+ class BodyPartFormatter {
+ 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
+ @li Failed returned when formatting failed. Currently equivalent to Ok
+ */
+ enum Result { Ok, NeedContent, AsIcon, Failed };
+
+ /**
+ Format body part \a part by generating some HTML and writing
+ that to \a writer.
+
+ @return the result code (see above)
+ */
+ virtual Result format( BodyPart * part, KMail::HtmlWriter * writer ) const = 0;
+ };
+
+ /**
+ @short interface for BodyPartFormatter plugins
+
+ The interface is queried by for types, subtypes, and the
+ corresponding bodypart formatter, and the result inserted into
+ the bodypart formatter factory.
+
+ Subtype alone or both type and subtype may be "*", which is
+ taken as a wildcard, so that e.g. type=text subtype=* matches
+ any text subtype, but with lesser specificity than a concrete
+ mimetype such as text/plain. type=* is only allowed when
+ subtype=*, too.
+ */
+ class BodyPartFormatterPlugin {
+ public:
+ virtual ~BodyPartFormatterPlugin() {}
+
+ virtual const BodyPartFormatter * bodyPartFormatter( int idx ) const = 0;
+ virtual const char * type( int idx ) const = 0;
+ virtual const char * subtype( int idx ) const = 0;
+
+ virtual const BodyPartURLHandler * urlHandler( int idx ) const = 0;
+ };
+
+ } // namespace Interface
+
+} // namespace KMail
+
+#endif // __KMAIL_INTERFACE_BODYPARTFORMATTER_H__
diff --git a/kmail/interfaces/bodyparturlhandler.h b/kmail/interfaces/bodyparturlhandler.h
new file mode 100644
index 00000000..1fd8b16d
--- /dev/null
+++ b/kmail/interfaces/bodyparturlhandler.h
@@ -0,0 +1,105 @@
+/* -*- c++ -*-
+ interfaces/bodyparturlhandler.h
+
+ This file is part of KMail's plugin interface.
+ Copyright (c) 2003, 2004 Marc Mutz <mutz@kde.org>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_INTERFACE_BODYPARTURLHANDLER_H__
+#define __KMAIL_INTERFACE_BODYPARTURLHANDLER_H__
+
+class QString;
+class QPoint;
+
+namespace KMail {
+ class Callback;
+
+ namespace Interface {
+
+ class BodyPart;
+
+ /**
+ * @short An interface to body part reader link handlers
+ * @author Marc Mutz <mutz@kde.org>
+ *
+ * This interface is a condensed of variant of the more general
+ * @see URLHandler interface, designed to make bodypart-dependent
+ * link operations possible without exposing KMail-internal
+ * classes.
+ *
+ * Implementation-wise, these handlers are called as a nested
+ * Chain Of Responsibilty by an internal implementation of
+ * URLHandler.
+ *
+ * You can create a link whose handling is passed to this handler
+ * by using BodyPart::makeLink( const QString & path ). \a path is
+ * what * is passed back to the methods of this interface.
+ *
+ * Note that the BodyPart interface does not provide a means of
+ * learning the content type of the body part passed. This is
+ * intentional. It is expected that either separate
+ * BodyPartURLHandlers are created for these purposes or else the
+ * information encoded into the path parameter by the
+ * BodyPartFormatter.
+ */
+ class BodyPartURLHandler {
+ public:
+ virtual ~BodyPartURLHandler() {}
+
+ /** Called when LMB-clicking on a link in the reader. Should
+ start processing equivalent to "opening" the link.
+
+ @return true if the click was handled by this handler, false
+ otherwise.
+ */
+ virtual bool handleClick( BodyPart * part, const QString & path, Callback& c ) const = 0;
+
+ /** 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.
+
+ @return true if the right-click was handled by this handler,
+ false otherwise.
+ */
+ virtual bool handleContextMenuRequest( BodyPart * part, const QString & path, const QPoint & p ) const = 0;
+
+ /** Called when hovering over a link.
+
+ @return a string to be shown in the status bar while
+ hovering over this link or QString::null if the link was not
+ handled by this handler.
+ */
+ virtual QString statusBarMessage( BodyPart * part, const QString & path ) const = 0;
+ };
+
+ } // namespace Interface
+
+} // namespace KMail
+
+#endif // __KMAIL_INTERFACES_BODYPARTURLHANDLER_H__
+
diff --git a/kmail/interfaces/htmlwriter.h b/kmail/interfaces/htmlwriter.h
new file mode 100644
index 00000000..3621038b
--- /dev/null
+++ b/kmail/interfaces/htmlwriter.h
@@ -0,0 +1,120 @@
+/* -*- c++ -*-
+ interfaces/htmlwriter.h
+
+ This file is part of KMail's plugin interface.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_INTERFACES_HTMLWRITER_H__
+#define __KMAIL_INTERFACES_HTMLWRITER_H__
+
+class QCString;
+class QString;
+
+namespace KMail {
+
+ /**
+ * @short An interface for HTML sinks.
+ * @author Marc Mutz <mutz@kde.org>
+ *
+ */
+ namespace Interface {
+ class HtmlWriter {
+ public:
+ virtual ~HtmlWriter() {}
+
+ /** Signal the begin of stuff to write, and give the CSS definitions */
+ virtual void begin( const QString & cssDefinitions ) = 0;
+ /** Write out a chunk of text. No HTML escaping is performed. */
+ virtual void write( const QString & html ) = 0;
+ /** Signal the end of stuff to write. */
+ virtual void end() = 0;
+ };
+ }
+
+ /**
+ * @short An interface to HTML sinks
+ * @author Marc Mutz <mutz@kde.org>
+ *
+ * @deprecated KMail should be ported to Interface::HtmlWriter. This
+ * interface exposes internal working models. The queueing
+ * vs. writing() issues exposed here should be hidden by using two
+ * different implementations of KHTMLPartHtmlWriter: one for
+ * queueing, and one for writing. This should be fixed before the
+ * release, so we an keep the plugin interface stable.
+ *
+ * Operate this interface in one and only one of the following two
+ * modes:
+ *
+ * @section Sync Mode
+ *
+ * In sync mode, use #begin() to initiate a session, then
+ * #write() some chunks of HTML code and finally #end() the session.
+ *
+ * @section Async Mode
+ *
+ * In async mode, use #begin() to initialize a session, then
+ * #queue() some chunks of HTML code and finally end the
+ * session by calling #flush().
+ *
+ * Queued HTML code is fed to the html sink using a timer. For this
+ * to work, control must return to the event loop so timer events
+ * are delivered.
+ *
+ * @section Combined mode
+ *
+ * You may combine the two modes in the following way only. Any
+ * number of #write() calls can precede #queue() calls,
+ * but once a chunk has been queued, you @em must @em not
+ * #write() more data, only #queue() it.
+ *
+ * Naturally, whenever you queued data in a given session, that
+ * session must be ended by calling #flush(), not #end().
+ */
+ class HtmlWriter : public Interface::HtmlWriter {
+ public:
+ virtual ~HtmlWriter() {}
+
+ /** Stop all possibly pending processing in order to be able to
+ call #begin() again. */
+ virtual void reset() = 0;
+
+ virtual void queue( const QString & str ) = 0;
+ /** (Start) flushing internal buffers, if any. */
+ virtual void flush() = 0;
+
+ /**
+ * Embed a part with Content-ID @p contentId, using url @p url.
+ */
+ virtual void embedPart( const QCString & contentId, const QString & url ) = 0;
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_INTERFACES_HTMLWRITER_H__
+
diff --git a/kmail/interfaces/observable.h b/kmail/interfaces/observable.h
new file mode 100644
index 00000000..184ee318
--- /dev/null
+++ b/kmail/interfaces/observable.h
@@ -0,0 +1,57 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ observable.h
+
+ This file is part of KMail's plugin interface.
+ Copyright (c) 2004 Marc Mutz <mutz@kde.org>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_INTERFACES_OBSERVABLE_H__
+#define __KMAIL_INTERFACES_OBSERVABLE_H__
+
+namespace KMail {
+ namespace Interface {
+
+ class Observer;
+
+ /**
+ @short observable interface
+ */
+ class Observable {
+ public:
+ virtual ~Observable() {}
+
+ virtual void attach( Observer * obs ) = 0;
+ virtual void detach( Observer * obs ) = 0;
+ virtual void notify() = 0;
+ };
+
+ } // namespace Interface
+
+} // namespace KMail
+
+#endif // __KMAIL_INTERFACES_OBSERVABLE_H__
diff --git a/kmail/interfaces/observer.h b/kmail/interfaces/observer.h
new file mode 100644
index 00000000..5f1d9b88
--- /dev/null
+++ b/kmail/interfaces/observer.h
@@ -0,0 +1,55 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ observer.h
+
+ This file is part of KMail's plugin interface.
+ Copyright (c) 2004 Marc Mutz <mutz@kde.org>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_INTERFACES_OBSERVER_H__
+#define __KMAIL_INTERFACES_OBSERVER_H__
+
+namespace KMail {
+ namespace Interface {
+
+ class Observable;
+
+ /**
+ @short observer interface
+ */
+ class Observer {
+ public:
+ virtual ~Observer() {}
+
+ virtual void update( Observable * obs ) = 0;
+ };
+
+ } // namespace Interface
+
+} // namespace KMail
+
+#endif // __KMAIL_INTERFACES_OBSERVER_H__
diff --git a/kmail/interfaces/rulewidgethandler.h b/kmail/interfaces/rulewidgethandler.h
new file mode 100644
index 00000000..e8fdd802
--- /dev/null
+++ b/kmail/interfaces/rulewidgethandler.h
@@ -0,0 +1,81 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ interfaces/rulewidgethandler.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2004 Ingo Kloecker <kloecker@kde.org>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_INTERFACES_RULEWIDGETHANDLER_H__
+#define __KMAIL_INTERFACES_RULEWIDGETHANDLER_H__
+
+#include "../kmsearchpattern.h"
+
+class QWidget;
+class QWidgetStack;
+class QCString;
+class QString;
+class QObject;
+
+namespace KMail {
+ /**
+ * @short An interface to filter/search rule widget handlers
+ */
+ class RuleWidgetHandler {
+ public:
+ virtual ~RuleWidgetHandler() {}
+
+ virtual QWidget * createFunctionWidget( int number,
+ QWidgetStack *functionStack,
+ const QObject *receiver ) const = 0;
+ virtual QWidget * createValueWidget( int number,
+ QWidgetStack *valueStack,
+ const QObject *receiver ) const = 0;
+ virtual KMSearchRule::Function function( const QCString & field,
+ const QWidgetStack *functionStack ) const = 0;
+ virtual QString value( const QCString & field,
+ const QWidgetStack *functionStack,
+ const QWidgetStack *valueStack ) const = 0;
+ virtual QString prettyValue( const QCString & field,
+ const QWidgetStack *functionStack,
+ const QWidgetStack *valueStack ) const = 0;
+ virtual bool handlesField( const QCString & field ) const = 0;
+ virtual void reset( QWidgetStack *functionStack,
+ QWidgetStack *valueStack ) const = 0;
+ virtual bool setRule( QWidgetStack *functionStack,
+ QWidgetStack *valueStack,
+ const KMSearchRule *rule ) const = 0;
+ virtual bool update( const QCString & field,
+ QWidgetStack *functionStack,
+ QWidgetStack *valueStack ) const = 0;
+
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_INTERFACES_RULEWIDGETHANDLER_H__
+
diff --git a/kmail/interfaces/urlhandler.h b/kmail/interfaces/urlhandler.h
new file mode 100644
index 00000000..808f3196
--- /dev/null
+++ b/kmail/interfaces/urlhandler.h
@@ -0,0 +1,79 @@
+/* -*- c++ -*-
+ interfaces/urlhandler.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_INTERFACES_URLHANDLER_H__
+#define __KMAIL_INTERFACES_URLHANDLER_H__
+
+class KURL;
+
+class QString;
+class QPoint;
+class KMReaderWin;
+
+namespace KMail {
+ /**
+ * @short An interface to reader link handlers
+ * @author Marc Mutz <mutz@kde.org>
+ *
+ * The KMReaderWin parameters are temporary until such time as
+ * the Memento-store is in place.
+ */
+ class URLHandler {
+ public:
+ virtual ~URLHandler() {}
+
+ /** Called when LMB-clicking on a link in the reader. Should start
+ processing equivalent to "opening" the link.
+
+ @return true if the click was handled by this URLHandler,
+ false otherwise.
+ */
+ virtual bool handleClick( const KURL & url, KMReaderWin * w ) const = 0;
+ /** 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.
+
+ @return true if the right-click was handled by this
+ URLHandler, false otherwise.
+ */
+ virtual bool handleContextMenuRequest( const KURL & url, const QPoint & p, KMReaderWin * w ) const = 0;
+ /** Called when hovering over a link.
+
+ @return a string to be shown in the status bar while hovering
+ over this link.
+ */
+ virtual QString statusBarMessage( const KURL & url, KMReaderWin * w ) const = 0;
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_INTERFACES_URLHANDLER_H__
+
diff --git a/kmail/isubject.cpp b/kmail/isubject.cpp
new file mode 100644
index 00000000..13f8e890
--- /dev/null
+++ b/kmail/isubject.cpp
@@ -0,0 +1,39 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "isubject.h"
+#include "interfaces/observer.h"
+
+#include <qtl.h>
+
+#include <kdebug.h>
+
+namespace KMail {
+
+ ISubject::~ISubject()
+ {
+ mObserverList.clear();
+ }
+
+ void ISubject::attach( Interface::Observer * pObserver )
+ {
+ if ( qFind( mObserverList.begin(), mObserverList.end(), pObserver ) == mObserverList.end() )
+ mObserverList.push_back( pObserver );
+ }
+
+ void ISubject::detach( Interface::Observer * pObserver ) {
+ QValueVector<Interface::Observer*>::iterator it = qFind( mObserverList.begin(), mObserverList.end(), pObserver );
+ if ( it != mObserverList.end() )
+ mObserverList.erase( it );
+ }
+
+ void ISubject::notify()
+ {
+ kdDebug(5006) << "ISubject::notify " << mObserverList.size() << endl;
+ for ( QValueVector<Interface::Observer*>::iterator it = mObserverList.begin() ; it != mObserverList.end() ; ++it )
+ (*it)->update( this );
+ }
+
+}
+
diff --git a/kmail/isubject.h b/kmail/isubject.h
new file mode 100644
index 00000000..eeb03bb4
--- /dev/null
+++ b/kmail/isubject.h
@@ -0,0 +1,63 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ *
+ * This file is part of KMail, the KDE mail client.
+ * Copyright (c) 2003 Carsten Burghardt <burghardt@kde.org>
+ *
+ * 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 Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#ifndef __KMAIL_ISUBJECT_H__
+#define __KMAIL_ISUBJECT_H__
+
+#include "interfaces/observable.h"
+
+#include <qvaluevector.h>
+
+namespace KMail {
+
+ namespace Interface {
+ class Observer;
+ }
+
+ class ISubject : public Interface::Observable {
+ public :
+ virtual ~ISubject();
+
+ /** \reimp Attach the observer if it is not in the list yet */
+ void attach( Interface::Observer * );
+
+ /** \reimp Detach the observer */
+ void detach( Interface::Observer * );
+
+ /** \reimp Notify all observers ( call observer->update() ) */
+ void notify();
+
+ private:
+ QValueVector<Interface::Observer*> mObserverList;
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_ISUBJECT_H__
diff --git a/kmail/jobscheduler.cpp b/kmail/jobscheduler.cpp
new file mode 100644
index 00000000..cedb530b
--- /dev/null
+++ b/kmail/jobscheduler.cpp
@@ -0,0 +1,256 @@
+/**
+ * Copyright (c) 2004 David Faure <faure@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#include "jobscheduler.h"
+#include "kmfolder.h"
+#include "folderstorage.h"
+#include "kmfoldermgr.h"
+#include <kdebug.h>
+
+using namespace KMail;
+
+JobScheduler::JobScheduler( QObject* parent, const char* name )
+ : QObject( parent, name ), mTimer( this, "mTimer" ),
+ mPendingImmediateTasks( 0 ),
+ mCurrentTask( 0 ), mCurrentJob( 0 )
+{
+ connect( &mTimer, SIGNAL( timeout() ), SLOT( slotRunNextJob() ) );
+ // No need to start the internal timer yet, we wait for a task to be scheduled
+}
+
+
+JobScheduler::~JobScheduler()
+{
+ // delete tasks in tasklist (no autodelete for QValueList)
+ for( TaskList::Iterator it = mTaskList.begin(); it != mTaskList.end(); ++it ) {
+ delete (*it);
+ }
+ delete mCurrentTask;
+ delete mCurrentJob;
+}
+
+void JobScheduler::registerTask( ScheduledTask* task )
+{
+ bool immediate = task->isImmediate();
+ int typeId = task->taskTypeId();
+ if ( typeId ) {
+ KMFolder* folder = task->folder();
+ // Search for an identical task already scheduled
+ for( TaskList::Iterator it = mTaskList.begin(); it != mTaskList.end(); ++it ) {
+ if ( (*it)->taskTypeId() == typeId && (*it)->folder() == folder ) {
+#ifdef DEBUG_SCHEDULER
+ kdDebug(5006) << "JobScheduler: already having task type " << typeId << " for folder " << folder->label() << endl;
+#endif
+ delete task;
+ if ( !mCurrentTask && immediate ) {
+ ScheduledTask* task = *it;
+ removeTask( it );
+ runTaskNow( task );
+ }
+ return;
+ }
+ }
+ // Note that scheduling an identical task as the one currently running is allowed.
+ }
+ if ( !mCurrentTask && immediate )
+ runTaskNow( task );
+ else {
+#ifdef DEBUG_SCHEDULER
+ kdDebug(5006) << "JobScheduler: adding task " << task << " (type " << task->taskTypeId()
+ << ") for folder " << task->folder() << " " << task->folder()->label() << endl;
+#endif
+ mTaskList.append( task );
+ if ( immediate )
+ ++mPendingImmediateTasks;
+ if ( !mCurrentTask && !mTimer.isActive() )
+ restartTimer();
+ }
+}
+
+void JobScheduler::removeTask( TaskList::Iterator& it )
+{
+ if ( (*it)->isImmediate() )
+ --mPendingImmediateTasks;
+ mTaskList.remove( it );
+}
+
+void JobScheduler::notifyOpeningFolder( KMFolder* folder )
+{
+ if ( mCurrentTask && mCurrentTask->folder() == folder ) {
+ if ( mCurrentJob->isOpeningFolder() ) { // set when starting a job for this folder
+#ifdef DEBUG_SCHEDULER
+ kdDebug(5006) << "JobScheduler: got the opening-notification for " << folder->label() << " as expected." << endl;
+#endif
+ } else {
+ // Jobs scheduled from here should always be cancellable.
+ // One exception though, is when ExpireJob does its final KMMoveCommand.
+ // Then that command shouldn't kill its own parent job just because it opens a folder...
+ if ( mCurrentJob->isCancellable() )
+ interruptCurrentTask();
+ }
+ }
+}
+
+void JobScheduler::interruptCurrentTask()
+{
+ Q_ASSERT( mCurrentTask );
+#ifdef DEBUG_SCHEDULER
+ kdDebug(5006) << "JobScheduler: interrupting job " << mCurrentJob << " for folder " << mCurrentTask->folder()->label() << endl;
+#endif
+ // File it again. This will either delete it or put it in mTaskList.
+ registerTask( mCurrentTask );
+ mCurrentTask = 0;
+ mCurrentJob->kill(); // This deletes the job and calls slotJobFinished!
+}
+
+void JobScheduler::slotRunNextJob()
+{
+ while ( !mCurrentJob ) {
+#ifdef DEBUG_SCHEDULER
+ kdDebug(5006) << "JobScheduler: slotRunNextJob" << endl;
+#endif
+ Q_ASSERT( mCurrentTask == 0 );
+ ScheduledTask* task = 0;
+ // Find a task suitable for being run
+ for( TaskList::Iterator it = mTaskList.begin(); it != mTaskList.end(); ++it ) {
+ // Remove if folder died
+ KMFolder* folder = (*it)->folder();
+ if ( folder == 0 ) {
+#ifdef DEBUG_SCHEDULER
+ kdDebug(5006) << " folder for task " << (*it) << " was deleted" << endl;
+#endif
+ removeTask( it );
+ if ( !mTaskList.isEmpty() )
+ slotRunNextJob(); // to avoid the mess with invalid iterators :)
+ else
+ mTimer.stop();
+ return;
+ }
+ // The condition is that the folder must be unused (not open)
+ // But first we ask search folders to release their access to it
+ kmkernel->searchFolderMgr()->tryReleasingFolder( folder );
+#ifdef DEBUG_SCHEDULER
+ kdDebug(5006) << " looking at folder " << folder->label()
+ << " " << folder->location()
+ << " isOpened=" << (*it)->folder()->isOpened() << endl;
+#endif
+ if ( !folder->isOpened() ) {
+ task = *it;
+ removeTask( it );
+ break;
+ }
+ }
+
+ if ( !task ) // found nothing to run, i.e. folder was opened
+ return; // Timer keeps running, i.e. try again in 1 minute
+
+ runTaskNow( task );
+ } // If nothing to do for that task, loop and find another one to run
+}
+
+void JobScheduler::restartTimer()
+{
+ if ( mPendingImmediateTasks > 0 )
+ slotRunNextJob();
+ else
+ {
+#ifdef DEBUG_SCHEDULER
+ mTimer.start( 10000 ); // 10 seconds
+#else
+ mTimer.start( 1 * 60000 ); // 1 minute
+#endif
+ }
+}
+
+void JobScheduler::runTaskNow( ScheduledTask* task )
+{
+ Q_ASSERT( mCurrentTask == 0 );
+ if ( mCurrentTask ) {
+ interruptCurrentTask();
+ }
+ mCurrentTask = task;
+ mTimer.stop();
+ mCurrentJob = mCurrentTask->run();
+#ifdef DEBUG_SCHEDULER
+ kdDebug(5006) << "JobScheduler: task " << mCurrentTask
+ << " (type " << mCurrentTask->taskTypeId() << ")"
+ << " for folder " << mCurrentTask->folder()->label()
+ << " returned job " << mCurrentJob << " "
+ << ( mCurrentJob?mCurrentJob->className():0 ) << endl;
+#endif
+ if ( !mCurrentJob ) { // nothing to do, e.g. folder deleted
+ delete mCurrentTask;
+ mCurrentTask = 0;
+ if ( !mTaskList.isEmpty() )
+ restartTimer();
+ return;
+ }
+ // Register the job in the folder. This makes it autodeleted if the folder is deleted.
+ mCurrentTask->folder()->storage()->addJob( mCurrentJob );
+ connect( mCurrentJob, SIGNAL( finished() ), this, SLOT( slotJobFinished() ) );
+ mCurrentJob->start();
+}
+
+void JobScheduler::slotJobFinished()
+{
+ // Do we need to test for mCurrentJob->error()? What do we do then?
+#ifdef DEBUG_SCHEDULER
+ kdDebug(5006) << "JobScheduler: slotJobFinished" << endl;
+#endif
+ delete mCurrentTask;
+ mCurrentTask = 0;
+ mCurrentJob = 0;
+ if ( !mTaskList.isEmpty() )
+ restartTimer();
+}
+
+// DCOP call to pause any background jobs
+void JobScheduler::pause()
+{
+ mPendingImmediateTasks = 0;
+ if ( mCurrentJob && mCurrentJob->isCancellable() )
+ interruptCurrentTask();
+ mTimer.stop();
+}
+
+void JobScheduler::resume()
+{
+ restartTimer();
+}
+
+////
+
+KMail::ScheduledJob::ScheduledJob( KMFolder* folder, bool immediate )
+ : FolderJob( 0, tOther, folder ), mImmediate( immediate ),
+ mOpeningFolder( false )
+{
+ mCancellable = true;
+ mSrcFolder = folder;
+}
+
+#include "jobscheduler.moc"
diff --git a/kmail/jobscheduler.h b/kmail/jobscheduler.h
new file mode 100644
index 00000000..531e51a5
--- /dev/null
+++ b/kmail/jobscheduler.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2004 David Faure <faure@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#ifndef KMAIL_JOBSCHEDULER_H
+#define KMAIL_JOBSCHEDULER_H
+
+#include <qobject.h>
+#include <qvaluelist.h>
+#include <qguardedptr.h>
+#include <qtimer.h>
+
+#include "folderjob.h"
+
+// If this define is set, JobScheduler will show debug output, and related kmkernel timers will be shortened
+// This is for debugging purposes only, don't commit with it.
+//#define DEBUG_SCHEDULER
+
+class KMFolder;
+namespace KMail {
+
+class FolderJob;
+class ScheduledJob;
+
+/**
+ * A scheduled task is some information about a folder job that should be run later.
+ * As long as it's not running, it's called a "task", i.e. something that needs to be done.
+ * Tasks are held in the JobScheduler.
+ */
+class ScheduledTask {
+public:
+ /// Create a scheduled task for a given folder
+ /// If @p immediate is true, the scheduler will run this task as soon
+ /// as possible (but won't interrupt a currently running job for it)
+ ScheduledTask( KMFolder* folder, bool immediate )
+ : mCurrentFolder( folder ), mImmediate( immediate ) {}
+ virtual ~ScheduledTask() {}
+
+ /// Run this task, i.e. create a job for it.
+ /// Important: the job's execute() method must either call open() on the
+ /// folder or storage immediately, or abort (deleting itself).
+ /// Usually, that job should also be cancellable.
+ /// Otherwise (if the open() is delayed) an unrelated open() could happen first
+ /// and mess things up.
+ /// If for some reason (e.g. folder deleted) nothing should be done, return 0.
+ virtual ScheduledJob* run() = 0;
+
+ /// An identifier for the type of task (a bit like QListViewItem::rtti)
+ /// This allows to automatically prevent two identical tasks from being scheduled
+ /// for the same folder. To circumvent this feature and make every task
+ /// unique, return 0 here.
+ virtual int taskTypeId() const = 0;
+
+ /// The folder which this task is about, 0 if it was deleted meanwhile.
+ KMFolder* folder() const { return mCurrentFolder; }
+
+ bool isImmediate() const { return mImmediate; }
+
+private:
+ QGuardedPtr<KMFolder> mCurrentFolder;
+ bool mImmediate;
+};
+
+/**
+ * The unique JobScheduler instance (owned by kmkernel) implements "background processing"
+ * of folder operations (like expiration and compaction). Tasks (things to be done)
+ * are registered with the JobScheduler, and it will execute them one at a time,
+ * separated with a 1-minute timer. The jobs themselves should use timers to avoid
+ * using too much CPU for too long. Tasks for opened folders are not executed until
+ * the folder is closed.
+ */
+class JobScheduler : public QObject
+{
+ Q_OBJECT
+public:
+ JobScheduler( QObject* parent, const char* name = 0 );
+ ~JobScheduler();
+
+ /// Register a task to be done for a given folder
+ /// The ownership of the task is transferred to the JobScheduler
+ void registerTask( ScheduledTask* task );
+
+ /// Called by [implementations of] FolderStorage::open()
+ /// Interrupt any running job for this folder and re-schedule it for later
+ void notifyOpeningFolder( KMFolder* folder );
+
+ // DCOP calls
+ void pause();
+ void resume();
+
+private slots:
+ /// Called by a timer to run the next job
+ void slotRunNextJob();
+
+ /// Called when the current job terminates
+ void slotJobFinished();
+
+private:
+ void restartTimer();
+ void interruptCurrentTask();
+ void runTaskNow( ScheduledTask* task );
+ typedef QValueList<ScheduledTask *> TaskList;
+ void removeTask( TaskList::Iterator& it );
+private:
+ TaskList mTaskList; // FIFO of tasks to be run
+
+ QTimer mTimer;
+ int mPendingImmediateTasks;
+
+ /// Information about the currently running job, if any
+ ScheduledTask* mCurrentTask;
+ ScheduledJob* mCurrentJob;
+};
+
+/**
+ * Base class for scheduled jobs
+ */
+class ScheduledJob : public FolderJob
+{
+public:
+ ScheduledJob( KMFolder* folder, bool immediate );
+
+ bool isOpeningFolder() const { return mOpeningFolder; }
+
+protected:
+ bool mImmediate;
+ bool mOpeningFolder;
+};
+
+} // namespace
+
+#endif /* KMAIL_JOBSCHEDULER_H */
diff --git a/kmail/kcm_kmail.cpp b/kmail/kcm_kmail.cpp
new file mode 100644
index 00000000..985acf6d
--- /dev/null
+++ b/kmail/kcm_kmail.cpp
@@ -0,0 +1,85 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ * kmail: KDE mail client
+ * This file: Copyright (C) 2000 Espen Sand, espen@kde.org
+ * Copyright (C) 2001-2003 Marc Mutz, mutz@kde.org
+ * Contains code segments and ideas from earlier kmail dialog code.
+ *
+ * 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.
+ *
+ */
+
+// This must be first
+#include <config.h>
+#include "configuredialog.h"
+#include "configuredialog_p.h"
+#include <kcmodule.h>
+
+//----------------------------
+// KCM stuff
+//----------------------------
+extern "C"
+{
+ KDE_EXPORT KCModule *create_kmail_config_misc( QWidget *parent, const char * )
+ {
+ MiscPage *page = new MiscPage( parent, "kcmkmail_config_misc" );
+ return page;
+ }
+}
+
+extern "C"
+{
+ KDE_EXPORT KCModule *create_kmail_config_appearance( QWidget *parent, const char * )
+ {
+ AppearancePage *page =
+ new AppearancePage( parent, "kcmkmail_config_appearance" );
+ return page;
+ }
+}
+
+extern "C"
+{
+ KDE_EXPORT KCModule *create_kmail_config_composer( QWidget *parent, const char * )
+ {
+ ComposerPage *page = new ComposerPage( parent, "kcmkmail_config_composer" );
+ return page;
+ }
+}
+
+extern "C"
+{
+ KDE_EXPORT KCModule *create_kmail_config_identity( QWidget *parent, const char * )
+ {
+ IdentityPage *page = new IdentityPage( parent, "kcmkmail_config_identity" );
+ return page;
+ }
+}
+
+extern "C"
+{
+ KDE_EXPORT KCModule *create_kmail_config_accounts( QWidget *parent, const char * )
+ {
+ AccountsPage *page = new AccountsPage( parent, "kcmkmail_config_accounts" );
+ return page;
+ }
+}
+
+extern "C"
+{
+ KDE_EXPORT KCModule *create_kmail_config_security( QWidget *parent, const char * )
+ {
+ SecurityPage *page = new SecurityPage( parent, "kcmkmail_config_security" );
+ return page;
+ }
+}
diff --git a/kmail/kcursorsaver.h b/kmail/kcursorsaver.h
new file mode 100644
index 00000000..930cab6f
--- /dev/null
+++ b/kmail/kcursorsaver.h
@@ -0,0 +1,61 @@
+#ifndef kcursorsaver_h
+#define kcursorsaver_h
+
+#include <qcursor.h>
+#include <qapplication.h>
+
+/**
+ * @short sets a cursor and makes sure it's restored on destruction
+ * Create a KCursorSaver object when you want to set the cursor.
+ * As soon as it gets out of scope, it will restore the original
+ * cursor.
+ */
+class KCursorSaver : public Qt
+{
+public:
+ /// constructor taking QCursor shapes
+ KCursorSaver(Qt::CursorShape shape) {
+ QApplication::setOverrideCursor( QCursor(shape) );
+ inited = true;
+ }
+
+ /// copy constructor. The right side won't restore the cursor
+ KCursorSaver( const KCursorSaver &rhs ) {
+ *this = rhs;
+ }
+
+ /// restore the cursor
+ ~KCursorSaver() {
+ if (inited)
+ QApplication::restoreOverrideCursor();
+ }
+
+ /// call this to explitly restore the cursor
+ inline void restoreCursor(void) {
+ QApplication::restoreOverrideCursor();
+ inited = false;
+ }
+
+protected:
+ void operator=( const KCursorSaver &rhs ) {
+ inited = rhs.inited;
+ rhs.inited = false;
+ }
+
+private:
+ mutable bool inited;
+};
+
+/**
+ * convenience functions
+ */
+namespace KBusyPtr {
+ inline KCursorSaver idle() {
+ return KCursorSaver(QCursor::ArrowCursor);
+ }
+ inline KCursorSaver busy() {
+ return KCursorSaver(QCursor::WaitCursor);
+ }
+}
+
+#endif /*kbusyptr_h_*/
diff --git a/kmail/keyresolver.cpp b/kmail/keyresolver.cpp
new file mode 100644
index 00000000..2161031f
--- /dev/null
+++ b/kmail/keyresolver.cpp
@@ -0,0 +1,1615 @@
+/* -*- c++ -*-
+ keyresolver.cpp
+
+ This file is part of libkleopatra, the KDE keymanagement library
+ Copyright (c) 2004 Klarälvdalens Datakonsult AB
+
+ Based on kpgp.cpp
+ Copyright (C) 2001,2002 the KPGP authors
+ See file libkdenetwork/AUTHORS.kpgp for details
+
+ Libkleopatra 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.
+
+ Libkleopatra is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "keyresolver.h"
+
+#include "kcursorsaver.h"
+#include "kleo_util.h"
+
+#include <libemailfunctions/email.h>
+#include <ui/keyselectiondialog.h>
+#include <kleo/cryptobackendfactory.h>
+#include <kleo/keylistjob.h>
+#include <kleo/dn.h>
+
+#include <gpgmepp/key.h>
+#include <gpgmepp/keylistresult.h>
+
+#include <kabc/stdaddressbook.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kinputdialog.h>
+#include <kmessagebox.h>
+
+#include <qstringlist.h>
+#include <qtl.h>
+
+#include <time.h>
+
+#include <algorithm>
+#include <memory>
+#include <iterator>
+#include <functional>
+#include <map>
+#include <set>
+#include <iostream>
+#include <cassert>
+
+
+//
+// some predicates to be used in STL algorithms:
+//
+
+static inline bool EmptyKeyList( const Kleo::KeyApprovalDialog::Item & item ) {
+ return item.keys.empty();
+}
+
+static inline QString ItemDotAddress( const Kleo::KeyResolver::Item & item ) {
+ return item.address;
+}
+
+static inline bool ApprovalNeeded( const Kleo::KeyResolver::Item & item ) {
+ return item.pref == Kleo::UnknownPreference || item.pref == Kleo::NeverEncrypt || item.keys.empty() ;
+}
+
+static inline Kleo::KeyResolver::Item
+CopyKeysAndEncryptionPreferences( const Kleo::KeyResolver::Item & oldItem,
+ const Kleo::KeyApprovalDialog::Item & newItem ) {
+ return Kleo::KeyResolver::Item( oldItem.address, newItem.keys, newItem.pref, oldItem.signPref, oldItem.format );
+}
+
+static inline bool ByKeyID( const GpgME::Key & left, const GpgME::Key & right ) {
+ return qstrcmp( left.keyID(), right.keyID() ) < 0 ;
+}
+
+static inline bool WithRespectToKeyID( const GpgME::Key & left, const GpgME::Key & right ) {
+ return qstrcmp( left.keyID(), right.keyID() ) == 0 ;
+}
+
+static bool ValidTrustedOpenPGPEncryptionKey( const GpgME::Key & key ) {
+ if ( key.protocol() != GpgME::Context::OpenPGP ) {
+ return false;
+ }
+#if 0
+ if ( key.isRevoked() )
+ kdWarning() << " is revoked" << endl;
+ if ( key.isExpired() )
+ kdWarning() << " is expired" << endl;
+ if ( key.isDisabled() )
+ kdWarning() << " is disabled" << endl;
+ if ( !key.canEncrypt() )
+ kdWarning() << " can't encrypt" << endl;
+#endif
+ if ( key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canEncrypt() )
+ return false;
+ const std::vector<GpgME::UserID> uids = key.userIDs();
+ for ( std::vector<GpgME::UserID>::const_iterator it = uids.begin() ; it != uids.end() ; ++it ) {
+ if ( !it->isRevoked() && it->validity() != GpgME::UserID::Marginal )
+ return true;
+#if 0
+ else
+ if ( it->isRevoked() )
+ kdWarning() << "a userid is revoked" << endl;
+ else
+ kdWarning() << "bad validity " << it->validity() << endl;
+#endif
+ }
+ return false;
+}
+
+static bool ValidTrustedSMIMEEncryptionKey( const GpgME::Key & key ) {
+ if ( key.protocol() != GpgME::Context::CMS )
+ return false;
+ if ( key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canEncrypt() )
+ return false;
+ return true;
+}
+
+static inline bool ValidTrustedEncryptionKey( const GpgME::Key & key ) {
+ switch ( key.protocol() ) {
+ case GpgME::Context::OpenPGP:
+ return ValidTrustedOpenPGPEncryptionKey( key );
+ case GpgME::Context::CMS:
+ return ValidTrustedSMIMEEncryptionKey( key );
+ default:
+ return false;
+ }
+}
+
+static inline bool ValidSigningKey( const GpgME::Key & key ) {
+ if ( key.isRevoked() || key.isExpired() || key.isDisabled() || !key.canSign() )
+ return false;
+ return key.hasSecret();
+}
+
+static inline bool ValidOpenPGPSigningKey( const GpgME::Key & key ) {
+ return key.protocol() == GpgME::Context::OpenPGP && ValidSigningKey( key );
+}
+
+static inline bool ValidSMIMESigningKey( const GpgME::Key & key ) {
+ return key.protocol() == GpgME::Context::CMS && ValidSigningKey( key );
+}
+
+static inline bool NotValidTrustedOpenPGPEncryptionKey( const GpgME::Key & key ) {
+ return !ValidTrustedOpenPGPEncryptionKey( key );
+}
+
+static inline bool NotValidTrustedSMIMEEncryptionKey( const GpgME::Key & key ) {
+ return !ValidTrustedSMIMEEncryptionKey( key );
+}
+
+static inline bool NotValidTrustedEncryptionKey( const GpgME::Key & key ) {
+ return !ValidTrustedEncryptionKey( key );
+}
+
+static inline bool NotValidSigningKey( const GpgME::Key & key ) {
+ return !ValidSigningKey( key );
+}
+
+static inline bool NotValidOpenPGPSigningKey( const GpgME::Key & key ) {
+ return !ValidOpenPGPSigningKey( key );
+}
+
+static inline bool NotValidSMIMESigningKey( const GpgME::Key & key ) {
+ return !ValidSMIMESigningKey( key );
+}
+
+static QStringList keysAsStrings( const std::vector<GpgME::Key>& keys ) {
+ QStringList strings;
+ for ( std::vector<GpgME::Key>::const_iterator it = keys.begin() ; it != keys.end() ; ++it ) {
+ assert( !(*it).userID(0).isNull() );
+ QString keyLabel = QString::fromUtf8( (*it).userID(0).email() );
+ if ( keyLabel.isEmpty() )
+ keyLabel = QString::fromUtf8( (*it).userID(0).name() );
+ if ( keyLabel.isEmpty() )
+ keyLabel = QString::fromUtf8( (*it).userID(0).id() );
+ strings.append( keyLabel );
+ }
+ return strings;
+}
+
+static inline std::vector<GpgME::Key> TrustedOrConfirmed( const std::vector<GpgME::Key> & keys ) {
+
+ std::vector<GpgME::Key> fishies;
+ std::vector<GpgME::Key> ickies;
+ std::vector<GpgME::Key>::const_iterator it = keys.begin();
+ const std::vector<GpgME::Key>::const_iterator end = keys.end();
+ for ( ; it != end ; it++ ) {
+ const GpgME::Key key = *it;
+ assert( ValidTrustedEncryptionKey( key ) );
+ const std::vector<GpgME::UserID> uids = key.userIDs();
+ for ( std::vector<GpgME::UserID>::const_iterator it = uids.begin() ; it != uids.end() ; ++it ) {
+ if ( !it->isRevoked() && it->validity() == GpgME::UserID::Marginal ) {
+ fishies.push_back( key );
+ break;
+ }
+ if ( !it->isRevoked() && it->validity() < GpgME::UserID::Never ) {
+ ickies.push_back( key );
+ break;
+ }
+ }
+ }
+
+ if ( fishies.empty() && ickies.empty() )
+ return keys;
+
+ // if some keys are not fully trusted, let the user confirm their use
+ QString msg = i18n("One or more of your configured OpenPGP encryption "
+ "keys or S/MIME certificates is not fully trusted "
+ "for encryption.");
+
+ if ( !fishies.empty() ) {
+ // certificates can't have marginal trust
+ msg += i18n( "\nThe following keys are only marginally trusted: \n");
+ msg += keysAsStrings( fishies ).join(",");
+ }
+ if ( !ickies.empty() ) {
+ msg += i18n( "\nThe following keys or certificates have unknown trust level: \n");
+ msg += keysAsStrings( ickies ).join(",");
+ }
+
+ if( KMessageBox::warningContinueCancel( 0, msg, i18n("Not Fully Trusted Encryption Keys"),
+ KStdGuiItem::cont(),
+ "not fully trusted encryption key warning" )
+ == KMessageBox::Continue )
+ return keys;
+ else
+ return std::vector<GpgME::Key>();
+}
+
+namespace {
+ struct IsNotForFormat : public std::unary_function<GpgME::Key,bool> {
+ IsNotForFormat( Kleo::CryptoMessageFormat f ) : format( f ) {}
+
+ bool operator()( const GpgME::Key & key ) const {
+ return
+ ( isOpenPGP( format ) && key.protocol() != GpgME::Context::OpenPGP ) ||
+ ( isSMIME( format ) && key.protocol() != GpgME::Context::CMS );
+ }
+
+ const Kleo::CryptoMessageFormat format;
+ };
+}
+
+
+
+class Kleo::KeyResolver::SigningPreferenceCounter : public std::unary_function<Kleo::KeyResolver::Item,void> {
+public:
+ SigningPreferenceCounter()
+ : mTotal( 0 ),
+ mUnknownSigningPreference( 0 ),
+ mNeverSign( 0 ),
+ mAlwaysSign( 0 ),
+ mAlwaysSignIfPossible( 0 ),
+ mAlwaysAskForSigning( 0 ),
+ mAskSigningWheneverPossible( 0 )
+ {
+
+ }
+ void operator()( const Kleo::KeyResolver::Item & item );
+#define make_int_accessor(x) unsigned int num##x() const { return m##x; }
+ make_int_accessor(UnknownSigningPreference)
+ make_int_accessor(NeverSign)
+ make_int_accessor(AlwaysSign)
+ make_int_accessor(AlwaysSignIfPossible)
+ make_int_accessor(AlwaysAskForSigning)
+ make_int_accessor(AskSigningWheneverPossible)
+ make_int_accessor(Total)
+#undef make_int_accessor
+private:
+ unsigned int mTotal;
+ unsigned int mUnknownSigningPreference, mNeverSign, mAlwaysSign,
+ mAlwaysSignIfPossible, mAlwaysAskForSigning, mAskSigningWheneverPossible;
+};
+
+void Kleo::KeyResolver::SigningPreferenceCounter::operator()( const Kleo::KeyResolver::Item & item ) {
+ switch ( item.signPref ) {
+#define CASE(x) case x: ++m##x; break
+ CASE(UnknownSigningPreference);
+ CASE(NeverSign);
+ CASE(AlwaysSign);
+ CASE(AlwaysSignIfPossible);
+ CASE(AlwaysAskForSigning);
+ CASE(AskSigningWheneverPossible);
+#undef CASE
+ }
+ ++mTotal;
+}
+
+
+
+class Kleo::KeyResolver::EncryptionPreferenceCounter : public std::unary_function<Item,void> {
+ const Kleo::KeyResolver * _this;
+public:
+ EncryptionPreferenceCounter( const Kleo::KeyResolver * kr, EncryptionPreference defaultPreference )
+ : _this( kr ),
+ mDefaultPreference( defaultPreference ),
+ mTotal( 0 ),
+ mNoKey( 0 ),
+ mNeverEncrypt( 0 ),
+ mUnknownPreference( 0 ),
+ mAlwaysEncrypt( 0 ),
+ mAlwaysEncryptIfPossible( 0 ),
+ mAlwaysAskForEncryption( 0 ),
+ mAskWheneverPossible( 0 )
+ {
+
+ }
+ void operator()( Item & item );
+
+#define make_int_accessor(x) unsigned int num##x() const { return m##x; }
+ make_int_accessor(NoKey)
+ make_int_accessor(NeverEncrypt)
+ make_int_accessor(UnknownPreference)
+ make_int_accessor(AlwaysEncrypt)
+ make_int_accessor(AlwaysEncryptIfPossible)
+ make_int_accessor(AlwaysAskForEncryption)
+ make_int_accessor(AskWheneverPossible)
+ make_int_accessor(Total)
+#undef make_int_accessor
+private:
+ EncryptionPreference mDefaultPreference;
+ unsigned int mTotal;
+ unsigned int mNoKey;
+ unsigned int mNeverEncrypt, mUnknownPreference, mAlwaysEncrypt,
+ mAlwaysEncryptIfPossible, mAlwaysAskForEncryption, mAskWheneverPossible;
+};
+
+void Kleo::KeyResolver::EncryptionPreferenceCounter::operator()( Item & item ) {
+ 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);
+ CASE(UnknownPreference);
+ CASE(AlwaysEncrypt);
+ CASE(AlwaysEncryptIfPossible);
+ CASE(AlwaysAskForEncryption);
+ CASE(AskWheneverPossible);
+#undef CASE
+ }
+ ++mTotal;
+}
+
+namespace {
+
+ class FormatPreferenceCounterBase : public std::unary_function<Kleo::KeyResolver::Item,void> {
+ public:
+ FormatPreferenceCounterBase()
+ : mTotal( 0 ),
+ mInlineOpenPGP( 0 ),
+ mOpenPGPMIME( 0 ),
+ mSMIME( 0 ),
+ mSMIMEOpaque( 0 )
+ {
+
+ }
+
+#define make_int_accessor(x) unsigned int num##x() const { return m##x; }
+ make_int_accessor(Total)
+ make_int_accessor(InlineOpenPGP)
+ make_int_accessor(OpenPGPMIME)
+ make_int_accessor(SMIME)
+ make_int_accessor(SMIMEOpaque)
+#undef make_int_accessor
+
+ unsigned int numOf( Kleo::CryptoMessageFormat f ) const {
+ switch ( f ) {
+#define CASE(x) case Kleo::x##Format: return m##x
+ CASE(InlineOpenPGP);
+ CASE(OpenPGPMIME);
+ CASE(SMIME);
+ CASE(SMIMEOpaque);
+#undef CASE
+ default: return 0;
+ }
+ }
+
+ protected:
+ unsigned int mTotal;
+ unsigned int mInlineOpenPGP, mOpenPGPMIME, mSMIME, mSMIMEOpaque;
+ };
+
+ class EncryptionFormatPreferenceCounter : public FormatPreferenceCounterBase {
+ public:
+ EncryptionFormatPreferenceCounter() : FormatPreferenceCounterBase() {}
+ void operator()( const Kleo::KeyResolver::Item & item );
+ };
+
+ class SigningFormatPreferenceCounter : public FormatPreferenceCounterBase {
+ public:
+ SigningFormatPreferenceCounter() : FormatPreferenceCounterBase() {}
+ void operator()( const Kleo::KeyResolver::Item & item );
+ };
+
+#define CASE(x) if ( item.format & Kleo::x##Format ) ++m##x;
+ 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() ) {
+ CASE(OpenPGPMIME);
+ CASE(InlineOpenPGP);
+ }
+ if ( item.format & (Kleo::SMIMEFormat|Kleo::SMIMEOpaqueFormat) &&
+ std::find_if( item.keys.begin(), item.keys.end(),
+ ValidTrustedSMIMEEncryptionKey ) != item.keys.end() ) {
+ CASE(SMIME);
+ CASE(SMIMEOpaque);
+ }
+ ++mTotal;
+ }
+
+ void SigningFormatPreferenceCounter::operator()( const Kleo::KeyResolver::Item & item ) {
+ CASE(InlineOpenPGP);
+ CASE(OpenPGPMIME);
+ CASE(SMIME);
+ CASE(SMIMEOpaque);
+ ++mTotal;
+ }
+#undef CASE
+
+} // anon namespace
+
+static QString canonicalAddress( const QString & _address ) {
+ const QString address = KPIM::getEmailAddress( _address );
+ if ( address.find('@') == -1 ) {
+ // local address
+ //char hostname[1024];
+ //gethostname(hostname,1024);
+ //return address + '@' + hostname;
+ return address + "@localdomain";
+ }
+ else
+ return address;
+}
+
+
+struct FormatInfo {
+ std::vector<Kleo::KeyResolver::SplitInfo> splitInfos;
+ std::vector<GpgME::Key> signKeys;
+};
+
+struct Kleo::KeyResolver::Private {
+ std::set<QCString> alreadyWarnedFingerprints;
+
+ std::vector<GpgME::Key> mOpenPGPSigningKeys; // signing
+ std::vector<GpgME::Key> mSMIMESigningKeys; // signing
+
+ std::vector<GpgME::Key> mOpenPGPEncryptToSelfKeys; // encryption to self
+ std::vector<GpgME::Key> mSMIMEEncryptToSelfKeys; // encryption to self
+
+ std::vector<Item> mPrimaryEncryptionKeys; // encryption to To/CC
+ std::vector<Item> mSecondaryEncryptionKeys; // encryption to BCC
+
+ std::map<CryptoMessageFormat,FormatInfo> mFormatInfoMap;
+
+ // key=email address, value=crypto preferences for this contact (from kabc)
+ typedef std::map<QString, ContactPreferences> ContactPreferencesMap;
+ ContactPreferencesMap mContactPreferencesMap;
+};
+
+
+Kleo::KeyResolver::KeyResolver( bool encToSelf, bool showApproval, bool oppEncryption,
+ unsigned int f,
+ int encrWarnThresholdKey, int signWarnThresholdKey,
+ int encrWarnThresholdRootCert, int signWarnThresholdRootCert,
+ int encrWarnThresholdChainCert, int signWarnThresholdChainCert )
+ : mEncryptToSelf( encToSelf ),
+ mShowApprovalDialog( showApproval ),
+ mOpportunisticEncyption( oppEncryption ),
+ mCryptoMessageFormats( f ),
+ mEncryptKeyNearExpiryWarningThreshold( encrWarnThresholdKey ),
+ mSigningKeyNearExpiryWarningThreshold( signWarnThresholdKey ),
+ mEncryptRootCertNearExpiryWarningThreshold( encrWarnThresholdRootCert ),
+ mSigningRootCertNearExpiryWarningThreshold( signWarnThresholdRootCert ),
+ mEncryptChainCertNearExpiryWarningThreshold( encrWarnThresholdChainCert ),
+ mSigningChainCertNearExpiryWarningThreshold( signWarnThresholdChainCert )
+{
+ d = new Private();
+}
+
+Kleo::KeyResolver::~KeyResolver() {
+ delete d; d = 0;
+}
+
+Kpgp::Result Kleo::KeyResolver::checkKeyNearExpiry( const GpgME::Key & key, const char * dontAskAgainName,
+ bool mine, bool sign, bool ca,
+ int recur_limit, const GpgME::Key & orig ) const {
+ if ( recur_limit <= 0 ) {
+ kdDebug() << "Kleo::KeyResolver::checkKeyNearExpiry(): key chain too long (>100 certs)" << endl;
+ return Kpgp::Ok;
+ }
+ 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 );
+ kdDebug() << "Key 0x" << key.shortKeyID() << " expires in less than "
+ << daysTillExpiry << " days" << endl;
+ const int threshold =
+ ca
+ ? ( key.isRoot()
+ ? ( sign
+ ? signingRootCertNearExpiryWarningThresholdInDays()
+ : encryptRootCertNearExpiryWarningThresholdInDays() )
+ : ( sign
+ ? signingChainCertNearExpiryWarningThresholdInDays()
+ : encryptChainCertNearExpiryWarningThresholdInDays() ) )
+ : ( sign
+ ? signingKeyNearExpiryWarningThresholdInDays()
+ : encryptKeyNearExpiryWarningThresholdInDays() );
+ if ( threshold > -1 && daysTillExpiry <= threshold ) {
+ const QString msg =
+ key.protocol() == GpgME::Context::OpenPGP
+ ? ( mine ? sign
+ ? i18n("<p>Your OpenPGP signing key</p><p align=center><b>%1</b> (KeyID 0x%2)</p>"
+ "<p>expires in less than a day.</p>",
+ "<p>Your OpenPGP signing key</p><p align=center><b>%1</b> (KeyID 0x%2)</p>"
+ "<p>expires in less than %n days.</p>",
+ daysTillExpiry )
+ : i18n("<p>Your OpenPGP encryption key</p><p align=center><b>%1</b> (KeyID 0x%2)</p>"
+ "<p>expires in less than a day.</p>",
+ "<p>Your OpenPGP encryption key</p><p align=center><b>%1</b> (KeyID 0x%2)</p>"
+ "<p>expires in less than %n days.</p>",
+ daysTillExpiry )
+ : i18n("<p>The OpenPGP key for</p><p align=center><b>%1</b> (KeyID 0x%2)</p>"
+ "<p>expires in less than a day.</p>",
+ "<p>The OpenPGP key for</p><p align=center><b>%1</b> (KeyID 0x%2)</p>"
+ "<p>expires in less than %n days.</p>",
+ daysTillExpiry ) ).arg( QString::fromUtf8( key.userID(0).id() ),
+ key.shortKeyID() )
+ : ( ca
+ ? ( key.isRoot()
+ ? ( mine ? sign
+ ? i18n("<p>The root certificate</p><p align=center><b>%3</b></p>"
+ "<p>for your S/MIME signing certificate</p><p align=center><b>%1</b> (serial number %2)</p>"
+ "<p>expires in less than a day.</p>",
+ "<p>The root certificate</p><p align=center><b>%3</b></p>"
+ "<p>for your S/MIME signing certificate</p><p align=center><b>%1</b> (serial number %2)</p>"
+ "<p>expires in less than %n days.</p>",
+ daysTillExpiry )
+ : i18n("<p>The root certificate</p><p align=center><b>%3</b></p>"
+ "<p>for your S/MIME encryption certificate</p><p align=center><b>%1</b> (serial number %2)</p>"
+ "<p>expires in less than a day.</p>",
+ "<p>The root certificate</p><p align=center><b>%3</b></p>"
+ "<p>for your S/MIME encryption certificate</p><p align=center><b>%1</b> (serial number %2)</p>"
+ "<p>expires in less than %n days.</p>",
+ daysTillExpiry )
+ : i18n("<p>The root certificate</p><p align=center><b>%3</b></p>"
+ "<p>for S/MIME certificate</p><p align=center><b>%1</b> (serial number %2)</p>"
+ "<p>expires in less than a day.</p>",
+ "<p>The root certificate</p><p align=center><b>%3</b></p>"
+ "<p>for S/MIME certificate</p><p align=center><b>%1</b> (serial number %2)</p>"
+ "<p>expires in less than %n days.</p>",
+ daysTillExpiry ) )
+ : ( mine ? sign
+ ? i18n("<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
+ "<p>for your S/MIME signing certificate</p><p align=center><b>%1</b> (serial number %2)</p>"
+ "<p>expires in less than a day.</p>",
+ "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
+ "<p>for your S/MIME signing certificate</p><p align=center><b>%1</b> (serial number %2)</p>"
+ "<p>expires in less than %n days.</p>",
+ daysTillExpiry )
+ : i18n("<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
+ "<p>for your S/MIME encryption certificate</p><p align=center><b>%1</b> (serial number %2)</p>"
+ "<p>expires in less than a day.</p>",
+ "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
+ "<p>for your S/MIME encryption certificate</p><p align=center><b>%1</b> (serial number %2)</p>"
+ "<p>expires in less than %n days.</p>",
+ daysTillExpiry )
+ : i18n("<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
+ "<p>for S/MIME certificate</p><p align=center><b>%1</b> (serial number %2)</p>"
+ "<p>expires in less than a day.</p>",
+ "<p>The intermediate CA certificate</p><p align=center><b>%3</b></p>"
+ "<p>for S/MIME certificate</p><p align=center><b>%1</b> (serial number %2)</p>"
+ "<p>expires in less than %n days.</p>",
+ daysTillExpiry ) ) ).arg( Kleo::DN( orig.userID(0).id() ).prettyDN(),
+ orig.issuerSerial(),
+ Kleo::DN( key.userID(0).id() ).prettyDN() )
+ : ( mine ? sign
+ ? i18n("<p>Your S/MIME signing certificate</p><p align=center><b>%1</b> (serial number %2)</p>"
+ "<p>expires in less than a day.</p>",
+ "<p>Your S/MIME signing certificate</p><p align=center><b>%1</b> (serial number %2)</p>"
+ "<p>expires in less than %n days.</p>",
+ daysTillExpiry )
+ : i18n("<p>Your S/MIME encryption certificate</p><p align=center><b>%1</b> (serial number %2)</p>"
+ "<p>expires in less than a day.</p>",
+ "<p>Your S/MIME encryption certificate</p><p align=center><b>%1</b> (serial number %2)</p>"
+ "<p>expires in less than %n days.</p>",
+ daysTillExpiry )
+ : i18n("<p>The S/MIME certificate for</p><p align=center><b>%1</b> (serial number %2)</p>"
+ "<p>expires in less than a day.</p>",
+ "<p>The S/MIME certificate for</p><p align=center><b>%1</b> (serial number %2)</p>"
+ "<p>expires in less than %n days.</p>",
+ daysTillExpiry ) ).arg( Kleo::DN( key.userID(0).id() ).prettyDN(),
+ key.issuerSerial() ) );
+ if ( KMessageBox::warningContinueCancel( 0, msg,
+ key.protocol() == GpgME::Context::OpenPGP
+ ? i18n("OpenPGP Key Expires Soon" )
+ : i18n("S/MIME Certificate Expires Soon" ),
+ KStdGuiItem::cont(), dontAskAgainName )
+ == KMessageBox::Cancel )
+ return Kpgp::Canceled;
+ }
+ if ( key.isRoot() )
+ return Kpgp::Ok;
+ else if ( const char * chain_id = key.chainID() ) {
+ const std::vector<GpgME::Key> issuer = lookup( chain_id, false );
+ if ( issuer.empty() )
+ return Kpgp::Ok;
+ else
+ return checkKeyNearExpiry( issuer.front(), dontAskAgainName, mine, sign,
+ true, recur_limit-1, ca ? orig : key );
+ }
+ return Kpgp::Ok;
+}
+
+Kpgp::Result Kleo::KeyResolver::setEncryptToSelfKeys( const QStringList & fingerprints ) {
+ if ( !encryptToSelf() )
+ return Kpgp::Ok;
+
+ std::vector<GpgME::Key> keys = lookup( fingerprints );
+ std::remove_copy_if( keys.begin(), keys.end(),
+ std::back_inserter( d->mOpenPGPEncryptToSelfKeys ),
+ NotValidTrustedOpenPGPEncryptionKey );
+ std::remove_copy_if( keys.begin(), keys.end(),
+ std::back_inserter( d->mSMIMEEncryptToSelfKeys ),
+ NotValidTrustedSMIMEEncryptionKey );
+
+ if ( d->mOpenPGPEncryptToSelfKeys.size() + d->mSMIMEEncryptToSelfKeys.size()
+ < keys.size() ) {
+ // too few keys remain...
+ const QString msg = i18n("One or more of your configured OpenPGP encryption "
+ "keys or S/MIME certificates is not usable for "
+ "encryption. Please reconfigure your encryption keys "
+ "and certificates for this identity in the identity "
+ "configuration dialog.\n"
+ "If you choose to continue, and the keys are needed "
+ "later on, you will be prompted to specify the keys "
+ "to use.");
+ return KMessageBox::warningContinueCancel( 0, msg, i18n("Unusable Encryption Keys"),
+ KStdGuiItem::cont(),
+ "unusable own encryption key warning" )
+ == KMessageBox::Continue ? Kpgp::Ok : Kpgp::Canceled ;
+ }
+
+ // check for near-expiry:
+
+ for ( std::vector<GpgME::Key>::const_iterator it = d->mOpenPGPEncryptToSelfKeys.begin() ; it != d->mOpenPGPEncryptToSelfKeys.end() ; ++it ) {
+ const Kpgp::Result r = checkKeyNearExpiry( *it, "own encryption key expires soon warning",
+ true, false );
+ if ( r != Kpgp::Ok )
+ return r;
+ }
+
+ for ( std::vector<GpgME::Key>::const_iterator it = d->mSMIMEEncryptToSelfKeys.begin() ; it != d->mSMIMEEncryptToSelfKeys.end() ; ++it ) {
+ const Kpgp::Result r = checkKeyNearExpiry( *it, "own encryption key expires soon warning",
+ true, false );
+ if ( r != Kpgp::Ok )
+ return r;
+ }
+
+ return Kpgp::Ok;
+}
+
+Kpgp::Result Kleo::KeyResolver::setSigningKeys( const QStringList & fingerprints ) {
+ std::vector<GpgME::Key> keys = lookup( fingerprints, true ); // secret keys
+ std::remove_copy_if( keys.begin(), keys.end(),
+ std::back_inserter( d->mOpenPGPSigningKeys ),
+ NotValidOpenPGPSigningKey );
+ std::remove_copy_if( keys.begin(), keys.end(),
+ std::back_inserter( d->mSMIMESigningKeys ),
+ NotValidSMIMESigningKey );
+
+ if ( d->mOpenPGPSigningKeys.size() + d->mSMIMESigningKeys.size() < keys.size() ) {
+ // too few keys remain...
+ const QString msg = i18n("One or more of your configured OpenPGP signing keys "
+ "or S/MIME signing certificates is not usable for "
+ "signing. Please reconfigure your signing keys "
+ "and certificates for this identity in the identity "
+ "configuration dialog.\n"
+ "If you choose to continue, and the keys are needed "
+ "later on, you will be prompted to specify the keys "
+ "to use.");
+ return KMessageBox::warningContinueCancel( 0, msg, i18n("Unusable Signing Keys"),
+ KStdGuiItem::cont(),
+ "unusable signing key warning" )
+ == KMessageBox::Continue ? Kpgp::Ok : Kpgp::Canceled ;
+ }
+
+ // check for near expiry:
+
+ for ( std::vector<GpgME::Key>::const_iterator it = d->mOpenPGPSigningKeys.begin() ; it != d->mOpenPGPSigningKeys.end() ; ++it ) {
+ const Kpgp::Result r = checkKeyNearExpiry( *it, "signing key expires soon warning",
+ true, true );
+ if ( r != Kpgp::Ok )
+ return r;
+ }
+
+ for ( std::vector<GpgME::Key>::const_iterator it = d->mSMIMESigningKeys.begin() ; it != d->mSMIMESigningKeys.end() ; ++it ) {
+ const Kpgp::Result r = checkKeyNearExpiry( *it, "signing key expires soon warning",
+ true, true );
+ if ( r != Kpgp::Ok )
+ return r;
+ }
+
+ return Kpgp::Ok;
+}
+
+void Kleo::KeyResolver::setPrimaryRecipients( const QStringList & addresses ) {
+ d->mPrimaryEncryptionKeys = getEncryptionItems( addresses );
+}
+
+void Kleo::KeyResolver::setSecondaryRecipients( const QStringList & addresses ) {
+ d->mSecondaryEncryptionKeys = getEncryptionItems( addresses );
+}
+
+std::vector<Kleo::KeyResolver::Item> Kleo::KeyResolver::getEncryptionItems( const QStringList & addresses ) {
+ std::vector<Item> items;
+ items.reserve( addresses.size() );
+ for ( QStringList::const_iterator it = addresses.begin() ; it != addresses.end() ; ++it ) {
+ QString addr = canonicalAddress( *it ).lower();
+ const ContactPreferences pref = lookupContactPreferences( addr );
+
+ items.push_back( Item( *it, /*getEncryptionKeys( *it, true ),*/
+ pref.encryptionPreference,
+ pref.signingPreference,
+ pref.cryptoMessageFormat ) );
+ }
+ return items;
+}
+
+static Kleo::Action action( bool doit, bool ask, bool dont, bool requested ) {
+ if ( requested && !dont )
+ return Kleo::DoIt;
+ if ( doit && !ask && !dont )
+ return Kleo::DoIt;
+ if ( !doit && ask && !dont )
+ return Kleo::Ask;
+ if ( !doit && !ask && dont )
+ return requested ? Kleo::Conflict : Kleo::DontDoIt ;
+ if ( !doit && !ask && !dont )
+ return Kleo::DontDoIt ;
+ return Kleo::Conflict;
+}
+
+Kleo::Action Kleo::KeyResolver::checkSigningPreferences( bool signingRequested ) const {
+
+ if ( signingRequested && d->mOpenPGPSigningKeys.empty() && d->mSMIMESigningKeys.empty() )
+ return Impossible;
+
+ SigningPreferenceCounter count;
+ count = std::for_each( d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
+ count );
+ count = std::for_each( d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
+ count );
+
+ unsigned int sign = count.numAlwaysSign();
+ unsigned int ask = count.numAlwaysAskForSigning();
+ const unsigned int dontSign = count.numNeverSign();
+ if ( signingPossible() ) {
+ sign += count.numAlwaysSignIfPossible();
+ ask += count.numAskSigningWheneverPossible();
+ }
+
+ return action( sign, ask, dontSign, signingRequested );
+}
+
+bool Kleo::KeyResolver::signingPossible() const {
+ return !d->mOpenPGPSigningKeys.empty() || !d->mSMIMESigningKeys.empty() ;
+}
+
+Kleo::Action Kleo::KeyResolver::checkEncryptionPreferences( bool encryptionRequested ) const {
+
+ if ( d->mPrimaryEncryptionKeys.empty() && d->mSecondaryEncryptionKeys.empty() )
+ return DontDoIt;
+
+ if ( encryptionRequested && encryptToSelf() &&
+ d->mOpenPGPEncryptToSelfKeys.empty() && d->mSMIMEEncryptToSelfKeys.empty() )
+ return Impossible;
+
+ EncryptionPreferenceCounter count( this, mOpportunisticEncyption ? AskWheneverPossible : UnknownPreference );
+ count = std::for_each( d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
+ count );
+ count = std::for_each( d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
+ count );
+
+ unsigned int encrypt = count.numAlwaysEncrypt();
+ unsigned int ask = count.numAlwaysAskForEncryption();
+ const unsigned int dontEncrypt = count.numNeverEncrypt() + count.numNoKey();
+ if ( encryptionPossible() ) {
+ encrypt += count.numAlwaysEncryptIfPossible();
+ ask += count.numAskWheneverPossible();
+ }
+
+ const Action act = action( encrypt, ask, dontEncrypt, encryptionRequested );
+ if ( act != Ask ||
+ std::for_each( d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
+ std::for_each( d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
+ EncryptionPreferenceCounter( this, UnknownPreference ) ) ).numAlwaysAskForEncryption() )
+ return act;
+ else
+ return AskOpportunistic;
+}
+
+bool Kleo::KeyResolver::encryptionPossible() const {
+ return std::find_if( d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
+ EmptyKeyList ) == d->mPrimaryEncryptionKeys.end()
+ && std::find_if( d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
+ EmptyKeyList ) == d->mSecondaryEncryptionKeys.end() ;
+}
+
+Kpgp::Result Kleo::KeyResolver::resolveAllKeys( bool& signingRequested, bool& encryptionRequested ) {
+ if ( !encryptionRequested && !signingRequested ) {
+ // make a dummy entry with all recipients, but no signing or
+ // encryption keys to avoid special-casing on the caller side:
+ dump();
+ d->mFormatInfoMap[OpenPGPMIMEFormat].splitInfos.push_back( SplitInfo( allRecipients() ) );
+ dump();
+ return Kpgp::Ok;
+ }
+ Kpgp::Result result = Kpgp::Ok;
+ if ( encryptionRequested )
+ result = resolveEncryptionKeys( signingRequested );
+ if ( result != Kpgp::Ok )
+ return result;
+ if ( signingRequested )
+ if ( encryptionRequested )
+ result = resolveSigningKeysForEncryption();
+ else {
+ result = resolveSigningKeysForSigningOnly();
+ if ( result == Kpgp::Failure ) {
+ signingRequested = false;
+ return Kpgp::Ok;
+ }
+ }
+ return result;
+}
+
+Kpgp::Result Kleo::KeyResolver::resolveEncryptionKeys( bool signingRequested ) {
+ //
+ // 1. Get keys for all recipients:
+ //
+
+ for ( std::vector<Item>::iterator it = d->mPrimaryEncryptionKeys.begin() ; it != d->mPrimaryEncryptionKeys.end() ; ++it ) {
+ if ( !it->needKeys )
+ continue;
+ it->keys = getEncryptionKeys( it->address, false );
+ if ( it->keys.empty() )
+ return Kpgp::Canceled;
+ QString addr = canonicalAddress( it->address ).lower();
+ const ContactPreferences pref = lookupContactPreferences( addr );
+ it->pref = pref.encryptionPreference;
+ it->signPref = pref.signingPreference;
+ it->format = pref.cryptoMessageFormat;
+ }
+
+ for ( std::vector<Item>::iterator it = d->mSecondaryEncryptionKeys.begin() ; it != d->mSecondaryEncryptionKeys.end() ; ++it ) {
+ if ( !it->needKeys )
+ continue;
+ it->keys = getEncryptionKeys( it->address, false );
+ if ( it->keys.empty() )
+ return Kpgp::Canceled;
+ QString addr = canonicalAddress( it->address ).lower();
+ const ContactPreferences pref = lookupContactPreferences( addr );
+ it->pref = pref.encryptionPreference;
+ it->signPref = pref.signingPreference;
+ it->format = pref.cryptoMessageFormat;
+ }
+
+ // 1a: Present them to the user
+
+ const Kpgp::Result res = showKeyApprovalDialog();
+ if ( res != Kpgp::Ok )
+ return res;
+
+ //
+ // 2. Check what the primary recipients need
+ //
+
+ // 2a. Try to find a common format for all primary recipients,
+ // else use as many formats as needed
+
+ const EncryptionFormatPreferenceCounter primaryCount
+ = std::for_each( d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
+ EncryptionFormatPreferenceCounter() );
+
+ CryptoMessageFormat commonFormat = AutoFormat;
+ for ( unsigned int i = 0 ; i < numConcreteCryptoMessageFormats ; ++i ) {
+ if ( !( concreteCryptoMessageFormats[i] & mCryptoMessageFormats ) )
+ continue;
+ if ( signingRequested && signingKeysFor( concreteCryptoMessageFormats[i] ).empty() )
+ continue;
+ if ( encryptToSelf() && encryptToSelfKeysFor( concreteCryptoMessageFormats[i] ).empty() )
+ continue;
+ if ( primaryCount.numOf( concreteCryptoMessageFormats[i] ) == primaryCount.numTotal() ) {
+ commonFormat = concreteCryptoMessageFormats[i];
+ break;
+ }
+ }
+ if ( commonFormat != AutoFormat )
+ addKeys( d->mPrimaryEncryptionKeys, commonFormat );
+ else
+ addKeys( d->mPrimaryEncryptionKeys );
+
+ collapseAllSplitInfos(); // these can be encrypted together
+
+ // 2b. Just try to find _something_ for each secondary recipient,
+ // with a preference to a common format (if that exists)
+
+ const EncryptionFormatPreferenceCounter secondaryCount
+ = std::for_each( d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
+ EncryptionFormatPreferenceCounter() );
+
+ if ( commonFormat != AutoFormat &&
+ secondaryCount.numOf( commonFormat ) == secondaryCount.numTotal() )
+ addKeys( d->mSecondaryEncryptionKeys, commonFormat );
+ else
+ addKeys( d->mSecondaryEncryptionKeys );
+
+ // 3. Check for expiry:
+
+ for ( unsigned int i = 0 ; i < numConcreteCryptoMessageFormats ; ++i ) {
+ const std::vector<SplitInfo> si_list = encryptionItems( concreteCryptoMessageFormats[i] );
+ for ( std::vector<SplitInfo>::const_iterator sit = si_list.begin() ; sit != si_list.end() ; ++sit )
+ for ( std::vector<GpgME::Key>::const_iterator kit = sit->keys.begin() ; kit != sit->keys.end() ; ++kit ) {
+ const Kpgp::Result r = checkKeyNearExpiry( *kit, "other encryption key near expiry warning",
+ false, false );
+ if ( r != Kpgp::Ok )
+ return r;
+ }
+ }
+
+ // 4. Check that we have the right keys for encryptToSelf()
+
+ if ( !encryptToSelf() )
+ return Kpgp::Ok;
+
+ // 4a. Check for OpenPGP keys
+
+ if ( !encryptionItems( InlineOpenPGPFormat ).empty() ||
+ !encryptionItems( OpenPGPMIMEFormat ).empty() ) {
+ // need them
+ if ( d->mOpenPGPEncryptToSelfKeys.empty() ) {
+ const QString msg = i18n("Examination of recipient's encryption preferences "
+ "yielded that the message should be encrypted using "
+ "OpenPGP, at least for some recipients;\n"
+ "however, you have not configured valid trusted "
+ "OpenPGP encryption keys for this identity.\n"
+ "You may continue without encrypting to yourself, "
+ "but be aware that you will not be able to read your "
+ "own messages if you do so.");
+ if ( KMessageBox::warningContinueCancel( 0, msg,
+ i18n("Unusable Encryption Keys"),
+ KStdGuiItem::cont(),
+ "encrypt-to-self will fail warning" )
+ == KMessageBox::Cancel )
+ return Kpgp::Canceled;
+ // FIXME: Allow selection
+ }
+ addToAllSplitInfos( d->mOpenPGPEncryptToSelfKeys,
+ InlineOpenPGPFormat|OpenPGPMIMEFormat );
+ }
+
+ // 4b. Check for S/MIME certs:
+
+ if ( !encryptionItems( SMIMEFormat ).empty() ||
+ !encryptionItems( SMIMEOpaqueFormat ).empty() ) {
+ // need them
+ if ( d->mSMIMEEncryptToSelfKeys.empty() ) {
+ // don't have one
+ const QString msg = i18n("Examination of recipient's encryption preferences "
+ "yielded that the message should be encrypted using "
+ "S/MIME, at least for some recipients;\n"
+ "however, you have not configured valid "
+ "S/MIME encryption certificates for this identity.\n"
+ "You may continue without encrypting to yourself, "
+ "but be aware that you will not be able to read your "
+ "own messages if you do so.");
+ if ( KMessageBox::warningContinueCancel( 0, msg,
+ i18n("Unusable Encryption Keys"),
+ KStdGuiItem::cont(),
+ "encrypt-to-self will fail warning" )
+ == KMessageBox::Cancel )
+ return Kpgp::Canceled;
+ // FIXME: Allow selection
+ }
+ addToAllSplitInfos( d->mSMIMEEncryptToSelfKeys,
+ SMIMEFormat|SMIMEOpaqueFormat );
+ }
+
+ // FIXME: Present another message if _both_ OpenPGP and S/MIME keys
+ // are missing.
+
+ return Kpgp::Ok;
+}
+
+Kpgp::Result Kleo::KeyResolver::resolveSigningKeysForEncryption() {
+ if ( ( !encryptionItems( InlineOpenPGPFormat ).empty() ||
+ !encryptionItems( OpenPGPMIMEFormat ).empty() )
+ && d->mOpenPGPSigningKeys.empty() ) {
+ const QString msg = i18n("Examination of recipient's signing preferences "
+ "yielded that the message should be signed using "
+ "OpenPGP, at least for some recipients;\n"
+ "however, you have not configured valid "
+ "OpenPGP signing certificates for this identity.");
+ if ( KMessageBox::warningContinueCancel( 0, msg,
+ i18n("Unusable Signing Keys"),
+ i18n("Do Not OpenPGP-Sign"),
+ "signing will fail warning" )
+ == KMessageBox::Cancel )
+ return Kpgp::Canceled;
+ // FIXME: Allow selection
+ }
+ if ( ( !encryptionItems( SMIMEFormat ).empty() ||
+ !encryptionItems( SMIMEOpaqueFormat ).empty() )
+ && d->mSMIMESigningKeys.empty() ) {
+ const QString msg = i18n("Examination of recipient's signing preferences "
+ "yielded that the message should be signed using "
+ "S/MIME, at least for some recipients;\n"
+ "however, you have not configured valid "
+ "S/MIME signing certificates for this identity.");
+ if ( KMessageBox::warningContinueCancel( 0, msg,
+ i18n("Unusable Signing Keys"),
+ i18n("Do Not S/MIME-Sign"),
+ "signing will fail warning" )
+ == KMessageBox::Cancel )
+ return Kpgp::Canceled;
+ // FIXME: Allow selection
+ }
+
+ // FIXME: Present another message if _both_ OpenPGP and S/MIME keys
+ // are missing.
+
+ for ( std::map<CryptoMessageFormat,FormatInfo>::iterator it = d->mFormatInfoMap.begin() ; it != d->mFormatInfoMap.end() ; ++it )
+ if ( !it->second.splitInfos.empty() ) {
+ dump();
+ it->second.signKeys = signingKeysFor( it->first );
+ dump();
+ }
+
+ return Kpgp::Ok;
+}
+
+Kpgp::Result Kleo::KeyResolver::resolveSigningKeysForSigningOnly() {
+ //
+ // we don't need to distinguish between primary and secondary
+ // recipients here:
+ //
+ SigningFormatPreferenceCounter count;
+ count = std::for_each( d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
+ count );
+ count = std::for_each( d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
+ count );
+
+ // try to find a common format that works for all (and that we have signing keys for):
+
+ CryptoMessageFormat commonFormat = AutoFormat;
+
+ for ( unsigned int i = 0 ; i < numConcreteCryptoMessageFormats ; ++i ) {
+ if ( !(mCryptoMessageFormats & concreteCryptoMessageFormats[i]) )
+ continue; // skip
+ if ( signingKeysFor( concreteCryptoMessageFormats[i] ).empty() )
+ continue; // skip
+ if ( count.numOf( concreteCryptoMessageFormats[i] ) == count.numTotal() ) {
+ commonFormat = concreteCryptoMessageFormats[i];
+ break;
+ }
+ }
+
+ if ( commonFormat != AutoFormat ) { // found
+ dump();
+ FormatInfo & fi = d->mFormatInfoMap[ commonFormat ];
+ fi.signKeys = signingKeysFor( commonFormat );
+ fi.splitInfos.resize( 1 );
+ fi.splitInfos.front() = SplitInfo( allRecipients() );
+ dump();
+ return Kpgp::Ok;
+ }
+
+ const QString msg = i18n("Examination of recipient's signing preferences "
+ "showed no common type of signature matching your "
+ "available signing keys.\n"
+ "Send message without signing?" );
+ if ( KMessageBox::warningContinueCancel( 0, msg, i18n("No signing possible"),
+ KStdGuiItem::cont() )
+ == KMessageBox::Continue ) {
+ d->mFormatInfoMap[OpenPGPMIMEFormat].splitInfos.push_back( SplitInfo( allRecipients() ) );
+ return Kpgp::Failure; // means "Ok, but without signing"
+ }
+ return Kpgp::Canceled;
+}
+
+std::vector<GpgME::Key> Kleo::KeyResolver::signingKeysFor( CryptoMessageFormat f ) const {
+ if ( isOpenPGP( f ) )
+ return d->mOpenPGPSigningKeys;
+ if ( isSMIME( f ) )
+ return d->mSMIMESigningKeys;
+ return std::vector<GpgME::Key>();
+}
+
+std::vector<GpgME::Key> Kleo::KeyResolver::encryptToSelfKeysFor( CryptoMessageFormat f ) const {
+ if ( isOpenPGP( f ) )
+ return d->mOpenPGPEncryptToSelfKeys;
+ if ( isSMIME( f ) )
+ return d->mSMIMEEncryptToSelfKeys;
+ return std::vector<GpgME::Key>();
+}
+
+QStringList Kleo::KeyResolver::allRecipients() const {
+ QStringList result;
+ std::transform( d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
+ std::back_inserter( result ), ItemDotAddress );
+ std::transform( d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
+ std::back_inserter( result ), ItemDotAddress );
+ return result;
+}
+
+void Kleo::KeyResolver::collapseAllSplitInfos() {
+ dump();
+ for ( unsigned int i = 0 ; i < numConcreteCryptoMessageFormats ; ++i ) {
+ std::map<CryptoMessageFormat,FormatInfo>::iterator pos =
+ d->mFormatInfoMap.find( concreteCryptoMessageFormats[i] );
+ if ( pos == d->mFormatInfoMap.end() )
+ continue;
+ std::vector<SplitInfo> & v = pos->second.splitInfos;
+ if ( v.size() < 2 )
+ continue;
+ SplitInfo & si = v.front();
+ for ( std::vector<SplitInfo>::const_iterator it = v.begin() + 1; it != v.end() ; ++it ) {
+ si.keys.insert( si.keys.end(), it->keys.begin(), it->keys.end() );
+ qCopy( it->recipients.begin(), it->recipients.end(), std::back_inserter( si.recipients ) );
+ }
+ v.resize( 1 );
+ }
+ dump();
+}
+
+void Kleo::KeyResolver::addToAllSplitInfos( const std::vector<GpgME::Key> & keys, unsigned int f ) {
+ dump();
+ if ( !f || keys.empty() )
+ return;
+ for ( unsigned int i = 0 ; i < numConcreteCryptoMessageFormats ; ++i ) {
+ if ( !( f & concreteCryptoMessageFormats[i] ) )
+ continue;
+ std::map<CryptoMessageFormat,FormatInfo>::iterator pos =
+ d->mFormatInfoMap.find( concreteCryptoMessageFormats[i] );
+ if ( pos == d->mFormatInfoMap.end() )
+ continue;
+ std::vector<SplitInfo> & v = pos->second.splitInfos;
+ for ( std::vector<SplitInfo>::iterator it = v.begin() ; it != v.end() ; ++it )
+ it->keys.insert( it->keys.end(), keys.begin(), keys.end() );
+ }
+ dump();
+}
+
+void Kleo::KeyResolver::dump() const {
+#ifndef NDEBUG
+ if ( d->mFormatInfoMap.empty() )
+ std::cerr << "Keyresolver: Format info empty" << std::endl;
+ for ( std::map<CryptoMessageFormat,FormatInfo>::const_iterator it = d->mFormatInfoMap.begin() ; it != d->mFormatInfoMap.end() ; ++it ) {
+ std::cerr << "Format info for " << Kleo::cryptoMessageFormatToString( it->first )
+ << ":" << std::endl
+ << " Signing keys: ";
+ for ( std::vector<GpgME::Key>::const_iterator sit = it->second.signKeys.begin() ; sit != it->second.signKeys.end() ; ++sit )
+ std::cerr << sit->shortKeyID() << " ";
+ std::cerr << std::endl;
+ unsigned int i = 0;
+ for ( std::vector<SplitInfo>::const_iterator sit = it->second.splitInfos.begin() ; sit != it->second.splitInfos.end() ; ++sit, ++i ) {
+ std::cerr << " SplitInfo #" << i << " encryption keys: ";
+ for ( std::vector<GpgME::Key>::const_iterator kit = sit->keys.begin() ; kit != sit->keys.end() ; ++kit )
+ std::cerr << kit->shortKeyID() << " ";
+ std::cerr << std::endl
+ << " SplitInfo #" << i << " recipients: "
+ << sit->recipients.join(", ").utf8() << std::endl;
+ }
+ }
+#endif
+}
+
+Kpgp::Result Kleo::KeyResolver::showKeyApprovalDialog() {
+ const bool showKeysForApproval = showApprovalDialog()
+ || std::find_if( d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
+ ApprovalNeeded ) != d->mPrimaryEncryptionKeys.end()
+ || std::find_if( d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
+ ApprovalNeeded ) != d->mSecondaryEncryptionKeys.end() ;
+
+ if ( !showKeysForApproval )
+ return Kpgp::Ok;
+
+ std::vector<Kleo::KeyApprovalDialog::Item> items;
+ items.reserve( d->mPrimaryEncryptionKeys.size() +
+ d->mSecondaryEncryptionKeys.size() );
+ std::copy( d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
+ std::back_inserter( items ) );
+ std::copy( d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
+ std::back_inserter( items ) );
+
+ std::vector<GpgME::Key> senderKeys;
+ senderKeys.reserve( d->mOpenPGPEncryptToSelfKeys.size() +
+ d->mSMIMEEncryptToSelfKeys.size() );
+ std::copy( d->mOpenPGPEncryptToSelfKeys.begin(), d->mOpenPGPEncryptToSelfKeys.end(),
+ std::back_inserter( senderKeys ) );
+ std::copy( d->mSMIMEEncryptToSelfKeys.begin(), d->mSMIMEEncryptToSelfKeys.end(),
+ std::back_inserter( senderKeys ) );
+
+ const KCursorSaver idle( KBusyPtr::idle() );
+
+ Kleo::KeyApprovalDialog dlg( items, senderKeys );
+
+ if ( dlg.exec() == QDialog::Rejected )
+ return Kpgp::Canceled;
+
+ items = dlg.items();
+ senderKeys = dlg.senderKeys();
+
+ if ( dlg.preferencesChanged() ) {
+ for ( uint i = 0; i < items.size(); ++i ) {
+ ContactPreferences pref = lookupContactPreferences( items[i].address );
+ pref.encryptionPreference = items[i].pref;
+ pref.pgpKeyFingerprints.clear();
+ pref.smimeCertFingerprints.clear();
+ const std::vector<GpgME::Key> & keys = items[i].keys;
+ for ( std::vector<GpgME::Key>::const_iterator it = keys.begin(), end = keys.end() ; it != end ; ++it ) {
+ if ( it->protocol() == GpgME::Context::OpenPGP ) {
+ if ( const char * fpr = it->primaryFingerprint() )
+ pref.pgpKeyFingerprints.push_back( fpr );
+ } else if ( it->protocol() == GpgME::Context::CMS ) {
+ if ( const char * fpr = it->primaryFingerprint() )
+ pref.smimeCertFingerprints.push_back( fpr );
+ }
+ }
+ saveContactPreference( items[i].address, pref );
+ }
+ }
+
+ // show a warning if the user didn't select an encryption key for
+ // herself:
+ if ( encryptToSelf() && senderKeys.empty() ) {
+ const QString msg = i18n("You did not select an encryption key for yourself "
+ "(encrypt to self). You will not be able to decrypt "
+ "your own message if you encrypt it.");
+ if ( KMessageBox::warningContinueCancel( 0, msg,
+ i18n("Missing Key Warning"),
+ i18n("&Encrypt") )
+ == KMessageBox::Cancel )
+ return Kpgp::Canceled;
+ else
+ mEncryptToSelf = false;
+ }
+
+ // count empty key ID lists
+ const unsigned int emptyListCount =
+ std::count_if( items.begin(), items.end(), EmptyKeyList );
+
+ // show a warning if the user didn't select an encryption key for
+ // some of the recipients
+ if ( items.size() == emptyListCount ) {
+ const QString msg = ( d->mPrimaryEncryptionKeys.size() +
+ d->mSecondaryEncryptionKeys.size() == 1 )
+ ? i18n("You did not select an encryption key for the "
+ "recipient of this message; therefore, the message "
+ "will not be encrypted.")
+ : i18n("You did not select an encryption key for any of the "
+ "recipients of this message; therefore, the message "
+ "will not be encrypted.");
+ if ( KMessageBox::warningContinueCancel( 0, msg,
+ i18n("Missing Key Warning"),
+ i18n("Send &Unencrypted") )
+ == KMessageBox::Cancel )
+ return Kpgp::Canceled;
+ } else if ( emptyListCount > 0 ) {
+ const QString msg = ( emptyListCount == 1 )
+ ? i18n("You did not select an encryption key for one of "
+ "the recipients: this person will not be able to "
+ "decrypt the message if you encrypt it.")
+ : i18n("You did not select encryption keys for some of "
+ "the recipients: these persons will not be able to "
+ "decrypt the message if you encrypt it." );
+ KCursorSaver idle( KBusyPtr::idle() );
+ if ( KMessageBox::warningContinueCancel( 0, msg,
+ i18n("Missing Key Warning"),
+ i18n("&Encrypt") )
+ == KMessageBox::Cancel )
+ return Kpgp::Canceled;
+ }
+
+ std::transform( d->mPrimaryEncryptionKeys.begin(), d->mPrimaryEncryptionKeys.end(),
+ items.begin(),
+ d->mPrimaryEncryptionKeys.begin(),
+ CopyKeysAndEncryptionPreferences );
+ std::transform( d->mSecondaryEncryptionKeys.begin(), d->mSecondaryEncryptionKeys.end(),
+ items.begin() + d->mPrimaryEncryptionKeys.size(),
+ d->mSecondaryEncryptionKeys.begin(),
+ CopyKeysAndEncryptionPreferences );
+
+ d->mOpenPGPEncryptToSelfKeys.clear();
+ d->mSMIMEEncryptToSelfKeys.clear();
+
+ std::remove_copy_if( senderKeys.begin(), senderKeys.end(),
+ std::back_inserter( d->mOpenPGPEncryptToSelfKeys ),
+ NotValidTrustedOpenPGPEncryptionKey );
+ std::remove_copy_if( senderKeys.begin(), senderKeys.end(),
+ std::back_inserter( d->mSMIMEEncryptToSelfKeys ),
+ NotValidTrustedSMIMEEncryptionKey );
+
+ return Kpgp::Ok;
+}
+
+std::vector<Kleo::KeyResolver::SplitInfo> Kleo::KeyResolver::encryptionItems( Kleo::CryptoMessageFormat f ) const {
+ dump();
+ std::map<CryptoMessageFormat,FormatInfo>::const_iterator it =
+ d->mFormatInfoMap.find( f );
+ return it != d->mFormatInfoMap.end() ? it->second.splitInfos : std::vector<SplitInfo>() ;
+}
+
+std::vector<GpgME::Key> Kleo::KeyResolver::signingKeys( CryptoMessageFormat f ) const {
+ dump();
+ std::map<CryptoMessageFormat,FormatInfo>::const_iterator it =
+ d->mFormatInfoMap.find( f );
+ return it != d->mFormatInfoMap.end() ? it->second.signKeys : std::vector<GpgME::Key>() ;
+}
+
+//
+//
+// Private helper methods below:
+//
+//
+
+
+std::vector<GpgME::Key> Kleo::KeyResolver::selectKeys( const QString & person, const QString & msg, const std::vector<GpgME::Key> & selectedKeys ) const {
+ Kleo::KeySelectionDialog dlg( i18n("Encryption Key Selection"),
+ msg, selectedKeys,
+ Kleo::KeySelectionDialog::ValidEncryptionKeys,
+ true, true ); // multi-selection and "remember choice" box
+
+ if ( dlg.exec() != QDialog::Accepted )
+ return std::vector<GpgME::Key>();
+ std::vector<GpgME::Key> keys = dlg.selectedKeys();
+ keys.erase( std::remove_if( keys.begin(), keys.end(),
+ NotValidTrustedEncryptionKey ),
+ keys.end() );
+ if ( !keys.empty() && dlg.rememberSelection() )
+ setKeysForAddress( person, dlg.pgpKeyFingerprints(), dlg.smimeFingerprints() );
+ return keys;
+}
+
+
+std::vector<GpgME::Key> Kleo::KeyResolver::getEncryptionKeys( const QString & person, bool quiet ) const {
+
+ const QString address = canonicalAddress( person ).lower();
+
+ // First look for this person's address in the address->key dictionary
+ const QStringList fingerprints = keysForAddress( address );
+
+ if ( !fingerprints.empty() ) {
+ kdDebug() << "Using encryption keys 0x"
+ << fingerprints.join( ", 0x" )
+ << " for " << person << endl;
+ std::vector<GpgME::Key> keys = lookup( fingerprints );
+ 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() ) {
+
+ // 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 "
+ "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 "
+ "be used for this recipient.").arg(person),
+ keys );
+ }
+ keys = TrustedOrConfirmed( keys );
+
+ if ( !keys.empty() )
+ return keys;
+ // hmmm, should we not return the keys in any case here?
+ }
+ }
+
+ // Now search all public keys for matching keys
+ std::vector<GpgME::Key> matchingKeys = lookup( person );
+ matchingKeys.erase( std::remove_if( matchingKeys.begin(), matchingKeys.end(),
+ NotValidTrustedEncryptionKey ),
+ 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 ),
+ 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
+ if ( !quiet )
+ matchingKeys = TrustedOrConfirmed( matchingKeys );
+ 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,
+ matchingKeys.empty()
+ ? i18n("if in your language something like "
+ "'key(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)
+ : i18n("if in your language something like "
+ "'key(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 ) );
+}
+
+
+std::vector<GpgME::Key> Kleo::KeyResolver::lookup( const QStringList & patterns, bool secret ) const {
+ if ( patterns.empty() )
+ return std::vector<GpgME::Key>();
+ kdDebug() << "Kleo::KeyResolver::lookup( \"" << patterns.join( "\", \"" )
+ << "\", " << secret << " )" << endl;
+ std::vector<GpgME::Key> result;
+ if ( mCryptoMessageFormats & (InlineOpenPGPFormat|OpenPGPMIMEFormat) )
+ if ( const Kleo::CryptoBackend::Protocol * p = Kleo::CryptoBackendFactory::instance()->openpgp() ) {
+ std::auto_ptr<Kleo::KeyListJob> job( p->keyListJob( false, false, true ) ); // use validating keylisting
+ if ( job.get() ) {
+ std::vector<GpgME::Key> keys;
+ job->exec( patterns, secret, keys );
+ result.insert( result.end(), keys.begin(), keys.end() );
+ }
+ }
+ if ( mCryptoMessageFormats & (SMIMEFormat|SMIMEOpaqueFormat) )
+ if ( const Kleo::CryptoBackend::Protocol * p = Kleo::CryptoBackendFactory::instance()->smime() ) {
+ std::auto_ptr<Kleo::KeyListJob> job( p->keyListJob( false, false, true ) ); // use validating keylisting
+ if ( job.get() ) {
+ std::vector<GpgME::Key> keys;
+ job->exec( patterns, secret, keys );
+ result.insert( result.end(), keys.begin(), keys.end() );
+ }
+ }
+ kdDebug() << " returned " << result.size() << " keys" << endl;
+ return result;
+}
+
+void Kleo::KeyResolver::addKeys( const std::vector<Item> & items, CryptoMessageFormat f ) {
+ dump();
+ for ( std::vector<Item>::const_iterator it = items.begin() ; it != items.end() ; ++it ) {
+ SplitInfo si( it->address );
+ std::remove_copy_if( it->keys.begin(), it->keys.end(),
+ std::back_inserter( si.keys ), IsNotForFormat( f ) );
+ dump();
+ kdWarning( si.keys.empty() )
+ << "Kleo::KeyResolver::addKeys(): Fix EncryptionFormatPreferenceCounter. "
+ << "It detected a common format, but the list of such keys for recipient \""
+ << it->address << "\" is empty!" << endl;
+ d->mFormatInfoMap[ f ].splitInfos.push_back( si );
+ }
+ dump();
+}
+
+void Kleo::KeyResolver::addKeys( const std::vector<Item> & items ) {
+ dump();
+ for ( std::vector<Item>::const_iterator it = items.begin() ; it != items.end() ; ++it ) {
+ SplitInfo si( it->address );
+ CryptoMessageFormat f = AutoFormat;
+ for ( unsigned int i = 0 ; i < numConcreteCryptoMessageFormats ; ++i ) {
+ if ( concreteCryptoMessageFormats[i] & it->format ) {
+ f = concreteCryptoMessageFormats[i];
+ break;
+ }
+ }
+ if ( f == AutoFormat )
+ kdWarning() << "Kleo::KeyResolver::addKeys(): Something went wrong. Didn't find a format for \""
+ << it->address << "\"" << endl;
+ else
+ std::remove_copy_if( it->keys.begin(), it->keys.end(),
+ std::back_inserter( si.keys ), IsNotForFormat( f ) );
+ d->mFormatInfoMap[ f ].splitInfos.push_back( si );
+ }
+ dump();
+}
+
+Kleo::KeyResolver::ContactPreferences Kleo::KeyResolver::lookupContactPreferences( const QString& address ) const
+{
+ const Private::ContactPreferencesMap::iterator it =
+ d->mContactPreferencesMap.find( address );
+ if ( it != d->mContactPreferencesMap.end() )
+ return it->second;
+
+ KABC::AddressBook *ab = KABC::StdAddressBook::self( true );
+ const KABC::Addressee::List res = ab->findByEmail( address );
+ ContactPreferences pref;
+ if ( !res.isEmpty() ) {
+ KABC::Addressee addr = res.first();
+ QString encryptPref = addr.custom( "KADDRESSBOOK", "CRYPTOENCRYPTPREF" );
+ pref.encryptionPreference = Kleo::stringToEncryptionPreference( encryptPref );
+ QString signPref = addr.custom( "KADDRESSBOOK", "CRYPTOSIGNPREF" );
+ pref.signingPreference = Kleo::stringToSigningPreference( signPref );
+ QString cryptoFormats = addr.custom( "KADDRESSBOOK", "CRYPTOPROTOPREF" );
+ pref.cryptoMessageFormat = Kleo::stringToCryptoMessageFormat( cryptoFormats );
+ pref.pgpKeyFingerprints = QStringList::split( ',', addr.custom( "KADDRESSBOOK", "OPENPGPFP" ) );
+ pref.smimeCertFingerprints = QStringList::split( ',', addr.custom( "KADDRESSBOOK", "SMIMEFP" ) );
+ }
+ // insert into map and grab resulting iterator
+ d->mContactPreferencesMap.insert( std::make_pair( address, pref ) );
+ return pref;
+}
+
+void Kleo::KeyResolver::saveContactPreference( const QString& email, const ContactPreferences& pref ) const
+{
+ d->mContactPreferencesMap.insert( std::make_pair( email, pref ) );
+ KABC::AddressBook *ab = KABC::StdAddressBook::self( true );
+ KABC::Addressee::List res = ab->findByEmail( email );
+
+ KABC::Addressee addr;
+ if ( res.isEmpty() ) {
+ bool ok = true;
+ QString fullName = KInputDialog::getText( i18n( "Name Selection" ), i18n( "Which name shall the contact '%1' have in your addressbook?" ).arg( email ), QString::null, &ok );
+ if ( ok ) {
+ addr.setNameFromString( fullName );
+ addr.insertEmail( email, true );
+ } else
+ return;
+ } else
+ addr = res.first();
+
+ addr.insertCustom( "KADDRESSBOOK", "CRYPTOENCRYPTPREF", Kleo::encryptionPreferenceToString( pref.encryptionPreference ) );
+ addr.insertCustom( "KADDRESSBOOK", "CRYPTOSIGNPREF", Kleo::signingPreferenceToString( pref.signingPreference ) );
+ addr.insertCustom( "KADDRESSBOOK", "CRYPTOPROTOPREF", cryptoMessageFormatToString( pref.cryptoMessageFormat ) );
+ addr.insertCustom( "KADDRESSBOOK", "OPENPGPFP", pref.pgpKeyFingerprints.join( "," ) );
+ addr.insertCustom( "KADDRESSBOOK", "SMIMEFP", pref.smimeCertFingerprints.join( "," ) );
+
+ ab->insertAddressee( addr );
+ KABC::Ticket *ticket = ab->requestSaveTicket( addr.resource() );
+ if ( ticket )
+ ab->save( ticket );
+
+ // Assumption: 'pref' comes from d->mContactPreferencesMap already, no need to update that
+}
+
+Kleo::KeyResolver::ContactPreferences::ContactPreferences()
+ : encryptionPreference( UnknownPreference ),
+ signingPreference( UnknownSigningPreference ),
+ cryptoMessageFormat( AutoFormat )
+{
+}
+
+QStringList Kleo::KeyResolver::keysForAddress( const QString & address ) const {
+ if( address.isEmpty() ) {
+ return QStringList();
+ }
+ QString addr = canonicalAddress( address ).lower();
+ const ContactPreferences pref = lookupContactPreferences( addr );
+ return pref.pgpKeyFingerprints + pref.smimeCertFingerprints;
+}
+
+void Kleo::KeyResolver::setKeysForAddress( const QString& address, const QStringList& pgpKeyFingerprints, const QStringList& smimeCertFingerprints ) const {
+ if( address.isEmpty() ) {
+ return;
+ }
+ QString addr = canonicalAddress( address ).lower();
+ ContactPreferences pref = lookupContactPreferences( addr );
+ pref.pgpKeyFingerprints = pgpKeyFingerprints;
+ pref.smimeCertFingerprints = smimeCertFingerprints;
+ saveContactPreference( addr, pref );
+}
diff --git a/kmail/keyresolver.h b/kmail/keyresolver.h
new file mode 100644
index 00000000..8cb7ebbb
--- /dev/null
+++ b/kmail/keyresolver.h
@@ -0,0 +1,319 @@
+/* -*- c++ -*-
+ keyresolver.h
+
+ This file is part of libkleopatra, the KDE keymanagement library
+ Copyright (c) 2004 Klarlvdalens Datakonsult AB
+
+ Based on kpgp.h
+ Copyright (C) 2001,2002 the KPGP authors
+ See file libkdenetwork/AUTHORS.kpgp for details
+
+ Libkleopatra 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.
+
+ Libkleopatra is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KLEO_KEYRESOLVER_H__
+#define __KLEO_KEYRESOLVER_H__
+
+#include <ui/keyapprovaldialog.h>
+
+#include <kleo/enum.h>
+
+#include <kpgp.h> // for Kpgp::Result
+#include <gpgmepp/key.h>
+
+#include <vector>
+
+class QStringList;
+
+namespace Kleo {
+
+
+ /**
+ \short A class to resolve signing/encryption keys w.r.t. per-recipient preferences
+
+ \section Step 1: Set the information needed
+
+ The constructor takes some basic options as arguments, such as
+ whether or not encryption was actually requested. Recipient and
+ sender information is then set by using \c
+ setEncryptToSelfKeys(), \c setSigningKeys(), \c
+ setPrimaryRecipients() (To/Cc) and \c setSecondaryRecipients()
+ (Bcc).
+
+ \section Step 2: Lookup and check per-recipient crypto preferences / Opportunistic Encryption
+
+ First, \c checkSigningPreferences() goes through all recipient's
+ signing perferences, to determine whether or not to sign. It also
+ takes into account the available signing keys and whether or not
+ the user explicitly requested signing.
+
+ \c checkEncryptionPreferences() does the same for encryption
+ preferences. If opportunistic encryption is enabled, recipients
+ without encryption preferences set are treated as if they had a
+ preference of \c AskWheneverPossible.
+
+ In both cases an Action code is returned, with the following
+ meanings:
+
+ <dl><dt>Conflict</dt><dd>A conflict was detected. E.g. one
+ recipient's preference was set to "always encrypt", while another
+ one's preference was set to "never encrypt". You should ask the
+ user what to do.</dd></dt>
+
+ <dt>DoIt, DontDoIt</dt><dd>Do/Don't sign/encrypt</dd>
+
+ <dt>Ask</dt><dd>(Some) crypto preferences request to prompt the
+ user, so do it.</dd>
+
+ <dt>Impossible</dt><dd>Signing or encryption is impossible,
+ e.g. due to missing keys or unsupported formats.</dd> </dl>
+
+ \section Step 3: Resolve all keys.
+
+ In case signing or encryption was implicitly or explicitly
+ requested by the user, \c resolveAllKeys() tries to find signing
+ keys for each required format, as well as encryption keys for all
+ recipients (incl. the sender, if encrypt-to-self is set).
+
+ \section Step 4: Get signing keys.
+
+ If, after key resolving, signing is still requested and
+ apparently possible, you can get the result of all this by
+ iterating over the available message formats and retrieving the
+ set of signing keys to use with a call to \c signingKeys().
+
+ \section Step 5: Get encrytion key sets.
+
+ If after key resolving, encryption is still requested and
+ apparently possible, you can get the result of all this by
+ calling \c encryptionItems() with the current message format at
+ hand as its argument.
+
+ This will return a list of recipient-list/key-list pairs that
+ each describe a copy of the (possibly signed) message to be
+ encrypted independantly.
+
+ Note that it's only necessary to sign the message once for each
+ message format, although it might be necessary to create more
+ than one message when encrypting. This is because encryption
+ allows the recipients to learn about the other recipients the
+ message was encrypted to, so each secondary (BCC) recipient need
+ a copy of it's own to hide the other secondary recipients.
+ */
+
+ class KeyResolver {
+ public:
+ KeyResolver( bool encToSelf, bool showApproval, bool oppEncryption,
+ unsigned int format,
+ int encrKeyNearExpiryThresholdDays,
+ int signKeyNearExpiryThresholdDays,
+ int encrRootCertNearExpiryThresholdDays,
+ int signRootCertNearExpiryThresholdDays,
+ int encrChainCertNearExpiryThresholdDays,
+ int signChainCertNearExpiryThresholdDays );
+
+ ~KeyResolver();
+
+ struct Item : public KeyApprovalDialog::Item {
+ Item()
+ : KeyApprovalDialog::Item(),
+ signPref( UnknownSigningPreference ),
+ format( AutoFormat ),
+ needKeys( true ) {}
+ Item( const QString & a,
+ EncryptionPreference e, SigningPreference s,
+ CryptoMessageFormat f )
+ : KeyApprovalDialog::Item( a, std::vector<GpgME::Key>(), e ),
+ signPref( s ), format( f ), needKeys( true ) {}
+ Item( const QString & a, const std::vector<GpgME::Key> & k,
+ EncryptionPreference e, SigningPreference s,
+ CryptoMessageFormat f )
+ : KeyApprovalDialog::Item( a, k, e ),
+ signPref( s ), format( f ), needKeys( false ) {}
+
+ SigningPreference signPref;
+ CryptoMessageFormat format;
+ bool needKeys;
+ };
+
+
+ /**
+ Set the fingerprints of keys to be used for encrypting to
+ self. Also looks them up and complains if they're not usable or
+ found.
+ */
+ Kpgp::Result setEncryptToSelfKeys( const QStringList & fingerprints );
+ /**
+ Set the fingerprints of keys to be used for signing. Also
+ looks them up and complains if they're not usable or found.
+ */
+ Kpgp::Result setSigningKeys( const QStringList & fingerprints );
+ /**
+ Set the list of primary (To/CC) recipient addresses. Also looks
+ up possible keys, but doesn't interact with the user.
+ */
+ void setPrimaryRecipients( const QStringList & addresses );
+ /**
+ Set the list of secondary (BCC) recipient addresses. Also looks
+ up possible keys, but doesn't interact with the user.
+ */
+ void setSecondaryRecipients( const QStringList & addresses );
+
+
+ /**
+ Determine whether to sign or not, depending on the
+ per-recipient signing preferences, as well as the availability
+ of usable signing keys.
+ */
+ Action checkSigningPreferences( bool signingRequested ) const;
+ /**
+ Determine whether to encrypt or not, depending on the
+ per-recipient encryption preferences, as well as the availability
+ of usable encryption keys.
+ */
+ Action checkEncryptionPreferences( bool encryptionRequested ) const;
+
+ /**
+ Queries the user for missing keys and displays a key approval
+ dialog if needed.
+ */
+ Kpgp::Result resolveAllKeys( bool& signingRequested, bool& encryptionRequested );
+
+ /**
+ @return the signing keys to use (if any) for the given message
+ format.
+ */
+ std::vector<GpgME::Key> signingKeys( CryptoMessageFormat f ) const;
+
+ struct SplitInfo {
+ SplitInfo() {}
+ SplitInfo( const QStringList & r ) : recipients( r ) {}
+ SplitInfo( const QStringList & r, const std::vector<GpgME::Key> & k )
+ : recipients( r ), keys( k ) {}
+ QStringList recipients;
+ std::vector<GpgME::Key> keys;
+ };
+ /** @return the found distinct sets of items for format \a f. The
+ returned vector will contain more than one item only if
+ secondary recipients have been specified.
+ */
+ std::vector<SplitInfo> encryptionItems( CryptoMessageFormat f ) const;
+
+ private:
+ void dump() const;
+ std::vector<Item> getEncryptionItems( const QStringList & recipients );
+ std::vector<GpgME::Key> getEncryptionKeys( const QString & recipient, bool quiet ) const;
+
+ Kpgp::Result showKeyApprovalDialog();
+
+ bool encryptionPossible() const;
+ bool signingPossible() const;
+ Kpgp::Result resolveEncryptionKeys( bool signingRequested );
+ Kpgp::Result resolveSigningKeysForEncryption();
+ Kpgp::Result resolveSigningKeysForSigningOnly();
+ Kpgp::Result checkKeyNearExpiry( const GpgME::Key & key,
+ const char * dontAskAgainName, bool mine,
+ bool sign, bool ca=false, int recurse_limit=100,
+ const GpgME::Key & orig_key=GpgME::Key::null ) const;
+ void collapseAllSplitInfos();
+ void addToAllSplitInfos( const std::vector<GpgME::Key> & keys, unsigned int formats );
+ void addKeys( const std::vector<Item> & items, CryptoMessageFormat f );
+ void addKeys( const std::vector<Item> & items );
+ QStringList allRecipients() const;
+ std::vector<GpgME::Key> signingKeysFor( CryptoMessageFormat f ) const;
+ std::vector<GpgME::Key> encryptToSelfKeysFor( CryptoMessageFormat f ) const;
+
+ std::vector<GpgME::Key> lookup( const QStringList & patterns, bool secret=false ) const;
+
+ bool haveTrustedEncryptionKey( const QString & person ) const;
+
+ std::vector<GpgME::Key> selectKeys( const QString & person, const QString & msg,
+ const std::vector<GpgME::Key> & selectedKeys=std::vector<GpgME::Key>() ) const;
+
+ QStringList keysForAddress( const QString & address ) const;
+ void setKeysForAddress( const QString & address, const QStringList& pgpKeyFingerprints, const QStringList& smimeCertFingerprints ) const;
+
+ bool encryptToSelf() const { return mEncryptToSelf; }
+ bool showApprovalDialog() const { return mShowApprovalDialog; }
+
+ int encryptKeyNearExpiryWarningThresholdInDays() const {
+ return mEncryptKeyNearExpiryWarningThreshold;
+ }
+ int signingKeyNearExpiryWarningThresholdInDays() const {
+ return mSigningKeyNearExpiryWarningThreshold;
+ }
+
+ int encryptRootCertNearExpiryWarningThresholdInDays() const {
+ return mEncryptRootCertNearExpiryWarningThreshold;
+ }
+ int signingRootCertNearExpiryWarningThresholdInDays() const {
+ return mSigningRootCertNearExpiryWarningThreshold;
+ }
+
+ int encryptChainCertNearExpiryWarningThresholdInDays() const {
+ return mEncryptChainCertNearExpiryWarningThreshold;
+ }
+ int signingChainCertNearExpiryWarningThresholdInDays() const {
+ return mSigningChainCertNearExpiryWarningThreshold;
+ }
+
+ struct ContactPreferences {
+ ContactPreferences();
+ Kleo::EncryptionPreference encryptionPreference;
+ Kleo::SigningPreference signingPreference;
+ Kleo::CryptoMessageFormat cryptoMessageFormat;
+ QStringList pgpKeyFingerprints;
+ QStringList smimeCertFingerprints;
+ };
+
+ ContactPreferences lookupContactPreferences( const QString& address ) const;
+ void saveContactPreference( const QString& email, const ContactPreferences& pref ) const;
+
+ private:
+ class EncryptionPreferenceCounter;
+ friend class ::Kleo::KeyResolver::EncryptionPreferenceCounter;
+ class SigningPreferenceCounter;
+ friend class ::Kleo::KeyResolver::SigningPreferenceCounter;
+
+ class Private;
+ Private * d;
+
+ bool mEncryptToSelf;
+ const bool mShowApprovalDialog : 1;
+ const bool mOpportunisticEncyption : 1;
+ const unsigned int mCryptoMessageFormats;
+
+ const int mEncryptKeyNearExpiryWarningThreshold;
+ const int mSigningKeyNearExpiryWarningThreshold;
+ const int mEncryptRootCertNearExpiryWarningThreshold;
+ const int mSigningRootCertNearExpiryWarningThreshold;
+ const int mEncryptChainCertNearExpiryWarningThreshold;
+ const int mSigningChainCertNearExpiryWarningThreshold;
+ };
+
+} // namespace Kleo
+
+#endif // __KLEO_KEYRESOLVER_H__
diff --git a/kmail/khtmlparthtmlwriter.cpp b/kmail/khtmlparthtmlwriter.cpp
new file mode 100644
index 00000000..643d743c
--- /dev/null
+++ b/kmail/khtmlparthtmlwriter.cpp
@@ -0,0 +1,154 @@
+/* -*- c++ -*-
+ khtmlparthtmlwriter.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include <config.h>
+
+#include "khtmlparthtmlwriter.h"
+
+#include <kdebug.h>
+#include <khtml_part.h>
+#include <khtmlview.h>
+
+#include <dom/dom_string.h>
+#include <dom/html_document.h>
+#include <dom/html_image.h>
+#include <dom/html_misc.h>
+
+#include <cassert>
+
+namespace KMail {
+
+ KHtmlPartHtmlWriter::KHtmlPartHtmlWriter( KHTMLPart * part,
+ QObject * parent, const char * name )
+ : QObject( parent, name ), HtmlWriter(),
+ mHtmlPart( part ), mState( Ended ), mHtmlTimer( 0, "mHtmlTimer" )
+ {
+ assert( part );
+ connect( &mHtmlTimer, SIGNAL(timeout()), SLOT(slotWriteNextHtmlChunk()) );
+ }
+
+ KHtmlPartHtmlWriter::~KHtmlPartHtmlWriter() {
+
+ }
+
+ void KHtmlPartHtmlWriter::begin( const QString & css ) {
+ if ( mState != Ended ) {
+ kdWarning( 5006 ) << "KHtmlPartHtmlWriter: begin() called on non-ended session!" << endl;
+ reset();
+ }
+
+ mEmbeddedPartMap.clear();
+
+ // clear the widget:
+ mHtmlPart->view()->setUpdatesEnabled( false );
+ mHtmlPart->view()->viewport()->setUpdatesEnabled( false );
+ static_cast<QScrollView *>(mHtmlPart->widget())->ensureVisible( 0, 0 );
+
+ mHtmlPart->begin( KURL( "file:/" ) );
+ if ( !css.isEmpty() )
+ mHtmlPart->setUserStyleSheet( css );
+ mState = Begun;
+ }
+
+ void KHtmlPartHtmlWriter::end() {
+ kdWarning( mState != Begun, 5006 ) << "KHtmlPartHtmlWriter: end() called on non-begun or queued session!" << endl;
+ mHtmlPart->end();
+
+ resolveCidUrls();
+
+ mHtmlPart->view()->viewport()->setUpdatesEnabled( true );
+ mHtmlPart->view()->setUpdatesEnabled( true );
+ mHtmlPart->view()->viewport()->repaint( false );
+ mState = Ended;
+ }
+
+ void KHtmlPartHtmlWriter::reset() {
+ if ( mState != Ended ) {
+ mHtmlTimer.stop();
+ mHtmlQueue.clear();
+ mState = Begun; // don't run into end()'s warning
+ end();
+ }
+ mState = Ended;
+ }
+
+ void KHtmlPartHtmlWriter::write( const QString & str ) {
+ kdWarning( mState != Begun, 5006 ) << "KHtmlPartHtmlWriter: write() called in Ended or Queued state!" << endl;
+ mHtmlPart->write( str );
+ }
+
+ void KHtmlPartHtmlWriter::queue( const QString & str ) {
+ static const uint chunksize = 16384;
+ for ( uint pos = 0 ; pos < str.length() ; pos += chunksize )
+ mHtmlQueue.push_back( str.mid( pos, chunksize ) );
+ mState = Queued;
+ }
+
+ void KHtmlPartHtmlWriter::flush() {
+ slotWriteNextHtmlChunk();
+ }
+
+ void KHtmlPartHtmlWriter::slotWriteNextHtmlChunk() {
+ if ( mHtmlQueue.empty() ) {
+ mState = Begun; // don't run into end()'s warning
+ end();
+ } else {
+ mHtmlPart->write( mHtmlQueue.front() );
+ mHtmlQueue.pop_front();
+ mHtmlTimer.start( 0, true );
+ }
+ }
+
+ void KHtmlPartHtmlWriter::embedPart( const QCString & contentId,
+ const QString & contentURL ) {
+ mEmbeddedPartMap[QString(contentId)] = contentURL;
+ }
+
+ void KHtmlPartHtmlWriter::resolveCidUrls()
+ {
+ DOM::HTMLDocument document = mHtmlPart->htmlDocument();
+ DOM::HTMLCollection images = document.images();
+ for ( DOM::Node node = images.firstItem(); !node.isNull(); node = images.nextItem() ) {
+ DOM::HTMLImageElement image( node );
+ KURL url( image.src().string() );
+ if ( url.protocol() == "cid" ) {
+ EmbeddedPartMap::const_iterator it = mEmbeddedPartMap.find( url.path() );
+ if ( it != mEmbeddedPartMap.end() ) {
+ kdDebug(5006) << "Replacing " << url.prettyURL() << " by " << it.data() << endl;
+ image.setSrc( it.data() );
+ }
+ }
+ }
+ }
+
+} // namespace KMail
+
+#include "khtmlparthtmlwriter.moc"
diff --git a/kmail/khtmlparthtmlwriter.h b/kmail/khtmlparthtmlwriter.h
new file mode 100644
index 00000000..92eb15a9
--- /dev/null
+++ b/kmail/khtmlparthtmlwriter.h
@@ -0,0 +1,83 @@
+/* -*- c++ -*-
+ khtmlparthtmlwriter.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_KHTMLPARTHTMLWRITER_H__
+#define __KMAIL_KHTMLPARTHTMLWRITER_H__
+
+#include "interfaces/htmlwriter.h"
+#include <qobject.h>
+
+#include <qstringlist.h>
+#include <qtimer.h>
+
+class QString;
+class KHTMLPart;
+
+namespace KMail {
+
+ class KHtmlPartHtmlWriter : public QObject, public HtmlWriter {
+ Q_OBJECT
+ public:
+ // Key is Content-Id, value is URL
+ typedef QMap<QString, QString> EmbeddedPartMap;
+ KHtmlPartHtmlWriter( KHTMLPart * part,
+ QObject * parent=0, const char * name = 0 );
+ virtual ~KHtmlPartHtmlWriter();
+
+ void begin( const QString & cssDefs );
+ void end();
+ void reset();
+ void write( const QString & str );
+ void queue( const QString & str );
+ void flush();
+ void embedPart( const QCString & contentId, const QString & url );
+
+ private slots:
+ void slotWriteNextHtmlChunk();
+
+ private:
+ void resolveCidUrls();
+
+ private:
+ KHTMLPart * mHtmlPart;
+ QStringList mHtmlQueue;
+ QTimer mHtmlTimer;
+ enum State {
+ Begun,
+ Queued,
+ Ended
+ } mState;
+ EmbeddedPartMap mEmbeddedPartMap;
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_KHTMLPARTHTMLWRITER_H__
diff --git a/kmail/kleo_util.h b/kmail/kleo_util.h
new file mode 100644
index 00000000..b2378e9f
--- /dev/null
+++ b/kmail/kleo_util.h
@@ -0,0 +1,81 @@
+/* -*- c++ -*-
+ kleo_util.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2004 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KDEPIM_KMAIL_KLEO_UTIL_H__
+#define __KDEPIM_KMAIL_KLEO_UTIL_H__
+
+#include <kleo/enum.h>
+
+static const Kleo::CryptoMessageFormat cryptoMessageFormats[] = {
+ Kleo::AutoFormat,
+ Kleo::InlineOpenPGPFormat,
+ Kleo::OpenPGPMIMEFormat,
+ Kleo::SMIMEFormat,
+ Kleo::SMIMEOpaqueFormat
+};
+static const int numCryptoMessageFormats = sizeof cryptoMessageFormats / sizeof *cryptoMessageFormats ;
+
+static const Kleo::CryptoMessageFormat concreteCryptoMessageFormats[] = {
+ Kleo::OpenPGPMIMEFormat,
+ Kleo::SMIMEFormat,
+ Kleo::SMIMEOpaqueFormat,
+ Kleo::InlineOpenPGPFormat
+};
+static const unsigned int numConcreteCryptoMessageFormats
+ = sizeof concreteCryptoMessageFormats / sizeof *concreteCryptoMessageFormats ;
+
+static inline Kleo::CryptoMessageFormat cb2format( int idx ) {
+ return cryptoMessageFormats[ idx >= 0 && idx < numCryptoMessageFormats ? idx : 0 ];
+}
+
+static inline int format2cb( Kleo::CryptoMessageFormat f ) {
+ for ( int i = 0 ; i < numCryptoMessageFormats ; ++i )
+ if ( f == cryptoMessageFormats[i] )
+ return i;
+ return 0;
+}
+
+
+//
+// some helper functions indicating the need for CryptoMessageFormat
+// to be a class type :)
+//
+
+static inline bool isSMIME( Kleo::CryptoMessageFormat f ) {
+ return f == Kleo::SMIMEFormat || f == Kleo::SMIMEOpaqueFormat ;
+}
+
+static inline bool isOpenPGP( Kleo::CryptoMessageFormat f ) {
+ return f == Kleo::InlineOpenPGPFormat || f == Kleo::OpenPGPMIMEFormat ;
+}
+
+
+#endif // __KDEPIM_KMAIL_KLEO_UTIL_H__
diff --git a/kmail/klistboxdialog.cpp b/kmail/klistboxdialog.cpp
new file mode 100644
index 00000000..030ca3b0
--- /dev/null
+++ b/kmail/klistboxdialog.cpp
@@ -0,0 +1,83 @@
+// This must be first
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "klistboxdialog.h"
+
+#include <qlabel.h>
+#include <qlayout.h>
+
+KListBoxDialog::KListBoxDialog( QString& _selectedString,
+ const QString& caption,
+ const QString& labelText,
+ QWidget* parent,
+ const char* name,
+ bool modal )
+ : KDialogBase( parent, name, modal, caption, Ok|Cancel, Ok, true ),
+ selectedString( _selectedString )
+
+{
+ if ( !name )
+ setName( "KListBoxDialog" );
+ resize( 400, 180 );
+
+ QFrame *page = makeMainWidget();
+ QVBoxLayout *topLayout = new QVBoxLayout( page, 0, spacingHint() );
+ labelAboveLA = new QLabel( page, "labelAboveLA" );
+ labelAboveLA->setText( labelText );
+
+ topLayout->addWidget( labelAboveLA );
+
+ entriesLB = new QListBox( page, "entriesLB" );
+
+ topLayout->addWidget( entriesLB );
+
+ commentBelowLA = new QLabel( page, "commentBelowLA" );
+ commentBelowLA->setText( "" );
+ topLayout->addWidget( commentBelowLA );
+ commentBelowLA->hide();
+
+ // signals and slots connections
+ connect( entriesLB, SIGNAL( highlighted( const QString& ) ),
+ this, SLOT( highlighted( const QString& ) ) );
+ connect( entriesLB, SIGNAL( selected(int) ),
+ SLOT( slotOk() ) );
+ // buddies
+ labelAboveLA->setBuddy( entriesLB );
+}
+
+/*
+ * Destroys the object and frees any allocated resources
+ */
+KListBoxDialog::~KListBoxDialog()
+{
+ // no need to delete child widgets, Qt does it all for us
+}
+
+void KListBoxDialog::setLabelAbove(const QString& label)
+{
+ labelAboveLA->setText( label );
+ if( label.isEmpty() )
+ labelAboveLA->hide();
+ else
+ labelAboveLA->show();
+}
+
+void KListBoxDialog::setCommentBelow(const QString& comment)
+{
+ commentBelowLA->setText( comment );
+ if( comment.isEmpty() )
+ commentBelowLA->hide();
+ else
+ commentBelowLA->show();
+}
+
+
+
+void KListBoxDialog::highlighted( const QString& txt )
+{
+ selectedString = txt;
+}
+
+#include "klistboxdialog.moc"
diff --git a/kmail/klistboxdialog.h b/kmail/klistboxdialog.h
new file mode 100644
index 00000000..10656233
--- /dev/null
+++ b/kmail/klistboxdialog.h
@@ -0,0 +1,36 @@
+#ifndef KLISTBOXDIALOG_H
+#define KLISTBOXDIALOG_H
+
+#include <kdialogbase.h>
+
+class QLabel;
+class QListBox;
+
+class KListBoxDialog : public KDialogBase
+{
+ Q_OBJECT
+
+public:
+ KListBoxDialog( QString& _selectedString,
+ const QString& caption,
+ const QString& labelText,
+ QWidget* parent = 0,
+ const char* name = 0,
+ bool modal = true );
+ ~KListBoxDialog();
+
+ void setLabelAbove( const QString& label );
+ void setCommentBelow(const QString& comment);
+
+ QListBox* entriesLB;
+
+private slots:
+ void highlighted( const QString& );
+
+protected:
+ QString& selectedString;
+ QLabel* labelAboveLA;
+ QLabel* commentBelowLA;
+};
+
+#endif
diff --git a/kmail/klistviewindexedsearchline.cpp b/kmail/klistviewindexedsearchline.cpp
new file mode 100644
index 00000000..781d2332
--- /dev/null
+++ b/kmail/klistviewindexedsearchline.cpp
@@ -0,0 +1,74 @@
+
+/* This file is part of KMail
+ * Copyright (C) 2005 Lus Pedro Coelho <luis@luispedro.org>
+ *
+ * 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 Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#include "klistviewindexedsearchline.h"
+#include <kdebug.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "headeritem.h"
+#include "kmheaders.h"
+#include "kmfolder.h"
+#include "index.h"
+
+using KMail::HeaderListQuickSearch;
+
+KListViewIndexedSearchLine::KListViewIndexedSearchLine( QWidget* parent, KListView* listView, KActionCollection* actionCollection, const char* name ):
+ HeaderListQuickSearch( parent, listView, actionCollection, name ),
+ mFiltering( false )
+{
+}
+
+KListViewIndexedSearchLine::~KListViewIndexedSearchLine() {
+}
+
+
+void KListViewIndexedSearchLine::updateSearch( const QString& s ) {
+ kdDebug( 5006 ) << "updateSearch( -" << s << "- )" << endl;
+ mFiltering = false;
+ if ( !s.isNull() && !s.isEmpty() ) {
+ bool ok = false;
+ KMMsgIndex* index = kmkernel->msgIndex();
+ if ( index ) {
+ mResults = index->simpleSearch( s, &ok );
+ std::sort( mResults.begin(), mResults.end() );
+ mFiltering = ok;
+ }
+ }
+ KListViewSearchLine::updateSearch( s );
+}
+
+bool KListViewIndexedSearchLine::itemMatches( const QListViewItem* item, const QString& s ) const {
+ if ( mFiltering &&
+ std::binary_search( mResults.begin(), mResults.end(), static_cast<const KMail::HeaderItem*>( item )->msgSerNum() ) )
+ return true;
+ return KMail::HeaderListQuickSearch::itemMatches( item, s );
+}
+
+#include "klistviewindexedsearchline.moc"
+
diff --git a/kmail/klistviewindexedsearchline.h b/kmail/klistviewindexedsearchline.h
new file mode 100644
index 00000000..f067c1ca
--- /dev/null
+++ b/kmail/klistviewindexedsearchline.h
@@ -0,0 +1,76 @@
+#ifndef LPC_KLISTVIEWINDEXEDSEARCHLINE_H1107549660_INCLUDE_GUARD_
+#define LPC_KLISTVIEWINDEXEDSEARCHLINE_H1107549660_INCLUDE_GUARD_
+
+/* This file is part of KMail
+ * Copyright (C) 2005 Lus Pedro Coelho <luis@luispedro.org>
+ *
+ * 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 Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+
+#include <klineedit.h>
+#include <klistviewsearchline.h>
+#include "headerlistquicksearch.h"
+#include <qhbox.h>
+
+#include <vector>
+
+class KListView;
+class QListViewItem;
+class QToolButton;
+class KListViewSearchLine;
+
+/**
+ * Extends HeaderListQuickSearch to also search inside message bodies using KMMsgIndex
+ */
+
+class KDEUI_EXPORT KListViewIndexedSearchLine: public KMail::HeaderListQuickSearch
+{
+ Q_OBJECT
+
+public:
+
+ explicit KListViewIndexedSearchLine(QWidget *parent, KListView *listView, KActionCollection* action, const char *name = 0);
+ ~KListViewIndexedSearchLine();
+
+public slots:
+ /**
+ * Updates search to only make visible the items that match \a s. If
+ * \a s is null then the line edit's text will be used.
+ */
+ virtual void updateSearch(const QString &s = QString::null);
+
+protected:
+ virtual bool itemMatches(const QListViewItem *item, const QString &s) const;
+
+private:
+ std::vector<unsigned> mResults;
+ bool mFiltering;
+
+};
+
+
+
+#endif /* LPC_KLISTVIEWINDEXEDSEARCHLINE_H1107549660_INCLUDE_GUARD_ */
diff --git a/kmail/kmaccount.cpp b/kmail/kmaccount.cpp
new file mode 100644
index 00000000..65222dd4
--- /dev/null
+++ b/kmail/kmaccount.cpp
@@ -0,0 +1,500 @@
+// KMail Account
+#include <config.h>
+
+#include "kmaccount.h"
+
+#include "accountmanager.h"
+using KMail::AccountManager;
+#include "globalsettings.h"
+#include "kmacctfolder.h"
+#include "kmfoldermgr.h"
+#include "kmfiltermgr.h"
+#include "messagesender.h"
+#include "kmmessage.h"
+#include "broadcaststatus.h"
+using KPIM::BroadcastStatus;
+#include "kmfoldercachedimap.h"
+
+#include "progressmanager.h"
+using KPIM::ProgressItem;
+using KPIM::ProgressManager;
+
+#include <libkpimidentities/identitymanager.h>
+#include <libkpimidentities/identity.h>
+
+using KMail::FolderJob;
+
+#include <kapplication.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+#include <kconfig.h>
+
+#include <qeventloop.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <assert.h>
+
+//----------------------
+#include "kmaccount.moc"
+
+//-----------------------------------------------------------------------------
+KMPrecommand::KMPrecommand(const QString &precommand, QObject *parent)
+ : QObject(parent), mPrecommand(precommand)
+{
+ BroadcastStatus::instance()->setStatusMsg(
+ i18n("Executing precommand %1").arg(precommand ));
+
+ mPrecommandProcess.setUseShell(true);
+ mPrecommandProcess << precommand;
+
+ connect(&mPrecommandProcess, SIGNAL(processExited(KProcess *)),
+ SLOT(precommandExited(KProcess *)));
+}
+
+//-----------------------------------------------------------------------------
+KMPrecommand::~KMPrecommand()
+{
+}
+
+
+//-----------------------------------------------------------------------------
+bool KMPrecommand::start()
+{
+ bool ok = mPrecommandProcess.start( KProcess::NotifyOnExit );
+ if (!ok) KMessageBox::error(0, i18n("Could not execute precommand '%1'.")
+ .arg(mPrecommand));
+ return ok;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMPrecommand::precommandExited(KProcess *p)
+{
+ int exitCode = p->normalExit() ? p->exitStatus() : -1;
+ if (exitCode)
+ KMessageBox::error(0, i18n("The precommand exited with code %1:\n%2")
+ .arg(exitCode).arg(strerror(exitCode)));
+ emit finished(!exitCode);
+}
+
+
+//-----------------------------------------------------------------------------
+KMAccount::KMAccount(AccountManager* aOwner, const QString& aName, uint id)
+ : KAccount( id, aName ),
+ mTrash(KMKernel::self()->trashFolder()->idString()),
+ mOwner(aOwner),
+ mFolder(0),
+ mTimer(0),
+ mInterval(0),
+ mExclude(false),
+ mCheckingMail(false),
+ mPrecommandSuccess(true),
+ mHasInbox(false),
+ mMailCheckProgressItem(0),
+ mIdentityId(0)
+{
+ assert(aOwner != 0);
+}
+
+void KMAccount::init() {
+ mTrash = kmkernel->trashFolder()->idString();
+ mExclude = false;
+ mInterval = 0;
+ mNewInFolder.clear();
+}
+
+//-----------------------------------------------------------------------------
+KMAccount::~KMAccount()
+{
+ if ( (kmkernel && !kmkernel->shuttingDown()) && mFolder ) mFolder->removeAccount(this);
+ if (mTimer) deinstallTimer();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMAccount::setName(const QString& aName)
+{
+ mName = aName;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMAccount::clearPasswd()
+{
+}
+
+
+//-----------------------------------------------------------------------------
+void KMAccount::setFolder(KMFolder* aFolder, bool addAccount)
+{
+ if(!aFolder) {
+ //kdDebug(5006) << "KMAccount::setFolder() : aFolder == 0" << endl;
+ mFolder = 0;
+ return;
+ }
+ mFolder = (KMAcctFolder*)aFolder;
+ if (addAccount) mFolder->addAccount(this);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMAccount::readConfig(KConfig& config)
+{
+ QString folderName;
+ mFolder = 0;
+ folderName = config.readEntry("Folder");
+ setCheckInterval(config.readNumEntry("check-interval", 0));
+ setTrash(config.readEntry("trash", kmkernel->trashFolder()->idString()));
+ setCheckExclude(config.readBoolEntry("check-exclude", false));
+ setPrecommand(config.readPathEntry("precommand"));
+ setIdentityId(config.readNumEntry("identity-id", 0));
+ if (!folderName.isEmpty())
+ {
+ setFolder(kmkernel->folderMgr()->findIdString(folderName), true);
+ }
+
+ if (mInterval == 0)
+ deinstallTimer();
+ else
+ installTimer();
+}
+
+void KMAccount::readTimerConfig()
+{
+ // Re-reads and checks check-interval value and deinstalls timer incase check-interval
+ // for mail check is disabled.
+ // Or else, the mail sync goes into a infinite loop (kolab/issue2607)
+ if (mInterval == 0)
+ deinstallTimer();
+ else
+ installTimer();
+}
+
+//-----------------------------------------------------------------------------
+void KMAccount::writeConfig(KConfig& config)
+{
+ // ID, Name
+ KAccount::writeConfig(config);
+
+ config.writeEntry("Type", type());
+ config.writeEntry("Folder", mFolder ? mFolder->idString() : QString::null);
+ config.writeEntry("check-interval", mInterval);
+ config.writeEntry("check-exclude", mExclude);
+ config.writePathEntry("precommand", mPrecommand);
+ config.writeEntry("trash", mTrash);
+ if ( mIdentityId && mIdentityId != kmkernel->identityManager()->defaultIdentity().uoid() )
+ config.writeEntry("identity-id", mIdentityId);
+ else
+ config.deleteEntry("identity-id");
+}
+
+
+//-----------------------------------------------------------------------------
+void KMAccount::sendReceipt(KMMessage* aMsg)
+{
+ KConfig* cfg = KMKernel::config();
+ bool sendReceipts;
+
+ KConfigGroupSaver saver(cfg, "General");
+
+ sendReceipts = cfg->readBoolEntry("send-receipts", false);
+ if (!sendReceipts) return;
+
+ KMMessage *newMsg = aMsg->createDeliveryReceipt();
+ if (newMsg) {
+ mReceipts.append(newMsg);
+ QTimer::singleShot( 0, this, SLOT( sendReceipts() ) );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+bool KMAccount::processNewMsg(KMMessage* aMsg)
+{
+ int rc, processResult;
+
+ assert(aMsg != 0);
+
+ // Save this one for readding
+ KMFolderCachedImap* parent = 0;
+ if( type() == "cachedimap" )
+ parent = static_cast<KMFolderCachedImap*>( aMsg->storage() );
+
+ // checks whether we should send delivery receipts
+ // and sends them.
+ sendReceipt(aMsg);
+
+ // Set status of new messages that are marked as old to read, otherwise
+ // the user won't see which messages newly arrived.
+ // This is only valid for pop accounts and produces wrong stati for imap.
+ if ( type() != "cachedimap" && type() != "imap" ) {
+ if ( aMsg->isOld() )
+ aMsg->setStatus(KMMsgStatusUnread); // -sanders
+ // aMsg->setStatus(KMMsgStatusRead);
+ else
+ aMsg->setStatus(KMMsgStatusNew);
+ }
+/*
+QFile fileD0( "testdat_xx-kmaccount-0" );
+if( fileD0.open( IO_WriteOnly ) ) {
+ QDataStream ds( &fileD0 );
+ ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() );
+ fileD0.close(); // If data is 0 we just create a zero length file.
+}
+*/
+ // 0==message moved; 1==processing ok, no move; 2==critical error, abort!
+
+ processResult = kmkernel->filterMgr()->process(aMsg,KMFilterMgr::Inbound,true,id());
+ if (processResult == 2) {
+ perror("Critical error: Unable to collect mail (out of space?)");
+ KMessageBox::information(0,(i18n("Critical error: "
+ "Unable to collect mail: ")) + QString::fromLocal8Bit(strerror(errno)));
+ return false;
+ }
+ else if (processResult == 1)
+ {
+ if( type() == "cachedimap" )
+ ; // already done by caller: parent->addMsgInternal( aMsg, false );
+ else {
+ // TODO: Perhaps it would be best, if this if was handled by a virtual
+ // method, so the if( !dimap ) above could die?
+ kmkernel->filterMgr()->tempOpenFolder(mFolder);
+ rc = mFolder->addMsg(aMsg);
+/*
+QFile fileD0( "testdat_xx-kmaccount-1" );
+if( fileD0.open( IO_WriteOnly ) ) {
+ QDataStream ds( &fileD0 );
+ ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() );
+ fileD0.close(); // If data is 0 we just create a zero length file.
+}
+*/
+ if (rc) {
+ perror("failed to add message");
+ KMessageBox::information(0, i18n("Failed to add message:\n") +
+ QString(strerror(rc)));
+ return false;
+ }
+ int count = mFolder->count();
+ // If count == 1, the message is immediately displayed
+ if (count != 1) mFolder->unGetMsg(count - 1);
+ }
+ }
+
+ // Count number of new messages for each folder
+ QString folderId;
+ if ( processResult == 1 ) {
+ folderId = ( type() == "cachedimap" ) ? parent->folder()->idString()
+ : mFolder->idString();
+ }
+ else {
+ folderId = aMsg->parent()->idString();
+ }
+ addToNewInFolder( folderId, 1 );
+
+ return true; //Everything's fine - message has been added by filter }
+}
+
+//-----------------------------------------------------------------------------
+void KMAccount::setCheckInterval(int aInterval)
+{
+ if (aInterval <= 0)
+ mInterval = 0;
+ else
+ mInterval = aInterval;
+ // Don't call installTimer from here! See #117935.
+}
+
+int KMAccount::checkInterval() const
+{
+ if ( mInterval <= 0 )
+ return mInterval;
+ return QMAX( mInterval, GlobalSettings::self()->minimumCheckInterval() );
+}
+
+//----------------------------------------------------------------------------
+void KMAccount::deleteFolderJobs()
+{
+ mJobList.setAutoDelete(true);
+ mJobList.clear();
+ mJobList.setAutoDelete(false);
+}
+
+//----------------------------------------------------------------------------
+void KMAccount::ignoreJobsForMessage( KMMessage* msg )
+{
+ //FIXME: remove, make folders handle those
+ for( QPtrListIterator<FolderJob> it(mJobList); it.current(); ++it ) {
+ if ( it.current()->msgList().first() == msg) {
+ FolderJob *job = it.current();
+ mJobList.remove( job );
+ delete job;
+ break;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMAccount::setCheckExclude(bool aExclude)
+{
+ mExclude = aExclude;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMAccount::installTimer()
+{
+ if (mInterval <= 0) return;
+ if(!mTimer)
+ {
+ mTimer = new QTimer(0, "mTimer");
+ connect(mTimer,SIGNAL(timeout()),SLOT(mailCheck()));
+ }
+ else
+ {
+ mTimer->stop();
+ }
+ mTimer->start( checkInterval() * 60000 );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMAccount::deinstallTimer()
+{
+ delete mTimer;
+ mTimer = 0;
+}
+
+//-----------------------------------------------------------------------------
+bool KMAccount::runPrecommand(const QString &precommand)
+{
+ // Run the pre command if there is one
+ if ( precommand.isEmpty() )
+ return true;
+
+ KMPrecommand precommandProcess(precommand, this);
+
+ BroadcastStatus::instance()->setStatusMsg(
+ i18n("Executing precommand %1").arg(precommand ));
+
+ connect(&precommandProcess, SIGNAL(finished(bool)),
+ SLOT(precommandExited(bool)));
+
+ kdDebug(5006) << "Running precommand " << precommand << endl;
+ if (!precommandProcess.start()) return false;
+
+ kapp->eventLoop()->enterLoop();
+
+ return mPrecommandSuccess;
+}
+
+//-----------------------------------------------------------------------------
+void KMAccount::precommandExited(bool success)
+{
+ mPrecommandSuccess = success;
+ kapp->eventLoop()->exitLoop();
+}
+
+//-----------------------------------------------------------------------------
+void KMAccount::mailCheck()
+{
+ if (mTimer)
+ mTimer->stop();
+
+ if ( kmkernel ) {
+ AccountManager *acctmgr = kmkernel->acctMgr();
+ if ( acctmgr )
+ acctmgr->singleCheckMail(this, false);
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMAccount::sendReceipts()
+{
+ QValueList<KMMessage*>::Iterator it;
+ for(it = mReceipts.begin(); it != mReceipts.end(); ++it)
+ kmkernel->msgSender()->send(*it); //might process events
+ mReceipts.clear();
+}
+
+//-----------------------------------------------------------------------------
+QString KMAccount::encryptStr(const QString &aStr)
+{
+ QString result;
+ for (uint i = 0; i < aStr.length(); i++)
+ /* yes, no typo. can't encode ' ' or '!' because
+ they're the unicode BOM. stupid scrambling. stupid. */
+ result += (aStr[i].unicode() <= 0x21 ) ? aStr[i] :
+ QChar(0x1001F - aStr[i].unicode());
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+QString KMAccount::importPassword(const QString &aStr)
+{
+ unsigned int i, val;
+ unsigned int len = aStr.length();
+ QCString result;
+ result.resize(len+1);
+
+ for (i=0; i<len; i++)
+ {
+ val = aStr[i] - ' ';
+ val = (255-' ') - val;
+ result[i] = (char)(val + ' ');
+ }
+ result[i] = '\0';
+
+ return encryptStr(result);
+}
+
+void KMAccount::invalidateIMAPFolders()
+{
+ // Default: Don't do anything. The IMAP account will handle it
+}
+
+void KMAccount::pseudoAssign( const KMAccount * a ) {
+ if ( !a ) return;
+
+ setName( a->name() );
+ setId( a->id() );
+ setCheckInterval( a->checkInterval() );
+ setCheckExclude( a->checkExclude() );
+ setFolder( a->folder() );
+ setPrecommand( a->precommand() );
+ setTrash( a->trash() );
+ setIdentityId( a->identityId() );
+}
+
+//-----------------------------------------------------------------------------
+void KMAccount::checkDone( bool newmail, CheckStatus status )
+{
+ setCheckingMail( false );
+ // Reset the timeout for automatic mailchecking. The user might have
+ // triggered the check manually.
+ if (mTimer)
+ mTimer->start( checkInterval() * 60000 );
+ if ( mMailCheckProgressItem ) {
+ // set mMailCheckProgressItem = 0 before calling setComplete() to prevent
+ // a race condition
+ ProgressItem *savedMailCheckProgressItem = mMailCheckProgressItem;
+ mMailCheckProgressItem = 0;
+ savedMailCheckProgressItem->setComplete(); // that will delete it
+ }
+
+ emit newMailsProcessed( mNewInFolder );
+ emit finishedCheck( newmail, status );
+ mNewInFolder.clear();
+}
+
+//-----------------------------------------------------------------------------
+void KMAccount::addToNewInFolder( QString folderId, int num )
+{
+ if ( mNewInFolder.find( folderId ) == mNewInFolder.end() )
+ mNewInFolder[folderId] = num;
+ else
+ mNewInFolder[folderId] += num;
+}
diff --git a/kmail/kmaccount.h b/kmail/kmaccount.h
new file mode 100644
index 00000000..9a896e19
--- /dev/null
+++ b/kmail/kmaccount.h
@@ -0,0 +1,336 @@
+/* -*- mode: C++ -*-
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ *
+ * 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 kmaccount_h
+#define kmaccount_h
+
+#include <kprocess.h>
+#include <kaccount.h>
+
+#include <qstring.h>
+#include <qguardedptr.h>
+#include <qvaluelist.h>
+#include <qmap.h>
+
+#include "kmmessage.h"
+class QTimer;
+
+class KMFolder;
+class KMAcctFolder;
+class KConfig;
+class KMFolderJob;
+class KMFolderCachedImap;
+class AccountsPageReceivingTab;
+namespace KMail {
+ class FolderJob;
+ class AccountManager;
+}
+using KMail::AccountManager;
+namespace KPIM { class ProgressItem; }
+using KMail::FolderJob;
+using KPIM::ProgressItem;
+
+class KMAccount;
+typedef QValueList< ::KMAccount* > AccountList;
+
+class KMPrecommand : public QObject
+{
+ Q_OBJECT
+
+public:
+ KMPrecommand(const QString &precommand, QObject *parent = 0);
+ virtual ~KMPrecommand();
+ bool start();
+
+protected slots:
+ void precommandExited(KProcess *);
+
+signals:
+ void finished(bool);
+
+protected:
+ KProcess mPrecommandProcess;
+ QString mPrecommand;
+};
+
+
+class KMAccount: public QObject, public KAccount
+{
+ Q_OBJECT
+ friend class KMail::AccountManager;
+ friend class ::KMail::FolderJob;
+ friend class ::AccountsPageReceivingTab; // part of the config dialog
+ friend class ::KMFolderCachedImap; /* HACK for processNewMSg() */
+
+public:
+ virtual ~KMAccount();
+
+ enum CheckStatus { CheckOK, CheckIgnored, CheckCanceled, CheckAborted,
+ CheckError };
+
+ /** The default check interval */
+ static const int DefaultCheckInterval = 5;
+
+ /**
+ * Returns type of the account
+ */
+ virtual QString type() const { return QString::null; }
+
+ /**
+ * Reimplemented, set account name
+ */
+ virtual void setName(const QString&);
+
+ /**
+ * Account name (reimpl because of ambiguous QObject::name())
+ */
+ virtual QString name() const { return KAccount::name(); }
+
+ /**
+ * Set password to "" (empty string)
+ */
+ virtual void clearPasswd();
+
+ /**
+ * Set intelligent default values to the fields of the account.
+ */
+ virtual void init();
+
+ /**
+ * A weak assignment operator
+ */
+ virtual void pseudoAssign(const KMAccount * a );
+
+ /**
+ * There can be exactly one folder that is fed by messages from an
+ * account. */
+ KMFolder* folder(void) const { return ((KMFolder*)((KMAcctFolder*)mFolder)); }
+ virtual void setFolder(KMFolder*, bool addAccount = false);
+
+ /**
+ * the id of the trash folder (if any) for this account
+ */
+ QString trash() const { return mTrash; }
+ virtual void setTrash(const QString& aTrash) { mTrash = aTrash; }
+
+ /**
+ * Process new mail for this account if one arrived. Returns TRUE if new
+ * mail has been found. Whether the mail is automatically loaded to
+ * an associated folder or not depends on the type of the account.
+ */
+ virtual void processNewMail(bool interactive) = 0;
+
+ /**
+ * Read config file entries. This method is called by the account
+ * manager when a new account is created. The config group is
+ * already properly set by the caller.
+ */
+ virtual void readConfig(KConfig& config);
+ void readTimerConfig();
+
+ /**
+ * Write all account information to given config file. The config group
+ * is already properly set by the caller.
+ */
+ virtual void writeConfig(KConfig& config);
+
+ /**
+ * Set/get interval for checking if new mail arrived (in minutes).
+ * An interval of zero (or less) disables the automatic checking.
+ */
+ virtual void setCheckInterval(int aInterval);
+ int checkInterval() const;
+
+ /**
+ * This can be used to provide a more complex calculation later if we want
+ */
+ inline int defaultCheckInterval(void) const { return DefaultCheckInterval; }
+
+ /**
+ * Deletes the set of FolderJob associated with this account.
+ */
+ void deleteFolderJobs();
+
+ /**
+ * delete jobs associated with this message
+ */
+ virtual void ignoreJobsForMessage( KMMessage* );
+ /**
+ * Set/get whether account should be part of the accounts checked
+ * with "Check Mail".
+ */
+ virtual void setCheckExclude(bool aExclude);
+ bool checkExclude(void) const { return mExclude; }
+
+ /**
+ * Pre command
+ */
+ const QString& precommand(void) const { return mPrecommand; }
+ virtual void setPrecommand(const QString &cmd) { mPrecommand = cmd; }
+
+ /**
+ * Runs the precommand. If the precommand is empty, the method
+ * will just return success and not actually do anything
+ *
+ * @return True if successful, false otherwise
+ */
+ bool runPrecommand(const QString &precommand);
+
+ /**
+ * Very primitive en/de-cryption so that the password is not
+ * readable in the config file. But still very easy breakable.
+ */
+ static QString encryptStr(const QString& inStr);
+ static QString decryptStr(const QString& inStr) { return encryptStr(inStr); }
+
+ static QString importPassword(const QString &);
+
+ /** @return whether this account has an inbox */
+ bool hasInbox() const { return mHasInbox; }
+ virtual void setHasInbox( bool has ) { mHasInbox = has; }
+
+ /**
+ * If this account is a disconnected IMAP account, invalidate it.
+ */
+ virtual void invalidateIMAPFolders();
+
+ /**
+ * Determines whether the account can be checked, currently.
+ * Reimplementations can use this to prevent mailchecks due to
+ * exceeded connection limits, or because a network link iis down.
+ * @return whether mail checks can proceed
+ */
+ virtual bool mailCheckCanProceed() const { return true; }
+
+ /**
+ * Set/Get if this account is currently checking mail
+ */
+ bool checkingMail() { return mCheckingMail; }
+ virtual void setCheckingMail( bool checking ) { mCheckingMail = checking; }
+
+ /**
+ * Call this if the newmail-check ended.
+ * @param newMail true if new mail arrived
+ * @param status the status of the mail check
+ */
+ void checkDone( bool newMail, CheckStatus status );
+
+ /**
+ * Abort all running mail checks. Used when closing the last KMMainWin.
+ * Ensure that mail check can be restarted later, e.g. if reopening a mainwindow
+ * from a composer window.
+ */
+ virtual void cancelMailCheck() {}
+
+ /**
+ * Call ->progress( int foo ) on this to update the account's progress
+ * indicators.
+ */
+ ProgressItem *mailCheckProgressItem() const {
+ return mMailCheckProgressItem;
+ }
+
+ /**
+ * Set/get identity for checking account.
+ */
+ void setIdentityId(uint identityId ) { mIdentityId = identityId; }
+ uint identityId() const{ return mIdentityId; }
+
+signals:
+ /**
+ * Emitted after the mail check is finished.
+ * @param newMail true if there was new mail
+ * @param status the status of the mail check
+ **/
+ virtual void finishedCheck( bool newMail, CheckStatus status );
+
+ /**
+ * Emitted after the mail check is finished.
+ * @param newInFolder number of new messages for each folder
+ **/
+ virtual void newMailsProcessed( const QMap<QString, int> & newInFolder );
+
+protected slots:
+ virtual void mailCheck();
+ virtual void sendReceipts();
+ virtual void precommandExited(bool);
+
+protected:
+ KMAccount( AccountManager* owner, const QString& accountName, uint id);
+
+ /**
+ * Does filtering and storing in a folder for the given message.
+ * Shall be called from within processNewMail() to process the new
+ * messages. Returns false if failed to add new message.
+ */
+ virtual bool processNewMsg(KMMessage* msg);
+
+ /**
+ * Send receipt of message back to sender (confirming
+ * delivery). Checks the config settings, calls
+ * @see KMMessage::createDeliveryReceipt and queues the resulting
+ * message in @p mReceipts.
+ */
+ virtual void sendReceipt(KMMessage* msg);
+
+ /**
+ * Install/deinstall automatic new-mail checker timer.
+ */
+ virtual void installTimer();
+ virtual void deinstallTimer();
+
+ /**
+ * Call this to increase the number of new messages in a folder for
+ * messages which are _not_ processed with processNewMsg().
+ * @param folderId the id of the folder
+ * @param num the number of new message in this folder
+ */
+ void addToNewInFolder( QString folderId, int num );
+
+protected:
+ QString mPrecommand;
+ QString mTrash;
+ AccountManager* mOwner;
+ QGuardedPtr<KMAcctFolder> mFolder;
+ QTimer *mTimer;
+ int mInterval; // this is a switch and a scalar at the same time. If it is 0,
+ // interval mail checking is turned off and the interval spinbox proposes a
+ // default value. If it is non-null, it is the count of minutes between two
+ // automated mail checks. This means that as soon as you disable interval
+ // mail checking, the value in the spin box returns to a default value.
+ bool mExclude;
+ bool mCheckingMail : 1;
+ bool mPrecommandSuccess;
+ QValueList<KMMessage*> mReceipts;
+ QPtrList<FolderJob> mJobList;
+ bool mHasInbox : 1;
+ QGuardedPtr<ProgressItem> mMailCheckProgressItem;
+ uint mIdentityId;
+private:
+ /**
+ * avoid compiler warning about hidden virtual
+ */
+ virtual void setName( const char *name ) { QObject::setName( name ); }
+
+private:
+ // for detailed (per folder) new mail notification
+ QMap<QString, int> mNewInFolder;
+};
+
+#endif /*kmaccount_h*/
diff --git a/kmail/kmacctcachedimap.cpp b/kmail/kmacctcachedimap.cpp
new file mode 100644
index 00000000..fbdc7925
--- /dev/null
+++ b/kmail/kmacctcachedimap.cpp
@@ -0,0 +1,479 @@
+/**
+ * kmacctcachedimap.cpp
+ *
+ * Copyright (c) 2002-2004 Bo Thorsen <bo@sonofthor.dk>
+ * Copyright (c) 2002-2003 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "kmacctcachedimap.h"
+using KMail::SieveConfig;
+
+#include "kmfoldertree.h"
+#include "kmfoldermgr.h"
+#include "kmfiltermgr.h"
+#include "kmfoldercachedimap.h"
+#include "kmmainwin.h"
+#include "accountmanager.h"
+using KMail::AccountManager;
+#include "progressmanager.h"
+
+#include <kio/passdlg.h>
+#include <kio/scheduler.h>
+#include <kio/slave.h>
+#include <kdebug.h>
+#include <kstandarddirs.h>
+#include <kapplication.h>
+#include <kconfig.h>
+
+#include <qstylesheet.h>
+
+KMAcctCachedImap::KMAcctCachedImap( AccountManager* aOwner,
+ const QString& aAccountName, uint id )
+ : KMail::ImapAccountBase( aOwner, aAccountName, id ), mFolder( 0 ),
+ mAnnotationCheckPassed(false),
+ mGroupwareType( GroupwareKolab ),
+ mSentCustomLoginCommand(false)
+{
+ // Never EVER set this for the cached IMAP account
+ mAutoExpunge = false;
+}
+
+
+//-----------------------------------------------------------------------------
+KMAcctCachedImap::~KMAcctCachedImap()
+{
+ killAllJobsInternal( true );
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMAcctCachedImap::type() const
+{
+ return "cachedimap";
+}
+
+void KMAcctCachedImap::init() {
+ ImapAccountBase::init();
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctCachedImap::pseudoAssign( const KMAccount * a ) {
+ killAllJobs( true );
+ if (mFolder)
+ {
+ mFolder->setContentState(KMFolderCachedImap::imapNoInformation);
+ mFolder->setSubfolderState(KMFolderCachedImap::imapNoInformation);
+ }
+ ImapAccountBase::pseudoAssign( a );
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctCachedImap::setImapFolder(KMFolderCachedImap *aFolder)
+{
+ mFolder = aFolder;
+ mFolder->setImapPath( "/" );
+ mFolder->setAccount( this );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMAcctCachedImap::setAutoExpunge( bool /*aAutoExpunge*/ )
+{
+ // Never EVER set this for the cached IMAP account
+ mAutoExpunge = false;
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctCachedImap::killAllJobs( bool disconnectSlave )
+{
+ //kdDebug(5006) << "killAllJobs: disconnectSlave=" << disconnectSlave << " " << mapJobData.count() << " jobs in map." << endl;
+ QValueList<KMFolderCachedImap*> folderList = killAllJobsInternal( disconnectSlave );
+ for( QValueList<KMFolderCachedImap*>::Iterator it = folderList.begin(); it != folderList.end(); ++it ) {
+ KMFolderCachedImap *fld = *it;
+ fld->resetSyncState();
+ fld->setContentState(KMFolderCachedImap::imapNoInformation);
+ fld->setSubfolderState(KMFolderCachedImap::imapNoInformation);
+ fld->sendFolderComplete(false);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Common between killAllJobs and the destructor - which shouldn't call sendFolderComplete
+QValueList<KMFolderCachedImap*> KMAcctCachedImap::killAllJobsInternal( bool disconnectSlave )
+{
+ // Make list of folders to reset. This must be done last, since folderComplete
+ // can trigger the next queued mail check already.
+ QValueList<KMFolderCachedImap*> folderList;
+ QMap<KIO::Job*, jobData>::Iterator it = mapJobData.begin();
+ for (; it != mapJobData.end(); ++it) {
+ if ((*it).parent)
+ folderList << static_cast<KMFolderCachedImap*>((*it).parent->storage());
+ // Kill the job - except if it's the one that already died and is calling us
+ if ( !it.key()->error() && mSlave ) {
+ it.key()->kill();
+ mSlave = 0; // killing a job, kills the slave
+ }
+ }
+ mapJobData.clear();
+
+ // Clear the joblist. Make SURE to stop the job emitting "finished"
+ for( QPtrListIterator<CachedImapJob> it( mJobList ); it.current(); ++it )
+ it.current()->setPassiveDestructor( true );
+ KMAccount::deleteFolderJobs();
+
+ if ( disconnectSlave && mSlave ) {
+ KIO::Scheduler::disconnectSlave( mSlave );
+ mSlave = 0;
+ }
+ return folderList;
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctCachedImap::cancelMailCheck()
+{
+ // Make list of folders to reset, like in killAllJobs
+ QValueList<KMFolderCachedImap*> folderList;
+ QMap<KIO::Job*, jobData>::Iterator it = mapJobData.begin();
+ for (; it != mapJobData.end(); ++it) {
+ if ( (*it).cancellable && (*it).parent )
+ folderList << static_cast<KMFolderCachedImap*>((*it).parent->storage());
+ }
+ // Kill jobs
+ ImapAccountBase::cancelMailCheck();
+ // Reset sync states and emit folderComplete, this is important for
+ // KMAccount::checkingMail() to be reset, in case we restart checking mail later.
+ for( QValueList<KMFolderCachedImap*>::Iterator it = folderList.begin(); it != folderList.end(); ++it ) {
+ KMFolderCachedImap *fld = *it;
+ fld->resetSyncState();
+ fld->setContentState(KMFolderCachedImap::imapNoInformation);
+ fld->setSubfolderState(KMFolderCachedImap::imapNoInformation);
+ fld->sendFolderComplete(false);
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctCachedImap::killJobsForItem(KMFolderTreeItem * fti)
+{
+ QMap<KIO::Job *, jobData>::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()
+{
+ mMailCheckFolders.clear();
+ mMailCheckFolders.append( mFoldersQueuedForChecking.front() );
+ mFoldersQueuedForChecking.pop_front();
+ if ( mFoldersQueuedForChecking.isEmpty() )
+ disconnect( this, SIGNAL( finishedCheck( bool, CheckStatus ) ),
+ this, SLOT( slotCheckQueuedFolders() ) );
+
+ kmkernel->acctMgr()->singleCheckMail(this, true);
+ mMailCheckFolders.clear();
+}
+
+void KMAcctCachedImap::processNewMail( bool /*interactive*/ )
+{
+ assert( mFolder ); // George says "better to crash then lose mail"
+
+ if ( mMailCheckFolders.isEmpty() )
+ processNewMail( mFolder, true );
+ else {
+ KMFolder* f = mMailCheckFolders.front();
+ mMailCheckFolders.pop_front();
+ processNewMail( static_cast<KMFolderCachedImap *>( f->storage() ), false );
+ }
+}
+
+void KMAcctCachedImap::processNewMail( KMFolderCachedImap* folder,
+ bool recurse )
+{
+ assert( folder ); // George says "better to crash then lose mail"
+
+ // This should never be set for a cached IMAP account
+ mAutoExpunge = false;
+ mCountLastUnread = 0;
+ mUnreadBeforeCheck.clear();
+ // stop sending noops during sync, that will keep the connection open
+ mNoopTimer.stop();
+
+ // reset namespace todo
+ if ( folder == mFolder ) {
+ QStringList nsToList = namespaces()[PersonalNS];
+ QStringList otherNSToCheck = namespaces()[OtherUsersNS];
+ otherNSToCheck += namespaces()[SharedNS];
+ for ( QStringList::Iterator it = otherNSToCheck.begin();
+ it != otherNSToCheck.end(); ++it ) {
+ if ( (*it).isEmpty() ) {
+ // empty namespaces are included in the "normal" listing
+ // as the folders are created under the root folder
+ nsToList += *it;
+ }
+ }
+ folder->setNamespacesToList( nsToList );
+ }
+
+ Q_ASSERT( !mMailCheckProgressItem );
+ mMailCheckProgressItem = KPIM::ProgressManager::createProgressItem(
+ "MailCheck" + QString::number( id() ),
+ QStyleSheet::escape( folder->label() ), // will be changed immediately in serverSync anyway
+ QString::null,
+ true, // can be cancelled
+ useSSL() || useTLS() );
+ connect( mMailCheckProgressItem, SIGNAL( progressItemCanceled( KPIM::ProgressItem* ) ),
+ this, SLOT( slotProgressItemCanceled( KPIM::ProgressItem* ) ) );
+
+ folder->setAccount(this);
+ connect(folder, SIGNAL(folderComplete(KMFolderCachedImap*, bool)),
+ this, SLOT(postProcessNewMail(KMFolderCachedImap*, bool)));
+ folder->serverSync( recurse );
+}
+
+void KMAcctCachedImap::postProcessNewMail( KMFolderCachedImap* folder, bool )
+{
+ mNoopTimer.start( 60000 ); // send a noop every minute to avoid "connection broken" errors
+ disconnect(folder, SIGNAL(folderComplete(KMFolderCachedImap*, bool)),
+ this, SLOT(postProcessNewMail(KMFolderCachedImap*, bool)));
+ mMailCheckProgressItem->setComplete();
+ mMailCheckProgressItem = 0;
+
+ if ( folder == mFolder ) {
+ // We remove everything from the deleted folders list after a full sync.
+ // Even if it fails (no permission), because on the next sync we want the folder to reappear,
+ // instead of the user being stuck with "can't delete" every time.
+ // And we do it for _all_ deleted folders, even those that were deleted on the server in the first place (slotListResult).
+ // Otherwise this might have side effects much later (e.g. when regaining permissions to a folder we could see before)
+
+#if 0 // this opens a race: delete a folder during a sync (after the sync checked that folder), and it'll be forgotten...
+ mDeletedFolders.clear();
+#endif
+ mPreviouslyDeletedFolders.clear();
+ }
+
+ KMail::ImapAccountBase::postProcessNewMail();
+}
+
+void KMAcctCachedImap::addUnreadMsgCount( const KMFolderCachedImap *folder,
+ int countUnread )
+{
+ if ( folder->imapPath() != "/INBOX/" ) {
+ // new mail in INBOX is processed with KMAccount::processNewMsg() and
+ // therefore doesn't need to be counted here
+ const QString folderId = folder->folder()->idString();
+ int newInFolder = countUnread;
+ if ( mUnreadBeforeCheck.find( folderId ) != mUnreadBeforeCheck.end() )
+ newInFolder -= mUnreadBeforeCheck[folderId];
+ if ( newInFolder > 0 )
+ addToNewInFolder( folderId, newInFolder );
+ }
+ mCountUnread += countUnread;
+}
+
+void KMAcctCachedImap::addLastUnreadMsgCount( const KMFolderCachedImap *folder,
+ int countLastUnread )
+{
+ mUnreadBeforeCheck[folder->folder()->idString()] = countLastUnread;
+ mCountLastUnread += countLastUnread;
+}
+
+//
+//
+// read/write config
+//
+//
+
+void KMAcctCachedImap::readConfig( /*const*/ KConfig/*Base*/ & config ) {
+ ImapAccountBase::readConfig( config );
+ // Apparently this method is only ever called once (from KMKernel::init) so this is ok
+ mPreviouslyDeletedFolders = config.readListEntry( "deleted-folders" );
+ mDeletedFolders.clear(); // but just in case...
+ const QStringList oldPaths = config.readListEntry( "renamed-folders-paths" );
+ const QStringList newNames = config.readListEntry( "renamed-folders-names" );
+ QStringList::const_iterator it = oldPaths.begin();
+ QStringList::const_iterator nameit = newNames.begin();
+ for( ; it != oldPaths.end() && nameit != newNames.end(); ++it, ++nameit ) {
+ addRenamedFolder( *it, QString::null, *nameit );
+ }
+ mGroupwareType = (GroupwareType)config.readNumEntry( "groupwareType", GroupwareKolab );
+}
+
+void KMAcctCachedImap::writeConfig( KConfig/*Base*/ & config ) /*const*/ {
+ ImapAccountBase::writeConfig( config );
+ config.writeEntry( "deleted-folders", mDeletedFolders + mPreviouslyDeletedFolders );
+ config.writeEntry( "renamed-folders-paths", mRenamedFolders.keys() );
+ const QValueList<RenamedFolder> values = mRenamedFolders.values();
+ QStringList lstNames;
+ QValueList<RenamedFolder>::const_iterator it = values.begin();
+ for ( ; it != values.end() ; ++it )
+ lstNames.append( (*it).mNewName );
+ config.writeEntry( "renamed-folders-names", lstNames );
+ config.writeEntry( "groupwareType", mGroupwareType );
+}
+
+void KMAcctCachedImap::invalidateIMAPFolders()
+{
+ invalidateIMAPFolders( mFolder );
+}
+
+void KMAcctCachedImap::invalidateIMAPFolders( KMFolderCachedImap* folder )
+{
+ if( !folder || !folder->folder() )
+ return;
+
+ folder->setAccount(this);
+
+ QStringList strList;
+ QValueList<QGuardedPtr<KMFolder> > folderList;
+ kmkernel->dimapFolderMgr()->createFolderList( &strList, &folderList,
+ folder->folder()->child(), QString::null,
+ false );
+ QValueList<QGuardedPtr<KMFolder> >::Iterator it;
+ mCountLastUnread = 0;
+ mUnreadBeforeCheck.clear();
+
+ for( it = folderList.begin(); it != folderList.end(); ++it ) {
+ KMFolder *f = *it;
+ if( f && f->folderType() == KMFolderTypeCachedImap ) {
+ KMFolderCachedImap *cfolder = static_cast<KMFolderCachedImap*>(f->storage());
+ // This invalidates the folder completely
+ cfolder->setUidValidity("INVALID");
+ cfolder->writeUidCache();
+ processNewMailSingleFolder( f );
+ }
+ }
+ folder->setUidValidity("INVALID");
+ folder->writeUidCache();
+
+ processNewMailSingleFolder( folder->folder() );
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctCachedImap::addDeletedFolder( KMFolder* folder )
+{
+ if ( !folder || folder->folderType() != KMFolderTypeCachedImap )
+ return;
+ KMFolderCachedImap* storage = static_cast<KMFolderCachedImap*>(folder->storage());
+ addDeletedFolder( storage->imapPath() );
+ kdDebug(5006) << k_funcinfo << storage->imapPath() << endl;
+
+ // Add all child folders too
+ if( folder->child() ) {
+ KMFolderNode *node = folder->child()->first();
+ while( node ) {
+ if( !node->isDir() ) {
+ addDeletedFolder( static_cast<KMFolder*>( node ) ); // recurse
+ }
+ node = folder->child()->next();
+ }
+ }
+}
+
+void KMAcctCachedImap::addDeletedFolder( const QString& imapPath )
+{
+ mDeletedFolders << imapPath;
+}
+
+QStringList KMAcctCachedImap::deletedFolderPaths( const QString& subFolderPath ) const
+{
+ QStringList lst;
+ for ( QStringList::const_iterator it = mDeletedFolders.begin(); it != mDeletedFolders.end(); ++it ) {
+ if ( (*it).startsWith( subFolderPath ) )
+ // We must reverse the order, so that sub sub sub folders are deleted first
+ lst.prepend( *it );
+ }
+ for ( QStringList::const_iterator it = mPreviouslyDeletedFolders.begin(); it != mPreviouslyDeletedFolders.end(); ++it ) {
+ if ( (*it).startsWith( subFolderPath ) )
+ lst.prepend( *it );
+ }
+ kdDebug(5006) << "KMAcctCachedImap::deletedFolderPaths for " << subFolderPath << " returning: " << lst << endl;
+ Q_ASSERT( !lst.isEmpty() );
+ return lst;
+}
+
+bool KMAcctCachedImap::isDeletedFolder( const QString& subFolderPath ) const
+{
+ return mDeletedFolders.find( subFolderPath ) != mDeletedFolders.end();
+}
+
+bool KMAcctCachedImap::isPreviouslyDeletedFolder( const QString& subFolderPath ) const
+{
+ return mPreviouslyDeletedFolders.find( subFolderPath ) != mPreviouslyDeletedFolders.end();
+}
+
+void KMAcctCachedImap::removeDeletedFolder( const QString& subFolderPath )
+{
+ mDeletedFolders.remove( subFolderPath );
+ mPreviouslyDeletedFolders.remove( subFolderPath );
+}
+
+void KMAcctCachedImap::addRenamedFolder( const QString& subFolderPath, const QString& oldLabel, const QString& newName )
+{
+ mRenamedFolders.insert( subFolderPath, RenamedFolder( oldLabel, newName ) );
+}
+
+void KMAcctCachedImap::removeRenamedFolder( const QString& subFolderPath )
+{
+ mRenamedFolders.remove( subFolderPath );
+}
+
+void KMAcctCachedImap::slotProgressItemCanceled( ProgressItem* )
+{
+ bool abortConnection = !mSlaveConnected;
+ killAllJobs( abortConnection );
+ if ( abortConnection ) {
+ // If we were trying to connect, tell kmfoldercachedimap so that it moves on
+ emit connectionResult( KIO::ERR_USER_CANCELED, QString::null );
+ }
+}
+
+FolderStorage* const KMAcctCachedImap::rootFolder() const
+{
+ return mFolder;
+}
+
+
+QString KMAcctCachedImap::renamedFolder( const QString& imapPath ) const
+{
+ QMap<QString, RenamedFolder>::ConstIterator renit = mRenamedFolders.find( imapPath );
+ if ( renit != mRenamedFolders.end() )
+ return (*renit).mNewName;
+ return QString::null;
+}
+
+#include "kmacctcachedimap.moc"
diff --git a/kmail/kmacctcachedimap.h b/kmail/kmacctcachedimap.h
new file mode 100644
index 00000000..6f10bfe5
--- /dev/null
+++ b/kmail/kmacctcachedimap.h
@@ -0,0 +1,230 @@
+/*
+ * kmacctcachedimap.h
+ *
+ * Copyright (c) 2002-2004 Bo Thorsen <bo@sonofthor.dk>
+ * Copyright (c) 2002-2003 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#ifndef KMAcctCachedImap_h
+#define KMAcctCachedImap_h
+
+#include "imapaccountbase.h"
+
+#include <qguardedptr.h>
+
+class KMFolderCachedImap;
+class KMFolderTreeItem;
+class KMFolder;
+namespace KMail {
+ class FolderJob;
+ class ImapJob;
+ class CachedImapJob;
+}
+using KMail::ImapJob;
+using KMail::CachedImapJob;
+
+namespace KIO {
+ class Job;
+}
+
+class KMAcctCachedImap: public KMail::ImapAccountBase
+{
+ Q_OBJECT
+ friend class ::KMail::ImapJob;
+ friend class ::KMail::CachedImapJob;
+
+public:
+ virtual ~KMAcctCachedImap();
+ virtual void init();
+
+ /** A weak assignment operator */
+ virtual void pseudoAssign( const KMAccount * a );
+
+ /**
+ * Overloaded to make sure it's never set for cached IMAP.
+ */
+ virtual void setAutoExpunge(bool);
+
+ /**
+ * Inherited methods.
+ */
+ virtual QString 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
+ */
+ virtual void killAllJobs( bool disconnectSlave=false );
+
+ /**
+ * Abort running mail checks
+ */
+ virtual void cancelMailCheck();
+
+ /**
+ * Set the top level pseudo folder
+ */
+ virtual void setImapFolder(KMFolderCachedImap *);
+ KMFolderCachedImap* imapFolder() const { return mFolder; }
+
+ virtual void readConfig( /*const*/ KConfig/*Base*/ & config );
+ virtual void writeConfig( KConfig/*Base*/ & config ) /*const*/;
+
+ /**
+ * Invalidate the local cache.
+ */
+ virtual void invalidateIMAPFolders();
+ virtual void invalidateIMAPFolders( KMFolderCachedImap* );
+
+ /**
+ * Remember that a folder got explicitely deleted - including all child folders
+ */
+ void addDeletedFolder( KMFolder* folder );
+
+ /**
+ * Remember that a folder got explicitely deleted - NOT including all child folders
+ * This is used when renaming a folder.
+ */
+ void addDeletedFolder( const QString& imapPath );
+
+ /**
+ * Ask if a folder was explicitely deleted in this session
+ */
+ bool isDeletedFolder( const QString& subFolderPath ) const;
+
+ /**
+ * Ask if a folder was explicitely deleted in a previous session
+ */
+ bool isPreviouslyDeletedFolder( const QString& subFolderPath ) const;
+
+ /**
+ * return the imap path to the deleted folder, as well as the paths for any child folders
+ */
+ QStringList deletedFolderPaths( const QString& subFolderPath ) const;
+
+ /**
+ * Remove folder from the "deleted folders" list
+ */
+ void removeDeletedFolder( const QString& subFolderPath );
+
+ /**
+ * Remember that a folder was renamed
+ */
+ void addRenamedFolder( const QString& subFolderPath,
+ const QString& oldLabel, const QString& newName );
+
+ /**
+ * Remove folder from "renamed folders" list
+ * Warning: @p subFolderPath is the OLD path
+ */
+ void removeRenamedFolder( const QString& subFolderPath );
+
+ struct RenamedFolder {
+ RenamedFolder() {} // for QMap
+ RenamedFolder( const QString& oldLabel, const QString& newName )
+ : mOldLabel( oldLabel ), mNewName( newName ) {}
+ QString mOldLabel;
+ QString mNewName;
+ };
+
+ /**
+ * Returns new name for folder if it was renamed
+ */
+ QString renamedFolder( const QString& imapPath ) const;
+ /**
+ * Returns the list of folders that were renamed
+ */
+ const QMap<QString, RenamedFolder>& renamedFolders() const { return mRenamedFolders; }
+
+ /**
+ * Add a folder's unread count to the new "unread messages count", done during a sync after getting new mail
+ */
+ void addUnreadMsgCount( const KMFolderCachedImap *folder, int countUnread );
+
+ /**
+ * Add a folder's unread count to the last "unread messages count", i.e. the counts before getting new mail
+ */
+ void addLastUnreadMsgCount( const KMFolderCachedImap *folder,
+ int countLastUnread );
+
+ /**
+ * Returns the root folder of this account
+ */
+ virtual FolderStorage* const rootFolder() const;
+
+ /** return if the account passed the annotation test */
+ bool annotationCheckPassed(){ return mAnnotationCheckPassed;};
+ void setAnnotationCheckPassed( bool a ){ mAnnotationCheckPassed = a; };
+
+ /** Describes whether the account is a groupware account. */
+ enum GroupwareType
+ {
+ GroupwareNone, ///< Normal IMAP account
+ GroupwareKolab, ///< A Kolab groupware account
+ GroupwareScalix ///< A Scalix groupware account
+ };
+
+ void setGroupwareType( GroupwareType type ){ mGroupwareType = type; }
+ GroupwareType groupwareType() const { return mGroupwareType; }
+
+ void setSentCustomLoginCommand( bool value ){ mSentCustomLoginCommand = value; }
+ bool sentCustomLoginCommand() const { return mSentCustomLoginCommand; }
+
+protected:
+ friend class ::AccountManager;
+ KMAcctCachedImap(AccountManager* owner, const QString& accountName, uint id);
+
+protected slots:
+ /** new-mail-notification for the current folder (is called via folderComplete) */
+ void postProcessNewMail(KMFolderCachedImap*, bool);
+
+ void slotProgressItemCanceled( KPIM::ProgressItem* );
+
+ virtual void slotCheckQueuedFolders();
+
+private:
+ QValueList<KMFolderCachedImap*> killAllJobsInternal( bool disconnectSlave );
+ void processNewMail( KMFolderCachedImap* folder, bool recurse );
+
+private:
+ QPtrList<CachedImapJob> mJobList;
+ KMFolderCachedImap *mFolder;
+ QStringList mDeletedFolders; // folders deleted in this session
+ QStringList mPreviouslyDeletedFolders; // folders deleted in a previous session
+ QMap<QString, RenamedFolder> mRenamedFolders;
+ bool mAnnotationCheckPassed;
+
+ GroupwareType mGroupwareType;
+ bool mSentCustomLoginCommand;
+};
+
+#endif /*KMAcctCachedImap_h*/
diff --git a/kmail/kmacctfolder.cpp b/kmail/kmacctfolder.cpp
new file mode 100644
index 00000000..3d363e14
--- /dev/null
+++ b/kmail/kmacctfolder.cpp
@@ -0,0 +1,46 @@
+// kmacctfolder.cpp
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "kmacctfolder.h"
+
+//-----------------------------------------------------------------------------
+KMAccount* KMAcctFolder::account()
+{
+ if ( acctList() )
+ return acctList()->first();
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctFolder::addAccount( KMAccount* aAcct )
+{
+ if ( !aAcct ) return;
+ if ( !acctList() )
+ setAcctList( new AccountList() );
+
+ acctList()->append( aAcct );
+ aAcct->setFolder( this );
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctFolder::clearAccountList()
+{
+ if ( acctList() )
+ acctList()->clear();
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctFolder::removeAccount( KMAccount* aAcct )
+{
+ if ( !aAcct || !acctList() ) return;
+
+ acctList()->remove( aAcct );
+ aAcct->setFolder( 0 );
+ if ( acctList()->count() <= 0 ) {
+ delete acctList();
+ setAcctList( 0 );
+ }
+}
diff --git a/kmail/kmacctfolder.h b/kmail/kmacctfolder.h
new file mode 100644
index 00000000..e622ebd3
--- /dev/null
+++ b/kmail/kmacctfolder.h
@@ -0,0 +1,55 @@
+/*
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ *
+ * 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 kmacctfolder_h
+#define kmacctfolder_h
+
+#include "kmfolder.h"
+
+class KMAccount;
+
+/** Simple wrapper class that contains the kmail account handling
+ * stuff that is usually not required outside kmail.
+ *
+ * WARNING: do not add virtual methods in this class. This class is
+ * used as a typecast for KMFolder only !!
+ * @author Stefan Taferner
+ */
+class KMAcctFolder: public KMFolder
+{
+public:
+ /** Returns first account or 0 if no account is associated with this
+ folder */
+ KMAccount* account();
+
+ /** Add given account to the list */
+ void addAccount( KMAccount* );
+
+ /** Remove given account from the list */
+ void removeAccount( KMAccount* );
+
+ /** Clear list of accounts */
+ void clearAccountList();
+
+private:
+ KMAcctFolder( KMFolderDir* parent, const QString& name,
+ KMFolderType aFolderType );
+};
+
+#endif /*kmacctfolder_h*/
diff --git a/kmail/kmacctimap.cpp b/kmail/kmacctimap.cpp
new file mode 100644
index 00000000..9e83d243
--- /dev/null
+++ b/kmail/kmacctimap.cpp
@@ -0,0 +1,621 @@
+/**
+ * kmacctimap.cpp
+ *
+ * Copyright (c) 2000-2002 Michael Haeckel <haeckel@kde.org>
+ *
+ * This file is based on popaccount.cpp by Don Sanders
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "kmacctimap.h"
+using KMail::SieveConfig;
+
+#include "kmmessage.h"
+#include "broadcaststatus.h"
+using KPIM::BroadcastStatus;
+#include "kmfoldertree.h"
+#include "kmfoldermgr.h"
+#include "kmfolderimap.h"
+#include "kmmainwin.h"
+#include "kmmsgdict.h"
+#include "kmfilter.h"
+#include "kmfiltermgr.h"
+#include "folderstorage.h"
+#include "imapjob.h"
+#include "actionscheduler.h"
+using KMail::ActionScheduler;
+using KMail::ImapJob;
+using KMail::ImapAccountBase;
+#include "progressmanager.h"
+using KPIM::ProgressItem;
+using KPIM::ProgressManager;
+#include <kio/scheduler.h>
+#include <kio/slave.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+
+#include <qstylesheet.h>
+
+#include <errno.h>
+
+//-----------------------------------------------------------------------------
+KMAcctImap::KMAcctImap(AccountManager* aOwner, const QString& aAccountName, uint id):
+ KMail::ImapAccountBase(aOwner, aAccountName, id),
+ mCountRemainChecks( 0 ),
+ mErrorTimer( 0, "mErrorTimer" )
+{
+ mFolder = 0;
+ mScheduler = 0;
+ mNoopTimer.start( 60000 ); // // send a noop every minute
+ mOpenFolders.setAutoDelete(true);
+ connect(kmkernel->imapFolderMgr(), SIGNAL(changed()),
+ this, SLOT(slotUpdateFolderList()));
+ connect(&mErrorTimer, SIGNAL(timeout()), SLOT(slotResetConnectionError()));
+
+ QString serNumUri = locateLocal( "data", "kmail/unfiltered." +
+ QString("%1").arg(KAccount::id()) );
+ KConfig config( serNumUri );
+ QStringList serNums = config.readListEntry( "unfiltered" );
+ mFilterSerNumsToSave.setAutoDelete( false );
+
+ for ( QStringList::ConstIterator it = serNums.begin();
+ it != serNums.end(); ++it ) {
+ mFilterSerNums.append( (*it).toUInt() );
+ mFilterSerNumsToSave.insert( *it, (const int *)1 );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+KMAcctImap::~KMAcctImap()
+{
+ killAllJobs( true );
+
+ QString serNumUri = locateLocal( "data", "kmail/unfiltered." +
+ QString("%1").arg(KAccount::id()) );
+ KConfig config( serNumUri );
+ QStringList serNums;
+ QDictIterator<int> it( mFilterSerNumsToSave );
+ for( ; it.current(); ++it )
+ serNums.append( it.currentKey() );
+ config.writeEntry( "unfiltered", serNums );
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMAcctImap::type() const
+{
+ return "imap";
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctImap::pseudoAssign( const KMAccount * a ) {
+ killAllJobs( true );
+ if (mFolder)
+ {
+ mFolder->setContentState(KMFolderImap::imapNoInformation);
+ mFolder->setSubfolderState(KMFolderImap::imapNoInformation);
+ }
+ ImapAccountBase::pseudoAssign( a );
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctImap::setImapFolder(KMFolderImap *aFolder)
+{
+ mFolder = aFolder;
+ mFolder->setImapPath( "/" );
+}
+
+
+//-----------------------------------------------------------------------------
+
+bool KMAcctImap::handleError( int errorCode, const QString &errorMsg, KIO::Job* job, const QString& context, bool abortSync )
+{
+ /* TODO check where to handle this one better. */
+ if ( errorCode == KIO::ERR_DOES_NOT_EXIST ) {
+ // folder is gone, so reload the folderlist
+ if ( mFolder )
+ mFolder->listDirectory();
+ return true;
+ }
+ return ImapAccountBase::handleError( errorCode, errorMsg, job, context, abortSync );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMAcctImap::killAllJobs( bool disconnectSlave )
+{
+ QMap<KIO::Job*, jobData>::Iterator it = mapJobData.begin();
+ for ( ; it != mapJobData.end(); ++it)
+ {
+ QPtrList<KMMessage> msgList = (*it).msgList;
+ QPtrList<KMMessage>::Iterator it2 = msgList.begin();
+ for ( ; it2 != msgList.end(); ++it2 ) {
+ KMMessage *msg = *it2;
+ if ( msg->transferInProgress() ) {
+ kdDebug(5006) << "KMAcctImap::killAllJobs - resetting mail" << endl;
+ msg->setTransferInProgress( false );
+ }
+ }
+ if ((*it).parent)
+ {
+ // clear folder state
+ KMFolderImap *fld = static_cast<KMFolderImap*>((*it).parent->storage());
+ fld->setCheckingValidity(false);
+ fld->quiet(false);
+ fld->setContentState(KMFolderImap::imapNoInformation);
+ fld->setSubfolderState(KMFolderImap::imapNoInformation);
+ fld->sendFolderComplete(FALSE);
+ fld->removeJobs();
+ }
+ if ( (*it).progressItem )
+ {
+ (*it).progressItem->setComplete();
+ }
+ }
+ if (mSlave && mapJobData.begin() != mapJobData.end())
+ {
+ mSlave->kill();
+ mSlave = 0;
+ }
+ // remove the jobs
+ mapJobData.clear();
+ // KMAccount::deleteFolderJobs(); doesn't work here always, it deletes jobs from
+ // its own mJobList instead of our mJobList...
+ KMAccount::deleteFolderJobs();
+ QPtrListIterator<ImapJob> it2( mJobList );
+ while ( it2.current() ) {
+ ImapJob *job = it2.current();
+ ++it2;
+ job->kill();
+ }
+ mJobList.clear();
+ // make sure that no new-mail-check is blocked
+ if (mCountRemainChecks > 0)
+ {
+ checkDone( false, CheckOK ); // returned 0 new messages
+ mCountRemainChecks = 0;
+ }
+ if ( disconnectSlave && slave() ) {
+ KIO::Scheduler::disconnectSlave( slave() );
+ mSlave = 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctImap::ignoreJobsForMessage( KMMessage* msg )
+{
+ if (!msg) return;
+ QPtrListIterator<ImapJob> it( mJobList );
+ while ( it.current() )
+ {
+ ImapJob *job = it.current();
+ ++it;
+ if ( job->msgList().first() == msg )
+ {
+ job->kill();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctImap::ignoreJobsForFolder( KMFolder* folder )
+{
+ QPtrListIterator<ImapJob> it( mJobList );
+ while ( it.current() )
+ {
+ ImapJob *job = it.current();
+ ++it;
+ if ( !job->msgList().isEmpty() && job->msgList().first()->parent() == folder )
+ {
+ job->kill();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctImap::removeSlaveJobsForFolder( KMFolder* folder )
+{
+ // Make sure the folder is not referenced in any kio slave jobs
+ QMap<KIO::Job*, jobData>::Iterator it = mapJobData.begin();
+ while ( it != mapJobData.end() ) {
+ QMap<KIO::Job*, jobData>::Iterator i = it;
+ it++;
+ if ( (*i).parent ) {
+ if ( (*i).parent == folder ) {
+ mapJobData.remove(i);
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctImap::cancelMailCheck()
+{
+ // Make list of folders to reset, like in killAllJobs
+ QValueList<KMFolderImap*> folderList;
+ QMap<KIO::Job*, jobData>::Iterator it = mapJobData.begin();
+ for (; it != mapJobData.end(); ++it) {
+ if ( (*it).cancellable && (*it).parent ) {
+ folderList << static_cast<KMFolderImap*>((*it).parent->storage());
+ }
+ }
+ // Kill jobs
+ // FIXME
+ // ImapAccountBase::cancelMailCheck();
+ killAllJobs( true );
+ // emit folderComplete, this is important for
+ // KMAccount::checkingMail() to be reset, in case we restart checking mail later.
+ for( QValueList<KMFolderImap*>::Iterator it = folderList.begin(); it != folderList.end(); ++it ) {
+ KMFolderImap *fld = *it;
+ fld->sendFolderComplete(FALSE);
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctImap::processNewMail(bool interactive)
+{
+ kdDebug() << "processNewMail " << mCheckingSingleFolder << ",status="<<makeConnection()<<endl;
+ if (!mFolder || !mFolder->folder() || !mFolder->folder()->child() ||
+ makeConnection() == ImapAccountBase::Error)
+ {
+ mCountRemainChecks = 0;
+ mCheckingSingleFolder = false;
+ checkDone( false, CheckError );
+ return;
+ }
+ // if necessary then initialize the list of folders which should be checked
+ if( mMailCheckFolders.isEmpty() )
+ {
+ slotUpdateFolderList();
+ // if no folders should be checked then the check is finished
+ if( mMailCheckFolders.isEmpty() )
+ {
+ checkDone( false, CheckOK );
+ mCheckingSingleFolder = false;
+ return;
+ }
+ }
+ // Ok, we're really checking, get a progress item;
+ Q_ASSERT( !mMailCheckProgressItem );
+ mMailCheckProgressItem =
+ ProgressManager::createProgressItem(
+ "MailCheckAccount" + name(),
+ i18n("Checking account: %1" ).arg( QStyleSheet::escape( name() ) ),
+ QString::null, // status
+ true, // can be canceled
+ useSSL() || useTLS() );
+
+ mMailCheckProgressItem->setTotalItems( mMailCheckFolders.count() );
+ connect ( mMailCheckProgressItem,
+ SIGNAL( progressItemCanceled( KPIM::ProgressItem*) ),
+ this,
+ SLOT( slotMailCheckCanceled() ) );
+
+ QValueList<QGuardedPtr<KMFolder> >::Iterator it;
+ // first get the current count of unread-messages
+ mCountRemainChecks = 0;
+ mCountUnread = 0;
+ mUnreadBeforeCheck.clear();
+ for (it = mMailCheckFolders.begin(); it != mMailCheckFolders.end(); ++it)
+ {
+ KMFolder *folder = *it;
+ if (folder && !folder->noContent())
+ {
+ mUnreadBeforeCheck[folder->idString()] = folder->countUnread();
+ }
+ }
+ bool gotError = false;
+ // then check for new mails
+ for (it = mMailCheckFolders.begin(); it != mMailCheckFolders.end(); ++it)
+ {
+ KMFolder *folder = *it;
+ if (folder && !folder->noContent())
+ {
+ KMFolderImap *imapFolder = static_cast<KMFolderImap*>(folder->storage());
+ if ( imapFolder->getContentState() != KMFolderImap::imapListingInProgress
+ && imapFolder->getContentState() != KMFolderImap::imapDownloadInProgress )
+ {
+ // connect the result-signals for new-mail-notification
+ mCountRemainChecks++;
+
+ if (imapFolder->isSelected()) {
+ connect(imapFolder, SIGNAL(folderComplete(KMFolderImap*, bool)),
+ this, SLOT(postProcessNewMail(KMFolderImap*, bool)));
+ imapFolder->getFolder();
+ } else if ( kmkernel->filterMgr()->atLeastOneIncomingFilterAppliesTo( id() ) &&
+ imapFolder->folder()->isSystemFolder() &&
+ imapFolder->imapPath() == "/INBOX/" ) {
+ imapFolder->open("acctimap"); // will be closed in the folderSelected slot
+ // first get new headers before we select the folder
+ imapFolder->setSelected( true );
+ connect( imapFolder, SIGNAL( folderComplete( KMFolderImap*, bool ) ),
+ this, SLOT( slotFolderSelected( KMFolderImap*, bool) ) );
+ imapFolder->getFolder();
+ }
+ else {
+ connect(imapFolder, SIGNAL(numUnreadMsgsChanged(KMFolder*)),
+ this, SLOT(postProcessNewMail(KMFolder*)));
+ bool ok = imapFolder->processNewMail(interactive);
+ if (!ok)
+ {
+ // there was an error so cancel
+ mCountRemainChecks--;
+ gotError = true;
+ if ( mMailCheckProgressItem ) {
+ mMailCheckProgressItem->incCompletedItems();
+ mMailCheckProgressItem->updateProgress();
+ }
+ }
+ }
+ }
+ }
+ } // end for
+ if ( gotError )
+ slotUpdateFolderList();
+ // for the case the account is down and all folders report errors
+ if ( mCountRemainChecks == 0 )
+ {
+ mCountLastUnread = 0; // => mCountUnread - mCountLastUnread == new count
+ ImapAccountBase::postProcessNewMail();
+ mUnreadBeforeCheck.clear();
+ mCheckingSingleFolder = false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctImap::postProcessNewMail(KMFolderImap* folder, bool)
+{
+ disconnect(folder, SIGNAL(folderComplete(KMFolderImap*, bool)),
+ this, SLOT(postProcessNewMail(KMFolderImap*, bool)));
+ postProcessNewMail(static_cast<KMFolder*>(folder->folder()));
+}
+
+void KMAcctImap::postProcessNewMail( KMFolder * folder )
+{
+ disconnect( folder->storage(), SIGNAL(numUnreadMsgsChanged(KMFolder*)),
+ this, SLOT(postProcessNewMail(KMFolder*)) );
+
+ if ( mMailCheckProgressItem ) {
+ mMailCheckProgressItem->incCompletedItems();
+ mMailCheckProgressItem->updateProgress();
+ mMailCheckProgressItem->setStatus( folder->prettyURL() + i18n(" completed") );
+ }
+ mCountRemainChecks--;
+
+ // count the unread messages
+ const QString folderId = folder->idString();
+ int newInFolder = folder->countUnread();
+ if ( mUnreadBeforeCheck.find( folderId ) != mUnreadBeforeCheck.end() )
+ newInFolder -= mUnreadBeforeCheck[folderId];
+ if ( newInFolder > 0 ) {
+ addToNewInFolder( folderId, newInFolder );
+ mCountUnread += newInFolder;
+ }
+
+ // Filter messages
+ QValueListIterator<Q_UINT32> filterIt = mFilterSerNums.begin();
+ QValueList<Q_UINT32> inTransit;
+
+ if (ActionScheduler::isEnabled() ||
+ kmkernel->filterMgr()->atLeastOneOnlineImapFolderTarget()) {
+ KMFilterMgr::FilterSet set = KMFilterMgr::Inbound;
+ QValueList<KMFilter*> filters = kmkernel->filterMgr()->filters();
+ if (!mScheduler) {
+ mScheduler = new KMail::ActionScheduler( set, filters );
+ mScheduler->setAccountId( id() );
+ connect( mScheduler, SIGNAL(filtered(Q_UINT32)), this, SLOT(slotFiltered(Q_UINT32)) );
+ } else {
+ mScheduler->setFilterList( filters );
+ }
+ }
+
+ while (filterIt != mFilterSerNums.end()) {
+ int idx = -1;
+ KMFolder *folder = 0;
+ KMMessage *msg = 0;
+ KMMsgDict::instance()->getLocation( *filterIt, &folder, &idx );
+ // It's possible that the message has been deleted or moved into a
+ // different folder, or that the serNum is stale
+ if ( !folder ) {
+ mFilterSerNumsToSave.remove( QString( "%1" ).arg( *filterIt ) );
+ ++filterIt;
+ continue;
+ }
+
+ KMFolderImap *imapFolder = dynamic_cast<KMFolderImap*>(folder->storage());
+ if (!imapFolder ||
+ !imapFolder->folder()->isSystemFolder() ||
+ !(imapFolder->imapPath() == "/INBOX/") ) { // sanity checking
+ mFilterSerNumsToSave.remove( QString( "%1" ).arg( *filterIt ) );
+ ++filterIt;
+ continue;
+ }
+
+ if (idx != -1) {
+
+ msg = folder->getMsg( idx );
+ if (!msg) { // sanity checking
+ mFilterSerNumsToSave.remove( QString( "%1" ).arg( *filterIt ) );
+ ++filterIt;
+ continue;
+ }
+
+ if (ActionScheduler::isEnabled() ||
+ kmkernel->filterMgr()->atLeastOneOnlineImapFolderTarget()) {
+ mScheduler->execFilters( msg );
+ } else {
+ if (msg->transferInProgress()) {
+ inTransit.append( *filterIt );
+ ++filterIt;
+ continue;
+ }
+ msg->setTransferInProgress(true);
+ if ( !msg->isComplete() ) {
+ FolderJob *job = folder->createJob(msg);
+ connect(job, SIGNAL(messageRetrieved(KMMessage*)),
+ SLOT(slotFilterMsg(KMMessage*)));
+ job->start();
+ } else {
+ mFilterSerNumsToSave.remove( QString( "%1" ).arg( *filterIt ) );
+ if (slotFilterMsg(msg) == 2) break;
+ }
+ }
+ }
+ ++filterIt;
+ }
+ mFilterSerNums = inTransit;
+
+ if (mCountRemainChecks == 0)
+ {
+ // all checks are done
+ mCountLastUnread = 0; // => mCountUnread - mCountLastUnread == new count
+ // when we check only one folder (=selected) and we have new mails
+ // then do not display a summary as the normal status message is better
+ bool showStatus = ( mCheckingSingleFolder && mCountUnread > 0 ) ? false : true;
+ ImapAccountBase::postProcessNewMail( showStatus );
+ mUnreadBeforeCheck.clear();
+ mCheckingSingleFolder = false;
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctImap::slotFiltered(Q_UINT32 serNum)
+{
+ mFilterSerNumsToSave.remove( QString( "%1" ).arg( serNum ) );
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctImap::slotUpdateFolderList()
+{
+ if ( !mFolder || !mFolder->folder() || !mFolder->folder()->child() )
+ {
+ kdWarning(5006) << "KMAcctImap::slotUpdateFolderList return" << endl;
+ return;
+ }
+ QStringList strList;
+ mMailCheckFolders.clear();
+ kmkernel->imapFolderMgr()->createFolderList(&strList, &mMailCheckFolders,
+ mFolder->folder()->child(), QString::null, false);
+ // the new list
+ QValueList<QGuardedPtr<KMFolder> > includedFolders;
+ // check for excluded folders
+ QValueList<QGuardedPtr<KMFolder> >::Iterator it;
+ for (it = mMailCheckFolders.begin(); it != mMailCheckFolders.end(); ++it)
+ {
+ KMFolderImap* folder = static_cast<KMFolderImap*>(((KMFolder*)(*it))->storage());
+ if (folder->includeInMailCheck())
+ includedFolders.append(*it);
+ }
+ mMailCheckFolders = includedFolders;
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctImap::listDirectory()
+{
+ mFolder->listDirectory();
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctImap::readConfig(KConfig& config)
+{
+ ImapAccountBase::readConfig( config );
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctImap::slotMailCheckCanceled()
+{
+ if( mMailCheckProgressItem )
+ mMailCheckProgressItem->setComplete();
+ cancelMailCheck();
+}
+
+//-----------------------------------------------------------------------------
+FolderStorage* const KMAcctImap::rootFolder() const
+{
+ return mFolder;
+}
+
+ImapAccountBase::ConnectionState KMAcctImap::makeConnection()
+{
+ if ( mSlaveConnectionError )
+ {
+ mErrorTimer.start(100, true); // Clear error flag
+ return Error;
+ }
+ return ImapAccountBase::makeConnection();
+}
+
+void KMAcctImap::slotResetConnectionError()
+{
+ mSlaveConnectionError = false;
+ kdDebug(5006) << k_funcinfo << endl;
+}
+
+void KMAcctImap::slotFolderSelected( KMFolderImap* folder, bool )
+{
+ folder->setSelected( false );
+ disconnect( folder, SIGNAL( folderComplete( KMFolderImap*, bool ) ),
+ this, SLOT( slotFolderSelected( KMFolderImap*, bool) ) );
+ postProcessNewMail( static_cast<KMFolder*>(folder->folder()) );
+ folder->close( "acctimap" );
+}
+
+void KMAcctImap::execFilters(Q_UINT32 serNum)
+{
+ if ( !kmkernel->filterMgr()->atLeastOneFilterAppliesTo( id() ) ) return;
+ QValueListIterator<Q_UINT32> findIt = mFilterSerNums.find( serNum );
+ if ( findIt != mFilterSerNums.end() )
+ return;
+ mFilterSerNums.append( serNum );
+ mFilterSerNumsToSave.insert( QString( "%1" ).arg( serNum ), (const int *)1 );
+}
+
+int KMAcctImap::slotFilterMsg( KMMessage *msg )
+{
+ if ( !msg ) {
+ // messageRetrieved(0) is always possible
+ return -1;
+ }
+ msg->setTransferInProgress(false);
+ Q_UINT32 serNum = msg->getMsgSerNum();
+ if ( serNum )
+ mFilterSerNumsToSave.remove( QString( "%1" ).arg( serNum ) );
+
+ int filterResult = kmkernel->filterMgr()->process(msg,
+ KMFilterMgr::Inbound,
+ true,
+ id() );
+ if (filterResult == 2) {
+ // something went horribly wrong (out of space?)
+ kmkernel->emergencyExit( i18n("Unable to process messages: " ) + QString::fromLocal8Bit(strerror(errno)));
+ return 2;
+ }
+ if (msg->parent()) { // unGet this msg
+ int idx = -1;
+ KMFolder * p = 0;
+ KMMsgDict::instance()->getLocation( msg, &p, &idx );
+ assert( p == msg->parent() ); assert( idx >= 0 );
+ p->unGetMsg( idx );
+ }
+
+ return filterResult;
+}
+
+#include "kmacctimap.moc"
diff --git a/kmail/kmacctimap.h b/kmail/kmacctimap.h
new file mode 100644
index 00000000..03aa0aa9
--- /dev/null
+++ b/kmail/kmacctimap.h
@@ -0,0 +1,152 @@
+/* -*- mode: C++ -*-
+ * kmacctimap.h
+ *
+ * Copyright (c) 2000-2002 Michael Haeckel <haeckel@kde.org>
+ *
+ * This file is based on popaccount.h by Don Sanders
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef KMAcctImap_h
+#define KMAcctImap_h
+
+#include "imapaccountbase.h"
+#include <qdict.h>
+
+class KMFolderImap;
+class KMFolderTreeItem;
+namespace KMail {
+ class ImapJob;
+ class ActionScheduler;
+}
+namespace KIO {
+ class Job;
+}
+class FolderStorage;
+
+//-----------------------------------------------------------------------------
+class KMAcctImap: public KMail::ImapAccountBase
+{
+ Q_OBJECT
+ friend class KMail::ImapJob;
+
+public:
+ virtual ~KMAcctImap();
+
+ /** A weak assignment operator */
+ virtual void pseudoAssign( const KMAccount * a );
+
+ /**
+ * Inherited methods.
+ */
+ virtual QString type(void) const;
+ virtual void processNewMail(bool);
+ ConnectionState makeConnection();
+
+ /**
+ * Kill all jobs related the the specified folder/msg
+ */
+ virtual void ignoreJobsForMessage( KMMessage * msg );
+ virtual void ignoreJobsForFolder( KMFolder * folder );
+ virtual void removeSlaveJobsForFolder( KMFolder * folder );
+
+ /**
+ * Kill the slave if any jobs are active
+ */
+ virtual void killAllJobs( bool disconnectSlave=false );
+
+ /**
+ * Set the top level pseudo folder
+ */
+ virtual void setImapFolder(KMFolderImap *);
+
+ /**
+ * Starts the folderlisting for the root folder
+ */
+ virtual void listDirectory();
+
+ /**
+ * Read config file entries. This method is called by the account
+ * manager when a new account is created. The config group is
+ * already properly set by the caller.
+ */
+ virtual void readConfig(KConfig& config);
+
+ /**
+ * Returns the root folder of this account
+ */
+ virtual FolderStorage* const rootFolder() const;
+
+ /**
+ * Queues a message for automatic filtering
+ */
+ void execFilters(Q_UINT32 serNum);
+
+public slots:
+ /**
+ * updates the new-mail-check folderlist
+ */
+ void slotFiltered(Q_UINT32 serNum);
+ void slotUpdateFolderList();
+
+protected:
+ friend class ::AccountManager;
+ KMAcctImap(AccountManager* owner, const QString& accountName, uint id);
+ /**
+ * Handle an error coming from a KIO job
+ * See ImapAccountBase::handleJobError for details.
+ */
+ virtual bool handleError( int error, const QString &errorMsg, KIO::Job* job, const QString& context, bool abortSync = false );
+ virtual void cancelMailCheck();
+
+ QPtrList<KMail::ImapJob> mJobList;
+ QGuardedPtr<KMFolderImap> mFolder;
+
+protected slots:
+ /** new-mail-notification for the current folder (is called via folderComplete) */
+ void postProcessNewMail(KMFolderImap*, bool);
+ /**
+ * new-mail-notification for not-selected folders (is called via
+ * numUnreadMsgsChanged)
+ */
+ void postProcessNewMail( KMFolder * f );
+
+ /**
+ * hooked up to the progress item signaling cancellation.
+ * Cleanup and reset state.
+ */
+ void slotMailCheckCanceled();
+
+ /**
+ * called to reset the connection error status
+ */
+ void slotResetConnectionError();
+
+ /**
+ * Slots for automatic filtering
+ */
+ void slotFolderSelected( KMFolderImap*, bool );
+ int slotFilterMsg( KMMessage* );
+
+private:
+ int mCountRemainChecks;
+ /** used to reset connection errors */
+ QTimer mErrorTimer;
+ QValueList<Q_UINT32> mFilterSerNums;
+ QDict<int> mFilterSerNumsToSave;
+ KMail::ActionScheduler *mScheduler;
+};
+
+#endif /*KMAcctImap_h*/
diff --git a/kmail/kmacctlocal.cpp b/kmail/kmacctlocal.cpp
new file mode 100644
index 00000000..dfc8a187
--- /dev/null
+++ b/kmail/kmacctlocal.cpp
@@ -0,0 +1,322 @@
+// kmacctlocal.cpp
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "kmacctlocal.h"
+#include "kmfoldermbox.h"
+#include "kmacctfolder.h"
+#include "broadcaststatus.h"
+using KPIM::BroadcastStatus;
+#include "progressmanager.h"
+using KPIM::ProgressManager;
+
+#include "kmfoldermgr.h"
+
+#include <kapplication.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+#include <kconfig.h>
+
+#include <qfileinfo.h>
+#include <qstylesheet.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <assert.h>
+
+//-----------------------------------------------------------------------------
+KMAcctLocal::KMAcctLocal(AccountManager* aOwner, const QString& aAccountName, uint id):
+ KMAccount(aOwner, aAccountName, id), mHasNewMail( false ),
+ mAddedOk( true ), mNumMsgs( 0 ),
+ mMsgsFetched( 0 ), mMailFolder( 0 )
+{
+ mLock = procmail_lockfile;
+}
+
+
+//-----------------------------------------------------------------------------
+KMAcctLocal::~KMAcctLocal()
+{
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMAcctLocal::type(void) const
+{
+ return "local";
+}
+
+
+//-----------------------------------------------------------------------------
+void KMAcctLocal::init() {
+ KMAccount::init();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMAcctLocal::pseudoAssign( const KMAccount * a )
+{
+ KMAccount::pseudoAssign( a );
+
+ const KMAcctLocal * l = dynamic_cast<const KMAcctLocal*>( a );
+ if ( !l ) return;
+
+ setLocation( l->location() );
+ setLockType( l->lockType() );
+ setProcmailLockFileName( l->procmailLockFileName() );
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctLocal::processNewMail(bool)
+{
+ mHasNewMail = false;
+
+ if ( !preProcess() ) {
+ return;
+ }
+
+ QTime t;
+ t.start();
+
+ for ( mMsgsFetched = 0; mMsgsFetched < mNumMsgs; ++mMsgsFetched )
+ {
+ if ( !fetchMsg() )
+ break;
+
+ if (t.elapsed() >= 200) { //hardwired constant
+ kapp->processEvents();
+ t.start();
+ }
+ }
+
+ postProcess();
+}
+
+
+//-----------------------------------------------------------------------------
+bool KMAcctLocal::preProcess()
+{
+ if ( precommand().isEmpty() ) {
+ QFileInfo fi( location() );
+ if ( fi.size() == 0 ) {
+ BroadcastStatus::instance()->setStatusMsgTransmissionCompleted( mName, 0 );
+ checkDone( mHasNewMail, CheckOK );
+ return false;
+ }
+ }
+
+ mMailFolder = new KMFolder( 0, location(), KMFolderTypeMbox,
+ false /* no index */, false /* don't export sernums */ );
+ KMFolderMbox* mboxStorage =
+ static_cast<KMFolderMbox*>(mMailFolder->storage());
+ mboxStorage->setLockType( mLock );
+ if ( mLock == procmail_lockfile)
+ mboxStorage->setProcmailLockFileName( mProcmailLockFileName );
+
+ if (!mFolder) {
+ checkDone( mHasNewMail, CheckError );
+ BroadcastStatus::instance()->setStatusMsg( i18n( "Transmission failed." ));
+ return false;
+ }
+
+ //BroadcastStatus::instance()->reset();
+ BroadcastStatus::instance()->setStatusMsg(
+ i18n("Preparing transmission from \"%1\"...").arg(mName));
+
+
+ Q_ASSERT( !mMailCheckProgressItem );
+ QString escapedName = QStyleSheet::escape( mName );
+ mMailCheckProgressItem = KPIM::ProgressManager::createProgressItem(
+ "MailCheck" + mName,
+ escapedName,
+ i18n("Preparing transmission from \"%1\"...").arg( escapedName ),
+ false, // cannot be canceled
+ false ); // no tls/ssl
+
+ // run the precommand
+ if (!runPrecommand(precommand()))
+ {
+ kdDebug(5006) << "cannot run precommand " << precommand() << endl;
+ checkDone( mHasNewMail, CheckError );
+ BroadcastStatus::instance()->setStatusMsg( i18n( "Running precommand failed." ));
+ return false;
+ }
+
+ const int rc = mMailFolder->open("acctlocalMail");
+ if ( rc != 0 ) {
+ QString aStr;
+ aStr = i18n("Cannot open file:");
+ aStr += mMailFolder->path()+"/"+mMailFolder->name();
+ KMessageBox::sorry(0, aStr);
+ kdDebug(5006) << "cannot open file " << mMailFolder->path() << "/"
+ << mMailFolder->name() << endl;
+ checkDone( mHasNewMail, CheckError );
+ BroadcastStatus::instance()->setStatusMsg( i18n( "Transmission failed." ));
+ return false;
+ }
+
+ if (!mboxStorage->isLocked()) {
+ kdDebug(5006) << "mailFolder could not be locked" << endl;
+ mMailFolder->close("acctlocalMail");
+ checkDone( mHasNewMail, CheckError );
+ QString errMsg = i18n( "Transmission failed: Could not lock %1." )
+ .arg( mMailFolder->location() );
+ BroadcastStatus::instance()->setStatusMsg( errMsg );
+ return false;
+ }
+
+ mFolder->open("acctlocalFold");
+
+ mNumMsgs = mMailFolder->count();
+
+ mMailCheckProgressItem->setTotalItems( mNumMsgs );
+
+ // prepare the static parts of the status message:
+ mStatusMsgStub = i18n("Moving message %3 of %2 from %1.")
+ .arg(mMailFolder->location()).arg( mNumMsgs );
+
+ //BroadcastStatus::instance()->setStatusProgressEnable( "L" + mName, true );
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+bool KMAcctLocal::fetchMsg()
+{
+ KMMessage* msg;
+
+ /* This causes mail eating
+ if (kmkernel->mailCheckAborted()) break; */
+
+ const QString statusMsg = mStatusMsgStub.arg( mMsgsFetched );
+ //BroadcastStatus::instance()->setStatusMsg( statusMsg );
+ mMailCheckProgressItem->incCompletedItems();
+ mMailCheckProgressItem->updateProgress();
+ mMailCheckProgressItem->setStatus( statusMsg );
+
+ msg = mMailFolder->take(0);
+ if (msg)
+ {
+#if 0
+ // debug code, don't remove
+ QFile fileD0( "testdat_xx-0-0" );
+ if( fileD0.open( IO_WriteOnly ) ) {
+ QCString s = msg->asString();
+ uint l = s.length();
+ if ( l > 0 ) {
+ QDataStream ds( &fileD0 );
+ ds.writeRawBytes( s.data(), l );
+ }
+ fileD0.close(); // If data is 0 we just create a zero length file.
+ }
+#endif
+ msg->setStatus(msg->headerField("Status").latin1(),
+ msg->headerField("X-Status").latin1());
+ msg->setEncryptionStateChar( msg->headerField( "X-KMail-EncryptionState" ).at(0) );
+ msg->setSignatureStateChar( msg->headerField( "X-KMail-SignatureState" ).at(0));
+ msg->setComplete(true);
+ msg->updateAttachmentState();
+
+ mAddedOk = processNewMsg(msg);
+
+ if (mAddedOk)
+ mHasNewMail = true;
+
+ return mAddedOk;
+ }
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMAcctLocal::postProcess()
+{
+ if (mAddedOk)
+ {
+ kmkernel->folderMgr()->syncAllFolders();
+ const int rc = mMailFolder->expunge();
+ if ( rc != 0 ) {
+ KMessageBox::queuedMessageBox( 0, KMessageBox::Information,
+ i18n( "<qt>Cannot remove mail from "
+ "mailbox <b>%1</b>:<br>%2</qt>" )
+ .arg( mMailFolder->location() )
+ .arg( strerror( rc ) ) );
+ }
+
+ if( mMailCheckProgressItem ) { // do this only once...
+ BroadcastStatus::instance()->setStatusMsgTransmissionCompleted( mName, mNumMsgs );
+ mMailCheckProgressItem->setStatus(
+ i18n( "Fetched 1 message from mailbox %1.",
+ "Fetched %n messages from mailbox %1.",
+ mNumMsgs ).arg( mMailFolder->location() ) );
+ mMailCheckProgressItem->setComplete();
+ mMailCheckProgressItem = 0;
+ }
+ }
+ // else warning is written already
+
+ mMailFolder->close("acctlocalMail");
+ delete mMailFolder; mMailFolder = 0;
+
+ mFolder->close("acctlocalFold");
+
+ checkDone( mHasNewMail, CheckOK );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMAcctLocal::readConfig(KConfig& config)
+{
+ KMAccount::readConfig(config);
+ mLocation = config.readPathEntry("Location", mLocation);
+ QString locktype = config.readEntry("LockType", "procmail_lockfile" );
+
+ if( locktype == "procmail_lockfile" ) {
+ mLock = procmail_lockfile;
+ mProcmailLockFileName = config.readEntry("ProcmailLockFile",
+ mLocation + ".lock");
+ } else if( locktype == "mutt_dotlock" )
+ mLock = mutt_dotlock;
+ else if( locktype == "mutt_dotlock_privileged" )
+ mLock = mutt_dotlock_privileged;
+ else if( locktype == "none" )
+ mLock = lock_none;
+ else mLock = FCNTL;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMAcctLocal::writeConfig(KConfig& config)
+{
+ KMAccount::writeConfig(config);
+
+ config.writePathEntry("Location", mLocation);
+
+ QString st = "fcntl";
+ if (mLock == procmail_lockfile) st = "procmail_lockfile";
+ else if (mLock == mutt_dotlock) st = "mutt_dotlock";
+ else if (mLock == mutt_dotlock_privileged) st = "mutt_dotlock_privileged";
+ else if (mLock == lock_none) st = "none";
+ config.writeEntry("LockType", st);
+
+ if (mLock == procmail_lockfile) {
+ config.writeEntry("ProcmailLockFile", mProcmailLockFileName);
+ }
+
+}
+
+
+//-----------------------------------------------------------------------------
+void KMAcctLocal::setLocation(const QString& aLocation)
+{
+ mLocation = aLocation;
+}
+
+void KMAcctLocal::setProcmailLockFileName(const QString& s)
+{
+ mProcmailLockFileName = s;
+}
diff --git a/kmail/kmacctlocal.h b/kmail/kmacctlocal.h
new file mode 100644
index 00000000..12c14c10
--- /dev/null
+++ b/kmail/kmacctlocal.h
@@ -0,0 +1,57 @@
+/* KMail account for local mail folders
+ *
+ */
+#ifndef kmacctlocal_h
+#define kmacctlocal_h
+
+#include "kmaccount.h"
+#include "kmglobal.h"
+
+class KMAcctLocal: public KMAccount
+{
+protected:
+ friend class ::AccountManager;
+
+ KMAcctLocal(AccountManager* owner, const QString& accountName, uint id);
+
+public:
+ virtual ~KMAcctLocal();
+ virtual void init(void);
+
+ virtual void pseudoAssign( const KMAccount * a );
+
+ /** Access to location of local mail file (usually something like
+ "/var/spool/mail/joe"). */
+ QString location(void) const { return mLocation; }
+ virtual void setLocation(const QString&);
+
+ /** Acceso to Locking method */
+ LockType lockType(void) const { return mLock; }
+ void setLockType(LockType lt) { mLock = lt; }
+
+ QString procmailLockFileName(void) const { return mProcmailLockFileName; }
+ void setProcmailLockFileName(const QString& s);
+
+ virtual QString type(void) const;
+ virtual void processNewMail(bool);
+ virtual void readConfig(KConfig&);
+ virtual void writeConfig(KConfig&);
+
+private:
+ bool preProcess();
+ bool fetchMsg();
+ void postProcess();
+
+private:
+ QString mLocation;
+ QString mProcmailLockFileName;
+ bool mHasNewMail;
+ bool mAddedOk;
+ LockType mLock;
+ int mNumMsgs;
+ int mMsgsFetched;
+ KMFolder *mMailFolder;
+ QString mStatusMsgStub;
+};
+
+#endif /*kmacctlocal_h*/
diff --git a/kmail/kmacctmaildir.cpp b/kmail/kmacctmaildir.cpp
new file mode 100644
index 00000000..0b62c6cb
--- /dev/null
+++ b/kmail/kmacctmaildir.cpp
@@ -0,0 +1,234 @@
+// kmacctmaildir.cpp
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <qfileinfo.h>
+#include "kmacctmaildir.h"
+#include "kmfoldermaildir.h"
+#include "kmacctfolder.h"
+#include "broadcaststatus.h"
+using KPIM::BroadcastStatus;
+#include "progressmanager.h"
+using KPIM::ProgressManager;
+
+#include <kapplication.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+#include <kconfig.h>
+
+#include <qstylesheet.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <assert.h>
+
+#ifdef HAVE_PATHS_H
+#include <paths.h> /* defines _PATH_MAILDIR */
+#endif
+
+#undef None
+
+//-----------------------------------------------------------------------------
+KMAcctMaildir::KMAcctMaildir(AccountManager* aOwner, const QString& aAccountName, uint id):
+ KMAccount(aOwner, aAccountName, id)
+{
+}
+
+
+//-----------------------------------------------------------------------------
+KMAcctMaildir::~KMAcctMaildir()
+{
+ mLocation = "";
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMAcctMaildir::type(void) const
+{
+ return "maildir";
+}
+
+
+//-----------------------------------------------------------------------------
+void KMAcctMaildir::init() {
+ KMAccount::init();
+
+ mLocation = getenv("MAIL");
+ if (mLocation.isNull()) {
+ mLocation = getenv("HOME");
+ mLocation += "/Maildir/";
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMAcctMaildir::pseudoAssign( const KMAccount * a )
+{
+ KMAccount::pseudoAssign( a );
+
+ const KMAcctMaildir * m = dynamic_cast<const KMAcctMaildir*>( a );
+ if ( !m ) return;
+
+ setLocation( m->location() );
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctMaildir::processNewMail(bool)
+{
+ QTime t;
+ hasNewMail = false;
+
+ if ( precommand().isEmpty() ) {
+ QFileInfo fi( location() );
+ if ( !fi.exists() ) {
+ checkDone( hasNewMail, CheckOK );
+ BroadcastStatus::instance()->setStatusMsgTransmissionCompleted( mName, 0 );
+ return;
+ }
+ }
+
+ KMFolder mailFolder(0, location(), KMFolderTypeMaildir,
+ false /* no index */, false /* don't export sernums */);
+
+ long num = 0;
+ long i;
+ int rc;
+ KMMessage* msg;
+ bool addedOk;
+
+ if (!mFolder) {
+ checkDone( hasNewMail, CheckError );
+ BroadcastStatus::instance()->setStatusMsg( i18n( "Transmission failed." ));
+ return;
+ }
+
+ BroadcastStatus::instance()->setStatusMsg(
+ i18n("Preparing transmission from \"%1\"...").arg(mName));
+
+ Q_ASSERT( !mMailCheckProgressItem );
+ QString escapedName = QStyleSheet::escape( mName );
+ mMailCheckProgressItem = KPIM::ProgressManager::createProgressItem(
+ "MailCheck" + mName,
+ escapedName,
+ i18n("Preparing transmission from \"%1\"...").arg( escapedName ),
+ false, // cannot be canceled
+ false ); // no tls/ssl
+
+ // run the precommand
+ if (!runPrecommand(precommand()))
+ {
+ kdDebug(5006) << "cannot run precommand " << precommand() << endl;
+ checkDone( hasNewMail, CheckError );
+ BroadcastStatus::instance()->setStatusMsg( i18n( "Transmission failed." ));
+ return;
+ }
+
+ rc = mailFolder.open("acctmaildirMail");
+ if (rc)
+ {
+ QString aStr = i18n("<qt>Cannot open folder <b>%1</b>.</qt>").arg( mailFolder.location() );
+ KMessageBox::sorry(0, aStr);
+ kdDebug(5006) << "cannot open folder " << mailFolder.location() << endl;
+ checkDone( hasNewMail, CheckError );
+ BroadcastStatus::instance()->setStatusMsg( i18n( "Transmission failed." ));
+ return;
+ }
+
+ mFolder->open("acctmaildirFold");
+
+
+ num = mailFolder.count();
+
+ addedOk = true;
+ t.start();
+
+ // prepare the static parts of the status message:
+ QString statusMsgStub = i18n("Moving message %3 of %2 from %1.")
+ .arg(mailFolder.location()).arg(num);
+
+ mMailCheckProgressItem->setTotalItems( num );
+
+ for (i=0; i<num; i++)
+ {
+
+ if( kmkernel->mailCheckAborted() ) {
+ BroadcastStatus::instance()->setStatusMsg( i18n("Transmission aborted.") );
+ num = i;
+ addedOk = false;
+ }
+ if (!addedOk) break;
+
+ QString statusMsg = statusMsgStub.arg(i);
+ mMailCheckProgressItem->incCompletedItems();
+ mMailCheckProgressItem->updateProgress();
+ mMailCheckProgressItem->setStatus( statusMsg );
+
+ msg = mailFolder.take(0);
+ if (msg)
+ {
+ msg->setStatus(msg->headerField("Status").latin1(),
+ msg->headerField("X-Status").latin1());
+ msg->setEncryptionStateChar( msg->headerField( "X-KMail-EncryptionState" ).at(0));
+ msg->setSignatureStateChar( msg->headerField( "X-KMail-SignatureState" ).at(0));
+
+ addedOk = processNewMsg(msg);
+ if (addedOk)
+ hasNewMail = true;
+ }
+
+ if (t.elapsed() >= 200) { //hardwired constant
+ kapp->processEvents();
+ t.start();
+ }
+
+ }
+
+ if( mMailCheckProgressItem ) { // do this only once...
+ BroadcastStatus::instance()->setStatusMsgTransmissionCompleted( num );
+ mMailCheckProgressItem->setStatus(
+ i18n( "Fetched 1 message from maildir folder %1.",
+ "Fetched %n messages from maildir folder %1.",
+ num ).arg(mailFolder.location() ) );
+
+ mMailCheckProgressItem->setComplete();
+ mMailCheckProgressItem = 0;
+ }
+ if (addedOk)
+ {
+ BroadcastStatus::instance()->setStatusMsgTransmissionCompleted( mName, num );
+ }
+ // else warning is written already
+
+ mailFolder.close("acctmaildirMail");
+ mFolder->close("acctmaildirFold");
+
+ checkDone( hasNewMail, CheckOK );
+
+ return;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMAcctMaildir::readConfig(KConfig& config)
+{
+ KMAccount::readConfig(config);
+ mLocation = config.readPathEntry("Location", mLocation);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMAcctMaildir::writeConfig(KConfig& config)
+{
+ KMAccount::writeConfig(config);
+ config.writePathEntry("Location", mLocation);
+}
+
+//-----------------------------------------------------------------------------
+void KMAcctMaildir::setLocation(const QString& aLocation)
+{
+ mLocation = aLocation;
+}
diff --git a/kmail/kmacctmaildir.h b/kmail/kmacctmaildir.h
new file mode 100644
index 00000000..05b90833
--- /dev/null
+++ b/kmail/kmacctmaildir.h
@@ -0,0 +1,37 @@
+/* KMail account for maildir mail folders
+ *
+ */
+#ifndef kmacctmaildir_h
+#define kmacctmaildir_h
+
+#include "kmaccount.h"
+#include "kmglobal.h"
+
+class KMAcctMaildir: public KMAccount
+{
+protected:
+ friend class ::AccountManager;
+
+ KMAcctMaildir(AccountManager* owner, const QString& accountName, uint id);
+
+public:
+ virtual ~KMAcctMaildir();
+ virtual void init(void);
+ virtual void pseudoAssign( const KMAccount * a );
+
+ /** Access to location of maildir mail file (usually something like
+ "/home/joe/Maildir"). */
+ const QString& location(void) const { return mLocation; }
+ virtual void setLocation(const QString&);
+
+ virtual QString type(void) const;
+ virtual void processNewMail(bool);
+ virtual void readConfig(KConfig&);
+ virtual void writeConfig(KConfig&);
+
+protected:
+ QString mLocation;
+ bool hasNewMail;
+};
+
+#endif /*kmacctmaildir_h*/
diff --git a/kmail/kmacctseldlg.cpp b/kmail/kmacctseldlg.cpp
new file mode 100644
index 00000000..e381b630
--- /dev/null
+++ b/kmail/kmacctseldlg.cpp
@@ -0,0 +1,77 @@
+/*
+ * kmail: KDE mail client
+ * This file: Copyright (C) 2000 Espen Sand, <espen@kde.org>
+ * Contains code segments and ideas from earlier kmail dialog code
+ * by Stefan Taferner <taferner@kde.org>
+ *
+ * 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.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <qbuttongroup.h>
+#include <qlayout.h>
+#include <qradiobutton.h>
+
+#include <klocale.h>
+
+#include "kmacctseldlg.moc"
+
+KMAcctSelDlg::KMAcctSelDlg( QWidget *parent, const char *name, bool modal )
+ : KDialogBase( parent, name, modal, i18n("Add Account"), Ok|Cancel, Ok )
+{
+ QFrame *page = makeMainWidget();
+ QVBoxLayout *topLayout = new QVBoxLayout( page, 0, spacingHint() );
+
+ QButtonGroup *group = new QButtonGroup( i18n("Account Type"), page );
+ connect(group, SIGNAL(clicked(int)), SLOT(buttonClicked(int)) );
+
+ topLayout->addWidget( group, 10 );
+ QVBoxLayout *vlay = new QVBoxLayout( group, spacingHint()*2, spacingHint() );
+ vlay->addSpacing( fontMetrics().lineSpacing() );
+
+ QRadioButton *radioButton1 = new QRadioButton( i18n("&Local mailbox"), group );
+ vlay->addWidget( radioButton1 );
+ QRadioButton *radioButton2 = new QRadioButton( i18n("&POP3"), group );
+ vlay->addWidget( radioButton2 );
+ QRadioButton *radioButton3 = new QRadioButton( i18n("&IMAP"), group );
+ vlay->addWidget( radioButton3 );
+ QRadioButton *radioButton4 = new QRadioButton( i18n("&Disconnected IMAP"), group );
+ vlay->addWidget( radioButton4 );
+ QRadioButton *radioButton5 = new QRadioButton( i18n("&Maildir mailbox"), group );
+ vlay->addWidget( radioButton5 );
+
+ vlay->addStretch( 10 );
+
+ radioButton2->setChecked(true); // Pop is most common ?
+ buttonClicked(1);
+}
+
+
+void KMAcctSelDlg::buttonClicked( int id )
+{
+ mSelectedButton = id;
+}
+
+
+int KMAcctSelDlg::selected( void ) const
+{
+ return mSelectedButton;
+}
+
+
diff --git a/kmail/kmacctseldlg.h b/kmail/kmacctseldlg.h
new file mode 100644
index 00000000..00f1fec9
--- /dev/null
+++ b/kmail/kmacctseldlg.h
@@ -0,0 +1,51 @@
+/*
+ * kmail: KDE mail client
+ * This file: Copyright (C) 2000 Espen Sand, <espen@kde.org>
+ * Contains code segments and ideas from earlier kmail dialog code
+ * by Stefan Taferner <taferner@kde.org>
+ *
+ * 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 kmacctseldlg_h
+#define kmacctseldlg_h
+
+#include <kdialogbase.h>
+
+/** Select account from given list of account types */
+class KMAcctSelDlg: public KDialogBase
+{
+ Q_OBJECT
+
+ public:
+ KMAcctSelDlg( QWidget *parent=0, const char *name=0, bool modal=true );
+
+ /**
+ * Returns selected button from the account selection group:
+ * 0=local mail, 1=pop3.
+ */
+ int selected(void) const;
+
+ private slots:
+ void buttonClicked(int);
+
+ private:
+ int mSelectedButton;
+};
+
+
+#endif /*kmacctseldlg_h*/
diff --git a/kmail/kmaddrbook.cpp b/kmail/kmaddrbook.cpp
new file mode 100644
index 00000000..288784db
--- /dev/null
+++ b/kmail/kmaddrbook.cpp
@@ -0,0 +1,135 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ *
+ * 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 <config.h>
+#include <unistd.h>
+
+#include "kmaddrbook.h"
+#include "kcursorsaver.h"
+
+#include <kapplication.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kabc/stdaddressbook.h>
+#include <kabc/distributionlist.h>
+#include <kabc/vcardconverter.h>
+#include <dcopref.h>
+
+#include <qregexp.h>
+
+void KabcBridge::addresses(QStringList& result) // includes lists
+{
+ KCursorSaver busy(KBusyPtr::busy()); // loading might take a while
+
+ KABC::AddressBook *addressBook = KABC::StdAddressBook::self( true );
+ KABC::AddressBook::ConstIterator it;
+ for( it = addressBook->begin(); it != addressBook->end(); ++it ) {
+ const QStringList emails = (*it).emails();
+ QString n = (*it).prefix() + " " +
+ (*it).givenName() + " " +
+ (*it).additionalName() + " " +
+ (*it).familyName() + " " +
+ (*it).suffix();
+ n = n.simplifyWhiteSpace();
+
+ QRegExp needQuotes("[^ 0-9A-Za-z\\x0080-\\xFFFF]");
+ QString endQuote = "\" ";
+ QStringList::ConstIterator mit;
+ QString addr, email;
+
+ for ( mit = emails.begin(); mit != emails.end(); ++mit ) {
+ email = *mit;
+ if (!email.isEmpty()) {
+ if (n.isEmpty() || (email.find( '<' ) != -1))
+ addr = QString::null;
+ else { // do we really need quotes around this name ?
+ if (n.find(needQuotes) != -1)
+ addr = '"' + n + endQuote;
+ else
+ addr = n + ' ';
+ }
+
+ if (!addr.isEmpty() && (email.find( '<' ) == -1)
+ && (email.find( '>' ) == -1)
+ && (email.find( ',' ) == -1))
+ addr += '<' + email + '>';
+ else
+ addr += email;
+ addr = addr.stripWhiteSpace();
+ result.append( addr );
+ }
+ }
+ }
+ KABC::DistributionListManager manager( addressBook );
+ manager.load();
+ result += manager.listNames();
+
+ result.sort();
+}
+
+QStringList KabcBridge::addresses()
+{
+ QStringList entries;
+ KABC::AddressBook::ConstIterator it;
+
+ const KABC::AddressBook *addressBook = KABC::StdAddressBook::self( true );
+ for( it = addressBook->begin(); it != addressBook->end(); ++it ) {
+ entries += (*it).fullEmail();
+ }
+ return entries;
+}
+
+//-----------------------------------------------------------------------------
+QString KabcBridge::expandNickName( const QString& nickName )
+{
+ if ( nickName.isEmpty() )
+ return QString::null;
+
+ const QString lowerNickName = nickName.lower();
+ const KABC::AddressBook *addressBook = KABC::StdAddressBook::self( true );
+ for( KABC::AddressBook::ConstIterator it = addressBook->begin();
+ it != addressBook->end(); ++it ) {
+ if ( (*it).nickName().lower() == lowerNickName )
+ return (*it).fullEmail();
+ }
+ return QString::null;
+}
+
+
+//-----------------------------------------------------------------------------
+
+QStringList KabcBridge::categories()
+{
+ KABC::AddressBook *addressBook = KABC::StdAddressBook::self( true );
+ KABC::Addressee::List addresses = addressBook->allAddressees();
+ QStringList allcategories, aux;
+
+ for ( KABC::Addressee::List::Iterator it = addresses.begin();
+ it != addresses.end(); ++it ) {
+ aux = ( *it ).categories();
+ for ( QStringList::ConstIterator itAux = aux.begin();
+ itAux != aux.end(); ++itAux ) {
+ // don't have duplicates in allcategories
+ if ( allcategories.find( *itAux ) == allcategories.end() )
+ allcategories += *itAux;
+ }
+ }
+ return allcategories;
+}
diff --git a/kmail/kmaddrbook.h b/kmail/kmaddrbook.h
new file mode 100644
index 00000000..bc4205a2
--- /dev/null
+++ b/kmail/kmaddrbook.h
@@ -0,0 +1,43 @@
+/*
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ *
+ * 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 KMAddrBook_h
+#define KMAddrBook_h
+
+#include <qstringlist.h>
+
+#include <kdeversion.h>
+#include <kabc/addressee.h>
+
+class QWidget;
+
+class KabcBridge {
+public:
+ static QStringList addresses();
+ static void addresses(QStringList& result);
+ static QString expandNickName( const QString& nickName );
+ /**
+ Returns all categories found in the addressbook.
+ @return A list of the categories
+ */
+ static QStringList categories();
+};
+
+
+#endif /*KMAddrBook_h*/
diff --git a/kmail/kmail-3.1-update-new-mail-notification-settings.pl b/kmail/kmail-3.1-update-new-mail-notification-settings.pl
new file mode 100755
index 00000000..61ca4246
--- /dev/null
+++ b/kmail/kmail-3.1-update-new-mail-notification-settings.pl
@@ -0,0 +1,38 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+my $presentation = -1;
+my $commandline = "";
+while ( <> ) {
+ chomp;
+ if ( /^msgbox-on-mail/ ) {
+ my ($key,$value) = split /=/;
+ if( -1 == $presentation ) {
+ $presentation = 0;
+ }
+ $presentation += 2*( $value eq "true" );
+ }
+ elsif ( /^exec-on-mail-cmd/ ) {
+ my ($key,$value) = split /=/;
+ $commandline = $value;
+ }
+ elsif ( /^exec-on-mail/ ) {
+ my ($key,$value) = split /=/;
+ if( -1 == $presentation ) {
+ $presentation = 0;
+ }
+ $presentation += 32*( $value eq "true" );
+ }
+}
+
+if ( "" ne $commandline ) {
+ print "commandline=$commandline\n";
+}
+if ( -1 != $presentation ) {
+ print "presentation=$presentation\n";
+}
+
+print "# DELETE msgbox-on-mail\n";
+print "# DELETE exec-on-mail\n";
+print "# DELETE exec-on-mail-cmd\n";
diff --git a/kmail/kmail-3.1-use-UOID-for-identities.pl b/kmail/kmail-3.1-use-UOID-for-identities.pl
new file mode 100755
index 00000000..e7641482
--- /dev/null
+++ b/kmail/kmail-3.1-use-UOID-for-identities.pl
@@ -0,0 +1,93 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+# this script goes through all the config keys that deal with
+# identities and replaces identities referenced by name to be referenced
+# by UOIDs. To this end, adds uoid keys to the identity groups.
+
+# read the whole config file:
+my $currentGroup = "";
+my %configFile;
+while ( <> ) {
+ chomp;
+ next if ( /^$/ ); # skip empty lines
+ next if ( /^\#/ ); # skip comments
+ if ( /^\[/ ) { # group begin
+ $currentGroup = $_;
+ next;
+ } elsif ( $currentGroup =~ /^\[Identity/ and /^uoid/ ) {
+ # We need to prevent this script from running twice, since it
+ # would change UOIDs of identities then.
+ # Presence of a uoid key in an [Identity #n] section is the
+ # best indicator:
+ exit;
+ } elsif ( $currentGroup ne "" ) { # normal entry
+ my ($key,$value) = split /=/;
+ $configFile{$currentGroup}{$key}=$value;
+ }
+}
+
+# filter out identity groups:
+my @identityGroups = grep { /^\[Identity \#\d+\]/ } keys %configFile;
+
+# create UOIDs for each identity:
+my %nameToUOID;
+foreach my $identityGroup (@identityGroups) {
+ my $uoid = int(rand 0x7fFFffFF);
+ my $name = $configFile{$identityGroup}{'Identity'};
+ $nameToUOID{$name} = $uoid;
+ # create the uoid entries of [Identity #n] groups:
+ print "${identityGroup}\nuoid=$uoid\n";
+}
+
+# change the default identity value:
+print "# DELETE [General]Default Identity\n[General]\nDefault Identity="
+ . $nameToUOID{$configFile{'[General]'}{'Default Identity'}} . "\n";
+
+# [Composer]previous-identity
+print "# DELETE [Composer]previous-identity\n[Composer]\nprevious-identity="
+ . $nameToUOID{$configFile{'[Composer]'}{'previous-identity'}} . "\n";
+
+# Now, go through all [Folder-*] groups and replace the Identity value
+# with the UOID. Also, move MailingListIdentity entries to Identity entries:
+my @folderGroups = grep { /^\[Folder-.*\]/ } keys %configFile;
+
+foreach my $folderGroup ( @folderGroups ) {
+ my $identity = "";
+ # delete the (MailingList)Identity keys:
+ print "# DELETE ${folderGroup}MailingListIdentity\n";
+ print "# DELETE ${folderGroup}Identity\n";
+ # extract the identity name:
+ if ( exists ($configFile{$folderGroup}{'Identity'}) ) {
+ $identity = $configFile{$folderGroup}{'Identity'};
+ }
+ if ( $identity eq ""
+ and exists($configFile{$folderGroup}{'MailingListIdentity'}) ) {
+ $identity = $configFile{$folderGroup}{'MailingListIdentity'};
+ }
+ # write the new Identity=<uoid> key if we have an UOID for the identity:
+ if ( exists( $nameToUOID{$identity} ) ) {
+ print "$folderGroup\nIdentity=" . $nameToUOID{$identity} . "\n";
+ }
+}
+
+# Now, go through all [Filter #n] groups and change arguments to the
+# 'set identity' filter action to use UOIDs:
+
+my @filterGroups = grep { /^\[Filter \#\d+\]/ } keys %configFile;
+
+foreach my $filterGroup (@filterGroups) {
+ my $numActions = +$configFile{$filterGroup}{'actions'};
+ # go through all actions in search for "set identity":
+ for ( my $i = 0 ; $i < $numActions ; ++$i ) {
+ my $actionName = "action-name-$i";
+ my $actionArgs = "action-args-$i";
+ if ( $configFile{$filterGroup}{$actionName} eq "set identity" ) {
+ # found one: replace it's argument with the UOID:
+ print "# DELETE $filterGroup$actionArgs\n$filterGroup\n$actionArgs="
+ . $nameToUOID{$configFile{$filterGroup}{$actionArgs}} . "\n";
+ }
+ }
+}
+
diff --git a/kmail/kmail-3.1.4-dont-use-UOID-0-for-any-identity.pl b/kmail/kmail-3.1.4-dont-use-UOID-0-for-any-identity.pl
new file mode 100644
index 00000000..0dba9080
--- /dev/null
+++ b/kmail/kmail-3.1.4-dont-use-UOID-0-for-any-identity.pl
@@ -0,0 +1,88 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+# this script goes through all the config keys that deal with
+# identities and replaces UOID 0 (which was erroneously always assigned to the
+# first identity) with a non-zero UOID.
+
+# read the whole config file
+my $currentGroup = "";
+my %configFile;
+while ( <> ) {
+ chomp;
+ next if ( /^$/ ); # skip empty lines
+ next if ( /^\#/ ); # skip comments
+ if ( /^\[/ ) { # group begin
+ $currentGroup = $_;
+ next;
+ } elsif ( $currentGroup ne "" ) { # normal entry
+ my ($key,$value) = split /=/;
+ $configFile{$currentGroup}{$key}=$value;
+ }
+}
+
+# filter out identity groups
+my @identityGroups = grep { /^\[Identity \#\d+\]/ } keys %configFile;
+
+# create new UOID for an identity with UOID 0
+my $newUoid = 0;
+foreach my $identityGroup (@identityGroups) {
+ my $oldUoid = $configFile{$identityGroup}{'uoid'};
+ if ( $oldUoid eq "0" ) {
+ $newUoid = int(rand 0x7fFFffFE) + 1;
+ # change the uoid entry this identity
+ print "# DELETE ${identityGroup}uoid\n";
+ print "${identityGroup}\nuoid=$newUoid\n";
+ }
+}
+
+if ( $newUoid != 0 ) {
+ # change the default identity value if it was 0
+ my $tempId = $configFile{'[General]'}{'Default Identity'};
+ if ( $tempId eq "0" ) {
+ print "# DELETE [General]Default Identity\n";
+ print "[General]\nDefault Identity=$newUoid\n";
+ }
+
+ # [Composer]previous-identity
+ $tempId = $configFile{'[Composer]'}{'previous-identity'};
+ if ( $tempId eq "0" ) {
+ print "# DELETE [Composer]previous-identity\n";
+ print "[Composer]\nprevious-identity=$newUoid\n";
+ }
+
+ # Now, go through all [Folder-*] groups and replace all occurrences of
+ # Identity=0 with Identity=$newUoid
+ my @folderGroups = grep { /^\[Folder-.*\]/ } keys %configFile;
+
+ foreach my $folderGroup ( @folderGroups ) {
+ $tempId = $configFile{$folderGroup}{'Identity'};
+ if ( $tempId eq "0" ) {
+ print "# DELETE ${folderGroup}Identity\n";
+ print "$folderGroup\nIdentity=$newUoid\n";
+ }
+ }
+
+ # Now, go through all [Filter #n] groups and replace all occurrences of
+ # UOID 0 as argument to the 'set identity' filter action with the new UOID
+ my @filterGroups = grep { /^\[Filter \#\d+\]/ } keys %configFile;
+
+ foreach my $filterGroup (@filterGroups) {
+ my $numActions = +$configFile{$filterGroup}{'actions'};
+ # go through all actions in search for "set identity":
+ for ( my $i = 0 ; $i < $numActions ; ++$i ) {
+ my $actionName = "action-name-$i";
+ my $actionArgs = "action-args-$i";
+ if ( $configFile{$filterGroup}{$actionName} eq "set identity" ) {
+ # found one:
+ # replace it's argument with the new UOID if necessary
+ $tempId = $configFile{$filterGroup}{$actionArgs};;
+ if ( $tempId eq "0" ) {
+ print "# DELETE $filterGroup$actionArgs\n";
+ print "$filterGroup\n$actionArgs=$newUoid\n";
+ }
+ }
+ }
+ }
+}
diff --git a/kmail/kmail-3.2-misc.sh b/kmail/kmail-3.2-misc.sh
new file mode 100755
index 00000000..6f6314d2
--- /dev/null
+++ b/kmail/kmail-3.2-misc.sh
@@ -0,0 +1,78 @@
+#! /usr/bin/env bash
+
+function delete_this_key() {
+ echo "# DELETE [$GROUP]$KEY"
+}
+
+while read; do
+ if [ "${REPLY#\[}" != "$REPLY" ] ; then # group name
+ GROUP="${REPLY:1:${#REPLY}-2}"
+ continue;
+ fi
+ # normal key=value pair:
+ KEY="${REPLY%%=*}"
+ VALUE="${REPLY#*=}"
+
+ case "$GROUP/$KEY" in
+ Geometry/MimePaneHeight)
+ case "$VALUE" in
+ [0-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9]) ;;
+ *) VALUE=100 ;;
+ esac
+ GeometryMimePaneHeight="$VALUE"
+ ;;
+ Geometry/MessagePaneHeight)
+ delete_this_key;
+ case "$VALUE" in
+ [0-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9]) ;;
+ *) VALUE=180 ;;
+ esac
+ GeometryMessagePaneHeight="$VALUE"
+ ;;
+ Geometry/FolderPaneHeight)
+ #
+ # keys to delete
+ #
+ delete_this_key
+ ;;
+ Geometry/windowLayout)
+ #
+ # break [Geometry]windowLayout={0,1,2,3,4} into
+ # [Geometry]FolderList={long,short} and
+ # [Reader]MimeTreeLocation={top,bottom}
+ #
+ delete_this_key
+ case "$VALUE" in
+ [0-4]) ;;
+ *) VALUE=1 ;;
+ esac
+ location=("top" "bottom" "bottom" "top" "top")
+ folder=("long" "long" "long" "short" "short")
+ echo "[Reader]"
+ echo "MimeTreeLocation=${location[$VALUE]}"
+ echo "[Geometry]"
+ echo "FolderList=${folder[$VALUE]}"
+ continue;
+ ;;
+ Geometry/showMIME)
+ #
+ # Rename [Geometry]showMime={0,1,2} into
+ # [Reader]MimeTreeMode={never,smart,always}
+ #
+ delete_this_key
+ case "$VALUE" in
+ [0-2]) ;;
+ *) VALUE=1 ;;
+ esac
+ substitution=("never" "smart" "always")
+ echo "[Reader]"
+ echo "MimeTreeMode=${substitution[$VALUE]}"
+ continue;
+ ;;
+ esac
+done
+
+if [ "$GeometryMimePaneHeight" ] && [ "$GeometryMessagePaneHeight" ]; then
+ echo "[Geometry]"
+ echo "ReaderPaneHeight=$(($GeometryMimePaneHeight+$GeometryMessagePaneHeight))"
+fi
diff --git a/kmail/kmail-3.2-update-loop-on-goto-unread-settings.sh b/kmail/kmail-3.2-update-loop-on-goto-unread-settings.sh
new file mode 100755
index 00000000..e98b27dd
--- /dev/null
+++ b/kmail/kmail-3.2-update-loop-on-goto-unread-settings.sh
@@ -0,0 +1,21 @@
+#! /usr/bin/env bash
+
+while read; do
+ KEY="${REPLY%%=*}"
+ VALUE="${REPLY#*=}"
+ if [ "$KEY" = "LoopOnGotoUnread" ]; then
+ case "$VALUE" in
+ [Ff][Aa][Ll][Ss][Ee]|0)
+ echo "LoopOnGotoUnread=0"
+ ;;
+ 2) # we were run already, be nice and don't change anything
+ echo "LoopOnGotoUnread=2"
+ ;;
+ *) # default
+ echo "LoopOnGotoUnread=1"
+ ;;
+ esac
+ else
+ echo "$REPLY"
+ fi
+done
diff --git a/kmail/kmail-3.3-aegypten.pl b/kmail/kmail-3.3-aegypten.pl
new file mode 100644
index 00000000..e891ea48
--- /dev/null
+++ b/kmail/kmail-3.3-aegypten.pl
@@ -0,0 +1,28 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+# This script updates some configuration keys and adds a 'mailto:' to all
+# mailing list posting addresses
+
+# read the whole config file
+my $currentGroup = "";
+my %configFile;
+while ( <> ) {
+ chomp; # eat the trailing '\n'
+ next if ( /^$/ ); # skip empty lines
+ next if ( /^\#/ ); # skip comments
+ if ( /^\[/ ) { # group begin
+ $currentGroup = $_;
+ next;
+ } elsif ( $currentGroup eq "" and /^pgpType/ ) {
+ my ($key,$value) = split /=/;
+ # 0 is auto -> delete key, i.e. use the default
+ # 1-4 map to the kpgp ones
+ # 5 is off -> off; since there's no such backend it won't set one
+ if ( $value != 0 ) {
+ my @newvalues = ("","Kpgp/gpg1","Kpgp/pgp v2","Kpgp/pgp v5","Kpgp/pgp v6","");
+ print "OpenPGP=$newvalues[$value]\n";
+ }
+ }
+}
diff --git a/kmail/kmail-3.3-misc.pl b/kmail/kmail-3.3-misc.pl
new file mode 100644
index 00000000..42b00812
--- /dev/null
+++ b/kmail/kmail-3.3-misc.pl
@@ -0,0 +1,27 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+# This script updates some configuration keys
+
+# read the whole config file
+my $currentGroup = "";
+my %configFile;
+while ( <> ) {
+ chomp; # eat the trailing '\n'
+ next if ( /^$/ ); # skip empty lines
+ next if ( /^\#/ ); # skip comments
+ if ( /^\[/ ) { # group begin
+ $currentGroup = $_;
+ next;
+ } elsif ( $currentGroup eq "[General]" and /^sendOnCheck/ ) {
+ my ($key,$value) = split /=/;
+ print "# DELETE $currentGroup$key\n";
+ if ( $value eq "true" ) {
+ print "[Behaviour]\nSendOnCheck=SendOnManualCheck\n";
+ }
+ else {
+ print "[Behaviour]\nSendOnCheck=DontSendOnCheck\n";
+ }
+ }
+}
diff --git a/kmail/kmail-3.3-move-identities.pl b/kmail/kmail-3.3-move-identities.pl
new file mode 100644
index 00000000..835cf4ff
--- /dev/null
+++ b/kmail/kmail-3.3-move-identities.pl
@@ -0,0 +1,31 @@
+#!/usr/bin/perl
+# David Faure <faure@kde.org>
+# License: GPL
+
+$currentGroup = "";
+
+while (<>) {
+ next if /^$/;
+ # recognize groups:
+ if ( /^\[(.+)\]$/ ) {
+ $currentGroup = $1;
+ if ( $currentGroup =~ /^Identity/ ) {
+ print "# DELETEGROUP [$currentGroup]\n";
+ print "[$currentGroup]\n";
+ }
+ next;
+ };
+ # Move over keys from the identity groups
+ if ( $currentGroup =~ /^Identity/ ) {
+ print;
+ }
+ # Move over the key for the default identity
+ elsif ( $currentGroup eq 'General' ) {
+ ($key,$value) = split /=/;
+ chomp $value;
+ if ( $key eq 'Default Identity' ) {
+ print "[$currentGroup]\n$key=$value\n";
+ print "# DELETE [$currentGroup]$key\n";
+ }
+ }
+}
diff --git a/kmail/kmail-3.3-split-sign-encr-keys.sh b/kmail/kmail-3.3-split-sign-encr-keys.sh
new file mode 100755
index 00000000..87c7a389
--- /dev/null
+++ b/kmail/kmail-3.3-split-sign-encr-keys.sh
@@ -0,0 +1,31 @@
+#! /usr/bin/env bash
+
+function delete_this_key() {
+ echo "# DELETE [$GROUP]$KEY"
+}
+
+while read; do
+ # Currently unused
+ if [ "${REPLY#\[}" != "$REPLY" ] ; then # group name
+ GROUP="${REPLY:1:${#REPLY}-2}"
+ continue;
+ fi
+ # normal key=value pair:
+ KEY="${REPLY%%=*}"
+ VALUE="${REPLY#*=}"
+
+ case "$GROUP" in
+ Identity*)
+ echo "# got Identity Key \"$KEY\""
+ case "$KEY" in
+ "Default PGP Key")
+ echo "[$GROUP]"
+ echo "PGP Signing Key=$VALUE"
+ echo "[$GROUP]"
+ echo "PGP Encryption Key=$VALUE"
+ delete_this_key
+ ;;
+ esac
+ ;;
+ esac
+done
diff --git a/kmail/kmail-3.3-use-ID-for-accounts.pl b/kmail/kmail-3.3-use-ID-for-accounts.pl
new file mode 100755
index 00000000..8270a57f
--- /dev/null
+++ b/kmail/kmail-3.3-use-ID-for-accounts.pl
@@ -0,0 +1,236 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+# this script goes through all the config keys that deal with
+# accounts and replaces accounts referenced by name to be referenced
+# by IDs.
+# It also renames the toplevel-folder and the sent/trash/drafts folder
+# accordingly and renames all references
+# last but not least we move the lokal folder-cache of (d)imap folders
+
+# read the whole config file
+my $currentGroup = "";
+my %configFile;
+while ( <> ) {
+ chomp;
+ next if ( /^$/ ); # skip empty lines
+ next if ( /^\#/ ); # skip comments
+ if ( /^\[/ ) { # group begin
+ $currentGroup = $_;
+ next;
+ } elsif ( $currentGroup =~ /^\[Account/ and /^Id/ ) {
+ # We need to prevent this script from running twice, since it
+ # would change IDs of accounts then.
+ # Presence of a id key in an Account section is the
+ # best indicator
+ exit;
+ } elsif ( $currentGroup ne "" ) { # normal entry
+ my ($key,$value) = split /=/;
+ $configFile{$currentGroup}{$key}=$value;
+ }
+}
+
+# filter out account groups
+my @accountGroups = grep { /^\[Account \d+\]/ } keys %configFile;
+
+# create IDs for each account
+my %nameToID;
+my %nameToType;
+foreach my $accountGroup (@accountGroups) {
+ my $id;
+ do {
+ $id = int(rand 0x7fFFffFF);
+ } while ($id <= 0);
+ my $name = $configFile{$accountGroup}{'Name'};
+ # remember id and type
+ $nameToID{$name} = $id;
+ $nameToType{$name} = $configFile{$accountGroup}{'Type'};
+ # create the id entries
+ print "${accountGroup}\nId=$id\n";
+}
+
+foreach my $accountGroup (@accountGroups) {
+ # rename the trash
+ my $trash = $configFile{$accountGroup}{'trash'};
+ if (&replaceID($trash)) {
+ print "# DELETE ".$accountGroup."trash\n";
+ print "${accountGroup}\ntrash=".&replaceID($trash)."\n";
+ }
+}
+
+# we need the directory where the imap cache is stored
+open(CMD, "kde-config --localprefix|");
+my $basedir = <CMD>;
+chomp( $basedir );
+$basedir = $basedir."share/apps/kmail";
+
+# Now, go through all [Folder-*] groups that belong to (d)imap folders
+# and replace the account name with the id
+my @folderGroups = grep { /^\[Folder-.*\]/ } keys %configFile;
+
+foreach my $folderGroup ( @folderGroups )
+{
+ my $isRootFolder = 1;
+ # extract the accountname
+ my (@parts) = split (/\[Folder-/,$folderGroup);
+ my $account = substr($parts[1], 0, -1);
+ if ($account =~ /^[^\/]*\.directory\//)
+ {
+ # .account.directory
+ my (@dirparts) = split (/\.directory\//,$account);
+ $account = substr( $dirparts[0], 1 );
+ # this is no root folder
+ $isRootFolder = 0;
+ }
+ # delete the old group and write the new entry
+ my $accountDecoded = QFileDecode( $account );
+ if ( exists( $nameToID{$accountDecoded} ) )
+ {
+ my $id = $nameToID{$accountDecoded};
+ print "# DELETEGROUP $folderGroup\n";
+ my $folderGroupNew = $folderGroup;
+ my $pattern = quotemeta( $account );
+ $folderGroupNew =~ s/$pattern/$id/;
+ # new account section
+ print "$folderGroupNew\n";
+ # print all original keys
+ my %groupData = %{$configFile{$folderGroup}};
+ foreach my $key ( keys %groupData ) {
+ print "$key=" . $groupData{$key} . "\n";
+ }
+ if ($isRootFolder) {
+ # new label and id of this rootfolder
+ print "SystemLabel=$account\n";
+ print "Id=".$id."\n";
+
+ # move the directory
+ my $subdir;
+ if ($nameToType{$accountDecoded} eq "imap") {
+ $subdir = "imap";
+ } elsif ($nameToType{$accountDecoded} eq "cachedimap") {
+ $subdir = "dimap";
+ }
+ my $oldname = QFileEncode( "$basedir/$subdir/\.$account\.directory" );
+ my $systemcall = "mv '$oldname' '$basedir/$subdir/\.".$id."\.directory'";
+ system($systemcall);
+
+ $oldname = QFileEncode( "$basedir/$subdir/$account" );
+ $systemcall = "mv '$oldname' '$basedir/$subdir/".$id."'";
+ system($systemcall);
+
+ $oldname = QFileEncode( "$basedir/$subdir/\.$account\.index" );
+ $systemcall = "mv '$oldname' '$basedir/$subdir/\.".$id."\.index'";
+ system($systemcall);
+
+ $oldname = QFileEncode( "$basedir/$subdir/\.$account\.index.ids" );
+ $systemcall = "mv '$oldname' '$basedir/$subdir/\.".$id."\.index.ids'";
+ system($systemcall);
+ }
+ }
+}
+
+# go through all identities and replace the sent-mail and drafts folder
+my @identities = grep { /^\[Identity #\d\]/ } keys %configFile;
+
+foreach my $identity (@identities)
+{
+ my $drafts = $configFile{$identity}{'Drafts'};
+ my $sent = $configFile{$identity}{'Fcc'};
+ # extract the account name
+ if (&replaceID($drafts))
+ {
+ print "# DELETE ".$identity."Drafts\n";
+ print "${identity}\nDrafts=".&replaceID($drafts)."\n";
+ }
+ if (&replaceID($sent))
+ {
+ print "# DELETE ".$identity."Fcc\n";
+ print "${identity}\nFcc=".&replaceID($sent)."\n";
+ }
+}
+
+# go through all filters and replace the target
+my @filterGroups = grep { /^\[Filter \#\d+\]/ } keys %configFile;
+foreach my $filterGroup (@filterGroups)
+{
+ my $numActions = +$configFile{$filterGroup}{'actions'};
+ # go through all actions in search for "set identity":
+ for ( my $i = 0 ; $i < $numActions ; ++$i )
+ {
+ my $actionName = "action-name-$i";
+ my $actionArgs = "action-args-$i";
+ if ( $configFile{$filterGroup}{$actionName} eq "transfer" &&
+ &replaceID($configFile{$filterGroup}{$actionArgs}) )
+ {
+ print "# DELETE $filterGroup$actionArgs\n";
+ print "$filterGroup\n$actionArgs=".
+ &replaceID($configFile{$filterGroup}{$actionArgs})."\n";
+ }
+ }
+}
+
+# previous fcc
+my $pfcc = $configFile{'[Composer]'}{'previous-fcc'};
+if (&replaceID($pfcc)) {
+ print "# DELETE [Composer]previous-fcc\n";
+ print "[Composer]\nprevious-fcc=".&replaceID($pfcc)."\n";
+}
+
+# GroupwareFolder
+my $groupware = $configFile{'[Groupware]'}{'GroupwareFolder'};
+if (&replaceID($groupware)) {
+ print "# DELETE [Groupware]GroupwareFolder\n";
+ print "[Groupware]\nGroupwareFolder=".&replaceID($groupware)."\n";
+}
+
+# and finally the startupFolder
+my $startup = $configFile{'[General]'}{'startupFolder'};
+if (&replaceID($startup)) {
+ print "# DELETE [General]startupFolder\n";
+ print "[General]\nstartupFolder=".&replaceID($startup)."\n";
+}
+
+## Returns input string with replaced account name
+## If there is nothing to replace it returns undef
+sub replaceID
+{
+ my ($input) = @_;
+
+ if ($input && $input =~ /\.directory/)
+ {
+ my (@dirparts) = split (/\.directory\//,$input);
+ my $account = substr( $dirparts[0], 1 );
+ my $accountDecoded = QFileDecode( $account );
+ if ( exists( $nameToID{$accountDecoded} ) )
+ {
+ my $pattern = quotemeta( $account );
+ $input =~ s/$pattern/$nameToID{$accountDecoded}/;
+ return $input;
+ }
+ }
+}
+
+## emulate QFileDecode
+sub QFileDecode
+{
+ my ($input) = @_;
+
+ $input =~ s/%20/ /g;
+ $input =~ s/%40/\@/g;
+ $input =~ s/%25/%/g; # must be the last one
+
+ return $input;
+}
+
+## emulate QFileEncode
+sub QFileEncode
+{
+ my ($input) = @_;
+
+ $input =~ s/%/%25/g; # must be the first one
+ $input =~ s/ /%20/g;
+ $input =~ s/\@/%40/g;
+
+ return $input;
+}
diff --git a/kmail/kmail-3.3b1-misc.pl b/kmail/kmail-3.3b1-misc.pl
new file mode 100644
index 00000000..b362850f
--- /dev/null
+++ b/kmail/kmail-3.3b1-misc.pl
@@ -0,0 +1,38 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+# This script updates some configuration keys and adds a 'mailto:' to all
+# mailing list posting addresses
+
+# read the whole config file
+my $currentGroup = "";
+my %configFile;
+while ( <> ) {
+ chomp; # eat the trailing '\n'
+ next if ( /^$/ ); # skip empty lines
+ next if ( /^\#/ ); # skip comments
+ if ( /^\[/ ) { # group begin
+ $currentGroup = $_;
+ next;
+ } elsif ( $currentGroup eq "[General]" and /^systray-on-mail/ ) {
+ my ($key,$value) = split /=/;
+ print "# DELETE $currentGroup$key\n";
+ print "[General]\nSystemTrayEnabled=$value\n";
+ } elsif ( $currentGroup eq "[General]" and /^systray-on-new/ ) {
+ my ($key,$value) = split /=/;
+ print "# DELETE $currentGroup$key\n";
+ if ( $value eq "true" ) {
+ print "[General]\nSystemTrayPolicy=ShowOnUnread\n";
+ }
+ else {
+ print "[General]\nSystemTrayPolicy=ShowAlways\n";
+ }
+ } elsif ( /^MailingListPostingAddress/ ) {
+ my ($key,$value) = split /=/;
+ if ( not $value eq "" and not $value =~ /^mailto:/ ) {
+ print "# DELETE $currentGroup$key\n";
+ print "$currentGroup\n$key=mailto:$value\n";
+ }
+ }
+}
diff --git a/kmail/kmail-3.4-misc.pl b/kmail/kmail-3.4-misc.pl
new file mode 100644
index 00000000..e9be4ff8
--- /dev/null
+++ b/kmail/kmail-3.4-misc.pl
@@ -0,0 +1,27 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+# This script updates some configuration keys
+
+# read the whole config file
+my $currentGroup = "";
+my %configFile;
+while ( <> ) {
+ chomp; # eat the trailing '\n'
+ next if ( /^$/ ); # skip empty lines
+ next if ( /^\#/ ); # skip comments
+ if ( /^\[/ ) { # group begin
+ $currentGroup = $_;
+ next;
+ } elsif ( $currentGroup eq "[Behaviour]" and /^JumpToUnread/ ) {
+ my ($key,$value) = split /=/;
+ print "# DELETE $currentGroup$key\n";
+ if ( $value eq "true" ) {
+ print "[Behaviour]\nActionEnterFolder=SelectFirstUnreadNew\n";
+ }
+ else {
+ print "[Behaviour]\nActionEnterFolder=SelectFirstNew\n";
+ }
+ }
+}
diff --git a/kmail/kmail-3.4.1-update-status-filters.pl b/kmail/kmail-3.4.1-update-status-filters.pl
new file mode 100644
index 00000000..735daf77
--- /dev/null
+++ b/kmail/kmail-3.4.1-update-status-filters.pl
@@ -0,0 +1,42 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+# This script converts lower case status filter rules to upper case.
+
+# read the whole config file
+my $currentGroup = "";
+my %configFile;
+while ( <> ) {
+ chomp;
+ next if ( /^$/ ); # skip empty lines
+ next if ( /^\#/ ); # skip comments
+ if ( /^\[/ ) { # group begin
+ $currentGroup = $_;
+ next;
+ } elsif ( $currentGroup ne "" ) { # normal entry
+ my ($key,$value) = split /=/;
+ $configFile{$currentGroup}{$key}=$value;
+ }
+}
+
+# go through all filters and check for rules which are no longer valid
+my @filterGroups = grep { /^\[Filter \#\d+\]/ } keys %configFile;
+foreach my $filterGroup (@filterGroups) {
+ my $numRules = $configFile{$filterGroup}{'rules'};
+ # go through all rules:
+ for ( my $i = 0; $i < $numRules; ++$i ) {
+ my $c = chr( ord("A") + $i );
+ my $fieldKey = "field$c";
+ my $field = $configFile{$filterGroup}{$fieldKey};
+ if ( $field eq "<status>" ) {
+ my $contentsKey = "contents$c";
+ my $contents = $configFile{$filterGroup}{$contentsKey};
+ if ( $contents =~ /^[a-z]/ ) {
+ $contents = ucfirst( $contents );
+ print "# DELETE $filterGroup$contentsKey\n";
+ print "$filterGroup\n$contentsKey=$contents\n";
+ }
+ }
+ }
+}
diff --git a/kmail/kmail-3.5-trigger-flag-migration.pl b/kmail/kmail-3.5-trigger-flag-migration.pl
new file mode 100644
index 00000000..809a611a
--- /dev/null
+++ b/kmail/kmail-3.5-trigger-flag-migration.pl
@@ -0,0 +1,39 @@
+#!/usr/bin/perl
+#
+# Copyright (c) 2007 Volker Krause <vkrause@kde.org>
+#
+# 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.
+# 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, US
+#
+
+$currentGroup = "";
+
+$source = $ARGV[0];
+
+while (<STDIN>) {
+ chomp;
+ next if /^$/;
+ next if /^\#/;
+
+ # recognize groups:
+ if ( /^\[(.+)\]$/ ) {
+ $currentGroup = $_;
+ next;
+ };
+
+ ($key,$value) = split /=/;
+ next if $key eq "";
+
+ if ( $currentGroup =~ /^\[Folder/ ) {
+ if( ($key eq "UploadAllFlags" or $key eq "StatusChangedLocally") and not $value eq "true" ) {
+ $value = "true";
+ print "#DELETE $currentGroup $key\n";
+ print "$currentGroup\n$key=$value\n"
+ }
+ }
+}
diff --git a/kmail/kmail-pgpidentity.pl b/kmail/kmail-pgpidentity.pl
new file mode 100755
index 00000000..b6e7bcad
--- /dev/null
+++ b/kmail/kmail-pgpidentity.pl
@@ -0,0 +1,34 @@
+#!/usr/bin/perl
+
+# For each KMail Identity convert the "PGP Identity" entry (which contains a
+# user ID) to a "Default PGP Key" entry (which contains a key ID)
+
+$DEBUG = 0;
+
+while(<>)
+{
+ if( /\[(Identity.*)\]/ )
+ {
+ $section = $1;
+ next;
+ }
+ if( /^PGP Identity=(.*)$/ )
+ {
+ print STDERR "\n[$section]PGP Identity=$1\n" if ( $DEBUG );
+
+ if( ( $1 ne "" ) &&
+ ( open GnuPG, "gpg --list-secret-keys --utf8-strings '$1' |" ) )
+ {
+ while (<GnuPG>)
+ {
+ # search in gpg's output for the key id of the first matching key
+ if(/^sec[^\/]*\/([0-9A-F]*)/)
+ {
+ print "[$section]\nDefault PGP Key=$1\n";
+ last;
+ }
+ }
+ }
+ print "# DELETE [$section]PGP Identity\n";
+ }
+}
diff --git a/kmail/kmail-upd-identities.pl b/kmail/kmail-upd-identities.pl
new file mode 100755
index 00000000..3dd9f4e9
--- /dev/null
+++ b/kmail/kmail-upd-identities.pl
@@ -0,0 +1,54 @@
+#!/usr/bin/perl
+
+my (%data);
+
+$currentGroup = "";
+
+while (<>) {
+ next if /^$/;
+ # filter out groups:
+ if ( /^\[(.+)\]$/ ) { $currentGroup = $1; next; };
+ # store all keys regarding Identities in the hash:
+ if ( $currentGroup =~ /^Identity/ ) {
+ ($key,$value) = split /=/;
+ chomp $value;
+ $data{$currentGroup}{$key} = $value;
+ }
+}
+
+# We need to prevent this script from being run twice, since it would
+# kill all identities then.
+# Non-presence of the [Identity]IdentityList key is the best indiator:
+unless ( defined( $data{'Identity'}{'IdentityList'} ) ) { exit; }
+
+# first, delete all old groups:
+foreach $group ( keys %data ) {
+ print "# DELETEGROUP [$group]\n";
+}
+
+# now, extract the list of valid identities (and their sequence):
+$rawIdentityList = $data{'Identity'}{'IdentityList'};
+# don't include the IdentityList anymore:
+delete $data{'Identity'}{'IdentityList'};
+# remove backslash-quoting:
+$rawIdentityList =~ s/\\(.)/$1/g;
+# split into a list at unquoted commas:
+@identities = split /(?<!\\),/, $rawIdentityList;
+# unquote individual items yet again:
+for ( @identities ) { s/\\(.)/$1/g; }
+# build the list of groups (this time incl. the default identity):
+@groups = ( 'Identity', map { $_ = "Identity-$_"; } @identities );
+# write out the groups, now named "Identity #n",
+# with the same data and the same keys that the old groups had:
+$n = 0;
+foreach $group (@groups) {
+ %groupData = %{$data{$group}};
+ print "[Identity #$n]\n";
+ foreach $key ( keys %groupData ) {
+ print "$key="
+ . $groupData{$key} . "\n";
+ }
+ $n++;
+}
+# remember which one is default:
+print "[General]\nDefault Identity=" . $data{'Identity'}{'Identity'} . "\n";
diff --git a/kmail/kmail.antispamrc b/kmail/kmail.antispamrc
new file mode 100644
index 00000000..098f80c2
--- /dev/null
+++ b/kmail/kmail.antispamrc
@@ -0,0 +1,237 @@
+[General]
+tools=11
+
+[Spamtool #1]
+Ident=spamassassin
+Version=1
+Priority=40
+VisibleName=SpamAssassin 2.x (Perl)
+Executable=spamassassin -V | grep "SpamAssassin version 2"
+URL=http://spamassassin.org
+PipeFilterName=SpamAssassin Check
+PipeCmdDetect=spamassassin -L
+ExecCmdSpam=sa-learn -L --spam --no-rebuild --single
+ExecCmdHam=sa-learn -L --ham --no-rebuild --single
+DetectionHeader=X-Spam-Flag
+DetectionPattern=yes
+DetectionPattern2=
+DetectionOnly=0
+UseRegExp=0
+SupportsBayes=1
+SupportsUnsure=0
+ScoreName=Spamassassin
+ScoreHeader=X-Spam-Status
+ScoreType=Adjusted
+ScoreValueRegexp=(?:hits|score)=([\d\.-]+)[^\d\.]
+ScoreThresholdRegexp=required=([\d\.-]+)[^\d\.]
+
+[Spamtool #2]
+Ident=bogofilter
+Version=2
+Priority=50
+VisibleName=Bogofilter
+Executable=bogofilter -V
+URL=http://bogofilter.sourceforge.net
+PipeFilterName=Bogofilter Check
+PipeCmdDetect=bogofilter -p -e -u
+ExecCmdSpam=bogofilter -N -s
+ExecCmdHam=bogofilter -S -n
+DetectionHeader=X-Bogosity
+DetectionPattern=(yes)|(spam\b)
+DetectionPattern2=
+DetectionOnly=0
+UseRegExp=1
+SupportsBayes=1
+SupportsUnsure=0
+ScoreName=Bogofilter
+ScoreHeader=X-Bogosity
+ScoreType=Decimal
+ScoreValueRegexp=spamicity=([\\d\\.]+),
+ScoreThresholdRegexp=
+
+[Spamtool #3]
+Ident=annoyance-filter
+Version=1
+Priority=30
+VisibleName=Annoyance-Filter
+Executable=$HOME/.annoyance-filter/annoyance-filter --version
+URL=http://www.fourmilab.ch/annoyance-filter
+PipeFilterName=Annoyance-Filter Check
+PipeCmdDetect=$HOME/.annoyance-filter/annoyance-filter --fread $HOME/.annoyance-filter/FastDict.bin --phrasemin 1 --phrasemax 2 --transcript - --test -
+ExecCmdSpam=$HOME/.annoyance-filter/annoyance-filter --read $HOME/.annoyance-filter/Dict.bin --phrasemin 1 --phrasemax 2 --junk - --prune --write $HOME/.annoyance-filter/Dict.bin --fwrite $HOME/.annoyance-filter/FastDict.bin
+ExecCmdHam=$HOME/.annoyance-filter/annoyance-filter --read $HOME/.annoyance-filter/Dict.bin --phrasemin 1 --phrasemax 2 --mail - --prune --write $HOME/.annoyance-filter/Dict.bin --fwrite $HOME/.annoyance-filter/FastDict.bin
+DetectionHeader=X-Annoyance-Filter-Classification
+DetectionPattern=Junk
+DetectionPattern2=
+DetectionOnly=0
+UseRegExp=0
+SupportsBayes=1
+SupportsUnsure=0
+
+[Spamtool #4]
+Ident=gmx-antispam
+Version=1
+Priority=71
+VisibleName=GMX Spam Filter
+Executable=echo
+URL=http://www.gmx.net/de/produkte/virenschutz/index.html
+PipeFilterName=
+PipeCmdDetect=
+ExecCmdSpam=
+ExecCmdHam=
+DetectionHeader=X-GMX-Antispam
+DetectionPattern=^[2345]
+DetectionPattern2=
+DetectionOnly=1
+UseRegExp=1
+SupportsBayes=0
+SupportsUnsure=0
+ServerPattern=gmx
+
+[Spamtool #5]
+Ident=spambayes
+Version=1
+Priority=20
+VisibleName=SpamBayes
+Executable=sb_filter.py -h
+URL=http://spambayes.sourceforge.net
+PipeFilterName=SpamBayes Check
+PipeCmdDetect=sb_filter.py
+ExecCmdSpam=sb_filter.py -s
+ExecCmdHam=sb_filter.py -g
+DetectionHeader=X-Spambayes-Classification
+DetectionPattern=spam
+DetectionPattern2=unsure
+DetectionOnly=0
+UseRegExp=0
+SupportsBayes=1
+SupportsUnsure=1
+ScoreName=Spambayes
+ScoreHeader=X-Spambayes-Classification
+ScoreType=Decimal
+ScoreValueRegexp=(?:spam|ham);\ ([\\d\\.]+)
+ScoreThresholdRegexp=
+
+[Spamtool #6]
+HeadersOnly=yes
+ScoreName=Razor
+ScoreHeader=X-Razor-Spam
+ScoreType=Bool
+ScoreValueRegexp=SPAM
+ScoreThresholdRegexp=
+
+[Spamtool #7]
+Ident=spamd
+Version=1
+Priority=45
+VisibleName=SpamAssassin 3.x (Daemon)
+Executable=echo "From: test" | spamc -x && spamassassin -V | grep "SpamAssassin version 3"
+URL=http://spamassassin.org
+PipeFilterName=SpamAssassin Service Check
+PipeCmdDetect=spamc
+ExecCmdSpam=sa-learn -L --spam --no-rebuild --single
+ExecCmdHam=sa-learn -L --ham --no-rebuild --single
+DetectionHeader=X-Spam-Flag
+DetectionPattern=yes
+DetectionPattern2=
+DetectionOnly=0
+UseRegExp=0
+SupportsBayes=1
+SupportsUnsure=0
+ScoreName=Spamassassin
+ScoreHeader=X-Spam-Status
+ScoreType=Adjusted
+ScoreValueRegexp=(?:hits|score)=([\d\.-]+)[^\d\.]
+ScoreThresholdRegexp=required=([\d\.-]+)[^\d\.]
+
+[Spamtool #8]
+Ident=spamassassin
+Version=1
+Priority=41
+VisibleName=SpamAssassin 3.x (Perl)
+Executable=spamassassin -V | grep "SpamAssassin version 3"
+URL=http://spamassassin.org
+PipeFilterName=SpamAssassin Check
+PipeCmdDetect=spamassassin -L
+ExecCmdSpam=sa-learn -L --spam --no-sync
+ExecCmdHam=sa-learn -L --ham --no-sync
+DetectionHeader=X-Spam-Flag
+DetectionPattern=yes
+DetectionPattern2=
+DetectionOnly=0
+UseRegExp=0
+SupportsBayes=1
+SupportsUnsure=0
+ScoreName=Spamassassin
+ScoreHeader=X-Spam-Status
+ScoreType=Adjusted
+ScoreValueRegexp=(?:hits|score)=([\d\.-]+)[^\d\.]
+ScoreThresholdRegexp=required=([\d\.-]+)[^\d\.]
+
+[Spamtool #9]
+Ident=bsfilter
+Version=1
+Priority=55
+VisibleName=Bsfilter
+Executable=bsfilter --help > /dev/null
+URL=http://bsfilter.org/
+PipeFilterName=Bsfilter Check
+PipeCmdDetect=bsfilter --pipe --insert-flag --insert-probability --header-prefix BSFilter
+ExecCmdSpam=bsfilter --add-spam --update
+ExecCmdHam=bsfilter --add-clean --update
+DetectionHeader=X-BSFilter-Flag
+DetectionPattern=Yes
+DetectionPattern2=
+DetectionOnly=0
+UseRegExp=0
+SupportsBayes=1
+SupportsUnsure=0
+ScoreName=Bsfilter
+ScoreHeader=X-BSFilter-Probability
+ScoreType=Decimal
+ScoreValueRegexp=(.+)
+ScoreThresholdRegexp=
+
+[Spamtool #10]
+Ident=dspam
+Version=1
+Priority=70
+VisibleName=DSpam 3.x
+URL=http://dspam.nuclearelephant.org
+HeadersOnly=yes
+DetectionHeader=X-DSPAM-Result
+DetectionPattern=Spam
+DetectionOnly=0
+UseRegExp=0
+ScoreName=DSpam
+SupportsUnsure=0
+ScoreHeader=X-DSPAM-Probability
+ConfidenceHeader=X-DSPAM-Confidence
+ScoreType=Decimal
+ScoreValueRegexp=([\d\.]+)\s*
+ScoreConfidenceRegexp=([\d\.]+)\s*
+
+[Spamtool #11]
+Ident=crm114
+Version=1
+Priority=65
+VisibleName=CRM114
+Executable=crm -v | grep "CRM114"
+URL=http://crm114.sourceforge.net
+PipeFilterName=CRM114 Check
+PipeCmdDetect=crm -u $HOME/.crm114 mailreaver.crm
+ExecCmdSpam=crm -u $HOME/.crm114 mailreaver.crm --spam
+ExecCmdHam=crm -u $HOME/.crm114 mailreaver.crm --good
+DetectionHeader=X-CRM114-Status
+DetectionPattern=SPAM
+DetectionPattern2=UNSURE
+DetectionOnly=0
+UseRegExp=0
+SupportsBayes=1
+SupportsUnsure=1
+ScoreName=CRM114
+ScoreHeader=X-CRM114-Status
+ScoreType=Bool
+ScoreValueRegexp=SPAM
+ScoreThresholdRegexp=
+
diff --git a/kmail/kmail.antispamrc-HOWTO b/kmail/kmail.antispamrc-HOWTO
new file mode 100644
index 00000000..9edcabb0
--- /dev/null
+++ b/kmail/kmail.antispamrc-HOWTO
@@ -0,0 +1,154 @@
+
+HOWTO for setting up a KMail antispam wizard configuration entry
+================================================================
+
+This is a HOWTO for setting up a KMail antispam wizard configuration
+entry. Since this possibly is more developer related, I put this as
+text file into the source SVN. Possibly some of this should go to the
+user documentation.
+
+I gathered the information from several mails and comments by Andreas
+Gungl, the original developer of the antispam wizard for KMail, and
+Ingo Kloecker.
+
+When you have questions do not hesitate to ask me, Martin Steigerwald,
+Andreas or Ingo.
+
+
+Basics
+------
+
+The configuration file for the KMail antispam wizard "kmail.antispamrc"
+consists of one entry for each spamfilter that the antispam wizard
+shall support.
+
+At the beginning of the file in the section "General" the option "tools"
+specifies the count of configured spam filters. Increase that by one
+when you add a new entry:
+
+[General]
+tools=11
+
+
+A spam filter configuration entry starts with a header like this:
+
+[Spamtool #11]
+
+
+After this you place all the options for the spam filter. Please use
+an ordering that is similar to the other entries in the configuration
+file so that things are unified a bit.
+
+
+General options
+---------------
+
+- Ident: Specifies the internal identifier for the entry
+
+- Version: Specifies the version of the entry (FIXME what is this for?)
+
+- Priority: Specifies the priority of the filter. This value is used to
+ place faster filters before the slower ones in the selection list. If the
+ user chooses the top item, he gets the fastest filter. Provider sided
+ "filters" (which produce headers tough) like the GMX filter have a prio
+ at about 70, they are very fast as they don't consume time on the client
+ side. Since CRM114 is almost as fast it gets 65 ;-).
+
+- VisibleName: This is the name that is presented to the user
+
+Or:
+
+- HeadersOnly=yes: This is used for entries where KMail should just parse
+ the mail headers for the spam score display (see Razor filter and below
+ for spam score display details).
+
+
+Spam filter options
+-------------------
+
+These specify details about the spamfilter.
+
+- Executable: Specifies a test whether the executable of the spamfilter
+ can be started. You should provide something which can be run on the
+ command line and returns [ $? -eq 0 ], i.e. it doesn't wait for any input
+ etc. It usually make sense to assume the program in the $PATH, so you
+ should better avoid /usr/bin. Set executable to "echo" for provider based
+ filters.
+ Example: Executable=crm -v | grep "CRM114"
+
+- URL: URL to the homepage of the filter
+
+- PipeFilterName: Name of the pipe-through filter used to send mails to the
+ spamfilter and get them back with added spam filter headers.
+
+- PipeCmdDetect: Command used to pipe the mail into.
+
+- ExecCmdSpam: Command used to mark a mail as spam.
+
+- ExecCmdHam: Command used to mark a mail as ham.
+
+- SupportsBayes: Set to 1 if you have a spam filter that can learn. Only
+ in this case KMail uses ExecCmdSpam and ExecCmdHam to let the user mark
+ mail as ham or spam.
+
+
+Spam detection options
+----------------------
+
+These specify how KMail shall detect whether a mail is spam, unsure or good.
+
+- DetectionHeader: The name of the header where the spam filter puts the spam
+ status of a mail into.
+
+- DetectionPattern: The pattern the spam filter uses for marking a mail as
+ spam.
+
+- DetectionPattern2: The pattern the spam filter uses for marking a mail as
+ unsure. Set "SupportsUnsure=1" when you use this.
+
+- DetectionOnly: Don't pipe mails through the spam filter, but just use headers
+ from outside, e.g. a provider based spam filter (See GMX).
+
+- UseRegExp: Set to 1 if you need to use regular expressions in the detection
+ patterns. KMail can operate faster when they are not required.
+
+- SupportsUnsure: Set to 1 if you have a spam filter that supports
+ classying mails as unsure to tell the user to train those.
+
+
+Spam score display
+------------------
+
+Those regular expressions are used to extract the actual "spamicity"
+score and the threshold (i.e. the upper bound for non-spam) from
+SpamAssassin's ScoreHeader. The score and the threshold are then used
+for showing the spam status in the message header, i.e. the small
+colorbar.
+
+You need to specify the following values:
+
+- ScoreName: The name that will be shown in the message header.
+
+- ScoreHeader: The message header containing the score value.
+
+- ScoreType: The type of the score, cf. below.
+
+- ScoreValueRegexp: A regular expression for extracting the score from
+ the ScoreHeader.
+
+- ScoreThresholdRegexp: A regular expression for extracting the threshold
+ from the ScoreHeader; only needed for Adjusted type. Please set to
+ nothing (ScoreThresholdRegexp=) if not needed.
+
+
+KMail supports the following ScoreType values:
+
+- Bool: Simple Yes or No (Razor)
+
+- Decimal: For probability between 0.0 and 1.0 (BogoFilter)
+
+- Percentage: For straight percentages between 0.0 and 100.0
+
+- Adjusted: Use this when we need to compare against a threshold
+ (SpamAssasssin)
+
diff --git a/kmail/kmail.antivirusrc b/kmail/kmail.antivirusrc
new file mode 100644
index 00000000..79fa5c13
--- /dev/null
+++ b/kmail/kmail.antivirusrc
@@ -0,0 +1,50 @@
+[General]
+tools=4
+
+[Virustool #1]
+Ident=ClamAV
+Version=1
+VisibleName=Clam Anti-Virus
+Executable=clamscan -V
+URL=http://clamav.sf.net
+PipeFilterName=Clam Anti-Virus Check
+PipeCmdDetect=kmail_clamav.sh
+DetectionHeader=X-Virus-Flag
+DetectionPattern=yes
+UseRegExp=0
+
+[Virustool #2]
+Ident=SophosAV
+Version=1
+VisibleName=Sophos AntiVirus
+Executable=sweep -v
+URL=http://www.sophos.com
+PipeFilterName=Sophos Anti-Virus Check
+PipeCmdDetect=kmail_sav.sh
+DetectionHeader=X-Virus-Flag
+DetectionPattern=yes
+UseRegExp=0
+
+[Virustool #3]
+Ident=FProt
+Version=1
+VisibleName=F-Prot AntiVirus
+Executable=f-prot -verno
+URL=http://www.f-prot.com
+PipeFilterName=F-Prot Anti-Virus Check
+PipeCmdDetect=kmail_fprot.sh
+DetectionHeader=X-Virus-Flag
+DetectionPattern=yes
+UseRegExp=0
+
+[Virustool #4]
+Ident=AntiVir
+Version=1
+VisibleName=H+BEDV AntiVir
+Executable= antivir --version
+URL=http://www.antivir.de
+PipeFilterName=AntiVir Anti-Virus Check
+PipeCmdDetect=kmail_antivir.sh
+DetectionHeader=X-Virus-Flag
+DetectionPattern=yes
+UseRegExp=0
diff --git a/kmail/kmail.kcfg b/kmail/kmail.kcfg
new file mode 100644
index 00000000..afb228cf
--- /dev/null
+++ b/kmail/kmail.kcfg
@@ -0,0 +1,670 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
+ http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
+ <include>qtextcodec.h</include>
+ <include>kapplication.h</include>
+ <kcfgfile name="kmailrc"/>
+ <group name="Behaviour">
+ <entry name="DelayedMarkAsRead" type="Bool">
+ <label></label>
+ <whatsthis></whatsthis>
+ <default>true</default>
+ </entry>
+ <entry name="DelayedMarkTime" type="UInt">
+ <label></label>
+ <whatsthis></whatsthis>
+ <default>0</default>
+ </entry>
+ <entry name="ActionEnterFolder" type="Enum">
+ <label></label>
+ <whatsthis></whatsthis>
+ <choices>
+ <choice name="SelectFirstNew"/>
+ <choice name="SelectFirstUnreadNew"/>
+ <choice name="SelectLastSelected"/>
+ </choices>
+ <default>SelectLastSelected</default>
+ </entry>
+ <entry name="NetworkState" type="Enum">
+ <choices>
+ <choice name="Online"/>
+ <choice name="Offline"/>
+ </choices>
+ <default>Online</default>
+ </entry>
+ <entry name="LoopOnGotoUnread" type="Enum">
+ <label></label>
+ <whatsthis></whatsthis>
+ <choices>
+ <choice name="DontLoop"/>
+ <choice name="LoopInCurrentFolder"/>
+ <choice name="LoopInAllFolders"/>
+ </choices>
+ <default>DontLoop</default>
+ </entry>
+ <entry name="ShowPopupAfterDnD" type="Bool">
+ <label></label>
+ <whatsthis></whatsthis>
+ <default>true</default>
+ </entry>
+ <entry name="ExcludeImportantMailFromExpiry" type="Bool">
+ <label></label>
+ <whatsthis></whatsthis>
+ <default>true</default>
+ </entry>
+
+ <entry name="SendOnCheck" type="Enum">
+ <label>Send queued mail on mail check</label>
+ <whatsthis>&lt;qt&gt;&lt;p&gt;Select whether you want KMail to send all messages in the outbox on manual or all mail checks, or whether you do not want messages to be sent automatically at all. &lt;/p&gt;&lt;/qt&gt;</whatsthis>
+ <choices>
+ <choice name="DontSendOnCheck"/>
+ <choice name="SendOnManualChecks"/>
+ <choice name="SendOnAllChecks"/>
+ </choices>
+ <default>DontSendOnCheck</default>
+ </entry>
+
+ <entry name="AutoLostFoundMove" type="Bool">
+ <label>Automatically move non-synced mails from folders with insufficient access rights</label>
+ <whatsthis>If there are new messages in a folder, which have not been uploaded to the server yet, but you do not have sufficient access rights on the folder now to upload them, these messages will automatically be moved into a lost and found folder.</whatsthis>
+ <default>true</default>
+ </entry>
+
+ <entry name="AllowLocalFlags" type="Bool">
+ <label>Allow local flags in read-only folders</label>
+ <default>false</default>
+ </entry>
+ </group>
+
+ <group name="ConfigurationDialogRestrictions">
+ <entry name="MinimumCheckInterval" type="Int">
+ <default>1</default>
+ <whatsthis>This setting allows administrators to set a minimum delay between two mail checks."
+ "The user will not be able to choose a value smaller than the value set here.</whatsthis>
+ </entry>
+ </group>
+
+ <group name="FolderSelectionDialog">
+ <entry name="LastSelectedFolder" type="String">
+ <whatsthis>The most recently selected folder in the folder selection dialog.</whatsthis>
+ <default>inbox</default>
+ </entry>
+
+ </group>
+
+ <group name="General">
+ <entry name="disregardUmask" type="Bool">
+ <label>Disregard the users umask setting and use "read-write for the user only" instead</label>
+ <default>false</default>
+ </entry>
+ <entry name="SystemTrayEnabled" type="Bool">
+ <label>Enable system tray icon</label>
+ <default>false</default>
+ </entry>
+ <entry name="SystemTrayPolicy" type="Enum">
+ <label>Policy for showing the system tray icon</label>
+ <choices>
+ <choice name="ShowAlways"/>
+ <choice name="ShowOnUnread"/>
+ </choices>
+ <default>ShowOnUnread</default>
+ </entry>
+ <entry name="CloseDespiteSystemTray" type="Bool">
+ <label>Close the application when the mainwindow is closed, even if there is a system tray icon active.</label>
+ <default>false</default>
+ </entry>
+ <entry name="VerboseNewMailNotification" type="Bool">
+ <label>Verbose new mail notification</label>
+ <whatsthis>If this option is enabled then for each folder the number of newly arrived messages is shown in the new mail notification; otherwise, you will only get a simple 'New mail arrived' message.</whatsthis>
+ <default>true</default>
+ </entry>
+ <entry name="ExternalEditor" type="String" key="external-editor">
+ <label>Specify e&amp;ditor:</label>
+ <default>kate %f</default>
+ </entry>
+ <entry name="UseExternalEditor" type="Bool" key="use-external-editor">
+ <label>Use e&amp;xternal editor instead of composer</label>
+ <default>false</default>
+ </entry>
+ <entry name="CustHeaderCount" type="Int" key="mime-header-count" />
+ <entry name="ReplyCurrentLanguage" type="Int" key="reply-current-language">
+ <default>0</default>
+ </entry>
+ <entry name="ReplyLanguagesCount" type="Int" key="reply-languages" >
+ <default>0</default>
+ <min>0</min>
+ </entry>
+ <entry name="FolderLoadingTimeout" type="Int" hidden="true">
+ <default>1000</default>
+ </entry>
+
+ <entry name="QuotaUnit" type="Enum">
+ <label></label>
+ <whatsthis></whatsthis>
+ <choices>
+ <choice name="KB"/>
+ <choice name="MB"/>
+ <choice name="GB"/>
+ </choices>
+ <default>MB</default>
+ </entry>
+
+ <entry name="CloseToQuotaThreshold" type="Int">
+ <label>The threshold for when to warn the user that a folder is nearing its quota limit.</label>
+ <default>85</default>
+ </entry>
+
+ </group>
+<!-- General -->
+
+ <group name="Groupware">
+ <entry name="GroupwareEnabled" type="Bool">
+ <label>Enable groupware functionality</label>
+ <whatsthis></whatsthis>
+ <default>true</default>
+ </entry>
+
+ <entry name="LegacyMangleFromToHeaders" type="Bool">
+ <label>Mangle From:/To: headers in replies to replies</label>
+ <whatsthis>Microsoft Outlook has a number of shortcomings in its implementation of the iCalendar standard; this option works around one of them. If you have problems with Outlook users not being able to get your replies, try setting this option.</whatsthis>
+ <default>false</default>
+ </entry>
+
+ <entry name="LegacyBodyInvites" type="Bool">
+ <label>Send groupware invitations in the mail body</label>
+ <whatsthis>Microsoft Outlook has a number of shortcomings in its implementation of the iCalendar standard; this option works around one of them. If you have problems with Outlook users not being able to get your invitations, try setting this option.</whatsthis>
+ <default>false</default>
+ </entry>
+
+ <entry name="ExchangeCompatibleInvitations" type="Bool">
+ <label>Exchange compatible invitations naming</label>
+ <whatsthis>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.</whatsthis>
+ <default>false</default>
+ </entry>
+
+ <entry name="AutomaticSending" type="Bool">
+ <label>Automatic invitation sending</label>
+ <whatsthis>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.</whatsthis>
+ <default>true</default>
+ </entry>
+
+ <entry name="AskForCommentWhenReactingToInvitation" type="Enum">
+ <label></label>
+ <whatsthis></whatsthis>
+ <choices>
+ <choice name="NeverAsk"/>
+ <choice name="AskForAllButAcceptance"/>
+ <choice name="AlwaysAsk"/>
+ </choices>
+ <default>AskForAllButAcceptance</default>
+ </entry>
+
+ <entry name="DeleteInvitationEmailsAfterSendingReply" type="Bool">
+ <label>Delete invitation emails after the reply to them has been sent</label>
+ <whatsthis>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.</whatsthis>
+ <default>true</default>
+ </entry>
+
+
+ </group>
+
+ <group name="IMAP Resource">
+ <entry name="TheIMAPResourceEnabled" type="Bool">
+ <whatsthis>&lt;p&gt;Enabling this makes it possible to store the entries from the Kontact applications (KOrganizer, KAddressBook, and KNotes.)&lt;/p&gt;&lt;p&gt;If you want to set this option you must also set the applications to use the IMAP resource; this is done in the KDE Control Center.&lt;/p&gt;</whatsthis>
+ <default>false</default>
+ </entry>
+
+ <entry name="HideGroupwareFolders" type="Bool">
+ <whatsthis>&lt;p&gt;Usually you will not have any reason to see the folders that hold the IMAP resources. But if you need to see them, you can set that here.&lt;/p&gt;</whatsthis>
+ <default>true</default>
+ </entry>
+
+ <entry name="ShowOnlyGroupwareFoldersForGroupwareAccount" type="Bool">
+ <default>false</default>
+ <whatsthis>&lt;p&gt;If the account used for storing groupware information "
+ "is not used to manage normal mail, set this option to make KMail only "
+ "show groupware folders in it. This is useful if you are handling regular "
+ "mail via an additional online IMAP account.&lt;/p&gt;</whatsthis>
+ </entry>
+
+ <entry name="TheIMAPResourceStorageFormat" type="Enum">
+ <whatsthis>&lt;p&gt;Choose the storage format of the groupware folders. &lt;ul&gt;&lt;li&gt;The default format is to use the ical (for calendar folders) and vcard (for addressbook folders) standards. This format makes all Kontact features available.&lt;/li&gt;&lt;li&gt;The Kolab XML format uses a custom model that matches more closely the one used in Outlook. This format gives better Outlook compatibility, when using a Kolab server or a compatible solution.&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;</whatsthis>
+ <choices>
+ <choice name="IcalVcard"/>
+ <choice name="XML"/>
+ </choices>
+ <default>IcalVcard</default>
+ </entry>
+
+ <entry name="TheIMAPResourceFolderParent" type="String">
+ <whatsthis>&lt;p&gt;This chooses the parent of the IMAP resource folders.&lt;/p&gt;&lt;p&gt;By default, the Kolab server sets the IMAP inbox to be the parent.&lt;/p&gt;</whatsthis>
+ <default>inbox</default>
+ </entry>
+
+ <entry name="TheIMAPResourceAccount" type="Int">
+ <whatsthis>&lt;p&gt;This is the ID of the account holding the IMAP resource folders.&lt;/p&gt;</whatsthis>
+ <default></default>
+ </entry>
+
+ <entry name="TheIMAPResourceFolderLanguage" type="Int">
+ <whatsthis>&lt;p&gt;If you want to set the folder names of the IMAP storage to your local language, you can choose between these available languages.&lt;/p&gt;&lt;p&gt; Please note, that the only reason to do so is for compatibility with Microsoft Outlook. It is considered a bad idea to set this, since it makes changing languages impossible. &lt;/p&gt;&lt;p&gt;So do not set this unless you have to.&lt;/p&gt;</whatsthis>
+ <default>0</default>
+ </entry>
+
+ <entry name="FilterOnlyDIMAPInbox" type="Bool">
+ <default>true</default>
+ <label>Only filter mails received in disconnected IMAP inbox.</label>
+ </entry>
+ <entry name="FilterGroupwareFolders" type="Bool">
+ <default>false</default>
+ <label>Also filter new mails received in groupware folders.</label>
+ </entry>
+
+ <entry name="ImmediatlySyncDIMAPOnGroupwareChanges" type="Bool">
+ <default>true</default>
+ <label>Synchronize groupware changes in DIMAP folders immediately when being online.</label>
+ </entry>
+ </group>
+
+ <group name="Internal">
+ <entry name="MsgDictSizeHint" type="Int" hidden="true">
+ <default>9973</default>
+ </entry>
+ <entry name="PreviousNewFeaturesMD5" type="String" hidden="true">
+ <whatsthis>This value is used to decide whether the KMail Introduction should be displayed.</whatsthis>
+ <default></default>
+ </entry>
+ </group>
+
+ <group name="Network">
+ <entry name="MaxConnectionsPerHost" type="Int">
+ <label>Maximal number of connections per host</label>
+ <whatsthis>This can be used to restrict the number of connections per host while checking for new mail. By default the number of connections is unlimited (0).</whatsthis>
+ <default>0</default>
+ <min>0</min>
+ </entry>
+ </group>
+
+ <group name="UserInterface">
+ <entry name="QuickSearchActive" type="Bool">
+ <label>Show quick search line edit</label>
+ <whatsthis>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.</whatsthis>
+ <default>true</default>
+ </entry>
+ <entry name="EnableFolderQuickSearch" type="Bool">
+ <label>Show folder quick search line edit</label>
+ <default>false</default>
+ </entry>
+ <entry name="HideLocalInbox" type="Bool">
+ <label>Hide local inbox if unused</label>
+ <default>true</default>
+ </entry>
+ </group>
+
+ <group name="Composer">
+ <entry name="ForwardingInlineByDefault" type="Bool">
+ <default>false</default>
+ <label>Forward Inline As Default.</label>
+ </entry>
+ <entry name="AllowSemicolonAsAddressSeparator" type="Bool">
+ <default>true</default>
+ <label>Allow the semicolon charactor (';') to be used as separator in the message composer.</label>
+ </entry>
+ <entry name="ForceReplyCharset" type="Bool" key="force-reply-charset">
+ <label>Keep original charset when replying or forwarding if possible</label>
+ <default>false</default>
+ </entry>
+ <entry name="AutoTextSignature" type="String" key="signature">
+ <label>A&amp;utomatically insert signature</label>
+ <default>auto</default>
+ </entry>
+ <entry name="StickyIdentity" type="Bool" key="sticky-identity">
+ <whatsthis>Remember this identity, so that it will be used in future composer windows as well.
+ </whatsthis>
+ <default>false</default>
+ </entry>
+ <entry name="StickyFcc" type="Bool" key="sticky-fcc">
+ <whatsthis>Remember this folder for sent items, so that it will be used in future composer windows as well.</whatsthis>
+ <default>false</default>
+ </entry>
+ <entry name="StickyTransport" type="Bool" key="sticky-transport">
+ <whatsthis>Remember this mail transport, so that it will be used in future composer windows as well.</whatsthis>
+ <default>false</default>
+ </entry>
+ <entry name="WordWrap" type="Bool" key="word-wrap">
+ <label>Word &amp;wrap at column:</label>
+ <default>true</default>
+ </entry>
+ <entry name="UseFixedFont" type="Bool" key="use-fixed-font">
+ <label>Use Fi&amp;xed Font</label>
+ <default>false</default>
+ </entry>
+ <entry name="LineWrapWidth" type="Int" key="break-at">
+ <label></label>
+ <default>78</default>
+ <min>30</min>
+ <max>255</max>
+ </entry>
+ <entry name="PreviousIdentity" type="UInt" key="previous-identity" />
+ <entry name="PreviousFcc" type="String" key="previous-fcc" />
+ <entry name="TransportHistory" type="StringList" key="transport-history" />
+ <entry name="CurrentTransport" type="String" key="current-transport" />
+ <entry name="DefaultTransport" type="String" key="default-transport" />
+ <entry name="MaxTransportEntries" type="Int" key="max-transport-items">
+ <default>10</default>
+ </entry>
+ <entry name="OutlookCompatibleAttachments" type="Bool" key="outlook-compatible-attachments">
+ <label>Outlook-compatible attachment naming</label>
+ <whatsthis>Turn this option on to make Outlook &#8482; understand attachment names containing non-English characters</whatsthis>
+ <default>false</default>
+ </entry>
+ <entry name="UseHtmlMarkup" type="Bool" key="html-markup">
+ <default>false</default>
+ </entry>
+ <entry name="PgpAutoSign" type="Bool" key="pgp-auto-sign">
+ <default>false</default>
+ </entry>
+ <entry name="PgpAutoEncrypt" type="Bool" key="pgp-auto-encrypt">
+ <default>false</default>
+ </entry>
+ <entry name="NeverEncryptDrafts" type="Bool" key="never-encrypt-drafts">
+ <default>true</default>
+ </entry>
+
+ <entry name="ChiasmusKey" type="String" key="chiasmus-key">
+ </entry>
+ <entry name="ChiasmusOptions" type="String" key="chiasmus-options">
+ </entry>
+
+ <entry name="ConfirmBeforeSend" type="Bool" key="confirm-before-send">
+ <label>Confirm &amp;before send</label>
+ <default>false</default>
+ </entry>
+ <entry name="RequestMDN" type="Bool" key="request-mdn">
+ <label>Automatically request &amp;message disposition notifications</label>
+ <whatsthis>&lt;qt&gt;&lt;p&gt;Enable this option if you want KMail to request Message Disposition Notifications (MDNs) for each of your outgoing messages.&lt;/p&gt;&lt;p&gt;This option only affects the default; you can still enable or disable MDN requesting on a per-message basis in the composer, menu item &lt;em&gt;Options&lt;/em&gt;-&gt;&lt;em&gt;Request Disposition Notification&lt;/em&gt;.&lt;/p&gt;&lt;/qt&gt;</whatsthis>
+ <default>false</default>
+ </entry>
+ <entry name="ShowRecentAddressesInComposer" type="Bool" key="showRecentAddressesInComposer">
+ <label>Use recent addresses for autocompletion</label>
+ <whatsthis>Disable this option if you do not want recently used addresses to appear in the autocompletion list in the composer's address fields.</whatsthis>
+ <default>true</default>
+ </entry>
+ <entry name="Headers" type="Int" key="headers">
+ <default>HDR_STANDARD</default>
+ </entry>
+ <entry name="CompletionMode" type="Int" key="Completion Mode">
+ <default code="true">KGlobalSettings::completionMode()</default>
+ </entry>
+ <entry name="AutoSpellChecking" type="Bool" key="autoSpellChecking">
+ <default>true</default>
+ </entry>
+ <entry name="ShowForgottenAttachmentWarning" type="Bool" key="showForgottenAttachmentWarning">
+ <default>true</default>
+ </entry>
+ <entry name="AttachmentKeywords" type="StringList" key="attachment-keywords">
+ </entry>
+ <entry name="ShowMessagePartDialogOnAttach" type="Bool" key="showMessagePartDialogOnAttach">
+ <default>false</default>
+ </entry>
+ <entry name="AutosaveInterval" type="Int" key="autosave">
+ <label>Autosave interval:</label>
+ <whatsthis>A backup copy of the text in the composer window can be created regularly. The interval used to create the backups is set here. You can disable autosaving by setting it to the value 0.</whatsthis>
+ <default>2</default>
+ </entry>
+ <entry name="PrependSignature" type="Bool" key="prepend-signature">
+ <label>Insert signatures above quoted text</label>
+ <default>false</default>
+ </entry>
+ <entry name="ReplyPrefixes" type="StringList" key="reply-prefixes">
+ <default>Re\\s*:,Re\\[\\d+\\]:,Re\\d+:</default>
+ </entry>
+ <entry name="ReplaceReplyPrefix" type="Bool" key="replace-reply-prefix">
+ <label>Replace recognized prefi&amp;x with "Re:"</label>
+ <default>true</default>
+ </entry>
+ <entry name="ForwardPrefixes" type="StringList" key="forward-prefixes">
+ <default>Fwd:,FW:</default>
+ </entry>
+ <entry name="ReplaceForwardPrefix" type="Bool" key="replace-forward-prefix">
+ <label>Replace recognized prefix with "&amp;Fwd:"</label>
+ <default>true</default>
+ </entry>
+ <entry name="SmartQuote" type="Bool" key="smart-quote">
+ <label>Use smart &amp;quoting</label>
+ <default>true</default>
+ </entry>
+
+ <entry name="AddresseeSelectorType" type="Enum">
+ <label>Type of addressee selector</label>
+ <whatsthis>Sets the type of the dialog for selecting recipients for To,
+ CC and BCC.</whatsthis>
+ <choices>
+ <choice name="New"/>
+ <choice name="Old"/>
+ </choices>
+ <default>Old</default>
+ </entry>
+ <entry name="RecipientsEditorType" type="Enum">
+ <label>Type of recipients editor</label>
+ <whatsthis>Sets the type of the recipients editor for editing To,
+ CC and BCC.</whatsthis>
+ <choices>
+ <choice name="Classic"/>
+ <choice name="MultiLine"/>
+ </choices>
+ <default>MultiLine</default>
+ </entry>
+ <entry name="SecondRecipientTypeDefault" type="Enum">
+ <choices>
+ <choice name="To"/>
+ <choice name="Cc"/>
+ </choices>
+ <default>To</default>
+ </entry>
+ <entry name="MaximumRecipients" type="Int">
+ <label>Maximum number of recipient editor lines.</label>
+ <default>200</default>
+ </entry>
+ <entry name="CustomTemplates" type="StringList" key="CustomTemplates" />
+
+ <entry name="MimetypesToStripWhenInlineForwarding" type="StringList">
+ <label>List of message part types to strip off mails that are being forwarded inline.</label>
+ </entry>
+
+ <entry name="MaximumAttachmentSize" type="Int">
+ <label>The maximum size in MB that email attachments are allowed to have.</label>
+ <default>50</default>
+ </entry>
+
+ <entry name="ShowSnippetManager" type="Bool">
+ <label>Show the Text Snippet Management and Insertion Panel in the composer.</label>
+ <default>false</default>
+ </entry>
+
+ <entry name="SnippetSplitterPosition" type="IntList"/>
+
+ <entry name="ShowGnuPGAuditLogAfterSuccessfulSignEncrypt" type="Bool">
+ <label>Show the GnuPG Audit Log even after crypto operations that completed successfully.</label>
+ <default>false</default>
+ </entry>
+
+ </group>
+<!-- Composer -->
+
+ <group name="Fonts">
+ <entry name="UseDefaultFonts" type="Bool" key="defaultFonts">
+ <default>true</default>
+ </entry>
+ <entry name="ComposerFont" type="Font" key="composer-font">
+ <default code="true">KGlobalSettings::generalFont()</default>
+ </entry>
+ <entry name="FixedFont" type="Font" key="fixed-font">
+ <default code="true">KGlobalSettings::fixedFont()</default>
+ </entry>
+
+ </group>
+
+ <group name="Geometry">
+ <entry name="ComposerSize" type="Size" key="composer">
+ <default>QSize(480,510)</default>
+ </entry>
+
+ </group>
+
+ <group name="Reader">
+ <entry name="UseDefaultColors" type="Bool" key="defaultColors">
+ <default>true</default>
+ </entry>
+ <entry name="ForegroundColor" type="Color" key="ForegroundColor">
+ <default code="true">kapp->palette().active().text()</default>
+ </entry>
+ <entry name="BackgroundColor" type="Color" key="BackgroundColor">
+ <default code="true">kapp->palette().active().base()</default>
+ </entry>
+ <entry name="FallbackCharacterEncoding" type="String" key="FallbackCharacterEncoding">
+ <default code="true"> QCString(QTextCodec::codecForLocale()->name()).lower() == "eucjp"? QCString("jis7") : QCString(QTextCodec::codecForLocale()->name()).lower()</default>
+ <label></label>
+ <whatsthis>Some emails, especially those generated automatically, do not specify the character encoding which needs to be used to properly display them. In such cases a fallback character encoding will be used, which you can configure here. Set it to the character encoding most commonly used in your part of the world. As a default the encoding configured for the whole system is used.</whatsthis>
+ </entry>
+
+ <entry name="OverrideCharacterEncoding" type="String" key="encoding">
+ <default></default>
+ <label></label>
+ <whatsthis>Changing this from its default 'Auto' will force the use of the specified encoding for all emails, regardless of what they specify themselves.</whatsthis>
+ </entry>
+
+ <entry name="ShowEmoticons" type="Bool">
+ <default>true</default>
+ <label>Replace smileys by emoticons</label>
+ <whatsthis>Enable this if you want smileys like :-) appearing in the message text to be replaced by emoticons (small pictures).</whatsthis>
+ </entry>
+ <entry name="ShowExpandQuotesMark" type="Bool">
+ <default>false</default>
+ <label>Show expand/collapse quote marks</label>
+ <whatsthis>Enable this option to show different levels of quoted text. Disable to hide the levels of quoted text.</whatsthis>
+ </entry>
+ <entry name="CollapseQuoteLevelSpin" type="Int" >
+ <label>Automatic collapse level:</label>
+ <default>3</default>
+ <min>0</min>
+ <max>10</max>
+ </entry>
+
+ <entry name="ShrinkQuotes" type="Bool">
+ <default>false</default>
+ <label>Reduce font size for quoted text</label>
+ <whatsthis>Enable this option to show quoted text with a smaller font.</whatsthis>
+ </entry>
+
+ <entry name="ChiasmusDecryptionKey" type="String">
+ </entry>
+
+ <entry name="ChiasmusDecryptionOptions" type="String">
+ </entry>
+
+ <entry name="ShowUserAgent" type="Bool">
+ <default>false</default>
+ <label>Show user agent in fancy headers</label>
+ <whatsthis>Enable this option to get the User-Agent and X-Mailer header lines displayed when using fancy headers.</whatsthis>
+ </entry>
+
+ <entry name="AllowAttachmentDeletion" type="Bool">
+ <default>true</default>
+ <label>Allow to delete attachments of existing mails.</label>
+ </entry>
+ <entry name="AllowAttachmentEditing" type="Bool">
+ <default>false</default>
+ <label>Allow to edit attachments of existing mails.</label>
+ </entry>
+
+ <entry name="AlwaysDecrypt" type="Bool">
+ <default>false</default>
+ <label>Always decrypt messages when viewing or ask befor decrypting</label>
+ </entry>
+ </group>
+
+ <group name="TextIndex">
+ <entry name="automaticDecrypt" type="Bool" key="automaticDecrypt">
+ <default>true</default>
+ <label></label>
+ </entry>
+ </group>
+
+ <group name="MDN">
+ <entry name="SendMDNsWithEmptySender" type="Bool">
+ <default>false</default>
+ <label>Send Message Disposition Notifications with an empty sender.</label>
+ <whatsthis>Send Message Disposition Notifications with an empty sender string. Some servers might be configure to reject such messages, so if you are experiencing problems sending MDNs, uncheck this option.</whatsthis>
+ </entry>
+ </group>
+
+ <group name="GlobalTemplates">
+ <entry name="PhrasesConverted" type="Bool" key="PhrasesConverted">
+ <label>Phrases has been converted to templates</label>
+ <whatsthis>Old phrases have been converted to templates</whatsthis>
+ <default>false</default>
+ </entry>
+ <entry name="TemplateNewMessage" type="String" key="TemplateNewMessage">
+ <label>Message template for new message</label>
+ <whatsthis></whatsthis>
+ <default code="true">TemplatesConfiguration::defaultNewMessage()</default>
+ </entry>
+ <entry name="TemplateReply" type="String" key="TemplateReply">
+ <label>Message template for reply</label>
+ <whatsthis></whatsthis>
+ <default code="true">TemplatesConfiguration::defaultReply()</default>
+ </entry>
+ <entry name="TemplateReplyAll" type="String" key="TemplateReplyAll">
+ <label>Message template for reply to all</label>
+ <whatsthis></whatsthis>
+ <default code="true">TemplatesConfiguration::defaultReplyAll()</default>
+ </entry>
+ <entry name="TemplateForward" type="String" key="TemplateForward">
+ <label>Message template for forward</label>
+ <whatsthis></whatsthis>
+ <default code="true">TemplatesConfiguration::defaultForward()</default>
+ </entry>
+ <entry name="QuoteString" type="String" key="QuoteString">
+ <label>Quote characters</label>
+ <whatsthis></whatsthis>
+ <default code="true">TemplatesConfiguration::defaultQuoteString()</default>
+ </entry>
+ </group>
+
+ <group name="OutOfOffice">
+ <entry name="AllowOutOfOfficeSettings" type="Bool">
+ <default>true</default>
+ <label>Allow out-of-office settings to to be changeable by the user.</label>
+ </entry>
+ <entry name="AllowOutOfOfficeUploadButNoSettings" type="Bool">
+ <default>false</default>
+ <label>Allow users to upload out-of-office sieve scripts, but disallow them changin any settings, such as the domain to react to and the spam reaction switch.</label>
+ </entry>
+ <entry name="OutOfOfficeDomain" type="String">
+ <default></default>
+ <label>Send out-of-office replies to mails coming from this domain only.</label>
+ </entry>
+ <entry name="OutOfOfficeReactToSpam" type="Bool">
+ <default>false</default>
+ <label>Allow out-of-office replies to be sent to messages marked as SPAM.</label>
+ </entry>
+ <entry name="CheckOutOfOfficeOnStartup" type="Bool">
+ <default>true</default>
+ <label>Check if there is still an active out-of-office reply configured when starting KMail.</label>
+ </entry>
+ </group>
+
+ <group name="FavoriteFolderView">
+ <entry name="EnableFavoriteFolderView" type="Bool">
+ <default>true</default>
+ </entry>
+ <entry name="FolderViewSplitterPosition" type="IntList"/>
+ <entry name="FavoriteFolderIds" type="IntList"/>
+ <entry name="FavoriteFolderNames" type="StringList"/>
+ <entry name="FavoriteFolderViewSeenInboxes" type="IntList"/>
+ </group>
+
+</kcfg>
diff --git a/kmail/kmail.upd b/kmail/kmail.upd
new file mode 100644
index 00000000..698cf87b
--- /dev/null
+++ b/kmail/kmail.upd
@@ -0,0 +1,179 @@
+# Update for transport configuration
+Id=1
+File=kmailrc
+Group=sending mail,Transport 1
+Script=upgrade-transport.pl,perl
+# Fix spelling of QuoutedText[123]
+Id=4
+File=kmailrc,kmailrc
+Group=Reader,Reader
+Key=QuoutedText1,QuotedText1
+Key=QuoutedText2,QuotedText2
+Key=QuoutedText3,QuotedText3
+# Remove obsolete PGP config entries
+Id=5
+File=kmailrc
+Group=Reader
+Key=PGPMessageOK,PGPMessageOkKeyOk
+Options=copy
+Key=PGPMessageWarn,PGPMessageOkKeyBad
+RemoveGroup=Kpgp
+File=kpgprc
+Group=<default>
+RemoveKey=user
+# Default to KAddressbook
+Id=6
+File=kmailrc
+Group=General
+RemoveKey=addressbook
+RemoveGroup=Addressbook
+# createOwnMessageIdHeaders -> useCustomMessageIdSuffix
+Id=7
+File=kmailrc
+Group=General
+Key=createOwnMessageIdHeaders,useCustomMessageIdSuffix
+# Upgrade "PGP Identity" (KDE 2.2.x) -> "Default PGP Key" (KDE 3.0)
+Id=8
+File=kmailrc
+Script=kmail-pgpidentity.pl,perl
+#
+# KDE 3.0 released
+#
+# Uppgrade the signature config options (where a trailing pipe (|) meant
+# to interpret the filename as a command (KDE 3.0 -> KDE 3.1)
+Id=9
+File=kmailrc
+Script=upgrade-signature.pl,perl
+# rename the config groups associated with identities.
+# get rid of Identity/IdentityList and addd group [IdentityManager] with
+# keys Idenities (number of identities) and "Default Identity" (3.0->3.1):
+Id=3.1-update-identities
+File=kmailrc
+Script=kmail-upd-identities.pl,perl
+# identify identities by UOID, not name...
+Id=3.1-use-identity-uoids
+File=kmailrc
+Script=kmail-3.1-use-UOID-for-identities.pl,perl
+# update new mail notification settings
+Id=3.1-new-mail-notification
+File=kmailrc,kmail.eventsrc
+Group=General,new-mail-arrived
+Options=overwrite
+Script=kmail-3.1-update-new-mail-notification-settings.pl,perl
+#
+# KDE 3.1 released
+#
+Id=3.2-update-loop-on-goto-unread-settings
+File=kmailrc
+Group=Behaviour
+Key=LoopOnGotoUnread
+Script=kmail-3.2-update-loop-on-goto-unread-settings.sh,bash
+# due to a bug the first identity always had UOID 0, assign another UOID
+Id=3.1.4-dont-use-UOID-0-for-any-identity
+File=kmailrc
+Script=kmail-3.1.4-dont-use-UOID-0-for-any-identity.pl,perl
+Id=3.2-misc
+########## W A R N I N G #### W A R N I N G #### W A R N I N G #######
+# collection of small adjustments. Since updates will be added to this
+# script during the pre-3.2 development, HEAD users should regularly
+# remove the "3.2-{misc,moves}-xx" keys from their kmailrc and kconf_updaterc.
+# For this to work, the script MUST be idempotent, ie. be robust
+# against being run multiple times.
+######################################################################
+File=kmailrc
+Script=kmail-3.2-misc.sh,bash
+#
+# reuse this section to add trivial stuff!
+#
+Id=3.2-moves
+File=kmailrc
+Group=Geometry,Reader
+Key=MimePaneHeight
+#
+Id=3.3-use-ID-for-accounts
+File=kmailrc
+Script=kmail-3.3-use-ID-for-accounts.pl,perl
+#
+Id=3.3-update-filter-rules
+File=kmailrc
+Script=kmail-3.3-update-filter-rules.pl,perl
+#
+Id=3.3-move-identities-to-own-file
+File=kmailrc,emailidentities
+Script=kmail-3.3-move-identities.pl,perl
+#
+# aegypten: moving options from kpgprc to kmailrc
+#
+Id=3.3-aegypten-kpgprc-to-kmailrc
+File=kpgprc,kmailrc
+Group=,Composer
+Key=showEncryptionResult,crypto-show-encryption-result
+Key=encryptToSelf,crypto-encrypt-to-self
+Key=showKeysForApproval,crypto-show-keys-for-approval
+
+Id=3.3-aegypten-kpgprc-to-libkleopatrarc
+File=kpgprc,libkleopatrarc
+Group=,Backends
+Script=kmail-3.3-aegypten.pl,perl
+
+Id=3.3-aegypten-emailidentities-split-sign-encr-keys
+File=emailidentities
+Script=kmail-3.3-split-sign-encr-keys.sh
+#
+Id=3.3-misc
+########## W A R N I N G #### W A R N I N G #### W A R N I N G #######
+# collection of small adjustments. Since updates will be added to this
+# script during the pre-3.3 development, HEAD users should regularly
+# remove the "3.3-{misc,moves}-xx" keys from their kmailrc and kconf_updaterc.
+# For this to work, the script MUST be idempotent, ie. be robust
+# against being run multiple times.
+######################################################################
+File=kmailrc
+Script=kmail-3.3-misc.pl,perl
+#
+# Updates which were added after the release of KDE 3.3 Beta 1
+Id=3.3b1-misc
+File=kmailrc
+Script=kmail-3.3b1-misc.pl,perl
+# Updates which were added after the release of KDE 3.3
+Id=3.4
+File=kmailrc
+Script=kmail-3.4-misc.pl,perl
+# Remove the MenuBar key so that menu bar will be visible in the separate reader window
+Id=3.4a
+File=kmailrc
+Group=Separate Reader Window
+RemoveKey=MenuBar
+# Use the normal message list font for the new fonts for important/new/unread
+Id=3.4b
+File=kmailrc
+Group=Fonts
+Options=copy
+Key=list-font,list-important-font
+Options=copy
+Key=list-font,list-new-font
+Options=copy
+Key=list-font,list-unread-font
+# Convert status filter rules to upper case (cf. bug #101001)
+Id=3.4.1
+File=kmailrc
+Script=kmail-3.4.1-update-status-filters.pl,perl
+# Remove the stored size so that we get a reasonable default now that we have 2 columns in the folder selection dialog
+Id=3.5.4
+File=kmailrc
+Group=FolderSelectionDialog
+RemoveKey=Size
+# Trigger migration of all local imap flags to the server
+# increment suffix every time you need to trigger that
+Id=3.5.7-imap-flag-migration-2
+Options=overwrite
+File=kmailrc
+Script=kmail-3.5-trigger-flag-migration.pl,perl
+#
+# Important notice:
+# If you add updates here, keep this text below them.
+# Make sure your updates can be safely run twice and add any new Id
+# to the QStringList in kmail/kmstartup.cpp:checkConfigUpdates().
+# Collect smaller stuff in kmail-<version>-misc and <version>-moves!
+# (mmutz)
+#
diff --git a/kmail/kmailIface.h b/kmail/kmailIface.h
new file mode 100644
index 00000000..1a4a04f2
--- /dev/null
+++ b/kmail/kmailIface.h
@@ -0,0 +1,219 @@
+#ifndef _KMCONTROLIFACE
+#define _KMCONTROLIFACE
+
+// no forward declarations - dcopidl2cpp won't work
+#include <dcopobject.h>
+#include <dcopref.h>
+#include <kurl.h>
+#include <qstringlist.h>
+
+/** checkMail wont show reader but will check mail. use openReader to
+ show if you give a filename to openReader it will show mbox or
+ message if it is valid rfc-822 message or mbox file. You can pass
+ hidden=1 to openComposer and it wont be visible that way you can
+ write messages and add attachments from other apps and send it via
+ kmail. Should I add showAddressBook? hmm... The openComposer
+ functions always return 1. sven <radej@kde.org> */
+class KMailIface : virtual public DCOPObject
+{
+ K_DCOP
+
+k_dcop:
+ virtual void checkMail() = 0;
+ virtual QStringList accounts() = 0;
+ virtual void checkAccount(const QString &account) = 0;
+ virtual void openReader() = 0;
+ virtual int openComposer(const QString &to, const QString &cc,
+ const QString &bcc, const QString &subject,
+ const QString &body, int hidden,
+ const KURL &messageFile) = 0;
+ virtual int openComposer(const QString &to, const QString &cc,
+ const QString &bcc, const QString &subject,
+ const QString &body, int hidden,
+ const KURL &messageFile,
+ const KURL &attachURL) = 0;
+ virtual int openComposer(const QString &to, const QString &cc,
+ const QString &bcc, const QString &subject,
+ const QString &body, int hidden,
+ const KURL &messageFile,
+ const KURL::List &attachURLs) = 0;
+ virtual int openComposer (const QString &to, const QString &cc,
+ const QString &bcc, const QString &subject,
+ const QString &body, int hidden,
+ const QString &attachName,
+ const QCString &attachCte,
+ const QCString &attachData,
+ const QCString &attachType,
+ const QCString &attachSubType,
+ const QCString &attachParamAttr,
+ const QString &attachParamValue,
+ const QCString &attachContDisp) = 0;
+ virtual int openComposer (const QString &to, const QString &cc,
+ const QString &bcc, const QString &subject,
+ const QString &body, int hidden,
+ const QString &attachName,
+ const QCString &attachCte,
+ const QCString &attachData,
+ const QCString &attachType,
+ const QCString &attachSubType,
+ const QCString &attachParamAttr,
+ const QString &attachParamValue,
+ const QCString &attachContDisp,
+ const QCString &attachCharset) = 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
+ order to actually send the mail. */
+ virtual DCOPRef openComposer(const QString &to, const QString &cc,
+ const QString &bcc, const QString &subject,
+ const QString &body, bool hidden) = 0;
+
+ /**
+ Send a certificate request to the CA specified in \a to. The
+ certificate is stored in the byte array \a certData. It needs
+ to stored according to BER and PKCS#10.
+ This method will set content type encoding, mime types, etc. as
+ per the MailTT specification.
+ */
+ virtual int sendCertificate( const QString& to,
+ const QByteArray& certData ) = 0;
+
+
+ virtual void compactAllFolders() = 0;
+
+ /** @param foldername the requested foldername in kmail (at the
+ zero level in the foldertree.
+ @param messagefile the name of the filename (local) with the
+ message to be added.
+ @param MsgStatusFlags a string coding the status of the message
+ with a char for each status e.g. a 'N' for new
+ this param is optional
+ @return =1, message added to folder, if folder doesn't exist, folder
+ has been created.
+ =0, an error occurred.
+ =-1, couldn't create folder and it didn't exist
+ =-2, couldn't read messageFile.
+ =-3, Can't allocate memory.
+ =-4, Message already exists in folder.
+ */
+ virtual int dcopAddMessage(const QString & foldername,
+ const QString & messagefile,
+ const QString & MsgStatusFlags = QString()) = 0;
+ virtual int dcopAddMessage(const QString & foldername,
+ const KURL & messagefile,
+ const QString & MsgStatusFlags = QString()) = 0;
+
+ virtual QStringList folderList() const =0;
+ virtual DCOPRef getFolder( const QString& vpath ) =0;
+ virtual void selectFolder( QString folder ) =0;
+ virtual bool canQueryClose() =0;
+
+ /**
+ * Set the KMail Default transport.
+ * @param transport the name of the transport as defined in the sending
+ accounts configuration.
+ */
+ virtual void setDefaultTransport( const QString & transport ) =0;
+
+ virtual int timeOfLastMessageCountChange() const =0;
+
+ /**
+ * Abort any running compaction/expiry, and don't launch any new ones
+ * until resumeBackgroundJobs() is called
+ */
+ virtual void pauseBackgroundJobs() = 0;
+
+ /**
+ * Resume compaction/expiry, and allow launching new ones
+ */
+ virtual void resumeBackgroundJobs() = 0;
+
+ /**
+ * Stop all network related jobs and enter offline mode
+ */
+ virtual void stopNetworkJobs() = 0;
+
+ /**
+ * Resume all network related jobs and enter online mode
+ */
+ virtual void resumeNetworkJobs() = 0;
+
+k_dcop_signals:
+ void unreadCountChanged();
+
+ void unreadCountChanged( const QString& folderURL, int numUnread );
+
+k_dcop_hidden:
+ /** DCOP call which is used by the Kontact plugin to create a new message.
+ *
+ * @TODO Rename to newMessageInternal()
+ * @TODO Merge this and the various openComposer methods for better code reuse
+ */
+ virtual DCOPRef newMessage(const QString &to,
+ const QString &cc,
+ const QString& bcc,
+ bool hidden,
+ bool useFolderId,
+ const KURL &messageFile,
+ const KURL &attachURL) = 0;
+
+ /** Shows the specified message in a separate message window.
+ @param serialNumber the serial number of the message to be shown.
+ @param messageId this parameter is ignored.
+ */
+ /* @TODO Get rid of the messageId parameter. */
+ virtual bool showMail( Q_UINT32 serialNumber, QString messageId ) = 0;
+
+ /**
+ * DCOP-enabled for KMailUniqueAppHandler in the kontact plugin
+ * @param noArgsOpensReader true in the kmail process, meaning that launching "kmail"
+ * will open a reader window or bring to front an existing one.
+ * noArgsOpensReader is false when this is called from kontact, so that typing
+ * "kmail" doesn't open a window.
+ * Returns true if the command line was handled, false if it was empty and
+ * not handled (due to noArgsOpensReader==false).
+ */
+ virtual bool handleCommandLine( bool noArgsOpensReader ) = 0;
+ virtual bool firstStart() = 0;
+ /**
+ *
+ * DCOP-enabled for use in kaddressbook drop
+ */
+ virtual QString getFrom( Q_UINT32 serialNumber ) = 0;
+ virtual QString debugScheduler() = 0;
+ virtual QString debugSernum( Q_UINT32 serialNumber ) = 0;
+
+ /** Does essentially the same as dcopAddMessage except that it doesn't reject
+ duplicate messages.
+
+ @param foldername the requested foldername in kmail (at the
+ zero level in the foldertree.
+ @param messagefile: the name of the filename (local) with the
+ message to be added.
+ @param MsgStatusFlags a string coding the status of the message
+ with a char for each status e.g. a 'N' for new
+ this param is optional
+ @return =1, message added to folder, if folder doesn't exist, folder
+ has been created.
+ =0, an error occurred.
+ =-1, couldn't create folder and it didn't exist
+ =-2, couldn't read messageFile.
+ =-3, Can't allocate memory.
+ =-4, Message already exists in folder.
+ */
+ virtual int dcopAddMessage_fastImport(const QString & foldername,
+ const QString & messagefile,
+ const QString & MsgStatusFlags = QString()) = 0;
+ virtual int dcopAddMessage_fastImport(const QString & foldername,
+ const KURL & messagefile,
+ const QString & MsgStatusFlags = QString()) = 0;
+
+ /** Clears the list of added message ids which is used to filter out
+ duplicates. */
+ virtual void dcopResetAddMessage() = 0;
+
+ virtual void loadProfile( const QString& path ) = 0;
+ virtual void saveToProfile( const QString& path ) const = 0;
+};
+
+#endif
diff --git a/kmail/kmail_config_accounts.desktop b/kmail/kmail_config_accounts.desktop
new file mode 100644
index 00000000..4c2c9f92
--- /dev/null
+++ b/kmail/kmail_config_accounts.desktop
@@ -0,0 +1,162 @@
+[Desktop Entry]
+Icon=network
+Type=Service
+ServiceTypes=KCModule
+
+X-KDE-ModuleType=Library
+X-KDE-Library=kmail
+X-KDE-FactoryName=kmail_config_accounts
+X-KDE-HasReadOnlyMode=false
+X-KDE-ParentApp=kmail
+X-KDE-ParentComponents=kmail,kontact_kmailplugin
+X-KDE-CfgDlgHierarchy=KMail
+
+Name=Accounts
+Name[af]=Rekeninge
+Name[ar]=الحسابات
+Name[be]=Рахункі
+Name[bg]=Сметки
+Name[br]=Kontoù
+Name[bs]=Računi
+Name[ca]=Comptes
+Name[cs]=Účty
+Name[cy]=Cyfrifon
+Name[da]=Konti
+Name[de]=Zugänge
+Name[el]=Λογαριασμοί
+Name[eo]=Kontoj
+Name[es]=Cuentas
+Name[et]=Kontod
+Name[eu]=Kontuak
+Name[fa]=حسابها
+Name[fi]=Tilit
+Name[fr]=Comptes
+Name[fy]=Akkounts
+Name[ga]=Cuntais
+Name[gl]=Contas
+Name[he]=חשבונות
+Name[hu]=Fiókok
+Name[is]=Tengingar
+Name[it]=Account
+Name[ja]=アカウント
+Name[ka]=ანგარიშები
+Name[kk]=Тіркелгілері
+Name[km]=គណនី
+Name[lt]=Paskyros
+Name[mk]=Сметки
+Name[ms]=Akaun
+Name[nb]=Kontoer
+Name[nds]=Kontos
+Name[ne]=खाता
+Name[nn]=Kontoar
+Name[pa]=ਖਾਤੇ
+Name[pl]=Konta
+Name[pt]=Contas
+Name[pt_BR]=Contas
+Name[ru]=Учётные записи
+Name[se]=Kontut
+Name[sk]=Účty
+Name[sl]=Računi
+Name[sr]=Налози
+Name[sr@Latn]=Nalozi
+Name[sv]=Konton
+Name[ta]=கணக்குகள்
+Name[tg]=Қайдҳои баҳисобгирӣ
+Name[tr]=Hesaplar
+Name[uk]=Рахунки
+Name[uz]=Hisoblar
+Name[uz@cyrillic]=Ҳисоблар
+Name[zh_CN]=账户
+Name[zh_TW]=帳號
+Comment=Setup for Sending and Receiving Messages
+Comment[af]=Opstel vir die stuur en ontvang van boodskappe
+Comment[ar]=تعيينات إرسال و إستقبال الرسائل
+Comment[bg]=Настройки на мрежата, сървърите и сметките
+Comment[bs]=Postavke za slanje i prijem poruka
+Comment[ca]=Configuració per enviar i rebre missatges
+Comment[cs]=Nastavení odesílání a přijímání zpráv
+Comment[da]=Opsætning til at sende og modtage breve
+Comment[de]=Einstellungen zum Senden und Empfangen von Nachrichten
+Comment[el]=Ρυθμίσεις για αποστολή και λήψη μηνυμάτων
+Comment[eo]=Agordo por sendi kaj ricevi mesaĝojn
+Comment[es]=Configuración para enviar y recibir mensajes
+Comment[et]=Kirjade saatmise ja saamise seadistused
+Comment[eu]=Mezuak bidali eta jasotzeko konfigurazioa
+Comment[fa]=برپایی برای ارسال و دریافت پیامها
+Comment[fi]=Viestien lähetys- ja vastaanottoasetukset
+Comment[fr]=Configuration de l'envoi et de la réception de messages
+Comment[fy]=Ynstellings foar it ferstjoeren en ûntfangen fan berjochten
+Comment[gl]=Configuración para Enviar e Recibir Mensaxes
+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
+Comment[mk]=Конфигурација за примање и испраќање пораки
+Comment[ms]=Setup untuk Menghantar dan Menerima Mesej
+Comment[nb]=Oppset for mottak og sending av meldinger
+Comment[nds]=Instellen för dat Sennen un Kriegen vun Narichten
+Comment[ne]=सन्देश पठाउन वा प्राप्त गर्न सेटअप गर्नुहोस्
+Comment[nl]=Instellingen voor het verzenden en ontvangen van berichten
+Comment[nn]=Oppsett av sending og mottak av meldingar
+Comment[pl]=Ustawienia wysyłania i odbierania wiadomości
+Comment[pt]=Configuração de Envio e Recepção de Mensagens
+Comment[pt_BR]=Configura o Envio e Recebimento de Mensagens
+Comment[ru]=Настройка отправки и получения сообщений
+Comment[se]=Heivet e-reivviid sáddema ja vuostáiváldima
+Comment[sk]=Nastavenie príjmu a odosielania správ
+Comment[sl]=Nastavitve za pošiljanje in prejemanje sporočil
+Comment[sr]=Подешавање слања и примања порука
+Comment[sr@Latn]=Podešavanje slanja i primanja poruka
+Comment[sv]=Inställningar för att skicka och ta emot brev
+Comment[ta]=அனுப்புதல் மற்றும் பெறுதல் தகவல்களுக்கான அமைப்பு
+Comment[tg]=Танзимоти фиристодан ва қабул кардани иттилоот
+Comment[tr]=Gönderilen ve Gelen Mesajlar için Yapılandırma
+Comment[uk]=Налаштування для надсилання і отримання повідомлень
+Comment[zh_CN]=收发邮件设置
+Comment[zh_TW]=發送與接收信件設定
+Keywords=kmail,accounts
+Keywords[bg]=пощенски, клиент, е-поща, kmail, accounts
+Keywords[br]=kmail,kontoù
+Keywords[ca]=kmail,comptes
+Keywords[cs]=kmail,účty
+Keywords[da]=kmail,konti
+Keywords[de]=KMail,Konten,Zugänge
+Keywords[el]=kmail,λογαριασμοί
+Keywords[es]=kmail,cuentas
+Keywords[et]=kmail,kontod
+Keywords[eu]=kmail,kontuak
+Keywords[fa]=kmail، حسابها
+Keywords[fi]=kmail,tilit
+Keywords[fr]=KMail,comptes
+Keywords[fy]=kmail,accounts,akkounts
+Keywords[ga]=kmail,cuntais
+Keywords[gl]=kmail,contas
+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,кпошта,сметка,сметки
+Keywords[ms]=kmail,akaun
+Keywords[nb]=kmail,kontoer
+Keywords[nds]=KMail,Kontos
+Keywords[ne]=केडीई मेल,खाता
+Keywords[nn]=KMail,kontoar
+Keywords[pl]=kmail,konta
+Keywords[pt]=kmail,contas
+Keywords[pt_BR]=kmail,contas
+Keywords[ru]=kmail,accounts,почта,учётные записи
+Keywords[sl]=kmail,računi
+Keywords[sr]=kmail,accounts,састављач,налози
+Keywords[sr@Latn]=kmail,accounts,sastavljač,nalozi
+Keywords[sv]=kmail,konton
+Keywords[ta]=கேஅஞ்சல்,கணக்குகள்
+Keywords[tr]=kmail,hesplar
+Keywords[uk]=kmail,accounts,пошта,рахунки
+Keywords[zh_CN]=kmail,account,账户
diff --git a/kmail/kmail_config_appearance.desktop b/kmail/kmail_config_appearance.desktop
new file mode 100644
index 00000000..09df7ea4
--- /dev/null
+++ b/kmail/kmail_config_appearance.desktop
@@ -0,0 +1,169 @@
+[Desktop Entry]
+Icon=looknfeel
+Type=Service
+ServiceTypes=KCModule
+
+X-KDE-ModuleType=Library
+X-KDE-Library=kmail
+X-KDE-FactoryName=kmail_config_appearance
+X-KDE-HasReadOnlyMode=false
+X-KDE-ParentApp=kmail
+X-KDE-ParentComponents=kmail,kontact_kmailplugin
+X-KDE-CfgDlgHierarchy=KMail
+
+Name=Appearance
+Name[ar]=المظهر
+Name[be]=Знешні выгляд
+Name[bg]=Външен вид
+Name[br]=Neuziadur
+Name[bs]=Izgled
+Name[ca]=Aparença
+Name[cs]=Vzhled
+Name[cy]=Golwg
+Name[da]=Udseende
+Name[de]=Erscheinungsbild
+Name[el]=Εμφάνιση
+Name[eo]=Prezentado
+Name[es]=Apariencia
+Name[et]=Välimus
+Name[eu]=Itxura
+Name[fa]=ظاهر
+Name[fi]=Ulkoasu
+Name[fr]=Apparence
+Name[fy]=Uterlik
+Name[ga]=Cuma
+Name[gl]=Apariencia
+Name[he]=מראה
+Name[hr]=Izgled
+Name[hu]=Megjelenés
+Name[is]=Útlit
+Name[it]=Aspetto
+Name[ja]=外観
+Name[ka]=იერსახე
+Name[kk]=Сыртқы көрінісі
+Name[km]=រូបរាង
+Name[ko]=모양
+Name[lt]=Išvaizda
+Name[mk]=Изглед
+Name[ms]=Rupa
+Name[nb]=Utseende
+Name[nds]=Utsehn
+Name[ne]=मोहडा
+Name[nl]=Uiterlijk
+Name[nn]=Utsjånad
+Name[pl]=Wygląd
+Name[pt]=Aparência
+Name[pt_BR]=Aparência
+Name[ru]=Внешний вид
+Name[rw]=Imigaragarire
+Name[se]=Fárda
+Name[sk]=Vzhľad
+Name[sl]=Videz
+Name[sr]=Изглед
+Name[sr@Latn]=Izgled
+Name[sv]=Uppträdande
+Name[ta]=தோற்றம்
+Name[tg]=Намуди зоҳирӣ
+Name[tr]=Görünüm
+Name[uk]=Вигляд
+Name[uz]=Koʻrinishi
+Name[uz@cyrillic]=Кўриниши
+Name[zh_CN]=外观
+Comment=Customize Visual Appearance
+Comment[af]=Pasmaak die visuele voorkoms
+Comment[bg]=Настройки на външния вид
+Comment[bs]=Prilagodi izgled
+Comment[ca]=Personalitza l'aparença visual
+Comment[cs]=Nastavení vzhledu
+Comment[cy]=Addasu Golwg
+Comment[da]=Brugerindstil visuelt udseende
+Comment[de]=Erscheinungsbild anpassen
+Comment[el]=Προσαρμογή εμφάνισης
+Comment[en_GB]=Customise Visual Appearance
+Comment[es]=Apariencia visual personalizada
+Comment[et]=Välimuse seadistused
+Comment[eu]=Pertsonalizatu itxura
+Comment[fa]=سفارشی کردن ظاهر تصویری
+Comment[fi]=Ulkonäköasetukset
+Comment[fr]=Personnalisation de l'apparence
+Comment[fy]=It uterlik oanpasse
+Comment[gl]=Personalizar a apariencia visual
+Comment[he]=הגדרת מראה
+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ą
+Comment[mk]=Приспособете ја визуелната појава
+Comment[ms]=Suaikan Rupa Visual
+Comment[nb]=Tilpass visuelt utseende
+Comment[nds]=Utsehn topassen
+Comment[ne]=दृश्यात्मक मोहडा अनुकूल गर्नुहोस्
+Comment[nl]=Pas het uiterlijk aan
+Comment[nn]=Tilpass utsjånad
+Comment[pl]=Zmiana wyglądu
+Comment[pt]=Personalizar a Aparência Visual
+Comment[pt_BR]=Personalizar Aparência Visual
+Comment[ru]=Настройка внешнего вида
+Comment[se]=Heivet fárdda
+Comment[sk]=Prispôsobenie vzhľadu
+Comment[sl]=Prilagodi videz
+Comment[sr]=Прилагодите визуелни приказ
+Comment[sr@Latn]=Prilagodite vizuelni prikaz
+Comment[sv]=Anpassa visuellt uppträdande
+Comment[ta]=பார்க்கும் தோற்றத்தை தனிபயனாக்கு
+Comment[tg]=Танзимоти намуди зоҳирӣ
+Comment[tr]=Görsel Görünümü Özelleştir
+Comment[uk]=Налаштування зовнішнього вигляду
+Comment[zh_CN]=自定义视觉外观
+Comment[zh_TW]=調整視覺顯示
+Keywords=kmail,appearance
+Keywords[bg]=пощенски, клиент, е-поща, външен, вид, външност, появяване kmail, appearance
+Keywords[br]=kmail,neuziadur
+Keywords[bs]=kmail,appearance,izgled
+Keywords[ca]=kmail,aparença
+Keywords[cs]=kmail,vzhled
+Keywords[da]=kmail,udseende
+Keywords[de]=KMail,Erscheinungsbild
+Keywords[el]=kmail,εμφάνιση
+Keywords[es]=kmail,apariencia
+Keywords[et]=kmail,välimus
+Keywords[eu]=kmail,itxura
+Keywords[fa]=kmail، ظاهر
+Keywords[fi]=kmail,ulkonäkö
+Keywords[fr]=KMail,apparence
+Keywords[fy]=kmail,uiterlijk,úterlik
+Keywords[ga]=kmail,cuma
+Keywords[gl]=kmail,apariencia
+Keywords[he]=kmail,appearance, מראה
+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,кпошта,појава,изглед,визуелно
+Keywords[ms]=kmail,rupa
+Keywords[nb]=kmail,utseende
+Keywords[nds]=KMail,Utsehn
+Keywords[ne]=केडीई मेल, मोहडा
+Keywords[nl]=kmail,uiterlijk
+Keywords[nn]=KMail,utsjånad
+Keywords[pl]=kmail,wygląd
+Keywords[pt]=kmail,aparência
+Keywords[pt_BR]=kmail,aparência
+Keywords[ru]=kmail,внешний вид
+Keywords[sk]=kmail,vzhľad
+Keywords[sl]=kmail,videz,izgled
+Keywords[sr]=kmail,изглед
+Keywords[sr@Latn]=kmail,izgled
+Keywords[sv]=kmail,utseende
+Keywords[ta]=கேஅஞ்சல், தோற்றம்
+Keywords[tg]=kmail,намуди зоҳирӣ
+Keywords[tr]=kmail,görünüm
+Keywords[uk]=kmail,вигляд
+Keywords[zh_CN]=kmail,appearance,外观
diff --git a/kmail/kmail_config_composer.desktop b/kmail/kmail_config_composer.desktop
new file mode 100644
index 00000000..5c75a1c4
--- /dev/null
+++ b/kmail/kmail_config_composer.desktop
@@ -0,0 +1,150 @@
+[Desktop Entry]
+Icon=edit
+Type=Service
+ServiceTypes=KCModule
+
+X-KDE-ModuleType=Library
+X-KDE-Library=kmail
+X-KDE-FactoryName=kmail_config_composer
+X-KDE-HasReadOnlyMode=false
+X-KDE-ParentApp=kmail
+X-KDE-ParentComponents=kmail,kontact_kmailplugin
+X-KDE-CfgDlgHierarchy=KMail
+
+Name=Composer
+Name[ar]=المحرر
+Name[bg]=Редактор
+Name[br]=Skridaozer
+Name[bs]=Sastavljač
+Name[ca]=Editor
+Name[cs]=Editor
+Name[cy]=Cyfansoddydd
+Name[da]=Brevskriver
+Name[de]=Komposer
+Name[el]=Σύνταξη μηνυμάτων
+Name[es]=Editor
+Name[et]=Koostaja
+Name[eu]=Editorea
+Name[fa]=مؤلف
+Name[fi]=Kirjoittaminen
+Name[fr]=Éditeur
+Name[fy]=Opsteller
+Name[ga]=Cumadóir
+Name[gl]=Compositor
+Name[he]=עורך
+Name[hu]=Szerkesztő
+Name[it]=Compositore
+Name[ja]=メール作成
+Name[ka]=წერილების რედაქტორი
+Name[kk]=Құрастарғыш
+Name[km]=កម្មវិធី​តែង
+Name[lt]=Redaktorius
+Name[mk]=Составувач
+Name[ms]=Penggubah
+Name[nb]=Komposer
+Name[nds]=Nettbreef-Editor
+Name[ne]=कम्पोजर
+Name[nl]=Opsteller
+Name[nn]=Skrivefelt
+Name[pa]=ਲੇਖਕ
+Name[pl]=Edytor
+Name[pt]=Compositor
+Name[pt_BR]=Editor de Mensagens
+Name[ru]=Редактор писем
+Name[se]=Čállinprográmma
+Name[sk]=Editor správ
+Name[sl]=Sestavljalnik
+Name[sr]=Састављач
+Name[sr@Latn]=Sastavljač
+Name[sv]=Brevfönster
+Name[ta]=கம்போசர்
+Name[tg]=Муҳаррири мактубҳо
+Name[tr]=Düzenleyici
+Name[uk]=Редактор листів
+Name[zh_CN]=编写器
+Name[zh_TW]=編寫器
+Comment=Templates & General Behavior
+Comment[af]=Voorbeelde & Algemene Gedrag
+Comment[bg]=Шаблони и поведение
+Comment[ca]=Plantilles i comportament general
+Comment[cs]=Šablony a obecné chování
+Comment[da]=Skabeloner & Generel opførsel
+Comment[de]=Vorlagen und allgemeines Verhalten
+Comment[el]=Πρότυπα & γενική Συμπεριφορά
+Comment[en_GB]=Templates & General Behaviour
+Comment[eo]=Ŝablonoj kaj Ĝenerala Konduto
+Comment[es]=Plantilla y comportamiento general
+Comment[et]=Mallid ja üldine käitumine
+Comment[fa]=عبارتها و رفتار عمومی
+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]=Үлгілер мен Жалпы тәртібі
+Comment[km]=ពុម្ព​ និង​ឥរិយាបទ​ទូទៅ
+Comment[lt]=Šablonai ir bendroji elgsena
+Comment[mk]=Обрасци и општо однесување
+Comment[nb]=Maler & generell oppførsel
+Comment[nds]=Vörlagen un allgemeen Bedregen
+Comment[ne]=टेम्प्लेट र साधारण व्यवहार
+Comment[nl]=Sjablonen en algemeen gedrag
+Comment[pl]=Szablony i ogólne zachowanie
+Comment[pt]=Modelos e Comportamento Geral
+Comment[pt_BR]=Modelos & Comportamento Geral
+Comment[ru]=Шаблоны и общие параметры
+Comment[sk]=Šablóny a všeobecné správanie
+Comment[sl]=Predloge in splošno obnašanje
+Comment[sr]=Шаблони и опште понашање
+Comment[sr@Latn]=Šabloni i opšte ponašanje
+Comment[sv]=Mallar och allmänt uppträdande
+Comment[tr]=Şablonlar & Genel Davranış
+Comment[uk]=Шаблони та загальна поведінка
+Comment[zh_CN]=模板和常规行为
+Comment[zh_TW]=樣本與一般行為
+Keywords=kmail,composer
+Keywords[bg]=пощенски, клиент, е-поща, редактор, съставител, форматиране, текст, kmail, komposer
+Keywords[bs]=kmail,composer,sastavljač
+Keywords[ca]=kmail,editor
+Keywords[cs]=kmail,editor
+Keywords[da]=kmail,brevskriver
+Keywords[de]=KMail,Komposer,Editor
+Keywords[el]=kmail,συντάκτης
+Keywords[es]=kmail,editor
+Keywords[et]=kmail,koostaja
+Keywords[eu]=kmail,editorea
+Keywords[fa]=kmail، مؤلف
+Keywords[fi]=kmail,kirjoitus
+Keywords[fr]=KMail,éditeur
+Keywords[fy]=kmail,opsteller
+Keywords[gl]=kmail,editor
+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,кпошта,составувач
+Keywords[ms]=kmail,penggubah
+Keywords[nb]=kmail,komposer
+Keywords[nds]=KMail,Editor,Nettbreefeditor
+Keywords[ne]=केडीई मेल, कम्पोजर
+Keywords[nl]=kmail,opsteller
+Keywords[nn]=KMail,skrivefelt
+Keywords[pl]=kmail,kompozytor,edytor
+Keywords[pt]=kmail,compositor
+Keywords[pt_BR]=kmail,editor de mensagens
+Keywords[ru]=kmail,composer,письмо
+Keywords[sk]=kmail,editor správ
+Keywords[sl]=kmail,composer,sestavljalnik
+Keywords[sr]=kmail,састављач
+Keywords[sr@Latn]=kmail,sastavljač
+Keywords[sv]=kmail,brevfönster
+Keywords[ta]=கேஅஞ்சல்,கம்போசர்
+Keywords[tg]=kmail,composer,мактуб
+Keywords[tr]=kmail,düzenleyici
+Keywords[uk]=kmail,редактор
+Keywords[zh_CN]=kmail,composer, 编写器
diff --git a/kmail/kmail_config_identity.desktop b/kmail/kmail_config_identity.desktop
new file mode 100644
index 00000000..8cf28eb0
--- /dev/null
+++ b/kmail/kmail_config_identity.desktop
@@ -0,0 +1,170 @@
+[Desktop Entry]
+Icon=identity
+Type=Service
+ServiceTypes=KCModule
+
+X-KDE-ModuleType=Library
+X-KDE-Library=kmail
+X-KDE-FactoryName=kmail_config_identity
+X-KDE-HasReadOnlyMode=false
+X-KDE-ParentApp=kmail
+X-KDE-ParentComponents=kmail,kontact_kmailplugin
+X-KDE-CfgDlgHierarchy=KMail
+
+Name=Identities
+Name[ar]=الهويات
+Name[be]=Увасабленні
+Name[bg]=Самоличност
+Name[br]=Anvelezhioù
+Name[bs]=Identiteti
+Name[ca]=Identitats
+Name[cs]=Identity
+Name[da]=Identiteter
+Name[de]=Identitäten
+Name[el]=Ταυτότητες
+Name[eo]=Identoj
+Name[es]=Identidades
+Name[et]=Identiteedid
+Name[eu]=Identitateak
+Name[fa]=هویتها
+Name[fi]=Henkilöllisyydet
+Name[fr]=Identités
+Name[fy]=Identiteiten
+Name[ga]=Aitheantais
+Name[gl]=Identidades
+Name[he]=זהויות
+Name[hu]=Azonosítók
+Name[is]=Auðkenni
+Name[it]=Identità
+Name[ja]=個人情報
+Name[ka]=პროფილები
+Name[kk]=Іс-әлпеттері
+Name[km]=អត្តសញ្ញាណ
+Name[lt]=Tapatybės
+Name[mk]=Идентитети
+Name[ms]=Identiti
+Name[nb]=Identiteter
+Name[nds]=Identiteten
+Name[ne]=परिचय
+Name[nl]=Identiteiten
+Name[nn]=Identitetar
+Name[pl]=Tożsamości
+Name[pt]=Identidades
+Name[pt_BR]=Identidades
+Name[ru]=Профили
+Name[se]=Identitehtat
+Name[sk]=Identity
+Name[sl]=Identitete
+Name[sr]=Идентитети
+Name[sr@Latn]=Identiteti
+Name[sv]=Identiteter
+Name[ta]=அடையாளங்கள்
+Name[tg]=Профилҳо
+Name[tr]=Kimlikler
+Name[uk]=Профілі
+Name[uz]=Shaxsiyatlar
+Name[uz@cyrillic]=Шахсиятлар
+Name[zh_CN]=身份
+Name[zh_TW]=身份
+Comment=Manage Identities
+Comment[af]=Bestuur identiteite
+Comment[ar]=تدبير الهويات
+Comment[be]=Кіраванне увасабленнямі
+Comment[bg]=Настройки на самоличността
+Comment[br]=Merañ an anvelezhioù
+Comment[bs]=Upravljaj identitetima
+Comment[ca]=Gestiona les identitats
+Comment[cs]=Správa identit
+Comment[da]=Håndtering af identiteter
+Comment[de]=Identitäten verwalten
+Comment[el]=Διαχείριση ταυτοτήτων
+Comment[eo]=Mastrumi identojn
+Comment[es]=Gestionar identidades
+Comment[et]=Identiteetide haldus
+Comment[eu]=Identitateak kudeatu
+Comment[fa]=مدیریت هویتها
+Comment[fi]=Henkilöllisyysasetukset
+Comment[fr]=Gestion des identités
+Comment[fy]=Identiteiten beheare
+Comment[gl]=Xestionar Identidades
+Comment[he]=ניהול זהויות
+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
+Comment[mk]=Менаџирајте ги идентитетите
+Comment[ms]=Uruskan identiti
+Comment[nb]=Identitetsbehandling
+Comment[nds]=Identiteten plegen
+Comment[ne]=परिचय व्यवस्थापन गर्नुहोस्
+Comment[nl]=Identiteiten beheren
+Comment[nn]=Handter identitetar
+Comment[pl]=Zarządzanie tożsamościami
+Comment[pt]=Gerir Identidades
+Comment[pt_BR]=Gerenciar Identidades
+Comment[ru]=Управление профилями
+Comment[se]=Gieđahala identitehtaid
+Comment[sk]=Správa identít
+Comment[sl]=Upravljanje z identitetami
+Comment[sr]=Управљање идентитетима
+Comment[sr@Latn]=Upravljanje identitetima
+Comment[sv]=Hantera identiteter
+Comment[ta]=அடையாளங்களை நிர்வகி
+Comment[tg]=Идоракунӣ бо профилҳо
+Comment[tr]=Kimlikleri Düzenle
+Comment[uk]=Керування профілями
+Comment[zh_CN]=管理身份
+Comment[zh_TW]=管理身份
+Keywords=kmail,identity
+Keywords[be]=K Пошта,увасабленне,kmail,identity
+Keywords[bg]=пощенски, клиент, е-поща, самоличност, информация, kmail, identity
+Keywords[br]=kmail,anvelezh
+Keywords[bs]=kmail,identity,identitet
+Keywords[ca]=kmail,identitat
+Keywords[cs]=kmail,identita
+Keywords[da]=kmail,identitet
+Keywords[de]=KMail,Identitäten,Identität
+Keywords[el]=kmail,ταυτότητα
+Keywords[es]=kmail,identidad
+Keywords[et]=kmail,identiteet
+Keywords[eu]=kmail,identitatea
+Keywords[fa]=kmail، هویت
+Keywords[fi]=kmail,henkilöllisyys
+Keywords[fr]=KMail,identités
+Keywords[fy]=kmail,identiteit
+Keywords[ga]=kmail,aitheantas
+Keywords[gl]=kmail,identidade
+Keywords[he]=kmail,identity,זהות
+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,кпошта,идентитет,идентитети
+Keywords[ms]=kmail,identiti
+Keywords[nb]=kmail,identitet
+Keywords[nds]=KMail,Identiteet,Identiteten
+Keywords[ne]=केडीई मेल, परिचय
+Keywords[nl]=kmail,identiteit
+Keywords[nn]=KMail,identitet
+Keywords[pl]=kmail,tożsamość,tożsamości
+Keywords[pt]=kmail,identidade
+Keywords[pt_BR]=kmail,identidade
+Keywords[ru]=kmail,identity,профиль
+Keywords[sl]=kmail,identiteta
+Keywords[sr]=kmail,идентитет
+Keywords[sr@Latn]=kmail,identitet
+Keywords[sv]=kmail,identifiera
+Keywords[ta]=கேஅஞ்சல்,அடையாளம்
+Keywords[tg]=kmail,identity,профил
+Keywords[tr]=kmail,kimlikler
+Keywords[uk]=kmail,профіль
+Keywords[uz]=kmail,shaxsiyat
+Keywords[uz@cyrillic]=kmail,шахсият
+Keywords[zh_CN]=kmail,identity, 身份
diff --git a/kmail/kmail_config_misc.desktop b/kmail/kmail_config_misc.desktop
new file mode 100644
index 00000000..6a33c643
--- /dev/null
+++ b/kmail/kmail_config_misc.desktop
@@ -0,0 +1,166 @@
+[Desktop Entry]
+Icon=misc
+Type=Service
+ServiceTypes=KCModule
+
+X-KDE-ModuleType=Library
+X-KDE-Library=kmail
+X-KDE-FactoryName=kmail_config_misc
+X-KDE-HasReadOnlyMode=false
+X-KDE-ParentApp=kmail
+X-KDE-ParentComponents=kmail,kontact_kmailplugin
+X-KDE-CfgDlgHierarchy=KMail
+
+Name=Misc
+Name[af]=Allerlei
+Name[be]=Рознае
+Name[bg]=Разни
+Name[br]=A bep seurt
+Name[bs]=Razno
+Name[ca]=Varis
+Name[cs]=Různé
+Name[cy]=Amrywiol
+Name[da]=Diverse
+Name[de]=Diverses
+Name[el]=Διάφορα
+Name[es]=Varios
+Name[et]=Muud
+Name[eu]=Miszelanea
+Name[fi]=Muut
+Name[fr]=Divers
+Name[fy]=Ferskate
+Name[ga]=Éagsúil
+Name[gl]=Miscelánea
+Name[he]=כללי
+Name[hu]=Egyéb
+Name[is]=Ýmislegt
+Name[it]=Varie
+Name[ja]=その他
+Name[ka]=სხვადასხვა
+Name[kk]=Басқалары
+Name[km]=ផ្សេងៗ
+Name[lt]=Įvairūs
+Name[mk]=Разно
+Name[ms]=Pelbagai
+Name[nb]=Div
+Name[nds]=Anner Saken
+Name[ne]=विविध
+Name[nl]=Diversen
+Name[nn]=Ymse
+Name[pa]=ਫੁਟਕਲ
+Name[pl]=Różne
+Name[pt]=Vários
+Name[pt_BR]=Extras
+Name[ru]=Прочее
+Name[se]=Feará mii
+Name[sk]=Rôzne
+Name[sl]=Razno
+Name[sr]=Разно
+Name[sr@Latn]=Razno
+Name[sv]=Diverse
+Name[ta]=இதர
+Name[tg]=Ғайра
+Name[tr]=Çeşitli
+Name[uk]=Різне
+Name[uz]=Har xil
+Name[uz@cyrillic]=Ҳар хил
+Name[zh_CN]=杂项
+Name[zh_TW]=其他
+Comment=Settings that don't fit elsewhere
+Comment[af]=Opstelling wat nêrens anders pas nie
+Comment[bg]=Разни настройки
+Comment[bs]=Postavke koje ne spadaju nigdje drugo
+Comment[ca]=Opcions que no van bé enlloc
+Comment[cs]=Nastavení nehodící se jinam
+Comment[da]=Indstillinger der ikke passer ind andre steder
+Comment[de]=Verschiedene Einstellungen
+Comment[el]=Ρυθμίσεις που δεν ταιριάζουν κάπου αλλού
+Comment[eo]=Agordoj kiuj apartenas nenie alie
+Comment[es]=Opciones que no encajan en ningún otro sitio
+Comment[et]=Seadistused, mis kuhugi mujale ei sobi
+Comment[eu]=Beste inon egokitzen ez diren ezarpenak
+Comment[fa]=تنظیماتی که مناسب جای دیگر نیستند
+Comment[fi]=Muut asetukset
+Comment[fr]=Paramètres qui ne rentrent dans aucune autre catégorie
+Comment[fy]=Ynstellings dy't eins nergens passe
+Comment[gl]=Opcións que non se encadran noutro sitio
+Comment[he]=הגדרות שלא יכולות להיכנס למקום אחר
+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
+Comment[mk]=Поставувања што не се вклопуваат на друго место
+Comment[ms]=Seting yang tidak muat di tempat lain
+Comment[nb]=Instillinger som ikke passer andre steder
+Comment[nds]=Verscheden Instellen
+Comment[ne]=कतै पनि नमिल्ने सेटिङ
+Comment[nl]=Instellingen die nergens echt passen
+Comment[nn]=Innstillingar som ikkje passar andre stader
+Comment[pl]=Ustawienia, które nie pasują gdzie indziej
+Comment[pt]=Outras configurações
+Comment[pt_BR]=Preferências que não se encaixam em outros lugares
+Comment[ru]=Другие настройки
+Comment[se]=Heivehusat mat eai heive eará báikkiin
+Comment[sk]=Nastavenie, ktoré sa inam nehodí
+Comment[sl]=Nastavitve, ki ne spadajo drugam
+Comment[sr]=Поставке које не припадају другде
+Comment[sr@Latn]=Postavke koje ne pripadaju drugde
+Comment[sv]=Inställningar som inte passar någon annanstans
+Comment[ta]=அமைப்புகள் எந்த இடத்திலும் பொருந்தவில்லை
+Comment[tg]=Дигар танзимотҳо
+Comment[tr]=Diğer bölümlere uymayan yapılandırma seçenekleri
+Comment[uk]=Інші параметри
+Comment[zh_CN]=其它设置
+Comment[zh_TW]=不屬於任何其他地方的設定
+Keywords=kmail,misc
+Keywords[be]=K Пошта,рознае,kmail,misc
+Keywords[bg]=пощенски, клиент, е-поща, разни, настройки, kmail, misc
+Keywords[br]=kmail,a bep seurt
+Keywords[bs]=kmail,misc,razno
+Keywords[ca]=kmail,varis
+Keywords[cs]=kmail,různé
+Keywords[da]=kmail,diverse
+Keywords[de]=KMail,Verschiedenes
+Keywords[el]=kmail,διάφορα
+Keywords[es]=kmail,varios
+Keywords[et]=kmail,muud
+Keywords[eu]=kmail,miszelanea
+Keywords[fi]=kmail,muut
+Keywords[fr]=KMail,divers
+Keywords[fy]=kmail,ferskate
+Keywords[ga]=kmail,éagsúil
+Keywords[he]=kmail,misc,כללי
+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,кпошта,разно
+Keywords[ms]=kmail,pelbagai
+Keywords[nb]=kmail,div
+Keywords[nds]=KMail,anner,saken
+Keywords[ne]=केडीई मेल, विविध
+Keywords[nl]=kmail,diversen
+Keywords[nn]=KMail,ymse
+Keywords[pl]=kmail,różne,inne
+Keywords[pt]=kmail,vários
+Keywords[pt_BR]=kmail, extras
+Keywords[ru]=kmail,misc,прочее,другие
+Keywords[sk]=kmail,rôzne
+Keywords[sl]=kmail,razno
+Keywords[sr]=kmail,разно
+Keywords[sr@Latn]=kmail,razno
+Keywords[sv]=kmail,diverse
+Keywords[ta]=கேஅஞ்சல், இதர
+Keywords[tg]=kmail,misc,ғайра,дигар
+Keywords[tr]=kmail,çeşitli
+Keywords[uk]=kmail,різне
+Keywords[uz]=kmail,har xil
+Keywords[uz@cyrillic]=kmail,ҳар хил
+Keywords[zh_CN]=kmail,misc,杂项
diff --git a/kmail/kmail_config_security.desktop b/kmail/kmail_config_security.desktop
new file mode 100644
index 00000000..1b61544e
--- /dev/null
+++ b/kmail/kmail_config_security.desktop
@@ -0,0 +1,172 @@
+[Desktop Entry]
+Icon=encrypted
+Type=Service
+ServiceTypes=KCModule
+
+X-KDE-ModuleType=Library
+X-KDE-Library=kmail
+X-KDE-FactoryName=kmail_config_security
+X-KDE-HasReadOnlyMode=false
+X-KDE-ParentApp=kmail
+X-KDE-ParentComponents=kmail,kontact_kmailplugin
+X-KDE-CfgDlgHierarchy=KMail
+
+Name=Security
+Name[af]=Sekuriteit
+Name[be]=Бясьпека
+Name[bg]=Сигурност
+Name[br]=Surentez
+Name[bs]=Sigurnost
+Name[ca]=Seguretat
+Name[cs]=Bezpečnost
+Name[cy]=Diogelwch
+Name[da]=Sikkerhed
+Name[de]=Sicherheit
+Name[el]=Ασφάλεια
+Name[eo]=Sekureco
+Name[es]=Seguridad
+Name[et]=Turvalisus
+Name[eu]=Sekuritatea
+Name[fa]=امنیت
+Name[fi]=Turvallisuus
+Name[fr]=Sécurité
+Name[fy]=Feiligens
+Name[ga]=Slándáil
+Name[gl]=Seguridade
+Name[he]=אבטחה
+Name[hu]=Biztonság
+Name[is]=Öryggi
+Name[it]=Sicurezza
+Name[ja]=セキュリティ
+Name[ka]=უსაფრთხოება
+Name[kk]=Қауіпсіздік
+Name[km]=សុវត្ថិភាព
+Name[lt]=Saugumas
+Name[mk]=Безбедност
+Name[ms]=Keselamatan
+Name[nb]=Sikkerhet
+Name[nds]=Sekerheit
+Name[ne]=सूरक्षा
+Name[nl]=Beveiliging
+Name[nn]=Tryggleik
+Name[pa]=ਸੁਰੱਖਿਆ
+Name[pl]=Bezpieczeństwo
+Name[pt]=Segurança
+Name[pt_BR]=Segurança
+Name[ru]=Безопасность
+Name[se]=Sihkarvuohta
+Name[sk]=Zabezpečenie
+Name[sl]=Varnost
+Name[sr]=Сигурност
+Name[sr@Latn]=Sigurnost
+Name[sv]=Säkerhet
+Name[ta]=பாதுகாப்பு
+Name[tg]=Амният
+Name[tr]=Güvenlik
+Name[uk]=Безпека
+Name[uz]=Xavfsizlik
+Name[uz@cyrillic]=Хавфсизлик
+Name[zh_CN]=安全
+Name[zh_TW]=安全性
+Comment=Security & Privacy Settings
+Comment[af]=Sekuriteit en provaatheid opstelling
+Comment[be]=Настаўленні бясьпекі і прыватнасьці
+Comment[bg]=Настройки на сигурността
+Comment[br]=Kefluniadur surentez ar buhez prevez
+Comment[bs]=Postavke sigurnosti i privatnosti
+Comment[ca]=Arranjament de seguretat i privadesa
+Comment[cs]=Nastavení soukromí a zabezpečení
+Comment[da]=Sikkerheds- & Privathedsindstillinger
+Comment[de]=Einstellungen zu Sicherheit und Privatsphäre
+Comment[el]=Ρυθμίσεις Ασφάλειας & Προσωπικού απόρρητου
+Comment[eo]=Agordoj pri Sekureco kaj Privateco
+Comment[es]=Opciones de seguridad y privacidad
+Comment[et]=Turvalisus- ja privaatsusseadistused
+Comment[eu]=Sekuritate & pribakortasun ezarpenak
+Comment[fa]=امنیت و تنظیمات محرمانگی
+Comment[fi]=Turvallisuus- ja yksityisyysasetukset
+Comment[fr]=Paramètres de sécurité et de confidentialité
+Comment[fy]=Feiligens- en privacy-ynstellings
+Comment[gl]=Opcións de Seguridade e Intimidade
+Comment[he]=הגדרות אבטחה ופרטיות
+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
+Comment[mk]=Поставувања за безбедност и приватност
+Comment[ms]=Seting Keselamatan & Peribadi
+Comment[nb]=Sikkerhet og personvern-innstillinger
+Comment[nds]=Sekerheit- un Privaatrebeet-Instellen
+Comment[ne]=सुरक्षा र गोप्यता सेटिङ
+Comment[nl]=Beveiligings- en privacy-instellingen
+Comment[nn]=Innstillingar for tryggleik og personvern
+Comment[pl]=Ustawienia bezpieczeństwa i prywatności
+Comment[pt]=Configuração de Segurança e Privacidade
+Comment[pt_BR]=Configurações de Segurança & Privacidade
+Comment[ru]=Настройки безопасности
+Comment[se]=Sihkarvuohta- ja iežas suodjalan heivehusat
+Comment[sk]=Zabezpečenie a súkromie
+Comment[sl]=Nastavitve varnosti in zasebnosti
+Comment[sr]=Поставке сигурности и приватности
+Comment[sr@Latn]=Postavke sigurnosti i privatnosti
+Comment[sv]=Inställningar för säkerhet och integritet
+Comment[ta]=பாதுகாப்பு & தனிப்பட்ட அமைவுகள்
+Comment[tg]=Танзимоти амният
+Comment[tr]=Güvenlik ve Gizlilik Ayarlar
+Comment[uk]=Параметри безпеки і конфіденційності
+Comment[zh_CN]=安全和隐私设置
+Comment[zh_TW]=安全性與隱私設定
+Keywords=kmail,security
+Keywords[be]=K Пошта,бясьпека,kmail,security
+Keywords[bg]=пощенски, клиент, е-поща, сигурност, поверителност, kmail, security
+Keywords[br]=kmail,surentez
+Keywords[bs]=kmail,security,sigurnost
+Keywords[ca]=kmail,seguretat
+Keywords[cs]=kmail,bezpečnost
+Keywords[da]=kmail,sikkerhed
+Keywords[de]=KMail,Sicherheit
+Keywords[el]=kmail,ασφάλεια
+Keywords[es]=kmail,seguridad
+Keywords[et]=kmail,turvalisus
+Keywords[eu]=kmail,sekuritate
+Keywords[fa]=kmail، امنیت
+Keywords[fi]=kmail,turvallisuus
+Keywords[fr]=KMail,sécurité
+Keywords[fy]=kmail,feiligens
+Keywords[ga]=kmail,slándáil
+Keywords[gl]=kmail,seguridade
+Keywords[he]=kmail,security,אבטחה
+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,кпошта,безбедност
+Keywords[ms]=kmail,keselamatan
+Keywords[nb]=kmail,sikkerhet
+Keywords[nds]=KMail,Sekerheit,Privaatrebeet
+Keywords[ne]=केडीई मेल, सुरक्षा
+Keywords[nl]=kmail,beveiliging
+Keywords[nn]=KMail,tryggleik
+Keywords[pl]=kmail,bezpieczeństwo,prywatność
+Keywords[pt]=kmail,segurança
+Keywords[pt_BR]=kmail,segurança
+Keywords[ru]=kmail,security,безопасность
+Keywords[sk]=kmail,zabezpečenie
+Keywords[sl]=kmail,varnost
+Keywords[sr]=kmail,сигурност
+Keywords[sr@Latn]=kmail,sigurnost
+Keywords[sv]=kmail,säkerhet
+Keywords[ta]=கேஅஞ்சல்,பாதுகாப்பு
+Keywords[tg]=kmail,security,амният
+Keywords[tr]=kmail,güvenlik
+Keywords[uk]=kmail,безпека
+Keywords[uz]=kmail,xavfsizlik
+Keywords[uz@cyrillic]=kmail,хавфсизлик
+Keywords[zh_CN]=kmail,security,安全
diff --git a/kmail/kmail_options.h b/kmail/kmail_options.h
new file mode 100644
index 00000000..39ce3da2
--- /dev/null
+++ b/kmail/kmail_options.h
@@ -0,0 +1,31 @@
+/* -*- mode: C++; c-file-style: "gnu" -*- */
+#ifndef KMAIL_OPTIONS_H
+#define KMAIL_OPTIONS_H
+
+#include <kcmdlineargs.h>
+#include <klocale.h>
+
+KCmdLineOptions kmail_options[] =
+{
+ { "s", 0 , 0 },
+ { "subject <subject>", I18N_NOOP("Set subject of message"), 0 },
+ { "c", 0 , 0 },
+ { "cc <address>", I18N_NOOP("Send CC: to 'address'"), 0 },
+ { "b", 0 , 0 },
+ { "bcc <address>", I18N_NOOP("Send BCC: to 'address'"), 0 },
+ { "h", 0 , 0 },
+ { "header <header>", I18N_NOOP("Add 'header' to message"), 0 },
+ { "msg <file>", I18N_NOOP("Read message body from 'file'"), 0 },
+ { "body <text>", I18N_NOOP("Set body of message"), 0 },
+ { "attach <url>", I18N_NOOP("Add an attachment to the mail. This can be repeated"), 0 },
+ { "check", I18N_NOOP("Only check for new mail"), 0 },
+ { "composer", I18N_NOOP("Only open composer window"), 0 },
+ { "view <url>", I18N_NOOP("View the given message file" ), 0 },
+ { "+[address|URL]", I18N_NOOP("Send message to 'address' resp. "
+ "attach the file the 'URL' points "
+ "to"), 0 },
+// { "+[file]", I18N_NOOP("Show message from 'file'"), 0 },
+ KCmdLineLastOption
+};
+
+#endif
diff --git a/kmail/kmail_part.cpp b/kmail/kmail_part.cpp
new file mode 100644
index 00000000..e9722c25
--- /dev/null
+++ b/kmail/kmail_part.cpp
@@ -0,0 +1,251 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+
+ This file is part of KMail.
+ Copyright (c) 2002-2003 Don Sanders <sanders@kde.org>,
+ Copyright (c) 2003 Zack Rusin <zack@kde.org>,
+ Based on the work of Cornelius Schumacher <schumacher@kde.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ As a special exception, permission is given to link this program
+ with any edition of Qt, and distribute the resulting executable,
+ without including the source code for Qt in the source distribution.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "kmail_part.h"
+
+#include "kmmainwin.h"
+#include "kmmainwidget.h"
+#include "kmfoldertree.h"
+#include "kmstartup.h"
+#include "aboutdata.h"
+#include "kmfolder.h"
+#include "accountmanager.h"
+using KMail::AccountManager;
+#include "sidebarextension.h"
+#include "infoextension.h"
+#include "recentaddresses.h"
+using KRecentAddress::RecentAddresses;
+
+#include <kapplication.h>
+#include <kparts/mainwindow.h>
+#include <kparts/genericfactory.h>
+#include <knotifyclient.h>
+#include <dcopclient.h>
+#include <kiconloader.h>
+#include <kdebug.h>
+#include <kstatusbar.h>
+#include <ksettings/dispatcher.h>
+
+
+#include <qlayout.h>
+
+
+typedef KParts::GenericFactory< KMailPart > KMailFactory;
+K_EXPORT_COMPONENT_FACTORY( libkmailpart, KMailFactory )
+
+KMailPart::KMailPart(QWidget *parentWidget, const char *widgetName,
+ QObject *parent, const char *name, const QStringList &) :
+ DCOPObject("KMailIface"), KParts::ReadOnlyPart(parent, name),
+ mParentWidget( parentWidget )
+{
+ kdDebug(5006) << "KMailPart()" << endl;
+ kdDebug(5006) << " InstanceName: " << kapp->instanceName() << endl;
+
+ setInstance(KMailFactory::instance());
+
+ kdDebug(5006) << "KMailPart()..." << endl;
+ kdDebug(5006) << " InstanceName: " << kapp->instanceName() << endl;
+
+ // import i18n data and icons from libraries:
+ KMail::insertLibraryCataloguesAndIcons();
+
+ // Make sure that the KNotify Daemon is running (this is necessary for people
+ // using KMail without KDE)
+ KNotifyClient::startDaemon();
+
+ KMail::lockOrDie();
+
+ kapp->dcopClient()->suspend(); // Don't handle DCOP requests yet
+
+ //local, do the init
+ KMKernel *mKMailKernel = new KMKernel();
+ mKMailKernel->init();
+ mKMailKernel->setXmlGuiInstance( KMailFactory::instance() );
+
+ // and session management
+ mKMailKernel->doSessionManagement();
+
+ // any dead letters?
+ mKMailKernel->recoverDeadLetters();
+
+ kmsetSignalHandler(kmsignalHandler);
+ kapp->dcopClient()->resume(); // Ok. We are ready for DCOP requests.
+
+ // create a canvas to insert our widget
+ QWidget *canvas = new QWidget(parentWidget, widgetName);
+ canvas->setFocusPolicy(QWidget::ClickFocus);
+ setWidget(canvas);
+ KGlobal::iconLoader()->addAppDir("kmail");
+#if 0
+ //It's also possible to make a part out of a readerWin
+ KMReaderWin *mReaderWin = new KMReaderWin( canvas, canvas, actionCollection() );
+ connect(mReaderWin, SIGNAL(urlClicked(const KURL&,int)),
+ mReaderWin, SLOT(slotUrlClicked()));
+ QVBoxLayout *topLayout = new QVBoxLayout(canvas);
+ topLayout->addWidget(mReaderWin);
+ mReaderWin->setAutoDelete( true );
+ kmkernel->inboxFolder()->open();
+ KMMessage *msg = kmkernel->inboxFolder()->getMsg(0);
+ mReaderWin->setMsg( msg, true );
+ mReaderWin->setFocusPolicy(QWidget::ClickFocus);
+ mStatusBar = new KMailStatusBarExtension(this);
+ //new KParts::SideBarExtension( kmkernel->mainWin()-mainKMWidget()->leftFrame(), this );
+ KGlobal::iconLoader()->addAppDir("kmail");
+ setXMLFile( "kmail_part.rc" );
+ kmkernel->inboxFolder()->close();
+#else
+ mainWidget = new KMMainWidget( canvas, "mainWidget", this, actionCollection(),
+ kapp->config());
+ QVBoxLayout *topLayout = new QVBoxLayout(canvas);
+ topLayout->addWidget(mainWidget);
+ mainWidget->setFocusPolicy(QWidget::ClickFocus);
+ mStatusBar = new KMailStatusBarExtension(this);
+ mStatusBar->addStatusBarItem( mainWidget->vacationScriptIndicator(), 2, false );
+
+ new KParts::SideBarExtension( mainWidget->folderTree(),
+ this,
+ "KMailSidebar" );
+
+ // Get to know when the user clicked on a folder in the KMail part and update the headerWidget of Kontact
+ KParts::InfoExtension *ie = new KParts::InfoExtension( this, "KMailInfo" );
+ connect( mainWidget->folderTree(), SIGNAL(folderSelected(KMFolder*)), this, SLOT(exportFolder(KMFolder*)) );
+ connect( mainWidget->folderTree(), SIGNAL(iconChanged(KMFolderTreeItem*)),
+ this, SLOT(slotIconChanged(KMFolderTreeItem*)) );
+ connect( mainWidget->folderTree(), SIGNAL(nameChanged(KMFolderTreeItem*)),
+ this, SLOT(slotNameChanged(KMFolderTreeItem*)) );
+ connect( this, SIGNAL(textChanged(const QString&)), ie, SIGNAL(textChanged(const QString&)) );
+ connect( this, SIGNAL(iconChanged(const QPixmap&)), ie, SIGNAL(iconChanged(const QPixmap&)) );
+
+ KGlobal::iconLoader()->addAppDir( "kmail" );
+ setXMLFile( "kmail_part.rc" );
+#endif
+
+ KSettings::Dispatcher::self()->registerInstance( KMailFactory::instance(), mKMailKernel,
+ SLOT( slotConfigChanged() ) );
+}
+
+KMailPart::~KMailPart()
+{
+ kdDebug(5006) << "Closing last KMMainWin: stopping mail check" << endl;
+ // Running KIO jobs prevent kapp from exiting, so we need to kill them
+ // if they are only about checking mail (not important stuff like moving messages)
+ kmkernel->abortMailCheck();
+ kmkernel->acctMgr()->cancelMailCheck();
+
+ mainWidget->destruct();
+ kmkernel->cleanup();
+ delete kmkernel;
+ KMail::cleanup(); // pid file (see kmstartup.cpp)
+}
+
+KAboutData *KMailPart::createAboutData()
+{
+ return new KMail::AboutData();
+}
+
+bool KMailPart::openFile()
+{
+ kdDebug(5006) << "KMailPart:openFile()" << endl;
+
+ mainWidget->show();
+ return true;
+}
+
+void KMailPart::exportFolder( KMFolder *folder )
+{
+ KMFolderTreeItem* fti = static_cast< KMFolderTreeItem* >( mainWidget->folderTree()->currentItem() );
+
+ if ( folder != 0 )
+ emit textChanged( folder->label() );
+
+ if ( fti )
+ emit iconChanged( fti->normalIcon( 22 ) );
+}
+
+void KMailPart::slotIconChanged( KMFolderTreeItem *fti )
+{
+ emit iconChanged( fti->normalIcon( 22 ) );
+}
+
+void KMailPart::slotNameChanged( KMFolderTreeItem *fti )
+{
+ emit textChanged( fti->folder()->label() );
+}
+
+//-----------------------------------------------------------------------------
+
+// The sole purpose of the following class is to publicize the protected
+// method KParts::MainWindow::createGUI() since we need to call it so that
+// the toolbar is redrawn when necessary.
+// It can be removed once createGUI() has been made public _and_ we don't
+// longer rely on kdelibs 3.2.
+class KPartsMainWindowWithPublicizedCreateGUI : public KParts::MainWindow
+{
+public:
+ void createGUIPublic( KParts::Part *part ) {
+ createGUI( part );
+ }
+};
+
+//-----------------------------------------------------------------------------
+
+void KMailPart::guiActivateEvent(KParts::GUIActivateEvent *e)
+{
+ kdDebug(5006) << "KMailPart::guiActivateEvent" << endl;
+ KParts::ReadOnlyPart::guiActivateEvent(e);
+ mainWidget->initializeFilterActions();
+ mainWidget->initializeFolderShortcutActions();
+ mainWidget->setupForwardingActionsList();
+ mainWidget->updateVactionScriptStatus();
+}
+
+void KMailPart::exit()
+{
+ delete this;
+}
+
+QWidget* KMailPart::parentWidget() const
+{
+ return mParentWidget;
+}
+
+
+KMailStatusBarExtension::KMailStatusBarExtension( KMailPart *parent )
+ : KParts::StatusBarExtension( parent ), mParent( parent )
+{
+}
+
+KMainWindow * KMailStatusBarExtension::mainWindow() const
+{
+ return static_cast<KMainWindow*>( mParent->parentWidget() );
+}
+
+#include "kmail_part.moc"
+
diff --git a/kmail/kmail_part.h b/kmail/kmail_part.h
new file mode 100644
index 00000000..7a4ed9b7
--- /dev/null
+++ b/kmail/kmail_part.h
@@ -0,0 +1,100 @@
+/*
+ This file is part of KMail.
+ Copyright (c) 2002-2003 Don Sanders <sanders@kde.org>,
+ Copyright (c) 2003 Zack Rusin <zack@kde.org>,
+ Based on the work of Cornelius Schumacher <schumacher@kde.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ As a special exception, permission is given to link this program
+ with any edition of Qt, and distribute the resulting executable,
+ without including the source code for Qt in the source distribution.
+*/
+#ifndef KMail_PART_H
+#define KMail_PART_H
+
+#include "kmailpartIface.h"
+
+#include <kdeversion.h>
+#include <kparts/browserextension.h>
+#include <kparts/statusbarextension.h>
+#include <kparts/factory.h>
+#include <kparts/event.h>
+#include <kparts/part.h>
+
+#include <qwidget.h>
+#include <kdepimmacros.h>
+
+class KInstance;
+class KAboutData;
+class KMailStatusBarExtension;
+class KMKernel;
+class KMMainWidget;
+namespace KPIM { class StatusbarProgressWidget; }
+using KPIM::StatusbarProgressWidget;
+class KMFolder;
+class KMFolderTreeItem;
+
+class ActionManager;
+
+class KDE_EXPORT KMailPart: public KParts::ReadOnlyPart, virtual public KMailPartIface
+{
+ Q_OBJECT
+ public:
+ KMailPart(QWidget *parentWidget, const char *widgetName,
+ QObject *parent, const char *name, const QStringList &);
+ virtual ~KMailPart();
+
+ QWidget* parentWidget() const;
+
+ static KAboutData *createAboutData();
+
+ public slots:
+ virtual void save() { /*TODO*/ }
+ virtual void exit();
+ virtual void updateEditMenu() {}
+ void exportFolder( KMFolder* folder );
+ void slotIconChanged( KMFolderTreeItem *fti );
+ void slotNameChanged( KMFolderTreeItem *fti );
+
+ signals:
+ void textChanged( const QString& );
+ void iconChanged( const QPixmap& );
+
+ protected:
+ virtual bool openFile();
+ virtual void guiActivateEvent(KParts::GUIActivateEvent *e);
+
+ private:
+ KMKernel *mKMailKernel;
+ KMMainWidget *mainWidget;
+ ActionManager *mActionManager;
+ KMailStatusBarExtension *mStatusBar;
+ QWidget *mParentWidget;
+};
+
+class KMailStatusBarExtension : public KParts::StatusBarExtension
+{
+public:
+ KMailStatusBarExtension( KMailPart *parent );
+
+ KMainWindow *mainWindow() const;
+
+private:
+ KMailPart *mParent;
+ StatusbarProgressWidget *mLittleProgress;
+};
+
+#endif
diff --git a/kmail/kmail_part.rc b/kmail/kmail_part.rc
new file mode 100644
index 00000000..72cf6ec3
--- /dev/null
+++ b/kmail/kmail_part.rc
@@ -0,0 +1,199 @@
+<!-- This file should be synchronized with kmmainwin.rc to provide
+ the same menu entries at the same place in KMail and Kontact -->
+
+<!DOCTYPE kpartgui>
+<kpartgui version="18" name="kmail_part" >
+ <MenuBar>
+ <Menu noMerge="1" name="file" >
+ <text>&amp;File</text>
+ <Menu name="file_new" >
+ <text>New</text>
+ <Action name="new_message" />
+ <Action name="new_from_template" />
+ <Separator/>
+ <!-- Action name="new_todo" />
+ etc -->
+ <Separator/>
+ <Action name="new_folder" />
+ </Menu>
+ <Action name="file_open" />
+ <Action name="file_save_as" />
+ <Action name="import"/>
+ <Action name="file_print" />
+ <Separator/>
+ <Action name="expire_all_folders" />
+ <Action name="file_invalidate_imap_cache"/>
+ <Action name="empty_trash"/>
+ <Separator/>
+ <Action name="online_status" />
+ <Action name="check_mail" />
+ <Action name="favorite_check_mail"/>
+ <Action name="check_mail_in" />
+ <Action name="send_queued" />
+ <Action name="send_queued_via" />
+ <Separator/>
+ <Action name="file_quit" />
+ </Menu>
+ <Menu noMerge="1" name="edit" >
+ <text>&amp;Edit</text>
+ <Action name="kmail_undo" />
+ <Action name="kmail_redo" />
+ <Separator/>
+ <Action name="kmail_copy" />
+ <Separator/>
+ <Action name="copy_folder"/>
+ <Action name="cut_folder"/>
+ <Action name="paste_folder"/>
+ <Separator/>
+ <Action name="copy_messages"/>
+ <Action name="cut_messages"/>
+ <Action name="paste_messages"/>
+ <Separator/>
+ <Action name="move_to_trash" />
+ <Action name="move_thread_to_trash" />
+ <Separator/>
+ <Action name="search_messages" />
+ <Action name="find_in_messages" />
+ <Separator/>
+ <Action name="mark_all_messages" />
+ <Action name="mark_all_text" />
+ </Menu>
+ <Menu noMerge="1" name="view">
+ <text>&amp;View</text>
+ <Action name="view_headers"/>
+ <Action name="view_attachments"/>
+ <Separator/>
+ <Action name="view_unread"/>
+ <Action name="view_columns_total"/>
+ <Separator/>
+ <Action name="expand_thread"/>
+ <Action name="collapse_thread"/>
+ <Action name="expand_all_threads"/>
+ <Action name="collapse_all_threads"/>
+ <Separator/>
+ <Action name="view_source"/>
+ <Separator/>
+ <Action name="toggle_fixedfont"/>
+ <Action name="encoding" />
+ </Menu>
+ <Menu noMerge="1" name="go">
+ <text>&amp;Go</text>
+ <Action name="go_next_message"/>
+ <Action name="go_next_unread_message"/>
+ <Action name="go_prev_message"/>
+ <Action name="go_prev_unread_message"/>
+ <Separator/>
+ <Action name="go_next_unread_folder"/>
+ <Action name="go_prev_unread_folder"/>
+ <Separator/>
+ <Action name="go_next_unread_text"/>
+ </Menu>
+ <Menu noMerge="1" name="folder" >
+ <text>F&amp;older</text>
+ <Action name="new_folder" />
+ <Separator/>
+ <Action name="mark_all_as_read" />
+ <Separator/>
+ <Action name="remove_duplicate_messages" />
+ <Separator/>
+ <Action name="refresh_folder" />
+ <Action name="troubleshoot_folder" />
+ <Separator/>
+ <Action name="empty" />
+ <Action name="delete_folder" />
+ <Separator/>
+ <Action name="prefer_html" />
+ <Action name="prefer_html_external_refs" />
+ <Action name="thread_messages" />
+ <Action name="thread_messages_by_subject" />
+ <Separator/>
+ <Action name="folder_mailinglist_properties" />
+ <Action name="folder_shortcut_command" />
+ <Action name="modify" />
+ </Menu>
+ <Menu noMerge="1" name="message" >
+ <text>&amp;Message</text>
+ <Action name="new_message" />
+ <Separator/>
+ <Action name="reply" />
+ <Action name="reply_all" />
+ <Menu noMerge="1" name="reply_special" >
+ <text>Reply Special</text>
+ <Action name="reply_author" />
+ <Action name="reply_list" />
+ <Action name="noquotereply" />
+ </Menu>
+ <Menu name="menubar_message_forward">
+ <text>&amp;Forward</text>
+ <ActionList name="forward_action_list"/>
+ </Menu>
+ <Action name="send_again" />
+ <Action name="edit"/>
+ <Separator/>
+ <Action name="copy_to" />
+ <Action name="move_to" />
+ <Separator/>
+ <Action name="set_status" />
+ <Action name="thread_status" />
+ <Separator/>
+ <Action name="create_filter"/>
+ <Menu name="apply_filter_actions" >
+ <text>A&amp;pply Filter</text>
+ <Action name="apply_filters" />
+ <ActionList name="menu_filter_actions" />
+ </Menu>
+ <Separator/>
+ <Action name="create_todo"/>
+ </Menu>
+ <Menu noMerge="1" name="tools">
+ <text>&amp;Tools</text>
+ <Action name="tools_start_certman"/>
+ <Action name="tools_start_kwatchgnupg"/>
+ <Separator/>
+ <Action name="tools_edit_vacation"/>
+ <Separator/>
+ <Action name="tools_debug_sieve"/>
+ <Action name="filter_log_viewer"/>
+ <Separator/>
+ <Action name="antiSpamWizard"/>
+ <Action name="antiVirusWizard"/>
+ </Menu>
+ <Menu noMerge="1" name="settings">
+ <text>&amp;Settings</text>
+ <Merge name="StandardToolBarMenuHandler"/>
+ <Action name="show_quick_search"/>
+ <Action name="filter" append="save_merge"/>
+ <Action name="popFilter" append="save_merge"/>
+ <Action name="sieveFilters" append="save_merge"/>
+ <Separator/>
+ <Action name="options_configure_keybinding" group="settings_configure"/>
+ <Action name="kmail_configure_notifications" group="settings_configure"/>
+ <Action name="options_configure_toolbars" group="settings_configure" />
+ <Action name="kmail_configure_kmail" group="settings_configure"/>
+ </Menu>
+ <Menu name="help">
+ <text>&amp;Help</text>
+ <Action name="help_kmail_welcomepage"/>
+ </Menu>
+ </MenuBar>
+ <ToolBar noMerge="1" name="mainToolBar" fullWidth="true" >
+ <text>Main Toolbar</text>
+ <Action name="new_message" />
+ <Action name="file_save_as" />
+ <Action name="file_print" />
+ <Separator/>
+ <Action name="check_mail_in" />
+ <Separator/>
+ <Action name="message_reply_menu" />
+ <Action name="message_forward"/>
+ <Separator/>
+ <Action name="go_prev_unread_message"/>
+ <Action name="go_next_unread_message"/>
+ <Separator/>
+ <Action name="move_to_trash" />
+ <Separator/>
+ <Action name="search_messages" />
+ <ActionList name="toolbar_filter_actions" />
+ <Action name="create_todo"/>
+ </ToolBar>
+</kpartgui>
diff --git a/kmail/kmail_view.desktop b/kmail/kmail_view.desktop
new file mode 100644
index 00000000..8110b86e
--- /dev/null
+++ b/kmail/kmail_view.desktop
@@ -0,0 +1,22 @@
+[Desktop Entry]
+Name=KMail
+Name[eo]=Retpoŝto
+Name[hi]=के-मेल
+Name[lv]=KPasts
+Name[mk]=КПошта
+Name[ne]=केडीई मेल
+Name[pa]=ਕੇ-ਪੱਤਰ
+Name[sv]=Kmail
+Name[ta]=Kஅஞ்சல்
+Name[th]=จัดการจดหมาย - K
+Name[zh_TW]=KMail 郵件軟體
+Type=Application
+Exec=kmail --view %u
+Icon=kmail
+DocPath=kmail/index.html
+NoDisplay=true
+MimeType=message/rfc822;application/mbox;application/x-mimearchive;
+
+X-KDE-StartupNotify=true
+X-DCOP-ServiceType=Unique
+X-DCOP-ServiceName=kmail
diff --git a/kmail/kmailicalIface.h b/kmail/kmailicalIface.h
new file mode 100644
index 00000000..81948fdf
--- /dev/null
+++ b/kmail/kmailicalIface.h
@@ -0,0 +1,175 @@
+/*
+ This file is part of KMail.
+ Copyright (c) 2003 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ Copyright (c) 2003 - 2004 Bo Thorsen <bo@sonofthor.dk>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+#ifndef KMAILICALIFACE_H
+#define KMAILICALIFACE_H
+
+#include <dcopobject.h>
+#include <qstringlist.h>
+#include <kurl.h>
+
+// yes, this is this very header - but it tells dcopidl to include it
+// in _stub.cpp and _skel.cpp files, to get the definition of the structs.
+// ### dcopidlng bug: "" is copied verbatim...
+// The kmail/ is so that it can be found by the resources easily
+#include <kmail/kmailicalIface.h>
+
+class KMailICalIface : virtual public DCOPObject
+{
+ K_DCOP
+
+public:
+k_dcop:
+ struct SubResource {
+ //dcopidl barfs on those constructors, but dcopidlng works
+ SubResource() {} // for QValueList
+ SubResource( const QString& loc, const QString& lab, bool rw, bool ar )
+ : location( loc ), label( lab ), writable( rw ), alarmRelevant( ar ) {}
+ QString location; // unique
+ QString label; // shown to the user
+ bool writable;
+ bool alarmRelevant;
+ };
+
+ /// The format of the mails containing other contents than actual mail
+ /// (like contacts, calendar etc.)
+ /// This is currently either ical/vcard, or XML.
+ /// For actual mail folders this simply to know which resource handles it
+ /// This enum matches the one defined in kmail.kcfg
+ enum StorageFormat { StorageIcalVcard, StorageXML };
+
+ /// This bitfield indicates which changes have been made in a folder, at syncing time.
+ enum FolderChanges { NoChange = 0, Contents = 1, ACL = 2 };
+
+ virtual bool isWritableFolder( const QString& type,
+ const QString& resource ) = 0;
+
+ virtual KMailICalIface::StorageFormat storageFormat( const QString& resource ) = 0;
+
+ virtual KURL getAttachment( const QString& resource,
+ Q_UINT32 sernum,
+ const QString& filename ) = 0;
+ virtual QString attachmentMimetype( const QString &resource,
+ Q_UINT32 sernum,
+ const QString &filename ) = 0;
+
+ virtual QStringList listAttachments( const QString &resource, Q_UINT32 sernum ) = 0;
+
+ /// Update a kolab storage entry. Returns the new mail serial number,
+ /// or 0 if something went wrong. Can be used for adding as well.
+ virtual Q_UINT32 update( const QString& resource,
+ Q_UINT32 sernum,
+ const QString& subject,
+ const QString& plainTextBody,
+ const QMap<QCString, QString>& customHeaders,
+ const QStringList& attachmentURLs,
+ const QStringList& attachmentMimetypes,
+ const QStringList& attachmentNames,
+ const QStringList& deletedAttachments ) = 0;
+
+ virtual bool deleteIncidenceKolab( const QString& resource,
+ Q_UINT32 sernum ) = 0;
+
+ /// Return the number of mails that need to be looked at by incidencesKolab.
+ /// This allows to call incidencesKolab in chunks.
+ virtual int incidencesKolabCount( const QString& mimetype /*ignored*/,
+ const QString& resource ) = 0;
+
+ virtual QMap<Q_UINT32, QString> incidencesKolab( const QString& mimetype,
+ const QString& resource,
+ int startIndex,
+ int nbMessages ) = 0;
+ /**
+ * Return list of subresources. @p contentsType is
+ * Mail, Calendar, Contact, Note, Task or Journal
+ */
+ virtual QValueList<KMailICalIface::SubResource> subresourcesKolab( const QString& contentsType ) = 0;
+
+ /**
+ * Trigger the creation of a new resource folder with name @param resource
+ * under parent @param.
+ * @return success or failure
+ */
+ virtual bool addSubresource( const QString& resource,
+ const QString& parent,
+ const QString& contentsType ) = 0;
+ /**
+ * Trigger the deletion of a new resource folder with id @param resource.
+ * @return success or failure
+ */
+ virtual bool removeSubresource( const QString& resource ) = 0;
+
+ /**
+ * Causes all resource folders of the given type to be synced with the server.
+ */
+ virtual bool triggerSync( const QString & ) = 0;
+
+k_dcop_signals:
+ void incidenceAdded( const QString& type, const QString& folder,
+ Q_UINT32 sernum, int format, const QString& entry );
+ void asyncLoadResult( const QMap<Q_UINT32, QString>, const QString& type,
+ const QString& folder );
+ void incidenceDeleted( const QString& type, const QString& folder,
+ const QString& uid );
+ void signalRefresh( const QString& type, const QString& folder );
+ void subresourceAdded( const QString& type, const QString& resource,
+ const QString& label, bool writable, bool alarmRelevant );
+ void subresourceDeleted( const QString& type, const QString& resource );
+};
+
+inline QDataStream& operator<<( QDataStream& str, const KMailICalIface::SubResource& subResource )
+{
+ str << subResource.location << subResource.label << subResource.writable << subResource.alarmRelevant;
+ return str;
+}
+inline QDataStream& operator>>( QDataStream& str, KMailICalIface::SubResource& subResource )
+{
+ str >> subResource.location >> subResource.label >> subResource.writable >> subResource.alarmRelevant;
+ return str;
+}
+
+inline QDataStream& operator<<( QDataStream& str, const KMailICalIface::StorageFormat& format )
+{
+ Q_UINT32 foo = format;
+ str << foo;
+ return str;
+}
+
+inline QDataStream& operator>>( QDataStream& str, KMailICalIface::StorageFormat& format )
+{
+ Q_UINT32 foo;
+ str >> foo;
+ format = ( KMailICalIface::StorageFormat )foo;
+ return str;
+}
+
+
+#endif
diff --git a/kmail/kmailicalifaceimpl.cpp b/kmail/kmailicalifaceimpl.cpp
new file mode 100644
index 00000000..38eb426f
--- /dev/null
+++ b/kmail/kmailicalifaceimpl.cpp
@@ -0,0 +1,2297 @@
+/*
+ This file is part of KMail.
+
+ Copyright (c) 2003 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ Copyright (c) 2003 - 2004 Bo Thorsen <bo@sonofthor.dk>
+ Copyright (c) 2004 Till Adam <adam@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "kmailicalifaceimpl.h"
+#include "kmfolder.h"
+#include "kmfoldertree.h"
+#include "kmfolderdir.h"
+#include "kmgroupware.h"
+#include "kmfoldermgr.h"
+#include "kmcommands.h"
+#include "kmfolderindex.h"
+#include "kmmsgdict.h"
+#include "kmmsgpart.h"
+using KMail::AccountManager;
+#include "kmfolderimap.h"
+#include "globalsettings.h"
+#include "accountmanager.h"
+#include "kmfoldercachedimap.h"
+#include "kmacctcachedimap.h"
+#include "acljobs.h"
+
+#include "scalix.h"
+
+#include <mimelib/enum.h>
+#include <mimelib/utility.h>
+#include <mimelib/body.h>
+#include <mimelib/mimepp.h>
+
+#include <qfile.h>
+#include <qmap.h>
+#include <qtextcodec.h>
+
+#include <kdebug.h>
+#include <kiconloader.h>
+#include <dcopclient.h>
+#include <kmessagebox.h>
+#include <kconfig.h>
+#include <kurl.h>
+#include <ktempfile.h>
+
+using namespace KMail;
+
+// Local helper methods
+static void vPartMicroParser( const QString& str, QString& s );
+static void reloadFolderTree();
+
+// The index in this array is the KMail::FolderContentsType enum
+static const struct {
+ const char* contentsTypeStr; // the string used in the DCOP interface
+ const char* mimetype;
+ KFolderTreeItem::Type treeItemType;
+ const char* annotation;
+ const char* translatedName;
+} s_folderContentsType[] = {
+ { "Mail", "application/x-vnd.kolab.mail", KFolderTreeItem::Other, "mail", I18N_NOOP( "Mail" ) },
+ { "Calendar", "application/x-vnd.kolab.event", KFolderTreeItem::Calendar, "event", I18N_NOOP( "Calendar" ) },
+ { "Contact", "application/x-vnd.kolab.contact", KFolderTreeItem::Contacts, "contact", I18N_NOOP( "Contacts" ) },
+ { "Note", "application/x-vnd.kolab.note", KFolderTreeItem::Notes, "note", I18N_NOOP( "Notes" ) },
+ { "Task", "application/x-vnd.kolab.task", KFolderTreeItem::Tasks, "task", I18N_NOOP( "Tasks" ) },
+ { "Journal", "application/x-vnd.kolab.journal", KFolderTreeItem::Journals, "journal", I18N_NOOP( "Journal" ) }
+};
+
+static QString folderContentsType( KMail::FolderContentsType type )
+{
+ return s_folderContentsType[type].contentsTypeStr;
+}
+
+static QString folderKolabMimeType( KMail::FolderContentsType type )
+{
+ return s_folderContentsType[type].mimetype;
+}
+
+KMailICalIfaceImpl::StorageFormat KMailICalIfaceImpl::globalStorageFormat() const {
+ return GlobalSettings::self()->theIMAPResourceStorageFormat()
+ == GlobalSettings::EnumTheIMAPResourceStorageFormat::XML ? StorageXML : StorageIcalVcard;
+}
+
+static KMail::FolderContentsType folderContentsType( const QString& type )
+{
+ for ( uint i = 0 ; i < sizeof s_folderContentsType / sizeof *s_folderContentsType; ++i )
+ if ( type == s_folderContentsType[i].contentsTypeStr )
+ return static_cast<KMail::FolderContentsType>( i );
+ return KMail::ContentsTypeMail;
+}
+
+static QString localizedDefaultFolderName( KMail::FolderContentsType type )
+{
+ return i18n( s_folderContentsType[type].translatedName );
+}
+
+const char* KMailICalIfaceImpl::annotationForContentsType( KMail::FolderContentsType type )
+{
+ return s_folderContentsType[type].annotation;
+}
+
+ExtraFolder::ExtraFolder( KMFolder* f )
+ : folder( f )
+{
+ folder->open("kmailicaliface::extrafolder");
+}
+
+ExtraFolder::~ExtraFolder()
+{
+ if ( folder )
+ folder->close("kmailicaliface::extrafolder");
+}
+
+
+/*
+ This interface has three parts to it - libkcal interface;
+ kmail interface; and helper functions.
+
+ The libkcal interface and the kmail interface have the same three
+ methods: add, delete and refresh. The only difference is that the
+ libkcal interface is used from the IMAP resource in libkcal and
+ the kmail interface is used from the groupware object in kmail.
+*/
+
+KMailICalIfaceImpl::KMailICalIfaceImpl()
+ : DCOPObject( "KMailICalIface" ), QObject( 0, "KMailICalIfaceImpl" ),
+ mContacts( 0 ), mCalendar( 0 ), mNotes( 0 ), mTasks( 0 ), mJournals( 0 ),
+ mFolderLanguage( 0 ), mFolderParentDir( 0 ), mFolderType( KMFolderTypeUnknown ),
+ mUseResourceIMAP( false ), mResourceQuiet( false ), mHideFolders( true )
+{
+ // Listen to config changes
+ connect( kmkernel, SIGNAL( configChanged() ), this, SLOT( readConfig() ) );
+ connect( kmkernel, SIGNAL( folderRemoved( KMFolder* ) ),
+ this, SLOT( slotFolderRemoved( KMFolder* ) ) );
+
+ mExtraFolders.setAutoDelete( true );
+ mAccumulators.setAutoDelete( true );
+}
+
+
+/* libkcal part of the interface, called from the resources using this
+ * when incidences are added or deleted */
+
+// Helper function to find an attachment of a given mimetype
+// Can't use KMMessage::findDwBodyPart since it only works with known mimetypes.
+static DwBodyPart* findBodyPartByMimeType( const KMMessage& msg, const char* sType, const char* sSubtype, bool startsWith = false )
+{
+ // quickly searching for our message part: since Kolab parts are
+ // top-level parts we do *not* have to travel into embedded multiparts
+ DwBodyPart* part = msg.getFirstDwBodyPart();
+ while( part ){
+ // kdDebug() << part->Headers().ContentType().TypeStr().c_str() << " "
+ // << part->Headers().ContentType().SubtypeStr().c_str() << endl;
+ if ( part->hasHeaders() ) {
+ DwMediaType& contentType = part->Headers().ContentType();
+ if ( startsWith ) {
+ if ( contentType.TypeStr() == sType
+ && QString( contentType.SubtypeStr().c_str() ).startsWith( sSubtype ) )
+ return part;
+ }
+ else
+ if ( contentType.TypeStr() == sType
+ && contentType.SubtypeStr() == sSubtype )
+ return part;
+ }
+ part = part->Next();
+ }
+ return 0;
+}
+
+// Helper function to find an attachment with a given filename
+static DwBodyPart* findBodyPart( const KMMessage& msg, const QString& attachmentName )
+{
+ // quickly searching for our message part: since Kolab parts are
+ // top-level parts we do *not* have to travel into embedded multiparts
+ for ( DwBodyPart* part = msg.getFirstDwBodyPart(); part; part = part->Next() ) {
+ //kdDebug(5006) << "findBodyPart: - " << part->Headers().ContentDisposition().Filename().c_str() << endl;
+ if ( part->hasHeaders()
+ && attachmentName == part->Headers().ContentDisposition().Filename().c_str() )
+ return part;
+ if ( part->hasHeaders() && attachmentName == part->Headers().ContentType().Name().c_str() )
+ return part;
+ }
+ return 0;
+}
+
+#if 0
+static void debugBodyParts( const char* foo, const KMMessage& msg )
+{
+ kdDebug(5006) << "--debugBodyParts " << foo << "--" << endl;
+ for ( DwBodyPart* part = msg.getFirstDwBodyPart(); part; part = part->Next() ) {
+ if ( part->hasHeaders() ) {
+ kdDebug(5006) << " bodypart: " << part << endl;
+ kdDebug(5006) << " " << part->Headers().AsString().c_str() << endl;
+ }
+ else
+ kdDebug(5006) << " part " << part << " has no headers" << endl;
+ }
+}
+#else
+inline static void debugBodyParts( const char*, const KMMessage& ) {}
+#endif
+
+
+// Add (or overwrite, resp.) an attachment in an existing mail,
+// attachments must be local files, they are identified by their names.
+// If lookupByName if false the attachment to replace is looked up by mimetype.
+// return value: wrong if attachment could not be added/updated
+bool KMailICalIfaceImpl::updateAttachment( KMMessage& msg,
+ const QString& attachmentURL,
+ const QString& attachmentName,
+ const QString& attachmentMimetype,
+ bool lookupByName )
+{
+ kdDebug(5006) << "KMailICalIfaceImpl::updateAttachment( " << attachmentURL << " )" << endl;
+
+ bool bOK = false;
+
+ KURL url( attachmentURL );
+ if ( url.isValid() && url.isLocalFile() ) {
+ const QString fileName( url.path() );
+ QFile file( fileName );
+ if( file.open( IO_ReadOnly ) ) {
+ QByteArray rawData = file.readAll();
+ file.close();
+
+ // create the new message part with data read from temp file
+ KMMessagePart msgPart;
+ msgPart.setName( attachmentName );
+
+ const int iSlash = attachmentMimetype.find('/');
+ const QCString sType = attachmentMimetype.left( iSlash ).latin1();
+ const QCString sSubtype = attachmentMimetype.mid( iSlash+1 ).latin1();
+ msgPart.setTypeStr( sType );
+ msgPart.setSubtypeStr( sSubtype );
+ QCString ctd("attachment;\n filename=\"");
+ ctd.append( attachmentName.latin1() );
+ ctd.append("\"");
+ msgPart.setContentDisposition( ctd );
+ QValueList<int> dummy;
+ msgPart.setBodyAndGuessCte( rawData, dummy );
+ msgPart.setPartSpecifier( fileName );
+
+ DwBodyPart* newPart = msg.createDWBodyPart( &msgPart );
+ // This whole method is a bit special. We mix code for writing and code for reading.
+ // E.g. we need to parse the content-disposition again for ContentDisposition().Filename()
+ // to work later on.
+ newPart->Headers().ContentDisposition().Parse();
+
+ DwBodyPart* part = lookupByName ? findBodyPart( msg, attachmentName )
+ : findBodyPartByMimeType( msg, sType, sSubtype );
+ if ( part ) {
+ // Make sure the replacing body part is pointing
+ // to the same next part as the original body part.
+ newPart->SetNext( part->Next() );
+ // call DwBodyPart::operator =
+ // which calls DwEntity::operator =
+ *part = *newPart;
+ delete newPart;
+ msg.setNeedsAssembly();
+ kdDebug(5006) << "Attachment " << attachmentName << " updated." << endl;
+ } else {
+ msg.addDwBodyPart( newPart );
+ kdDebug(5006) << "Attachment " << attachmentName << " added." << endl;
+ }
+ bOK = true;
+ }else{
+ kdDebug(5006) << "Attachment " << attachmentURL << " can not be read." << endl;
+ }
+ }else{
+ kdDebug(5006) << "Attachment " << attachmentURL << " not a local file." << endl;
+ }
+
+ return bOK;
+}
+
+// Look for the attachment with the right mimetype
+bool KMailICalIfaceImpl::kolabXMLFoundAndDecoded( const KMMessage& msg, const QString& mimetype, QString& s )
+{
+ const int iSlash = mimetype.find('/');
+ const QCString sType = mimetype.left( iSlash ).latin1();
+ const QCString sSubtype = mimetype.mid( iSlash+1 ).latin1();
+ DwBodyPart* part = findBodyPartByMimeType( msg, sType, sSubtype, true /* starts with sSubtype, to accept application/x-vnd.kolab.contact.distlist */ );
+ if ( part ) {
+ KMMessagePart msgPart;
+ KMMessage::bodyPart(part, &msgPart);
+ s = msgPart.bodyToUnicode( QTextCodec::codecForName( "utf8" ) );
+ return true;
+ }
+ return false;
+}
+
+// Delete an attachment in an existing mail.
+// return value: wrong if attachment could not be deleted
+//
+// This code could be optimized: for now we just replace
+// the attachment by an empty dummy attachment since Mimelib
+// does not provide an option for deleting attachments yet.
+bool KMailICalIfaceImpl::deleteAttachment( KMMessage& msg,
+ const QString& attachmentName )
+{
+ kdDebug(5006) << "KMailICalIfaceImpl::deleteAttachment( " << attachmentName << " )" << endl;
+
+ bool bOK = false;
+
+ // quickly searching for our message part: since Kolab parts are
+ // top-level parts we do *not* have to travel into embedded multiparts
+ DwBodyPart* part = findBodyPart( msg, attachmentName );
+ if ( part ) {
+ msg.getTopLevelPart()->Body().RemoveBodyPart( part );
+ delete part;
+ msg.setNeedsAssembly();
+ kdDebug(5006) << "Attachment deleted." << endl;
+ bOK = true;
+ }
+
+ if( !bOK ){
+ kdDebug(5006) << "Attachment " << attachmentName << " not found." << endl;
+ }
+
+ return bOK;
+}
+
+static void setIcalVcardContentTypeHeader( KMMessage *msg, KMail::FolderContentsType t, KMFolder *folder )
+{
+ KMAcctCachedImap::GroupwareType groupwareType = KMAcctCachedImap::GroupwareKolab;
+
+ KMFolderCachedImap *imapFolder = dynamic_cast<KMFolderCachedImap*>( folder->storage() );
+ if ( imapFolder )
+ groupwareType = imapFolder->account()->groupwareType();
+
+ msg->setType( DwMime::kTypeText );
+ if ( t == KMail::ContentsTypeCalendar || t == KMail::ContentsTypeTask
+ || t == KMail::ContentsTypeJournal ) {
+ msg->setSubtype( DwMime::kSubtypeVCal );
+
+ if ( groupwareType == KMAcctCachedImap::GroupwareKolab )
+ msg->setHeaderField("Content-Type",
+ "text/calendar; method=REQUEST; charset=\"utf-8\"");
+ else if ( groupwareType == KMAcctCachedImap::GroupwareScalix )
+ msg->setHeaderField("Content-Type",
+ "text/calendar; method=PUBLISH; charset=\"UTF-8\"");
+
+ } else if ( t == KMail::ContentsTypeContact ) {
+ msg->setSubtype( DwMime::kSubtypeXVCard );
+ if ( groupwareType == KMAcctCachedImap::GroupwareKolab )
+ msg->setHeaderField( "Content-Type", "Text/X-VCard; charset=\"utf-8\"" );
+ else if ( groupwareType == KMAcctCachedImap::GroupwareScalix )
+ msg->setHeaderField( "Content-Type", "application/scalix-properties; charset=\"UTF-8\"" );
+ } else {
+ kdWarning(5006) << k_funcinfo << "Attempt to write non-groupware contents to folder" << endl;
+ }
+}
+
+static void setXMLContentTypeHeader( KMMessage *msg, const QString plainTextBody )
+{
+ // add a first body part to be displayed by all mailer
+ // than can NOT display Kolab data: no matter if these
+ // mailers are MIME compliant or not
+ KMMessagePart firstPart;
+ firstPart.setType( DwMime::kTypeText );
+ firstPart.setSubtype( DwMime::kSubtypePlain );
+ msg->removeHeaderField( "Content-Type" );
+ msg->setType( DwMime::kTypeMultipart );
+ msg->setSubtype( DwMime::kSubtypeMixed );
+ msg->headers().ContentType().CreateBoundary( 0 );
+ msg->headers().ContentType().Assemble();
+ firstPart.setBodyFromUnicode( plainTextBody );
+ msg->addBodyPart( &firstPart );
+}
+
+// Store a new entry that was received from the resource
+Q_UINT32 KMailICalIfaceImpl::addIncidenceKolab( KMFolder& folder,
+ const QString& subject,
+ const QString& plainTextBody,
+ const QMap<QCString, QString>& customHeaders,
+ const QStringList& attachmentURLs,
+ const QStringList& attachmentNames,
+ const QStringList& attachmentMimetypes )
+{
+ kdDebug(5006) << "KMailICalIfaceImpl::addIncidenceKolab( " << attachmentNames << " )" << endl;
+
+ Q_UINT32 sernum = 0;
+ bool bAttachOK = true;
+
+ // Make a new message for the incidence
+ KMMessage* msg = new KMMessage();
+ msg->initHeader();
+ msg->setSubject( subject );
+ msg->setAutomaticFields( true );
+
+ QMap<QCString, QString>::ConstIterator ith = customHeaders.begin();
+ const QMap<QCString, QString>::ConstIterator ithEnd = customHeaders.end();
+ for ( ; ith != ithEnd ; ++ith ) {
+ msg->setHeaderField( ith.key(), ith.data() );
+ }
+ // In case of the ical format, simply add the plain text content with the
+ // right content type
+ if ( storageFormat( &folder ) == StorageXML ) {
+ setXMLContentTypeHeader( msg, plainTextBody );
+ } else if ( storageFormat( &folder ) == StorageIcalVcard ) {
+ const KMail::FolderContentsType t = folder.storage()->contentsType();
+ setIcalVcardContentTypeHeader( msg, t, &folder );
+ msg->setBodyEncoded( plainTextBody.utf8() );
+ } else {
+ kdWarning(5006) << k_funcinfo << "Attempt to write to folder with unknown storage type" << endl;
+ }
+
+ Q_ASSERT( attachmentMimetypes.count() == attachmentURLs.count() );
+ Q_ASSERT( attachmentNames.count() == attachmentURLs.count() );
+ // Add all attachments by reading them from their temp. files
+ QStringList::ConstIterator itmime = attachmentMimetypes.begin();
+ QStringList::ConstIterator iturl = attachmentURLs.begin();
+ for( QStringList::ConstIterator itname = attachmentNames.begin();
+ itname != attachmentNames.end()
+ && itmime != attachmentMimetypes.end()
+ && iturl != attachmentURLs.end();
+ ++itname, ++iturl, ++itmime ){
+ bool byname = !(*itmime).startsWith( "application/x-vnd.kolab." );
+ if( !updateAttachment( *msg, *iturl, *itname, *itmime, byname ) ){
+ kdWarning(5006) << "Attachment error, can not add Incidence." << endl;
+ bAttachOK = false;
+ break;
+ }
+ }
+
+ if( bAttachOK ){
+ // Mark the message as read and store it in the folder
+ msg->cleanupHeader();
+ //debugBodyParts( "after cleanup", *msg );
+ msg->touch();
+ if ( folder.addMsg( msg ) == 0 )
+ // Message stored
+ sernum = msg->getMsgSerNum();
+ kdDebug(5006) << "addIncidenceKolab(): Message done and saved. Sernum: "
+ << sernum << endl;
+
+ //debugBodyParts( "after addMsg", *msg );
+ addFolderChange( &folder, Contents );
+ syncFolder( &folder );
+ } else
+ kdError(5006) << "addIncidenceKolab(): Message *NOT* saved!\n";
+
+ return sernum;
+}
+
+bool KMailICalIfaceImpl::deleteIncidenceKolab( const QString& resource,
+ Q_UINT32 sernum )
+{
+ // Find the message from the serial number and delete it.
+ if( !mUseResourceIMAP )
+ return false;
+
+ kdDebug(5006) << "KMailICalIfaceImpl::deleteIncidenceKolab( "
+ << resource << ", " << sernum << ")\n";
+
+ // Find the folder
+ KMFolder* f = findResourceFolder( resource );
+ if( !f ) {
+ kdError(5006) << "deleteIncidenceKolab(" << resource << ") : Not an IMAP resource folder" << endl;
+ return false;
+ }
+
+ bool rc = false;
+
+ KMMessage* msg = findMessageBySerNum( sernum, f );
+ if( msg ) {
+ // Message found - delete it and return happy
+ deleteMsg( msg );
+ syncFolder( f );
+ rc = true;
+ } else {
+ kdDebug(5006) << "Message not found, cannot remove serNum " << sernum << endl;
+ }
+ return rc;
+}
+
+
+int KMailICalIfaceImpl::incidencesKolabCount( const QString& mimetype,
+ const QString& resource )
+{
+ Q_UNUSED( mimetype ); // honouring that would be too slow...
+
+ if( !mUseResourceIMAP )
+ return 0;
+
+ KMFolder* f = findResourceFolder( resource );
+ if( !f ) {
+ kdError(5006) << "incidencesKolab(" << resource << ") : Not an IMAP resource folder" << endl;
+ return 0;
+ }
+
+ f->open("kolabcount");
+ int n = f->count();
+ f->close("kolabcount");
+ kdDebug(5006) << "KMailICalIfaceImpl::incidencesKolabCount( "
+ << resource << " ) returned " << n << endl;
+ return n;
+}
+
+QMap<Q_UINT32, QString> KMailICalIfaceImpl::incidencesKolab( const QString& mimetype,
+ const QString& resource,
+ int startIndex,
+ int nbMessages )
+{
+ /// Get the mimetype attachments from this folder. Returns a
+ /// QMap with serialNumber/attachment pairs.
+ /// (serial numbers of the mail are provided for easier later update)
+
+ QMap<Q_UINT32, QString> aMap;
+ if( !mUseResourceIMAP )
+ return aMap;
+
+ KMFolder* f = findResourceFolder( resource );
+ if( !f ) {
+ kdError(5006) << "incidencesKolab(" << resource << ") : Not an IMAP resource folder" << endl;
+ return aMap;
+ }
+
+ f->open( "incidences" );
+
+ 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
+ bool unget = !f->isMessage(i);
+ KMMessage* msg = f->getMsg( i );
+#else // faster
+ KMMessage* msg = f->storage()->readTemporaryMsg(i);
+#endif
+ if ( msg ) {
+ const int iSlash = mimetype.find('/');
+ const QCString sType = mimetype.left( iSlash ).latin1();
+ const QCString sSubtype = mimetype.mid( iSlash+1 ).latin1();
+ if ( sType.isEmpty() || sSubtype.isEmpty() ) {
+ kdError(5006) << mimetype << " not an type/subtype combination" << endl;
+ } else {
+ DwBodyPart* dwPart = findBodyPartByMimeType( *msg, sType, sSubtype );
+ if ( dwPart ) {
+ KMMessagePart msgPart;
+ KMMessage::bodyPart(dwPart, &msgPart);
+ aMap.insert(msg->getMsgSerNum(), msgPart.bodyToUnicode( QTextCodec::codecForName( "utf8" ) ));
+ } else {
+ // Check if the whole message has the right types. This is what
+ // happens in the case of ical storage, where the whole mail is
+ // the data
+ const QCString type( msg->typeStr() );
+ const QCString subtype( msg->subtypeStr() );
+ if (type.lower() == sType && subtype.lower() == sSubtype ) {
+ aMap.insert( msg->getMsgSerNum(), msg->bodyToUnicode() );
+ }
+ // This is *not* an error: it may be that not all of the messages
+ // have a message part that is matching the wanted MIME type
+ }
+ }
+#if 0
+ if( unget ) f->unGetMsg(i);
+#else
+ delete msg;
+#endif
+ }
+ }
+ f->close( "incidences" );
+ return aMap;
+}
+
+
+/* Called when a message that was downloaded from an online imap folder
+ * arrives. Needed when listing incidences on online account folders. */
+// TODO: Till, port me
+void KMailICalIfaceImpl::slotMessageRetrieved( KMMessage* msg )
+{
+ if( !msg ) return;
+
+ KMFolder *parent = msg->parent();
+ Q_ASSERT( parent );
+ Q_UINT32 sernum = msg->getMsgSerNum();
+
+ // do we have an accumulator for this folder?
+ Accumulator *ac = mAccumulators.find( parent->location() );
+ if( ac ) {
+ QString s;
+ if ( !vPartFoundAndDecoded( msg, s ) ) return;
+ QString uid( "UID" );
+ vPartMicroParser( s, uid );
+ const Q_UINT32 sernum = msg->getMsgSerNum();
+ mUIDToSerNum.insert( uid, sernum );
+ ac->add( s );
+ if( ac->isFull() ) {
+ /* if this was the last one we were waiting for, tell the resource
+ * about the new incidences and clean up. */
+ //asyncLoadResult( ac->incidences, ac->type, ac->folder );
+ mAccumulators.remove( ac->folder ); // autodelete
+ }
+ } else {
+ /* We are not accumulating for this folder, so this one was added
+ * by KMail. Do your thang. */
+ slotIncidenceAdded( msg->parent(), msg->getMsgSerNum() );
+ }
+
+ if ( mTheUnGetMes.contains( sernum ) ) {
+ mTheUnGetMes.remove( sernum );
+ int i = 0;
+ KMFolder* folder = 0;
+ KMMsgDict::instance()->getLocation( sernum, &folder, &i );
+ folder->unGetMsg( i );
+ }
+}
+
+static int dimapAccountCount()
+{
+ KMail::AccountManager *mgr = kmkernel->acctMgr();
+ KMAccount *account = mgr->first();
+ int count = 0;
+ while ( account ) {
+ if ( dynamic_cast<KMAcctCachedImap*>( account ) )
+ ++count;
+ account = mgr->next();
+ }
+ return count;
+}
+
+static QString subresourceLabelForPresentation( const KMFolder * folder )
+{
+ QString label = folder->prettyURL();
+ QStringList parts = QStringList::split( QString::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.
+ if ( parts[1] == QString::fromLatin1("user") ) {
+ QStringList remainder(parts);
+ remainder.pop_front();
+ remainder.pop_front();
+ remainder.pop_front();
+ label = i18n("%1's %2")
+ .arg( parts[2] )
+ .arg( remainder.join( QString::fromLatin1("/") ) );
+ }
+ // Another special case is our own folders, under the imap INBOX, make
+ // those prettier too
+ const KMFolder *parent = folder;
+ while ( parent->parent() && parent->parent()->owner() ) {
+ parent = parent->parent()->owner();
+ if ( parent->isSystemFolder() ) {
+ QStringList remainder(parts);
+ remainder.pop_front();
+ remainder.pop_front();
+ if ( dimapAccountCount() > 1 ) {
+ label = i18n( "My %1 (%2)")
+ .arg( remainder.join( QString::fromLatin1("/") ),
+ static_cast<const KMFolderCachedImap*>( folder->storage() )->account()->name() );
+ } else {
+ label = i18n("My %1")
+ .arg( remainder.join( QString::fromLatin1("/") ) );
+ }
+ break;
+ }
+ }
+ return label;
+}
+
+/* list all available subresources */
+QValueList<KMailICalIfaceImpl::SubResource> KMailICalIfaceImpl::subresourcesKolab( const QString& contentsType )
+{
+ QValueList<SubResource> subResources;
+
+ // Add the default one
+ KMFolder* f = folderFromType( contentsType, QString::null );
+ if ( f ) {
+ subResources.append( SubResource( f->location(), subresourceLabelForPresentation( f ),
+ !f->isReadOnly(), folderIsAlarmRelevant( f ) ) );
+ kdDebug(5006) << "Adding(1) folder " << f->location() << " " <<
+ ( f->isReadOnly() ? "readonly" : "" ) << endl;
+ }
+
+ // get the extra ones
+ const KMail::FolderContentsType t = folderContentsType( contentsType );
+ QDictIterator<ExtraFolder> it( mExtraFolders );
+ for ( ; it.current(); ++it ){
+ f = it.current()->folder;
+ if ( f && f->storage()->contentsType() == t ) {
+ subResources.append( SubResource( f->location(), subresourceLabelForPresentation( f ),
+ !f->isReadOnly(), folderIsAlarmRelevant( f ) ) );
+ kdDebug(5006) << "Adding(2) folder " << f->location() << " " <<
+ ( f->isReadOnly() ? "readonly" : "" ) << endl;
+ }
+ }
+
+ if ( subResources.isEmpty() )
+ kdDebug(5006) << "subresourcesKolab: No folder found for " << contentsType << endl;
+ return subResources;
+}
+
+bool KMailICalIfaceImpl::triggerSync( const QString& contentsType )
+{
+ kdDebug(5006) << k_funcinfo << endl;
+ QValueList<KMailICalIfaceImpl::SubResource> folderList = subresourcesKolab( contentsType );
+ for ( QValueList<KMailICalIfaceImpl::SubResource>::const_iterator it( folderList.begin() ),
+ end( folderList.end() );
+ it != end ; ++it ) {
+ KMFolder * const f = findResourceFolder( (*it).location );
+ if ( !f ) continue;
+ if ( f->folderType() == KMFolderTypeImap || f->folderType() == KMFolderTypeCachedImap ) {
+ if ( !kmkernel->askToGoOnline() ) {
+ return false;
+ }
+ }
+
+ if ( f->folderType() == KMFolderTypeImap ) {
+ KMFolderImap *imap = static_cast<KMFolderImap*>( f->storage() );
+ imap->getAndCheckFolder();
+ } else if ( f->folderType() == KMFolderTypeCachedImap ) {
+ KMFolderCachedImap* cached = static_cast<KMFolderCachedImap*>( f->storage() );
+ cached->account()->processNewMailSingleFolder( f );
+ }
+ }
+ return true;
+}
+
+/* Used by the resource to query whether folders are writable. */
+bool KMailICalIfaceImpl::isWritableFolder( const QString& type,
+ const QString& resource )
+{
+ KMFolder* f = folderFromType( type, resource );
+ if ( !f )
+ // Definitely not writable
+ return false;
+
+ return !f->isReadOnly();
+}
+
+/* Used by the resource to query the storage format of the folder. */
+KMailICalIfaceImpl::StorageFormat KMailICalIfaceImpl::storageFormat( const QString& resource )
+{
+ StorageFormat format;
+ KMFolder* f = findResourceFolder( resource );
+ if ( f )
+ format = storageFormat( f );
+ else
+ format = globalStorageFormat();
+ return format;
+}
+
+/**
+ // This finds the message with serial number "sernum", sets the
+ // xml attachments to hold the contents of "xml", and updates all
+ // attachments.
+ // The mail can have additional attachments, and these are not
+ // touched! They belong to other clients - like Outlook
+ // So we delete all the attachments listed in the
+ // "deletedAttachments" arg, and then update/add all the attachments
+ // given by the urllist attachments.
+
+ // If the mail does not already exist, id will not be a valid serial
+ // number, and the mail is just added instead. In this case
+ // the deletedAttachments can be forgotten.
+*/
+Q_UINT32 KMailICalIfaceImpl::update( const QString& resource,
+ Q_UINT32 sernum,
+ const QString& subject,
+ const QString& plainTextBody,
+ const QMap<QCString, QString>& customHeaders,
+ const QStringList& attachmentURLs,
+ const QStringList& attachmentMimetypes,
+ const QStringList& attachmentNames,
+ const QStringList& deletedAttachments )
+{
+ Q_UINT32 rc = 0;
+
+ if( !mUseResourceIMAP )
+ return rc;
+
+ Q_ASSERT( !resource.isEmpty() );
+
+ kdDebug(5006) << "KMailICalIfaceImpl::update( " << resource << ", " << sernum << " )\n";
+ kdDebug(5006) << attachmentURLs << "\n";
+ kdDebug(5006) << attachmentMimetypes << "\n";
+ kdDebug(5006) << attachmentNames << "\n";
+ kdDebug(5006) << "deleted attachments:" << deletedAttachments << "\n";
+
+ // Find the folder
+ KMFolder* f = findResourceFolder( resource );
+ if( !f ) {
+ kdError(5006) << "update(" << resource << ") : Not an IMAP resource folder" << endl;
+ return rc;
+ }
+
+ f->open( "ifaceupdate" );
+
+ KMMessage* msg = 0;
+ if ( sernum != 0 ) {
+ msg = findMessageBySerNum( sernum, f );
+ if ( !msg ) return 0;
+ // Message found - make a copy and update it:
+ KMMessage* newMsg = new KMMessage( *msg );
+ newMsg->setSubject( subject );
+ QMap<QCString, QString>::ConstIterator ith = customHeaders.begin();
+ const QMap<QCString, QString>::ConstIterator ithEnd = customHeaders.begin();
+ for ( ; ith != ithEnd ; ++ith )
+ newMsg->setHeaderField( ith.key(), ith.data() );
+ newMsg->setParent( 0 ); // workaround strange line in KMMsgBase::assign. newMsg is not in any folder yet.
+ // Note that plainTextBody isn't used in this branch. We assume it's still valid from when the mail was created.
+
+ // Delete some attachments according to list
+ for( QStringList::ConstIterator it = deletedAttachments.begin();
+ it != deletedAttachments.end();
+ ++it ){
+ if( !deleteAttachment( *newMsg, *it ) ){
+ // Note: It is _not_ an error if an attachment was already deleted.
+ }
+ }
+
+ const KMail::FolderContentsType t = f->storage()->contentsType();
+ const QCString type = msg->typeStr();
+ const QCString subtype = msg->subtypeStr();
+ const bool messageWasIcalVcardFormat = ( type.lower() == "text" &&
+ ( subtype.lower() == "calendar" || subtype.lower() == "x-vcard" ) );
+
+ if ( storageFormat( f ) == StorageIcalVcard ) {
+ //kdDebug(5006) << k_funcinfo << " StorageFormatIcalVcard " << endl;
+ if ( !messageWasIcalVcardFormat ) {
+ setIcalVcardContentTypeHeader( newMsg, t, f );
+ }
+ newMsg->setBodyEncoded( plainTextBody.utf8() );
+ } else if ( storageFormat( f ) == StorageXML ) {
+ if ( messageWasIcalVcardFormat ) {
+ // this was originally an ical event, but the folder changed to xml,
+ // convert
+ setXMLContentTypeHeader( newMsg, plainTextBody );
+ }
+ //kdDebug(5006) << k_funcinfo << " StorageFormatXML " << endl;
+ // Add all attachments by reading them from their temp. files
+ QStringList::ConstIterator iturl = attachmentURLs.begin();
+ QStringList::ConstIterator itmime = attachmentMimetypes.begin();
+ QStringList::ConstIterator itname = attachmentNames.begin();
+ for( ;
+ iturl != attachmentURLs.end()
+ && itmime != attachmentMimetypes.end()
+ && itname != attachmentNames.end();
+ ++iturl, ++itname, ++itmime ){
+ bool byname = !(*itmime).startsWith( "application/x-vnd.kolab." );
+ if( !updateAttachment( *newMsg, *iturl, *itname, *itmime, byname ) ){
+ kdDebug(5006) << "Attachment error, can not update attachment " << *iturl << endl;
+ break;
+ }
+ }
+ }
+
+ //debugBodyParts( "in update, before cleanup", *newMsg );
+
+ // This is necessary for the headers to be readable later on
+ newMsg->cleanupHeader();
+
+ //debugBodyParts( "in update, after cleanup", *newMsg );
+
+ deleteMsg( msg );
+ if ( f->addMsg( newMsg ) == 0 ) {
+ // Message stored
+ rc = newMsg->getMsgSerNum();
+ kdDebug(5006) << "forget about " << sernum << ", it's " << rc << " now" << endl;
+ }
+ addFolderChange( f, Contents );
+ syncFolder( f );
+ } else {
+ // Message not found - store it newly
+ rc = addIncidenceKolab( *f, subject, plainTextBody, customHeaders,
+ attachmentURLs,
+ attachmentNames,
+ attachmentMimetypes );
+ }
+
+ f->close("ifaceupdate");
+ return rc;
+}
+
+KURL KMailICalIfaceImpl::getAttachment( const QString& resource,
+ Q_UINT32 sernum,
+ const QString& filename )
+{
+ // This finds the attachment with the filename, saves it to a
+ // temp file and returns a URL to it. It's up to the resource
+ // to delete the tmp file later.
+ if( !mUseResourceIMAP )
+ return KURL();
+
+ kdDebug(5006) << "KMailICalIfaceImpl::getAttachment( "
+ << resource << ", " << sernum << ", " << filename << " )\n";
+
+ // Find the folder
+ KMFolder* f = findResourceFolder( resource );
+ if( !f ) {
+ kdError(5006) << "getAttachment(" << resource << ") : Not an IMAP resource folder" << endl;
+ return KURL();
+ }
+ if ( storageFormat( f ) != StorageXML ) {
+ kdError(5006) << "getAttachment(" << resource << ") : Folder has wrong storage format " << storageFormat( f ) << endl;
+ return KURL();
+ }
+
+ KURL url;
+
+ bool bOK = false;
+ bool quiet = mResourceQuiet;
+ mResourceQuiet = true;
+
+ KMMessage* msg = findMessageBySerNum( sernum, f );
+ if( msg ) {
+ // Message found - look for the attachment:
+
+ DwBodyPart* part = findBodyPart( *msg, filename );
+ if ( part ) {
+ // Save the contents of the attachment.
+ KMMessagePart aPart;
+ msg->bodyPart( part, &aPart );
+ QByteArray rawData( aPart.bodyDecodedBinary() );
+
+ KTempFile file;
+ file.file()->writeBlock( rawData.data(), rawData.size() );
+
+ url.setPath( file.name() );
+
+ bOK = true;
+ }
+
+ if( !bOK ){
+ kdDebug(5006) << "Attachment " << filename << " not found." << endl;
+ }
+ }else{
+ kdDebug(5006) << "Message not found." << endl;
+ }
+
+ mResourceQuiet = quiet;
+ return url;
+}
+
+QString KMailICalIfaceImpl::attachmentMimetype( const QString & resource,
+ Q_UINT32 sernum,
+ const QString & filename )
+{
+ if( !mUseResourceIMAP )
+ return QString();
+ KMFolder* f = findResourceFolder( resource );
+ if( !f || storageFormat( f ) != StorageXML ) {
+ kdError(5006) << "attachmentMimetype(" << resource << ") : Wrong folder" << endl;
+ return QString();
+ }
+
+ KMMessage* msg = findMessageBySerNum( sernum, f );
+ if( msg ) {
+ // Message found - look for the attachment:
+ DwBodyPart* part = findBodyPart( *msg, filename );
+ if ( part ) {
+ KMMessagePart kmPart;
+ msg->bodyPart( part, &kmPart );
+ return QString( kmPart.typeStr() ) + "/" + QString( kmPart.subtypeStr() );
+ } else {
+ kdDebug(5006) << "Attachment " << filename << " not found." << endl;
+ }
+ } else {
+ kdDebug(5006) << "Message not found." << endl;
+ }
+
+ return QString();
+}
+
+QStringList KMailICalIfaceImpl::listAttachments(const QString & resource, Q_UINT32 sernum)
+{
+ QStringList rv;
+ if( !mUseResourceIMAP )
+ return rv;
+
+ // Find the folder
+ KMFolder* f = findResourceFolder( resource );
+ if( !f ) {
+ kdError(5006) << "listAttachments(" << resource << ") : Not an IMAP resource folder" << endl;
+ return rv;
+ }
+ if ( storageFormat( f ) != StorageXML ) {
+ kdError(5006) << "listAttachment(" << resource << ") : Folder has wrong storage format " << storageFormat( f ) << endl;
+ return rv;
+ }
+
+ KMMessage* msg = findMessageBySerNum( sernum, f );
+ if( msg ) {
+ for ( DwBodyPart* part = msg->getFirstDwBodyPart(); part; part = part->Next() ) {
+ if ( part->hasHeaders() ) {
+ QString name;
+ DwMediaType& contentType = part->Headers().ContentType();
+ if ( QString( contentType.SubtypeStr().c_str() ).startsWith( "x-vnd.kolab." )
+ || QString( contentType.SubtypeStr().c_str() ).contains( "tnef" ) )
+ continue;
+ if ( !part->Headers().ContentDisposition().Filename().empty() )
+ name = part->Headers().ContentDisposition().Filename().c_str();
+ else if ( !contentType.Name().empty() )
+ name = contentType.Name().c_str();
+ if ( !name.isEmpty() )
+ rv.append( name );
+ }
+ }
+ } else {
+ kdDebug(5006) << "Message not found." << endl;
+ }
+
+ return rv;
+}
+
+
+// ============================================================================
+
+/* KMail part of the interface. These slots are connected to the resource
+ * folders and inform us of folders or incidences in them changing, being
+ * added or going away. */
+
+void KMailICalIfaceImpl::slotFolderRemoved( KMFolder* folder )
+{
+ // pretend the folder just changed back to the mail type, which
+ // does the right thing, namely remove resource
+ folderContentsTypeChanged( folder, KMail::ContentsTypeMail );
+ KConfigGroup configGroup( kmkernel->config(), "GroupwareFolderInfo" );
+ configGroup.deleteEntry( folder->idString() + "-storageFormat" );
+ configGroup.deleteEntry( folder->idString() + "-changes" );
+}
+
+// KMail added a file to one of the groupware folders
+void KMailICalIfaceImpl::slotIncidenceAdded( KMFolder* folder,
+ Q_UINT32 sernum )
+{
+ if( mResourceQuiet || !mUseResourceIMAP )
+ return;
+
+// kdDebug(5006) << "KMailICalIfaceImpl::slotIncidenceAdded" << endl;
+ QString type = folderContentsType( folder->storage()->contentsType() );
+ if( type.isEmpty() ) {
+ kdError(5006) << "Not an IMAP resource folder" << endl;
+ return;
+ }
+ // Get the index of the mail
+ int i = 0;
+ KMFolder* aFolder = 0;
+ KMMsgDict::instance()->getLocation( sernum, &aFolder, &i );
+ assert( folder == aFolder );
+
+ bool unget = !folder->isMessage( i );
+ QString s;
+ QString uid( "UID" );
+ KMMessage *msg = folder->getMsg( i );
+ if( !msg ) return;
+ if( msg->isComplete() ) {
+
+ bool ok = false;
+ StorageFormat format = storageFormat( folder );
+ switch( format ) {
+ case StorageIcalVcard:
+ // Read the iCal or vCard
+ ok = vPartFoundAndDecoded( msg, s );
+ if ( ok )
+ vPartMicroParser( s, uid );
+ break;
+ case StorageXML:
+ // Read the XML from the attachment with the given mimetype
+ if ( kolabXMLFoundAndDecoded( *msg,
+ folderKolabMimeType( folder->storage()->contentsType() ), s ) ) {
+ uid = msg->subject();
+ ok = true;
+ }
+ break;
+ }
+ if ( !ok ) {
+ if ( unget )
+ folder->unGetMsg( i );
+ return;
+ }
+ const Q_UINT32 sernum = msg->getMsgSerNum();
+ mUIDToSerNum.insert( uid, sernum );
+
+ // tell the resource if we didn't trigger this ourselves
+ if ( mInTransit.contains( uid ) ) {
+ mInTransit.remove( uid );
+ }
+ incidenceAdded( type, folder->location(), sernum, format, s );
+ } else {
+ // go get the rest of it, then try again
+ // TODO: Till, port me
+ if ( unget ) mTheUnGetMes.insert( msg->getMsgSerNum(), true );
+ FolderJob *job = msg->parent()->createJob( msg );
+ connect( job, SIGNAL( messageRetrieved( KMMessage* ) ),
+ this, SLOT( slotMessageRetrieved( KMMessage* ) ) );
+ job->start();
+ return;
+ }
+ if( unget ) folder->unGetMsg(i);
+}
+
+// KMail deleted a file
+void KMailICalIfaceImpl::slotIncidenceDeleted( KMFolder* folder,
+ Q_UINT32 sernum )
+{
+ if( mResourceQuiet || !mUseResourceIMAP )
+ return;
+
+ QString type = folderContentsType( folder->storage()->contentsType() );
+ //kdDebug(5006) << folder << " " << type << " " << sernum << endl;
+ if( !type.isEmpty() ) {
+ // Get the index of the mail
+ int i = 0;
+ KMFolder* aFolder = 0;
+ KMMsgDict::instance()->getLocation( sernum, &aFolder, &i );
+ assert( folder == aFolder );
+
+ // Read the iCal or vCard
+ bool unget = !folder->isMessage( i );
+ QString s;
+ bool ok = false;
+ KMMessage* msg = folder->getMsg( i );
+ QString uid( "UID" );
+ switch( storageFormat( folder ) ) {
+ case StorageIcalVcard:
+ if( vPartFoundAndDecoded( msg, s ) ) {
+ vPartMicroParser( s, uid );
+ ok = true;
+ }
+ break;
+ case StorageXML:
+ if ( kolabXMLFoundAndDecoded( *msg, folderKolabMimeType( folder->storage()->contentsType() ), s ) ) {
+ uid = msg->subject();
+ ok = true;
+ }
+ break;
+ }
+ if ( ok ) {
+ kdDebug(5006) << "Emitting DCOP signal incidenceDeleted( "
+ << type << ", " << folder->location() << ", " << uid
+ << " )" << endl;
+ incidenceDeleted( type, folder->location(), uid );
+ }
+ if( unget ) folder->unGetMsg(i);
+ } else
+ kdError(5006) << "Not a groupware folder" << endl;
+}
+
+// KMail orders a refresh
+void KMailICalIfaceImpl::slotRefresh( const QString& type )
+{
+ if( mUseResourceIMAP ) {
+ signalRefresh( type, QString::null /* PENDING(bo) folder->location() */ );
+ kdDebug(5006) << "Emitting DCOP signal signalRefresh( " << type << " )" << endl;
+ }
+}
+
+// This is among other things called when an expunge of a folder happens
+void KMailICalIfaceImpl::slotRefreshFolder( KMFolder* folder)
+{
+ // TODO: The resources would of course be better off, if only this
+ // folder would need refreshing. Currently it just orders a reload of
+ // the type of the folder
+ if( mUseResourceIMAP && folder ) {
+ if( folder == mCalendar || folder == mContacts
+ || folder == mNotes || folder == mTasks
+ || folder == mJournals || mExtraFolders.find( folder->location() ) ) {
+ // Refresh the folder of this type
+ KMail::FolderContentsType ct = folder->storage()->contentsType();
+ slotRefresh( s_folderContentsType[ct].contentsTypeStr );
+ }
+ }
+}
+
+/****************************
+ * The folder and message stuff code
+ */
+
+KMFolder* KMailICalIfaceImpl::folderFromType( const QString& type,
+ const QString& folder )
+{
+ if( mUseResourceIMAP ) {
+ KMFolder* f = 0;
+ if ( !folder.isEmpty() ) {
+ f = extraFolder( type, folder );
+ if ( f )
+ return f;
+ }
+
+ if( type == "Calendar" ) f = mCalendar;
+ else if( type == "Contact" ) f = mContacts;
+ else if( type == "Note" ) f = mNotes;
+ else if( type == "Task" || type == "Todo" ) f = mTasks;
+ else if( type == "Journal" ) f = mJournals;
+
+ if ( f && ( folder.isEmpty() || folder == f->location() ) )
+ return f;
+
+ kdError(5006) << "No folder ( " << type << ", " << folder << " )\n";
+ }
+
+ return 0;
+}
+
+
+// Returns true if folder is a resource folder. If the resource isn't enabled
+// this always returns false
+bool KMailICalIfaceImpl::isResourceFolder( KMFolder* folder ) const
+{
+ return mUseResourceIMAP && folder &&
+ ( isStandardResourceFolder( folder ) || mExtraFolders.find( folder->location() )!=0 );
+}
+
+bool KMailICalIfaceImpl::isStandardResourceFolder( KMFolder* folder ) const
+{
+ return ( folder == mCalendar || folder == mTasks || folder == mJournals ||
+ folder == mNotes || folder == mContacts );
+}
+
+bool KMailICalIfaceImpl::hideResourceFolder( KMFolder* folder ) const
+{
+ return mHideFolders && isResourceFolder( folder );
+}
+
+bool KMailICalIfaceImpl::hideResourceAccountRoot( KMFolder* folder ) const
+{
+ KMFolderCachedImap *dimapFolder = dynamic_cast<KMFolderCachedImap*>( folder->storage() );
+ bool hide = dimapFolder && mHideFolders
+ && (int)dimapFolder->account()->id() == GlobalSettings::self()->theIMAPResourceAccount()
+ && GlobalSettings::self()->showOnlyGroupwareFoldersForGroupwareAccount();
+ return hide;
+
+}
+
+KFolderTreeItem::Type KMailICalIfaceImpl::folderType( KMFolder* folder ) const
+{
+ if( mUseResourceIMAP && folder ) {
+ if( folder == mCalendar || folder == mContacts
+ || folder == mNotes || folder == mTasks
+ || folder == mJournals || mExtraFolders.find( folder->location() ) ) {
+ KMail::FolderContentsType ct = folder->storage()->contentsType();
+ return s_folderContentsType[ct].treeItemType;
+ }
+ }
+
+ return KFolderTreeItem::Other;
+}
+
+// Global tables of foldernames is different languages
+// For now: 0->English, 1->German, 2->French, 3->Dutch
+static QMap<KFolderTreeItem::Type,QString> folderNames[4];
+QString KMailICalIfaceImpl::folderName( KFolderTreeItem::Type type, int language ) const
+{
+ // With the XML storage, folders are always (internally) named in English
+ if ( GlobalSettings::self()->theIMAPResourceStorageFormat() == GlobalSettings::EnumTheIMAPResourceStorageFormat::XML )
+ language = 0;
+
+ static bool folderNamesSet = false;
+ if( !folderNamesSet ) {
+ folderNamesSet = true;
+ /* NOTE: If you add something here, you also need to update
+ GroupwarePage in configuredialog.cpp */
+
+ // English
+ folderNames[0][KFolderTreeItem::Calendar] = QString::fromLatin1("Calendar");
+ folderNames[0][KFolderTreeItem::Tasks] = QString::fromLatin1("Tasks");
+ folderNames[0][KFolderTreeItem::Journals] = QString::fromLatin1("Journal");
+ folderNames[0][KFolderTreeItem::Contacts] = QString::fromLatin1("Contacts");
+ folderNames[0][KFolderTreeItem::Notes] = QString::fromLatin1("Notes");
+
+ // German
+ folderNames[1][KFolderTreeItem::Calendar] = QString::fromLatin1("Kalender");
+ folderNames[1][KFolderTreeItem::Tasks] = QString::fromLatin1("Aufgaben");
+ folderNames[1][KFolderTreeItem::Journals] = QString::fromLatin1("Journal");
+ folderNames[1][KFolderTreeItem::Contacts] = QString::fromLatin1("Kontakte");
+ folderNames[1][KFolderTreeItem::Notes] = QString::fromLatin1("Notizen");
+
+ // French
+ folderNames[2][KFolderTreeItem::Calendar] = QString::fromLatin1("Calendrier");
+ // Tasks = Tâches (â == 0xE2 in latin1)
+ folderNames[2][KFolderTreeItem::Tasks] = QString::fromLatin1("T\342ches");
+ folderNames[2][KFolderTreeItem::Journals] = QString::fromLatin1("Journal");
+ folderNames[2][KFolderTreeItem::Contacts] = QString::fromLatin1("Contacts");
+ folderNames[2][KFolderTreeItem::Notes] = QString::fromLatin1("Notes");
+
+ // Dutch
+ folderNames[3][KFolderTreeItem::Calendar] = QString::fromLatin1("Agenda");
+ folderNames[3][KFolderTreeItem::Tasks] = QString::fromLatin1("Taken");
+ folderNames[3][KFolderTreeItem::Journals] = QString::fromLatin1("Logboek");
+ folderNames[3][KFolderTreeItem::Contacts] = QString::fromLatin1("Contactpersonen");
+ folderNames[3][KFolderTreeItem::Notes] = QString::fromLatin1("Notities");
+ }
+
+ if( language < 0 || language > 3 ) {
+ return folderNames[mFolderLanguage][type];
+ }
+ else {
+ return folderNames[language][type];
+ }
+}
+
+
+// Find message matching a given UID
+KMMessage *KMailICalIfaceImpl::findMessageByUID( const QString& uid, KMFolder* folder )
+{
+ if( !folder || !mUIDToSerNum.contains( uid ) ) return 0;
+ int i;
+ KMFolder *aFolder;
+ KMMsgDict::instance()->getLocation( mUIDToSerNum[uid], &aFolder, &i );
+ Q_ASSERT( aFolder == folder );
+ return folder->getMsg( i );
+}
+
+// Find message matching a given serial number
+KMMessage *KMailICalIfaceImpl::findMessageBySerNum( Q_UINT32 serNum, KMFolder* folder )
+{
+ if( !folder ) return 0;
+
+ KMMessage *message = 0;
+ KMFolder* aFolder = 0;
+ int index;
+ KMMsgDict::instance()->getLocation( serNum, &aFolder, &index );
+
+ if( aFolder && aFolder != folder ) {
+ kdWarning(5006) << "findMessageBySerNum( " << serNum << " ) found it in folder " << aFolder->location() << ", expected " << folder->location() << endl;
+ } else {
+ if( aFolder )
+ message = aFolder->getMsg( index );
+ if (!message)
+ kdWarning(5006) << "findMessageBySerNum( " << serNum << " ) invalid serial number\n" << endl;
+ }
+ return message;
+}
+
+void KMailICalIfaceImpl::deleteMsg( KMMessage *msg )
+{
+ if( !msg ) return;
+ // Commands are now delayed; can't use that anymore, we need immediate deletion
+ //( new KMDeleteMsgCommand( msg->parent(), msg ) )->start();
+ KMFolder *srcFolder = msg->parent();
+ int idx = srcFolder->find(msg);
+ assert(idx != -1);
+ // kill existing jobs since we are about to delete the message
+ srcFolder->ignoreJobsForMessage( msg );
+ if ( !msg->transferInProgress() ) {
+ srcFolder->removeMsg(idx);
+ delete msg;
+ } else {
+ kdDebug(5006) << k_funcinfo << "Message cannot be deleted now because it is currently in use " << msg << endl;
+ msg->deleteWhenUnused();
+ }
+ addFolderChange( srcFolder, Contents );
+}
+
+void KMailICalIfaceImpl::folderContentsTypeChanged( KMFolder* folder,
+ KMail::FolderContentsType contentsType )
+{
+ if ( !mUseResourceIMAP )
+ return;
+// kdDebug(5006) << "folderContentsTypeChanged( " << folder->name()
+// << ", " << contentsType << ")\n";
+
+ // The builtins can't change type
+ if ( isStandardResourceFolder( folder ) )
+ return;
+
+ // Check if already know that 'extra folder'
+ const QString location = folder->location();
+ ExtraFolder* ef = mExtraFolders.find( location );
+ if ( ef && ef->folder ) {
+ // Notify that the old folder resource is no longer available
+ subresourceDeleted(folderContentsType( folder->storage()->contentsType() ), location );
+
+ if ( contentsType == KMail::ContentsTypeMail ) {
+ // Delete the old entry, stop listening and stop here
+ mExtraFolders.remove( location );
+ folder->disconnect( this );
+ return;
+ }
+ // So the type changed to another groupware type, ok.
+ } else {
+ if ( ef && !ef->folder ) // deleted folder, clean up
+ mExtraFolders.remove( location );
+ if ( contentsType == KMail::ContentsTypeMail )
+ return;
+
+ //kdDebug(5006) << "registering " << location << " as extra folder" << endl;
+ // Make a new entry for the list
+ ef = new ExtraFolder( folder );
+ mExtraFolders.insert( location, ef );
+
+ FolderInfo info = readFolderInfo( folder );
+ mFolderInfoMap.insert( folder, info );
+
+ // Adjust the folder names of all foo.default folders.
+ // German users will get Kalender as the name of all default Calendar folders,
+ // including their own, so that the default calendar folder of their Japanese
+ // coworker appears as /user/hirohito/Kalender, although Hirohito sees his folder
+ // in Japanese. On the server the folders are always in English.
+ if ( folder->folderType() == KMFolderTypeCachedImap ) {
+ QString annotation = static_cast<KMFolderCachedImap*>( folder->storage() )->annotationFolderType();
+ kdDebug(5006) << "folderContentsTypeChanged: " << folder->name() << " has annotation " << annotation << endl;
+ if ( annotation == QString( s_folderContentsType[contentsType].annotation ) + ".default" )
+ folder->setLabel( localizedDefaultFolderName( contentsType ) );
+ }
+
+ connectFolder( folder );
+ }
+ // Tell about the new resource
+ subresourceAdded( folderContentsType( contentsType ), location, subresourceLabelForPresentation(folder),
+ !folder->isReadOnly(), folderIsAlarmRelevant( folder ) );
+}
+
+KMFolder* KMailICalIfaceImpl::extraFolder( const QString& type,
+ const QString& folder )
+{
+ // If an extra folder exists that matches the type and folder location,
+ // use that
+ int t = folderContentsType( type );
+ if ( t < 1 || t > 5 )
+ return 0;
+
+ ExtraFolder* ef = mExtraFolders.find( folder );
+ if ( ef && ef->folder && ef->folder->storage()->contentsType() == t )
+ return ef->folder;
+
+ return 0;
+}
+
+KMailICalIfaceImpl::StorageFormat KMailICalIfaceImpl::storageFormat( KMFolder* folder ) const
+{
+ FolderInfoMap::ConstIterator it = mFolderInfoMap.find( folder );
+ if ( it != mFolderInfoMap.end() )
+ return (*it).mStorageFormat;
+ return globalStorageFormat();
+}
+
+void KMailICalIfaceImpl::setStorageFormat( KMFolder* folder, StorageFormat format )
+{
+ FolderInfoMap::Iterator it = mFolderInfoMap.find( folder );
+ if ( it != mFolderInfoMap.end() ) {
+ (*it).mStorageFormat = format;
+ } else {
+ FolderInfo info( format, NoChange );
+ mFolderInfoMap.insert( folder, info );
+ }
+ KConfigGroup configGroup( kmkernel->config(), "GroupwareFolderInfo" );
+ configGroup.writeEntry( folder->idString() + "-storageFormat",
+ format == StorageXML ? "xml" : "icalvcard" );
+}
+
+void KMailICalIfaceImpl::addFolderChange( KMFolder* folder, FolderChanges changes )
+{
+ FolderInfoMap::Iterator it = mFolderInfoMap.find( folder );
+ if ( it != mFolderInfoMap.end() ) {
+ (*it).mChanges = static_cast<FolderChanges>( (*it).mChanges | changes );
+ } else { // Otherwise, well, it's a folder we don't care about.
+ kdDebug(5006) << "addFolderChange: nothing known about folder " << folder->location() << endl;
+ }
+ KConfigGroup configGroup( kmkernel->config(), "GroupwareFolderInfo" );
+ configGroup.writeEntry( folder->idString() + "-changes", (*it).mChanges );
+}
+
+KMailICalIfaceImpl::FolderInfo KMailICalIfaceImpl::readFolderInfo( const KMFolder * const folder ) const
+{
+ KConfigGroup configGroup( kmkernel->config(), "GroupwareFolderInfo" );
+ QString str = configGroup.readEntry( folder->idString() + "-storageFormat", "unset" );
+ FolderInfo info;
+ if ( str == "unset" ) {
+ info.mStorageFormat = globalStorageFormat();
+ configGroup.writeEntry( folder->idString() + "-storageFormat",
+ info.mStorageFormat == StorageXML ? "xml" : "icalvcard" );
+ } else {
+ info.mStorageFormat = ( str == "xml" ) ? StorageXML : StorageIcalVcard;
+ }
+ info.mChanges = (FolderChanges) configGroup.readNumEntry( folder->idString() + "-changes" );
+ return info;
+}
+
+
+void KMailICalIfaceImpl::folderSynced( KMFolder* folder, const KURL& folderURL )
+{
+ FolderInfoMap::Iterator it = mFolderInfoMap.find( folder );
+ if ( it != mFolderInfoMap.end() && (*it).mChanges ) {
+ handleFolderSynced( folder, folderURL, (*it).mChanges );
+ (*it).mChanges = NoChange;
+ }
+}
+
+void KMailICalIfaceImpl::handleFolderSynced( KMFolder* folder,
+ const KURL& folderURL,
+ int _changes )
+{
+ // This is done here instead of in the resource, because
+ // there could be 0, 1, or N kolab resources at this point.
+ // We can hack the N case, but not the 0 case.
+ // So the idea of a DCOP signal for this wouldn't work.
+ if ( ( _changes & KMailICalIface::Contents ) ||
+ ( _changes & KMailICalIface::ACL ) ) {
+ if ( storageFormat( folder ) == StorageXML && folder->storage()->contentsType() == KMail::ContentsTypeCalendar )
+ triggerKolabFreeBusy( folderURL );
+ }
+}
+
+void KMailICalIfaceImpl::folderDeletedOnServer( const KURL& folderURL )
+{
+ triggerKolabFreeBusy( folderURL );
+}
+
+void KMailICalIfaceImpl::triggerKolabFreeBusy( const KURL& folderURL )
+{
+ /* Steffen said: you must issue an authenticated HTTP GET request to
+ https://kolabserver/freebusy/trigger/user@domain/Folder/NestedFolder.pfb
+ (replace .pfb with .xpfb for extended fb lists). */
+ KURL httpURL( folderURL );
+ // Keep username ("user@domain"), pass, and host from the imap url
+ httpURL.setProtocol( "https" );
+ httpURL.setPort( 0 ); // remove imap port
+
+ // IMAP path is either /INBOX/<path> or /user/someone/<path>
+ QString path = folderURL.path( -1 );
+ Q_ASSERT( path.startsWith( "/" ) );
+ int secondSlash = path.find( '/', 1 );
+ if ( secondSlash == -1 ) {
+ kdWarning() << "KCal::ResourceKolab::fromKMailFolderSynced path is too short: " << path << endl;
+ return;
+ }
+ if ( path.startsWith( "/INBOX/", false ) ) {
+ // If INBOX, replace it with the username (which is user@domain)
+ path = path.mid( secondSlash );
+ path.prepend( folderURL.user() );
+ } else {
+ // If user, just remove it. So we keep the IMAP-returned username.
+ // This assumes it's a known user on the same domain.
+ path = path.mid( secondSlash );
+ }
+
+ httpURL.setPath( "/freebusy/trigger/" + path + ".pfb" );
+ httpURL.setQuery( QString::null );
+ // Ensure that we encode everything with UTF8
+ httpURL = KURL( httpURL.url(0,106), 106 );
+ kdDebug() << "Triggering PFB update for " << folderURL << " : getting " << httpURL << endl;
+ // "Fire and forget". No need for error handling, nor for explicit deletion.
+ // Maybe we should try to prevent launching it if it's already running (for this URL) though.
+ /*KIO::Job* job =*/ KIO::get( httpURL, false, false /*no progress info*/ );
+}
+
+void KMailICalIfaceImpl::slotFolderPropertiesChanged( KMFolder* folder )
+{
+ if ( isResourceFolder( folder ) ) {
+ const QString location = folder->location();
+ const QString contentsTypeStr = folderContentsType( folder->storage()->contentsType() );
+ subresourceDeleted( contentsTypeStr, location );
+
+ subresourceAdded( contentsTypeStr, location, subresourceLabelForPresentation( folder ),
+ !folder->isReadOnly(), folderIsAlarmRelevant( folder ) );
+
+ }
+}
+
+// Must only be connected to a signal from KMFolder!
+void KMailICalIfaceImpl::slotFolderRenamed()
+{
+ const KMFolder* folder = static_cast<const KMFolder *>( sender() );
+ slotFolderPropertiesChanged( const_cast<KMFolder*>( folder ) );
+}
+
+void KMailICalIfaceImpl::slotFolderLocationChanged( const QString &oldLocation,
+ const QString &newLocation )
+{
+ KMFolder *folder = findResourceFolder( oldLocation );
+ ExtraFolder* ef = mExtraFolders.find( oldLocation );
+ if ( ef ) {
+ // reuse the ExtraFolder entry, but adjust the key
+ mExtraFolders.setAutoDelete( false );
+ mExtraFolders.remove( oldLocation );
+ mExtraFolders.setAutoDelete( true );
+ mExtraFolders.insert( newLocation, ef );
+ }
+ if ( folder )
+ subresourceDeleted( folderContentsType( folder->storage()->contentsType() ), oldLocation );
+
+}
+
+KMFolder* KMailICalIfaceImpl::findResourceFolder( const QString& resource )
+{
+ // Try the standard folders
+ if( mCalendar && mCalendar->location() == resource )
+ return mCalendar;
+ if ( mContacts && mContacts->location() == resource )
+ return mContacts;
+ if ( mNotes && mNotes->location() == resource )
+ return mNotes;
+ if ( mTasks && mTasks->location() == resource )
+ return mTasks;
+ if ( mJournals && mJournals->location() == resource )
+ return mJournals;
+
+ // No luck. Try the extrafolders
+ ExtraFolder* ef = mExtraFolders.find( resource );
+ if ( ef )
+ return ef->folder;
+
+ // No luck at all
+ return 0;
+}
+
+/****************************
+ * The config stuff
+ */
+
+void KMailICalIfaceImpl::readConfig()
+{
+ bool enabled = GlobalSettings::self()->theIMAPResourceEnabled() &&
+ ( GlobalSettings::self()->theIMAPResourceAccount() != 0 );
+
+ if( !enabled ) {
+ if( mUseResourceIMAP == true ) {
+ // Shutting down
+ mUseResourceIMAP = false;
+ cleanup();
+ reloadFolderTree();
+ }
+ return;
+ }
+ mUseResourceIMAP = enabled;
+
+ // Read remaining options
+ const bool hideFolders = GlobalSettings::self()->hideGroupwareFolders();
+ QString parentName = GlobalSettings::self()->theIMAPResourceFolderParent();
+
+ // Find the folder parent
+ KMFolderDir* folderParentDir;
+ KMFolderType folderType;
+ KMFolder* folderParent = kmkernel->findFolderById( parentName );
+ if( folderParent == 0 ) {
+ // Parent folder not found. It was probably deleted. The user will have to
+ // configure things again.
+ kdDebug(5006) << "Groupware folder " << parentName << " not found. Groupware functionality disabled" << endl;
+ // Or maybe the inbox simply wasn't created on the first startup
+ KMAccount* account = kmkernel->acctMgr()->find( GlobalSettings::self()->theIMAPResourceAccount() );
+ Q_ASSERT( account );
+ if ( account ) {
+ // just in case we were connected already
+ disconnect( account, SIGNAL( finishedCheck( bool, CheckStatus ) ),
+ this, SLOT( slotCheckDone() ) );
+ connect( account, SIGNAL( finishedCheck( bool, CheckStatus ) ),
+ this, SLOT( slotCheckDone() ) );
+ }
+ mUseResourceIMAP = false;
+ // We can't really call cleanup(), if those folders were completely deleted.
+ mCalendar = 0;
+ mTasks = 0;
+ mJournals = 0;
+ mContacts = 0;
+ mNotes = 0;
+ return;
+ } else {
+ folderParentDir = folderParent->createChildFolder();
+ folderType = folderParent->folderType();
+ }
+
+ KMAcctCachedImap::GroupwareType groupwareType = dynamic_cast<KMFolderCachedImap *>( folderParent->storage() )->account()->groupwareType();
+
+ if ( groupwareType == KMAcctCachedImap::GroupwareKolab ) {
+ // Make sure the folder parent has the subdirs
+ // Globally there are 3 cases: nothing found, some stuff found by type/name heuristics, or everything found OK
+ bool noneFound = true;
+ bool mustFix = false; // true when at least one was found by heuristics
+ QValueVector<StandardFolderSearchResult> results( KMail::ContentsTypeLast + 1 );
+ for ( int i = 0; i < KMail::ContentsTypeLast+1; ++i ) {
+ if ( i != KMail::ContentsTypeMail ) {
+ results[i] = findStandardResourceFolder( folderParentDir, static_cast<KMail::FolderContentsType>(i) );
+ if ( results[i].found == StandardFolderSearchResult::FoundAndStandard )
+ noneFound = false;
+ else if ( results[i].found == StandardFolderSearchResult::FoundByType ||
+ results[i].found == StandardFolderSearchResult::FoundByName ) {
+ mustFix = true;
+ noneFound = false;
+ } else // NotFound
+ mustFix = true;
+ }
+ }
+
+ // Check if something changed
+ if( mUseResourceIMAP && !noneFound && !mustFix && mFolderParentDir == folderParentDir
+ && mFolderType == folderType ) {
+ // Nothing changed
+ if ( hideFolders != mHideFolders ) {
+ // Well, the folder hiding has changed
+ mHideFolders = hideFolders;
+ reloadFolderTree();
+ }
+ return;
+ }
+
+ if( noneFound || mustFix ) {
+ QString msg;
+ QString parentFolderName = folderParent != 0 ? folderParent->name() : folderParentDir->name();
+ if ( noneFound ) {
+ // No subfolder was found, so ask if we can make them
+ msg = i18n("KMail will now create the required groupware folders"
+ " as subfolders of %1; if you do not want this, cancel"
+ " and the IMAP resource will be disabled").arg(parentFolderName);
+ } else {
+ // Some subfolders were found, be more precise
+ QString operations = "<ul>";
+ for ( int i = 0; i < KMail::ContentsTypeLast+1; ++i ) {
+ if ( i != KMail::ContentsTypeMail ) {
+ QString typeName = localizedDefaultFolderName( static_cast<KMail::FolderContentsType>( i ) );
+ if ( results[i].found == StandardFolderSearchResult::NotFound )
+ operations += "<li>" + i18n( "%1: no folder found. It will be created." ).arg( typeName ) + "</li>";
+ else if ( results[i].found == StandardFolderSearchResult::FoundByType || results[i].found == StandardFolderSearchResult::FoundByName )
+ operations += "<li>" + i18n( "%1: found folder %2. It will be set as the main groupware folder." ).
+ arg( typeName ).arg( results[i].folder->label() ) + "</li>";
+ }
+ }
+ operations += "</ul>";
+
+ msg = i18n("<qt>KMail found the following groupware folders in %1 and needs to perform the following operations: %2"
+ "<br>If you do not want this, cancel"
+ " and the IMAP resource will be disabled").arg(parentFolderName, operations);
+
+ }
+
+ if( KMessageBox::questionYesNo( 0, msg,
+ i18n("Standard Groupware Folders"), KStdGuiItem::cont(), KStdGuiItem::cancel() ) == KMessageBox::No ) {
+
+ GlobalSettings::self()->setTheIMAPResourceEnabled( false );
+ mUseResourceIMAP = false;
+ mFolderParentDir = 0;
+ mFolderParent = 0;
+ reloadFolderTree();
+ return;
+ }
+ }
+
+ // Make the new settings work
+ mUseResourceIMAP = true;
+ mFolderLanguage = GlobalSettings::self()->theIMAPResourceFolderLanguage();
+ if( mFolderLanguage > 3 ) mFolderLanguage = 0;
+ mFolderParentDir = folderParentDir;
+ mFolderParent = folderParent;
+ mFolderType = folderType;
+ mHideFolders = hideFolders;
+
+ // Close the previous folders
+ cleanup();
+
+ // Set the new folders
+ mCalendar = initFolder( KMail::ContentsTypeCalendar );
+ mTasks = initFolder( KMail::ContentsTypeTask );
+ mJournals = initFolder( KMail::ContentsTypeJournal );
+ mContacts = initFolder( KMail::ContentsTypeContact );
+ mNotes = initFolder( KMail::ContentsTypeNote );
+
+ // Store final annotation (with .default) so that we won't ask again on next startup
+ if ( mCalendar->folderType() == KMFolderTypeCachedImap )
+ static_cast<KMFolderCachedImap *>( mCalendar->storage() )->updateAnnotationFolderType();
+ if ( mTasks->folderType() == KMFolderTypeCachedImap )
+ static_cast<KMFolderCachedImap *>( mTasks->storage() )->updateAnnotationFolderType();
+ if ( mJournals->folderType() == KMFolderTypeCachedImap )
+ static_cast<KMFolderCachedImap *>( mJournals->storage() )->updateAnnotationFolderType();
+ if ( mContacts->folderType() == KMFolderTypeCachedImap )
+ static_cast<KMFolderCachedImap *>( mContacts->storage() )->updateAnnotationFolderType();
+ if ( mNotes->folderType() == KMFolderTypeCachedImap )
+ static_cast<KMFolderCachedImap *>( 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;
+
+ // Find all extra folders
+ QStringList folderNames;
+ QValueList<QGuardedPtr<KMFolder> > folderList;
+ kmkernel->dimapFolderMgr()->createFolderList(&folderNames, &folderList);
+ for(QValueList<QGuardedPtr<KMFolder> >::iterator it = folderList.begin();
+ it != folderList.end(); ++it)
+ {
+ FolderStorage* storage = (*it)->storage();
+ if ( storage->contentsType() != 0 ) {
+ folderContentsTypeChanged( *it, storage->contentsType() );
+ }
+ }
+
+ // If we just created them, they might have been registered as extra folders temporarily.
+ // -> undo that.
+ mExtraFolders.remove( mCalendar->location() );
+ mExtraFolders.remove( mTasks->location() );
+ mExtraFolders.remove( mJournals->location() );
+ 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 );
+ subresourceAdded( folderContentsType( KMail::ContentsTypeContact ), mContacts->location(), mContacts->label(), true, false );
+ subresourceAdded( folderContentsType( KMail::ContentsTypeNote ), mNotes->location(), mNotes->label(), true, false );
+ } else if ( groupwareType == KMAcctCachedImap::GroupwareScalix ) {
+ // Make the new settings work
+ mUseResourceIMAP = true;
+ mFolderParentDir = folderParentDir;
+ mFolderParent = folderParent;
+ mFolderType = folderType;
+ mHideFolders = false;
+
+ // Close the previous folders
+ cleanup();
+
+ // Set the new folders
+ mCalendar = initScalixFolder( KMail::ContentsTypeCalendar );
+ mTasks = initScalixFolder( KMail::ContentsTypeTask );
+ mJournals = 0;
+ mContacts = initScalixFolder( KMail::ContentsTypeContact );
+ mNotes = initScalixFolder( KMail::ContentsTypeNote );
+
+ // Store final annotation (with .default) so that we won't ask again on next startup
+ if ( mCalendar->folderType() == KMFolderTypeCachedImap )
+ static_cast<KMFolderCachedImap *>( mCalendar->storage() )->updateAnnotationFolderType();
+ if ( mTasks->folderType() == KMFolderTypeCachedImap )
+ static_cast<KMFolderCachedImap *>( mTasks->storage() )->updateAnnotationFolderType();
+ if ( mContacts->folderType() == KMFolderTypeCachedImap )
+ static_cast<KMFolderCachedImap *>( mContacts->storage() )->updateAnnotationFolderType();
+ if ( mNotes->folderType() == KMFolderTypeCachedImap )
+ static_cast<KMFolderCachedImap *>( 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;
+
+ // Find all extra folders
+ QStringList folderNames;
+ QValueList<QGuardedPtr<KMFolder> > folderList;
+ kmkernel->dimapFolderMgr()->createFolderList(&folderNames, &folderList);
+ QValueList<QGuardedPtr<KMFolder> >::iterator it;
+ for(it = folderList.begin(); it != folderList.end(); ++it)
+ {
+ FolderStorage *storage = (*it)->storage();
+
+ if ( (*it)->folderType() == KMFolderTypeCachedImap ) {
+ KMFolderCachedImap *imapFolder = static_cast<KMFolderCachedImap*>( storage );
+
+ const QString attributes = imapFolder->folderAttributes();
+ if ( attributes.contains( "X-FolderClass" ) ) {
+ if ( !attributes.contains( "X-SpecialFolder" ) || (*it)->location().contains( "@" ) ) {
+ const Scalix::FolderAttributeParser parser( attributes );
+ if ( !parser.folderClass().isEmpty() ) {
+ FolderContentsType type = Scalix::Utils::scalixIdToContentsType( parser.folderClass() );
+ imapFolder->setContentsType( type );
+ folderContentsTypeChanged( *it, type );
+ }
+ }
+ }
+ }
+ }
+
+ // If we just created them, they might have been registered as extra folders temporarily.
+ // -> undo that.
+ mExtraFolders.remove( mCalendar->location() );
+ mExtraFolders.remove( mTasks->location() );
+ 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::ContentsTypeContact ), mContacts->location(), mContacts->label(), true, false );
+ subresourceAdded( folderContentsType( KMail::ContentsTypeNote ), mNotes->location(), mNotes->label(), true, false );
+ }
+
+ reloadFolderTree();
+}
+
+void KMailICalIfaceImpl::slotCheckDone()
+{
+ QString parentName = GlobalSettings::self()->theIMAPResourceFolderParent();
+ KMFolder* folderParent = kmkernel->findFolderById( parentName );
+ //kdDebug(5006) << k_funcinfo << " folderParent=" << folderParent << endl;
+ if ( folderParent ) // cool it exists now
+ {
+ KMAccount* account = kmkernel->acctMgr()->find( GlobalSettings::self()->theIMAPResourceAccount() );
+ if ( account )
+ disconnect( account, SIGNAL( finishedCheck( bool, CheckStatus ) ),
+ this, SLOT( slotCheckDone() ) );
+ readConfig();
+ }
+}
+
+KMFolder* KMailICalIfaceImpl::initFolder( KMail::FolderContentsType contentsType )
+{
+ // Figure out what type of folder this is supposed to be
+ KMFolderType type = mFolderType;
+ if( type == KMFolderTypeUnknown ) type = KMFolderTypeMaildir;
+
+ KFolderTreeItem::Type itemType = s_folderContentsType[contentsType].treeItemType;
+ //kdDebug(5006) << "KMailICalIfaceImpl::initFolder " << folderName( itemType ) << endl;
+
+ // Find the folder
+ StandardFolderSearchResult result = findStandardResourceFolder( mFolderParentDir, contentsType );
+ KMFolder* folder = result.folder;
+
+ if ( !folder ) {
+ // The folder isn't there yet - create it
+ folder =
+ mFolderParentDir->createFolder( localizedDefaultFolderName( contentsType ), false, type );
+ if( mFolderType == KMFolderTypeImap ) {
+ KMFolderImap* parentFolder = static_cast<KMFolderImap*>( mFolderParent->storage() );
+ parentFolder->createFolder( localizedDefaultFolderName( contentsType ) );
+ static_cast<KMFolderImap*>( folder->storage() )->setAccount( parentFolder->account() );
+ }
+ // Groupware folder created, use the global setting for storage format
+ setStorageFormat( folder, globalStorageFormat() );
+ } else {
+ FolderInfo info = readFolderInfo( folder );
+ mFolderInfoMap.insert( folder, info );
+ //kdDebug(5006) << "Found existing folder type " << itemType << " : " << folder->location() << endl;
+ }
+
+ if( folder->canAccess() != 0 ) {
+ KMessageBox::sorry(0, i18n("You do not have read/write permission to your %1 folder.")
+ .arg( folderName( itemType ) ) );
+ return 0;
+ }
+ folder->storage()->setContentsType( contentsType );
+ folder->setSystemFolder( true );
+ folder->storage()->writeConfig();
+ folder->open("ifacefolder");
+ connectFolder( folder );
+ return folder;
+}
+
+KMFolder* KMailICalIfaceImpl::initScalixFolder( KMail::FolderContentsType contentsType )
+{
+ // Figure out what type of folder this is supposed to be
+ KMFolderType type = mFolderType;
+ if( type == KMFolderTypeUnknown ) type = KMFolderTypeMaildir;
+
+ KMFolder* folder = 0;
+
+ // Find all extra folders
+ QStringList folderNames;
+ QValueList<QGuardedPtr<KMFolder> > folderList;
+ Q_ASSERT( kmkernel );
+ Q_ASSERT( kmkernel->dimapFolderMgr() );
+ kmkernel->dimapFolderMgr()->createFolderList(&folderNames, &folderList);
+ QValueList<QGuardedPtr<KMFolder> >::iterator it = folderList.begin();
+ for(; it != folderList.end(); ++it)
+ {
+ FolderStorage *storage = (*it)->storage();
+
+ if ( (*it)->folderType() == KMFolderTypeCachedImap ) {
+ KMFolderCachedImap *imapFolder = static_cast<KMFolderCachedImap*>( storage );
+
+ const QString attributes = imapFolder->folderAttributes();
+ if ( attributes.contains( "X-SpecialFolder" ) ) {
+ const Scalix::FolderAttributeParser parser( attributes );
+ if ( contentsType == Scalix::Utils::scalixIdToContentsType( parser.folderClass() ) ) {
+ folder = *it;
+ break;
+ }
+ }
+ }
+ }
+
+ if ( !folder ) {
+ return 0;
+ } else {
+ FolderInfo info = readFolderInfo( folder );
+ mFolderInfoMap.insert( folder, info );
+ //kdDebug(5006) << "Found existing folder type " << itemType << " : " << folder->location() << endl;
+ }
+
+ if( folder->canAccess() != 0 ) {
+ KMessageBox::sorry(0, i18n("You do not have read/write permission to your folder.") );
+ return 0;
+ }
+ folder->storage()->setContentsType( contentsType );
+ folder->setSystemFolder( true );
+ folder->storage()->writeConfig();
+ folder->open( "scalixfolder" );
+ connectFolder( folder );
+ return folder;
+}
+
+void KMailICalIfaceImpl::connectFolder( KMFolder* folder )
+{
+ // avoid multiple connections
+ disconnect( folder, SIGNAL( msgAdded( KMFolder*, Q_UINT32 ) ),
+ this, SLOT( slotIncidenceAdded( KMFolder*, Q_UINT32 ) ) );
+ disconnect( folder, SIGNAL( msgRemoved( KMFolder*, Q_UINT32 ) ),
+ this, SLOT( slotIncidenceDeleted( KMFolder*, Q_UINT32 ) ) );
+ disconnect( folder, SIGNAL( expunged( KMFolder* ) ),
+ this, SLOT( slotRefreshFolder( KMFolder* ) ) );
+ disconnect( folder->storage(), SIGNAL( readOnlyChanged( KMFolder* ) ),
+ this, SLOT( slotFolderPropertiesChanged( KMFolder* ) ) );
+ disconnect( folder, SIGNAL( nameChanged() ),
+ this, SLOT( slotFolderRenamed() ) );
+ disconnect( folder->storage(), SIGNAL( locationChanged( const QString&, const QString&) ),
+ this, SLOT( slotFolderLocationChanged( const QString&, const QString&) ) );
+
+ // Setup the signals to listen for changes
+ connect( folder, SIGNAL( msgAdded( KMFolder*, Q_UINT32 ) ),
+ this, SLOT( slotIncidenceAdded( KMFolder*, Q_UINT32 ) ) );
+ connect( folder, SIGNAL( msgRemoved( KMFolder*, Q_UINT32 ) ),
+ this, SLOT( slotIncidenceDeleted( KMFolder*, Q_UINT32 ) ) );
+ connect( folder, SIGNAL( expunged( KMFolder* ) ),
+ this, SLOT( slotRefreshFolder( KMFolder* ) ) );
+ connect( folder->storage(), SIGNAL( readOnlyChanged( KMFolder* ) ),
+ this, SLOT( slotFolderPropertiesChanged( KMFolder* ) ) );
+ connect( folder, SIGNAL( nameChanged() ),
+ this, SLOT( slotFolderRenamed() ) );
+ connect( folder->storage(), SIGNAL( locationChanged( const QString&, const QString&) ),
+ this, SLOT( slotFolderLocationChanged( const QString&, const QString&) ) );
+
+}
+
+static void cleanupFolder( KMFolder* folder, KMailICalIfaceImpl* _this )
+{
+ if( folder ) {
+ folder->setSystemFolder( false );
+ folder->disconnect( _this );
+ folder->close("ifacefolder");
+ }
+}
+
+void KMailICalIfaceImpl::cleanup()
+{
+ cleanupFolder( mContacts, this );
+ cleanupFolder( mCalendar, this );
+ cleanupFolder( mNotes, this );
+ cleanupFolder( mTasks, this );
+ cleanupFolder( mJournals, this );
+
+ mContacts = mCalendar = mNotes = mTasks = mJournals = 0;
+}
+
+QString KMailICalIfaceImpl::folderPixmap( KFolderTreeItem::Type type ) const
+{
+ if( !mUseResourceIMAP )
+ return QString::null;
+
+ if( type == KFolderTreeItem::Contacts )
+ return QString::fromLatin1( "kmgroupware_folder_contacts" );
+ else if( type == KFolderTreeItem::Calendar )
+ return QString::fromLatin1( "kmgroupware_folder_calendar" );
+ else if( type == KFolderTreeItem::Notes )
+ return QString::fromLatin1( "kmgroupware_folder_notes" );
+ else if( type == KFolderTreeItem::Tasks )
+ return QString::fromLatin1( "kmgroupware_folder_tasks" );
+ else if( type == KFolderTreeItem::Journals )
+ return QString::fromLatin1( "kmgroupware_folder_journals" );
+
+ return QString::null;
+}
+
+static void reloadFolderTree()
+{
+ // Make the folder tree show the icons or not
+ kmkernel->folderMgr()->contentsChanged();
+}
+
+// This is a very light-weight and fast 'parser' to retrieve
+// a data entry from a vCal taking continuation lines
+// into account
+static void vPartMicroParser( const QString& str, QString& s )
+{
+ QString line;
+ uint len = str.length();
+
+ for( uint i=0; i<len; ++i){
+ if( str[i] == '\r' || str[i] == '\n' ){
+ if( str[i] == '\r' )
+ ++i;
+ if( i+1 < len && str[i+1] == ' ' ){
+ // found a continuation line, skip it's leading blanc
+ ++i;
+ }else{
+ // found a logical line end, process the line
+ if( line.startsWith( s ) ) {
+ s = line.mid( s.length() + 1 );
+ return;
+ }
+ line = "";
+ }
+ } else {
+ line += str[i];
+ }
+ }
+
+ // Not found. Clear it
+ s.truncate(0);
+}
+
+// Returns the first child folder having the given annotation
+static KMFolder* findFolderByAnnotation( KMFolderDir* folderParentDir, const QString& annotation )
+{
+ QPtrListIterator<KMFolderNode> it( *folderParentDir );
+ for ( ; it.current(); ++it ) {
+ if ( !it.current()->isDir() ) {
+ KMFolder* folder = static_cast<KMFolder *>( it.current() );
+ if ( folder->folderType() == KMFolderTypeCachedImap ) {
+ QString folderAnnotation = static_cast<KMFolderCachedImap*>( folder->storage() )->annotationFolderType();
+ //kdDebug(5006) << "findStandardResourceFolder: " << folder->name() << " has annotation " << folderAnnotation << endl;
+ if ( folderAnnotation == annotation )
+ return folder;
+ }
+ }
+ }
+ return 0;
+}
+
+KMailICalIfaceImpl::StandardFolderSearchResult KMailICalIfaceImpl::findStandardResourceFolder( KMFolderDir* folderParentDir, KMail::FolderContentsType contentsType )
+{
+ if ( GlobalSettings::self()->theIMAPResourceStorageFormat() == GlobalSettings::EnumTheIMAPResourceStorageFormat::XML )
+ {
+ // Look for a folder with an annotation like "event.default"
+ KMFolder* folder = findFolderByAnnotation( folderParentDir, QString( s_folderContentsType[contentsType].annotation ) + ".default" );
+ if ( folder )
+ return StandardFolderSearchResult( folder, StandardFolderSearchResult::FoundAndStandard );
+
+ // Fallback: look for a folder with an annotation like "event"
+ folder = findFolderByAnnotation( folderParentDir, QString( s_folderContentsType[contentsType].annotation ) );
+ if ( folder )
+ return StandardFolderSearchResult( folder, StandardFolderSearchResult::FoundByType );
+
+ // Fallback: look for the folder by name (we'll need to change its type)
+ KMFolderNode* node = folderParentDir->hasNamedFolder( localizedDefaultFolderName( contentsType ) );
+ if ( node && !node->isDir() )
+ return StandardFolderSearchResult( static_cast<KMFolder *>( node ), StandardFolderSearchResult::FoundByName );
+
+ kdDebug(5006) << "findStandardResourceFolder: found no resource folder for " << s_folderContentsType[contentsType].annotation << endl;
+ return StandardFolderSearchResult( 0, StandardFolderSearchResult::NotFound );
+ }
+ else // icalvcard: look up standard resource folders by name
+ {
+ KFolderTreeItem::Type itemType = s_folderContentsType[contentsType].treeItemType;
+ unsigned int folderLanguage = GlobalSettings::self()->theIMAPResourceFolderLanguage();
+ if( folderLanguage > 3 ) folderLanguage = 0;
+ KMFolderNode* node = folderParentDir->hasNamedFolder( folderName( itemType, folderLanguage ) );
+ if ( !node || node->isDir() )
+ return StandardFolderSearchResult( 0, StandardFolderSearchResult::NotFound );
+ return StandardFolderSearchResult( static_cast<KMFolder*>( node ), StandardFolderSearchResult::FoundAndStandard );
+ }
+}
+
+/* We treat all folders as relevant wrt alarms for which we have Administer
+ * rights or for which the "Incidences relevant for everyone" annotation has
+ * been set. It can be reasonably assumed that those are "ours". All local folders
+ * must be ours anyhow. */
+bool KMailICalIfaceImpl::folderIsAlarmRelevant( const KMFolder *folder )
+{
+ bool administerRights = true;
+ bool relevantForOwner = true;
+ bool relevantForEveryone = false;
+ if ( folder->folderType() == KMFolderTypeImap ) {
+ const KMFolderImap *imapFolder = static_cast<const KMFolderImap*>( folder->storage() );
+ administerRights =
+ imapFolder->userRights() <= 0 || imapFolder->userRights() & KMail::ACLJobs::Administer;
+ }
+ if ( folder->folderType() == KMFolderTypeCachedImap ) {
+ const KMFolderCachedImap *dimapFolder = static_cast<const KMFolderCachedImap*>( folder->storage() );
+ administerRights =
+ dimapFolder->userRights() <= 0 || dimapFolder->userRights() & KMail::ACLJobs::Administer;
+ relevantForOwner = !dimapFolder->alarmsBlocked() && ( dimapFolder->incidencesFor () == KMFolderCachedImap::IncForAdmins );
+ relevantForEveryone = !dimapFolder->alarmsBlocked() && ( dimapFolder->incidencesFor() == KMFolderCachedImap::IncForReaders );
+ }
+#if 0
+ kdDebug(5006) << k_funcinfo << endl;
+ kdDebug(5006) << "Folder: " << folder->label() << " has administer rights: " << administerRights << endl;
+ kdDebug(5006) << "and is relevant for owner: " << relevantForOwner << endl;
+ kdDebug(5006) << "and relevant for everyone: " << relevantForEveryone << endl;
+#endif
+ return ( administerRights && relevantForOwner ) || relevantForEveryone;
+}
+
+void KMailICalIfaceImpl::setResourceQuiet(bool q)
+{
+ mResourceQuiet = q;
+}
+
+bool KMailICalIfaceImpl::isResourceQuiet() const
+{
+ return mResourceQuiet;
+}
+
+
+bool KMailICalIfaceImpl::addSubresource( const QString& resource,
+ const QString& parent,
+ const QString& contentsType )
+{
+ kdDebug(5006) << "Adding subresource to parent: " << parent << " with name: " << resource << endl;
+ kdDebug(5006) << "contents type: " << contentsType << endl;
+ KMFolder *folder = findResourceFolder( parent );
+ KMFolderDir *parentFolderDir = !parent.isEmpty() && folder ? folder->createChildFolder(): mFolderParentDir;
+ if ( !parentFolderDir || parentFolderDir->hasNamedFolder( resource ) ) return false;
+
+ KMFolderType type = mFolderType;
+ if( type == KMFolderTypeUnknown ) type = KMFolderTypeMaildir;
+
+ KMFolder* newFolder = parentFolderDir->createFolder( resource, false, type );
+ if ( !newFolder ) return false;
+ if( mFolderType == KMFolderTypeImap )
+ static_cast<KMFolderImap*>( folder->storage() )->createFolder( resource );
+
+ StorageFormat defaultFormat = GlobalSettings::self()->theIMAPResourceStorageFormat() == GlobalSettings::EnumTheIMAPResourceStorageFormat::XML ? StorageXML : StorageIcalVcard;
+ setStorageFormat( newFolder, folder ? storageFormat( folder ) : defaultFormat );
+ newFolder->storage()->setContentsType( folderContentsType( contentsType ) );
+ newFolder->storage()->writeConfig();
+ newFolder->open( "ical_subresource" );
+ connectFolder( newFolder );
+ reloadFolderTree();
+
+ return true;
+}
+
+bool KMailICalIfaceImpl::removeSubresource( const QString& location )
+{
+ kdDebug(5006) << k_funcinfo << endl;
+
+ KMFolder *folder = findResourceFolder( location );
+
+ // We don't allow the default folders to be deleted, so check for
+ // those first. It would be nicer to produce a more meaningful error,
+ // or prevent deletion of the builtin folders from the gui already.
+ if ( !folder || isStandardResourceFolder( folder ) )
+ return false;
+
+ // the folder will be removed, which implies closed, so make sure
+ // nothing is using it anymore first
+ subresourceDeleted( folderContentsType( folder->storage()->contentsType() ), location );
+ mExtraFolders.remove( location );
+ folder->disconnect( this );
+
+ if ( folder->folderType() == KMFolderTypeImap )
+ kmkernel->imapFolderMgr()->remove( folder );
+ else if ( folder->folderType() == KMFolderTypeCachedImap ) {
+ // Deleted by user -> tell the account (see KMFolderCachedImap::listDirectory2)
+ KMFolderCachedImap* storage = static_cast<KMFolderCachedImap*>( folder->storage() );
+ KMAcctCachedImap* acct = storage->account();
+ if ( acct )
+ acct->addDeletedFolder( folder );
+ kmkernel->dimapFolderMgr()->remove( folder );
+ }
+ return true;
+}
+
+void KMailICalIfaceImpl::syncFolder(KMFolder * folder) const
+{
+ if ( kmkernel->isOffline() || !GlobalSettings::immediatlySyncDIMAPOnGroupwareChanges() )
+ return;
+ KMFolderCachedImap *dimapFolder = dynamic_cast<KMFolderCachedImap*>( folder->storage() );
+ if ( !dimapFolder )
+ return;
+ // check if the folder exists already, otherwise sync its parent as well to create it
+ if ( dimapFolder->imapPath().isEmpty() ) {
+ if ( folder->parent() && folder->parent()->owner() )
+ syncFolder( folder->parent()->owner() );
+ else
+ return;
+ }
+ dimapFolder->account()->processNewMailSingleFolder( folder );
+}
+
+#include "kmailicalifaceimpl.moc"
diff --git a/kmail/kmailicalifaceimpl.h b/kmail/kmailicalifaceimpl.h
new file mode 100644
index 00000000..26177486
--- /dev/null
+++ b/kmail/kmailicalifaceimpl.h
@@ -0,0 +1,347 @@
+/*
+ This file is part of KMail.
+
+ Copyright (c) 2003 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ Copyright (c) 2003 - 2004 Bo Thorsen <bo@sonofthor.dk>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+/** @file This file contains the class KMailICalIfaceImpl which actually
+* implements the ICal (DCOP) interface for KMail.
+*/
+
+#ifndef KMAILICALIFACEIMPL_H
+#define KMAILICALIFACEIMPL_H
+
+#include "kmailicalIface.h"
+#include "kmfoldertype.h"
+#include <kfoldertree.h>
+
+#include <qdict.h>
+#include <qguardedptr.h>
+#include <qmap.h>
+
+class KMFolder;
+class KMMessage;
+class KMFolderDir;
+class KMFolderTreeItem;
+
+namespace KMail {
+
+ // Local helper class
+class ExtraFolder {
+public:
+ ExtraFolder( KMFolder* f );
+ ~ExtraFolder();
+ QGuardedPtr<KMFolder> folder;
+};
+
+class Accumulator {
+public:
+ Accumulator( const QString& t, const QString& f, int c )
+ :type( t ), folder( f ), count( c ) {}
+
+ void add( const QString& incidence ) {
+ incidences << incidence;
+ count--;
+ }
+ bool isFull() { return count == 0; }
+
+ const QString type;
+ const QString folder;
+ QStringList incidences;
+ int count;
+};
+
+}
+
+/** The implementation of the interface. */
+class KMailICalIfaceImpl : public QObject, virtual public KMailICalIface {
+ Q_OBJECT
+public:
+ KMailICalIfaceImpl();
+
+ bool isWritableFolder( const QString& type, const QString& resource );
+
+ StorageFormat storageFormat( const QString &resource );
+
+ /// Update a kolab storage entry.
+ /// If message is not there, it is added and
+ /// given the subject as Subject: header.
+ /// Returns the new mail serial number,
+ /// or 0 if something went wrong,
+ Q_UINT32 update( const QString& resource,
+ Q_UINT32 sernum,
+ const QString& subject,
+ const QString& plainTextBody,
+ const QMap<QCString, QString>& customHeaders,
+ const QStringList& attachmentURLs,
+ const QStringList& attachmentMimetypes,
+ const QStringList& attachmentNames,
+ const QStringList& deletedAttachments );
+
+ bool deleteIncidenceKolab( const QString& resource,
+ Q_UINT32 sernum );
+ int incidencesKolabCount( const QString& mimetype,
+ const QString& resource );
+ QMap<Q_UINT32, QString> incidencesKolab( const QString& mimetype,
+ const QString& resource,
+ int startIndex,
+ int nbMessages );
+
+ QValueList<SubResource> subresourcesKolab( const QString& contentsType );
+
+ bool triggerSync( const QString& contentsType );
+
+ // "Get" an attachment. This actually saves the attachment in a file
+ // and returns a URL to it
+ KURL getAttachment( const QString& resource,
+ Q_UINT32 sernum,
+ const QString& filename );
+
+ QString attachmentMimetype( const QString &resource,
+ Q_UINT32 sernum,
+ const QString &filename );
+
+ QStringList listAttachments( const QString &resource, Q_UINT32 sernum );
+
+
+ bool removeSubresource( const QString& );
+
+ bool addSubresource( const QString& resource,
+ const QString& parent,
+ const QString& contentsType );
+
+ // tell KOrganizer about messages to be deleted
+ void msgRemoved( KMFolder*, KMMessage* );
+
+ /** Initialize all folders. */
+ void initFolders();
+
+ /** Disconnect all slots and close the dirs. */
+ void cleanup();
+
+ /**
+ * Returns true if resource mode is enabled and folder is one of the
+ * resource folders.
+ */
+ bool isResourceFolder( KMFolder* folder ) const;
+
+ /* Returns true if the folder is one of the standard resource folders, as
+ * opposed to an extra folder. */
+ bool isStandardResourceFolder( KMFolder* folder ) const;
+
+ /**
+ * Returns true if isResourceFolder( folder ) returns true, and
+ * imap folders should be hidden.
+ */
+ bool hideResourceFolder( KMFolder* folder ) const;
+
+ /**
+ * Returns true if the given folder is the root of the groupware account,
+ * groupware folders are hidden, and only groupware folders shown in this
+ * account.
+ */
+ bool hideResourceAccountRoot( KMFolder* folder ) const;
+
+ /**
+ * Returns the resource folder type. Other is returned if resource
+ * isn't enabled or it isn't a resource folder.
+ */
+ KFolderTreeItem::Type folderType( KMFolder* folder ) const;
+
+ /**
+ * Returns the name of the standard icon for a folder of given type or
+ * QString::null if the type is no groupware type.
+ */
+ QString folderPixmap( KFolderTreeItem::Type type ) const;
+
+ /** Returns the localized name of a folder of given type.
+ */
+ QString folderName( KFolderTreeItem::Type type, int language = -1 ) const;
+
+ /** Get the folder that holds *type* entries */
+ KMFolder* folderFromType( const QString& type, const QString& folder );
+
+ /** Return the ical type of a folder */
+ QString icalFolderType( KMFolder* folder ) const;
+
+ /** Find message matching a given UID. */
+ KMMessage* findMessageByUID( const QString& uid, KMFolder* folder );
+ /** Find message matching a given serial number. */
+ static KMMessage* findMessageBySerNum( Q_UINT32 serNum, KMFolder* folder );
+
+ /** Convenience function to delete a message. */
+ void deleteMsg( KMMessage* msg );
+
+ bool isEnabled() const { return mUseResourceIMAP; }
+
+ /** Called when a folders contents have changed */
+ void folderContentsTypeChanged( KMFolder*, KMail::FolderContentsType );
+
+ /// @return the storage format of a given folder
+ StorageFormat storageFormat( KMFolder* folder ) const;
+ /// Set the storage format of a given folder. Called when seeing the kolab annotation.
+ void setStorageFormat( KMFolder* folder, StorageFormat format );
+
+
+ static const char* annotationForContentsType( KMail::FolderContentsType type );
+
+ // Called after a folder was synced with the server
+ void folderSynced( KMFolder* folder, const KURL& folderURL );
+ // Called when deletion of a folder from the server suceeded,
+ // triggers fb re-generation
+ void folderDeletedOnServer( const KURL& folderURL );
+ void addFolderChange( KMFolder* folder, FolderChanges changes );
+
+ // See CachedImapJob::slotPutMessageResult
+ bool isResourceQuiet() const;
+ void setResourceQuiet(bool q);
+
+public slots:
+ /* (Re-)Read configuration file */
+ void readConfig();
+ void slotFolderRemoved( KMFolder* folder );
+
+ void slotIncidenceAdded( KMFolder* folder, Q_UINT32 sernum );
+ void slotIncidenceDeleted( KMFolder* folder, Q_UINT32 sernum );
+ void slotRefresh( const QString& type);
+
+ // Called when a folder is made readonly or readwrite, or renamed,
+ // or any other similar change that affects the resources
+ void slotFolderPropertiesChanged( KMFolder* folder );
+
+private slots:
+ void slotRefreshFolder( KMFolder* );
+ void slotCheckDone();
+ void slotFolderLocationChanged( const QString&, const QString& );
+ void slotFolderRenamed();
+ void slotMessageRetrieved( KMMessage* );
+
+private:
+ /** Helper function for initFolders. Initializes a single folder. */
+ KMFolder* initFolder( KMail::FolderContentsType contentsType );
+ KMFolder* initScalixFolder( KMail::FolderContentsType contentsType );
+
+ void connectFolder( KMFolder* folder );
+
+ KMFolder* extraFolder( const QString& type, const QString& folder );
+
+ void syncFolder( KMFolder* folder ) const;
+
+ struct StandardFolderSearchResult
+ {
+ enum FoundEnum { FoundAndStandard, NotFound, FoundByType, FoundByName };
+ StandardFolderSearchResult() : folder( 0 ) {}
+ StandardFolderSearchResult( KMFolder* f, FoundEnum e ) : folder( f ), found( e ) {}
+ KMFolder* folder; // NotFound implies folder==0 of course.
+ FoundEnum found;
+ };
+
+ StandardFolderSearchResult findStandardResourceFolder( KMFolderDir* folderParentDir, KMail::FolderContentsType contentsType );
+ KMFolder* findResourceFolder( const QString& resource );
+
+
+ bool updateAttachment( KMMessage& msg,
+ const QString& attachmentURL,
+ const QString& attachmentName,
+ const QString& attachmentMimetype,
+ bool lookupByName );
+ bool deleteAttachment( KMMessage& msg,
+ const QString& attachmentURL );
+ Q_UINT32 addIncidenceKolab( KMFolder& folder,
+ const QString& subject,
+ const QString& plainTextBody,
+ const QMap<QCString, QString>& customHeaders,
+ const QStringList& attachmentURLs,
+ const QStringList& attachmentNames,
+ const QStringList& attachmentMimetypes );
+ static bool kolabXMLFoundAndDecoded( const KMMessage& msg, const QString& mimetype, QString& s );
+
+ void handleFolderSynced( KMFolder* folder,
+ const KURL& folderURL,
+ int _changes );
+ void triggerKolabFreeBusy( const KURL& folderURL );
+
+ StorageFormat globalStorageFormat() const;
+
+ static bool folderIsAlarmRelevant( const KMFolder * );
+
+private:
+ QGuardedPtr<KMFolder> mContacts;
+ QGuardedPtr<KMFolder> mCalendar;
+ QGuardedPtr<KMFolder> mNotes;
+ QGuardedPtr<KMFolder> mTasks;
+ QGuardedPtr<KMFolder> mJournals;
+
+ // The extra IMAP resource folders
+ // Key: folder location. Data: folder.
+ QDict<KMail::ExtraFolder> mExtraFolders;
+ // used for collecting incidences during async loading
+ QDict<KMail::Accumulator> mAccumulators;
+ // More info for each folder we care about (mContacts etc. as well as the extra folders)
+ // The reason for storing it here is that it can be shared between
+ // kmfoldercachedimap and kmfolderimap, and that it's groupware data anyway.
+ struct FolderInfo {
+ FolderInfo() {} // for QMap
+ FolderInfo( StorageFormat f, FolderChanges c ) :
+ mStorageFormat( f ), mChanges( c ) {}
+ StorageFormat mStorageFormat;
+ FolderChanges mChanges;
+ };
+ // The storage format used for each folder that we care about
+ typedef QMap<KMFolder*, FolderInfo> FolderInfoMap;
+ // helper for reading the FolderInfo from the config file
+ FolderInfo readFolderInfo( const KMFolder * const folder ) const;
+
+ FolderInfoMap mFolderInfoMap;
+
+ unsigned int mFolderLanguage;
+
+ KMFolderDir* mFolderParentDir;
+ KMFolder* mFolderParent;
+ KMFolderType mFolderType;
+
+ bool mUseResourceIMAP;
+ bool mResourceQuiet;
+ bool mHideFolders;
+
+ /*
+ * Bunch of maps to keep track of incidents currently in transfer, ones
+ * which need to be ungotten, once we are done, once with updates pending.
+ * Since these are transient attributes of only a small but changing number
+ * of incidences they are not encapsulated in a struct or somesuch.
+ */
+ QMap<QString, Q_UINT32> mUIDToSerNum;
+ QMap<Q_UINT32, bool> mTheUnGetMes;
+ QMap<QString, QString> mPendingUpdates;
+ QMap<QString, bool> mInTransit;
+
+};
+
+#endif // KMAILICALIFACEIMPL_H
diff --git a/kmail/kmailpartIface.h b/kmail/kmailpartIface.h
new file mode 100644
index 00000000..11904f80
--- /dev/null
+++ b/kmail/kmailpartIface.h
@@ -0,0 +1,35 @@
+/*
+ This file is part of KMail.
+ Copyright (c) 2002 Don Sanders <sanders@kde.org>
+ Based on the work of Cornelius Schumacher <schumacher@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef KMAILIFACE_H
+#define KMAILIFACE_H
+
+#include <dcopobject.h>
+#include <kurl.h>
+
+class KMailPartIface : virtual public DCOPObject
+{
+ K_DCOP
+ k_dcop:
+ virtual void save() = 0;
+ virtual void exit() = 0;
+};
+
+#endif
diff --git a/kmail/kmatmlistview.cpp b/kmail/kmatmlistview.cpp
new file mode 100644
index 00000000..aca518e6
--- /dev/null
+++ b/kmail/kmatmlistview.cpp
@@ -0,0 +1,175 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+// kmatmlistview.cpp
+// Author: Markus Wuebben <markus.wuebben@kde.org>
+// This code is published under the GPL.
+
+#include <config.h>
+
+#include "kmatmlistview.h"
+#include <qcheckbox.h>
+#include <qheader.h>
+
+KMAtmListViewItem::KMAtmListViewItem( QListView *parent )
+ : QObject(),
+ QListViewItem( parent )
+{
+ mCBCompress = new QCheckBox( listView()->viewport() );
+ mCBEncrypt = new QCheckBox( listView()->viewport() );
+ mCBSign = new QCheckBox( listView()->viewport() );
+ mCBCompress->setShown( true );
+ updateAllCheckBoxes();
+
+ connect( mCBCompress, SIGNAL( clicked() ), this, SLOT( slotCompress() ) );
+ connect( listView()->header(), SIGNAL( sizeChange(int, int, int) ),
+ SLOT( slotHeaderChange( int, int, int ) ) );
+ connect( listView()->header(), SIGNAL( indexChange(int, int, int) ),
+ SLOT( slotHeaderChange( int, int, int ) ) );
+ connect( listView()->header(), SIGNAL( clicked( int ) ), SLOT( slotHeaderClick( int ) ) );
+}
+
+KMAtmListViewItem::~KMAtmListViewItem()
+{
+ delete mCBEncrypt;
+ mCBEncrypt = 0;
+ delete mCBSign;
+ mCBSign = 0;
+ delete mCBCompress;
+ mCBCompress = 0;
+}
+
+void KMAtmListViewItem::updateCheckBox( int headerSection, QCheckBox *cb )
+{
+ //Calculate some values to determine the x-position where the checkbox
+ //will be drawn
+ int sectionWidth = listView()->header()->sectionSize( headerSection );
+ int sectionPos = listView()->header()->sectionPos( headerSection );
+ int sectionOffset = sectionWidth / 2 - height() / 4;
+
+ //Resize and move the checkbox
+ cb->resize( sectionWidth - sectionOffset - 1, height() - 2 );
+ listView()->moveChild( cb, sectionPos + sectionOffset, itemPos() + 1 );
+
+ //Set the correct background color
+ QColor bg;
+ if ( isSelected() ) {
+ bg = listView()->colorGroup().highlight();
+ } else {
+ bg = listView()->colorGroup().base();
+ }
+ cb->setPaletteBackgroundColor( bg );
+}
+
+void KMAtmListViewItem::updateAllCheckBoxes()
+{
+ updateCheckBox( 4, mCBCompress );
+ updateCheckBox( 5, mCBEncrypt );
+ updateCheckBox( 6, mCBSign );
+}
+
+// Each time a cell is about to be painted, the item's checkboxes are updated
+// as well. This is necessary to keep the positions of the checkboxes
+// up-to-date. The signals which are, in the constructor of this class,
+// connected to the update slots are not sufficent because unfortunatly,
+// Qt does not provide a signal for changed item positions, e.g. during
+// deleting or adding items. The problem with this is that this function does
+// not catch updates which are off-screen, which means under some circumstances
+// checkboxes have invalid positions. This should not happen anymore, but was
+// the cause of bug 113458. Therefore, both the signals connected in the
+// constructor and this function are necessary to keep the checkboxes'
+// positions in sync, and hopefully is enough.
+void KMAtmListViewItem::paintCell ( QPainter * p, const QColorGroup &cg,
+ int column, int width, int align )
+{
+ switch ( column ) {
+ case 4: updateCheckBox( 4, mCBCompress ); break;
+ case 5: updateCheckBox( 5, mCBEncrypt ); break;
+ case 6: updateCheckBox( 6, mCBSign ); break;
+ }
+
+ QListViewItem::paintCell( p, cg, column, width, align );
+}
+
+int KMAtmListViewItem::compare( QListViewItem *i, int col, bool ascending ) const
+{
+ if ( col != 1 ) {
+ return QListViewItem::compare( i, col, ascending );
+ }
+
+ return mAttachmentSize -
+ (static_cast<KMAtmListViewItem*>(i))->mAttachmentSize;
+}
+
+void KMAtmListViewItem::enableCryptoCBs( bool on )
+{
+ // Show/Hide the appropriate checkboxes.
+ // This should not be necessary because the caller hides the columns
+ // containing the checkboxes anyway.
+ mCBEncrypt->setShown( on );
+ mCBSign->setShown( on );
+}
+
+void KMAtmListViewItem::setEncrypt( bool on )
+{
+ if ( mCBEncrypt ) {
+ mCBEncrypt->setChecked( on );
+ }
+}
+
+bool KMAtmListViewItem::isEncrypt()
+{
+ if ( mCBEncrypt ) {
+ return mCBEncrypt->isChecked();
+ } else {
+ return false;
+ }
+}
+
+void KMAtmListViewItem::setSign( bool on )
+{
+ if ( mCBSign ) {
+ mCBSign->setChecked( on );
+ }
+}
+
+bool KMAtmListViewItem::isSign()
+{
+ if ( mCBSign ) {
+ return mCBSign->isChecked();
+ } else {
+ return false;
+ }
+}
+
+void KMAtmListViewItem::setCompress( bool on )
+{
+ mCBCompress->setChecked( on );
+}
+
+bool KMAtmListViewItem::isCompress()
+{
+ return mCBCompress->isChecked();
+}
+
+void KMAtmListViewItem::slotCompress()
+{
+ if ( mCBCompress->isChecked() ) {
+ emit compress( itemPos() );
+ } else {
+ emit uncompress( itemPos() );
+ }
+}
+
+// Update the item's checkboxes when the position of those change
+// due to different column positions
+void KMAtmListViewItem::slotHeaderChange ( int, int, int )
+{
+ updateAllCheckBoxes();
+}
+
+//Update the item's checkboxes when the list is being sorted
+void KMAtmListViewItem::slotHeaderClick( int )
+{
+ updateAllCheckBoxes();
+}
+
+#include "kmatmlistview.moc"
diff --git a/kmail/kmatmlistview.h b/kmail/kmatmlistview.h
new file mode 100644
index 00000000..f50d4e39
--- /dev/null
+++ b/kmail/kmatmlistview.h
@@ -0,0 +1,71 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ * KMAtmListViewItem Header File
+ * Author: Markus Wuebben <markus.wuebben@kde.org>
+ */
+#ifndef __KMAIL_KMATMLISTVIEW_H__
+#define __KMAIL_KMATMLISTVIEW_H__
+
+#include <qlistview.h>
+#include <qcstring.h>
+
+class KMComposeWin;
+class MessageComposer;
+class QCheckBox;
+
+class KMAtmListViewItem : public QObject, public QListViewItem
+{
+ Q_OBJECT
+
+public:
+ KMAtmListViewItem( QListView *parent );
+ virtual ~KMAtmListViewItem();
+
+ //A custom compare function is needed because the size column is
+ //human-readable and therefore doesn't sort correctly.
+ virtual int compare( QListViewItem *i, int col, bool ascending ) const;
+
+ virtual void paintCell ( QPainter * p, const QColorGroup & cg, int column, int width, int align );
+
+ void setUncompressedMimeType( const QCString & type, const QCString & subtype ) {
+ mType = type; mSubtype = subtype;
+ }
+ void setAttachmentSize( int numBytes ) {
+ mAttachmentSize = numBytes;
+ }
+ void uncompressedMimeType( QCString & type, QCString & subtype ) const {
+ type = mType; subtype = mSubtype;
+ }
+ void setUncompressedCodec( const QCString &codec ) { mCodec = codec; }
+ QCString uncompressedCodec() const { return mCodec; }
+
+ void enableCryptoCBs( bool on );
+ void setEncrypt( bool on );
+ bool isEncrypt();
+ void setSign( bool on );
+ bool isSign();
+ void setCompress( bool on );
+ bool isCompress();
+
+signals:
+ void compress( int );
+ void uncompress( int );
+
+private slots:
+ void slotCompress();
+ void slotHeaderChange( int, int, int );
+ void slotHeaderClick( int );
+
+protected:
+
+ void updateCheckBox( int headerSection, QCheckBox *cb );
+ void updateAllCheckBoxes();
+
+private:
+ QCheckBox *mCBEncrypt;
+ QCheckBox *mCBSign;
+ QCheckBox *mCBCompress;
+ QCString mType, mSubtype, mCodec;
+ int mAttachmentSize;
+};
+
+#endif // __KMAIL_KMATMLISTVIEW_H__
diff --git a/kmail/kmcommands.cpp b/kmail/kmcommands.cpp
new file mode 100644
index 00000000..3119bb4b
--- /dev/null
+++ b/kmail/kmcommands.cpp
@@ -0,0 +1,3559 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2002 Don Sanders <sanders@kde.org>
+
+ 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
+*/
+
+//
+// This file implements various "command" classes. These command classes
+// are based on the command design pattern.
+//
+// Historically various operations were implemented as slots of KMMainWin.
+// This proved inadequate as KMail has multiple top level windows
+// (KMMainWin, KMReaderMainWin, SearchWindow, KMComposeWin) that may
+// benefit from using these operations. It is desirable that these
+// classes can operate without depending on or altering the state of
+// a KMMainWin, in fact it is possible no KMMainWin object even exists.
+//
+// Now these operations have been rewritten as KMCommand based classes,
+// making them independent of KMMainWin.
+//
+// The base command class KMCommand is async, which is a difference
+// from the conventional command pattern. As normal derived classes implement
+// the execute method, but client classes call start() instead of
+// calling execute() directly. start() initiates async operations,
+// and on completion of these operations calls execute() and then deletes
+// the command. (So the client must not construct commands on the stack).
+//
+// The type of async operation supported by KMCommand is retrieval
+// of messages from an IMAP server.
+
+#include "kmcommands.h"
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <mimelib/enum.h>
+#include <mimelib/field.h>
+#include <mimelib/mimepp.h>
+#include <mimelib/string.h>
+#include <kapplication.h>
+#include <dcopclient.h>
+
+#include <qtextcodec.h>
+#include <qpopupmenu.h>
+#include <qeventloop.h>
+
+#include <libemailfunctions/email.h>
+#include <kdcopservicestarter.h>
+#include <kdebug.h>
+#include <kfiledialog.h>
+#include <kabc/stdaddressbook.h>
+#include <kabc/addresseelist.h>
+#include <kdirselectdialog.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kparts/browserextension.h>
+#include <kprogress.h>
+#include <krun.h>
+#include <kbookmarkmanager.h>
+#include <kstandarddirs.h>
+#include <ktempfile.h>
+#include <kimproxy.h>
+#include <kuserprofile.h>
+// KIO headers
+#include <kio/job.h>
+#include <kio/netaccess.h>
+
+#include <libkpimidentities/identitymanager.h>
+
+#include "actionscheduler.h"
+using KMail::ActionScheduler;
+#include "mailinglist-magic.h"
+#include "kmaddrbook.h"
+#include <kaddrbook.h>
+#include "composer.h"
+#include "kmfiltermgr.h"
+#include "kmfoldermbox.h"
+#include "kmfolderimap.h"
+#include "kmfoldermgr.h"
+#include "kmheaders.h"
+#include "headeritem.h"
+#include "kmmainwidget.h"
+#include "kmmsgdict.h"
+#include "messagesender.h"
+#include "kmmsgpartdlg.h"
+#include "undostack.h"
+#include "kcursorsaver.h"
+#include "partNode.h"
+#include "objecttreeparser.h"
+using KMail::ObjectTreeParser;
+using KMail::FolderJob;
+#include "chiasmuskeyselector.h"
+#include "mailsourceviewer.h"
+using KMail::MailSourceViewer;
+#include "kmreadermainwin.h"
+#include "secondarywindow.h"
+using KMail::SecondaryWindow;
+#include "redirectdialog.h"
+using KMail::RedirectDialog;
+#include "util.h"
+#include "templateparser.h"
+#include "editorwatcher.h"
+#include "korghelper.h"
+
+#include "broadcaststatus.h"
+#include "globalsettings.h"
+
+#include <libkdepim/kfileio.h>
+#include "kcalendariface_stub.h"
+
+#include "progressmanager.h"
+using KPIM::ProgressManager;
+using KPIM::ProgressItem;
+#include <kmime_mdn.h>
+using namespace KMime;
+
+#include <kleo/specialjob.h>
+#include <kleo/cryptobackend.h>
+#include <kleo/cryptobackendfactory.h>
+
+#include <qclipboard.h>
+
+#include <memory>
+
+class LaterDeleterWithCommandCompletion : public KMail::Util::LaterDeleter
+{
+public:
+ LaterDeleterWithCommandCompletion( KMCommand* command )
+ :LaterDeleter( command ), m_result( KMCommand::Failed )
+ {
+ }
+ ~LaterDeleterWithCommandCompletion()
+ {
+ setResult( m_result );
+ KMCommand *command = static_cast<KMCommand*>( m_object );
+ emit command->completed( command );
+ }
+ void setResult( KMCommand::Result v ) { m_result = v; }
+private:
+ KMCommand::Result m_result;
+};
+
+
+KMCommand::KMCommand( QWidget *parent )
+ : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
+ mEmitsCompletedItself( false ), mParent( parent )
+{
+}
+
+KMCommand::KMCommand( QWidget *parent, const QPtrList<KMMsgBase> &msgList )
+ : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
+ mEmitsCompletedItself( false ), mParent( parent ), mMsgList( msgList )
+{
+}
+
+KMCommand::KMCommand( QWidget *parent, KMMsgBase *msgBase )
+ : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
+ mEmitsCompletedItself( false ), mParent( parent )
+{
+ mMsgList.append( msgBase );
+}
+
+KMCommand::KMCommand( QWidget *parent, KMMessage *msg )
+ : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
+ mEmitsCompletedItself( false ), mParent( parent )
+{
+ if (msg)
+ mMsgList.append( &msg->toMsgBase() );
+}
+
+KMCommand::~KMCommand()
+{
+ QValueListIterator<QGuardedPtr<KMFolder> > fit;
+ for ( fit = mFolders.begin(); fit != mFolders.end(); ++fit ) {
+ if (!(*fit))
+ continue;
+ (*fit)->close("kmcommand");
+ }
+}
+
+KMCommand::Result KMCommand::result()
+{
+ if ( mResult == Undefined )
+ kdDebug(5006) << k_funcinfo << "mResult is Undefined" << endl;
+ return mResult;
+}
+
+void KMCommand::start()
+{
+ QTimer::singleShot( 0, this, SLOT( slotStart() ) );
+}
+
+
+const QPtrList<KMMessage> KMCommand::retrievedMsgs() const
+{
+ return mRetrievedMsgs;
+}
+
+KMMessage *KMCommand::retrievedMessage() const
+{
+ return mRetrievedMsgs.getFirst();
+}
+
+QWidget *KMCommand::parentWidget() const
+{
+ return mParent;
+}
+
+int KMCommand::mCountJobs = 0;
+
+void KMCommand::slotStart()
+{
+ connect( this, SIGNAL( messagesTransfered( KMCommand::Result ) ),
+ this, SLOT( slotPostTransfer( KMCommand::Result ) ) );
+ kmkernel->filterMgr()->ref();
+
+ if (mMsgList.find(0) != -1) {
+ emit messagesTransfered( Failed );
+ return;
+ }
+
+ if ((mMsgList.count() == 1) &&
+ (mMsgList.getFirst()->isMessage()) &&
+ (mMsgList.getFirst()->parent() == 0))
+ {
+ // Special case of operating on message that isn't in a folder
+ mRetrievedMsgs.append((KMMessage*)mMsgList.getFirst());
+ emit messagesTransfered( OK );
+ return;
+ }
+
+ for (KMMsgBase *mb = mMsgList.first(); mb; mb = mMsgList.next())
+ if (!mb->parent()) {
+ emit messagesTransfered( Failed );
+ return;
+ } else {
+ keepFolderOpen( mb->parent() );
+ }
+
+ // transfer the selected messages first
+ transferSelectedMsgs();
+}
+
+void KMCommand::slotPostTransfer( KMCommand::Result result )
+{
+ disconnect( this, SIGNAL( messagesTransfered( KMCommand::Result ) ),
+ this, SLOT( slotPostTransfer( KMCommand::Result ) ) );
+ if ( result == OK )
+ result = execute();
+ mResult = result;
+ QPtrListIterator<KMMessage> it( mRetrievedMsgs );
+ KMMessage* msg;
+ while ( (msg = it.current()) != 0 )
+ {
+ ++it;
+ if (msg->parent())
+ msg->setTransferInProgress(false);
+ }
+ kmkernel->filterMgr()->deref();
+ if ( !emitsCompletedItself() )
+ emit completed( this );
+ if ( !deletesItself() )
+ deleteLater();
+}
+
+void KMCommand::transferSelectedMsgs()
+{
+ // make sure no other transfer is active
+ if (KMCommand::mCountJobs > 0) {
+ emit messagesTransfered( Failed );
+ return;
+ }
+
+ bool complete = true;
+ KMCommand::mCountJobs = 0;
+ mCountMsgs = 0;
+ mRetrievedMsgs.clear();
+ mCountMsgs = mMsgList.count();
+ uint totalSize = 0;
+ // the KProgressDialog for the user-feedback. Only enable it if it's needed.
+ // For some commands like KMSetStatusCommand it's not needed. Note, that
+ // for some reason the KProgressDialog eats the MouseReleaseEvent (if a
+ // command is executed after the MousePressEvent), cf. bug #71761.
+ if ( mCountMsgs > 0 ) {
+ mProgressDialog = new KProgressDialog(mParent, "transferProgress",
+ i18n("Please wait"),
+ i18n("Please wait while the message is transferred",
+ "Please wait while the %n messages are transferred", mMsgList.count()),
+ true);
+ mProgressDialog->setMinimumDuration(1000);
+ }
+ for (KMMsgBase *mb = mMsgList.first(); mb; mb = mMsgList.next())
+ {
+ // check if all messages are complete
+ KMMessage *thisMsg = 0;
+ if ( mb->isMessage() )
+ thisMsg = static_cast<KMMessage*>(mb);
+ else
+ {
+ KMFolder *folder = mb->parent();
+ int idx = folder->find(mb);
+ if (idx < 0) continue;
+ thisMsg = folder->getMsg(idx);
+ }
+ if (!thisMsg) continue;
+ if ( thisMsg->transferInProgress() &&
+ thisMsg->parent()->folderType() == KMFolderTypeImap )
+ {
+ thisMsg->setTransferInProgress( false, true );
+ thisMsg->parent()->ignoreJobsForMessage( thisMsg );
+ }
+
+ if ( thisMsg->parent() && !thisMsg->isComplete() &&
+ ( !mProgressDialog || !mProgressDialog->wasCancelled() ) )
+ {
+ kdDebug(5006)<<"### INCOMPLETE\n";
+ // the message needs to be transferred first
+ complete = false;
+ KMCommand::mCountJobs++;
+ FolderJob *job = thisMsg->parent()->createJob(thisMsg);
+ job->setCancellable( false );
+ totalSize += thisMsg->msgSizeServer();
+ // emitted when the message was transferred successfully
+ connect(job, SIGNAL(messageRetrieved(KMMessage*)),
+ this, SLOT(slotMsgTransfered(KMMessage*)));
+ // emitted when the job is destroyed
+ connect(job, SIGNAL(finished()),
+ this, SLOT(slotJobFinished()));
+ connect(job, SIGNAL(progress(unsigned long, unsigned long)),
+ this, SLOT(slotProgress(unsigned long, unsigned long)));
+ // msg musn't be deleted
+ thisMsg->setTransferInProgress(true);
+ job->start();
+ } else {
+ thisMsg->setTransferInProgress(true);
+ mRetrievedMsgs.append(thisMsg);
+ }
+ }
+
+ if (complete)
+ {
+ delete mProgressDialog;
+ mProgressDialog = 0;
+ emit messagesTransfered( OK );
+ } else {
+ // wait for the transfer and tell the progressBar the necessary steps
+ if ( mProgressDialog ) {
+ connect(mProgressDialog, SIGNAL(cancelClicked()),
+ this, SLOT(slotTransferCancelled()));
+ mProgressDialog->progressBar()->setTotalSteps(totalSize);
+ }
+ }
+}
+
+void KMCommand::slotMsgTransfered(KMMessage* msg)
+{
+ if ( mProgressDialog && mProgressDialog->wasCancelled() ) {
+ emit messagesTransfered( Canceled );
+ return;
+ }
+
+ // save the complete messages
+ mRetrievedMsgs.append(msg);
+}
+
+void KMCommand::slotProgress( unsigned long done, unsigned long /*total*/ )
+{
+ mProgressDialog->progressBar()->setProgress( done );
+}
+
+void KMCommand::slotJobFinished()
+{
+ // the job is finished (with / without error)
+ KMCommand::mCountJobs--;
+
+ if ( mProgressDialog && mProgressDialog->wasCancelled() ) return;
+
+ if ( (mCountMsgs - static_cast<int>(mRetrievedMsgs.count())) > KMCommand::mCountJobs )
+ {
+ // the message wasn't retrieved before => error
+ if ( mProgressDialog )
+ mProgressDialog->hide();
+ slotTransferCancelled();
+ return;
+ }
+ // update the progressbar
+ if ( mProgressDialog ) {
+ mProgressDialog->setLabel(i18n("Please wait while the message is transferred",
+ "Please wait while the %n messages are transferred", KMCommand::mCountJobs));
+ }
+ if (KMCommand::mCountJobs == 0)
+ {
+ // all done
+ delete mProgressDialog;
+ mProgressDialog = 0;
+ emit messagesTransfered( OK );
+ }
+}
+
+void KMCommand::slotTransferCancelled()
+{
+ // kill the pending jobs
+ QValueListIterator<QGuardedPtr<KMFolder> > fit;
+ for ( fit = mFolders.begin(); fit != mFolders.end(); ++fit ) {
+ if (!(*fit))
+ continue;
+ KMFolder *folder = *fit;
+ KMFolderImap *imapFolder = dynamic_cast<KMFolderImap*>(folder);
+ if (imapFolder && imapFolder->account()) {
+ imapFolder->account()->killAllJobs();
+ }
+ }
+
+ KMCommand::mCountJobs = 0;
+ mCountMsgs = 0;
+ // unget the transfered messages
+ QPtrListIterator<KMMessage> it( mRetrievedMsgs );
+ KMMessage* msg;
+ while ( (msg = it.current()) != 0 )
+ {
+ KMFolder *folder = msg->parent();
+ ++it;
+ if (!folder)
+ continue;
+ msg->setTransferInProgress(false);
+ int idx = folder->find(msg);
+ if (idx > 0) folder->unGetMsg(idx);
+ }
+ mRetrievedMsgs.clear();
+ emit messagesTransfered( Canceled );
+}
+
+void KMCommand::keepFolderOpen( KMFolder *folder )
+{
+ folder->open("kmcommand");
+ mFolders.append( folder );
+}
+
+KMMailtoComposeCommand::KMMailtoComposeCommand( const KURL &url,
+ KMMessage *msg )
+ :mUrl( url ), mMessage( msg )
+{
+}
+
+KMCommand::Result KMMailtoComposeCommand::execute()
+{
+ KMMessage *msg = new KMMessage;
+ uint id = 0;
+
+ if ( mMessage && mMessage->parent() )
+ id = mMessage->parent()->identity();
+
+ msg->initHeader(id);
+ msg->setCharset("utf-8");
+ msg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );
+
+ KMail::Composer * win = KMail::makeComposer( msg, id );
+ win->setCharset("", true);
+ win->setFocusToSubject();
+ win->show();
+
+ return OK;
+}
+
+
+KMMailtoReplyCommand::KMMailtoReplyCommand( QWidget *parent,
+ const KURL &url, KMMessage *msg, const QString &selection )
+ :KMCommand( parent, msg ), mUrl( url ), mSelection( selection )
+{
+}
+
+KMCommand::Result KMMailtoReplyCommand::execute()
+{
+ //TODO : consider factoring createReply into this method.
+ KMMessage *msg = retrievedMessage();
+ if ( !msg || !msg->codec() ) {
+ return Failed;
+ }
+ KMMessage *rmsg = msg->createReply( KMail::ReplyNone, mSelection );
+ rmsg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );
+
+ KMail::Composer * win = KMail::makeComposer( rmsg, 0 );
+ win->setCharset(msg->codec()->mimeName(), true);
+ win->setReplyFocus();
+ win->show();
+
+ return OK;
+}
+
+
+KMMailtoForwardCommand::KMMailtoForwardCommand( QWidget *parent,
+ const KURL &url, KMMessage *msg )
+ :KMCommand( parent, msg ), mUrl( url )
+{
+}
+
+KMCommand::Result KMMailtoForwardCommand::execute()
+{
+ //TODO : consider factoring createForward into this method.
+ KMMessage *msg = retrievedMessage();
+ if ( !msg || !msg->codec() ) {
+ return Failed;
+ }
+ KMMessage *fmsg = msg->createForward();
+ fmsg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );
+
+ KMail::Composer * win = KMail::makeComposer( fmsg );
+ win->setCharset(msg->codec()->mimeName(), true);
+ win->show();
+
+ return OK;
+}
+
+
+KMAddBookmarksCommand::KMAddBookmarksCommand( const KURL &url, QWidget *parent )
+ : KMCommand( parent ), mUrl( url )
+{
+}
+
+KMCommand::Result KMAddBookmarksCommand::execute()
+{
+ QString filename = locateLocal( "data", QString::fromLatin1("konqueror/bookmarks.xml") );
+ KBookmarkManager *bookManager = KBookmarkManager::managerForFile( filename,
+ false );
+ KBookmarkGroup group = bookManager->root();
+ group.addBookmark( bookManager, mUrl.path(), KURL( mUrl ) );
+ if( bookManager->save() ) {
+ bookManager->emitChanged( group );
+ }
+
+ return OK;
+}
+
+KMMailtoAddAddrBookCommand::KMMailtoAddAddrBookCommand( const KURL &url,
+ QWidget *parent )
+ : KMCommand( parent ), mUrl( url )
+{
+}
+
+KMCommand::Result KMMailtoAddAddrBookCommand::execute()
+{
+ KAddrBookExternal::addEmail( KMMessage::decodeMailtoUrl( mUrl.path() ),
+ parentWidget() );
+
+ return OK;
+}
+
+
+KMMailtoOpenAddrBookCommand::KMMailtoOpenAddrBookCommand( const KURL &url,
+ QWidget *parent )
+ : KMCommand( parent ), mUrl( url )
+{
+}
+
+KMCommand::Result KMMailtoOpenAddrBookCommand::execute()
+{
+ KAddrBookExternal::openEmail( KMMessage::decodeMailtoUrl( mUrl.path() ),
+ parentWidget() );
+
+ return OK;
+}
+
+
+KMUrlCopyCommand::KMUrlCopyCommand( const KURL &url, KMMainWidget *mainWidget )
+ :mUrl( url ), mMainWidget( mainWidget )
+{
+}
+
+KMCommand::Result KMUrlCopyCommand::execute()
+{
+ QClipboard* clip = QApplication::clipboard();
+
+ if (mUrl.protocol() == "mailto") {
+ // put the url into the mouse selection and the clipboard
+ QString address = KMMessage::decodeMailtoUrl( mUrl.path() );
+ clip->setSelectionMode( true );
+ clip->setText( address );
+ clip->setSelectionMode( false );
+ clip->setText( address );
+ KPIM::BroadcastStatus::instance()->setStatusMsg( i18n( "Address copied to clipboard." ));
+ } else {
+ // put the url into the mouse selection and the clipboard
+ clip->setSelectionMode( true );
+ clip->setText( mUrl.url() );
+ clip->setSelectionMode( false );
+ clip->setText( mUrl.url() );
+ KPIM::BroadcastStatus::instance()->setStatusMsg( i18n( "URL copied to clipboard." ));
+ }
+
+ return OK;
+}
+
+
+KMUrlOpenCommand::KMUrlOpenCommand( const KURL &url, KMReaderWin *readerWin )
+ :mUrl( url ), mReaderWin( readerWin )
+{
+}
+
+KMCommand::Result KMUrlOpenCommand::execute()
+{
+ if ( !mUrl.isEmpty() )
+ mReaderWin->slotUrlOpen( mUrl, KParts::URLArgs() );
+
+ return OK;
+}
+
+
+KMUrlSaveCommand::KMUrlSaveCommand( const KURL &url, QWidget *parent )
+ : KMCommand( parent ), mUrl( url )
+{
+}
+
+KMCommand::Result KMUrlSaveCommand::execute()
+{
+ if ( mUrl.isEmpty() )
+ return OK;
+ KURL saveUrl = KFileDialog::getSaveURL(mUrl.fileName(), QString::null,
+ parentWidget() );
+ if ( saveUrl.isEmpty() )
+ return Canceled;
+ if ( KIO::NetAccess::exists( saveUrl, false, parentWidget() ) )
+ {
+ if (KMessageBox::warningContinueCancel(0,
+ i18n("<qt>File <b>%1</b> exists.<br>Do you want to replace it?</qt>")
+ .arg(saveUrl.prettyURL()), i18n("Save to File"), i18n("&Replace"))
+ != KMessageBox::Continue)
+ return Canceled;
+ }
+ KIO::Job *job = KIO::file_copy(mUrl, saveUrl, -1, true);
+ connect(job, SIGNAL(result(KIO::Job*)), SLOT(slotUrlSaveResult(KIO::Job*)));
+ setEmitsCompletedItself( true );
+ return OK;
+}
+
+void KMUrlSaveCommand::slotUrlSaveResult( KIO::Job *job )
+{
+ if ( job->error() ) {
+ job->showErrorDialog();
+ setResult( Failed );
+ emit completed( this );
+ }
+ else {
+ setResult( OK );
+ emit completed( this );
+ }
+}
+
+
+KMEditMsgCommand::KMEditMsgCommand( QWidget *parent, KMMessage *msg )
+ :KMCommand( parent, msg )
+{
+}
+
+KMCommand::Result KMEditMsgCommand::execute()
+{
+ KMMessage *msg = retrievedMessage();
+ if ( !msg || !msg->parent() ||
+ ( !kmkernel->folderIsDraftOrOutbox( msg->parent() ) &&
+ !kmkernel->folderIsTemplates( msg->parent() ) ) )
+ return Failed;
+
+ // Remember the old parent, we need it a bit further down to be able
+ // to put the unchanged messsage back in the original folder if the nth
+ // edit is discarded, for n > 1.
+ KMFolder *parent = msg->parent();
+ if ( parent )
+ parent->take( parent->find( msg ) );
+
+ KMail::Composer * win = KMail::makeComposer();
+ msg->setTransferInProgress(false); // From here on on, the composer owns the message.
+ win->setMsg(msg, false, true);
+ win->setFolder( parent );
+ win->show();
+
+ return OK;
+}
+
+KMUseTemplateCommand::KMUseTemplateCommand( QWidget *parent, KMMessage *msg )
+ :KMCommand( parent, msg )
+{
+}
+
+KMCommand::Result KMUseTemplateCommand::execute()
+{
+ KMMessage *msg = retrievedMessage();
+ if ( !msg || !msg->parent() ||
+ !kmkernel->folderIsTemplates( msg->parent() ) )
+ return Failed;
+
+ // Take a copy of the original message, which remains unchanged.
+ KMMessage *newMsg = new KMMessage( new DwMessage( *msg->asDwMessage() ) );
+ newMsg->setComplete( msg->isComplete() );
+
+ // these fields need to be regenerated for the new message
+ newMsg->removeHeaderField("Date");
+ newMsg->removeHeaderField("Message-ID");
+
+ KMail::Composer *win = KMail::makeComposer();
+ newMsg->setTransferInProgress( false ); // From here on on, the composer owns the message.
+ win->setMsg( newMsg, false, true );
+ win->show();
+
+ return OK;
+}
+
+KMShowMsgSrcCommand::KMShowMsgSrcCommand( QWidget *parent,
+ KMMessage *msg, bool fixedFont )
+ :KMCommand( parent, msg ), mFixedFont( fixedFont )
+{
+ // remember complete state
+ mMsgWasComplete = msg->isComplete();
+}
+
+KMCommand::Result KMShowMsgSrcCommand::execute()
+{
+ KMMessage *msg = retrievedMessage();
+ if ( !msg || !msg->codec() ) {
+ return Failed;
+ }
+ if ( msg->isComplete() && !mMsgWasComplete )
+ msg->notify(); // notify observers as msg was transfered
+ QString str = msg->codec()->toUnicode( msg->asString() );
+
+ MailSourceViewer *viewer = new MailSourceViewer(); // deletes itself upon close
+ viewer->setCaption( i18n("Message as Plain Text") );
+ viewer->setText(str);
+ if( mFixedFont )
+ viewer->setFont(KGlobalSettings::fixedFont());
+
+ // Well, there is no widget to be seen here, so we have to use QCursor::pos()
+ // Update: (GS) I'm not going to make this code behave according to Xinerama
+ // configuration because this is quite the hack.
+ if (QApplication::desktop()->isVirtualDesktop()) {
+ int scnum = QApplication::desktop()->screenNumber(QCursor::pos());
+ viewer->resize(QApplication::desktop()->screenGeometry(scnum).width()/2,
+ 2*QApplication::desktop()->screenGeometry(scnum).height()/3);
+ } else {
+ viewer->resize(QApplication::desktop()->geometry().width()/2,
+ 2*QApplication::desktop()->geometry().height()/3);
+ }
+ viewer->show();
+
+ return OK;
+}
+
+static KURL subjectToUrl( const QString & subject ) {
+ return KFileDialog::getSaveURL( subject.stripWhiteSpace()
+ .replace( QDir::separator(), '_' ),
+ "*.mbox" );
+}
+
+KMSaveMsgCommand::KMSaveMsgCommand( QWidget *parent, KMMessage * msg )
+ : KMCommand( parent ),
+ mMsgListIndex( 0 ),
+ mStandAloneMessage( 0 ),
+ mOffset( 0 ),
+ mTotalSize( msg ? msg->msgSize() : 0 )
+{
+ if ( !msg ) return;
+ setDeletesItself( true );
+ // If the mail has a serial number, operate on sernums, if it does not
+ // we need to work with the pointer, but can be reasonably sure it won't
+ // go away, since it'll be an encapsulated message or one that was opened
+ // from an .eml file.
+ if ( msg->getMsgSerNum() != 0 ) {
+ mMsgList.append( msg->getMsgSerNum() );
+ if ( msg->parent() ) {
+ msg->parent()->open( "kmsavemsgcommand" );
+ }
+ } else {
+ mStandAloneMessage = msg;
+ }
+ mUrl = subjectToUrl( msg->cleanSubject() );
+}
+
+KMSaveMsgCommand::KMSaveMsgCommand( QWidget *parent,
+ const QPtrList<KMMsgBase> &msgList )
+ : KMCommand( parent ),
+ mMsgListIndex( 0 ),
+ mStandAloneMessage( 0 ),
+ mOffset( 0 ),
+ mTotalSize( 0 )
+{
+ if (!msgList.getFirst())
+ return;
+ setDeletesItself( true );
+ KMMsgBase *msgBase = msgList.getFirst();
+
+ // We operate on serNums and not the KMMsgBase pointers, as those can
+ // change, or become invalid when changing the current message, switching
+ // folders, etc.
+ QPtrListIterator<KMMsgBase> it(msgList);
+ while ( it.current() ) {
+ mMsgList.append( (*it)->getMsgSerNum() );
+ mTotalSize += (*it)->msgSize();
+ if ((*it)->parent() != 0)
+ (*it)->parent()->open("kmcommand");
+ ++it;
+ }
+ mMsgListIndex = 0;
+ mUrl = subjectToUrl( msgBase->cleanSubject() );
+}
+
+KURL KMSaveMsgCommand::url()
+{
+ return mUrl;
+}
+
+KMCommand::Result KMSaveMsgCommand::execute()
+{
+ mJob = KIO::put( mUrl, S_IRUSR|S_IWUSR, false, false );
+ mJob->slotTotalSize( mTotalSize );
+ mJob->setAsyncDataEnabled( true );
+ mJob->setReportDataSent( true );
+ connect(mJob, SIGNAL(dataReq(KIO::Job*, QByteArray &)),
+ SLOT(slotSaveDataReq()));
+ connect(mJob, SIGNAL(result(KIO::Job*)),
+ SLOT(slotSaveResult(KIO::Job*)));
+ setEmitsCompletedItself( true );
+ return OK;
+}
+
+void KMSaveMsgCommand::slotSaveDataReq()
+{
+ int remainingBytes = mData.size() - mOffset;
+ if ( remainingBytes > 0 ) {
+ // eat leftovers first
+ if ( remainingBytes > MAX_CHUNK_SIZE )
+ remainingBytes = MAX_CHUNK_SIZE;
+
+ QByteArray data;
+ data.duplicate( mData.data() + mOffset, remainingBytes );
+ mJob->sendAsyncData( data );
+ mOffset += remainingBytes;
+ return;
+ }
+ // No leftovers, process next message.
+ if ( mMsgListIndex < mMsgList.size() ) {
+ KMMessage *msg = 0;
+ int idx = -1;
+ KMFolder * p = 0;
+ KMMsgDict::instance()->getLocation( mMsgList[mMsgListIndex], &p, &idx );
+ assert( p );
+ assert( idx >= 0 );
+ //kdDebug() << "SERNUM: " << mMsgList[mMsgListIndex] << " idx: " << idx << " folder: " << p->prettyURL() << endl;
+ msg = p->getMsg(idx);
+
+ if ( msg ) {
+ if ( msg->transferInProgress() ) {
+ QByteArray data = QByteArray();
+ mJob->sendAsyncData( data );
+ }
+ msg->setTransferInProgress( true );
+ if (msg->isComplete() ) {
+ slotMessageRetrievedForSaving( msg );
+ } else {
+ // retrieve Message first
+ if ( msg->parent() && !msg->isComplete() ) {
+ FolderJob *job = msg->parent()->createJob( msg );
+ job->setCancellable( false );
+ connect(job, SIGNAL( messageRetrieved( KMMessage* ) ),
+ this, SLOT( slotMessageRetrievedForSaving( KMMessage* ) ) );
+ job->start();
+ }
+ }
+ } else {
+ mJob->slotError( KIO::ERR_ABORTED,
+ i18n("The message was removed while saving it. "
+ "It has not been saved.") );
+ }
+ } else {
+ if ( mStandAloneMessage ) {
+ // do the special case of a standalone message
+ slotMessageRetrievedForSaving( mStandAloneMessage );
+ mStandAloneMessage = 0;
+ } else {
+ // No more messages. Tell the putjob we are done.
+ QByteArray data = QByteArray();
+ mJob->sendAsyncData( data );
+ }
+ }
+}
+
+void KMSaveMsgCommand::slotMessageRetrievedForSaving(KMMessage *msg)
+{
+ if ( msg ) {
+ mData = KMFolderMbox::escapeFrom( msg->asDwString() );
+ KMail::Util::insert( mData, 0, msg->mboxMessageSeparator() );
+ KMail::Util::append( mData, "\n" );
+ msg->setTransferInProgress(false);
+
+ mOffset = 0;
+ QByteArray data;
+ int size;
+ // Unless it is great than 64 k send the whole message. kio buffers for us.
+ if( mData.size() > (unsigned int) MAX_CHUNK_SIZE )
+ size = MAX_CHUNK_SIZE;
+ else
+ size = mData.size();
+
+ data.duplicate( mData, size );
+ mJob->sendAsyncData( data );
+ mOffset += size;
+ }
+ ++mMsgListIndex;
+ // Get rid of the message.
+ if ( msg && msg->parent() && msg->getMsgSerNum() ) {
+ int idx = -1;
+ KMFolder * p = 0;
+ KMMsgDict::instance()->getLocation( msg, &p, &idx );
+ assert( p == msg->parent() ); assert( idx >= 0 );
+ p->unGetMsg( idx );
+ p->close("kmcommand");
+ }
+}
+
+void KMSaveMsgCommand::slotSaveResult(KIO::Job *job)
+{
+ if (job->error())
+ {
+ if (job->error() == KIO::ERR_FILE_ALREADY_EXIST)
+ {
+ if (KMessageBox::warningContinueCancel(0,
+ i18n("File %1 exists.\nDo you want to replace it?")
+ .arg(mUrl.prettyURL()), i18n("Save to File"), i18n("&Replace"))
+ == KMessageBox::Continue) {
+ mOffset = 0;
+
+ mJob = KIO::put( mUrl, S_IRUSR|S_IWUSR, true, false );
+ mJob->slotTotalSize( mTotalSize );
+ mJob->setAsyncDataEnabled( true );
+ mJob->setReportDataSent( true );
+ connect(mJob, SIGNAL(dataReq(KIO::Job*, QByteArray &)),
+ SLOT(slotSaveDataReq()));
+ connect(mJob, SIGNAL(result(KIO::Job*)),
+ SLOT(slotSaveResult(KIO::Job*)));
+ }
+ }
+ else
+ {
+ job->showErrorDialog();
+ setResult( Failed );
+ emit completed( this );
+ deleteLater();
+ }
+ } else {
+ setResult( OK );
+ emit completed( this );
+ deleteLater();
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+KMOpenMsgCommand::KMOpenMsgCommand( QWidget *parent, const KURL & url,
+ const QString & encoding )
+ : KMCommand( parent ),
+ mUrl( url ),
+ mEncoding( encoding )
+{
+ setDeletesItself( true );
+}
+
+KMCommand::Result KMOpenMsgCommand::execute()
+{
+ if ( mUrl.isEmpty() ) {
+ mUrl = KFileDialog::getOpenURL( ":OpenMessage", "message/rfc822 application/mbox",
+ parentWidget(), i18n("Open Message") );
+ }
+ if ( mUrl.isEmpty() ) {
+ setDeletesItself( false );
+ return Canceled;
+ }
+ mJob = KIO::get( mUrl, false, false );
+ mJob->setReportDataSent( true );
+ connect( mJob, SIGNAL( data( KIO::Job *, const QByteArray & ) ),
+ this, SLOT( slotDataArrived( KIO::Job*, const QByteArray & ) ) );
+ connect( mJob, SIGNAL( result( KIO::Job * ) ),
+ SLOT( slotResult( KIO::Job * ) ) );
+ setEmitsCompletedItself( true );
+ return OK;
+}
+
+void KMOpenMsgCommand::slotDataArrived( KIO::Job *, const QByteArray & data )
+{
+ if ( data.isEmpty() )
+ return;
+
+ mMsgString.append( data.data(), data.size() );
+}
+
+void KMOpenMsgCommand::slotResult( KIO::Job *job )
+{
+ if ( job->error() ) {
+ // handle errors
+ job->showErrorDialog();
+ setResult( Failed );
+ emit completed( this );
+ }
+ else {
+ int startOfMessage = 0;
+ if ( mMsgString.compare( 0, 5, "From ", 5 ) == 0 ) {
+ startOfMessage = mMsgString.find( '\n' );
+ if ( startOfMessage == -1 ) {
+ KMessageBox::sorry( parentWidget(),
+ i18n( "The file does not contain a message." ) );
+ setResult( Failed );
+ emit completed( this );
+ // Emulate closing of a secondary window so that KMail exits in case it
+ // was started with the --view command line option. Otherwise an
+ // invisible KMail would keep running.
+ SecondaryWindow *win = new SecondaryWindow();
+ win->close();
+ win->deleteLater();
+ deleteLater();
+ return;
+ }
+ startOfMessage += 1; // the message starts after the '\n'
+ }
+ // check for multiple messages in the file
+ bool multipleMessages = true;
+ int endOfMessage = mMsgString.find( "\nFrom " );
+ if ( endOfMessage == -1 ) {
+ endOfMessage = mMsgString.length();
+ multipleMessages = false;
+ }
+ DwMessage *dwMsg = new DwMessage;
+ dwMsg->FromString( mMsgString.substr( startOfMessage,
+ endOfMessage - startOfMessage ) );
+ dwMsg->Parse();
+ // check whether we have a message ( no headers => this isn't a message )
+ if ( dwMsg->Headers().NumFields() == 0 ) {
+ KMessageBox::sorry( parentWidget(),
+ i18n( "The file does not contain a message." ) );
+ delete dwMsg; dwMsg = 0;
+ setResult( Failed );
+ emit completed( this );
+ // Emulate closing of a secondary window (see above).
+ SecondaryWindow *win = new SecondaryWindow();
+ win->close();
+ win->deleteLater();
+ deleteLater();
+ return;
+ }
+ KMMessage *msg = new KMMessage( dwMsg );
+ msg->setReadyToShow( true );
+ KMReaderMainWin *win = new KMReaderMainWin();
+ win->showMsg( mEncoding, msg );
+ win->show();
+ if ( multipleMessages )
+ KMessageBox::information( win,
+ i18n( "The file contains multiple messages. "
+ "Only the first message is shown." ) );
+ setResult( OK );
+ emit completed( this );
+ }
+ deleteLater();
+}
+
+//-----------------------------------------------------------------------------
+
+//TODO: ReplyTo, NoQuoteReplyTo, ReplyList, ReplyToAll, ReplyAuthor
+// are all similar and should be factored
+KMReplyToCommand::KMReplyToCommand( QWidget *parent, KMMessage *msg,
+ const QString &selection )
+ : KMCommand( parent, msg ), mSelection( selection )
+{
+}
+
+KMCommand::Result KMReplyToCommand::execute()
+{
+ KCursorSaver busy(KBusyPtr::busy());
+ KMMessage *msg = retrievedMessage();
+ if ( !msg || !msg->codec() ) {
+ return Failed;
+ }
+ KMMessage *reply = msg->createReply( KMail::ReplySmart, mSelection );
+ KMail::Composer * win = KMail::makeComposer( reply );
+ win->setCharset( msg->codec()->mimeName(), true );
+ win->setReplyFocus();
+ win->show();
+
+ return OK;
+}
+
+
+KMNoQuoteReplyToCommand::KMNoQuoteReplyToCommand( QWidget *parent,
+ KMMessage *msg )
+ : KMCommand( parent, msg )
+{
+}
+
+KMCommand::Result KMNoQuoteReplyToCommand::execute()
+{
+ KCursorSaver busy(KBusyPtr::busy());
+ KMMessage *msg = retrievedMessage();
+ if ( !msg || !msg->codec() ) {
+ return Failed;
+ }
+ KMMessage *reply = msg->createReply( KMail::ReplySmart, "", true);
+ KMail::Composer * win = KMail::makeComposer( reply );
+ win->setCharset(msg->codec()->mimeName(), true);
+ win->setReplyFocus(false);
+ win->show();
+
+ return OK;
+}
+
+
+KMReplyListCommand::KMReplyListCommand( QWidget *parent,
+ KMMessage *msg, const QString &selection )
+ : KMCommand( parent, msg ), mSelection( selection )
+{
+}
+
+KMCommand::Result KMReplyListCommand::execute()
+{
+ KCursorSaver busy(KBusyPtr::busy());
+ KMMessage *msg = retrievedMessage();
+ if ( !msg || !msg->codec() ) {
+ return Failed;
+ }
+ KMMessage *reply = msg->createReply( KMail::ReplyList, mSelection);
+ KMail::Composer * win = KMail::makeComposer( reply );
+ win->setCharset(msg->codec()->mimeName(), true);
+ win->setReplyFocus(false);
+ win->show();
+
+ return OK;
+}
+
+
+KMReplyToAllCommand::KMReplyToAllCommand( QWidget *parent,
+ KMMessage *msg, const QString &selection )
+ :KMCommand( parent, msg ), mSelection( selection )
+{
+}
+
+KMCommand::Result KMReplyToAllCommand::execute()
+{
+ KCursorSaver busy(KBusyPtr::busy());
+ KMMessage *msg = retrievedMessage();
+ if ( !msg || !msg->codec() ) {
+ return Failed;
+ }
+ KMMessage *reply = msg->createReply( KMail::ReplyAll, mSelection );
+ KMail::Composer * win = KMail::makeComposer( reply );
+ win->setCharset( msg->codec()->mimeName(), true );
+ win->setReplyFocus();
+ win->show();
+
+ return OK;
+}
+
+
+KMReplyAuthorCommand::KMReplyAuthorCommand( QWidget *parent, KMMessage *msg,
+ const QString &selection )
+ : KMCommand( parent, msg ), mSelection( selection )
+{
+}
+
+KMCommand::Result KMReplyAuthorCommand::execute()
+{
+ KCursorSaver busy(KBusyPtr::busy());
+ KMMessage *msg = retrievedMessage();
+ if ( !msg || !msg->codec() ) {
+ return Failed;
+ }
+ KMMessage *reply = msg->createReply( KMail::ReplyAuthor, mSelection );
+ KMail::Composer * win = KMail::makeComposer( reply );
+ win->setCharset( msg->codec()->mimeName(), true );
+ win->setReplyFocus();
+ win->show();
+
+ return OK;
+}
+
+
+KMForwardInlineCommand::KMForwardInlineCommand( QWidget *parent,
+ const QPtrList<KMMsgBase> &msgList, uint identity )
+ : KMCommand( parent, msgList ),
+ mIdentity( identity )
+{
+}
+
+KMForwardInlineCommand::KMForwardInlineCommand( QWidget *parent,
+ KMMessage *msg, uint identity )
+ : KMCommand( parent, msg ),
+ mIdentity( identity )
+{
+}
+
+KMCommand::Result KMForwardInlineCommand::execute()
+{
+ QPtrList<KMMessage> msgList = retrievedMsgs();
+
+ if (msgList.count() >= 2) { // Multiple forward
+
+ uint id = 0;
+ QPtrList<KMMessage> linklist;
+ for ( KMMessage *msg = msgList.first(); msg; msg = msgList.next() ) {
+ // set the identity
+ if (id == 0)
+ id = msg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt();
+
+ // msgText += msg->createForwardBody();
+ linklist.append( msg );
+ }
+ if ( id == 0 )
+ id = mIdentity; // use folder identity if no message had an id set
+ KMMessage *fwdMsg = new KMMessage;
+ fwdMsg->initHeader( id );
+ fwdMsg->setAutomaticFields( true );
+ fwdMsg->setCharset( "utf-8" );
+ // 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 );
+
+ fwdMsg->link( msg, KMMsgStatusForwarded );
+ }
+
+ KCursorSaver busy( KBusyPtr::busy() );
+ KMail::Composer * win = KMail::makeComposer( fwdMsg, id );
+ win->setCharset("");
+ win->show();
+
+ } else { // forward a single message at most
+
+ KMMessage *msg = msgList.getFirst();
+ if ( !msg || !msg->codec() )
+ return Failed;
+
+ KCursorSaver busy( KBusyPtr::busy() );
+ KMMessage *fwdMsg = msg->createForward();
+
+ uint id = msg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt();
+ if ( id == 0 )
+ id = mIdentity;
+ {
+ KMail::Composer * win = KMail::makeComposer( fwdMsg, id );
+ win->setCharset( fwdMsg->codec()->mimeName(), true );
+ win->setBody( fwdMsg->bodyToUnicode() );
+ win->show();
+ }
+ }
+ return OK;
+}
+
+
+KMForwardAttachedCommand::KMForwardAttachedCommand( QWidget *parent,
+ const QPtrList<KMMsgBase> &msgList, uint identity, KMail::Composer *win )
+ : KMCommand( parent, msgList ), mIdentity( identity ),
+ mWin( QGuardedPtr<KMail::Composer>( win ))
+{
+}
+
+KMForwardAttachedCommand::KMForwardAttachedCommand( QWidget *parent,
+ KMMessage * msg, uint identity, KMail::Composer *win )
+ : KMCommand( parent, msg ), mIdentity( identity ),
+ mWin( QGuardedPtr< KMail::Composer >( win ))
+{
+}
+
+KMCommand::Result KMForwardAttachedCommand::execute()
+{
+ QPtrList<KMMessage> msgList = retrievedMsgs();
+ KMMessage *fwdMsg = new KMMessage;
+
+ if (msgList.count() >= 2) {
+ // don't respect X-KMail-Identity headers because they might differ for
+ // the selected mails
+ fwdMsg->initHeader(mIdentity);
+ }
+ else if (msgList.count() == 1) {
+ KMMessage *msg = msgList.getFirst();
+ fwdMsg->initFromMessage(msg);
+ fwdMsg->setSubject( msg->forwardSubject() );
+ }
+
+ fwdMsg->setAutomaticFields(true);
+
+ KCursorSaver busy(KBusyPtr::busy());
+ if (!mWin)
+ mWin = KMail::makeComposer(fwdMsg, mIdentity);
+
+ // iterate through all the messages to be forwarded
+ for (KMMessage *msg = msgList.first(); msg; msg = msgList.next()) {
+ // remove headers that shouldn't be forwarded
+ msg->removePrivateHeaderFields();
+ msg->removeHeaderField("BCC");
+ // set the part
+ KMMessagePart *msgPart = new KMMessagePart;
+ msgPart->setTypeStr("message");
+ msgPart->setSubtypeStr("rfc822");
+ msgPart->setCharset(msg->charset());
+ msgPart->setName("forwarded message");
+ msgPart->setContentDescription(msg->from()+": "+msg->subject());
+ msgPart->setContentDisposition( "inline" );
+ // THIS HAS TO BE AFTER setCte()!!!!
+ msgPart->setMessageBody( KMail::Util::ByteArray( msg->asDwString() ) );
+ msgPart->setCharset("");
+
+ fwdMsg->link(msg, KMMsgStatusForwarded);
+ mWin->addAttach(msgPart);
+ }
+
+ mWin->show();
+
+ return OK;
+}
+
+
+KMForwardDigestCommand::KMForwardDigestCommand( QWidget *parent,
+ const QPtrList<KMMsgBase> &msgList, uint identity, KMail::Composer *win )
+ : KMCommand( parent, msgList ), mIdentity( identity ),
+ mWin( QGuardedPtr<KMail::Composer>( win ))
+{
+}
+
+KMForwardDigestCommand::KMForwardDigestCommand( QWidget *parent,
+ KMMessage * msg, uint identity, KMail::Composer *win )
+ : KMCommand( parent, msg ), mIdentity( identity ),
+ mWin( QGuardedPtr< KMail::Composer >( win ))
+{
+}
+
+KMCommand::Result KMForwardDigestCommand::execute()
+{
+ QPtrList<KMMessage> msgList = retrievedMsgs();
+
+ if ( msgList.count() < 2 )
+ return Undefined; // must have more than 1 for a digest
+
+ uint id = 0;
+ KMMessage *fwdMsg = new KMMessage;
+ KMMessagePart *msgPart = new KMMessagePart;
+ QString msgPartText;
+ int msgCnt = 0; // incase there are some we can't forward for some reason
+
+ // dummy header initialization; initialization with the correct identity
+ // is done below
+ fwdMsg->initHeader( id );
+ fwdMsg->setAutomaticFields( true );
+ fwdMsg->mMsg->Headers().ContentType().CreateBoundary( 1 );
+ QCString boundary( fwdMsg->mMsg->Headers().ContentType().Boundary().c_str() );
+ msgPartText = i18n("\nThis is a MIME digest forward. The content of the"
+ " message is contained in the attachment(s).\n\n\n");
+ // iterate through all the messages to be forwarded
+ for ( KMMessage *msg = msgList.first(); msg; msg = msgList.next() ) {
+ // set the identity
+ if ( id == 0 )
+ id = msg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt();
+ // set the part header
+ msgPartText += "--";
+ msgPartText += QString::fromLatin1( boundary );
+ msgPartText += "\nContent-Type: MESSAGE/RFC822";
+ msgPartText += QString( "; CHARSET=%1" ).arg( msg->charset() );
+ msgPartText += '\n';
+ DwHeaders dwh;
+ dwh.MessageId().CreateDefault();
+ msgPartText += QString( "Content-ID: %1\n" ).arg( dwh.MessageId().AsString().c_str() );
+ msgPartText += QString( "Content-Description: %1" ).arg( msg->subject() );
+ if ( !msg->subject().contains( "(fwd)" ) )
+ msgPartText += " (fwd)";
+ msgPartText += "\n\n";
+ // remove headers that shouldn't be forwarded
+ msg->removePrivateHeaderFields();
+ msg->removeHeaderField( "BCC" );
+ // set the part
+ msgPartText += msg->headerAsString();
+ msgPartText += '\n';
+ msgPartText += msg->body();
+ msgPartText += '\n'; // eot
+ msgCnt++;
+ fwdMsg->link( msg, KMMsgStatusForwarded );
+ }
+
+ if ( id == 0 )
+ id = mIdentity; // use folder identity if no message had an id set
+ fwdMsg->initHeader( id );
+ msgPartText += "--";
+ msgPartText += QString::fromLatin1( boundary );
+ msgPartText += "--\n";
+ QCString tmp;
+ msgPart->setTypeStr( "MULTIPART" );
+ tmp.sprintf( "Digest; boundary=\"%s\"", boundary.data() );
+ msgPart->setSubtypeStr( tmp );
+ msgPart->setName( "unnamed" );
+ msgPart->setCte( DwMime::kCte7bit ); // does it have to be 7bit?
+ msgPart->setContentDescription( QString( "Digest of %1 messages." ).arg( msgCnt ) );
+ // THIS HAS TO BE AFTER setCte()!!!!
+ msgPart->setBodyEncoded( QCString( msgPartText.ascii() ) );
+ KCursorSaver busy( KBusyPtr::busy() );
+ KMail::Composer * win = KMail::makeComposer( fwdMsg, id );
+ win->addAttach( msgPart );
+ win->show();
+ return OK;
+}
+
+KMRedirectCommand::KMRedirectCommand( QWidget *parent,
+ KMMessage *msg )
+ : KMCommand( parent, msg )
+{
+}
+
+KMCommand::Result KMRedirectCommand::execute()
+{
+ KMMessage *msg = retrievedMessage();
+ if ( !msg || !msg->codec() )
+ return Failed;
+
+ RedirectDialog dlg( parentWidget(), "redirect", true,
+ kmkernel->msgSender()->sendImmediate() );
+ if (dlg.exec()==QDialog::Rejected) return Failed;
+
+ KMMessage *newMsg = msg->createRedirect( dlg.to() );
+ KMFilterAction::sendMDN( msg, KMime::MDN::Dispatched );
+
+ const KMail::MessageSender::SendMethod method = dlg.sendImmediate()
+ ? KMail::MessageSender::SendImmediate
+ : KMail::MessageSender::SendLater;
+ if ( !kmkernel->msgSender()->send( newMsg, method ) ) {
+ kdDebug(5006) << "KMRedirectCommand: could not redirect message (sending failed)" << endl;
+ return Failed; // error: couldn't send
+ }
+ return OK;
+}
+
+
+KMCustomReplyToCommand::KMCustomReplyToCommand( QWidget *parent, KMMessage *msg,
+ const QString &selection,
+ const QString &tmpl )
+ : KMCommand( parent, msg ), mSelection( selection ), mTemplate( tmpl )
+{
+}
+
+KMCommand::Result KMCustomReplyToCommand::execute()
+{
+ KCursorSaver busy(KBusyPtr::busy());
+ KMMessage *msg = retrievedMessage();
+ if ( !msg || !msg->codec() ) {
+ return Failed;
+ }
+ KMMessage *reply = msg->createReply( KMail::ReplySmart, mSelection,
+ false, true, false, mTemplate );
+ KMail::Composer * win = KMail::makeComposer( reply );
+ win->setCharset( msg->codec()->mimeName(), true );
+ win->setReplyFocus();
+ win->show();
+
+ return OK;
+}
+
+
+KMCustomReplyAllToCommand::KMCustomReplyAllToCommand( QWidget *parent, KMMessage *msg,
+ const QString &selection,
+ const QString &tmpl )
+ : KMCommand( parent, msg ), mSelection( selection ), mTemplate( tmpl )
+{
+}
+
+KMCommand::Result KMCustomReplyAllToCommand::execute()
+{
+ KCursorSaver busy(KBusyPtr::busy());
+ KMMessage *msg = retrievedMessage();
+ if ( !msg || !msg->codec() ) {
+ return Failed;
+ }
+ KMMessage *reply = msg->createReply( KMail::ReplyAll, mSelection,
+ false, true, false, mTemplate );
+ KMail::Composer * win = KMail::makeComposer( reply );
+ win->setCharset( msg->codec()->mimeName(), true );
+ win->setReplyFocus();
+ win->show();
+
+ return OK;
+}
+
+
+KMCustomForwardCommand::KMCustomForwardCommand( QWidget *parent,
+ const QPtrList<KMMsgBase> &msgList, uint identity, const QString &tmpl )
+ : KMCommand( parent, msgList ),
+ mIdentity( identity ), mTemplate( tmpl )
+{
+}
+
+KMCustomForwardCommand::KMCustomForwardCommand( QWidget *parent,
+ KMMessage *msg, uint identity, const QString &tmpl )
+ : KMCommand( parent, msg ),
+ mIdentity( identity ), mTemplate( tmpl )
+{
+}
+
+KMCommand::Result KMCustomForwardCommand::execute()
+{
+ QPtrList<KMMessage> msgList = retrievedMsgs();
+
+ if (msgList.count() >= 2) { // Multiple forward
+
+ uint id = 0;
+ QPtrList<KMMessage> linklist;
+ for ( KMMessage *msg = msgList.first(); msg; msg = msgList.next() ) {
+ // set the identity
+ if (id == 0)
+ id = msg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt();
+
+ // msgText += msg->createForwardBody();
+ linklist.append( msg );
+ }
+ if ( id == 0 )
+ id = mIdentity; // use folder identity if no message had an id set
+ KMMessage *fwdMsg = new KMMessage;
+ fwdMsg->initHeader( id );
+ fwdMsg->setAutomaticFields( true );
+ fwdMsg->setCharset( "utf-8" );
+ // 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 );
+
+ fwdMsg->link( msg, KMMsgStatusForwarded );
+ }
+
+ KCursorSaver busy( KBusyPtr::busy() );
+ KMail::Composer * win = KMail::makeComposer( fwdMsg, id );
+ win->setCharset("");
+ win->show();
+
+ } else { // forward a single message at most
+
+ KMMessage *msg = msgList.getFirst();
+ if ( !msg || !msg->codec() )
+ return Failed;
+
+ KCursorSaver busy( KBusyPtr::busy() );
+ KMMessage *fwdMsg = msg->createForward( mTemplate );
+
+ uint id = msg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt();
+ if ( id == 0 )
+ id = mIdentity;
+ {
+ KMail::Composer * win = KMail::makeComposer( fwdMsg, id );
+ win->setCharset( fwdMsg->codec()->mimeName(), true );
+ win->show();
+ }
+ }
+ return OK;
+}
+
+
+KMPrintCommand::KMPrintCommand( QWidget *parent,
+ KMMessage *msg, bool htmlOverride, bool htmlLoadExtOverride,
+ bool useFixedFont, const QString & encoding )
+ : KMCommand( parent, msg ), mHtmlOverride( htmlOverride ),
+ mHtmlLoadExtOverride( htmlLoadExtOverride ),
+ mUseFixedFont( useFixedFont ), mEncoding( encoding )
+{
+ mOverrideFont = KGlobalSettings::generalFont();
+}
+
+
+void KMPrintCommand::setOverrideFont( const QFont& font )
+{
+ mOverrideFont = font;
+}
+
+KMCommand::Result KMPrintCommand::execute()
+{
+ KMReaderWin printWin( 0, 0, 0 );
+ printWin.setPrinting( true );
+ printWin.readConfig();
+ printWin.setHtmlOverride( mHtmlOverride );
+ printWin.setHtmlLoadExtOverride( mHtmlLoadExtOverride );
+ printWin.setUseFixedFont( mUseFixedFont );
+ printWin.setOverrideEncoding( mEncoding );
+ printWin.setPrintFont( mOverrideFont );
+ printWin.setDecryptMessageOverwrite( true );
+ printWin.setMsg( retrievedMessage(), true );
+ printWin.printMsg();
+
+ return OK;
+}
+
+
+KMSetStatusCommand::KMSetStatusCommand( KMMsgStatus status,
+ const QValueList<Q_UINT32> &serNums, bool toggle )
+ : mStatus( status ), mSerNums( serNums ), mToggle( toggle )
+{
+}
+
+KMCommand::Result KMSetStatusCommand::execute()
+{
+ QValueListIterator<Q_UINT32> it;
+ int idx = -1;
+ KMFolder *folder = 0;
+ bool parentStatus = false;
+
+ // Toggle actions on threads toggle the whole thread
+ // depending on the state of the parent.
+ if (mToggle) {
+ KMMsgBase *msg;
+ KMMsgDict::instance()->getLocation( *mSerNums.begin(), &folder, &idx );
+ if (folder) {
+ msg = folder->getMsgBase(idx);
+ if (msg && (msg->status()&mStatus))
+ parentStatus = true;
+ else
+ parentStatus = false;
+ }
+ }
+ QMap< KMFolder*, QValueList<int> > folderMap;
+ for ( it = mSerNums.begin(); it != mSerNums.end(); ++it ) {
+ KMMsgDict::instance()->getLocation( *it, &folder, &idx );
+ if (folder) {
+ if (mToggle) {
+ KMMsgBase *msg = folder->getMsgBase(idx);
+ // check if we are already at the target toggle state
+ if (msg) {
+ bool myStatus;
+ if (msg->status()&mStatus)
+ myStatus = true;
+ else
+ myStatus = false;
+ if (myStatus != parentStatus)
+ continue;
+ }
+ }
+ /* Collect the ids for each folder in a separate list and
+ send them off in one go at the end. */
+ folderMap[folder].append(idx);
+ }
+ }
+ QMapIterator< KMFolder*, QValueList<int> > it2 = folderMap.begin();
+ while ( it2 != folderMap.end() ) {
+ KMFolder *f = it2.key();
+ f->setStatus( (*it2), mStatus, mToggle );
+ ++it2;
+ }
+ //kapp->dcopClient()->emitDCOPSignal( "unreadCountChanged()", QByteArray() );
+
+ return OK;
+}
+
+
+KMFilterCommand::KMFilterCommand( const QCString &field, const QString &value )
+ : mField( field ), mValue( value )
+{
+}
+
+KMCommand::Result KMFilterCommand::execute()
+{
+ kmkernel->filterMgr()->createFilter( mField, mValue );
+
+ return OK;
+}
+
+
+KMFilterActionCommand::KMFilterActionCommand( QWidget *parent,
+ const QPtrList<KMMsgBase> &msgList,
+ KMFilter *filter )
+ : KMCommand( parent, msgList ), mFilter( filter )
+{
+ QPtrListIterator<KMMsgBase> it(msgList);
+ while ( it.current() ) {
+ serNumList.append( (*it)->getMsgSerNum() );
+ ++it;
+ }
+}
+
+KMCommand::Result KMFilterActionCommand::execute()
+{
+ KCursorSaver busy( KBusyPtr::busy() );
+
+ int msgCount = 0;
+ int msgCountToFilter = serNumList.count();
+ ProgressItem* progressItem =
+ ProgressManager::createProgressItem ( "filter"+ProgressManager::getUniqueID(),
+ i18n( "Filtering messages" ) );
+ progressItem->setTotalItems( msgCountToFilter );
+ QValueList<Q_UINT32>::const_iterator it;
+ for ( it = serNumList.begin(); it != serNumList.end(); it++ ) {
+ Q_UINT32 serNum = *it;
+ int diff = msgCountToFilter - ++msgCount;
+ if ( diff < 10 || !( msgCount % 20 ) || msgCount <= 10 ) {
+ progressItem->updateProgress();
+ QString statusMsg = i18n("Filtering message %1 of %2");
+ statusMsg = statusMsg.arg( msgCount ).arg( msgCountToFilter );
+ KPIM::BroadcastStatus::instance()->setStatusMsg( statusMsg );
+ KApplication::kApplication()->eventLoop()->processEvents( QEventLoop::ExcludeUserInput, 50 );
+ }
+
+ int filterResult = kmkernel->filterMgr()->process( serNum, mFilter );
+ if (filterResult == 2) {
+ // something went horribly wrong (out of space?)
+ perror("Critical error");
+ kmkernel->emergencyExit( i18n("Not enough free disk space?" ));
+ }
+ progressItem->incCompletedItems();
+ }
+
+ progressItem->setComplete();
+ progressItem = 0;
+ return OK;
+}
+
+
+KMMetaFilterActionCommand::KMMetaFilterActionCommand( KMFilter *filter,
+ KMHeaders *headers,
+ KMMainWidget *main )
+ : QObject( main ),
+ mFilter( filter ), mHeaders( headers ), mMainWidget( main )
+{
+}
+
+void KMMetaFilterActionCommand::start()
+{
+ if (ActionScheduler::isEnabled() ) {
+ // use action scheduler
+ KMFilterMgr::FilterSet set = KMFilterMgr::All;
+ QValueList<KMFilter*> filters;
+ filters.append( mFilter );
+ ActionScheduler *scheduler = new ActionScheduler( set, filters, mHeaders );
+ scheduler->setAlwaysMatch( true );
+ scheduler->setAutoDestruct( true );
+
+ int contentX, contentY;
+ HeaderItem *nextItem = mHeaders->prepareMove( &contentX, &contentY );
+ QPtrList<KMMsgBase> msgList = *mHeaders->selectedMsgs(true);
+ mHeaders->finalizeMove( nextItem, contentX, contentY );
+
+ for (KMMsgBase *msg = msgList.first(); msg; msg = msgList.next())
+ scheduler->execFilters( msg );
+ } else {
+ KMCommand *filterCommand =
+ new KMFilterActionCommand( mMainWidget,
+ *mHeaders->selectedMsgs(), mFilter );
+ filterCommand->start();
+ int contentX, contentY;
+ HeaderItem *item = mHeaders->prepareMove( &contentX, &contentY );
+ mHeaders->finalizeMove( item, contentX, contentY );
+ }
+}
+
+FolderShortcutCommand::FolderShortcutCommand( KMMainWidget *mainwidget,
+ KMFolder *folder )
+ : mMainWidget( mainwidget ), mFolder( folder ), mAction( 0 )
+{
+}
+
+
+FolderShortcutCommand::~FolderShortcutCommand()
+{
+ if ( mAction ) mAction->unplugAll();
+ delete mAction;
+}
+
+void FolderShortcutCommand::start()
+{
+ mMainWidget->slotSelectFolder( mFolder );
+}
+
+void FolderShortcutCommand::setAction( KAction* action )
+{
+ mAction = action;
+}
+
+KMMailingListFilterCommand::KMMailingListFilterCommand( QWidget *parent,
+ KMMessage *msg )
+ : KMCommand( parent, msg )
+{
+}
+
+KMCommand::Result KMMailingListFilterCommand::execute()
+{
+ QCString name;
+ QString value;
+ KMMessage *msg = retrievedMessage();
+ if (!msg)
+ return Failed;
+
+ if ( !MailingList::name( msg, name, value ).isEmpty() ) {
+ kmkernel->filterMgr()->createFilter( name, value );
+ return OK;
+ }
+ else
+ return Failed;
+}
+
+
+void KMMenuCommand::folderToPopupMenu(bool move,
+ QObject *receiver, KMMenuToFolder *aMenuToFolder, QPopupMenu *menu )
+{
+ while ( menu->count() )
+ {
+ QPopupMenu *popup = menu->findItem( menu->idAt( 0 ) )->popup();
+ if (popup)
+ delete popup;
+ else
+ menu->removeItemAt( 0 );
+ }
+
+ if (!kmkernel->imapFolderMgr()->dir().first() &&
+ !kmkernel->dimapFolderMgr()->dir().first())
+ { // only local folders
+ makeFolderMenu( &kmkernel->folderMgr()->dir(), move,
+ receiver, aMenuToFolder, menu );
+ } else {
+ // operate on top-level items
+ QPopupMenu* subMenu = new QPopupMenu(menu);
+ makeFolderMenu( &kmkernel->folderMgr()->dir(),
+ move, receiver, aMenuToFolder, subMenu );
+ menu->insertItem( i18n( "Local Folders" ), subMenu );
+ KMFolderDir* fdir = &kmkernel->imapFolderMgr()->dir();
+ for (KMFolderNode *node = fdir->first(); node; node = fdir->next()) {
+ if (node->isDir())
+ continue;
+ subMenu = new QPopupMenu(menu);
+ makeFolderMenu( node, move, receiver, aMenuToFolder, subMenu );
+ menu->insertItem( node->label(), subMenu );
+ }
+ fdir = &kmkernel->dimapFolderMgr()->dir();
+ for (KMFolderNode *node = fdir->first(); node; node = fdir->next()) {
+ if (node->isDir())
+ continue;
+ subMenu = new QPopupMenu(menu);
+ makeFolderMenu( node, move, receiver, aMenuToFolder, subMenu );
+ menu->insertItem( node->label(), subMenu );
+ }
+ }
+}
+
+void KMMenuCommand::makeFolderMenu(KMFolderNode* node, bool move,
+ QObject *receiver, KMMenuToFolder *aMenuToFolder, QPopupMenu *menu )
+{
+ // connect the signals
+ if (move)
+ {
+ disconnect(menu, SIGNAL(activated(int)), receiver,
+ SLOT(moveSelectedToFolder(int)));
+ connect(menu, SIGNAL(activated(int)), receiver,
+ SLOT(moveSelectedToFolder(int)));
+ } else {
+ disconnect(menu, SIGNAL(activated(int)), receiver,
+ SLOT(copySelectedToFolder(int)));
+ connect(menu, SIGNAL(activated(int)), receiver,
+ SLOT(copySelectedToFolder(int)));
+ }
+
+ KMFolder *folder = 0;
+ KMFolderDir *folderDir = 0;
+ if (node->isDir()) {
+ folderDir = static_cast<KMFolderDir*>(node);
+ } else {
+ folder = static_cast<KMFolder*>(node);
+ folderDir = folder->child();
+ }
+
+ if (folder && !folder->noContent())
+ {
+ int menuId;
+ if (move)
+ menuId = menu->insertItem(i18n("Move to This Folder"));
+ else
+ menuId = menu->insertItem(i18n("Copy to This Folder"));
+ aMenuToFolder->insert( menuId, folder );
+ menu->setItemEnabled( menuId, !folder->isReadOnly() );
+ menu->insertSeparator();
+ }
+
+ if (!folderDir)
+ return;
+
+ for (KMFolderNode *it = folderDir->first(); it; it = folderDir->next() ) {
+ if (it->isDir())
+ continue;
+ KMFolder *child = static_cast<KMFolder*>(it);
+ QString label = child->label();
+ label.replace("&","&&");
+ if (child->child() && child->child()->first()) {
+ // descend
+ QPopupMenu *subMenu = new QPopupMenu(menu, "subMenu");
+ makeFolderMenu( child, move, receiver,
+ aMenuToFolder, subMenu );
+ menu->insertItem( label, subMenu );
+ } else {
+ // insert an item
+ int menuId = menu->insertItem( label );
+ aMenuToFolder->insert( menuId, child );
+ menu->setItemEnabled( menuId, !child->isReadOnly() );
+ }
+ }
+ return;
+}
+
+
+KMCopyCommand::KMCopyCommand( KMFolder* destFolder,
+ const QPtrList<KMMsgBase> &msgList )
+:mDestFolder( destFolder ), mMsgList( msgList )
+{
+ setDeletesItself( true );
+}
+
+KMCopyCommand::KMCopyCommand( KMFolder* destFolder, KMMessage * msg )
+ :mDestFolder( destFolder )
+{
+ setDeletesItself( true );
+ mMsgList.append( &msg->toMsgBase() );
+}
+
+KMCommand::Result KMCopyCommand::execute()
+{
+ KMMsgBase *msgBase;
+ KMMessage *msg, *newMsg;
+ int idx = -1;
+ bool isMessage;
+ QPtrList<KMMessage> list;
+ QPtrList<KMMessage> localList;
+
+ if (mDestFolder && mDestFolder->open("kmcommand") != 0)
+ {
+ deleteLater();
+ return Failed;
+ }
+
+ setEmitsCompletedItself( true );
+ KCursorSaver busy(KBusyPtr::busy());
+
+ for (msgBase = mMsgList.first(); msgBase; msgBase = mMsgList.next() )
+ {
+ KMFolder *srcFolder = msgBase->parent();
+ if (( isMessage = msgBase->isMessage() ))
+ {
+ msg = static_cast<KMMessage*>(msgBase);
+ } else {
+ idx = srcFolder->find(msgBase);
+ assert(idx != -1);
+ msg = srcFolder->getMsg(idx);
+ // corrupt IMAP cache, see FolderStorage::getMsg()
+ if ( msg == 0 ) {
+ KMessageBox::error( parentWidget(), i18n("Corrupt IMAP cache detected in folder %1. "
+ "Copying of messages aborted.").arg( srcFolder->prettyURL() ) );
+ deleteLater();
+ return Failed;
+ }
+ }
+
+ if (srcFolder && mDestFolder &&
+ (srcFolder->folderType()== KMFolderTypeImap) &&
+ (mDestFolder->folderType() == KMFolderTypeImap) &&
+ (static_cast<KMFolderImap*>(srcFolder->storage())->account() ==
+ static_cast<KMFolderImap*>(mDestFolder->storage())->account()))
+ {
+ // imap => imap with same account
+ list.append(msg);
+ } else {
+ newMsg = new KMMessage( new DwMessage( *msg->asDwMessage() ) );
+ newMsg->setComplete(msg->isComplete());
+ // make sure the attachment state is only calculated when it's complete
+ if (!newMsg->isComplete())
+ newMsg->setReadyToShow(false);
+ newMsg->setStatus(msg->status());
+
+ if (srcFolder && !newMsg->isComplete())
+ {
+ // imap => others
+ newMsg->setParent(msg->parent());
+ FolderJob *job = srcFolder->createJob(newMsg);
+ job->setCancellable( false );
+ mPendingJobs << job;
+ connect(job, SIGNAL(messageRetrieved(KMMessage*)),
+ mDestFolder, SLOT(reallyAddCopyOfMsg(KMMessage*)));
+ connect( job, SIGNAL(result(KMail::FolderJob*)),
+ this, SLOT(slotJobFinished(KMail::FolderJob*)) );
+ job->start();
+ } else {
+ // local => others
+ localList.append(newMsg);
+ }
+ }
+
+ if (srcFolder && !isMessage && list.isEmpty())
+ {
+ assert(idx != -1);
+ srcFolder->unGetMsg( idx );
+ }
+
+ } // end for
+
+ bool deleteNow = false;
+ if (!localList.isEmpty())
+ {
+ QValueList<int> index;
+ mDestFolder->addMsg( localList, index );
+ for ( QValueListIterator<int> it = index.begin(); it != index.end(); ++it ) {
+ mDestFolder->unGetMsg( *it );
+ }
+ if ( mDestFolder->folderType() == KMFolderTypeImap ) {
+ if ( mPendingJobs.isEmpty() ) {
+ // wait for the end of the copy before closing the folder
+ KMFolderImap *imapDestFolder = static_cast<KMFolderImap*>(mDestFolder->storage());
+ connect( imapDestFolder, SIGNAL( folderComplete( KMFolderImap*, bool ) ),
+ this, SLOT( slotFolderComplete( KMFolderImap*, bool ) ) );
+ }
+ } else {
+ deleteNow = list.isEmpty() && mPendingJobs.isEmpty(); // we're done if there are no other mails we need to fetch
+ }
+ }
+
+//TODO: Get rid of the other cases just use this one for all types of folder
+//TODO: requires adding copyMsg and getFolder methods to KMFolder.h
+ if (!list.isEmpty())
+ {
+ // copy the message(s); note: the list is empty afterwards!
+ KMFolderImap *imapDestFolder = static_cast<KMFolderImap*>(mDestFolder->storage());
+ connect( imapDestFolder, SIGNAL( folderComplete( KMFolderImap*, bool ) ),
+ this, SLOT( slotFolderComplete( KMFolderImap*, bool ) ) );
+ imapDestFolder->copyMsg(list);
+ imapDestFolder->getFolder();
+ }
+
+ // only close the folder and delete the job if we're done
+ // otherwise this is done in slotMsgAdded or slotFolderComplete
+ if ( deleteNow )
+ {
+ mDestFolder->close("kmcommand");
+ setResult( OK );
+ emit completed( this );
+ deleteLater();
+ }
+
+ return OK;
+}
+
+void KMCopyCommand::slotJobFinished(KMail::FolderJob * job)
+{
+ mPendingJobs.remove( job );
+ if ( job->error() ) {
+ kdDebug(5006) << k_funcinfo << "folder job failed: " << job->error() << endl;
+ // kill all pending jobs
+ for ( QValueList<KMail::FolderJob*>::Iterator it = mPendingJobs.begin(); it != mPendingJobs.end(); ++it ) {
+ disconnect( (*it), SIGNAL(result(KMail::FolderJob*)),
+ this, SLOT(slotJobFinished(KMail::FolderJob*)) );
+ (*it)->kill();
+ }
+ mPendingJobs.clear();
+ setResult( Failed );
+ }
+
+ if ( mPendingJobs.isEmpty() )
+ {
+ mDestFolder->close("kmcommand");
+ emit completed( this );
+ deleteLater();
+ }
+}
+
+void KMCopyCommand::slotFolderComplete( KMFolderImap*, bool success )
+{
+ kdDebug(5006) << k_funcinfo << success << endl;
+ if ( !success )
+ setResult( Failed );
+ mDestFolder->close( "kmcommand" );
+ emit completed( this );
+ deleteLater();
+}
+
+
+KMMoveCommand::KMMoveCommand( KMFolder* destFolder,
+ const QPtrList<KMMsgBase> &msgList)
+ : mDestFolder( destFolder ), mProgressItem( 0 )
+{
+ QPtrList<KMMsgBase> tmp = msgList;
+ for ( KMMsgBase *msgBase = tmp.first(); msgBase; msgBase = tmp.next() )
+ mSerNumList.append( msgBase->getMsgSerNum() );
+}
+
+KMMoveCommand::KMMoveCommand( KMFolder* destFolder,
+ KMMessage *msg )
+ : mDestFolder( destFolder ), mProgressItem( 0 )
+{
+ mSerNumList.append( msg->getMsgSerNum() );
+}
+
+KMMoveCommand::KMMoveCommand( KMFolder* destFolder,
+ KMMsgBase *msgBase )
+ : mDestFolder( destFolder ), mProgressItem( 0 )
+{
+ mSerNumList.append( msgBase->getMsgSerNum() );
+}
+
+KMMoveCommand::KMMoveCommand( Q_UINT32 )
+ : mProgressItem( 0 )
+{
+}
+
+KMCommand::Result KMMoveCommand::execute()
+{
+ setEmitsCompletedItself( true );
+ setDeletesItself( true );
+ typedef QMap< KMFolder*, QPtrList<KMMessage>* > FolderToMessageListMap;
+ FolderToMessageListMap folderDeleteList;
+
+ if (mDestFolder && mDestFolder->open("kmcommand") != 0) {
+ completeMove( Failed );
+ return Failed;
+ }
+ KCursorSaver busy(KBusyPtr::busy());
+
+ // TODO set SSL state according to source and destfolder connection?
+ Q_ASSERT( !mProgressItem );
+ mProgressItem =
+ ProgressManager::createProgressItem (
+ "move"+ProgressManager::getUniqueID(),
+ mDestFolder ? i18n( "Moving messages" ) : i18n( "Deleting messages" ) );
+ connect( mProgressItem, SIGNAL( progressItemCanceled( KPIM::ProgressItem* ) ),
+ this, SLOT( slotMoveCanceled() ) );
+
+ KMMessage *msg;
+ int rc = 0;
+ int index;
+ QPtrList<KMMessage> list;
+ int undoId = -1;
+ mCompleteWithAddedMsg = false;
+
+ if (mDestFolder) {
+ connect (mDestFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
+ this, SLOT(slotMsgAddedToDestFolder(KMFolder*, Q_UINT32)));
+ mLostBoys = mSerNumList;
+ }
+ mProgressItem->setTotalItems( mSerNumList.count() );
+
+ for ( QValueList<Q_UINT32>::ConstIterator it = mSerNumList.constBegin(); it != mSerNumList.constEnd(); ++it ) {
+ KMFolder *srcFolder;
+ int idx = -1;
+ KMMsgDict::instance()->getLocation( *it, &srcFolder, &idx );
+ if (srcFolder == mDestFolder)
+ continue;
+ assert(idx != -1);
+ srcFolder->open( "kmmovecommand" );
+ mOpenedFolders.append( srcFolder );
+ msg = srcFolder->getMsg(idx);
+ if ( !msg ) {
+ kdDebug(5006) << k_funcinfo << "No message found for serial number " << *it << endl;
+ continue;
+ }
+ bool undo = msg->enableUndo();
+
+ if ( msg && msg->transferInProgress() &&
+ srcFolder->folderType() == KMFolderTypeImap )
+ {
+ // cancel the download
+ msg->setTransferInProgress( false, true );
+ static_cast<KMFolderImap*>(srcFolder->storage())->ignoreJobsForMessage( msg );
+ }
+
+ if (mDestFolder) {
+ if (mDestFolder->folderType() == KMFolderTypeImap) {
+ /* If we are moving to an imap folder, connect to it's completed
+ * signal so we notice when all the mails should have showed up in it
+ * but haven't for some reason. */
+ KMFolderImap *imapFolder = static_cast<KMFolderImap*> ( mDestFolder->storage() );
+ disconnect (imapFolder, SIGNAL(folderComplete( KMFolderImap*, bool )),
+ this, SLOT(slotImapFolderCompleted( KMFolderImap*, bool )));
+
+ connect (imapFolder, SIGNAL(folderComplete( KMFolderImap*, bool )),
+ this, SLOT(slotImapFolderCompleted( KMFolderImap*, bool )));
+ list.append(msg);
+ } else {
+ // We are moving to a local folder.
+ if ( srcFolder->folderType() == KMFolderTypeImap )
+ {
+ // do not complete here but wait until all messages are transferred
+ mCompleteWithAddedMsg = true;
+ }
+ rc = mDestFolder->moveMsg(msg, &index);
+ if (rc == 0 && index != -1) {
+ KMMsgBase *mb = mDestFolder->unGetMsg( mDestFolder->count() - 1 );
+ if (undo && mb)
+ {
+ if ( undoId == -1 )
+ undoId = kmkernel->undoStack()->newUndoAction( srcFolder, mDestFolder );
+ kmkernel->undoStack()->addMsgToAction( undoId, mb->getMsgSerNum() );
+ }
+ } else if (rc != 0) {
+ // Something went wrong. Stop processing here, it is likely that the
+ // other moves would fail as well.
+ completeMove( Failed );
+ return Failed;
+ }
+ }
+ } else {
+ // really delete messages that are already in the trash folder or if
+ // we are really, really deleting, not just moving to trash
+ if (srcFolder->folderType() == KMFolderTypeImap) {
+ if (!folderDeleteList[srcFolder])
+ folderDeleteList[srcFolder] = new QPtrList<KMMessage>;
+ folderDeleteList[srcFolder]->append( msg );
+ } else {
+ srcFolder->removeMsg(idx);
+ delete msg;
+ }
+ }
+ }
+ if (!list.isEmpty() && mDestFolder) {
+ // will be completed with folderComplete signal
+ mDestFolder->moveMsg(list, &index);
+ } else {
+ FolderToMessageListMap::Iterator it;
+ for ( it = folderDeleteList.begin(); it != folderDeleteList.end(); ++it ) {
+ it.key()->removeMsg(*it.data());
+ delete it.data();
+ }
+ if ( !mCompleteWithAddedMsg ) {
+ // imap folders will be completed in slotMsgAddedToDestFolder
+ completeMove( OK );
+ }
+ }
+
+ return OK;
+}
+
+void KMMoveCommand::slotImapFolderCompleted(KMFolderImap* imapFolder, bool success)
+{
+ disconnect (imapFolder, SIGNAL(folderComplete( KMFolderImap*, bool )),
+ this, SLOT(slotImapFolderCompleted( KMFolderImap*, bool )));
+ if ( success ) {
+ // the folder was checked successfully but we were still called, so check
+ // if we are still waiting for messages to show up. If so, uidValidity
+ // changed, or something else went wrong. Clean up.
+
+ /* Unfortunately older UW imap servers change uid validity for each put job.
+ * Yes, it is really that broken. *sigh* So we cannot report error here, I guess. */
+ if ( !mLostBoys.isEmpty() ) {
+ kdDebug(5006) << "### Not all moved messages reported back that they were " << endl
+ << "### added to the target folder. Did uidValidity change? " << endl;
+ }
+ completeMove( OK );
+ } else {
+ // Should we inform the user here or leave that to the caller?
+ completeMove( Failed );
+ }
+}
+
+void KMMoveCommand::slotMsgAddedToDestFolder(KMFolder *folder, Q_UINT32 serNum)
+{
+ if ( folder != mDestFolder || mLostBoys.find( serNum ) == mLostBoys.end() ) {
+ //kdDebug(5006) << "KMMoveCommand::msgAddedToDestFolder different "
+ // "folder or invalid serial number." << endl;
+ return;
+ }
+ mLostBoys.remove(serNum);
+ if ( mLostBoys.isEmpty() ) {
+ // we are done. All messages transferred to the host succesfully
+ disconnect (mDestFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
+ this, SLOT(slotMsgAddedToDestFolder(KMFolder*, Q_UINT32)));
+ if (mDestFolder && mDestFolder->folderType() != KMFolderTypeImap) {
+ mDestFolder->sync();
+ }
+ if ( mCompleteWithAddedMsg ) {
+ completeMove( OK );
+ }
+ } else {
+ if ( mProgressItem ) {
+ mProgressItem->incCompletedItems();
+ mProgressItem->updateProgress();
+ }
+ }
+}
+
+void KMMoveCommand::completeMove( Result result )
+{
+ if ( mDestFolder )
+ mDestFolder->close("kmcommand");
+ while ( !mOpenedFolders.empty() ) {
+ KMFolder *folder = mOpenedFolders.back();
+ mOpenedFolders.pop_back();
+ folder->close("kmcommand");
+ }
+ if ( mProgressItem ) {
+ mProgressItem->setComplete();
+ mProgressItem = 0;
+ }
+ setResult( result );
+ emit completed( this );
+ deleteLater();
+}
+
+void KMMoveCommand::slotMoveCanceled()
+{
+ completeMove( Canceled );
+}
+
+// srcFolder doesn't make much sense for searchFolders
+KMDeleteMsgCommand::KMDeleteMsgCommand( KMFolder* srcFolder,
+ const QPtrList<KMMsgBase> &msgList )
+:KMMoveCommand( findTrashFolder( srcFolder ), msgList)
+{
+ srcFolder->open("kmcommand");
+ mOpenedFolders.push_back( srcFolder );
+}
+
+KMDeleteMsgCommand::KMDeleteMsgCommand( KMFolder* srcFolder, KMMessage * msg )
+:KMMoveCommand( findTrashFolder( srcFolder ), msg)
+{
+ srcFolder->open("kmcommand");
+ mOpenedFolders.push_back( srcFolder );
+}
+
+KMDeleteMsgCommand::KMDeleteMsgCommand( Q_UINT32 sernum )
+:KMMoveCommand( sernum )
+{
+ KMFolder *srcFolder = 0;
+ int idx;
+ KMMsgDict::instance()->getLocation( sernum, &srcFolder, &idx );
+ if ( srcFolder ) {
+ KMMsgBase *msg = srcFolder->getMsgBase( idx );
+ srcFolder->open("kmcommand");
+ mOpenedFolders.push_back( srcFolder );
+ addMsg( msg );
+ }
+ setDestFolder( findTrashFolder( srcFolder ) );
+}
+
+KMFolder * KMDeleteMsgCommand::findTrashFolder( KMFolder * folder )
+{
+ KMFolder* trash = folder->trashFolder();
+ if( !trash )
+ trash = kmkernel->trashFolder();
+ if( trash != folder )
+ return trash;
+ return 0;
+}
+
+
+KMUrlClickedCommand::KMUrlClickedCommand( const KURL &url, uint identity,
+ KMReaderWin *readerWin, bool htmlPref, KMMainWidget *mainWidget )
+ :mUrl( url ), mIdentity( identity ), mReaderWin( readerWin ),
+ mHtmlPref( htmlPref ), mMainWidget( mainWidget )
+{
+}
+
+KMCommand::Result KMUrlClickedCommand::execute()
+{
+ KMMessage* msg;
+
+ if (mUrl.protocol() == "mailto")
+ {
+ msg = new KMMessage;
+ msg->initHeader(mIdentity);
+ msg->setCharset("utf-8");
+ msg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );
+ QString query=mUrl.query();
+ while (!query.isEmpty()) {
+ QString queryPart;
+ int secondQuery = query.find('?',1);
+ if (secondQuery != -1)
+ queryPart = query.left(secondQuery);
+ else
+ queryPart = query;
+ query = query.mid(queryPart.length());
+
+ if (queryPart.left(9) == "?subject=")
+ msg->setSubject( KURL::decode_string(queryPart.mid(9)) );
+ else if (queryPart.left(6) == "?body=")
+ // It is correct to convert to latin1() as URL should not contain
+ // anything except ascii.
+ msg->setBody( KURL::decode_string(queryPart.mid(6)).latin1() );
+ else if (queryPart.left(4) == "?cc=")
+ msg->setCc( KURL::decode_string(queryPart.mid(4)) );
+ }
+
+ KMail::Composer * win = KMail::makeComposer( msg, mIdentity );
+ win->setCharset("", true);
+ win->show();
+ }
+ else if ( mUrl.protocol() == "im" )
+ {
+ kmkernel->imProxy()->chatWithContact( mUrl.path() );
+ }
+ else if ((mUrl.protocol() == "http") || (mUrl.protocol() == "https") ||
+ (mUrl.protocol() == "ftp") || (mUrl.protocol() == "file") ||
+ (mUrl.protocol() == "ftps") || (mUrl.protocol() == "sftp" ) ||
+ (mUrl.protocol() == "help") || (mUrl.protocol() == "vnc") ||
+ (mUrl.protocol() == "smb") || (mUrl.protocol() == "fish") ||
+ (mUrl.protocol() == "news"))
+ {
+ KPIM::BroadcastStatus::instance()->setStatusMsg( i18n("Opening URL..."));
+ KMimeType::Ptr mime = KMimeType::findByURL( mUrl );
+ if (mime->name() == "application/x-desktop" ||
+ mime->name() == "application/x-executable" ||
+ mime->name() == "application/x-msdos-program" ||
+ mime->name() == "application/x-shellscript" )
+ {
+ if (KMessageBox::warningYesNo( 0, i18n( "<qt>Do you really want to execute <b>%1</b>?</qt>" )
+ .arg( mUrl.prettyURL() ), QString::null, i18n("Execute"), KStdGuiItem::cancel() ) != KMessageBox::Yes)
+ return Canceled;
+ }
+ KRun * runner = new KRun( mUrl );
+ runner->setRunExecutables( false );
+ }
+ else
+ return Failed;
+
+ return OK;
+}
+
+KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( QWidget *parent, KMMessage *msg )
+ : KMCommand( parent, msg ), mImplicitAttachments( true ), mEncoded( false )
+{
+}
+
+KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( QWidget *parent, const QPtrList<KMMsgBase>& msgs )
+ : KMCommand( parent, msgs ), mImplicitAttachments( true ), mEncoded( false )
+{
+}
+
+KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( QWidget *parent, QPtrList<partNode>& attachments,
+ KMMessage *msg, bool encoded )
+ : KMCommand( parent ), mImplicitAttachments( false ), mEncoded( encoded )
+{
+ for ( QPtrListIterator<partNode> it( attachments ); it.current(); ++it ) {
+ mAttachmentMap.insert( it.current(), msg );
+ }
+}
+
+KMCommand::Result KMSaveAttachmentsCommand::execute()
+{
+ setEmitsCompletedItself( true );
+ if ( mImplicitAttachments ) {
+ QPtrList<KMMessage> msgList = retrievedMsgs();
+ KMMessage *msg;
+ for ( QPtrListIterator<KMMessage> itr( msgList );
+ ( msg = itr.current() );
+ ++itr ) {
+ partNode *rootNode = partNode::fromMessage( msg );
+ for ( partNode *child = rootNode; child;
+ child = child->firstChild() ) {
+ for ( partNode *node = child; node; node = node->nextSibling() ) {
+ if ( node->type() != DwMime::kTypeMultipart )
+ mAttachmentMap.insert( node, msg );
+ }
+ }
+ }
+ }
+ setDeletesItself( true );
+ // load all parts
+ KMLoadPartsCommand *command = new KMLoadPartsCommand( mAttachmentMap );
+ connect( command, SIGNAL( partsRetrieved() ),
+ this, SLOT( slotSaveAll() ) );
+ command->start();
+
+ return OK;
+}
+
+void KMSaveAttachmentsCommand::slotSaveAll()
+{
+ // now that all message parts have been retrieved, remove all parts which
+ // don't represent an attachment if they were not explicitely passed in the
+ // c'tor
+ if ( mImplicitAttachments ) {
+ for ( PartNodeMessageMap::iterator it = mAttachmentMap.begin();
+ it != mAttachmentMap.end(); ) {
+ // only body parts which have a filename or a name parameter (except for
+ // the root node for which name is set to the message's subject) are
+ // considered attachments
+ if ( it.key()->msgPart().fileName().stripWhiteSpace().isEmpty() &&
+ ( it.key()->msgPart().name().stripWhiteSpace().isEmpty() ||
+ !it.key()->parentNode() ) ) {
+ PartNodeMessageMap::iterator delIt = it;
+ ++it;
+ mAttachmentMap.remove( delIt );
+ }
+ else
+ ++it;
+ }
+ if ( mAttachmentMap.isEmpty() ) {
+ KMessageBox::information( 0, i18n("Found no attachments to save.") );
+ setResult( OK ); // The user has already been informed.
+ emit completed( this );
+ deleteLater();
+ return;
+ }
+ }
+
+ KURL url, dirUrl;
+ if ( mAttachmentMap.count() > 1 ) {
+ // get the dir
+ dirUrl = KDirSelectDialog::selectDirectory( QString::null, false,
+ parentWidget(),
+ i18n("Save Attachments To") );
+ if ( !dirUrl.isValid() ) {
+ setResult( Canceled );
+ emit completed( this );
+ deleteLater();
+ return;
+ }
+
+ // we may not get a slash-terminated url out of KDirSelectDialog
+ dirUrl.adjustPath( 1 );
+ }
+ else {
+ // only one item, get the desired filename
+ partNode *node = mAttachmentMap.begin().key();
+ // replace all ':' with '_' because ':' isn't allowed on FAT volumes
+ QString s =
+ node->msgPart().fileName().stripWhiteSpace().replace( ':', '_' );
+ if ( s.isEmpty() )
+ s = node->msgPart().name().stripWhiteSpace().replace( ':', '_' );
+ if ( s.isEmpty() )
+ s = i18n("filename for an unnamed attachment", "attachment.1");
+ url = KFileDialog::getSaveURL( s, QString::null, parentWidget(),
+ QString::null );
+ if ( url.isEmpty() ) {
+ setResult( Canceled );
+ emit completed( this );
+ deleteLater();
+ return;
+ }
+ }
+
+ QMap< QString, int > renameNumbering;
+
+ Result globalResult = OK;
+ int unnamedAtmCount = 0;
+ for ( PartNodeMessageMap::const_iterator it = mAttachmentMap.begin();
+ it != mAttachmentMap.end();
+ ++it ) {
+ KURL curUrl;
+ if ( !dirUrl.isEmpty() ) {
+ curUrl = dirUrl;
+ QString s =
+ it.key()->msgPart().fileName().stripWhiteSpace().replace( ':', '_' );
+ if ( s.isEmpty() )
+ s = it.key()->msgPart().name().stripWhiteSpace().replace( ':', '_' );
+ if ( s.isEmpty() ) {
+ ++unnamedAtmCount;
+ s = i18n("filename for the %1-th unnamed attachment",
+ "attachment.%1")
+ .arg( unnamedAtmCount );
+ }
+ curUrl.setFileName( s );
+ } else {
+ curUrl = url;
+ }
+
+ if ( !curUrl.isEmpty() ) {
+
+ // Rename the file if we have already saved one with the same name:
+ // try appending a number before extension (e.g. "pic.jpg" => "pic_2.jpg")
+ QString origFile = curUrl.fileName();
+ QString file = origFile;
+
+ while ( renameNumbering.contains(file) ) {
+ file = origFile;
+ int num = renameNumbering[file] + 1;
+ int dotIdx = file.findRev('.');
+ file = file.insert( (dotIdx>=0) ? dotIdx : file.length(), QString("_") + QString::number(num) );
+ }
+ curUrl.setFileName(file);
+
+ // Increment the counter for both the old and the new filename
+ if ( !renameNumbering.contains(origFile))
+ renameNumbering[origFile] = 1;
+ else
+ renameNumbering[origFile]++;
+
+ if ( file != origFile ) {
+ if ( !renameNumbering.contains(file))
+ renameNumbering[file] = 1;
+ else
+ renameNumbering[file]++;
+ }
+
+
+ if ( KIO::NetAccess::exists( curUrl, false, parentWidget() ) ) {
+ if ( KMessageBox::warningContinueCancel( parentWidget(),
+ i18n( "A file named %1 already exists. Do you want to overwrite it?" )
+ .arg( curUrl.fileName() ),
+ i18n( "File Already Exists" ), i18n("&Overwrite") ) == KMessageBox::Cancel) {
+ continue;
+ }
+ }
+ // save
+ const Result result = saveItem( it.key(), curUrl );
+ if ( result != OK )
+ globalResult = result;
+ }
+ }
+ setResult( globalResult );
+ emit completed( this );
+ deleteLater();
+}
+
+KMCommand::Result KMSaveAttachmentsCommand::saveItem( partNode *node,
+ const KURL& url )
+{
+ bool bSaveEncrypted = false;
+ bool bEncryptedParts = node->encryptionState() != KMMsgNotEncrypted;
+ if( bEncryptedParts )
+ if( KMessageBox::questionYesNo( parentWidget(),
+ i18n( "The part %1 of the message is encrypted. Do you want to keep the encryption when saving?" ).
+ arg( url.fileName() ),
+ i18n( "KMail Question" ), i18n("Keep Encryption"), i18n("Do Not Keep") ) ==
+ KMessageBox::Yes )
+ bSaveEncrypted = true;
+
+ bool bSaveWithSig = true;
+ if( node->signatureState() != KMMsgNotSigned )
+ if( KMessageBox::questionYesNo( parentWidget(),
+ i18n( "The part %1 of the message is signed. Do you want to keep the signature when saving?" ).
+ arg( url.fileName() ),
+ i18n( "KMail Question" ), i18n("Keep Signature"), i18n("Do Not Keep") ) !=
+ KMessageBox::Yes )
+ bSaveWithSig = false;
+
+ QByteArray data;
+ if ( mEncoded )
+ {
+ // This does not decode the Message Content-Transfer-Encoding
+ // but saves the _original_ content of the message part
+ data = KMail::Util::ByteArray( node->msgPart().dwBody() );
+ }
+ else
+ {
+ if( bSaveEncrypted || !bEncryptedParts) {
+ partNode *dataNode = node;
+ QCString rawReplyString;
+ bool gotRawReplyString = false;
+ if( !bSaveWithSig ) {
+ if( DwMime::kTypeMultipart == node->type() &&
+ DwMime::kSubtypeSigned == node->subType() ){
+ // carefully look for the part that is *not* the signature part:
+ if( node->findType( DwMime::kTypeApplication,
+ DwMime::kSubtypePgpSignature,
+ true, false ) ){
+ dataNode = node->findTypeNot( DwMime::kTypeApplication,
+ DwMime::kSubtypePgpSignature,
+ true, false );
+ }else if( node->findType( DwMime::kTypeApplication,
+ DwMime::kSubtypePkcs7Mime,
+ true, false ) ){
+ dataNode = node->findTypeNot( DwMime::kTypeApplication,
+ DwMime::kSubtypePkcs7Mime,
+ true, false );
+ }else{
+ dataNode = node->findTypeNot( DwMime::kTypeMultipart,
+ DwMime::kSubtypeUnknown,
+ true, false );
+ }
+ }else{
+ ObjectTreeParser otp( 0, 0, false, false, false );
+
+ // process this node and all it's siblings and descendants
+ dataNode->setProcessed( false, true );
+ otp.parseObjectTree( dataNode );
+
+ rawReplyString = otp.rawReplyString();
+ gotRawReplyString = true;
+ }
+ }
+ QByteArray cstr = gotRawReplyString
+ ? rawReplyString
+ : dataNode->msgPart().bodyDecodedBinary();
+ data = cstr;
+ size_t size = cstr.size();
+ if ( dataNode->msgPart().type() == DwMime::kTypeText ) {
+ // convert CRLF to LF before writing text attachments to disk
+ size = KMail::Util::crlf2lf( cstr.data(), size );
+ }
+ data.resize( size );
+ }
+ }
+ QDataStream ds;
+ QFile file;
+ KTempFile tf;
+ tf.setAutoDelete( true );
+ if ( url.isLocalFile() )
+ {
+ // save directly
+ file.setName( url.path() );
+ if ( !file.open( IO_WriteOnly ) )
+ {
+ KMessageBox::error( parentWidget(),
+ i18n( "%2 is detailed error description",
+ "Could not write the file %1:\n%2" )
+ .arg( file.name() )
+ .arg( QString::fromLocal8Bit( strerror( errno ) ) ),
+ i18n( "KMail Error" ) );
+ return Failed;
+ }
+
+ // #79685 by default use the umask the user defined, but let it be configurable
+ if ( GlobalSettings::self()->disregardUmask() )
+ fchmod( file.handle(), S_IRUSR | S_IWUSR );
+
+ ds.setDevice( &file );
+ } else
+ {
+ // tmp file for upload
+ ds.setDevice( tf.file() );
+ }
+
+ ds.writeRawBytes( data.data(), data.size() );
+ if ( !url.isLocalFile() )
+ {
+ tf.close();
+ if ( !KIO::NetAccess::upload( tf.name(), url, parentWidget() ) )
+ {
+ KMessageBox::error( parentWidget(),
+ i18n( "Could not write the file %1." )
+ .arg( url.path() ),
+ i18n( "KMail Error" ) );
+ return Failed;
+ }
+ } else
+ file.close();
+ return OK;
+}
+
+KMLoadPartsCommand::KMLoadPartsCommand( QPtrList<partNode>& parts, KMMessage *msg )
+ : mNeedsRetrieval( 0 )
+{
+ for ( QPtrListIterator<partNode> it( parts ); it.current(); ++it ) {
+ mPartMap.insert( it.current(), msg );
+ }
+}
+
+KMLoadPartsCommand::KMLoadPartsCommand( partNode *node, KMMessage *msg )
+ : mNeedsRetrieval( 0 )
+{
+ mPartMap.insert( node, msg );
+}
+
+KMLoadPartsCommand::KMLoadPartsCommand( PartNodeMessageMap& partMap )
+ : mNeedsRetrieval( 0 ), mPartMap( partMap )
+{
+}
+
+void KMLoadPartsCommand::slotStart()
+{
+ for ( PartNodeMessageMap::const_iterator it = mPartMap.begin();
+ it != mPartMap.end();
+ ++it ) {
+ if ( !it.key()->msgPart().isComplete() &&
+ !it.key()->msgPart().partSpecifier().isEmpty() ) {
+ // incomplete part, so retrieve it first
+ ++mNeedsRetrieval;
+ KMFolder* curFolder = it.data()->parent();
+ if ( curFolder ) {
+ FolderJob *job =
+ curFolder->createJob( it.data(), FolderJob::tGetMessage,
+ 0, it.key()->msgPart().partSpecifier() );
+ job->setCancellable( false );
+ connect( job, SIGNAL(messageUpdated(KMMessage*, QString)),
+ this, SLOT(slotPartRetrieved(KMMessage*, QString)) );
+ job->start();
+ } else
+ kdWarning(5006) << "KMLoadPartsCommand - msg has no parent" << endl;
+ }
+ }
+ if ( mNeedsRetrieval == 0 )
+ execute();
+}
+
+void KMLoadPartsCommand::slotPartRetrieved( KMMessage *msg,
+ QString partSpecifier )
+{
+ DwBodyPart *part =
+ msg->findDwBodyPart( msg->getFirstDwBodyPart(), partSpecifier );
+ if ( part ) {
+ // update the DwBodyPart in the partNode
+ for ( PartNodeMessageMap::const_iterator it = mPartMap.begin();
+ it != mPartMap.end();
+ ++it ) {
+ if ( it.key()->dwPart()->partId() == part->partId() )
+ it.key()->setDwPart( part );
+ }
+ } else
+ kdWarning(5006) << "KMLoadPartsCommand::slotPartRetrieved - could not find bodypart!" << endl;
+ --mNeedsRetrieval;
+ if ( mNeedsRetrieval == 0 )
+ execute();
+}
+
+KMCommand::Result KMLoadPartsCommand::execute()
+{
+ emit partsRetrieved();
+ setResult( OK );
+ emit completed( this );
+ deleteLater();
+ return OK;
+}
+
+KMResendMessageCommand::KMResendMessageCommand( QWidget *parent,
+ KMMessage *msg )
+ :KMCommand( parent, msg )
+{
+}
+
+KMCommand::Result KMResendMessageCommand::execute()
+{
+ KMMessage *msg = retrievedMessage();
+ if ( !msg || !msg->codec() ) {
+ return Failed;
+ }
+ KMMessage *newMsg = new KMMessage(*msg);
+
+ QStringList whiteList;
+ whiteList << "To" << "Cc" << "Bcc" << "Subject";
+ newMsg->sanitizeHeaders( whiteList );
+
+ newMsg->setCharset(msg->codec()->mimeName());
+ newMsg->setParent( 0 );
+
+ // make sure we have an identity set, default, if necessary
+ newMsg->setHeaderField("X-KMail-Identity", QString::number( newMsg->identityUoid() ));
+ newMsg->applyIdentity( newMsg->identityUoid() );
+
+ KMail::Composer * win = KMail::makeComposer();
+ win->setMsg(newMsg, false, true);
+ win->show();
+
+ return OK;
+}
+
+KMMailingListCommand::KMMailingListCommand( QWidget *parent, KMFolder *folder )
+ : KMCommand( parent ), mFolder( folder )
+{
+}
+
+KMCommand::Result KMMailingListCommand::execute()
+{
+ KURL::List lst = urls();
+ QString handler = ( mFolder->mailingList().handler() == MailingList::KMail )
+ ? "mailto" : "https";
+
+ KMCommand *command = 0;
+ for ( KURL::List::Iterator itr = lst.begin(); itr != lst.end(); ++itr ) {
+ if ( handler == (*itr).protocol() ) {
+ command = new KMUrlClickedCommand( *itr, mFolder->identity(), 0, false );
+ }
+ }
+ if ( !command && !lst.empty() ) {
+ command =
+ new KMUrlClickedCommand( lst.first(), mFolder->identity(), 0, false );
+ }
+ if ( command ) {
+ connect( command, SIGNAL( completed( KMCommand * ) ),
+ this, SLOT( commandCompleted( KMCommand * ) ) );
+ setDeletesItself( true );
+ setEmitsCompletedItself( true );
+ command->start();
+ return OK;
+ }
+ return Failed;
+}
+
+void KMMailingListCommand::commandCompleted( KMCommand *command )
+{
+ setResult( command->result() );
+ emit completed( this );
+ deleteLater();
+}
+
+KMMailingListPostCommand::KMMailingListPostCommand( QWidget *parent, KMFolder *folder )
+ : KMMailingListCommand( parent, folder )
+{
+}
+KURL::List KMMailingListPostCommand::urls() const
+{
+ return mFolder->mailingList().postURLS();
+}
+
+KMMailingListSubscribeCommand::KMMailingListSubscribeCommand( QWidget *parent, KMFolder *folder )
+ : KMMailingListCommand( parent, folder )
+{
+}
+KURL::List KMMailingListSubscribeCommand::urls() const
+{
+ return mFolder->mailingList().subscribeURLS();
+}
+
+KMMailingListUnsubscribeCommand::KMMailingListUnsubscribeCommand( QWidget *parent, KMFolder *folder )
+ : KMMailingListCommand( parent, folder )
+{
+}
+KURL::List KMMailingListUnsubscribeCommand::urls() const
+{
+ return mFolder->mailingList().unsubscribeURLS();
+}
+
+KMMailingListArchivesCommand::KMMailingListArchivesCommand( QWidget *parent, KMFolder *folder )
+ : KMMailingListCommand( parent, folder )
+{
+}
+KURL::List KMMailingListArchivesCommand::urls() const
+{
+ return mFolder->mailingList().archiveURLS();
+}
+
+KMMailingListHelpCommand::KMMailingListHelpCommand( QWidget *parent, KMFolder *folder )
+ : KMMailingListCommand( parent, folder )
+{
+}
+KURL::List KMMailingListHelpCommand::urls() const
+{
+ return mFolder->mailingList().helpURLS();
+}
+
+KMIMChatCommand::KMIMChatCommand( const KURL &url, KMMessage *msg )
+ :mUrl( url ), mMessage( msg )
+{
+}
+
+KMCommand::Result KMIMChatCommand::execute()
+{
+ kdDebug( 5006 ) << k_funcinfo << " URL is: " << mUrl << endl;
+ QString addr = KMMessage::decodeMailtoUrl( mUrl.path() );
+ // find UID for mail address
+ KABC::AddressBook *addressBook = KABC::StdAddressBook::self( true );
+ KABC::AddresseeList addressees = addressBook->findByEmail( KPIM::getEmailAddress( addr ) ) ;
+
+ // start chat
+ if( addressees.count() == 1 ) {
+ kmkernel->imProxy()->chatWithContact( addressees[0].uid() );
+ return OK;
+ }
+ else
+ {
+ kdDebug( 5006 ) << "Didn't find exactly one addressee, couldn't tell who to chat to for that email address. Count = " << addressees.count() << endl;
+
+ QString apology;
+ if ( addressees.isEmpty() )
+ apology = i18n( "There is no Address Book entry for this email address. Add them to the Address Book and then add instant messaging addresses using your preferred messaging client." );
+ else
+ {
+ apology = i18n( "More than one Address Book entry uses this email address:\n %1\n it is not possible to determine who to chat with." );
+ QStringList nameList;
+ KABC::AddresseeList::const_iterator it = addressees.begin();
+ KABC::AddresseeList::const_iterator end = addressees.end();
+ for ( ; it != end; ++it )
+ {
+ nameList.append( (*it).realName() );
+ }
+ QString names = nameList.join( QString::fromLatin1( ",\n" ) );
+ apology = apology.arg( names );
+ }
+
+ KMessageBox::sorry( parentWidget(), apology );
+ return Failed;
+ }
+}
+
+KMHandleAttachmentCommand::KMHandleAttachmentCommand( partNode* node,
+ KMMessage* msg, int atmId, const QString& atmName,
+ AttachmentAction action, KService::Ptr offer, QWidget* parent )
+: KMCommand( parent ), mNode( node ), mMsg( msg ), mAtmId( atmId ), mAtmName( atmName ),
+ mAction( action ), mOffer( offer ), mJob( 0 )
+{
+}
+
+void KMHandleAttachmentCommand::slotStart()
+{
+ if ( !mNode->msgPart().isComplete() )
+ {
+ // load the part
+ kdDebug(5006) << "load part" << endl;
+ KMLoadPartsCommand *command = new KMLoadPartsCommand( mNode, mMsg );
+ connect( command, SIGNAL( partsRetrieved() ),
+ this, SLOT( slotPartComplete() ) );
+ command->start();
+ } else
+ {
+ execute();
+ }
+}
+
+void KMHandleAttachmentCommand::slotPartComplete()
+{
+ execute();
+}
+
+KMCommand::Result KMHandleAttachmentCommand::execute()
+{
+ switch( mAction )
+ {
+ case Open:
+ atmOpen();
+ break;
+ case OpenWith:
+ atmOpenWith();
+ break;
+ case View:
+ atmView();
+ break;
+ case Save:
+ atmSave();
+ break;
+ case Properties:
+ atmProperties();
+ break;
+ case ChiasmusEncrypt:
+ atmEncryptWithChiasmus();
+ return Undefined;
+ break;
+ default:
+ kdDebug(5006) << "unknown action " << mAction << endl;
+ break;
+ }
+ setResult( OK );
+ emit completed( this );
+ deleteLater();
+ return OK;
+}
+
+QString KMHandleAttachmentCommand::createAtmFileLink() const
+{
+ QFileInfo atmFileInfo( mAtmName );
+
+ if ( atmFileInfo.size() == 0 )
+ {
+ kdDebug(5006) << k_funcinfo << "rewriting attachment" << endl;
+ // there is something wrong so write the file again
+ QByteArray data = mNode->msgPart().bodyDecodedBinary();
+ size_t size = data.size();
+ if ( mNode->msgPart().type() == DwMime::kTypeText && size) {
+ // convert CRLF to LF before writing text attachments to disk
+ size = KMail::Util::crlf2lf( data.data(), size );
+ }
+ KPIM::kBytesToFile( data.data(), size, mAtmName, false, false, false );
+ }
+
+ KTempFile *linkFile = new KTempFile( locateLocal("tmp", atmFileInfo.fileName() +"_["),
+ "]."+ atmFileInfo.extension() );
+
+ linkFile->setAutoDelete(true);
+ QString linkName = linkFile->name();
+ delete linkFile;
+
+ if ( ::link(QFile::encodeName( mAtmName ), QFile::encodeName( linkName )) == 0 ) {
+ return linkName; // success
+ }
+ return QString::null;
+}
+
+KService::Ptr KMHandleAttachmentCommand::getServiceOffer()
+{
+ KMMessagePart& msgPart = mNode->msgPart();
+ const QString contentTypeStr =
+ ( msgPart.typeStr() + '/' + msgPart.subtypeStr() ).lower();
+
+ if ( contentTypeStr == "text/x-vcard" ) {
+ atmView();
+ return 0;
+ }
+ // determine the MIME type of the attachment
+ KMimeType::Ptr mimetype;
+ // prefer the value of the Content-Type header
+ mimetype = KMimeType::mimeType( contentTypeStr );
+ if ( mimetype->name() == "application/octet-stream" ) {
+ // consider the filename if Content-Type is application/octet-stream
+ mimetype = KMimeType::findByPath( mAtmName, 0, true /* no disk access */ );
+ }
+ if ( ( mimetype->name() == "application/octet-stream" )
+ && msgPart.isComplete() ) {
+ // consider the attachment's contents if neither the Content-Type header
+ // nor the filename give us a clue
+ mimetype = KMimeType::findByFileContent( mAtmName );
+ }
+ return KServiceTypeProfile::preferredService( mimetype->name(), "Application" );
+}
+
+void KMHandleAttachmentCommand::atmOpen()
+{
+ if ( !mOffer )
+ mOffer = getServiceOffer();
+ if ( !mOffer ) {
+ kdDebug(5006) << k_funcinfo << "got no offer" << endl;
+ return;
+ }
+
+ KURL::List lst;
+ KURL url;
+ bool autoDelete = true;
+ QString fname = createAtmFileLink();
+
+ if ( fname.isNull() ) {
+ autoDelete = false;
+ fname = mAtmName;
+ }
+
+ url.setPath( fname );
+ lst.append( url );
+ if ( (KRun::run( *mOffer, lst, autoDelete ) <= 0) && autoDelete ) {
+ QFile::remove(url.path());
+ }
+}
+
+void KMHandleAttachmentCommand::atmOpenWith()
+{
+ KURL::List lst;
+ KURL url;
+ bool autoDelete = true;
+ QString fname = createAtmFileLink();
+
+ if ( fname.isNull() ) {
+ autoDelete = false;
+ fname = mAtmName;
+ }
+
+ url.setPath( fname );
+ lst.append( url );
+ if ( (! KRun::displayOpenWithDialog(lst, autoDelete)) && autoDelete ) {
+ QFile::remove( url.path() );
+ }
+}
+
+void KMHandleAttachmentCommand::atmView()
+{
+ // we do not handle this ourself
+ emit showAttachment( mAtmId, mAtmName );
+}
+
+void KMHandleAttachmentCommand::atmSave()
+{
+ QPtrList<partNode> parts;
+ parts.append( mNode );
+ // save, do not leave encoded
+ KMSaveAttachmentsCommand *command =
+ new KMSaveAttachmentsCommand( 0, parts, mMsg, false );
+ command->start();
+}
+
+void KMHandleAttachmentCommand::atmProperties()
+{
+ KMMsgPartDialogCompat dlg( parentWidget() , 0, true );
+ KMMessagePart& msgPart = mNode->msgPart();
+ dlg.setMsgPart( &msgPart );
+ dlg.exec();
+}
+
+void KMHandleAttachmentCommand::atmEncryptWithChiasmus()
+{
+ const partNode * node = mNode;
+ Q_ASSERT( node );
+ if ( !node )
+ return;
+
+ // FIXME: better detection of mimetype??
+ if ( !mAtmName.endsWith( ".xia", false ) )
+ return;
+
+ const Kleo::CryptoBackend::Protocol * chiasmus =
+ Kleo::CryptoBackendFactory::instance()->protocol( "Chiasmus" );
+ Q_ASSERT( chiasmus );
+ if ( !chiasmus )
+ return;
+
+ const STD_NAMESPACE_PREFIX auto_ptr<Kleo::SpecialJob> listjob( chiasmus->specialJob( "x-obtain-keys", QMap<QString,QVariant>() ) );
+ if ( !listjob.get() ) {
+ const QString msg = i18n( "Chiasmus backend does not offer the "
+ "\"x-obtain-keys\" function. Please report this bug." );
+ KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) );
+ return;
+ }
+
+ if ( listjob->exec() ) {
+ listjob->showErrorDialog( parentWidget(), i18n( "Chiasmus Backend Error" ) );
+ return;
+ }
+
+ const QVariant result = listjob->property( "result" );
+ if ( result.type() != QVariant::StringList ) {
+ const QString msg = i18n( "Unexpected return value from Chiasmus backend: "
+ "The \"x-obtain-keys\" function did not return a "
+ "string list. Please report this bug." );
+ KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) );
+ return;
+ }
+
+ const QStringList keys = result.toStringList();
+ if ( keys.empty() ) {
+ const QString msg = i18n( "No keys have been found. Please check that a "
+ "valid key path has been set in the Chiasmus "
+ "configuration." );
+ KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) );
+ return;
+ }
+
+ ChiasmusKeySelector selectorDlg( parentWidget(), i18n( "Chiasmus Decryption Key Selection" ),
+ keys, GlobalSettings::chiasmusDecryptionKey(),
+ GlobalSettings::chiasmusDecryptionOptions() );
+ if ( selectorDlg.exec() != QDialog::Accepted )
+ return;
+
+ GlobalSettings::setChiasmusDecryptionOptions( selectorDlg.options() );
+ GlobalSettings::setChiasmusDecryptionKey( selectorDlg.key() );
+ assert( !GlobalSettings::chiasmusDecryptionKey().isEmpty() );
+
+ Kleo::SpecialJob * job = chiasmus->specialJob( "x-decrypt", QMap<QString,QVariant>() );
+ if ( !job ) {
+ const QString msg = i18n( "Chiasmus backend does not offer the "
+ "\"x-decrypt\" function. Please report this bug." );
+ KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) );
+ return;
+ }
+
+ const QByteArray input = node->msgPart().bodyDecodedBinary();
+
+ if ( !job->setProperty( "key", GlobalSettings::chiasmusDecryptionKey() ) ||
+ !job->setProperty( "options", GlobalSettings::chiasmusDecryptionOptions() ) ||
+ !job->setProperty( "input", input ) ) {
+ const QString msg = i18n( "The \"x-decrypt\" function does not accept "
+ "the expected parameters. Please report this bug." );
+ KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) );
+ return;
+ }
+
+ setDeletesItself( true ); // the job below is async, we have to cleanup ourselves
+ if ( job->start() ) {
+ job->showErrorDialog( parentWidget(), i18n( "Chiasmus Decryption Error" ) );
+ return;
+ }
+
+ mJob = job;
+ connect( job, SIGNAL(result(const GpgME::Error&,const QVariant&)),
+ this, SLOT(slotAtmDecryptWithChiasmusResult(const GpgME::Error&,const QVariant&)) );
+}
+
+static const QString chomp( const QString & base, const QString & suffix, bool cs ) {
+ return base.endsWith( suffix, cs ) ? base.left( base.length() - suffix.length() ) : base ;
+}
+
+void KMHandleAttachmentCommand::slotAtmDecryptWithChiasmusResult( const GpgME::Error & err, const QVariant & result )
+{
+ LaterDeleterWithCommandCompletion d( this );
+ if ( !mJob )
+ return;
+ Q_ASSERT( mJob == sender() );
+ if ( mJob != sender() )
+ return;
+ Kleo::Job * job = mJob;
+ mJob = 0;
+ if ( err.isCanceled() )
+ return;
+ if ( err ) {
+ job->showErrorDialog( parentWidget(), i18n( "Chiasmus Decryption Error" ) );
+ return;
+ }
+
+ if ( result.type() != QVariant::ByteArray ) {
+ const QString msg = i18n( "Unexpected return value from Chiasmus backend: "
+ "The \"x-decrypt\" function did not return a "
+ "byte array. Please report this bug." );
+ KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) );
+ return;
+ }
+
+ const KURL url = KFileDialog::getSaveURL( chomp( mAtmName, ".xia", false ), QString::null, parentWidget() );
+ if ( url.isEmpty() )
+ return;
+
+ bool overwrite = KMail::Util::checkOverwrite( url, parentWidget() );
+ if ( !overwrite )
+ return;
+
+ d.setDisabled( true ); // we got this far, don't delete yet
+ KIO::Job * uploadJob = KIO::storedPut( result.toByteArray(), url, -1, overwrite, false /*resume*/ );
+ uploadJob->setWindow( parentWidget() );
+ connect( uploadJob, SIGNAL(result(KIO::Job*)),
+ this, SLOT(slotAtmDecryptWithChiasmusUploadResult(KIO::Job*)) );
+}
+
+void KMHandleAttachmentCommand::slotAtmDecryptWithChiasmusUploadResult( KIO::Job * job )
+{
+ if ( job->error() )
+ job->showErrorDialog();
+ LaterDeleterWithCommandCompletion d( this );
+ d.setResult( OK );
+}
+
+
+AttachmentModifyCommand::AttachmentModifyCommand(partNode * node, KMMessage * msg, QWidget * parent) :
+ KMCommand( parent, msg ),
+ mPartIndex( node->nodeId() ),
+ mSernum( 0 )
+{
+}
+
+AttachmentModifyCommand::~ AttachmentModifyCommand()
+{
+}
+
+KMCommand::Result AttachmentModifyCommand::execute()
+{
+ KMMessage *msg = retrievedMessage();
+ if ( !msg )
+ return Failed;
+ mSernum = msg->getMsgSerNum();
+
+ mFolder = msg->parent();
+ if ( !mFolder || !mFolder->storage() )
+ return Failed;
+
+ Result res = doAttachmentModify();
+ if ( res != OK )
+ return res;
+
+ setEmitsCompletedItself( true );
+ setDeletesItself( true );
+ return OK;
+}
+
+void AttachmentModifyCommand::storeChangedMessage(KMMessage * msg)
+{
+ if ( !mFolder || !mFolder->storage() ) {
+ kdWarning(5006) << k_funcinfo << "We lost the folder!" << endl;
+ setResult( Failed );
+ emit completed( this );
+ deleteLater();
+ }
+ int res = mFolder->addMsg( msg ) != 0;
+ if ( mFolder->folderType() == KMFolderTypeImap ) {
+ KMFolderImap *f = static_cast<KMFolderImap*>( mFolder->storage() );
+ connect( f, SIGNAL(folderComplete(KMFolderImap*,bool)),
+ SLOT(messageStoreResult(KMFolderImap*,bool)) );
+ } else {
+ messageStoreResult( 0, res == 0 );
+ }
+}
+
+void AttachmentModifyCommand::messageStoreResult(KMFolderImap* folder, bool success )
+{
+ Q_UNUSED( folder );
+ if ( success ) {
+ KMCommand *delCmd = new KMDeleteMsgCommand( mSernum );
+ connect( delCmd, SIGNAL(completed(KMCommand*)), SLOT(messageDeleteResult(KMCommand*)) );
+ delCmd->start();
+ return;
+ }
+ kdWarning(5006) << k_funcinfo << "Adding modified message failed." << endl;
+ setResult( Failed );
+ emit completed( this );
+ deleteLater();
+}
+
+void AttachmentModifyCommand::messageDeleteResult(KMCommand * cmd)
+{
+ setResult( cmd->result() );
+ emit completed( this );
+ 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)
+{
+ accu++;
+ if ( index < accu ) // should not happen
+ return 0;
+ DwBodyPart *current = dynamic_cast<DwBodyPart*>( 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;
+}
+
+
+KMDeleteAttachmentCommand::KMDeleteAttachmentCommand(partNode * node, KMMessage * msg, QWidget * parent) :
+ AttachmentModifyCommand( node, msg, parent )
+{
+ kdDebug(5006) << k_funcinfo << endl;
+}
+
+KMDeleteAttachmentCommand::~KMDeleteAttachmentCommand()
+{
+ kdDebug(5006) << k_funcinfo << endl;
+}
+
+KMCommand::Result KMDeleteAttachmentCommand::doAttachmentModify()
+{
+ KMMessage *msg = retrievedMessage();
+ KMMessagePart part;
+ DwBodyPart *dwpart = findPart( msg, mPartIndex );
+ if ( !dwpart )
+ return Failed;
+ KMMessage::bodyPart( dwpart, &part, true );
+ if ( !part.isComplete() )
+ return Failed;
+
+ DwBody *parentNode = dynamic_cast<DwBody*>( 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 );
+ QString 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( QByteArray() );
+ QCString 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() );
+ newMsg->setStatus( msg->status() );
+
+ storeChangedMessage( newMsg );
+ return OK;
+}
+
+
+KMEditAttachmentCommand::KMEditAttachmentCommand(partNode * node, KMMessage * msg, QWidget * parent) :
+ AttachmentModifyCommand( node, msg, parent )
+{
+ kdDebug(5006) << k_funcinfo << endl;
+ mTempFile.setAutoDelete( true );
+}
+
+KMEditAttachmentCommand::~ KMEditAttachmentCommand()
+{
+}
+
+KMCommand::Result KMEditAttachmentCommand::doAttachmentModify()
+{
+ KMMessage *msg = retrievedMessage();
+ KMMessagePart part;
+ DwBodyPart *dwpart = findPart( msg, mPartIndex );
+ if ( !dwpart )
+ return Failed;
+ KMMessage::bodyPart( dwpart, &part, true );
+ if ( !part.isComplete() )
+ return Failed;
+
+ if( !dynamic_cast<DwBody*>( dwpart->Parent() ) )
+ return Failed;
+
+ mTempFile.file()->writeBlock( part.bodyDecodedBinary() );
+ mTempFile.file()->flush();
+
+ KMail::EditorWatcher *watcher = new KMail::EditorWatcher( KURL(mTempFile.file()->name()), part.typeStr() + "/" + part.subtypeStr(), false, this );
+ connect( watcher, SIGNAL(editDone(KMail::EditorWatcher*)), SLOT(editDone(KMail::EditorWatcher*)) );
+ if ( !watcher->start() )
+ return Failed;
+ setEmitsCompletedItself( true );
+ setDeletesItself( true );
+ return OK;
+}
+
+void KMEditAttachmentCommand::editDone(KMail::EditorWatcher * watcher)
+{
+ kdDebug(5006) << k_funcinfo << endl;
+ // anything changed?
+ if ( !watcher->fileChanged() ) {
+ kdDebug(5006) << k_funcinfo << "File has not been changed" << endl;
+ setResult( Canceled );
+ emit completed( this );
+ deleteLater();
+ }
+
+ mTempFile.file()->reset();
+ QByteArray data = mTempFile.file()->readAll();
+
+ // build the new message
+ KMMessage *msg = retrievedMessage();
+ KMMessagePart part;
+ DwBodyPart *dwpart = findPart( msg, mPartIndex );
+ KMMessage::bodyPart( dwpart, &part, true );
+
+ DwBody *parentNode = dynamic_cast<DwBody*>( dwpart->Parent() );
+ assert( parentNode );
+ parentNode->RemoveBodyPart( dwpart );
+
+ KMMessagePart att;
+ att.duplicate( part );
+ att.setBodyEncodedBinary( data );
+
+ DwBodyPart* newDwPart = msg->createDWBodyPart( &att );
+ parentNode->AddBodyPart( newDwPart );
+ msg->getTopLevelPart()->Assemble();
+
+ KMMessage *newMsg = new KMMessage();
+ newMsg->fromDwString( msg->asDwString() );
+ newMsg->setStatus( msg->status() );
+
+ storeChangedMessage( newMsg );
+}
+
+
+CreateTodoCommand::CreateTodoCommand(QWidget * parent, KMMessage * msg)
+ : KMCommand( parent, msg )
+{
+}
+
+KMCommand::Result CreateTodoCommand::execute()
+{
+ KMMessage *msg = retrievedMessage();
+ if ( !msg || !msg->codec() ) {
+ return Failed;
+ }
+
+ KMail::KorgHelper::ensureRunning();
+
+ QString txt = i18n("From: %1\nTo: %2\nSubject: %3").arg( msg->from() )
+ .arg( msg->to() ).arg( msg->subject() );
+
+ KTempFile tf;
+ tf.setAutoDelete( true );
+ QString uri = "kmail:" + QString::number( msg->getMsgSerNum() ) + "/" + msg->msgId();
+ tf.file()->writeBlock( msg->asDwString().c_str(), msg->asDwString().length() );
+ tf.close();
+
+ KCalendarIface_stub *iface = new KCalendarIface_stub( kapp->dcopClient(), "korganizer", "CalendarIface" );
+ iface->openTodoEditor( i18n("Mail: %1").arg( msg->subject() ), txt,
+ uri, tf.name(), QStringList(), "message/rfc822" );
+ delete iface;
+
+ return OK;
+}
+
+#include "kmcommands.moc"
diff --git a/kmail/kmcommands.h b/kmail/kmcommands.h
new file mode 100644
index 00000000..02a48c4a
--- /dev/null
+++ b/kmail/kmcommands.h
@@ -0,0 +1,1101 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+
+#ifndef KMCommands_h
+#define KMCommands_h
+
+#include <qdatetime.h>
+#include <qguardedptr.h>
+#include <qptrlist.h>
+#include <qvaluelist.h>
+#include <qvaluevector.h>
+#include <qtimer.h>
+#include <qfont.h>
+#include <kio/job.h>
+#include "kmmsgbase.h" // for KMMsgStatus
+#include <mimelib/string.h>
+#include <kdepimmacros.h>
+#include <kservice.h>
+#include <ktempfile.h>
+
+class QPopupMenu;
+class KMainWindow;
+class KAction;
+class KProgressDialog;
+class KProcess;
+class KMFilter;
+class KMFolder;
+class KMFolderImap;
+class KMFolderNode;
+class KMHeaders;
+class KMMainWidget;
+class KMMessage;
+class KMMsgBase;
+class KMReaderWin;
+class partNode;
+class DwBodyPart;
+class DwEntity;
+namespace KIO { class Job; }
+namespace KMail {
+ class Composer;
+ class FolderJob;
+ class EditorWatcher;
+}
+namespace GpgME { class Error; }
+namespace Kleo { class SpecialJob; }
+
+typedef QMap<int,KMFolder*> KMMenuToFolder;
+typedef QMap<partNode*, KMMessage*> PartNodeMessageMap;
+
+class KDE_EXPORT KMCommand : public QObject
+{
+ Q_OBJECT
+ friend class LaterDeleterWithCommandCompletion;
+
+public:
+ enum Result { Undefined, OK, Canceled, Failed };
+
+ // Trival constructor, don't retrieve any messages
+ KMCommand( QWidget *parent = 0 );
+ // Retrieve all messages in msgList when start is called.
+ KMCommand( QWidget *parent, const QPtrList<KMMsgBase> &msgList );
+ // Retrieve the single message msgBase when start is called.
+ KMCommand( QWidget *parent, KMMsgBase *msgBase );
+ // Retrieve the single message msgBase when start is called.
+ KMCommand( QWidget *parent, KMMessage *message );
+ virtual ~KMCommand();
+
+ /** These folders will be closed by the dtor, handy, if you need to keep
+ a folder open during the lifetime of the command, but don't want to
+ care about closing it again.
+ */
+ void keepFolderOpen( KMFolder *folder );
+
+ /** Returns the result of the command. Only call this method from the slot
+ connected to completed().
+ */
+ Result result();
+
+public slots:
+ // Retrieve messages then calls execute
+ void start();
+
+ // advance the progressbar, emitted by the folderjob
+ void slotProgress( unsigned long done, unsigned long total );
+
+signals:
+ void messagesTransfered( KMCommand::Result result );
+ /** Emitted when the command has completed.
+ * @param result The status of the command. */
+ void completed( KMCommand *command );
+
+protected:
+ // Returns list of messages retrieved
+ const QPtrList<KMMessage> retrievedMsgs() const;
+ // Returns the single message retrieved
+ KMMessage *retrievedMessage() const;
+ // Returns the parent widget
+ QWidget *parentWidget() const;
+
+ bool deletesItself() { return mDeletesItself; }
+ /** Specify whether the subclass takes care of the deletion of the object.
+ By default the base class will delete the object.
+ @param deletesItself true if the subclass takes care of deletion, false
+ if the base class should take care of deletion
+ */
+ void setDeletesItself( bool deletesItself )
+ { mDeletesItself = deletesItself; }
+
+ bool emitsCompletedItself() { return mEmitsCompletedItself; }
+ /** Specify whether the subclass takes care of emitting the completed()
+ signal. By default the base class will emit this signal.
+ @param emitsCompletedItself true if the subclass emits the completed
+ signal, false if the base class should emit
+ the signal
+ */
+ void setEmitsCompletedItself( bool emitsCompletedItself )
+ { mEmitsCompletedItself = emitsCompletedItself; }
+
+ /** Use this to set the result of the command.
+ @param result The result of the command.
+ */
+ void setResult( Result result )
+ { mResult = result; }
+
+private:
+ // execute should be implemented by derived classes
+ virtual Result execute() = 0;
+
+ /** transfers the list of (imap)-messages
+ * this is a necessary preparation for e.g. forwarding */
+ void transferSelectedMsgs();
+
+private slots:
+ /** Called from start() via a single shot timer. */
+ virtual void slotStart();
+
+ void slotPostTransfer( KMCommand::Result result );
+ /** the msg has been transferred */
+ void slotMsgTransfered(KMMessage* msg);
+ /** the KMImapJob is finished */
+ void slotJobFinished();
+ /** the transfer was canceled */
+ void slotTransferCancelled();
+
+private:
+ // ProgressDialog for transferring messages
+ KProgressDialog* mProgressDialog;
+ //Currently only one async command allowed at a time
+ static int mCountJobs;
+ int mCountMsgs;
+ Result mResult;
+ bool mDeletesItself : 1;
+ bool mEmitsCompletedItself : 1;
+
+ QWidget *mParent;
+ QPtrList<KMMessage> mRetrievedMsgs;
+ QPtrList<KMMsgBase> mMsgList;
+ QValueList<QGuardedPtr<KMFolder> > mFolders;
+};
+
+class KDE_EXPORT KMMailtoComposeCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMMailtoComposeCommand( const KURL &url, KMMessage *msg=0 );
+
+private:
+ virtual Result execute();
+
+ KURL mUrl;
+ KMMessage *mMessage;
+};
+
+class KDE_EXPORT KMMailtoReplyCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMMailtoReplyCommand( QWidget *parent, const KURL &url,
+ KMMessage *msg, const QString &selection );
+
+private:
+ virtual Result execute();
+
+ KURL mUrl;
+ QString mSelection;
+};
+
+class KDE_EXPORT KMMailtoForwardCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMMailtoForwardCommand( QWidget *parent, const KURL &url,
+ KMMessage *msg );
+
+private:
+ virtual Result execute();
+
+ KURL mUrl;
+};
+
+class KDE_EXPORT KMMailtoAddAddrBookCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMMailtoAddAddrBookCommand( const KURL &url, QWidget *parent );
+
+private:
+ virtual Result execute();
+
+ KURL mUrl;
+};
+
+class KDE_EXPORT KMAddBookmarksCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMAddBookmarksCommand( const KURL &url, QWidget *parent );
+
+private:
+ virtual Result execute();
+
+ KURL mUrl;
+};
+
+
+class KDE_EXPORT KMMailtoOpenAddrBookCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMMailtoOpenAddrBookCommand( const KURL &url, QWidget *parent );
+
+private:
+ virtual Result execute();
+
+ KURL mUrl;
+};
+
+class KDE_EXPORT KMUrlCopyCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMUrlCopyCommand( const KURL &url, KMMainWidget *mainWidget = 0 );
+
+private:
+ virtual Result execute();
+
+ KURL mUrl;
+ KMMainWidget *mMainWidget;
+};
+
+class KDE_EXPORT KMUrlOpenCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMUrlOpenCommand( const KURL &url, KMReaderWin *readerWin );
+
+private:
+ virtual Result execute();
+
+ KURL mUrl;
+ KMReaderWin *mReaderWin;
+};
+
+class KDE_EXPORT KMUrlSaveCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMUrlSaveCommand( const KURL &url, QWidget *parent );
+
+private slots:
+ void slotUrlSaveResult( KIO::Job *job );
+
+private:
+ virtual Result execute();
+
+ KURL mUrl;
+};
+
+class KDE_EXPORT KMEditMsgCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMEditMsgCommand( QWidget *parent, KMMessage *msg );
+
+private:
+ virtual Result execute();
+};
+
+class KDE_EXPORT KMUseTemplateCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMUseTemplateCommand( QWidget *parent, KMMessage *msg );
+
+private:
+ virtual Result execute();
+};
+
+class KDE_EXPORT KMShowMsgSrcCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMShowMsgSrcCommand( QWidget *parent, KMMessage *msg,
+ bool fixedFont );
+ virtual Result execute();
+
+private:
+ bool mFixedFont;
+ bool mMsgWasComplete;
+};
+
+class KDE_EXPORT KMSaveMsgCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMSaveMsgCommand( QWidget *parent, const QPtrList<KMMsgBase> &msgList );
+ KMSaveMsgCommand( QWidget *parent, KMMessage * msg );
+ KURL url();
+
+private:
+ virtual Result execute();
+
+private slots:
+ void slotSaveDataReq();
+ void slotSaveResult(KIO::Job *job);
+ /** the message has been transferred for saving */
+ void slotMessageRetrievedForSaving(KMMessage *msg);
+
+private:
+ static const int MAX_CHUNK_SIZE = 64*1024;
+ KURL mUrl;
+ QValueList<unsigned long> mMsgList;
+ unsigned int mMsgListIndex;
+ KMMessage *mStandAloneMessage;
+ QByteArray mData;
+ int mOffset;
+ size_t mTotalSize;
+ KIO::TransferJob *mJob;
+};
+
+class KDE_EXPORT KMOpenMsgCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMOpenMsgCommand( QWidget *parent, const KURL & url = KURL(),
+ const QString & encoding = QString() );
+
+private:
+ virtual Result execute();
+
+private slots:
+ void slotDataArrived( KIO::Job *job, const QByteArray & data );
+ void slotResult( KIO::Job *job );
+
+private:
+ static const int MAX_CHUNK_SIZE = 64*1024;
+ KURL mUrl;
+ DwString mMsgString;
+ KIO::TransferJob *mJob;
+ const QString mEncoding;
+};
+
+class KDE_EXPORT KMSaveAttachmentsCommand : public KMCommand
+{
+ Q_OBJECT
+public:
+ /** Use this to save all attachments of the given message.
+ @param parent The parent widget of the command used for message boxes.
+ @param msg The message of which the attachments should be saved.
+ */
+ KMSaveAttachmentsCommand( QWidget *parent, KMMessage *msg );
+ /** Use this to save all attachments of the given messages.
+ @param parent The parent widget of the command used for message boxes.
+ @param msgs The messages of which the attachments should be saved.
+ */
+ KMSaveAttachmentsCommand( QWidget *parent, const QPtrList<KMMsgBase>& msgs );
+ /** Use this to save the specified attachments of the given message.
+ @param parent The parent widget of the command used for message
+ boxes.
+ @param attachments The attachments that should be saved.
+ @param msg The message that the attachments belong to.
+ @param encoded True if the transport encoding should not be removed
+ when the attachment is saved.
+ */
+ KMSaveAttachmentsCommand( QWidget *parent, QPtrList<partNode> &attachments,
+ KMMessage *msg, bool encoded = false );
+
+private slots:
+ void slotSaveAll();
+
+private:
+ virtual Result execute();
+ Result saveItem( partNode *node, const KURL& url );
+
+private:
+ PartNodeMessageMap mAttachmentMap;
+ bool mImplicitAttachments;
+ bool mEncoded;
+};
+
+class KDE_EXPORT KMReplyToCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMReplyToCommand( QWidget *parent, KMMessage *msg,
+ const QString &selection = QString::null );
+
+private:
+ virtual Result execute();
+
+private:
+ QString mSelection;
+};
+
+class KDE_EXPORT KMNoQuoteReplyToCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMNoQuoteReplyToCommand( QWidget *parent, KMMessage *msg );
+
+private:
+ virtual Result execute();
+};
+
+class KMReplyListCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMReplyListCommand( QWidget *parent, KMMessage *msg,
+ const QString &selection = QString::null );
+
+private:
+ virtual Result execute();
+
+private:
+ QString mSelection;
+};
+
+class KDE_EXPORT KMReplyToAllCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMReplyToAllCommand( QWidget *parent, KMMessage *msg,
+ const QString &selection = QString::null );
+
+private:
+ virtual Result execute();
+
+private:
+ QString mSelection;
+};
+
+class KDE_EXPORT KMReplyAuthorCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMReplyAuthorCommand( QWidget *parent, KMMessage *msg,
+ const QString &selection = QString::null );
+
+private:
+ virtual Result execute();
+
+private:
+ QString mSelection;
+};
+
+class KDE_EXPORT KMForwardInlineCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMForwardInlineCommand( QWidget *parent, const QPtrList<KMMsgBase> &msgList,
+ uint identity = 0 );
+ KMForwardInlineCommand( QWidget *parent, KMMessage * msg,
+ uint identity = 0 );
+
+private:
+ virtual Result execute();
+
+private:
+ uint mIdentity;
+};
+
+class KDE_EXPORT KMForwardAttachedCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMForwardAttachedCommand( QWidget *parent, const QPtrList<KMMsgBase> &msgList,
+ uint identity = 0, KMail::Composer *win = 0 );
+ KMForwardAttachedCommand( QWidget *parent, KMMessage * msg,
+ uint identity = 0, KMail::Composer *win = 0 );
+
+private:
+ virtual Result execute();
+
+ uint mIdentity;
+ QGuardedPtr<KMail::Composer> mWin;
+};
+
+class KDE_EXPORT KMForwardDigestCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMForwardDigestCommand( QWidget *parent, const QPtrList<KMMsgBase> &msgList,
+ uint identity = 0, KMail::Composer *win = 0 );
+ KMForwardDigestCommand( QWidget *parent, KMMessage * msg,
+ uint identity = 0, KMail::Composer *win = 0 );
+
+private:
+ virtual Result execute();
+
+ uint mIdentity;
+ QGuardedPtr<KMail::Composer> mWin;
+};
+
+class KDE_EXPORT KMRedirectCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMRedirectCommand( QWidget *parent, KMMessage *msg );
+
+private:
+ virtual Result execute();
+};
+
+class KDE_EXPORT KMCustomReplyToCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMCustomReplyToCommand( QWidget *parent, KMMessage *msg,
+ const QString &selection,
+ const QString &tmpl );
+
+private:
+ virtual Result execute();
+
+private:
+ QString mSelection;
+ QString mTemplate;
+};
+
+class KDE_EXPORT KMCustomReplyAllToCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMCustomReplyAllToCommand( QWidget *parent, KMMessage *msg,
+ const QString &selection,
+ const QString &tmpl );
+
+private:
+ virtual Result execute();
+
+private:
+ QString mSelection;
+ QString mTemplate;
+};
+
+class KDE_EXPORT KMCustomForwardCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMCustomForwardCommand( QWidget *parent, const QPtrList<KMMsgBase> &msgList,
+ uint identity, const QString &tmpl );
+ KMCustomForwardCommand( QWidget *parent, KMMessage * msg,
+ uint identity, const QString &tmpl );
+
+private:
+ virtual Result execute();
+
+ uint mIdentity;
+ QString mTemplate;
+};
+
+class KDE_EXPORT KMPrintCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMPrintCommand( QWidget *parent, KMMessage *msg,
+ bool htmlOverride=false,
+ bool htmlLoadExtOverride=false,
+ bool useFixedFont = false,
+ const QString & encoding = QString() );
+
+ void setOverrideFont( const QFont& );
+
+private:
+ virtual Result execute();
+
+ bool mHtmlOverride;
+ bool mHtmlLoadExtOverride;
+ bool mUseFixedFont;
+ QFont mOverrideFont;
+ QString mEncoding;
+};
+
+class KDE_EXPORT KMSetStatusCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ // Serial numbers
+ KMSetStatusCommand( KMMsgStatus status, const QValueList<Q_UINT32> &,
+ bool toggle=false );
+
+private:
+ virtual Result execute();
+
+ KMMsgStatus mStatus;
+ QValueList<Q_UINT32> mSerNums;
+ QValueList<int> mIds;
+ bool mToggle;
+};
+
+class KDE_EXPORT KMFilterCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMFilterCommand( const QCString &field, const QString &value );
+
+private:
+ virtual Result execute();
+
+ QCString mField;
+ QString mValue;
+};
+
+
+class KDE_EXPORT KMFilterActionCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMFilterActionCommand( QWidget *parent,
+ const QPtrList<KMMsgBase> &msgList,
+ KMFilter *filter );
+
+private:
+ virtual Result execute();
+ QValueList<Q_UINT32> serNumList;
+ KMFilter *mFilter;
+};
+
+
+class KDE_EXPORT KMMetaFilterActionCommand : public QObject
+{
+ Q_OBJECT
+
+public:
+ KMMetaFilterActionCommand( KMFilter *filter, KMHeaders *headers,
+ KMMainWidget *main );
+
+public slots:
+ void start();
+
+private:
+ KMFilter *mFilter;
+ KMHeaders *mHeaders;
+ KMMainWidget *mMainWidget;
+};
+
+class KDE_EXPORT FolderShortcutCommand : public QObject
+{
+ Q_OBJECT
+
+public:
+ FolderShortcutCommand( KMMainWidget* mainwidget, KMFolder *folder );
+ ~FolderShortcutCommand();
+
+public slots:
+ void start();
+ /** Assign a KActio to the command which is used to trigger it. This
+ * action will be deleted along with the command, so you don't need to
+ * keep track of it separately. */
+ void setAction( KAction* );
+
+private:
+ KMMainWidget *mMainWidget;
+ KMFolder *mFolder;
+ KAction *mAction;
+};
+
+
+class KDE_EXPORT KMMailingListFilterCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMMailingListFilterCommand( QWidget *parent, KMMessage *msg );
+
+private:
+ virtual Result execute();
+};
+
+
+ /** Returns a popupmenu containing a hierarchy of folder names.
+ Each item in the popupmenu is connected to a slot, if
+ move is TRUE this slot will cause all selected messages to
+ be moved into the given folder, otherwise messages will be
+ copied.
+ Am empty KMMenuToFolder must be passed in. */
+
+class KDE_EXPORT KMMenuCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ static void folderToPopupMenu(bool move, QObject *receiver,
+ KMMenuToFolder *aMenuToFolder, QPopupMenu *menu );
+
+ static void makeFolderMenu(KMFolderNode* item, bool move,
+ QObject *receiver, KMMenuToFolder *aMenuToFolder, QPopupMenu *menu );
+};
+
+class KDE_EXPORT KMCopyCommand : public KMMenuCommand
+{
+ Q_OBJECT
+
+public:
+ KMCopyCommand( KMFolder* destFolder,
+ const QPtrList<KMMsgBase> &msgList );
+ KMCopyCommand( KMFolder* destFolder, KMMessage *msg );
+
+protected slots:
+ void slotJobFinished( KMail::FolderJob *job );
+
+ void slotFolderComplete( KMFolderImap*, bool success );
+
+private:
+ virtual Result execute();
+
+ KMFolder *mDestFolder;
+ QPtrList<KMMsgBase> mMsgList;
+ QValueList<KMail::FolderJob*> mPendingJobs;
+};
+
+namespace KPIM {
+ class ProgressItem;
+}
+class KDE_EXPORT KMMoveCommand : public KMMenuCommand
+{
+ Q_OBJECT
+
+public:
+ KMMoveCommand( KMFolder* destFolder, const QPtrList<KMMsgBase> &msgList );
+ KMMoveCommand( KMFolder* destFolder, KMMessage * msg );
+ KMMoveCommand( KMFolder* destFolder, KMMsgBase * msgBase );
+ KMFolder* destFolder() const { return mDestFolder; }
+
+public slots:
+ void slotImapFolderCompleted(KMFolderImap *folder, bool success);
+ void slotMsgAddedToDestFolder(KMFolder *folder, Q_UINT32 serNum);
+ void slotMoveCanceled();
+
+protected:
+ // Needed for KMDeleteCommand for "move to trash"
+ KMMoveCommand( Q_UINT32 sernum );
+ void setDestFolder( KMFolder* folder ) { mDestFolder = folder; }
+ void addMsg( KMMsgBase *msg ) { mSerNumList.append( msg->getMsgSerNum() ); }
+ QValueVector<KMFolder*> mOpenedFolders;
+
+private:
+ virtual Result execute();
+ void completeMove( Result result );
+
+ KMFolder *mDestFolder;
+ QValueList<Q_UINT32> mSerNumList;
+ // List of serial numbers that have to be transferred to a host.
+ // Ticked off as they come in via msgAdded signals.
+ QValueList<Q_UINT32> mLostBoys;
+ KPIM::ProgressItem *mProgressItem;
+ bool mCompleteWithAddedMsg;
+};
+
+class KDE_EXPORT KMDeleteMsgCommand : public KMMoveCommand
+{
+ Q_OBJECT
+
+public:
+ KMDeleteMsgCommand( KMFolder* srcFolder, const QPtrList<KMMsgBase> &msgList );
+ KMDeleteMsgCommand( KMFolder* srcFolder, KMMessage * msg );
+ KMDeleteMsgCommand( Q_UINT32 sernum );
+
+private:
+ static KMFolder * findTrashFolder( KMFolder * srcFolder );
+
+};
+
+class KDE_EXPORT KMUrlClickedCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMUrlClickedCommand( const KURL &url, uint identity,
+ KMReaderWin *readerWin, bool mHtmlPref, KMMainWidget *mainWidget = 0 );
+
+private:
+ virtual Result execute();
+
+ KURL mUrl;
+ uint mIdentity;
+ KMReaderWin *mReaderWin;
+ bool mHtmlPref;
+ KMMainWidget *mMainWidget;
+};
+
+class KDE_EXPORT KMLoadPartsCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMLoadPartsCommand( QPtrList<partNode>& parts, KMMessage* msg );
+ KMLoadPartsCommand( partNode* node, KMMessage* msg );
+ KMLoadPartsCommand( PartNodeMessageMap& partMap );
+
+public slots:
+ void slotPartRetrieved( KMMessage* msg, QString partSpecifier );
+
+signals:
+ void partsRetrieved();
+
+private:
+ // Retrieve parts then calls execute
+ virtual void slotStart();
+
+ virtual Result execute();
+
+ int mNeedsRetrieval;
+ PartNodeMessageMap mPartMap;
+};
+
+class KDE_EXPORT KMResendMessageCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMResendMessageCommand( QWidget *parent, KMMessage *msg=0 );
+
+private:
+ virtual Result execute();
+};
+
+class KDE_EXPORT KMMailingListCommand : public KMCommand
+{
+ Q_OBJECT
+public:
+ KMMailingListCommand( QWidget *parent, KMFolder *folder );
+private:
+ virtual Result execute();
+private slots:
+ void commandCompleted( KMCommand *command );
+protected:
+ virtual KURL::List urls() const =0;
+protected:
+ KMFolder *mFolder;
+};
+
+class KDE_EXPORT KMMailingListPostCommand : public KMMailingListCommand
+{
+ Q_OBJECT
+public:
+ KMMailingListPostCommand( QWidget *parent, KMFolder *folder );
+protected:
+ virtual KURL::List urls() const;
+};
+
+class KDE_EXPORT KMMailingListSubscribeCommand : public KMMailingListCommand
+{
+ Q_OBJECT
+public:
+ KMMailingListSubscribeCommand( QWidget *parent, KMFolder *folder );
+protected:
+ virtual KURL::List urls() const;
+};
+
+class KDE_EXPORT KMMailingListUnsubscribeCommand : public KMMailingListCommand
+{
+ Q_OBJECT
+public:
+ KMMailingListUnsubscribeCommand( QWidget *parent, KMFolder *folder );
+protected:
+ virtual KURL::List urls() const;
+};
+
+class KDE_EXPORT KMMailingListArchivesCommand : public KMMailingListCommand
+{
+ Q_OBJECT
+public:
+ KMMailingListArchivesCommand( QWidget *parent, KMFolder *folder );
+protected:
+ virtual KURL::List urls() const;
+};
+
+class KDE_EXPORT KMMailingListHelpCommand : public KMMailingListCommand
+{
+ Q_OBJECT
+public:
+ KMMailingListHelpCommand( QWidget *parent, KMFolder *folder );
+protected:
+ virtual KURL::List urls() const;
+};
+
+class KDE_EXPORT KMIMChatCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ KMIMChatCommand( const KURL &url, KMMessage *msg=0 );
+
+private:
+ /**
+ * Try to start a chat with the addressee associated the mail address in <i>url</i>.
+ * If there is no addressee for the email address, or more than one, a KMessageBox is shown to inform the user.
+ * If the addressee does not have instant messaging address(es), this is currently handled by the instant messaging client
+ * which handles the request, since we don't have a convenient API for extracting them using KABC.
+ */
+ virtual Result execute();
+
+ KURL mUrl;
+ KMMessage *mMessage;
+};
+
+class KDE_EXPORT KMHandleAttachmentCommand : public KMCommand
+{
+ Q_OBJECT
+
+public:
+ /** Supported types of attachment handling */
+ enum AttachmentAction
+ {
+ Open = 1,
+ OpenWith = 2,
+ View = 3,
+ Save = 4,
+ Properties = 5,
+ ChiasmusEncrypt = 6
+ };
+ /**
+ * Construct a new command
+ * @param node the partNode
+ * @param msg the KMMessage
+ * @param atmId the ID of the attachment, the partNode must know this
+ * @param atmName the name of the attachment
+ * @param action what to do with the attachment
+ * @param offer specify a KService that should handle the "open" action, 0 otherwise
+ */
+ KMHandleAttachmentCommand( partNode* node, KMMessage* msg, int atmId,
+ const QString& atmName, AttachmentAction action, KService::Ptr offer, QWidget* parent );
+
+
+signals:
+ void showAttachment( int id, const QString& name );
+
+private:
+ virtual Result execute();
+
+ QString createAtmFileLink() const;
+
+ /** Get a KService if it was not specified */
+ KService::Ptr getServiceOffer();
+
+ /** Open needs a valid KService */
+ void atmOpen();
+
+ /** Display an open-with dialog */
+ void atmOpenWith();
+
+ /**
+ * Viewing is not supported by this command
+ * so it just emits showAttachment
+ */
+ void atmView();
+
+ /** Save the attachment */
+ void atmSave();
+
+ /** Display the properties */
+ void atmProperties();
+
+ /** Encrypt using chiasmus */
+ void atmEncryptWithChiasmus();
+
+private slots:
+ /** Called from start() via a single shot timer. */
+ virtual void slotStart();
+
+ /**
+ * Called when the part was downloaded
+ * Calls execute
+ */
+ void slotPartComplete();
+
+ void slotAtmDecryptWithChiasmusResult( const GpgME::Error &, const QVariant & );
+ void slotAtmDecryptWithChiasmusUploadResult( KIO::Job * );
+
+private:
+ partNode* mNode;
+ KMMessage* mMsg;
+ int mAtmId;
+ QString mAtmName;
+ AttachmentAction mAction;
+ KService::Ptr mOffer;
+ Kleo::SpecialJob *mJob;
+
+};
+
+
+/** Base class for commands modifying attachements of existing messages. */
+class KDE_EXPORT AttachmentModifyCommand : public KMCommand
+{
+ Q_OBJECT
+ public:
+ AttachmentModifyCommand( partNode *node, KMMessage *msg, QWidget *parent );
+ ~AttachmentModifyCommand();
+
+ protected:
+ void storeChangedMessage( KMMessage* msg );
+ DwBodyPart* findPart( KMMessage* msg, int index );
+ virtual Result doAttachmentModify() = 0;
+
+ protected:
+ int mPartIndex;
+ Q_UINT32 mSernum;
+
+ private:
+ Result execute();
+ DwBodyPart* findPartInternal( DwEntity* root, int index, int &accu );
+
+ private slots:
+ void messageStoreResult( KMFolderImap* folder, bool success );
+ void messageDeleteResult( KMCommand *cmd );
+
+ private:
+ QGuardedPtr<KMFolder> mFolder;
+};
+
+class KDE_EXPORT KMDeleteAttachmentCommand : public AttachmentModifyCommand
+{
+ Q_OBJECT
+ public:
+ KMDeleteAttachmentCommand( partNode *node, KMMessage *msg, QWidget *parent );
+ ~KMDeleteAttachmentCommand();
+
+ protected:
+ Result doAttachmentModify();
+};
+
+
+class KDE_EXPORT KMEditAttachmentCommand : public AttachmentModifyCommand
+{
+ Q_OBJECT
+ public:
+ KMEditAttachmentCommand( partNode *node, KMMessage *msg, QWidget *parent );
+ ~KMEditAttachmentCommand();
+
+ protected:
+ Result doAttachmentModify();
+
+ private slots:
+ void editDone( KMail::EditorWatcher *watcher );
+
+ private:
+ KTempFile mTempFile;
+};
+
+class KDE_EXPORT CreateTodoCommand : public KMCommand
+{
+ Q_OBJECT
+ public:
+ CreateTodoCommand( QWidget *parent, KMMessage *msg );
+
+ private:
+ Result execute();
+};
+
+#endif /*KMCommands_h*/
diff --git a/kmail/kmcomposerui.rc b/kmail/kmcomposerui.rc
new file mode 100644
index 00000000..8e015e3d
--- /dev/null
+++ b/kmail/kmcomposerui.rc
@@ -0,0 +1,115 @@
+<!DOCTYPE kpartgui ><kpartgui version="32" name="kmcomposer" >
+ <MenuBar>
+ <Menu noMerge="1" name="file" >
+ <text>&amp;Message</text>
+ <Action name="new_composer" />
+ <Action name="open_mailreader" />
+ <Separator/>
+ <Action name="send_default" />
+ <Action name="send_default_via" />
+ <Action name="send_alternative" />
+ <Action name="send_alternative_via" />
+ <Action name="save_in_drafts" />
+ <Action name="save_in_templates" />
+ <Separator/>
+ <Action name="insert_file" />
+ <Action name="insert_file_recent" />
+ <Action name="file_print" />
+ <Separator/>
+ <Action name="file_close" />
+ </Menu>
+ <Menu name="edit" >
+ <text>&amp;Edit</text>
+ <Action name="paste_quoted" append="edit_paste_merge"/>
+ <Action name="paste_att" append="edit_paste_merge"/>
+ <Action name="clean_spaces" />
+ <Action name="append_signature" />
+ <Action name="prepend_signature" />
+ <Action name="insert_signature_at_cursor_position" />
+ <Separator/>
+ <Action name="tools_quote"/>
+ <Action name="tools_unquote"/>
+ </Menu>
+ <Menu noMerge="1" name="options" >
+ <text>&amp;Options</text>
+ <Action name="urgent" />
+ <Action name="options_request_mdn" />
+ <Separator/>
+ <Action name="sign_message" />
+ <Action name="encrypt_message" />
+ <Action name="encrypt_message_chiasmus"/>
+ <Separator/>
+ <Action name="options_select_crypto" />
+ <Action name="html"/>
+ <Action name="charsets" />
+ <Action name="wordwrap" />
+ <Action name="options_auto_spellchecking" />
+ </Menu>
+ <Menu noMerge="1" name="view" >
+ <text>&amp;View</text>
+ <Action name="show_all_fields" />
+ <Separator/>
+ <Action name="show_identity" />
+ <Action name="show_dictionary" />
+ <Action name="show_fcc" />
+ <Action name="show_transport" />
+ <Separator/>
+ <Action name="show_from" />
+ <Action name="show_reply_to" />
+ <Action name="show_to" />
+ <Action name="show_cc" />
+ <Action name="show_bcc" />
+ <Action name="show_subject" />
+ <Separator/>
+ <Action name="toggle_fixedfont" />
+ <Action name="snippets" />
+ </Menu>
+ <Menu name="attach" >
+ <text>&amp;Attach</text>
+ <Action name="attach_public_key" />
+ <Action name="attach_my_public_key" />
+ <Separator/>
+ <Action name="attach" />
+ <Action name="remove" />
+ <Action name="attach_save" />
+ <Action name="attach_properties" />
+ </Menu>
+ <Menu name="tools">
+ <Action name="spellcheck" />
+ <Action name="select_recipients"/>
+ <Action name="addressbook"/>
+ <Action name="save_distribution_list"/>
+ </Menu>
+ <Menu name="settings" >
+ <text>&amp;Settings</text>
+ <Action name="setup_spellchecker" append="save_merge"/>
+ </Menu>
+ </MenuBar>
+ <ToolBar noMerge="1" name="mainToolBar" fullWidth="true" ><text>Main Toolbar</text>
+ <Action name="send_default_via" />
+ <Action name="send_alternative_via" />
+ <Separator/>
+ <Action name="attach" />
+ <Separator/>
+ <Action name="edit_cut" />
+ <Action name="edit_copy" />
+ <Action name="edit_paste" />
+ <Separator/>
+ <Action name="sign_message" />
+ <Action name="encrypt_message" />
+ <Action name="options_select_crypto" />
+ </ToolBar>
+ <ToolBar noMerge="1" enable="0" name="htmlToolBar" ><text>HTML Toolbar</text>
+ <Action name="text_list" />
+ <Action name="text_font" />
+ <Action name="text_size" />
+ <Action name="format_reset" />
+ <Action name="text_bold" />
+ <Action name="text_italic" />
+ <Action name="text_under" />
+ <Action name="format_color" />
+ <Action name="align_left" />
+ <Action name="align_center" />
+ <Action name="align_right" />
+ </ToolBar>
+</kpartgui>
diff --git a/kmail/kmcomposewin.cpp b/kmail/kmcomposewin.cpp
new file mode 100644
index 00000000..8e9eb6b1
--- /dev/null
+++ b/kmail/kmcomposewin.cpp
@@ -0,0 +1,5233 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+// kmcomposewin.cpp
+// Author: Markus Wuebben <markus.wuebben@kde.org>
+// This code is published under the GPL.
+
+#undef GrayScale
+#undef Color
+#include <config.h>
+
+#define REALLY_WANT_KMCOMPOSEWIN_H
+#include "kmcomposewin.h"
+#undef REALLY_WANT_KMCOMPOSEWIN_H
+
+#include "kmedit.h"
+#include "kmlineeditspell.h"
+#include "kmatmlistview.h"
+
+#include "kmmainwin.h"
+#include "kmreadermainwin.h"
+#include "messagesender.h"
+#include "kmmsgpartdlg.h"
+#include <kpgpblock.h>
+#include <kaddrbook.h>
+#include "kmaddrbook.h"
+#include "kmmsgdict.h"
+#include "kmfolderimap.h"
+#include "kmfoldermgr.h"
+#include "kmfoldercombobox.h"
+#include "kmtransport.h"
+#include "kmcommands.h"
+#include "kcursorsaver.h"
+#include "partNode.h"
+#include "encodingdetector.h"
+#include "attachmentlistview.h"
+#include "transportmanager.h"
+using KMail::AttachmentListView;
+#include "dictionarycombobox.h"
+using KMail::DictionaryComboBox;
+#include "addressesdialog.h"
+using KPIM::AddressesDialog;
+#include "addresseeemailselection.h"
+using KPIM::AddresseeEmailSelection;
+using KPIM::AddresseeSelectorDialog;
+#include <maillistdrag.h>
+using KPIM::MailListDrag;
+#include "recentaddresses.h"
+using KRecentAddress::RecentAddresses;
+#include "kleo_util.h"
+#include "stl_util.h"
+#include "recipientseditor.h"
+#include "editorwatcher.h"
+
+#include "attachmentcollector.h"
+#include "objecttreeparser.h"
+
+#include "kmfoldermaildir.h"
+
+#include <libkpimidentities/identitymanager.h>
+#include <libkpimidentities/identitycombo.h>
+#include <libkpimidentities/identity.h>
+#include <libkdepim/kfileio.h>
+#include <libemailfunctions/email.h>
+#include <kleo/cryptobackendfactory.h>
+#include <kleo/exportjob.h>
+#include <kleo/specialjob.h>
+#include <ui/progressdialog.h>
+#include <ui/keyselectiondialog.h>
+
+#include <gpgmepp/context.h>
+#include <gpgmepp/key.h>
+
+#include <kabc/vcardconverter.h>
+#include <libkdepim/kvcarddrag.h>
+#include <kio/netaccess.h>
+
+#include "klistboxdialog.h"
+
+#include "messagecomposer.h"
+#include "chiasmuskeyselector.h"
+
+#include <kcharsets.h>
+#include <kcompletionbox.h>
+#include <kcursor.h>
+#include <kcombobox.h>
+#include <kstdaccel.h>
+#include <kpopupmenu.h>
+#include <kedittoolbar.h>
+#include <kkeydialog.h>
+#include <kdebug.h>
+#include <kfiledialog.h>
+#include <kwin.h>
+#include <kinputdialog.h>
+#include <kmessagebox.h>
+#include <kurldrag.h>
+#include <kio/scheduler.h>
+#include <ktempfile.h>
+#include <klocale.h>
+#include <kapplication.h>
+#include <kstatusbar.h>
+#include <kaction.h>
+#include <kstdaction.h>
+#include <kdirwatch.h>
+#include <kstdguiitem.h>
+#include <kiconloader.h>
+#include <kpushbutton.h>
+#include <kuserprofile.h>
+#include <krun.h>
+#include <ktempdir.h>
+#include <kstandarddirs.h>
+//#include <keditlistbox.h>
+#include "globalsettings.h"
+#include "replyphrases.h"
+
+#include <kspell.h>
+#include <kspelldlg.h>
+#include <spellingfilter.h>
+#include <ksyntaxhighlighter.h>
+#include <kcolordialog.h>
+#include <kzip.h>
+#include <ksavefile.h>
+
+#include <qtabdialog.h>
+#include <qregexp.h>
+#include <qbuffer.h>
+#include <qtooltip.h>
+#include <qtextcodec.h>
+#include <qheader.h>
+#include <qwhatsthis.h>
+#include <qfontdatabase.h>
+
+#include <mimelib/mimepp.h>
+
+#include <algorithm>
+#include <memory>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+
+#include "kmcomposewin.moc"
+
+#include "snippetwidget.h"
+
+KMail::Composer * KMail::makeComposer( KMMessage * msg, uint identitiy ) {
+ return KMComposeWin::create( msg, identitiy );
+}
+
+KMail::Composer * KMComposeWin::create( KMMessage * msg, uint identitiy ) {
+ return new KMComposeWin( msg, identitiy );
+}
+
+//-----------------------------------------------------------------------------
+KMComposeWin::KMComposeWin( KMMessage *aMsg, uint id )
+ : MailComposerIface(), KMail::Composer( "kmail-composer#" ),
+ mSpellCheckInProgress( false ),
+ mDone( false ),
+ mAtmModified( false ),
+ mMsg( 0 ),
+ mAttachMenu( 0 ),
+ mSigningAndEncryptionExplicitlyDisabled( false ),
+ mFolder( 0 ),
+ mUseHTMLEditor( false ),
+ mId( id ),
+ mAttachPK( 0 ), mAttachMPK( 0 ),
+ mAttachRemoveAction( 0 ), mAttachSaveAction( 0 ), mAttachPropertiesAction( 0 ),
+ mAppendSignatureAction( 0 ), mPrependSignatureAction( 0 ), mInsertSignatureAction( 0 ),
+ mSignAction( 0 ), mEncryptAction( 0 ), mRequestMDNAction( 0 ),
+ mUrgentAction( 0 ), mAllFieldsAction( 0 ), mFromAction( 0 ),
+ mReplyToAction( 0 ), mToAction( 0 ), mCcAction( 0 ), mBccAction( 0 ),
+ mSubjectAction( 0 ),
+ mIdentityAction( 0 ), mTransportAction( 0 ), mFccAction( 0 ),
+ mWordWrapAction( 0 ), mFixedFontAction( 0 ), mAutoSpellCheckingAction( 0 ),
+ mDictionaryAction( 0 ), mSnippetAction( 0 ),
+ mEncodingAction( 0 ),
+ mCryptoModuleAction( 0 ),
+ mEncryptChiasmusAction( 0 ),
+ mEncryptWithChiasmus( false ),
+ mComposer( 0 ),
+ mLabelWidth( 0 ),
+ mAutoSaveTimer( 0 ), mLastAutoSaveErrno( 0 ),
+ mSignatureStateIndicator( 0 ), mEncryptionStateIndicator( 0 ),
+ mPreserveUserCursorPosition( false )
+{
+ mClassicalRecipients = GlobalSettings::self()->recipientsEditorType() ==
+ GlobalSettings::EnumRecipientsEditorType::Classic;
+
+ mSubjectTextWasSpellChecked = false;
+ if (kmkernel->xmlGuiInstance())
+ setInstance( kmkernel->xmlGuiInstance() );
+ mMainWidget = new QWidget(this);
+ // splitter between the headers area and the actual editor
+ mHeadersToEditorSplitter = new QSplitter( Qt::Vertical, mMainWidget, "mHeadersToEditorSplitter" );
+ mHeadersToEditorSplitter->setChildrenCollapsible( false );
+ mHeadersArea = new QWidget( mHeadersToEditorSplitter );
+ mHeadersArea->setSizePolicy( mHeadersToEditorSplitter->sizePolicy().horData(), QSizePolicy::Maximum );
+ QVBoxLayout *v = new QVBoxLayout( mMainWidget );
+ v->addWidget( mHeadersToEditorSplitter );
+ mIdentity = new KPIM::IdentityCombo(kmkernel->identityManager(), mHeadersArea);
+ mDictionaryCombo = new DictionaryComboBox( mHeadersArea );
+ mFcc = new KMFolderComboBox(mHeadersArea);
+ mFcc->showOutboxFolder( false );
+ mTransport = new QComboBox(true, mHeadersArea);
+ mEdtFrom = new KMLineEdit(false,mHeadersArea, "fromLine");
+
+ mEdtReplyTo = new KMLineEdit(true,mHeadersArea, "replyToLine");
+ mLblReplyTo = new QLabel(mHeadersArea);
+ mBtnReplyTo = new QPushButton("...",mHeadersArea);
+ mBtnReplyTo->setFocusPolicy(QWidget::NoFocus);
+ connect(mBtnReplyTo,SIGNAL(clicked()),SLOT(slotAddrBookReplyTo()));
+ connect(mEdtReplyTo,SIGNAL(completionModeChanged(KGlobalSettings::Completion)),
+ SLOT(slotCompletionModeChanged(KGlobalSettings::Completion)));
+
+ if ( mClassicalRecipients ) {
+ mRecipientsEditor = 0;
+
+ mEdtTo = new KMLineEdit(true,mHeadersArea, "toLine");
+ mEdtCc = new KMLineEdit(true,mHeadersArea, "ccLine");
+ mEdtBcc = new KMLineEdit(true,mHeadersArea, "bccLine");
+
+ mLblTo = new QLabel(mHeadersArea);
+ mLblCc = new QLabel(mHeadersArea);
+ mLblBcc = new QLabel(mHeadersArea);
+
+ mBtnTo = new QPushButton("...",mHeadersArea);
+ mBtnCc = new QPushButton("...",mHeadersArea);
+ mBtnBcc = new QPushButton("...",mHeadersArea);
+ //mBtnFrom = new QPushButton("...",mHeadersArea);
+
+ QString tip = i18n("Select email address(es)");
+ QToolTip::add( mBtnTo, tip );
+ QToolTip::add( mBtnCc, tip );
+ QToolTip::add( mBtnBcc, tip );
+ QToolTip::add( mBtnReplyTo, tip );
+
+ mBtnTo->setFocusPolicy(QWidget::NoFocus);
+ mBtnCc->setFocusPolicy(QWidget::NoFocus);
+ mBtnBcc->setFocusPolicy(QWidget::NoFocus);
+ //mBtnFrom->setFocusPolicy(QWidget::NoFocus);
+
+ connect(mBtnTo,SIGNAL(clicked()),SLOT(slotAddrBookTo()));
+ connect(mBtnCc,SIGNAL(clicked()),SLOT(slotAddrBookTo()));
+ connect(mBtnBcc,SIGNAL(clicked()),SLOT(slotAddrBookTo()));
+ //connect(mBtnFrom,SIGNAL(clicked()),SLOT(slotAddrBookFrom()));
+
+ connect(mEdtTo,SIGNAL(completionModeChanged(KGlobalSettings::Completion)),
+ SLOT(slotCompletionModeChanged(KGlobalSettings::Completion)));
+ connect(mEdtCc,SIGNAL(completionModeChanged(KGlobalSettings::Completion)),
+ SLOT(slotCompletionModeChanged(KGlobalSettings::Completion)));
+ connect(mEdtBcc,SIGNAL(completionModeChanged(KGlobalSettings::Completion)),
+ SLOT(slotCompletionModeChanged(KGlobalSettings::Completion)));
+
+ mEdtTo->setFocus();
+ } else {
+ mEdtTo = 0;
+ mEdtCc = 0;
+ mEdtBcc = 0;
+
+ mLblTo = 0;
+ mLblCc = 0;
+ mLblBcc = 0;
+
+ mBtnTo = 0;
+ mBtnCc = 0;
+ mBtnBcc = 0;
+ //mBtnFrom = 0;
+
+ mRecipientsEditor = new RecipientsEditor( mHeadersArea );
+ connect( mRecipientsEditor,
+ SIGNAL( completionModeChanged( KGlobalSettings::Completion ) ),
+ SLOT( slotCompletionModeChanged( KGlobalSettings::Completion ) ) );
+ connect( mRecipientsEditor, SIGNAL(sizeHintChanged()), SLOT(recipientEditorSizeHintChanged()) );
+
+ mRecipientsEditor->setFocus();
+ }
+ mEdtSubject = new KMLineEditSpell(false,mHeadersArea, "subjectLine");
+ mLblIdentity = new QLabel(mHeadersArea);
+ mDictionaryLabel = new QLabel( mHeadersArea );
+ mLblFcc = new QLabel(mHeadersArea);
+ mLblTransport = new QLabel(mHeadersArea);
+ mLblFrom = new QLabel(mHeadersArea);
+ mLblSubject = new QLabel(mHeadersArea);
+ QString sticky = i18n("Sticky");
+ mBtnIdentity = new QCheckBox(sticky,mHeadersArea);
+ mBtnFcc = new QCheckBox(sticky,mHeadersArea);
+ mBtnTransport = new QCheckBox(sticky,mHeadersArea);
+
+ //setWFlags( WType_TopLevel | WStyle_Dialog );
+ mHtmlMarkup = GlobalSettings::self()->useHtmlMarkup();
+ mShowHeaders = GlobalSettings::self()->headers();
+ mDone = false;
+ mGrid = 0;
+ mAtmListView = 0;
+ mAtmList.setAutoDelete(true);
+ mAtmTempList.setAutoDelete(true);
+ mAtmModified = false;
+ mAutoDeleteMsg = false;
+ mFolder = 0;
+ mAutoCharset = true;
+ mFixedFontAction = 0;
+ mTempDir = 0;
+ // the attachment view is separated from the editor by a splitter
+ mSplitter = new QSplitter( Qt::Vertical, mHeadersToEditorSplitter, "mSplitter" );
+ mSplitter->setChildrenCollapsible( false );
+ mSnippetSplitter = new QSplitter( Qt::Horizontal, mSplitter, "mSnippetSplitter");
+ mSnippetSplitter->setChildrenCollapsible( false );
+
+ QWidget *editorAndCryptoStateIndicators = new QWidget( mSnippetSplitter );
+ QVBoxLayout *vbox = new QVBoxLayout( editorAndCryptoStateIndicators );
+ QHBoxLayout *hbox = new QHBoxLayout( vbox );
+ {
+ mSignatureStateIndicator = new QLabel( editorAndCryptoStateIndicators );
+ mSignatureStateIndicator->setAlignment( Qt::AlignHCenter );
+ hbox->addWidget( mSignatureStateIndicator );
+
+ KConfigGroup reader( KMKernel::config(), "Reader" );
+ QPalette p( mSignatureStateIndicator->palette() );
+
+ QColor defaultSignedColor( 0x40, 0xFF, 0x40 ); // light green // pgp ok, trusted key
+ QColor defaultEncryptedColor( 0x00, 0x80, 0xFF ); // light blue // pgp encrypted
+ p.setColor( QColorGroup::Background, reader.readColorEntry( "PGPMessageOkKeyOk", &defaultSignedColor ) );
+ mSignatureStateIndicator->setPalette( p );
+
+ mEncryptionStateIndicator = new QLabel( editorAndCryptoStateIndicators );
+ mEncryptionStateIndicator->setAlignment( Qt::AlignHCenter );
+ hbox->addWidget( mEncryptionStateIndicator );
+ p.setColor( QColorGroup::Background, reader.readColorEntry( "PGPMessageEncr" , &defaultEncryptedColor ) );
+ mEncryptionStateIndicator->setPalette( p );
+ }
+
+ mEditor = new KMEdit( editorAndCryptoStateIndicators, this, mDictionaryCombo->spellConfig() );
+ vbox->addWidget( mEditor );
+
+ mSnippetWidget = new SnippetWidget( mEditor, actionCollection(), mSnippetSplitter );
+ mSnippetWidget->setShown( GlobalSettings::self()->showSnippetManager() );
+
+ // mSplitter->moveToFirst( editorAndCryptoStateIndicators );
+ mSplitter->setOpaqueResize( true );
+
+ mEditor->initializeAutoSpellChecking();
+ mEditor->setTextFormat(Qt::PlainText);
+ mEditor->setAcceptDrops( true );
+
+ QWhatsThis::add( mBtnIdentity,
+ GlobalSettings::self()->stickyIdentityItem()->whatsThis() );
+ QWhatsThis::add( mBtnFcc,
+ GlobalSettings::self()->stickyFccItem()->whatsThis() );
+ QWhatsThis::add( mBtnTransport,
+ GlobalSettings::self()->stickyTransportItem()->whatsThis() );
+
+ mSpellCheckInProgress=false;
+
+ setCaption( i18n("Composer") );
+ setMinimumSize(200,200);
+
+ mBtnIdentity->setFocusPolicy(QWidget::NoFocus);
+ mBtnFcc->setFocusPolicy(QWidget::NoFocus);
+ mBtnTransport->setFocusPolicy(QWidget::NoFocus);
+
+ mAtmListView = new AttachmentListView( this, mSplitter,
+ "attachment list view" );
+ mAtmListView->setSelectionMode( QListView::Extended );
+ mAtmListView->addColumn( i18n("Name"), 200 );
+ mAtmListView->addColumn( i18n("Size"), 80 );
+ mAtmListView->addColumn( i18n("Encoding"), 120 );
+ int atmColType = mAtmListView->addColumn( i18n("Type"), 120 );
+ // Stretch "Type".
+ mAtmListView->header()->setStretchEnabled( true, atmColType );
+ mAtmEncryptColWidth = 80;
+ mAtmSignColWidth = 80;
+ mAtmCompressColWidth = 100;
+ mAtmColCompress = mAtmListView->addColumn( i18n("Compress"),
+ mAtmCompressColWidth );
+ mAtmColEncrypt = mAtmListView->addColumn( i18n("Encrypt"),
+ mAtmEncryptColWidth );
+ mAtmColSign = mAtmListView->addColumn( i18n("Sign"),
+ mAtmSignColWidth );
+ mAtmListView->setColumnWidth( mAtmColEncrypt, 0 );
+ mAtmListView->setColumnWidth( mAtmColSign, 0 );
+ mAtmListView->setAllColumnsShowFocus( true );
+
+ connect( mAtmListView,
+ SIGNAL( doubleClicked( QListViewItem* ) ),
+ SLOT( slotAttachEdit() ) );
+ connect( mAtmListView,
+ SIGNAL( rightButtonPressed( QListViewItem*, const QPoint&, int ) ),
+ SLOT( slotAttachPopupMenu( QListViewItem*, const QPoint&, int ) ) );
+ connect( mAtmListView,
+ SIGNAL( selectionChanged() ),
+ SLOT( slotUpdateAttachActions() ) );
+ connect( mAtmListView,
+ SIGNAL( attachmentDeleted() ),
+ SLOT( slotAttachRemove() ) );
+ connect( mAtmListView,
+ SIGNAL( dragStarted() ),
+ SLOT( slotAttachmentDragStarted() ) );
+ mAttachMenu = 0;
+
+ readConfig();
+ setupStatusBar();
+ setupActions();
+ setupEditor();
+ slotUpdateSignatureAndEncrypionStateIndicators();
+
+ applyMainWindowSettings(KMKernel::config(), "Composer");
+
+ connect( mEdtSubject, SIGNAL( subjectTextSpellChecked() ),
+ SLOT( slotSubjectTextSpellChecked() ) );
+ connect(mEdtSubject,SIGNAL(textChanged(const QString&)),
+ SLOT(slotUpdWinTitle(const QString&)));
+ connect(mIdentity,SIGNAL(identityChanged(uint)),
+ SLOT(slotIdentityChanged(uint)));
+ connect( kmkernel->identityManager(), SIGNAL(changed(uint)),
+ SLOT(slotIdentityChanged(uint)));
+
+ connect(mEdtFrom,SIGNAL(completionModeChanged(KGlobalSettings::Completion)),
+ SLOT(slotCompletionModeChanged(KGlobalSettings::Completion)));
+ connect(kmkernel->folderMgr(),SIGNAL(folderRemoved(KMFolder*)),
+ SLOT(slotFolderRemoved(KMFolder*)));
+ connect(kmkernel->imapFolderMgr(),SIGNAL(folderRemoved(KMFolder*)),
+ SLOT(slotFolderRemoved(KMFolder*)));
+ connect(kmkernel->dimapFolderMgr(),SIGNAL(folderRemoved(KMFolder*)),
+ SLOT(slotFolderRemoved(KMFolder*)));
+ connect( kmkernel, SIGNAL( configChanged() ),
+ this, SLOT( slotConfigChanged() ) );
+
+ connect (mEditor, SIGNAL (spellcheck_done(int)),
+ this, SLOT (slotSpellcheckDone (int)));
+ connect (mEditor, SIGNAL( attachPNGImageData(const QByteArray &) ),
+ this, SLOT ( slotAttachPNGImageData(const QByteArray &) ) );
+ connect (mEditor, SIGNAL( focusChanged(bool) ),
+ this, SLOT (editorFocusChanged(bool)) );
+
+ mMainWidget->resize(480,510);
+ setCentralWidget(mMainWidget);
+ rethinkFields();
+
+ if ( !mClassicalRecipients ) {
+ // This is ugly, but if it isn't called the line edits in the recipients
+ // editor aren't wide enough until the first resize event comes.
+ rethinkFields();
+ }
+
+ if ( GlobalSettings::self()->useExternalEditor() ) {
+ mEditor->setUseExternalEditor(true);
+ mEditor->setExternalEditorPath( GlobalSettings::self()->externalEditor() );
+ }
+
+ initAutoSave();
+ slotUpdateSignatureActions();
+ mMsg = 0;
+ if (aMsg)
+ setMsg(aMsg);
+ fontChanged( mEditor->currentFont() ); // set toolbar buttons to correct values
+
+ mDone = true;
+}
+
+//-----------------------------------------------------------------------------
+KMComposeWin::~KMComposeWin()
+{
+ writeConfig();
+ if (mFolder && mMsg)
+ {
+ mAutoDeleteMsg = false;
+ mFolder->addMsg(mMsg);
+ // Ensure that the message is correctly and fully parsed
+ mFolder->unGetMsg( mFolder->count() - 1 );
+ }
+ if (mAutoDeleteMsg) {
+ delete mMsg;
+ mMsg = 0;
+ }
+ QMap<KIO::Job*, atmLoadData>::Iterator it = mMapAtmLoadData.begin();
+ while ( it != mMapAtmLoadData.end() )
+ {
+ KIO::Job *job = it.key();
+ mMapAtmLoadData.remove( it );
+ job->kill();
+ it = mMapAtmLoadData.begin();
+ }
+ deleteAll( mComposedMessages );
+
+ for ( std::set<KTempDir*>::iterator it = mTempDirs.begin() ; it != mTempDirs.end() ; ++it ) {
+ delete *it;
+ }
+}
+
+void KMComposeWin::setAutoDeleteWindow( bool f )
+{
+ if ( f )
+ setWFlags( getWFlags() | WDestructiveClose );
+ else
+ setWFlags( getWFlags() & ~WDestructiveClose );
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::send(int how)
+{
+ switch (how) {
+ case 1:
+ slotSendNow();
+ break;
+ default:
+ case 0:
+ // TODO: find out, what the default send method is and send it this way
+ case 2:
+ slotSendLater();
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::addAttachmentsAndSend(const KURL::List &urls, const QString &/*comment*/, int how)
+{
+ if (urls.isEmpty())
+ {
+ send(how);
+ return;
+ }
+ mAttachFilesSend = how;
+ mAttachFilesPending = urls;
+ connect(this, SIGNAL(attachmentAdded(const KURL&, bool)), SLOT(slotAttachedFile(const KURL&)));
+ for( KURL::List::ConstIterator itr = urls.begin(); itr != urls.end(); ++itr ) {
+ if (!addAttach( *itr ))
+ mAttachFilesPending.remove(mAttachFilesPending.find(*itr)); // only remove one copy of the url
+ }
+
+ if (mAttachFilesPending.isEmpty() && mAttachFilesSend == how)
+ {
+ send(mAttachFilesSend);
+ mAttachFilesSend = -1;
+ }
+}
+
+void KMComposeWin::slotAttachedFile(const KURL &url)
+{
+ if (mAttachFilesPending.isEmpty())
+ return;
+ mAttachFilesPending.remove(mAttachFilesPending.find(url)); // only remove one copy of url
+ if (mAttachFilesPending.isEmpty())
+ {
+ send(mAttachFilesSend);
+ mAttachFilesSend = -1;
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::addAttachment(KURL url,QString /*comment*/)
+{
+ addAttach(url);
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::addAttachment(const QString &name,
+ const QCString &/*cte*/,
+ const QByteArray &data,
+ const QCString &type,
+ const QCString &subType,
+ const QCString &paramAttr,
+ const QString &paramValue,
+ const QCString &contDisp)
+{
+ if (!data.isEmpty()) {
+ KMMessagePart *msgPart = new KMMessagePart;
+ msgPart->setName(name);
+ if( type == "message" && subType == "rfc822" ) {
+ msgPart->setMessageBody( data );
+ } else {
+ QValueList<int> dummy;
+ msgPart->setBodyAndGuessCte(data, dummy,
+ kmkernel->msgSender()->sendQuotedPrintable());
+ }
+ msgPart->setTypeStr(type);
+ msgPart->setSubtypeStr(subType);
+ msgPart->setParameter(paramAttr,paramValue);
+ msgPart->setContentDisposition(contDisp);
+ addAttach(msgPart);
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotAttachPNGImageData(const QByteArray &image)
+{
+ bool ok;
+
+ QString attName = KInputDialog::getText( "KMail", i18n("Name of the attachment:"), QString::null, &ok, this );
+ if ( !ok )
+ return;
+
+ if ( !attName.lower().endsWith(".png") ) attName += ".png";
+
+ addAttachment( attName, "base64", image, "image", "png", QCString(), QString(), QCString() );
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::setBody(QString body)
+{
+ mEditor->setText(body);
+}
+
+//-----------------------------------------------------------------------------
+bool KMComposeWin::event(QEvent *e)
+{
+ if (e->type() == QEvent::ApplicationPaletteChange)
+ {
+ readColorConfig();
+ }
+ return KMail::Composer::event(e);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::readColorConfig(void)
+{
+ if ( GlobalSettings::self()->useDefaultColors() ) {
+ mForeColor = QColor(kapp->palette().active().text());
+ mBackColor = QColor(kapp->palette().active().base());
+ } else {
+ mForeColor = GlobalSettings::self()->foregroundColor();
+ mBackColor = GlobalSettings::self()->backgroundColor();
+ }
+
+ // Color setup
+ mPalette = kapp->palette();
+ QColorGroup cgrp = mPalette.active();
+ cgrp.setColor( QColorGroup::Base, mBackColor);
+ cgrp.setColor( QColorGroup::Text, mForeColor);
+ mPalette.setDisabled(cgrp);
+ mPalette.setActive(cgrp);
+ mPalette.setInactive(cgrp);
+
+ mEdtFrom->setPalette(mPalette);
+ mEdtReplyTo->setPalette(mPalette);
+ if ( mClassicalRecipients ) {
+ mEdtTo->setPalette(mPalette);
+ mEdtCc->setPalette(mPalette);
+ mEdtBcc->setPalette(mPalette);
+ }
+ mEdtSubject->setPalette(mPalette);
+ mTransport->setPalette(mPalette);
+ mEditor->setPalette(mPalette);
+ mFcc->setPalette(mPalette);
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::readConfig( bool reload /* = false */ )
+{
+ mDefCharset = KMMessage::defaultCharset();
+ mBtnIdentity->setChecked( GlobalSettings::self()->stickyIdentity() );
+ if (mBtnIdentity->isChecked()) {
+ mId = (GlobalSettings::self()->previousIdentity()!=0) ?
+ GlobalSettings::self()->previousIdentity() : mId;
+ }
+ mBtnFcc->setChecked( GlobalSettings::self()->stickyFcc() );
+ mBtnTransport->setChecked( GlobalSettings::self()->stickyTransport() );
+ QStringList transportHistory = GlobalSettings::self()->transportHistory();
+ QString currentTransport = GlobalSettings::self()->currentTransport();
+
+ mEdtFrom->setCompletionMode( (KGlobalSettings::Completion)GlobalSettings::self()->completionMode() );
+ mEdtReplyTo->setCompletionMode( (KGlobalSettings::Completion)GlobalSettings::self()->completionMode() );
+ if ( mClassicalRecipients ) {
+ mEdtTo->setCompletionMode( (KGlobalSettings::Completion)GlobalSettings::self()->completionMode() );
+ mEdtCc->setCompletionMode( (KGlobalSettings::Completion)GlobalSettings::self()->completionMode() );
+ mEdtBcc->setCompletionMode( (KGlobalSettings::Completion)GlobalSettings::self()->completionMode() );
+ }
+ else
+ mRecipientsEditor->setCompletionMode( (KGlobalSettings::Completion)GlobalSettings::self()->completionMode() );
+
+ readColorConfig();
+
+ if ( GlobalSettings::self()->useDefaultFonts() ) {
+ mBodyFont = KGlobalSettings::generalFont();
+ mFixedFont = KGlobalSettings::fixedFont();
+ } else {
+ mBodyFont = GlobalSettings::self()->composerFont();
+ mFixedFont = GlobalSettings::self()->fixedFont();
+ }
+
+ slotUpdateFont();
+ mEdtFrom->setFont(mBodyFont);
+ mEdtReplyTo->setFont(mBodyFont);
+ if ( mClassicalRecipients ) {
+ mEdtTo->setFont(mBodyFont);
+ mEdtCc->setFont(mBodyFont);
+ mEdtBcc->setFont(mBodyFont);
+ }
+ mEdtSubject->setFont(mBodyFont);
+
+ if ( !reload ) {
+ QSize siz = GlobalSettings::self()->composerSize();
+ if (siz.width() < 200) siz.setWidth(200);
+ if (siz.height() < 200) siz.setHeight(200);
+ resize(siz);
+
+ if ( !GlobalSettings::self()->snippetSplitterPosition().isEmpty() ) {
+ mSnippetSplitter->setSizes( GlobalSettings::self()->snippetSplitterPosition() );
+ } else {
+ QValueList<int> defaults;
+ defaults << (int)(width() * 0.8) << (int)(width() * 0.2);
+ mSnippetSplitter->setSizes( defaults );
+ }
+ }
+
+ mIdentity->setCurrentIdentity( mId );
+
+ kdDebug(5006) << "KMComposeWin::readConfig. " << mIdentity->currentIdentityName() << endl;
+ 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() )
+ transportHistory.remove( transportHistory.last() );
+ mTransport->insertStringList( transportHistory );
+ mTransport->setCurrentText( GlobalSettings::self()->defaultTransport() );
+ if ( mBtnTransport->isChecked() ) {
+ setTransport( currentTransport );
+ }
+
+ QString fccName = "";
+ if ( mBtnFcc->isChecked() ) {
+ fccName = GlobalSettings::self()->previousFcc();
+ } else if ( !ident.fcc().isEmpty() ) {
+ fccName = ident.fcc();
+ }
+
+ setFcc( fccName );
+}
+
+//-----------------------------------------------------------------------------
+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() );
+ GlobalSettings::self()->setPreviousFcc( mFcc->getFolder()->idString() );
+ GlobalSettings::self()->setAutoSpellChecking(
+ mAutoSpellCheckingAction->isChecked() );
+ QStringList transportHistory = GlobalSettings::self()->transportHistory();
+ transportHistory.remove(mTransport->currentText());
+ if (KMTransportInfo::availableTransports().findIndex(mTransport
+ ->currentText()) == -1) {
+ transportHistory.prepend(mTransport->currentText());
+ }
+ GlobalSettings::self()->setTransportHistory( transportHistory );
+ GlobalSettings::self()->setUseFixedFont( mFixedFontAction->isChecked() );
+ GlobalSettings::self()->setUseHtmlMarkup( mHtmlMarkup );
+ GlobalSettings::self()->setComposerSize( size() );
+ GlobalSettings::self()->setShowSnippetManager( mSnippetAction->isChecked() );
+
+ KConfigGroupSaver saver( KMKernel::config(), "Geometry" );
+ saveMainWindowSettings( KMKernel::config(), "Composer" );
+ GlobalSettings::setSnippetSplitterPosition( mSnippetSplitter->sizes() );
+
+ // make sure config changes are written to disk, cf. bug 127538
+ GlobalSettings::self()->writeConfig();
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::autoSaveMessage()
+{
+ kdDebug(5006) << k_funcinfo << endl;
+ if ( !mMsg || mComposer || mAutoSaveFilename.isEmpty() )
+ return;
+ kdDebug(5006) << k_funcinfo << "autosaving message" << endl;
+
+ if ( mAutoSaveTimer )
+ mAutoSaveTimer->stop();
+
+ connect( this, SIGNAL( applyChangesDone( bool ) ),
+ this, SLOT( slotContinueAutoSave() ) );
+ // This method is called when KMail crashed, so don't try signing/encryption
+ // and don't disable controls because it is also called from a timer and
+ // then the disabling is distracting.
+ applyChanges( true, true );
+
+ // Don't continue before the applyChanges is done!
+}
+
+void KMComposeWin::slotContinueAutoSave()
+{
+ disconnect( this, SIGNAL( applyChangesDone( bool ) ),
+ this, SLOT( slotContinueAutoSave() ) );
+
+ // Ok, it's done now - continue dead letter saving
+ if ( mComposedMessages.isEmpty() ) {
+ kdDebug(5006) << "Composing the message failed." << endl;
+ return;
+ }
+ KMMessage *msg = mComposedMessages.first();
+ if ( !msg ) // a bit of extra defensiveness
+ return;
+
+ kdDebug(5006) << k_funcinfo << "opening autoSaveFile " << mAutoSaveFilename
+ << endl;
+ const QString filename =
+ KMKernel::localDataPath() + "autosave/cur/" + mAutoSaveFilename;
+ KSaveFile autoSaveFile( filename, 0600 );
+ int status = autoSaveFile.status();
+ kdDebug(5006) << k_funcinfo << "autoSaveFile.status() = " << status << endl;
+ if ( status == 0 ) { // no error
+ kdDebug(5006) << "autosaving message in " << filename << endl;
+ int fd = autoSaveFile.handle();
+ const DwString& msgStr = msg->asDwString();
+ if ( ::write( fd, msgStr.data(), msgStr.length() ) == -1 )
+ status = errno;
+ }
+ if ( status == 0 ) {
+ kdDebug(5006) << k_funcinfo << "closing autoSaveFile" << endl;
+ autoSaveFile.close();
+ mLastAutoSaveErrno = 0;
+ }
+ else {
+ kdDebug(5006) << k_funcinfo << "autosaving failed" << endl;
+ autoSaveFile.abort();
+ if ( status != mLastAutoSaveErrno ) {
+ // don't show the same error message twice
+ KMessageBox::queuedMessageBox( 0, KMessageBox::Sorry,
+ i18n("Autosaving the message as %1 "
+ "failed.\n"
+ "Reason: %2" )
+ .arg( filename, strerror( status ) ),
+ i18n("Autosaving Failed") );
+ mLastAutoSaveErrno = status;
+ }
+ }
+
+ if ( autoSaveInterval() > 0 )
+ updateAutoSave();
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotView(void)
+{
+ if (!mDone)
+ return; // otherwise called from rethinkFields during the construction
+ // which is not the intended behavior
+ int id;
+
+ //This sucks awfully, but no, I cannot get an activated(int id) from
+ // actionContainer()
+ if (!sender()->isA("KToggleAction"))
+ return;
+ KToggleAction *act = (KToggleAction *) sender();
+
+ if (act == mAllFieldsAction)
+ id = 0;
+ else if (act == mIdentityAction)
+ id = HDR_IDENTITY;
+ else if (act == mTransportAction)
+ id = HDR_TRANSPORT;
+ else if (act == mFromAction)
+ id = HDR_FROM;
+ else if (act == mReplyToAction)
+ id = HDR_REPLY_TO;
+ else if (act == mToAction)
+ id = HDR_TO;
+ else if (act == mCcAction)
+ id = HDR_CC;
+ else if (act == mBccAction)
+ id = HDR_BCC;
+ else if (act == mSubjectAction)
+ id = HDR_SUBJECT;
+ else if (act == mFccAction)
+ id = HDR_FCC;
+ else if ( act == mDictionaryAction )
+ id = HDR_DICTIONARY;
+ else
+ {
+ id = 0;
+ kdDebug(5006) << "Something is wrong (Oh, yeah?)" << endl;
+ return;
+ }
+
+ // sanders There's a bug here this logic doesn't work if no
+ // fields are shown and then show all fields is selected.
+ // Instead of all fields being shown none are.
+ if (!act->isChecked())
+ {
+ // hide header
+ if (id > 0) mShowHeaders = mShowHeaders & ~id;
+ else mShowHeaders = abs(mShowHeaders);
+ }
+ else
+ {
+ // show header
+ if (id > 0) mShowHeaders |= id;
+ else mShowHeaders = -abs(mShowHeaders);
+ }
+ rethinkFields(true);
+}
+
+int KMComposeWin::calcColumnWidth(int which, long allShowing, int width)
+{
+ if ( (allShowing & which) == 0 )
+ return width;
+
+ QLabel *w;
+ if ( which == HDR_IDENTITY )
+ w = mLblIdentity;
+ else if ( which == HDR_DICTIONARY )
+ w = mDictionaryLabel;
+ else if ( which == HDR_FCC )
+ w = mLblFcc;
+ else if ( which == HDR_TRANSPORT )
+ w = mLblTransport;
+ else if ( which == HDR_FROM )
+ w = mLblFrom;
+ else if ( which == HDR_REPLY_TO )
+ w = mLblReplyTo;
+ else if ( which == HDR_SUBJECT )
+ w = mLblSubject;
+ else
+ return width;
+
+ w->setBuddy( mEditor ); // set dummy so we don't calculate width of '&' for this label.
+ w->adjustSize();
+ w->show();
+ return QMAX( width, w->sizeHint().width() );
+}
+
+void KMComposeWin::rethinkFields(bool fromSlot)
+{
+ //This sucks even more but again no ids. sorry (sven)
+ int mask, row, numRows;
+ long showHeaders;
+
+ if (mShowHeaders < 0)
+ showHeaders = HDR_ALL;
+ else
+ showHeaders = mShowHeaders;
+
+ for (mask=1,mNumHeaders=0; mask<=showHeaders; mask<<=1)
+ if ((showHeaders&mask) != 0) mNumHeaders++;
+
+ numRows = mNumHeaders + 1;
+
+ delete mGrid;
+
+ mGrid = new QGridLayout( mHeadersArea, numRows, 3, KDialogBase::marginHint()/2, KDialogBase::spacingHint());
+ mGrid->setColStretch(0, 1);
+ mGrid->setColStretch(1, 100);
+ mGrid->setColStretch(2, 1);
+ mGrid->setRowStretch(mNumHeaders, 100);
+
+ row = 0;
+ kdDebug(5006) << "KMComposeWin::rethinkFields" << endl;
+ if (mRecipientsEditor)
+ mLabelWidth = mRecipientsEditor->setFirstColumnWidth( 0 );
+ mLabelWidth = calcColumnWidth( HDR_IDENTITY, showHeaders, mLabelWidth );
+ mLabelWidth = calcColumnWidth( HDR_DICTIONARY, showHeaders, mLabelWidth );
+ mLabelWidth = calcColumnWidth( HDR_FCC, showHeaders, mLabelWidth );
+ mLabelWidth = calcColumnWidth( HDR_TRANSPORT, showHeaders, mLabelWidth );
+ mLabelWidth = calcColumnWidth( HDR_FROM, showHeaders, mLabelWidth );
+ mLabelWidth = calcColumnWidth( HDR_REPLY_TO, showHeaders, mLabelWidth );
+ mLabelWidth = calcColumnWidth( HDR_SUBJECT, showHeaders, mLabelWidth );
+
+ if (!fromSlot) mAllFieldsAction->setChecked(showHeaders==HDR_ALL);
+
+ if (!fromSlot) mIdentityAction->setChecked(abs(mShowHeaders)&HDR_IDENTITY);
+ rethinkHeaderLine(showHeaders,HDR_IDENTITY, row, i18n("&Identity:"),
+ mLblIdentity, mIdentity, mBtnIdentity);
+
+ if (!fromSlot) mDictionaryAction->setChecked(abs(mShowHeaders)&HDR_DICTIONARY);
+ rethinkHeaderLine(showHeaders,HDR_DICTIONARY, row, i18n("&Dictionary:"),
+ mDictionaryLabel, mDictionaryCombo, 0 );
+
+ if (!fromSlot) mFccAction->setChecked(abs(mShowHeaders)&HDR_FCC);
+ rethinkHeaderLine(showHeaders,HDR_FCC, row, i18n("&Sent-Mail folder:"),
+ mLblFcc, mFcc, mBtnFcc);
+
+ if (!fromSlot) mTransportAction->setChecked(abs(mShowHeaders)&HDR_TRANSPORT);
+ rethinkHeaderLine(showHeaders,HDR_TRANSPORT, row, i18n("&Mail transport:"),
+ mLblTransport, mTransport, mBtnTransport);
+
+ if (!fromSlot) mFromAction->setChecked(abs(mShowHeaders)&HDR_FROM);
+ rethinkHeaderLine(showHeaders,HDR_FROM, row, i18n("sender address field", "&From:"),
+ mLblFrom, mEdtFrom /*, mBtnFrom */ );
+
+ QWidget *prevFocus = mEdtFrom;
+
+ if (!fromSlot) mReplyToAction->setChecked(abs(mShowHeaders)&HDR_REPLY_TO);
+ rethinkHeaderLine(showHeaders,HDR_REPLY_TO,row,i18n("&Reply to:"),
+ mLblReplyTo, mEdtReplyTo, mBtnReplyTo);
+ 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:"),
+ mLblTo, mEdtTo, mBtnTo,
+ i18n("Primary Recipients"),
+ i18n("<qt>The email addresses you put "
+ "in this field receive a copy of the email.</qt>"));
+ if ( showHeaders & HDR_TO ) {
+ prevFocus = connectFocusMoving( prevFocus, mEdtTo );
+ }
+
+ if (!fromSlot) mCcAction->setChecked(abs(mShowHeaders)&HDR_CC);
+ rethinkHeaderLine(showHeaders, HDR_CC, row, i18n("&Copy to (CC):"),
+ mLblCc, mEdtCc, mBtnCc,
+ i18n("Additional Recipients"),
+ i18n("<qt>The email addresses you put "
+ "in this field receive a copy of the email. "
+ "Technically it is the same thing as putting all the "
+ "addresses in the <b>To:</b> field but differs in "
+ "that it usually symbolises the receiver of the "
+ "Carbon Copy (CC) is a listener, not the main "
+ "recipient.</qt>"));
+ if ( showHeaders & HDR_CC ) {
+ prevFocus = connectFocusMoving( prevFocus, mEdtCc );
+ }
+
+ if (!fromSlot) mBccAction->setChecked(abs(mShowHeaders)&HDR_BCC);
+ rethinkHeaderLine(showHeaders,HDR_BCC, row, i18n("&Blind copy to (BCC):"),
+ mLblBcc, mEdtBcc, mBtnBcc,
+ i18n("Hidden Recipients"),
+ i18n("<qt>Essentially the same thing "
+ "as the <b>Copy To:</b> field but differs in that "
+ "all other recipients do not see who receives a "
+ "blind copy.</qt>"));
+ if ( showHeaders & HDR_BCC ) {
+ prevFocus = connectFocusMoving( prevFocus, mEdtBcc );
+ }
+ } else {
+ mGrid->addMultiCellWidget( mRecipientsEditor, row, row, 0, 2 );
+ ++row;
+
+ if ( showHeaders & HDR_REPLY_TO ) {
+ connect( mEdtReplyTo, SIGNAL( focusDown() ), mRecipientsEditor,
+ SLOT( setFocusTop() ) );
+ } else {
+ connect( mEdtFrom, SIGNAL( focusDown() ), mRecipientsEditor,
+ SLOT( setFocusTop() ) );
+ }
+ if ( showHeaders & HDR_REPLY_TO ) {
+ connect( mRecipientsEditor, SIGNAL( focusUp() ), mEdtReplyTo, SLOT( setFocus() ) );
+ } else {
+ connect( mRecipientsEditor, SIGNAL( focusUp() ), mEdtFrom, SLOT( setFocus() ) );
+ }
+
+ connect( mRecipientsEditor, SIGNAL( focusDown() ), mEdtSubject,
+ SLOT( setFocus() ) );
+ connect( mEdtSubject, SIGNAL( focusUp() ), mRecipientsEditor,
+ SLOT( setFocusBottom() ) );
+
+ prevFocus = mRecipientsEditor;
+ }
+ if (!fromSlot) mSubjectAction->setChecked(abs(mShowHeaders)&HDR_SUBJECT);
+ rethinkHeaderLine(showHeaders,HDR_SUBJECT, row, i18n("S&ubject:"),
+ mLblSubject, mEdtSubject);
+ connectFocusMoving( mEdtSubject, mEditor );
+
+ assert(row<=mNumHeaders);
+
+
+ if( !mAtmList.isEmpty() )
+ mAtmListView->show();
+ else
+ mAtmListView->hide();
+ resize(this->size());
+ repaint();
+
+ mHeadersArea->setMaximumHeight( mHeadersArea->sizeHint().height() );
+ mGrid->activate();
+ mHeadersArea->show();
+
+ slotUpdateAttachActions();
+ mIdentityAction->setEnabled(!mAllFieldsAction->isChecked());
+ mDictionaryAction->setEnabled( !mAllFieldsAction->isChecked() );
+ mTransportAction->setEnabled(!mAllFieldsAction->isChecked());
+ mFromAction->setEnabled(!mAllFieldsAction->isChecked());
+ if ( mReplyToAction ) mReplyToAction->setEnabled(!mAllFieldsAction->isChecked());
+ if ( mToAction ) mToAction->setEnabled(!mAllFieldsAction->isChecked());
+ if ( mCcAction ) mCcAction->setEnabled(!mAllFieldsAction->isChecked());
+ if ( mBccAction ) mBccAction->setEnabled(!mAllFieldsAction->isChecked());
+ mFccAction->setEnabled(!mAllFieldsAction->isChecked());
+ mSubjectAction->setEnabled(!mAllFieldsAction->isChecked());
+ if (mRecipientsEditor)
+ mRecipientsEditor->setFirstColumnWidth( mLabelWidth );
+}
+
+QWidget *KMComposeWin::connectFocusMoving( QWidget *prev, QWidget *next )
+{
+ connect( prev, SIGNAL( focusDown() ), next, SLOT( setFocus() ) );
+ connect( next, SIGNAL( focusUp() ), prev, SLOT( setFocus() ) );
+
+ return next;
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::rethinkHeaderLine(int aValue, int aMask, int& aRow,
+ const QString &aLabelStr, QLabel* aLbl,
+ QLineEdit* aEdt, QPushButton* aBtn,
+ const QString &toolTip, const QString &whatsThis )
+{
+ if (aValue & aMask)
+ {
+ aLbl->setText(aLabelStr);
+ if ( !toolTip.isEmpty() )
+ QToolTip::add( aLbl, toolTip );
+ if ( !whatsThis.isEmpty() )
+ QWhatsThis::add( aLbl, whatsThis );
+ aLbl->setFixedWidth( mLabelWidth );
+ aLbl->setBuddy(aEdt);
+ mGrid->addWidget(aLbl, aRow, 0);
+ aEdt->setBackgroundColor( mBackColor );
+ aEdt->show();
+
+ if (aBtn) {
+ mGrid->addWidget(aEdt, aRow, 1);
+
+ mGrid->addWidget(aBtn, aRow, 2);
+ aBtn->show();
+ } else {
+ mGrid->addMultiCellWidget(aEdt, aRow, aRow, 1, 2 );
+ }
+ aRow++;
+ }
+ else
+ {
+ aLbl->hide();
+ aEdt->hide();
+ if (aBtn) aBtn->hide();
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::rethinkHeaderLine(int aValue, int aMask, int& aRow,
+ const QString &aLabelStr, QLabel* aLbl,
+ QComboBox* aCbx, QCheckBox* aChk)
+{
+ if (aValue & aMask)
+ {
+ aLbl->setText(aLabelStr);
+ aLbl->adjustSize();
+ aLbl->resize((int)aLbl->sizeHint().width(),aLbl->sizeHint().height() + 6);
+ aLbl->setMinimumSize(aLbl->size());
+ aLbl->show();
+ aLbl->setBuddy(aCbx);
+ mGrid->addWidget(aLbl, aRow, 0);
+ aCbx->show();
+ aCbx->setMinimumSize(100, aLbl->height()+2);
+
+ mGrid->addWidget(aCbx, aRow, 1);
+ if ( aChk ) {
+ mGrid->addWidget(aChk, aRow, 2);
+ aChk->setFixedSize(aChk->sizeHint().width(), aLbl->height());
+ aChk->show();
+ }
+ aRow++;
+ }
+ else
+ {
+ aLbl->hide();
+ aCbx->hide();
+ if ( aChk )
+ aChk->hide();
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::getTransportMenu()
+{
+ QStringList availTransports;
+
+ mActNowMenu->clear();
+ mActLaterMenu->clear();
+ availTransports = KMail::TransportManager::transportNames();
+ QStringList::Iterator it;
+ int id = 0;
+ for(it = availTransports.begin(); it != availTransports.end() ; ++it, id++)
+ {
+ mActNowMenu->insertItem((*it).replace("&", "&&"), id);
+ mActLaterMenu->insertItem((*it).replace("&", "&&"), id);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::setupActions(void)
+{
+ KActionMenu *actActionNowMenu, *actActionLaterMenu;
+
+ if (kmkernel->msgSender()->sendImmediate()) //default == send now?
+ {
+ //default = send now, alternative = queue
+ ( void ) new KAction( i18n("&Send Mail"), "mail_send", CTRL+Key_Return,
+ this, SLOT(slotSendNow()), actionCollection(),"send_default");
+
+ // FIXME: change to mail_send_via icon when this exits.
+ actActionNowMenu = new KActionMenu (i18n("&Send Mail Via"), "mail_send",
+ actionCollection(), "send_default_via" );
+
+ (void) new KAction (i18n("Send &Later"), "queue", 0, this,
+ SLOT(slotSendLater()), actionCollection(),"send_alternative");
+ actActionLaterMenu = new KActionMenu (i18n("Send &Later Via"), "queue",
+ actionCollection(), "send_alternative_via" );
+
+ }
+ else //no, default = send later
+ {
+ //default = queue, alternative = send now
+ (void) new KAction (i18n("Send &Later"), "queue",
+ CTRL+Key_Return,
+ this, SLOT(slotSendLater()), actionCollection(),"send_default");
+ actActionLaterMenu = new KActionMenu (i18n("Send &Later Via"), "queue",
+ actionCollection(), "send_default_via" );
+
+ ( void ) new KAction( i18n("&Send Mail"), "mail_send", 0,
+ this, SLOT(slotSendNow()), actionCollection(),"send_alternative");
+
+ // FIXME: change to mail_send_via icon when this exits.
+ actActionNowMenu = new KActionMenu (i18n("&Send Mail Via"), "mail_send",
+ actionCollection(), "send_alternative_via" );
+
+ }
+
+ // needed for sending "default transport"
+ actActionNowMenu->setDelayed(true);
+ actActionLaterMenu->setDelayed(true);
+
+ connect( actActionNowMenu, SIGNAL( activated() ), this,
+ SLOT( slotSendNow() ) );
+ connect( actActionLaterMenu, SIGNAL( activated() ), this,
+ SLOT( slotSendLater() ) );
+
+
+ mActNowMenu = actActionNowMenu->popupMenu();
+ mActLaterMenu = actActionLaterMenu->popupMenu();
+
+ connect( mActNowMenu, SIGNAL( activated( int ) ), this,
+ SLOT( slotSendNowVia( int ) ) );
+ connect( mActNowMenu, SIGNAL( aboutToShow() ), this,
+ SLOT( getTransportMenu() ) );
+
+ connect( mActLaterMenu, SIGNAL( activated( int ) ), this,
+ SLOT( slotSendLaterVia( int ) ) );
+ connect( mActLaterMenu, SIGNAL( aboutToShow() ), this,
+ SLOT( getTransportMenu() ) );
+
+
+
+
+ (void) new KAction (i18n("Save as &Draft"), "filesave", 0,
+ this, SLOT(slotSaveDraft()),
+ actionCollection(), "save_in_drafts");
+ (void) new KAction (i18n("Save as &Template"), "filesave", 0,
+ this, SLOT(slotSaveTemplate()),
+ actionCollection(), "save_in_templates");
+ (void) new KAction (i18n("&Insert File..."), "fileopen", 0,
+ this, SLOT(slotInsertFile()),
+ actionCollection(), "insert_file");
+ mRecentAction = new KRecentFilesAction (i18n("&Insert File Recent"),
+ "fileopen", 0,
+ this, SLOT(slotInsertRecentFile(const KURL&)),
+ actionCollection(), "insert_file_recent");
+
+ mRecentAction->loadEntries( KMKernel::config() );
+
+ (void) new KAction (i18n("&Address Book"), "contents",0,
+ this, SLOT(slotAddrBook()),
+ actionCollection(), "addressbook");
+ (void) new KAction (i18n("&New Composer"), "mail_new",
+ KStdAccel::shortcut(KStdAccel::New),
+ this, SLOT(slotNewComposer()),
+ actionCollection(), "new_composer");
+ (void) new KAction (i18n("New Main &Window"), "window_new", 0,
+ this, SLOT(slotNewMailReader()),
+ actionCollection(), "open_mailreader");
+
+ if ( !mClassicalRecipients ) {
+ new KAction( i18n("Select &Recipients..."), CTRL + Key_L, mRecipientsEditor,
+ SLOT( selectRecipients() ), actionCollection(), "select_recipients" );
+ new KAction( i18n("Save &Distribution List..."), 0, mRecipientsEditor,
+ SLOT( saveDistributionList() ), actionCollection(),
+ "save_distribution_list" );
+ }
+
+ //KStdAction::save(this, SLOT(), actionCollection(), "save_message");
+ KStdAction::print (this, SLOT(slotPrint()), actionCollection());
+ KStdAction::close (this, SLOT(slotClose()), actionCollection());
+
+ KStdAction::undo (this, SLOT(slotUndo()), actionCollection());
+ KStdAction::redo (this, SLOT(slotRedo()), actionCollection());
+ KStdAction::cut (this, SLOT(slotCut()), actionCollection());
+ KStdAction::copy (this, SLOT(slotCopy()), actionCollection());
+ KStdAction::pasteText (this, SLOT(slotPasteClipboard()), actionCollection());
+ KStdAction::selectAll (this, SLOT(slotMarkAll()), actionCollection());
+
+ KStdAction::find (this, SLOT(slotFind()), actionCollection());
+ KStdAction::findNext(this, SLOT(slotSearchAgain()), actionCollection());
+
+ KStdAction::replace (this, SLOT(slotReplace()), actionCollection());
+ KStdAction::spelling (this, SLOT(slotSpellcheck()), actionCollection(), "spellcheck");
+
+ mPasteQuotation = new KAction (i18n("Pa&ste as Quotation"),0,this,SLOT( slotPasteClipboardAsQuotation()),
+ actionCollection(), "paste_quoted");
+
+ (void) new KAction (i18n("Paste as Attac&hment"),0,this,SLOT( slotPasteClipboardAsAttachment()),
+ actionCollection(), "paste_att");
+
+ mAddQuoteChars = new KAction(i18n("Add &Quote Characters"), 0, this,
+ SLOT(slotAddQuotes()), actionCollection(), "tools_quote");
+
+ mRemQuoteChars = new KAction(i18n("Re&move Quote Characters"), 0, this,
+ SLOT(slotRemoveQuotes()), actionCollection(), "tools_unquote");
+
+
+ (void) new KAction (i18n("Cl&ean Spaces"), 0, this, SLOT(slotCleanSpace()),
+ actionCollection(), "clean_spaces");
+
+ mFixedFontAction = new KToggleAction( i18n("Use Fi&xed Font"), 0, this,
+ SLOT(slotUpdateFont()), actionCollection(), "toggle_fixedfont" );
+ mFixedFontAction->setChecked( GlobalSettings::self()->useFixedFont() );
+
+ //these are checkable!!!
+ mUrgentAction = new KToggleAction (i18n("&Urgent"), 0,
+ actionCollection(),
+ "urgent");
+ mRequestMDNAction = new KToggleAction ( i18n("&Request Disposition Notification"), 0,
+ actionCollection(),
+ "options_request_mdn");
+ mRequestMDNAction->setChecked(GlobalSettings::self()->requestMDN());
+ //----- Message-Encoding Submenu
+ mEncodingAction = new KSelectAction( i18n( "Se&t Encoding" ), "charset",
+ 0, this, SLOT(slotSetCharset() ),
+ actionCollection(), "charsets" );
+ mWordWrapAction = new KToggleAction (i18n("&Wordwrap"), 0,
+ actionCollection(), "wordwrap");
+ mWordWrapAction->setChecked(GlobalSettings::self()->wordWrap());
+ connect(mWordWrapAction, SIGNAL(toggled(bool)), SLOT(slotWordWrapToggled(bool)));
+
+ mSnippetAction = new KToggleAction ( i18n("&Snippets"), 0,
+ actionCollection(), "snippets");
+ connect(mSnippetAction, SIGNAL(toggled(bool)), mSnippetWidget, SLOT(setShown(bool)) );
+ mSnippetAction->setChecked( GlobalSettings::self()->showSnippetManager() );
+
+ mAutoSpellCheckingAction =
+ new KToggleAction( i18n( "&Automatic Spellchecking" ), "spellcheck", 0,
+ actionCollection(), "options_auto_spellchecking" );
+ const bool spellChecking = GlobalSettings::self()->autoSpellChecking();
+ mAutoSpellCheckingAction->setEnabled( !GlobalSettings::self()->useExternalEditor() );
+ mAutoSpellCheckingAction->setChecked( !GlobalSettings::self()->useExternalEditor() && spellChecking );
+ slotAutoSpellCheckingToggled( !GlobalSettings::self()->useExternalEditor() && spellChecking );
+ connect( mAutoSpellCheckingAction, SIGNAL( toggled( bool ) ),
+ this, SLOT( slotAutoSpellCheckingToggled( bool ) ) );
+
+ QStringList encodings = KMMsgBase::supportedEncodings(true);
+ encodings.prepend( i18n("Auto-Detect"));
+ mEncodingAction->setItems( encodings );
+ mEncodingAction->setCurrentItem( -1 );
+
+ //these are checkable!!!
+ markupAction = new KToggleAction (i18n("Formatting (HTML)"), 0, this,
+ SLOT(slotToggleMarkup()),
+ actionCollection(), "html");
+
+ mAllFieldsAction = new KToggleAction (i18n("&All Fields"), 0, this,
+ SLOT(slotView()),
+ actionCollection(), "show_all_fields");
+ mIdentityAction = new KToggleAction (i18n("&Identity"), 0, this,
+ SLOT(slotView()),
+ actionCollection(), "show_identity");
+ mDictionaryAction = new KToggleAction (i18n("&Dictionary"), 0, this,
+ SLOT(slotView()),
+ actionCollection(), "show_dictionary");
+ mFccAction = new KToggleAction (i18n("&Sent-Mail Folder"), 0, this,
+ SLOT(slotView()),
+ actionCollection(), "show_fcc");
+ mTransportAction = new KToggleAction (i18n("&Mail Transport"), 0, this,
+ SLOT(slotView()),
+ actionCollection(), "show_transport");
+ mFromAction = new KToggleAction (i18n("&From"), 0, this,
+ SLOT(slotView()),
+ actionCollection(), "show_from");
+ mReplyToAction = new KToggleAction (i18n("&Reply To"), 0, this,
+ SLOT(slotView()),
+ actionCollection(), "show_reply_to");
+ if ( mClassicalRecipients ) {
+ mToAction = new KToggleAction (i18n("&To"), 0, this,
+ SLOT(slotView()),
+ actionCollection(), "show_to");
+ mCcAction = new KToggleAction (i18n("&CC"), 0, this,
+ SLOT(slotView()),
+ actionCollection(), "show_cc");
+ mBccAction = new KToggleAction (i18n("&BCC"), 0, this,
+ SLOT(slotView()),
+ actionCollection(), "show_bcc");
+ }
+ mSubjectAction = new KToggleAction (i18n("S&ubject"), 0, this,
+ SLOT(slotView()),
+ actionCollection(), "show_subject");
+ //end of checkable
+
+ mAppendSignatureAction = new KAction (i18n("Append S&ignature"), 0, this,
+ SLOT(slotAppendSignature()),
+ actionCollection(), "append_signature");
+ mPrependSignatureAction = new KAction (i18n("Prepend S&ignature"), 0, this,
+ SLOT(slotPrependSignature()),
+ actionCollection(), "prepend_signature");
+
+ mInsertSignatureAction = new KAction (i18n("Insert Signature At C&ursor Position"), "edit", 0, this,
+ SLOT(slotInsertSignatureAtCursor()),
+ actionCollection(), "insert_signature_at_cursor_position");
+
+ mAttachPK = new KAction (i18n("Attach &Public Key..."), 0, this,
+ SLOT(slotInsertPublicKey()),
+ actionCollection(), "attach_public_key");
+ mAttachMPK = new KAction (i18n("Attach &My Public Key"), 0, this,
+ SLOT(slotInsertMyPublicKey()),
+ actionCollection(), "attach_my_public_key");
+ (void) new KAction (i18n("&Attach File..."), "attach",
+ 0, this, SLOT(slotAttachFile()),
+ actionCollection(), "attach");
+ mAttachRemoveAction = new KAction (i18n("&Remove Attachment"), 0, this,
+ SLOT(slotAttachRemove()),
+ actionCollection(), "remove");
+ mAttachSaveAction = new KAction (i18n("&Save Attachment As..."), "filesave",0,
+ this, SLOT(slotAttachSave()),
+ actionCollection(), "attach_save");
+ mAttachPropertiesAction = new KAction (i18n("Attachment Pr&operties"), 0, this,
+ SLOT(slotAttachProperties()),
+ actionCollection(), "attach_properties");
+
+ setStandardToolBarMenuEnabled(true);
+
+ KStdAction::keyBindings(this, SLOT(slotEditKeys()), actionCollection());
+ KStdAction::configureToolbars(this, SLOT(slotEditToolbars()), actionCollection());
+ KStdAction::preferences(kmkernel, SLOT(slotShowConfigurationDialog()), actionCollection());
+
+ (void) new KAction (i18n("&Spellchecker..."), 0, this, SLOT(slotSpellcheckConfig()),
+ actionCollection(), "setup_spellchecker");
+
+ if ( Kleo::CryptoBackendFactory::instance()->protocol( "Chiasmus" ) ) {
+ KToggleAction * a = new KToggleAction( i18n( "Encrypt Message with Chiasmus..." ),
+ "chidecrypted", 0, actionCollection(),
+ "encrypt_message_chiasmus" );
+ a->setCheckedState( KGuiItem( i18n( "Encrypt Message with Chiasmus..." ), "chiencrypted" ) );
+ mEncryptChiasmusAction = a;
+ connect( mEncryptChiasmusAction, SIGNAL(toggled(bool)),
+ this, SLOT(slotEncryptChiasmusToggled(bool)) );
+ } else {
+ mEncryptChiasmusAction = 0;
+ }
+
+ mEncryptAction = new KToggleAction (i18n("&Encrypt Message"),
+ "decrypted", 0,
+ actionCollection(), "encrypt_message");
+ mSignAction = new KToggleAction (i18n("&Sign Message"),
+ "signature", 0,
+ actionCollection(), "sign_message");
+ // get PGP user id for the chosen identity
+ const KPIM::Identity & ident =
+ kmkernel->identityManager()->identityForUoidOrDefault( mIdentity->currentIdentity() );
+ // PENDING(marc): check the uses of this member and split it into
+ // smime/openpgp and or enc/sign, if necessary:
+ mLastIdentityHasSigningKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty();
+ mLastIdentityHasEncryptionKey = !ident.pgpEncryptionKey().isEmpty() || !ident.smimeEncryptionKey().isEmpty();
+
+ mLastEncryptActionState = false;
+ mLastSignActionState = GlobalSettings::self()->pgpAutoSign();
+
+ // "Attach public key" is only possible if OpenPGP support is available:
+ mAttachPK->setEnabled( Kleo::CryptoBackendFactory::instance()->openpgp() );
+
+ // "Attach my public key" is only possible if OpenPGP support is
+ // available and the user specified his key for the current identity:
+ mAttachMPK->setEnabled( Kleo::CryptoBackendFactory::instance()->openpgp() &&
+ !ident.pgpEncryptionKey().isEmpty() );
+
+ if ( !Kleo::CryptoBackendFactory::instance()->openpgp() && !Kleo::CryptoBackendFactory::instance()->smime() ) {
+ // no crypto whatsoever
+ mEncryptAction->setEnabled( false );
+ setEncryption( false );
+ mSignAction->setEnabled( false );
+ setSigning( false );
+ } else {
+ const bool canOpenPGPSign = Kleo::CryptoBackendFactory::instance()->openpgp()
+ && !ident.pgpSigningKey().isEmpty();
+ const bool canSMIMESign = Kleo::CryptoBackendFactory::instance()->smime()
+ && !ident.smimeSigningKey().isEmpty();
+
+ setEncryption( false );
+ setSigning( ( canOpenPGPSign || canSMIMESign ) && GlobalSettings::self()->pgpAutoSign() );
+ }
+
+ connect(mEncryptAction, SIGNAL(toggled(bool)),
+ SLOT(slotEncryptToggled( bool )));
+ connect(mSignAction, SIGNAL(toggled(bool)),
+ SLOT(slotSignToggled( bool )));
+
+ QStringList l;
+ for ( int i = 0 ; i < numCryptoMessageFormats ; ++i )
+ l.push_back( Kleo::cryptoMessageFormatToLabel( cryptoMessageFormats[i] ) );
+
+ mCryptoModuleAction = new KSelectAction( i18n( "&Cryptographic Message Format" ), 0,
+ this, SLOT(slotSelectCryptoModule()),
+ actionCollection(), "options_select_crypto" );
+ mCryptoModuleAction->setItems( l );
+ mCryptoModuleAction->setCurrentItem( format2cb( ident.preferredCryptoMessageFormat() ) );
+ slotSelectCryptoModule( true /* initialize */ );
+
+ QStringList styleItems;
+ styleItems << i18n( "Standard" );
+ styleItems << i18n( "Bulleted List (Disc)" );
+ styleItems << i18n( "Bulleted List (Circle)" );
+ styleItems << i18n( "Bulleted List (Square)" );
+ styleItems << i18n( "Ordered List (Decimal)" );
+ styleItems << i18n( "Ordered List (Alpha lower)" );
+ styleItems << i18n( "Ordered List (Alpha upper)" );
+
+ listAction = new KSelectAction( i18n( "Select Style" ), 0, actionCollection(),
+ "text_list" );
+ listAction->setItems( styleItems );
+ connect( listAction, SIGNAL( activated( const QString& ) ),
+ SLOT( slotListAction( const QString& ) ) );
+ fontAction = new KFontAction( "Select Font", 0, actionCollection(),
+ "text_font" );
+ connect( fontAction, SIGNAL( activated( const QString& ) ),
+ SLOT( slotFontAction( const QString& ) ) );
+ fontSizeAction = new KFontSizeAction( "Select Size", 0, actionCollection(),
+ "text_size" );
+ connect( fontSizeAction, SIGNAL( fontSizeChanged( int ) ),
+ SLOT( slotSizeAction( int ) ) );
+
+ alignLeftAction = new KToggleAction (i18n("Align Left"), "text_left", 0,
+ this, SLOT(slotAlignLeft()), actionCollection(),
+ "align_left");
+ alignLeftAction->setChecked( true );
+ alignRightAction = new KToggleAction (i18n("Align Right"), "text_right", 0,
+ this, SLOT(slotAlignRight()), actionCollection(),
+ "align_right");
+ alignCenterAction = new KToggleAction (i18n("Align Center"), "text_center", 0,
+ this, SLOT(slotAlignCenter()), actionCollection(),
+ "align_center");
+ textBoldAction = new KToggleAction( i18n("&Bold"), "text_bold", CTRL+Key_B,
+ this, SLOT(slotTextBold()),
+ actionCollection(), "text_bold");
+ textItalicAction = new KToggleAction( i18n("&Italic"), "text_italic", CTRL+Key_I,
+ this, SLOT(slotTextItalic()),
+ actionCollection(), "text_italic");
+ textUnderAction = new KToggleAction( i18n("&Underline"), "text_under", CTRL+Key_U,
+ this, SLOT(slotTextUnder()),
+ actionCollection(), "text_under");
+ actionFormatReset = new KAction( i18n( "Reset Font Settings" ), "eraser", 0,
+ this, SLOT( slotFormatReset() ),
+ actionCollection(), "format_reset");
+ actionFormatColor = new KAction( i18n( "Text Color..." ), "colorize", 0,
+ this, SLOT( slotTextColor() ),
+ actionCollection(), "format_color");
+
+ // editorFocusChanged(false);
+ createGUI("kmcomposerui.rc");
+
+ connect( toolBar("htmlToolBar"), SIGNAL( visibilityChanged(bool) ),
+ this, SLOT( htmlToolBarVisibilityChanged(bool) ) );
+
+ // In Kontact, this entry would read "Configure Kontact", but bring
+ // up KMail's config dialog. That's sensible, though, so fix the label.
+ KAction* configureAction = actionCollection()->action("options_configure" );
+ if ( configureAction )
+ configureAction->setText( i18n("Configure KMail..." ) );
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::setupStatusBar(void)
+{
+ statusBar()->insertItem("", 0, 1);
+ statusBar()->setItemAlignment(0, AlignLeft | AlignVCenter);
+
+ statusBar()->insertItem(i18n( " Spellcheck: %1 ").arg( " " ), 3, 0, true );
+ statusBar()->insertItem(i18n( " Column: %1 ").arg(" "), 2, 0, true);
+ statusBar()->insertItem(i18n( " Line: %1 ").arg(" "), 1, 0, true);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::updateCursorPosition()
+{
+ int col,line;
+ QString temp;
+ line = mEditor->currentLine();
+ col = mEditor->currentColumn();
+ temp = i18n(" Line: %1 ").arg(line+1);
+ statusBar()->changeItem(temp,1);
+ temp = i18n(" Column: %1 ").arg(col+1);
+ statusBar()->changeItem(temp,2);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::setupEditor(void)
+{
+ //QPopupMenu* menu;
+ mEditor->setModified(false);
+ QFontMetrics fm(mBodyFont);
+ mEditor->setTabStopWidth(fm.width(QChar(' ')) * 8);
+ //mEditor->setFocusPolicy(QWidget::ClickFocus);
+
+ slotWordWrapToggled( GlobalSettings::self()->wordWrap() );
+
+ // Font setup
+ slotUpdateFont();
+
+ /* installRBPopup() is broken in kdelibs, we should wait for
+ the new klibtextedit (dnaber, 2002-01-01)
+ menu = new QPopupMenu(this);
+ //#ifdef BROKEN
+ menu->insertItem(i18n("Undo"),mEditor,
+ SLOT(undo()), KStdAccel::shortcut(KStdAccel::Undo));
+ menu->insertItem(i18n("Redo"),mEditor,
+ SLOT(redo()), KStdAccel::shortcut(KStdAccel::Redo));
+ menu->insertSeparator();
+ //#endif //BROKEN
+ menu->insertItem(i18n("Cut"), this, SLOT(slotCut()));
+ menu->insertItem(i18n("Copy"), this, SLOT(slotCopy()));
+ menu->insertItem(i18n("Paste"), this, SLOT(slotPasteClipboard()));
+ menu->insertItem(i18n("Mark All"),this, SLOT(slotMarkAll()));
+ menu->insertSeparator();
+ menu->insertItem(i18n("Find..."), this, SLOT(slotFind()));
+ menu->insertItem(i18n("Replace..."), this, SLOT(slotReplace()));
+ menu->insertSeparator();
+ menu->insertItem(i18n("Fixed Font Widths"), this, SLOT(slotUpdateFont()));
+ mEditor->installRBPopup(menu);
+ */
+ updateCursorPosition();
+ connect(mEditor,SIGNAL(CursorPositionChanged()),SLOT(updateCursorPosition()));
+ connect( mEditor, SIGNAL( currentFontChanged( const QFont & ) ),
+ this, SLOT( fontChanged( const QFont & ) ) );
+ connect( mEditor, SIGNAL( currentAlignmentChanged( int ) ),
+ this, SLOT( alignmentChanged( int ) ) );
+
+}
+
+
+//-----------------------------------------------------------------------------
+static QString cleanedUpHeaderString( const QString & s )
+{
+ // remove invalid characters from the header strings
+ QString res( s );
+ res.replace( '\r', "" );
+ res.replace( '\n', " " );
+ return res.stripWhiteSpace();
+}
+
+//-----------------------------------------------------------------------------
+QString KMComposeWin::subject() const
+{
+ return cleanedUpHeaderString( mEdtSubject->text() );
+}
+
+//-----------------------------------------------------------------------------
+QString KMComposeWin::to() const
+{
+ if ( mEdtTo ) {
+ return cleanedUpHeaderString( mEdtTo->text() );
+ } else if ( mRecipientsEditor ) {
+ return mRecipientsEditor->recipientString( Recipient::To );
+ } else {
+ return QString::null;
+ }
+}
+
+//-----------------------------------------------------------------------------
+QString KMComposeWin::cc() const
+{
+ if ( mEdtCc && !mEdtCc->isHidden() ) {
+ return cleanedUpHeaderString( mEdtCc->text() );
+ } else if ( mRecipientsEditor ) {
+ return mRecipientsEditor->recipientString( Recipient::Cc );
+ } else {
+ return QString::null;
+ }
+}
+
+//-----------------------------------------------------------------------------
+QString KMComposeWin::bcc() const
+{
+ if ( mEdtBcc && !mEdtBcc->isHidden() ) {
+ return cleanedUpHeaderString( mEdtBcc->text() );
+ } else if ( mRecipientsEditor ) {
+ return mRecipientsEditor->recipientString( Recipient::Bcc );
+ } else {
+ return QString::null;
+ }
+}
+
+//-----------------------------------------------------------------------------
+QString KMComposeWin::from() const
+{
+ return cleanedUpHeaderString( mEdtFrom->text() );
+}
+
+//-----------------------------------------------------------------------------
+QString KMComposeWin::replyTo() const
+{
+ if ( mEdtReplyTo ) {
+ return cleanedUpHeaderString( mEdtReplyTo->text() );
+ } else {
+ return QString::null;
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::verifyWordWrapLengthIsAdequate(const QString &body)
+{
+ int maxLineLength = 0;
+ int curPos;
+ int oldPos = 0;
+ if (mEditor->QTextEdit::wordWrap() == QTextEdit::FixedColumnWidth) {
+ for (curPos = 0; curPos < (int)body.length(); ++curPos)
+ if (body[curPos] == '\n') {
+ if ((curPos - oldPos) > maxLineLength)
+ maxLineLength = curPos - oldPos;
+ oldPos = curPos;
+ }
+ if ((curPos - oldPos) > maxLineLength)
+ maxLineLength = curPos - oldPos;
+ if (mEditor->wrapColumnOrWidth() < maxLineLength) // column
+ mEditor->setWrapColumnOrWidth(maxLineLength);
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::decryptOrStripOffCleartextSignature( QCString& body )
+{
+ QPtrList<Kpgp::Block> pgpBlocks;
+ QStrList nonPgpBlocks;
+ if( Kpgp::Module::prepareMessageForDecryption( body,
+ 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();
+
+ body = nonPgpBlocks.first()
+ + block->text()
+ + nonPgpBlocks.last();
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::setTransport( const QString & transport )
+{
+ kdDebug(5006) << "KMComposeWin::setTransport( \"" << transport << "\" )" << endl;
+ // Don't change the transport combobox if transport is empty
+ if ( transport.isEmpty() )
+ return;
+
+ bool transportFound = false;
+ for ( int i = 0; i < mTransport->count(); ++i ) {
+ if ( mTransport->text(i) == transport ) {
+ transportFound = true;
+ mTransport->setCurrentItem(i);
+ kdDebug(5006) << "transport found, it's no. " << i << " in the list" << endl;
+ break;
+ }
+ }
+ if ( !transportFound ) { // unknown transport
+ kdDebug(5006) << "unknown transport \"" << transport << "\"" << endl;
+ if ( transport.startsWith("smtp://") || transport.startsWith("smtps://") ||
+ transport.startsWith("file://") ) {
+ // set custom transport
+ mTransport->setEditText( transport );
+ }
+ else {
+ // neither known nor custom transport -> use default transport
+ mTransport->setCurrentText( GlobalSettings::self()->defaultTransport() );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::setMsg(KMMessage* newMsg, bool mayAutoSign,
+ bool allowDecryption, bool isModified)
+{
+ //assert(newMsg!=0);
+ if(!newMsg)
+ {
+ kdDebug(5006) << "KMComposeWin::setMsg() : newMsg == 0!" << endl;
+ return;
+ }
+ mMsg = newMsg;
+ KPIM::IdentityManager * im = kmkernel->identityManager();
+
+ mEdtFrom->setText(mMsg->from());
+ mEdtReplyTo->setText(mMsg->replyTo());
+ if ( mClassicalRecipients ) {
+ mEdtTo->setText(mMsg->to());
+ mEdtCc->setText(mMsg->cc());
+ mEdtBcc->setText(mMsg->bcc());
+ } else {
+ mRecipientsEditor->setRecipientString( mMsg->to(), Recipient::To );
+ mRecipientsEditor->setRecipientString( mMsg->cc(), Recipient::Cc );
+ mRecipientsEditor->setRecipientString( mMsg->bcc(), Recipient::Bcc );
+ mRecipientsEditor->setFocusBottom();
+ }
+ mEdtSubject->setText(mMsg->subject());
+
+ const bool stickyIdentity = mBtnIdentity->isChecked();
+ const bool messageHasIdentity = !newMsg->headerField("X-KMail-Identity").isEmpty();
+ if (!stickyIdentity && messageHasIdentity)
+ mId = newMsg->headerField("X-KMail-Identity").stripWhiteSpace().toUInt();
+
+ // don't overwrite the header values with identity specific values
+ // unless the identity is sticky
+ if ( !stickyIdentity ) {
+ disconnect(mIdentity,SIGNAL(identityChanged(uint)),
+ this, SLOT(slotIdentityChanged(uint)));
+ }
+ // load the mId into the gui, sticky or not, without emitting
+ mIdentity->setCurrentIdentity( mId );
+ const uint idToApply = mId;
+ if ( !stickyIdentity ) {
+ connect(mIdentity,SIGNAL(identityChanged(uint)),
+ this, SLOT(slotIdentityChanged(uint)));
+ } else {
+ // load the message's state into the mId, without applying it to the gui
+ // that's so we can detect that the id changed (because a sticky was set)
+ // on apply()
+ if ( messageHasIdentity )
+ mId = newMsg->headerField("X-KMail-Identity").stripWhiteSpace().toUInt();
+ else
+ mId = im->defaultIdentity().uoid();
+ }
+ // manually load the identity's value into the fields; either the one from the
+ // messge, where appropriate, or the one from the sticky identity. What's in
+ // mId might have changed meanwhile, thus the save value
+ slotIdentityChanged( idToApply );
+
+ const KPIM::Identity & ident = im->identityForUoid( mIdentity->currentIdentity() );
+
+ // check for the presence of a DNT header, indicating that MDN's were
+ // requested
+ QString mdnAddr = newMsg->headerField("Disposition-Notification-To");
+ mRequestMDNAction->setChecked( ( !mdnAddr.isEmpty() &&
+ im->thatIsMe( mdnAddr ) ) ||
+ GlobalSettings::self()->requestMDN() );
+
+ // check for presence of a priority header, indicating urgent mail:
+ mUrgentAction->setChecked( newMsg->isUrgent() );
+
+ if (!ident.isXFaceEnabled() || ident.xface().isEmpty())
+ mMsg->removeHeaderField("X-Face");
+ else
+ {
+ QString xface = ident.xface();
+ if (!xface.isEmpty())
+ {
+ int numNL = ( xface.length() - 1 ) / 70;
+ for ( int i = numNL; i > 0; --i )
+ xface.insert( i*70, "\n\t" );
+ mMsg->setHeaderField("X-Face", xface);
+ }
+ }
+
+ // enable/disable encryption if the message was/wasn't encrypted
+ switch ( mMsg->encryptionState() ) {
+ case KMMsgFullyEncrypted: // fall through
+ case KMMsgPartiallyEncrypted:
+ mLastEncryptActionState = true;
+ break;
+ case KMMsgNotEncrypted:
+ mLastEncryptActionState = false;
+ break;
+ default: // nothing
+ break;
+ }
+
+ // enable/disable signing if the message was/wasn't signed
+ switch ( mMsg->signatureState() ) {
+ case KMMsgFullySigned: // fall through
+ case KMMsgPartiallySigned:
+ mLastSignActionState = true;
+ break;
+ case KMMsgNotSigned:
+ mLastSignActionState = false;
+ break;
+ default: // nothing
+ break;
+ }
+
+ // if these headers are present, the state of the message should be overruled
+ if ( mMsg->headers().FindField( "X-KMail-SignatureActionEnabled" ) )
+ mLastSignActionState = (mMsg->headerField( "X-KMail-SignatureActionEnabled" ) == "true");
+ if ( mMsg->headers().FindField( "X-KMail-EncryptActionEnabled" ) )
+ mLastEncryptActionState = (mMsg->headerField( "X-KMail-EncryptActionEnabled" ) == "true");
+ if ( mMsg->headers().FindField( "X-KMail-CryptoMessageFormat" ) )
+ mCryptoModuleAction->setCurrentItem( format2cb( static_cast<Kleo::CryptoMessageFormat>(
+ mMsg->headerField( "X-KMail-CryptoMessageFormat" ).toInt() ) ) );
+
+ mLastIdentityHasSigningKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty();
+ mLastIdentityHasEncryptionKey = !ident.pgpEncryptionKey().isEmpty() || !ident.smimeEncryptionKey().isEmpty();
+
+ if ( Kleo::CryptoBackendFactory::instance()->openpgp() || Kleo::CryptoBackendFactory::instance()->smime() ) {
+ const bool canOpenPGPSign = Kleo::CryptoBackendFactory::instance()->openpgp()
+ && !ident.pgpSigningKey().isEmpty();
+ const bool canSMIMESign = Kleo::CryptoBackendFactory::instance()->smime()
+ && !ident.smimeSigningKey().isEmpty();
+
+ setEncryption( mLastEncryptActionState );
+ setSigning( ( canOpenPGPSign || canSMIMESign ) && mLastSignActionState );
+ }
+ slotUpdateSignatureAndEncrypionStateIndicators();
+
+ // "Attach my public key" is only possible if the user uses OpenPGP
+ // support and he specified his key:
+ mAttachMPK->setEnabled( Kleo::CryptoBackendFactory::instance()->openpgp() &&
+ !ident.pgpEncryptionKey().isEmpty() );
+
+ QString transport = newMsg->headerField("X-KMail-Transport");
+ if (!mBtnTransport->isChecked() && !transport.isEmpty())
+ setTransport( transport );
+
+ if (!mBtnFcc->isChecked())
+ {
+ if (!mMsg->fcc().isEmpty())
+ setFcc(mMsg->fcc());
+ else
+ setFcc(ident.fcc());
+ }
+
+ mDictionaryCombo->setCurrentByDictionary( ident.dictionary() );
+
+ partNode * root = partNode::fromMessage( mMsg );
+
+ KMail::ObjectTreeParser otp; // all defaults are ok
+ otp.parseObjectTree( root );
+
+ KMail::AttachmentCollector ac;
+ ac.setDiveIntoEncryptions( true );
+ ac.setDiveIntoSignatures( true );
+ ac.setDiveIntoMessages( false );
+
+ ac.collectAttachmentsFrom( root );
+
+ for ( std::vector<partNode*>::const_iterator it = ac.attachments().begin() ; it != ac.attachments().end() ; ++it )
+ addAttach( new KMMessagePart( (*it)->msgPart() ) );
+
+ mEditor->setText( otp.textualContent() );
+ mCharset = otp.textualContentCharset();
+ if ( partNode * n = root->findType( DwMime::kTypeText, DwMime::kSubtypeHtml ) )
+ if ( partNode * p = n->parentNode() )
+ if ( p->hasType( DwMime::kTypeMultipart ) &&
+ p->hasSubType( DwMime::kSubtypeAlternative ) )
+ if ( mMsg->headerField( "X-KMail-Markup" ) == "true" ) {
+ toggleMarkup( true );
+
+ // get cte decoded body part
+ mCharset = n->msgPart().charset();
+ QCString bodyDecoded = n->msgPart().bodyDecoded();
+
+ // respect html part charset
+ const QTextCodec *codec = KMMsgBase::codecForName( mCharset );
+ if ( codec ) {
+ mEditor->setText( codec->toUnicode( bodyDecoded ) );
+ } else {
+ mEditor->setText( QString::fromLocal8Bit( bodyDecoded ) );
+ }
+ }
+
+ if ( mCharset.isEmpty() )
+ mCharset = mMsg->charset();
+ if ( mCharset.isEmpty() )
+ mCharset = mDefCharset;
+ setCharset( mCharset );
+
+ /* Handle the special case of non-mime mails */
+ if ( mMsg->numBodyParts() == 0 && otp.textualContent().isEmpty() ) {
+ mCharset=mMsg->charset();
+ if ( mCharset.isEmpty() || mCharset == "default" )
+ mCharset = mDefCharset;
+
+ QCString bodyDecoded = mMsg->bodyDecoded();
+
+ if( allowDecryption )
+ decryptOrStripOffCleartextSignature( bodyDecoded );
+
+ const QTextCodec *codec = KMMsgBase::codecForName(mCharset);
+ if (codec) {
+ mEditor->setText(codec->toUnicode(bodyDecoded));
+ } else
+ mEditor->setText(QString::fromLocal8Bit(bodyDecoded));
+ }
+#ifdef BROKEN_FOR_OPAQUE_SIGNED_OR_ENCRYPTED_MAILS
+ const int num = mMsg->numBodyParts();
+ kdDebug(5006) << "KMComposeWin::setMsg() mMsg->numBodyParts="
+ << mMsg->numBodyParts() << endl;
+
+ if ( num > 0 ) {
+ KMMessagePart bodyPart;
+ int firstAttachment = 0;
+
+ mMsg->bodyPart(1, &bodyPart);
+ if ( bodyPart.typeStr().lower() == "text" &&
+ bodyPart.subtypeStr().lower() == "html" ) {
+ // check whether we are inside a mp/al body part
+ partNode *root = partNode::fromMessage( mMsg );
+ partNode *node = root->findType( DwMime::kTypeText,
+ DwMime::kSubtypeHtml );
+ if ( node && node->parentNode() &&
+ node->parentNode()->hasType( DwMime::kTypeMultipart ) &&
+ node->parentNode()->hasSubType( DwMime::kSubtypeAlternative ) ) {
+ // we have a mp/al body part with a text and an html body
+ kdDebug(5006) << "KMComposeWin::setMsg() : text/html found" << endl;
+ firstAttachment = 2;
+ if ( mMsg->headerField( "X-KMail-Markup" ) == "true" )
+ toggleMarkup( true );
+ }
+ delete root; root = 0;
+ }
+ if ( firstAttachment == 0 ) {
+ mMsg->bodyPart(0, &bodyPart);
+ if ( bodyPart.typeStr().lower() == "text" ) {
+ // we have a mp/mx body with a text body
+ kdDebug(5006) << "KMComposeWin::setMsg() : text/* found" << endl;
+ firstAttachment = 1;
+ }
+ }
+
+ if ( firstAttachment != 0 ) // there's text to show
+ {
+ mCharset = bodyPart.charset();
+ if ( mCharset.isEmpty() || mCharset == "default" )
+ mCharset = mDefCharset;
+
+ QCString bodyDecoded = bodyPart.bodyDecoded();
+
+ if( allowDecryption )
+ decryptOrStripOffCleartextSignature( bodyDecoded );
+
+ // As nobody seems to know the purpose of the following line and
+ // as it breaks word wrapping of long lines if drafts with attachments
+ // are opened for editting in the composer (cf. Bug#41102) I comment it
+ // out. Ingo, 2002-04-21
+ //verifyWordWrapLengthIsAdequate(bodyDecoded);
+
+ const QTextCodec *codec = KMMsgBase::codecForName(mCharset);
+ if (codec)
+ mEditor->setText(codec->toUnicode(bodyDecoded));
+ else
+ mEditor->setText(QString::fromLocal8Bit(bodyDecoded));
+ //mEditor->insertLine("\n", -1); <-- why ?
+ } else mEditor->setText("");
+ for( int i = firstAttachment; i < num; ++i )
+ {
+ KMMessagePart *msgPart = new KMMessagePart;
+ mMsg->bodyPart(i, msgPart);
+ QCString mimeType = msgPart->typeStr().lower() + '/'
+ + msgPart->subtypeStr().lower();
+ // don't add the detached signature as attachment when editting a
+ // PGP/MIME signed message
+ if( mimeType != "application/pgp-signature" ) {
+ addAttach(msgPart);
+ }
+ }
+ } else{
+ mCharset=mMsg->charset();
+ if ( mCharset.isEmpty() || mCharset == "default" )
+ mCharset = mDefCharset;
+
+ QCString bodyDecoded = mMsg->bodyDecoded();
+
+ if( allowDecryption )
+ decryptOrStripOffCleartextSignature( bodyDecoded );
+
+ const QTextCodec *codec = KMMsgBase::codecForName(mCharset);
+ if (codec) {
+ mEditor->setText(codec->toUnicode(bodyDecoded));
+ } else
+ mEditor->setText(QString::fromLocal8Bit(bodyDecoded));
+ }
+
+ setCharset(mCharset);
+#endif // BROKEN_FOR_OPAQUE_SIGNED_OR_ENCRYPTED_MAILS
+
+ if( (GlobalSettings::self()->autoTextSignature()=="auto") && mayAutoSign ) {
+ //
+ // Espen 2000-05-16
+ // Delay the signature appending. It may start a fileseletor.
+ // Not user friendy if this modal fileseletor opens before the
+ // composer.
+ //
+ //QTimer::singleShot( 200, this, SLOT(slotAppendSignature()) );
+ if ( GlobalSettings::self()->prependSignature() ) {
+ QTimer::singleShot( 0, this, SLOT(slotPrependSignature()) );
+ } else {
+ QTimer::singleShot( 0, this, SLOT(slotAppendSignature()) );
+ }
+ }
+
+ if ( mMsg->getCursorPos() > 0 ) {
+ // The message has a cursor position explicitly set, so avoid
+ // changing it when appending the signature.
+ mPreserveUserCursorPosition = true;
+ }
+ setModified( isModified );
+
+ // do this even for new messages
+ mEditor->setCursorPositionFromStart( (unsigned int) mMsg->getCursorPos() );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::setFcc( const QString &idString )
+{
+ // check if the sent-mail folder still exists
+ if ( ! idString.isEmpty() && kmkernel->findFolderById( idString ) ) {
+ mFcc->setFolder( idString );
+ } else {
+ mFcc->setFolder( kmkernel->sentFolder() );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+bool KMComposeWin::isModified() const
+{
+ return ( mEditor->isModified() ||
+ mEdtFrom->edited() ||
+ ( mEdtReplyTo && mEdtReplyTo->edited() ) ||
+ ( mEdtTo && mEdtTo->edited() ) ||
+ ( mEdtCc && mEdtCc->edited() ) ||
+ ( mEdtBcc && mEdtBcc->edited() ) ||
+ ( mRecipientsEditor && mRecipientsEditor->isModified() ) ||
+ mEdtSubject->edited() ||
+ mAtmModified ||
+ ( mTransport->lineEdit() && mTransport->lineEdit()->edited() ) );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::setModified( bool modified )
+{
+ mEditor->setModified( modified );
+ if ( !modified ) {
+ mEdtFrom->setEdited( false );
+ if ( mEdtReplyTo ) mEdtReplyTo->setEdited( false );
+ if ( mEdtTo ) mEdtTo->setEdited( false );
+ if ( mEdtCc ) mEdtCc->setEdited( false );
+ if ( mEdtBcc ) mEdtBcc->setEdited( false );
+ if ( mRecipientsEditor ) mRecipientsEditor->clearModified();
+ mEdtSubject->setEdited( false );
+ mAtmModified = false ;
+ if ( mTransport->lineEdit() )
+ mTransport->lineEdit()->setEdited( false );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+bool KMComposeWin::queryClose ()
+{
+ if ( !mEditor->checkExternalEditorFinished() )
+ return false;
+ if ( kmkernel->shuttingDown() || kapp->sessionSaving() )
+ return true;
+ if ( mComposer && mComposer->isPerformingSignOperation() ) // since the non-gpg-agent gpg plugin gets a passphrase using QDialog::exec()
+ return false; // the user can try to close the window, which destroys mComposer mid-call.
+
+ if ( isModified() ) {
+ bool istemplate = ( mFolder!=0 && mFolder->isTemplates() );
+ const QString savebut = ( istemplate ?
+ i18n("Re&save as Template") :
+ i18n("&Save as Draft") );
+ const QString savetext = ( istemplate ?
+ i18n("Resave this message in the Templates folder. "
+ "It can then be used at a later time.") :
+ i18n("Save this message in the Drafts folder. "
+ "It can then be edited and sent at a later time.") );
+
+ const int rc = KMessageBox::warningYesNoCancel( this,
+ i18n("Do you want to save the message for later or discard it?"),
+ i18n("Close Composer"),
+ KGuiItem(savebut, "filesave", QString::null, savetext),
+ KStdGuiItem::discard() );
+ if ( rc == KMessageBox::Cancel )
+ return false;
+ else if ( rc == KMessageBox::Yes ) {
+ // doSend will close the window. Just return false from this method
+ if ( istemplate ) {
+ slotSaveTemplate();
+ } else {
+ slotSaveDraft();
+ }
+ return false;
+ }
+ }
+ cleanupAutoSave();
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+bool KMComposeWin::userForgotAttachment()
+{
+ bool checkForForgottenAttachments = GlobalSettings::self()->showForgottenAttachmentWarning();
+
+ if ( !checkForForgottenAttachments || ( mAtmList.count() > 0 ) )
+ return false;
+
+
+ QStringList attachWordsList = GlobalSettings::self()->attachmentKeywords();
+
+ if ( attachWordsList.isEmpty() ) {
+ // default value (FIXME: this is duplicated in configuredialog.cpp)
+ attachWordsList << QString::fromLatin1("attachment")
+ << QString::fromLatin1("attached");
+ if ( QString::fromLatin1("attachment") != i18n("attachment") )
+ attachWordsList << i18n("attachment");
+ if ( QString::fromLatin1("attached") != i18n("attached") )
+ attachWordsList << i18n("attached");
+ }
+
+ QRegExp rx ( QString::fromLatin1("\\b") +
+ attachWordsList.join("\\b|\\b") +
+ QString::fromLatin1("\\b") );
+ rx.setCaseSensitive( false );
+
+ bool gotMatch = false;
+
+ // check whether the subject contains one of the attachment key words
+ // unless the message is a reply or a forwarded message
+ QString subj = subject();
+ gotMatch = ( KMMessage::stripOffPrefixes( subj ) == subj )
+ && ( rx.search( subj ) >= 0 );
+
+ if ( !gotMatch ) {
+ // check whether the non-quoted text contains one of the attachment key
+ // words
+ QRegExp quotationRx ("^([ \\t]*([|>:}#]|[A-Za-z]+>))+");
+ for ( int i = 0; i < mEditor->numLines(); ++i ) {
+ QString line = mEditor->textLine( i );
+ gotMatch = ( quotationRx.search( line ) < 0 )
+ && ( rx.search( line ) >= 0 );
+ if ( gotMatch )
+ break;
+ }
+ }
+
+ if ( !gotMatch )
+ return false;
+
+ int rc = KMessageBox::warningYesNoCancel( this,
+ i18n("The message you have composed seems to refer to an "
+ "attached file but you have not attached anything.\n"
+ "Do you want to attach a file to your message?"),
+ i18n("File Attachment Reminder"),
+ i18n("&Attach File..."),
+ i18n("&Send as Is") );
+ if ( rc == KMessageBox::Cancel )
+ return true;
+ if ( rc == KMessageBox::Yes ) {
+ slotAttachFile();
+ //preceed with editing
+ return true;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::applyChanges( bool dontSignNorEncrypt, bool dontDisable )
+{
+ kdDebug(5006) << "entering KMComposeWin::applyChanges" << endl;
+
+ if(!mMsg || mComposer) {
+ kdDebug(5006) << "KMComposeWin::applyChanges() : mMsg == 0!\n" << endl;
+ emit applyChangesDone( false );
+ return;
+ }
+
+ // Make new job and execute it
+ mComposer = new MessageComposer( this );
+ connect( mComposer, SIGNAL( done( bool ) ),
+ this, SLOT( slotComposerDone( bool ) ) );
+
+ // TODO: Add a cancel button for the following operations?
+ // Disable any input to the window, so that we have a snapshot of the
+ // composed stuff
+ if ( !dontDisable ) setEnabled( false );
+ // apply the current state to the composer and let it do it's thing
+ mComposer->setDisableBreaking( mDisableBreaking ); // FIXME
+ mComposer->applyChanges( dontSignNorEncrypt );
+}
+
+void KMComposeWin::slotComposerDone( bool rc )
+{
+ deleteAll( mComposedMessages );
+ mComposedMessages = mComposer->composedMessageList();
+ emit applyChangesDone( rc );
+ delete mComposer;
+ mComposer = 0;
+
+ // re-enable the composewin, the messsage composition is now done
+ setEnabled( true );
+}
+
+const KPIM::Identity & KMComposeWin::identity() const {
+ return kmkernel->identityManager()->identityForUoidOrDefault( mIdentity->currentIdentity() );
+}
+
+uint KMComposeWin::identityUid() const {
+ return mIdentity->currentIdentity();
+}
+
+Kleo::CryptoMessageFormat KMComposeWin::cryptoMessageFormat() const {
+ if ( !mCryptoModuleAction )
+ return Kleo::AutoFormat;
+ return cb2format( mCryptoModuleAction->currentItem() );
+}
+
+bool KMComposeWin::encryptToSelf() const {
+// return !Kpgp::Module::getKpgp() || Kpgp::Module::getKpgp()->encryptToSelf();
+ KConfigGroup group( KMKernel::config(), "Composer" );
+ return group.readBoolEntry( "crypto-encrypt-to-self", true );
+}
+
+bool KMComposeWin::queryExit ()
+{
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+bool KMComposeWin::addAttach(const KURL aUrl)
+{
+ if ( !aUrl.isValid() ) {
+ KMessageBox::sorry( this, i18n( "<qt><p>KMail could not recognize the location of the attachment (%1);</p>"
+ "<p>you have to specify the full path if you wish to attach a file.</p></qt>" )
+ .arg( aUrl.prettyURL() ) );
+ return false;
+ }
+
+ const int maxAttachmentSize = GlobalSettings::maximumAttachmentSize();
+ const uint maximumAttachmentSizeInByte = maxAttachmentSize*1024*1024;
+ if ( aUrl.isLocalFile() && QFileInfo( aUrl.pathOrURL() ).size() > maximumAttachmentSizeInByte ) {
+ KMessageBox::sorry( this, i18n( "<qt><p>Your administrator has disallowed attaching files bigger than %1 MB.</p>" ).arg( maxAttachmentSize ) );
+ return false;
+ }
+
+ KIO::TransferJob *job = KIO::get(aUrl);
+ KIO::Scheduler::scheduleJob( job );
+ atmLoadData ld;
+ ld.url = aUrl;
+ ld.data = QByteArray();
+ ld.insert = false;
+ if( !aUrl.fileEncoding().isEmpty() )
+ ld.encoding = aUrl.fileEncoding().latin1();
+
+ mMapAtmLoadData.insert(job, ld);
+ mAttachJobs[job] = aUrl;
+ connect(job, SIGNAL(result(KIO::Job *)),
+ this, SLOT(slotAttachFileResult(KIO::Job *)));
+ connect(job, SIGNAL(data(KIO::Job *, const QByteArray &)),
+ this, SLOT(slotAttachFileData(KIO::Job *, const QByteArray &)));
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::addAttach(const KMMessagePart* msgPart)
+{
+ mAtmList.append(msgPart);
+
+ // show the attachment listbox if it does not up to now
+ if (mAtmList.count()==1)
+ {
+ mAtmListView->resize(mAtmListView->width(), 50);
+ mAtmListView->show();
+ resize(size());
+ }
+
+ // add a line in the attachment listbox
+ KMAtmListViewItem *lvi = new KMAtmListViewItem( mAtmListView );
+ msgPartToItem(msgPart, lvi);
+ mAtmItemList.append(lvi);
+
+ // the Attach file job has finished, so the possibly present tmp dir can be deleted now.
+ if ( mTempDir != 0 ) {
+ delete mTempDir;
+ mTempDir = 0;
+ }
+
+ connect( lvi, SIGNAL( compress( int ) ),
+ this, SLOT( compressAttach( int ) ) );
+ connect( lvi, SIGNAL( uncompress( int ) ),
+ this, SLOT( uncompressAttach( int ) ) );
+
+ slotUpdateAttachActions();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotUpdateAttachActions()
+{
+ int selectedCount = 0;
+ for ( QPtrListIterator<QListViewItem> it(mAtmItemList); *it; ++it ) {
+ if ( (*it)->isSelected() ) {
+ ++selectedCount;
+ }
+ }
+
+ mAttachRemoveAction->setEnabled( selectedCount >= 1 );
+ mAttachSaveAction->setEnabled( selectedCount == 1 );
+ mAttachPropertiesAction->setEnabled( selectedCount == 1 );
+}
+
+
+//-----------------------------------------------------------------------------
+
+QString KMComposeWin::prettyMimeType( const QString& type )
+{
+ QString t = type.lower();
+ KServiceType::Ptr st = KServiceType::serviceType( t );
+ return st ? st->comment() : t;
+}
+
+void KMComposeWin::msgPartToItem(const KMMessagePart* msgPart,
+ KMAtmListViewItem *lvi, bool loadDefaults)
+{
+ assert(msgPart != 0);
+
+ if (!msgPart->fileName().isEmpty())
+ lvi->setText(0, msgPart->fileName());
+ else
+ lvi->setText(0, msgPart->name());
+ lvi->setText(1, KIO::convertSize( msgPart->decodedSize()));
+ lvi->setText(2, msgPart->contentTransferEncodingStr());
+ lvi->setText(3, prettyMimeType(msgPart->typeStr() + "/" + msgPart->subtypeStr()));
+ lvi->setAttachmentSize(msgPart->decodedSize());
+
+ if ( loadDefaults ) {
+ if( canSignEncryptAttachments() ) {
+ lvi->enableCryptoCBs( true );
+ lvi->setEncrypt( mEncryptAction->isChecked() );
+ lvi->setSign( mSignAction->isChecked() );
+ } else {
+ lvi->enableCryptoCBs( false );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::removeAttach(const QString &aUrl)
+{
+ int idx;
+ KMMessagePart* msgPart;
+ for(idx=0,msgPart=mAtmList.first(); msgPart;
+ msgPart=mAtmList.next(),idx++) {
+ if (msgPart->name() == aUrl) {
+ removeAttach(idx);
+ return;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::removeAttach(int idx)
+{
+ mAtmModified = true;
+ mAtmList.remove(idx);
+ delete mAtmItemList.take(idx);
+
+ if( mAtmList.isEmpty() )
+ {
+ mAtmListView->hide();
+ mAtmListView->setMinimumSize(0, 0);
+ resize(size());
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+bool KMComposeWin::encryptFlagOfAttachment(int idx)
+{
+ return (int)(mAtmItemList.count()) > idx
+ ? static_cast<KMAtmListViewItem*>( mAtmItemList.at( idx ) )->isEncrypt()
+ : false;
+}
+
+
+//-----------------------------------------------------------------------------
+bool KMComposeWin::signFlagOfAttachment(int idx)
+{
+ return (int)(mAtmItemList.count()) > idx
+ ? ((KMAtmListViewItem*)(mAtmItemList.at( idx )))->isSign()
+ : false;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::addrBookSelInto()
+{
+ if ( mClassicalRecipients ) {
+ if ( GlobalSettings::self()->addresseeSelectorType() ==
+ GlobalSettings::EnumAddresseeSelectorType::New ) {
+ addrBookSelIntoNew();
+ } else {
+ addrBookSelIntoOld();
+ }
+ } else {
+ kdWarning() << "To be implemented: call recipients picker." << endl;
+ }
+}
+
+void KMComposeWin::addrBookSelIntoOld()
+{
+ AddressesDialog dlg( this );
+ QString txt;
+ QStringList lst;
+
+ txt = to();
+ if ( !txt.isEmpty() ) {
+ lst = KPIM::splitEmailAddrList( txt );
+ dlg.setSelectedTo( lst );
+ }
+
+ txt = mEdtCc->text();
+ if ( !txt.isEmpty() ) {
+ lst = KPIM::splitEmailAddrList( txt );
+ dlg.setSelectedCC( lst );
+ }
+
+ txt = mEdtBcc->text();
+ if ( !txt.isEmpty() ) {
+ lst = KPIM::splitEmailAddrList( txt );
+ dlg.setSelectedBCC( lst );
+ }
+
+ dlg.setRecentAddresses( RecentAddresses::self( KMKernel::config() )->kabcAddresses() );
+
+ if (dlg.exec()==QDialog::Rejected) return;
+
+ mEdtTo->setText( dlg.to().join(", ") );
+ mEdtTo->setEdited( true );
+
+ mEdtCc->setText( dlg.cc().join(", ") );
+ mEdtCc->setEdited( true );
+
+ mEdtBcc->setText( dlg.bcc().join(", ") );
+ mEdtBcc->setEdited( true );
+
+ //Make sure BCC field is shown if needed
+ if ( !mEdtBcc->text().isEmpty() ) {
+ mShowHeaders |= HDR_BCC;
+ rethinkFields( false );
+ }
+}
+
+void KMComposeWin::addrBookSelIntoNew()
+{
+ AddresseeEmailSelection selection;
+
+ AddresseeSelectorDialog dlg( &selection );
+
+ QString txt;
+ QStringList lst;
+
+ txt = to();
+ if ( !txt.isEmpty() ) {
+ lst = KPIM::splitEmailAddrList( txt );
+ selection.setSelectedTo( lst );
+ }
+
+ txt = mEdtCc->text();
+ if ( !txt.isEmpty() ) {
+ lst = KPIM::splitEmailAddrList( txt );
+ selection.setSelectedCC( lst );
+ }
+
+ txt = mEdtBcc->text();
+ if ( !txt.isEmpty() ) {
+ lst = KPIM::splitEmailAddrList( txt );
+ selection.setSelectedBCC( lst );
+ }
+
+ if (dlg.exec()==QDialog::Rejected) return;
+
+ QStringList list = selection.to() + selection.toDistributionLists();
+ mEdtTo->setText( list.join(", ") );
+ mEdtTo->setEdited( true );
+
+ list = selection.cc() + selection.ccDistributionLists();
+ mEdtCc->setText( list.join(", ") );
+ mEdtCc->setEdited( true );
+
+ list = selection.bcc() + selection.bccDistributionLists();
+ mEdtBcc->setText( list.join(", ") );
+ mEdtBcc->setEdited( true );
+
+ //Make sure BCC field is shown if needed
+ if ( !mEdtBcc->text().isEmpty() ) {
+ mShowHeaders |= HDR_BCC;
+ rethinkFields( false );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::setCharset(const QCString& aCharset, bool forceDefault)
+{
+ if ((forceDefault && GlobalSettings::self()->forceReplyCharset()) || aCharset.isEmpty())
+ mCharset = mDefCharset;
+ else
+ mCharset = aCharset.lower();
+
+ if ( mCharset.isEmpty() || mCharset == "default" )
+ mCharset = mDefCharset;
+
+ if (mAutoCharset)
+ {
+ mEncodingAction->setCurrentItem( 0 );
+ return;
+ }
+
+ QStringList encodings = mEncodingAction->items();
+ int i = 0;
+ bool charsetFound = false;
+ for ( QStringList::Iterator it = encodings.begin(); it != encodings.end();
+ ++it, i++ )
+ {
+ if (i > 0 && ((mCharset == "us-ascii" && i == 1) ||
+ (i != 1 && KGlobal::charsets()->codecForName(
+ KGlobal::charsets()->encodingForName(*it))
+ == KGlobal::charsets()->codecForName(mCharset))))
+ {
+ mEncodingAction->setCurrentItem( i );
+ slotSetCharset();
+ charsetFound = true;
+ break;
+ }
+ }
+ if (!aCharset.isEmpty() && !charsetFound) setCharset("", true);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotAddrBook()
+{
+ KAddrBookExternal::openAddressBook(this);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotAddrBookFrom()
+{
+ addrBookSelInto();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotAddrBookReplyTo()
+{
+ addrBookSelInto();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotAddrBookTo()
+{
+ addrBookSelInto();
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotAttachFile()
+{
+ // Create File Dialog and return selected file(s)
+ // We will not care about any permissions, existence or whatsoever in
+ // this function.
+
+ KFileDialog fdlg(QString::null, QString::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.exec();
+ KURL::List files = fdlg.selectedURLs();
+
+ for (KURL::List::Iterator it = files.begin(); it != files.end(); ++it)
+ addAttach(*it);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotAttachFileData(KIO::Job *job, const QByteArray &data)
+{
+ QMap<KIO::Job*, atmLoadData>::Iterator it = mMapAtmLoadData.find(job);
+ assert(it != mMapAtmLoadData.end());
+ QBuffer buff((*it).data);
+ buff.open(IO_WriteOnly | IO_Append);
+ buff.writeBlock(data.data(), data.size());
+ buff.close();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotAttachFileResult(KIO::Job *job)
+{
+ QMap<KIO::Job*, atmLoadData>::Iterator it = mMapAtmLoadData.find(job);
+ assert(it != mMapAtmLoadData.end());
+ KURL attachURL;
+ QMap<KIO::Job*, KURL>::iterator jit = mAttachJobs.find(job);
+ bool attachURLfound = (jit != mAttachJobs.end());
+ if (attachURLfound)
+ {
+ attachURL = jit.data();
+ mAttachJobs.remove(jit);
+ }
+ if (job->error())
+ {
+ mMapAtmLoadData.remove(it);
+ job->showErrorDialog();
+ if (attachURLfound)
+ emit attachmentAdded(attachURL, false);
+ return;
+ }
+ if ((*it).insert)
+ {
+ (*it).data.resize((*it).data.size() + 1);
+ (*it).data[(*it).data.size() - 1] = '\0';
+ if ( const QTextCodec * codec = KGlobal::charsets()->codecForName((*it).encoding) )
+ mEditor->insert( codec->toUnicode( (*it).data ) );
+ else
+ mEditor->insert( QString::fromLocal8Bit( (*it).data ) );
+ mMapAtmLoadData.remove(it);
+ if (attachURLfound)
+ emit attachmentAdded(attachURL, true);
+ return;
+ }
+ QCString partCharset;
+ if ( !( *it ).url.fileEncoding().isEmpty() ) {
+ partCharset = QCString( ( *it ).url.fileEncoding().latin1() );
+ } else {
+ EncodingDetector ed;
+ KLocale *loc = KGlobal::locale();
+ ed.setAutoDetectLanguage( EncodingDetector::scriptForLanguageCode ( loc->language() ) );
+ ed.analyze( (*it).data );
+ partCharset = ed.encoding();
+ if ( partCharset.isEmpty() ) //shouldn't happen
+ partCharset = mCharset;
+ }
+
+ KMMessagePart* msgPart;
+
+ KCursorSaver busy(KBusyPtr::busy());
+ QString name( (*it).url.fileName() );
+ // ask the job for the mime type of the file
+ QString mimeType = static_cast<KIO::MimetypeJob*>(job)->mimetype();
+
+ if ( name.isEmpty() ) {
+ // URL ends with '/' (e.g. http://www.kde.org/)
+ // guess a reasonable filename
+ if( mimeType == "text/html" )
+ name = "index.html";
+ else {
+ // try to determine a reasonable extension
+ QStringList patterns( KMimeType::mimeType( mimeType )->patterns() );
+ QString ext;
+ if( !patterns.isEmpty() ) {
+ ext = patterns[0];
+ int i = ext.findRev( '.' );
+ if( i == -1 )
+ ext.prepend( '.' );
+ else if( i > 0 )
+ ext = ext.mid( i );
+ }
+ name = QString("unknown") += ext;
+ }
+ }
+
+ name.truncate( 256 ); // is this needed?
+
+ QCString encoding = KMMsgBase::autoDetectCharset(partCharset,
+ KMMessage::preferredCharsets(), name);
+ if ( encoding.isEmpty() )
+ encoding = "utf-8";
+
+ QCString encName;
+ if ( GlobalSettings::self()->outlookCompatibleAttachments() )
+ encName = KMMsgBase::encodeRFC2047String( name, encoding );
+ else
+ encName = KMMsgBase::encodeRFC2231String( name, encoding );
+ bool RFC2231encoded = false;
+ if ( !GlobalSettings::self()->outlookCompatibleAttachments() )
+ RFC2231encoded = name != QString( encName );
+
+ // create message part
+ msgPart = new KMMessagePart;
+ msgPart->setName(name);
+ QValueList<int> allowedCTEs;
+ if ( mimeType == "message/rfc822" ) {
+ msgPart->setMessageBody( (*it).data );
+ allowedCTEs << DwMime::kCte7bit;
+ allowedCTEs << DwMime::kCte8bit;
+ } else {
+ msgPart->setBodyAndGuessCte((*it).data, allowedCTEs,
+ !kmkernel->msgSender()->sendQuotedPrintable());
+ kdDebug(5006) << "autodetected cte: " << msgPart->cteStr() << endl;
+ }
+ int slash = mimeType.find( '/' );
+ if( slash == -1 )
+ slash = mimeType.length();
+ msgPart->setTypeStr( mimeType.left( slash ).latin1() );
+ msgPart->setSubtypeStr( mimeType.mid( slash + 1 ).latin1() );
+ msgPart->setContentDisposition(QCString("attachment;\n\tfilename")
+ + ( RFC2231encoded ? "*=" + encName : "=\"" + encName + '"' ) );
+
+ mMapAtmLoadData.remove(it);
+
+ msgPart->setCharset(partCharset);
+
+ // show message part dialog, if not configured away (default):
+ KConfigGroup composer(KMKernel::config(), "Composer");
+ if ( GlobalSettings::self()->showMessagePartDialogOnAttach() ) {
+ const KCursorSaver saver( QCursor::ArrowCursor );
+ KMMsgPartDialogCompat dlg(mMainWidget);
+ int encodings = 0;
+ for ( QValueListConstIterator<int> it = allowedCTEs.begin() ;
+ it != allowedCTEs.end() ; ++it )
+ switch ( *it ) {
+ case DwMime::kCteBase64: encodings |= KMMsgPartDialog::Base64; break;
+ case DwMime::kCteQp: encodings |= KMMsgPartDialog::QuotedPrintable; break;
+ case DwMime::kCte7bit: encodings |= KMMsgPartDialog::SevenBit; break;
+ case DwMime::kCte8bit: encodings |= KMMsgPartDialog::EightBit; break;
+ default: ;
+ }
+ dlg.setShownEncodings( encodings );
+ dlg.setMsgPart(msgPart);
+ if (!dlg.exec()) {
+ delete msgPart;
+ msgPart = 0;
+ if (attachURLfound)
+ emit attachmentAdded(attachURL, false);
+ return;
+ }
+ }
+ mAtmModified = true;
+ if (msgPart->typeStr().lower() != "text") msgPart->setCharset(QCString());
+
+ // add the new attachment to the list
+ addAttach(msgPart);
+
+ if (attachURLfound)
+ emit attachmentAdded(attachURL, true);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotInsertFile()
+{
+ KFileDialog fdlg(QString::null, QString::null, this, 0, true);
+ fdlg.setOperationMode( KFileDialog::Opening );
+ fdlg.okButton()->setText(i18n("&Insert"));
+ fdlg.setCaption(i18n("Insert File"));
+ fdlg.toolBar()->insertCombo(KMMsgBase::supportedEncodings(false), 4711,
+ false, 0, 0, 0);
+ KComboBox *combo = fdlg.toolBar()->getCombo(4711);
+ for (int i = 0; i < combo->count(); i++)
+ if (KGlobal::charsets()->codecForName(KGlobal::charsets()->
+ encodingForName(combo->text(i)))
+ == QTextCodec::codecForLocale()) combo->setCurrentItem(i);
+ if (!fdlg.exec()) return;
+
+ KURL u = fdlg.selectedURL();
+ mRecentAction->addURL(u);
+ // Prevent race condition updating list when multiple composers are open
+ {
+ KConfig *config = KMKernel::config();
+ KConfigGroupSaver saver( config, "Composer" );
+ QString encoding = KGlobal::charsets()->encodingForName(combo->currentText()).latin1();
+ QStringList urls = config->readListEntry( "recent-urls" );
+ QStringList encodings = config->readListEntry( "recent-encodings" );
+ // Prevent config file from growing without bound
+ // Would be nicer to get this constant from KRecentFilesAction
+ uint mMaxRecentFiles = 30;
+ while (urls.count() > mMaxRecentFiles)
+ urls.erase( urls.fromLast() );
+ while (encodings.count() > mMaxRecentFiles)
+ encodings.erase( encodings.fromLast() );
+ // sanity check
+ if (urls.count() != encodings.count()) {
+ urls.clear();
+ encodings.clear();
+ }
+ urls.prepend( u.prettyURL() );
+ encodings.prepend( encoding );
+ config->writeEntry( "recent-urls", urls );
+ config->writeEntry( "recent-encodings", encodings );
+ mRecentAction->saveEntries( config );
+ }
+ slotInsertRecentFile(u);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotInsertRecentFile(const KURL& u)
+{
+ if (u.fileName().isEmpty()) return;
+
+ KIO::Job *job = KIO::get(u);
+ atmLoadData ld;
+ ld.url = u;
+ ld.data = QByteArray();
+ ld.insert = true;
+ // Get the encoding previously used when inserting this file
+ {
+ KConfig *config = KMKernel::config();
+ KConfigGroupSaver saver( config, "Composer" );
+ QStringList urls = config->readListEntry( "recent-urls" );
+ QStringList encodings = config->readListEntry( "recent-encodings" );
+ int index = urls.findIndex( u.prettyURL() );
+ if (index != -1) {
+ QString encoding = encodings[ index ];
+ ld.encoding = encoding.latin1();
+ }
+ }
+ mMapAtmLoadData.insert(job, ld);
+ connect(job, SIGNAL(result(KIO::Job *)),
+ this, SLOT(slotAttachFileResult(KIO::Job *)));
+ connect(job, SIGNAL(data(KIO::Job *, const QByteArray &)),
+ this, SLOT(slotAttachFileData(KIO::Job *, const QByteArray &)));
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotSetCharset()
+{
+ if (mEncodingAction->currentItem() == 0)
+ {
+ mAutoCharset = true;
+ return;
+ }
+ mAutoCharset = false;
+
+ mCharset = KGlobal::charsets()->encodingForName( mEncodingAction->
+ currentText() ).latin1();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotSelectCryptoModule( bool init )
+{
+ if ( !init ) {
+ setModified( true );
+ }
+ if( canSignEncryptAttachments() ) {
+ // if the encrypt/sign columns are hidden then show them
+ if( 0 == mAtmListView->columnWidth( mAtmColEncrypt ) ) {
+ // set/unset signing/encryption for all attachments according to the
+ // state of the global sign/encrypt action
+ if( !mAtmList.isEmpty() ) {
+ for( KMAtmListViewItem* lvi = static_cast<KMAtmListViewItem*>( mAtmItemList.first() );
+ lvi;
+ lvi = static_cast<KMAtmListViewItem*>( mAtmItemList.next() ) ) {
+ lvi->setSign( mSignAction->isChecked() );
+ lvi->setEncrypt( mEncryptAction->isChecked() );
+ }
+ }
+ int totalWidth = 0;
+ // determine the total width of the columns
+ for( int col=0; col < mAtmColEncrypt; col++ )
+ totalWidth += mAtmListView->columnWidth( col );
+ int reducedTotalWidth = totalWidth - mAtmEncryptColWidth
+ - mAtmSignColWidth;
+ // reduce the width of all columns so that the encrypt and sign column
+ // fit
+ int usedWidth = 0;
+ for( int col=0; col < mAtmColEncrypt-1; col++ ) {
+ int newWidth = mAtmListView->columnWidth( col ) * reducedTotalWidth
+ / totalWidth;
+ mAtmListView->setColumnWidth( col, newWidth );
+ usedWidth += newWidth;
+ }
+ // the last column before the encrypt column gets the remaining space
+ // (because of rounding errors the width of this column isn't calculated
+ // the same way as the width of the other columns)
+ mAtmListView->setColumnWidth( mAtmColEncrypt-1,
+ reducedTotalWidth - usedWidth );
+ mAtmListView->setColumnWidth( mAtmColEncrypt, mAtmEncryptColWidth );
+ mAtmListView->setColumnWidth( mAtmColSign, mAtmSignColWidth );
+ for( KMAtmListViewItem* lvi = static_cast<KMAtmListViewItem*>( mAtmItemList.first() );
+ lvi;
+ lvi = static_cast<KMAtmListViewItem*>( mAtmItemList.next() ) ) {
+ lvi->enableCryptoCBs( true );
+ }
+ }
+ } else {
+ // if the encrypt/sign columns are visible then hide them
+ if( 0 != mAtmListView->columnWidth( mAtmColEncrypt ) ) {
+ mAtmEncryptColWidth = mAtmListView->columnWidth( mAtmColEncrypt );
+ mAtmSignColWidth = mAtmListView->columnWidth( mAtmColSign );
+ int totalWidth = 0;
+ // determine the total width of the columns
+ for( int col=0; col < mAtmListView->columns(); col++ )
+ totalWidth += mAtmListView->columnWidth( col );
+ int reducedTotalWidth = totalWidth - mAtmEncryptColWidth
+ - mAtmSignColWidth;
+ // increase the width of all columns so that the visible columns take
+ // up the whole space
+ int usedWidth = 0;
+ for( int col=0; col < mAtmColEncrypt-1; col++ ) {
+ int newWidth = mAtmListView->columnWidth( col ) * totalWidth
+ / reducedTotalWidth;
+ mAtmListView->setColumnWidth( col, newWidth );
+ usedWidth += newWidth;
+ }
+ // the last column before the encrypt column gets the remaining space
+ // (because of rounding errors the width of this column isn't calculated
+ // the same way as the width of the other columns)
+ mAtmListView->setColumnWidth( mAtmColEncrypt-1, totalWidth - usedWidth );
+ mAtmListView->setColumnWidth( mAtmColEncrypt, 0 );
+ mAtmListView->setColumnWidth( mAtmColSign, 0 );
+ for( KMAtmListViewItem* lvi = static_cast<KMAtmListViewItem*>( mAtmItemList.first() );
+ lvi;
+ lvi = static_cast<KMAtmListViewItem*>( mAtmItemList.next() ) ) {
+ lvi->enableCryptoCBs( false );
+ }
+ }
+ }
+}
+
+static void showExportError( QWidget * w, const GpgME::Error & err ) {
+ assert( err );
+ const QString msg = i18n("<qt><p>An error occurred while trying to export "
+ "the key from the backend:</p>"
+ "<p><b>%1</b></p></qt>")
+ .arg( QString::fromLocal8Bit( err.asString() ) );
+ KMessageBox::error( w, msg, i18n("Key Export Failed") );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotInsertMyPublicKey()
+{
+ // get PGP user id for the chosen identity
+ mFingerprint =
+ kmkernel->identityManager()->identityForUoidOrDefault( mIdentity->currentIdentity() ).pgpEncryptionKey();
+ if ( !mFingerprint.isEmpty() )
+ startPublicKeyExport();
+}
+
+void KMComposeWin::startPublicKeyExport() {
+ if ( mFingerprint.isEmpty() || !Kleo::CryptoBackendFactory::instance()->openpgp() )
+ return;
+ Kleo::ExportJob * job = Kleo::CryptoBackendFactory::instance()->openpgp()->publicKeyExportJob( true );
+ assert( job );
+
+ connect( job, SIGNAL(result(const GpgME::Error&,const QByteArray&)),
+ this, SLOT(slotPublicKeyExportResult(const GpgME::Error&,const QByteArray&)) );
+
+ const GpgME::Error err = job->start( mFingerprint );
+ if ( err )
+ showExportError( this, err );
+ else
+ (void)new Kleo::ProgressDialog( job, i18n("Exporting key..."), this );
+}
+
+void KMComposeWin::slotPublicKeyExportResult( const GpgME::Error & err, const QByteArray & keydata ) {
+ if ( err ) {
+ showExportError( this, err );
+ return;
+ }
+
+ // create message part
+ KMMessagePart * msgPart = new KMMessagePart();
+ msgPart->setName( i18n("OpenPGP key 0x%1").arg( mFingerprint ) );
+ msgPart->setTypeStr("application");
+ msgPart->setSubtypeStr("pgp-keys");
+ QValueList<int> dummy;
+ msgPart->setBodyAndGuessCte(keydata, dummy, false);
+ msgPart->setContentDisposition( "attachment;\n\tfilename=0x" + QCString( mFingerprint.latin1() ) + ".asc" );
+
+ // add the new attachment to the list
+ addAttach(msgPart);
+ rethinkFields(); //work around initial-size bug in Qt-1.32
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotInsertPublicKey()
+{
+ Kleo::KeySelectionDialog dlg( i18n("Attach Public OpenPGP Key"),
+ i18n("Select the public key which should "
+ "be attached."),
+ std::vector<GpgME::Key>(),
+ Kleo::KeySelectionDialog::PublicKeys|Kleo::KeySelectionDialog::OpenPGPKeys,
+ false /* no multi selection */,
+ false /* no remember choice box */,
+ this, "attach public key selection dialog" );
+ if ( dlg.exec() != QDialog::Accepted )
+ return;
+
+ mFingerprint = dlg.fingerprint();
+ startPublicKeyExport();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotAttachPopupMenu(QListViewItem *, const QPoint &, int)
+{
+ if (!mAttachMenu)
+ {
+ mAttachMenu = new QPopupMenu(this);
+
+ mOpenId = mAttachMenu->insertItem(i18n("to open", "Open"), this,
+ SLOT(slotAttachOpen()));
+ mOpenWithId = mAttachMenu->insertItem(i18n("Open With..."), this,
+ SLOT(slotAttachOpenWith()));
+ mViewId = mAttachMenu->insertItem(i18n("to view", "View"), this,
+ SLOT(slotAttachView()));
+ mEditId = mAttachMenu->insertItem( i18n("Edit"), this, SLOT(slotAttachEdit()) );
+ mEditWithId = mAttachMenu->insertItem( i18n("Edit With..."), this,
+ SLOT(slotAttachEditWith()) );
+ mRemoveId = mAttachMenu->insertItem(i18n("Remove"), this, SLOT(slotAttachRemove()));
+ mSaveAsId = mAttachMenu->insertItem( SmallIconSet("filesaveas"), i18n("Save As..."), this,
+ SLOT( slotAttachSave() ) );
+ mPropertiesId = mAttachMenu->insertItem( i18n("Properties"), this,
+ SLOT( slotAttachProperties() ) );
+ mAttachMenu->insertSeparator();
+ mAttachMenu->insertItem(i18n("Add Attachment..."), this, SLOT(slotAttachFile()));
+ }
+
+ int selectedCount = 0;
+ for ( QPtrListIterator<QListViewItem> it(mAtmItemList); *it; ++it ) {
+ if ( (*it)->isSelected() ) {
+ ++selectedCount;
+ }
+ }
+
+ mAttachMenu->setItemEnabled( mOpenId, selectedCount > 0 );
+ mAttachMenu->setItemEnabled( mOpenWithId, selectedCount > 0 );
+ mAttachMenu->setItemEnabled( mViewId, selectedCount > 0 );
+ mAttachMenu->setItemEnabled( mEditId, selectedCount == 1 );
+ mAttachMenu->setItemEnabled( mEditWithId, selectedCount == 1 );
+ mAttachMenu->setItemEnabled( mRemoveId, selectedCount > 0 );
+ mAttachMenu->setItemEnabled( mSaveAsId, selectedCount == 1 );
+ mAttachMenu->setItemEnabled( mPropertiesId, selectedCount == 1 );
+
+ mAttachMenu->popup(QCursor::pos());
+}
+
+//-----------------------------------------------------------------------------
+int KMComposeWin::currentAttachmentNum()
+{
+ int i = 0;
+ for ( QPtrListIterator<QListViewItem> it(mAtmItemList); *it; ++it, ++i )
+ if ( *it == mAtmListView->currentItem() )
+ return i;
+ return -1;
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotAttachProperties()
+{
+ int idx = currentAttachmentNum();
+
+ if (idx < 0) return;
+
+ KMMessagePart* msgPart = mAtmList.at(idx);
+ msgPart->setCharset(mCharset);
+
+ KMMsgPartDialogCompat dlg(mMainWidget);
+ dlg.setMsgPart(msgPart);
+ KMAtmListViewItem* listItem = (KMAtmListViewItem*)(mAtmItemList.at(idx));
+ if( canSignEncryptAttachments() && listItem ) {
+ dlg.setCanSign( true );
+ dlg.setCanEncrypt( true );
+ dlg.setSigned( listItem->isSign() );
+ dlg.setEncrypted( listItem->isEncrypt() );
+ } else {
+ dlg.setCanSign( false );
+ dlg.setCanEncrypt( false );
+ }
+ if (dlg.exec())
+ {
+ mAtmModified = true;
+ // values may have changed, so recreate the listbox line
+ if( listItem ) {
+ msgPartToItem(msgPart, listItem);
+ if( canSignEncryptAttachments() ) {
+ listItem->setSign( dlg.isSigned() );
+ listItem->setEncrypt( dlg.isEncrypted() );
+ }
+ }
+ }
+ if (msgPart->typeStr().lower() != "text") msgPart->setCharset(QCString());
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::compressAttach( int idx )
+{
+ if (idx < 0) return;
+
+ unsigned int i;
+ for ( i = 0; i < mAtmItemList.count(); ++i )
+ if ( mAtmItemList.at( i )->itemPos() == idx )
+ break;
+
+ if ( i > mAtmItemList.count() )
+ return;
+
+ KMMessagePart* msgPart;
+ msgPart = mAtmList.at( i );
+ QByteArray array;
+ QBuffer dev( array );
+ KZip zip( &dev );
+ QByteArray decoded = msgPart->bodyDecodedBinary();
+ if ( ! zip.open( IO_WriteOnly ) ) {
+ KMessageBox::sorry(0, i18n("KMail could not compress the file.") );
+ static_cast<KMAtmListViewItem*>( mAtmItemList.at( i ) )->setCompress( false );
+ return;
+ }
+
+ zip.setCompression( KZip::DeflateCompression );
+ if ( ! zip.writeFile( msgPart->name(), "", "", decoded.size(),
+ decoded.data() ) ) {
+ KMessageBox::sorry(0, i18n("KMail could not compress the file.") );
+ static_cast<KMAtmListViewItem*>( mAtmItemList.at( i ) )->setCompress( false );
+ return;
+ }
+ zip.close();
+ if ( array.size() >= decoded.size() ) {
+ if ( KMessageBox::questionYesNo( this, i18n("The compressed file is larger "
+ "than the original. Do you want to keep the original one?" ), QString::null, i18n("Keep"), i18n("Compress") )
+ == KMessageBox::Yes ) {
+ static_cast<KMAtmListViewItem*>( mAtmItemList.at( i ) )->setCompress( false );
+ return;
+ }
+ }
+ static_cast<KMAtmListViewItem*>( mAtmItemList.at( i ) )->setUncompressedCodec(
+ msgPart->cteStr() );
+
+ msgPart->setCteStr( "base64" );
+ msgPart->setBodyEncodedBinary( array );
+ QString name = msgPart->name() + ".zip";
+
+ msgPart->setName( name );
+
+ QCString cDisp = "attachment;";
+ QCString encoding = KMMsgBase::autoDetectCharset( msgPart->charset(),
+ KMMessage::preferredCharsets(), name );
+ kdDebug(5006) << "encoding: " << encoding << endl;
+ if ( encoding.isEmpty() ) encoding = "utf-8";
+ kdDebug(5006) << "encoding after: " << encoding << endl;
+ QCString encName;
+ if ( GlobalSettings::self()->outlookCompatibleAttachments() )
+ encName = KMMsgBase::encodeRFC2047String( name, encoding );
+ else
+ encName = KMMsgBase::encodeRFC2231String( name, encoding );
+
+ cDisp += "\n\tfilename";
+ if ( name != QString( encName ) )
+ cDisp += "*=" + encName;
+ else
+ cDisp += "=\"" + encName + '"';
+ msgPart->setContentDisposition( cDisp );
+
+ static_cast<KMAtmListViewItem*>( mAtmItemList.at( i ) )->setUncompressedMimeType(
+ msgPart->typeStr(), msgPart->subtypeStr() );
+ msgPart->setTypeStr( "application" );
+ msgPart->setSubtypeStr( "x-zip" );
+
+ KMAtmListViewItem* listItem = static_cast<KMAtmListViewItem*>( mAtmItemList.at( i ) );
+ msgPartToItem( msgPart, listItem, false );
+}
+
+//-----------------------------------------------------------------------------
+
+void KMComposeWin::uncompressAttach( int idx )
+{
+ if (idx < 0) return;
+
+ unsigned int i;
+ for ( i = 0; i < mAtmItemList.count(); ++i )
+ if ( mAtmItemList.at( i )->itemPos() == idx )
+ break;
+
+ if ( i > mAtmItemList.count() )
+ return;
+
+ KMMessagePart* msgPart;
+ msgPart = mAtmList.at( i );
+
+ QBuffer dev( msgPart->bodyDecodedBinary() );
+ KZip zip( &dev );
+ QByteArray decoded;
+
+ decoded = msgPart->bodyDecodedBinary();
+ if ( ! zip.open( IO_ReadOnly ) ) {
+ KMessageBox::sorry(0, i18n("KMail could not uncompress the file.") );
+ static_cast<KMAtmListViewItem *>( mAtmItemList.at( i ) )->setCompress( true );
+ return;
+ }
+ const KArchiveDirectory *dir = zip.directory();
+
+ KZipFileEntry *entry;
+ if ( dir->entries().count() != 1 ) {
+ KMessageBox::sorry(0, i18n("KMail could not uncompress the file.") );
+ static_cast<KMAtmListViewItem *>( mAtmItemList.at( i ) )->setCompress( true );
+ return;
+ }
+ entry = (KZipFileEntry*)dir->entry( dir->entries()[0] );
+
+ msgPart->setCteStr(
+ static_cast<KMAtmListViewItem*>( mAtmItemList.at(i) )->uncompressedCodec() );
+
+ msgPart->setBodyEncodedBinary( entry->data() );
+ QString name = entry->name();
+ msgPart->setName( name );
+
+ zip.close();
+
+ QCString cDisp = "attachment;";
+ QCString encoding = KMMsgBase::autoDetectCharset( msgPart->charset(),
+ KMMessage::preferredCharsets(), name );
+ if ( encoding.isEmpty() ) encoding = "utf-8";
+
+ QCString encName;
+ if ( GlobalSettings::self()->outlookCompatibleAttachments() )
+ encName = KMMsgBase::encodeRFC2047String( name, encoding );
+ else
+ encName = KMMsgBase::encodeRFC2231String( name, encoding );
+
+ cDisp += "\n\tfilename";
+ if ( name != QString( encName ) )
+ cDisp += "*=" + encName;
+ else
+ cDisp += "=\"" + encName + '"';
+ msgPart->setContentDisposition( cDisp );
+
+ QCString type, subtype;
+ static_cast<KMAtmListViewItem*>( mAtmItemList.at( i ) )->uncompressedMimeType( type,
+ subtype );
+
+ msgPart->setTypeStr( type );
+ msgPart->setSubtypeStr( subtype );
+
+ KMAtmListViewItem* listItem = static_cast<KMAtmListViewItem*>(mAtmItemList.at( i ));
+ msgPartToItem( msgPart, listItem, false );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotAttachView()
+{
+ int i = 0;
+ for ( QPtrListIterator<QListViewItem> it(mAtmItemList); *it; ++it, ++i ) {
+ if ( (*it)->isSelected() ) {
+ viewAttach( i );
+ }
+ }
+}
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotAttachOpen()
+{
+ int i = 0;
+ for ( QPtrListIterator<QListViewItem> it(mAtmItemList); *it; ++it, ++i ) {
+ if ( (*it)->isSelected() ) {
+ openAttach( i, false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotAttachOpenWith()
+{
+ int i = 0;
+ for ( QPtrListIterator<QListViewItem> it(mAtmItemList); *it; ++it, ++i ) {
+ if ( (*it)->isSelected() ) {
+ openAttach( i, true );
+ }
+ }
+}
+
+void KMComposeWin::slotAttachEdit()
+{
+ int i = 0;
+ for ( QPtrListIterator<QListViewItem> it(mAtmItemList); *it; ++it, ++i ) {
+ if ( (*it)->isSelected() ) {
+ editAttach( i, false );
+ }
+ }
+}
+
+void KMComposeWin::slotAttachEditWith()
+{
+ int i = 0;
+ for ( QPtrListIterator<QListViewItem> it(mAtmItemList); *it; ++it, ++i ) {
+ if ( (*it)->isSelected() ) {
+ editAttach( i, true );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+bool KMComposeWin::inlineSigningEncryptionSelected() {
+ if ( !mSignAction->isChecked() && !mEncryptAction->isChecked() )
+ return false;
+ return cryptoMessageFormat() == Kleo::InlineOpenPGPFormat;
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::viewAttach( int index )
+{
+ QString pname;
+ KMMessagePart* msgPart;
+ msgPart = mAtmList.at(index);
+ pname = msgPart->name().stripWhiteSpace();
+ if (pname.isEmpty()) pname=msgPart->contentDescription();
+ if (pname.isEmpty()) pname="unnamed";
+
+ KTempFile* atmTempFile = new KTempFile();
+ mAtmTempList.append( atmTempFile );
+ atmTempFile->setAutoDelete( true );
+ KPIM::kByteArrayToFile(msgPart->bodyDecodedBinary(), atmTempFile->name(), false, false,
+ false);
+ KMReaderMainWin *win = new KMReaderMainWin(msgPart, false,
+ atmTempFile->name(), pname, mCharset );
+ win->show();
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::openAttach( int index, bool with )
+{
+ KMMessagePart* msgPart = mAtmList.at(index);
+ const QString contentTypeStr =
+ ( msgPart->typeStr() + '/' + msgPart->subtypeStr() ).lower();
+
+ KMimeType::Ptr mimetype;
+ mimetype = KMimeType::mimeType( contentTypeStr );
+
+ KTempFile* atmTempFile = new KTempFile();
+ mAtmTempList.append( atmTempFile );
+ const bool autoDelete = true;
+ atmTempFile->setAutoDelete( autoDelete );
+
+ KURL url;
+ url.setPath( atmTempFile->name() );
+
+ KPIM::kByteArrayToFile( msgPart->bodyDecodedBinary(), atmTempFile->name(), false, false,
+ false );
+ if ( ::chmod( QFile::encodeName( atmTempFile->name() ), S_IRUSR ) != 0) {
+ QFile::remove(url.path());
+ return;
+ }
+
+ KService::Ptr offer =
+ KServiceTypeProfile::preferredService( mimetype->name(), "Application" );
+
+ if ( with || !offer || mimetype->name() == "application/octet-stream" ) {
+ if ( ( !KRun::displayOpenWithDialog( url, autoDelete ) ) && autoDelete ) {
+ QFile::remove(url.path());
+ }
+ }
+ else {
+ if ( ( !KRun::run( *offer, url, autoDelete ) ) && autoDelete ) {
+ QFile::remove( url.path() );
+ }
+ }
+}
+
+void KMComposeWin::editAttach(int index, bool openWith)
+{
+ KMMessagePart* msgPart = mAtmList.at(index);
+ const QString contentTypeStr =
+ ( msgPart->typeStr() + '/' + msgPart->subtypeStr() ).lower();
+
+ KTempFile* atmTempFile = new KTempFile();
+ mAtmTempList.append( atmTempFile );
+ atmTempFile->setAutoDelete( true );
+ atmTempFile->file()->writeBlock( msgPart->bodyDecodedBinary() );
+ atmTempFile->file()->flush();
+
+
+ KMail::EditorWatcher *watcher = new KMail::EditorWatcher( KURL( atmTempFile->name() ), contentTypeStr, openWith, this );
+ connect( watcher, SIGNAL(editDone(KMail::EditorWatcher*)), SLOT(slotEditDone(KMail::EditorWatcher*)) );
+ if ( watcher->start() ) {
+ mEditorMap.insert( watcher, msgPart );
+ mEditorTempFiles.insert( watcher, atmTempFile );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotAttachSave()
+{
+ KMMessagePart* msgPart;
+ QString fileName, pname;
+ int idx = currentAttachmentNum();
+
+ if (idx < 0) return;
+
+ msgPart = mAtmList.at(idx);
+ pname = msgPart->name();
+ if (pname.isEmpty()) pname="unnamed";
+
+ KURL url = KFileDialog::getSaveURL(QString::null, QString::null, 0, i18n("Save Attachment As"));
+
+ if( url.isEmpty() )
+ return;
+
+ kmkernel->byteArrayToRemoteFile(msgPart->bodyDecodedBinary(), url);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotAttachRemove()
+{
+ bool attachmentRemoved = false;
+ int i = 0;
+ for ( QPtrListIterator<QListViewItem> it(mAtmItemList); *it; ) {
+ if ( (*it)->isSelected() ) {
+ removeAttach( i );
+ attachmentRemoved = true;
+ }
+ else {
+ ++it;
+ ++i;
+ }
+ }
+
+ if ( attachmentRemoved ) {
+ setModified( true );
+ slotUpdateAttachActions();
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotFind()
+{
+ mEditor->search();
+}
+
+void KMComposeWin::slotSearchAgain()
+{
+ mEditor->repeatSearch();
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotReplace()
+{
+ mEditor->replace();
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotUpdateFont()
+{
+ kdDebug() << "KMComposeWin::slotUpdateFont " << endl;
+ if ( ! mFixedFontAction ) {
+ return;
+ }
+ mEditor->setFont( mFixedFontAction->isChecked() ? mFixedFont : mBodyFont );
+}
+
+QString KMComposeWin::quotePrefixName() const
+{
+ if ( !msg() )
+ return QString::null;
+
+ int languageNr = GlobalSettings::self()->replyCurrentLanguage();
+ ReplyPhrases replyPhrases( QString::number(languageNr) );
+ replyPhrases.readConfig();
+ QString quotePrefix = msg()->formatString(
+ replyPhrases.indentPrefix() );
+
+ quotePrefix = msg()->formatString(quotePrefix);
+ return quotePrefix;
+}
+
+void KMComposeWin::slotPasteClipboardAsQuotation()
+{
+ if( mEditor->hasFocus() && msg() )
+ {
+ QString s = QApplication::clipboard()->text();
+ if (!s.isEmpty())
+ mEditor->insert(addQuotesToText(s));
+ }
+}
+
+void KMComposeWin::slotPasteClipboardAsAttachment()
+{
+ KURL url( QApplication::clipboard()->text( QClipboard::Clipboard ) );
+ if ( url.isValid() ) {
+ addAttach(QApplication::clipboard()->text( QClipboard::Clipboard ) );
+ return;
+ }
+
+ QMimeSource *mimeSource = QApplication::clipboard()->data();
+ if ( QImageDrag::canDecode(mimeSource) ) {
+ slotAttachPNGImageData(mimeSource->encodedData("image/png"));
+ }
+ else {
+ bool ok;
+ QString attName = KInputDialog::getText( "KMail", i18n("Name of the attachment:"), QString::null, &ok, this );
+ if ( !ok )
+ return;
+ KMMessagePart *msgPart = new KMMessagePart;
+ msgPart->setName(attName);
+ QValueList<int> dummy;
+ msgPart->setBodyAndGuessCte(QCString(QApplication::clipboard()->text().latin1()), dummy,
+ kmkernel->msgSender()->sendQuotedPrintable());
+ addAttach(msgPart);
+ }
+}
+
+void KMComposeWin::slotAddQuotes()
+{
+ if( mEditor->hasFocus() && msg() )
+ {
+ // TODO: I think this is backwards.
+ // i.e, if no region is marked then add quotes to every line
+ // else add quotes only on the lines that are marked.
+
+ if ( mEditor->hasMarkedText() ) {
+ QString s = mEditor->markedText();
+ if(!s.isEmpty())
+ mEditor->insert(addQuotesToText(s));
+ } else {
+ int l = mEditor->currentLine();
+ int c = mEditor->currentColumn();
+ QString s = mEditor->textLine(l);
+ s.prepend(quotePrefixName());
+ mEditor->insertLine(s,l);
+ mEditor->removeLine(l+1);
+ mEditor->setCursorPosition(l,c+2);
+ }
+ }
+}
+
+QString KMComposeWin::addQuotesToText(const QString &inputText)
+{
+ QString answer = QString( inputText );
+ QString indentStr = quotePrefixName();
+ answer.replace( '\n', '\n' + indentStr);
+ answer.prepend( indentStr );
+ answer += '\n';
+ return KMMessage::smartQuote( answer, GlobalSettings::self()->lineWrapWidth() );
+}
+
+QString KMComposeWin::removeQuotesFromText(const QString &inputText)
+{
+ QString s = inputText;
+
+ // remove first leading quote
+ QString quotePrefix = '^' + quotePrefixName();
+ QRegExp rx(quotePrefix);
+ s.remove(rx);
+
+ // now remove all remaining leading quotes
+ quotePrefix = '\n' + quotePrefixName();
+ rx = quotePrefix;
+ s.replace(rx, "\n");
+
+ return s;
+}
+
+void KMComposeWin::slotRemoveQuotes()
+{
+ if( mEditor->hasFocus() && msg() )
+ {
+ // TODO: I think this is backwards.
+ // i.e, if no region is marked then remove quotes from every line
+ // else remove quotes only on the lines that are marked.
+
+ if ( mEditor->hasMarkedText() ) {
+ QString s = mEditor->markedText();
+ mEditor->insert(removeQuotesFromText(s));
+ } else {
+ int l = mEditor->currentLine();
+ int c = mEditor->currentColumn();
+ QString s = mEditor->textLine(l);
+ mEditor->insertLine(removeQuotesFromText(s),l);
+ mEditor->removeLine(l+1);
+ mEditor->setCursorPosition(l,c-2);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotUndo()
+{
+ QWidget* fw = focusWidget();
+ if (!fw) return;
+
+ if ( ::qt_cast<KEdit*>(fw) )
+ static_cast<QTextEdit*>(fw)->undo();
+ else if (::qt_cast<QLineEdit*>(fw))
+ static_cast<QLineEdit*>(fw)->undo();
+}
+
+void KMComposeWin::slotRedo()
+{
+ QWidget* fw = focusWidget();
+ if (!fw) return;
+
+ if (::qt_cast<KEdit*>(fw))
+ static_cast<KEdit*>(fw)->redo();
+ else if (::qt_cast<QLineEdit*>(fw))
+ static_cast<QLineEdit*>(fw)->redo();
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotCut()
+{
+ QWidget* fw = focusWidget();
+ if (!fw) return;
+
+ if (::qt_cast<KEdit*>(fw))
+ static_cast<KEdit*>(fw)->cut();
+ else if (::qt_cast<QLineEdit*>(fw))
+ static_cast<QLineEdit*>(fw)->cut();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotCopy()
+{
+ QWidget* fw = focusWidget();
+ if (!fw) return;
+
+#ifdef KeyPress
+#undef KeyPress
+#endif
+
+ QKeyEvent k(QEvent::KeyPress, Key_C, 0, ControlButton);
+ kapp->notify(fw, &k);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotPasteClipboard()
+{
+ paste( QClipboard::Clipboard );
+}
+
+void KMComposeWin::paste( QClipboard::Mode mode )
+{
+ QWidget* fw = focusWidget();
+ if (!fw) return;
+
+ QMimeSource *mimeSource = QApplication::clipboard()->data( mode );
+ if ( mimeSource->provides("image/png") ) {
+ slotAttachPNGImageData(mimeSource->encodedData("image/png"));
+ } else if ( KURLDrag::canDecode( mimeSource ) ) {
+ KURL::List urlList;
+ if( KURLDrag::decode( mimeSource, urlList ) ) {
+ const QString asText = i18n("Add as Text");
+ const QString asAttachment = i18n("Add as Attachment");
+ const QString text = i18n("Please select whether you want to insert the content as text into the editor, "
+ "or append the referenced file as an attachment.");
+ const QString caption = i18n("Paste as text or attachment?");
+
+ int id = KMessageBox::questionYesNoCancel( this, text, caption,
+ KGuiItem( asText ), KGuiItem( asAttachment) );
+ switch ( id) {
+ case KMessageBox::Yes:
+ for ( KURL::List::Iterator it = urlList.begin();
+ it != urlList.end(); ++it ) {
+ mEditor->insert( (*it).url() );
+ }
+ break;
+ case KMessageBox::No:
+ for ( KURL::List::Iterator it = urlList.begin();
+ it != urlList.end(); ++it ) {
+ addAttach( *it );
+ }
+ break;
+ }
+ }
+ } else if ( QTextDrag::canDecode( mimeSource ) ) {
+ QString s;
+ if ( QTextDrag::decode( mimeSource, s ) )
+ mEditor->insert( s );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotMarkAll()
+{
+ QWidget* fw = focusWidget();
+ if (!fw) return;
+
+ if (::qt_cast<QLineEdit*>(fw))
+ static_cast<QLineEdit*>(fw)->selectAll();
+ else if (::qt_cast<KEdit*>(fw))
+ static_cast<KEdit*>(fw)->selectAll();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotClose()
+{
+ close(false);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotNewComposer()
+{
+ KMComposeWin* win;
+ KMMessage* msg = new KMMessage;
+
+ msg->initHeader();
+ win = new KMComposeWin(msg);
+ win->show();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotNewMailReader()
+{
+ KMMainWin *kmmwin = new KMMainWin(0);
+ kmmwin->show();
+ //d->resize(d->size());
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotUpdWinTitle(const QString& text)
+{
+ QString s( text );
+ // Remove characters that show badly in most window decorations:
+ // newlines tend to become boxes.
+ if (text.isEmpty())
+ setCaption("("+i18n("unnamed")+")");
+ else setCaption( s.replace( QChar('\n'), ' ' ) );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotEncryptToggled(bool on)
+{
+ setEncryption( on, true /* set by the user */ );
+ slotUpdateSignatureAndEncrypionStateIndicators();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::setEncryption( bool encrypt, bool setByUser )
+{
+ if ( setByUser )
+ setModified( true );
+ if ( !mEncryptAction->isEnabled() )
+ encrypt = false;
+ // 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 )
+ KMessageBox::sorry( this,
+ i18n("<qt><p>You have requested that messages be "
+ "encrypted to yourself, but the currently selected "
+ "identity does not define an (OpenPGP or S/MIME) "
+ "encryption key to use for this.</p>"
+ "<p>Please select the key(s) to use "
+ "in the identity configuration.</p>"
+ "</qt>"),
+ i18n("Undefined Encryption Key") );
+ encrypt = false;
+ }
+
+ // make sure the mEncryptAction is in the right state
+ mEncryptAction->setChecked( encrypt );
+
+ // show the appropriate icon
+ if ( encrypt )
+ mEncryptAction->setIcon("encrypted");
+ else
+ mEncryptAction->setIcon("decrypted");
+
+ // mark the attachments for (no) encryption
+ if ( canSignEncryptAttachments() ) {
+ for ( KMAtmListViewItem* entry =
+ static_cast<KMAtmListViewItem*>( mAtmItemList.first() );
+ entry;
+ entry = static_cast<KMAtmListViewItem*>( mAtmItemList.next() ) )
+ entry->setEncrypt( encrypt );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotSignToggled(bool on)
+{
+ setSigning( on, true /* set by the user */ );
+ slotUpdateSignatureAndEncrypionStateIndicators();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::setSigning( bool sign, bool setByUser )
+{
+ if ( setByUser )
+ setModified( true );
+ if ( !mSignAction->isEnabled() )
+ sign = false;
+
+ // check if the user defined a signing key for the current identity
+ if ( sign && !mLastIdentityHasSigningKey ) {
+ if ( setByUser )
+ KMessageBox::sorry( this,
+ i18n("<qt><p>In order to be able to sign "
+ "this message you first have to "
+ "define the (OpenPGP or S/MIME) signing key "
+ "to use.</p>"
+ "<p>Please select the key to use "
+ "in the identity configuration.</p>"
+ "</qt>"),
+ i18n("Undefined Signing Key") );
+ sign = false;
+ }
+
+ // make sure the mSignAction is in the right state
+ mSignAction->setChecked( sign );
+
+ // mark the attachments for (no) signing
+ if ( canSignEncryptAttachments() ) {
+ for ( KMAtmListViewItem* entry =
+ static_cast<KMAtmListViewItem*>( mAtmItemList.first() );
+ entry;
+ entry = static_cast<KMAtmListViewItem*>( mAtmItemList.next() ) )
+ entry->setSign( sign );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotWordWrapToggled(bool on)
+{
+ if (on)
+ {
+ mEditor->setWordWrap( QTextEdit::FixedColumnWidth );
+ mEditor->setWrapColumnOrWidth( GlobalSettings::self()->lineWrapWidth() );
+ }
+ else
+ {
+ mEditor->setWordWrap( QTextEdit::WidgetWidth );
+ }
+}
+
+
+void KMComposeWin::disableWordWrap()
+{
+ mEditor->setWordWrap( QTextEdit::NoWrap );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotPrint()
+{
+ mMessageWasModified = isModified();
+ connect( this, SIGNAL( applyChangesDone( bool ) ),
+ this, SLOT( slotContinuePrint( bool ) ) );
+ applyChanges( true );
+}
+
+void KMComposeWin::slotContinuePrint( bool rc )
+{
+ disconnect( this, SIGNAL( applyChangesDone( bool ) ),
+ this, SLOT( slotContinuePrint( bool ) ) );
+
+ if( rc ) {
+ if ( mComposedMessages.isEmpty() ) {
+ kdDebug(5006) << "Composing the message failed." << endl;
+ return;
+ }
+ KMCommand *command = new KMPrintCommand( this, mComposedMessages.first() );
+ command->start();
+ setModified( mMessageWasModified );
+ }
+}
+
+//----------------------------------------------------------------------------
+bool KMComposeWin::validateAddresses( QWidget * parent, const QString & addresses )
+{
+ QString brokenAddress;
+ KPIM::EmailParseResult errorCode = KMMessage::isValidEmailAddressList( KMMessage::expandAliases( addresses ), brokenAddress );
+ if ( !( errorCode == KPIM::AddressOk || errorCode == KPIM::AddressEmpty ) ) {
+ QString errorMsg( "<qt><p><b>" + brokenAddress +
+ "</b></p><p>" + KPIM::emailParseResultToString( errorCode ) +
+ "</p></qt>" );
+ KMessageBox::sorry( parent, errorMsg, i18n("Invalid Email Address") );
+ return false;
+ }
+ return true;
+}
+
+//----------------------------------------------------------------------------
+void KMComposeWin::doSend( KMail::MessageSender::SendMethod method,
+ KMComposeWin::SaveIn saveIn )
+{
+ if ( method != KMail::MessageSender::SendLater && kmkernel->isOffline() ) {
+ KMessageBox::information( this,
+ i18n("KMail is currently in offline mode,"
+ "your messages will be kept in the outbox until you go online."),
+ i18n("Online/Offline"), "kmailIsOffline" );
+ mSendMethod = KMail::MessageSender::SendLater;
+ } else {
+ mSendMethod = method;
+ }
+ mSaveIn = saveIn;
+
+ if ( saveIn == KMComposeWin::None ) {
+ if ( KPIM::getFirstEmailAddress( from() ).isEmpty() ) {
+ if ( !( mShowHeaders & HDR_FROM ) ) {
+ mShowHeaders |= HDR_FROM;
+ rethinkFields( false );
+ }
+ mEdtFrom->setFocus();
+ KMessageBox::sorry( this,
+ i18n("You must enter your email address in the "
+ "From: field. You should also set your email "
+ "address for all identities, so that you do "
+ "not have to enter it for each message.") );
+ return;
+ }
+ if ( to().isEmpty() )
+ {
+ if ( cc().isEmpty() && bcc().isEmpty()) {
+ if ( mEdtTo ) mEdtTo->setFocus();
+ KMessageBox::information( this,
+ i18n("You must specify at least one receiver,"
+ "either in the To: field or as CC or as BCC.") );
+ return;
+ }
+ else {
+ if ( mEdtTo ) mEdtTo->setFocus();
+ int rc =
+ KMessageBox::questionYesNo( this,
+ i18n("To field is missing."
+ "Send message anyway?"),
+ i18n("No To: specified") );
+ if ( rc == KMessageBox::No ){
+ return;
+ }
+ }
+ }
+
+ // Validate the To:, CC: and BCC fields
+ if ( !validateAddresses( this, to().stripWhiteSpace() ) ) {
+ return;
+ }
+
+ if ( !validateAddresses( this, cc().stripWhiteSpace() ) ) {
+ return;
+ }
+
+ if ( !validateAddresses( this, bcc().stripWhiteSpace() ) ) {
+ return;
+ }
+
+ if (subject().isEmpty())
+ {
+ mEdtSubject->setFocus();
+ int rc =
+ KMessageBox::questionYesNo( this,
+ i18n("You did not specify a subject. "
+ "Send message anyway?"),
+ i18n("No Subject Specified"),
+ i18n("S&end as Is"),
+ i18n("&Specify the Subject"),
+ "no_subject_specified" );
+ if( rc == KMessageBox::No )
+ {
+ return;
+ }
+ }
+
+ if ( userForgotAttachment() )
+ return;
+ }
+
+ KCursorSaver busy(KBusyPtr::busy());
+ mMsg->setDateToday();
+
+ // If a user sets up their outgoing messages preferences wrong and then
+ // sends mail that gets 'stuck' in their outbox, they should be able to
+ // rectify the problem by editing their outgoing preferences and
+ // resending.
+ // Hence this following conditional
+ QString hf = mMsg->headerField("X-KMail-Transport");
+ if ((mTransport->currentText() != mTransport->text(0)) ||
+ (!hf.isEmpty() && (hf != mTransport->text(0))))
+ mMsg->setHeaderField("X-KMail-Transport", mTransport->currentText());
+
+ mDisableBreaking = ( saveIn != KMComposeWin::None );
+
+ const bool neverEncrypt = ( mDisableBreaking && GlobalSettings::self()->neverEncryptDrafts() )
+ || mSigningAndEncryptionExplicitlyDisabled;
+ connect( this, SIGNAL( applyChangesDone( bool ) ),
+ SLOT( slotContinueDoSend( bool ) ) );
+
+ if ( mEditor->textFormat() == Qt::RichText )
+ mMsg->setHeaderField( "X-KMail-Markup", "true" );
+ else
+ mMsg->removeHeaderField( "X-KMail-Markup" );
+ if ( mEditor->textFormat() == Qt::RichText && inlineSigningEncryptionSelected() ) {
+ QString keepBtnText = mEncryptAction->isChecked() ?
+ mSignAction->isChecked() ? i18n( "&Keep markup, do not sign/encrypt" )
+ : i18n( "&Keep markup, do not encrypt" )
+ : i18n( "&Keep markup, do not sign" );
+ QString yesBtnText = mEncryptAction->isChecked() ?
+ mSignAction->isChecked() ? i18n("Sign/Encrypt (delete markup)")
+ : i18n( "Encrypt (delete markup)" )
+ : i18n( "Sign (delete markup)" );
+ int ret = KMessageBox::warningYesNoCancel(this,
+ i18n("<qt><p>Inline signing/encrypting of HTML messages is not possible;</p>"
+ "<p>do you want to delete your markup?</p></qt>"),
+ i18n("Sign/Encrypt Message?"),
+ KGuiItem( yesBtnText ),
+ KGuiItem( keepBtnText ) );
+ if ( KMessageBox::Cancel == ret )
+ return;
+ if ( KMessageBox::No == ret ) {
+ mEncryptAction->setChecked(false);
+ mSignAction->setChecked(false);
+ }
+ else {
+ toggleMarkup(false);
+ }
+ }
+
+ if (neverEncrypt && saveIn != KMComposeWin::None ) {
+ // we can't use the state of the mail itself, to remember the
+ // signing and encryption state, so let's add a header instead
+ mMsg->setHeaderField( "X-KMail-SignatureActionEnabled", mSignAction->isChecked()? "true":"false" );
+ mMsg->setHeaderField( "X-KMail-EncryptActionEnabled", mEncryptAction->isChecked()? "true":"false" );
+ mMsg->setHeaderField( "X-KMail-CryptoMessageFormat", QString::number( cryptoMessageFormat() ) );
+ } else {
+ mMsg->removeHeaderField( "X-KMail-SignatureActionEnabled" );
+ mMsg->removeHeaderField( "X-KMail-EncryptActionEnabled" );
+ mMsg->removeHeaderField( "X-KMail-CryptoMessageFormat" );
+ }
+
+
+ kdDebug(5006) << "KMComposeWin::doSend() - calling applyChanges()"
+ << endl;
+ applyChanges( neverEncrypt );
+}
+
+bool KMComposeWin::saveDraftOrTemplate( const QString &folderName,
+ KMMessage *msg )
+{
+ KMFolder *theFolder = 0, *imapTheFolder = 0;
+ // get the draftsFolder
+ if ( !folderName.isEmpty() ) {
+ theFolder = kmkernel->folderMgr()->findIdString( folderName );
+ if ( theFolder == 0 )
+ // This is *NOT* supposed to be "imapDraftsFolder", because a
+ // dIMAP folder works like a normal folder
+ theFolder = kmkernel->dimapFolderMgr()->findIdString( folderName );
+ if ( theFolder == 0 )
+ imapTheFolder = kmkernel->imapFolderMgr()->findIdString( folderName );
+ if ( !theFolder && !imapTheFolder ) {
+ const KPIM::Identity & id = kmkernel->identityManager()
+ ->identityForUoidOrDefault( msg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt() );
+ KMessageBox::information( 0,
+ i18n("The custom drafts or templates folder for "
+ "identify \"%1\" does not exist (anymore); "
+ "therefore, the default drafts or templates "
+ "folder will be used.")
+ .arg( id.identityName() ) );
+ }
+ }
+ if ( imapTheFolder && imapTheFolder->noContent() )
+ imapTheFolder = 0;
+
+ bool didOpen = false;
+ if ( theFolder == 0 ) {
+ theFolder = ( mSaveIn==KMComposeWin::Drafts ?
+ kmkernel->draftsFolder() : kmkernel->templatesFolder() );
+ } else {
+ //XXX this looks really, really fishy
+ theFolder->open( "composer" );
+ didOpen = true;
+ }
+ kdDebug(5006) << k_funcinfo << "theFolder=" << theFolder->name() << endl;
+ if ( imapTheFolder )
+ kdDebug(5006) << k_funcinfo << "imapTheFolder=" << imapTheFolder->name() << endl;
+
+ bool sentOk = !( theFolder->addMsg( msg ) );
+
+ // Ensure the message is correctly and fully parsed
+ theFolder->unGetMsg( theFolder->count() - 1 );
+ msg = theFolder->getMsg( theFolder->count() - 1 );
+ // Does that assignment needs to be propagated out to the caller?
+ // Assuming the send is OK, the iterator is set to 0 immediately afterwards.
+ if ( imapTheFolder ) {
+ // move the message to the imap-folder and highlight it
+ imapTheFolder->moveMsg( msg );
+ (static_cast<KMFolderImap*>( imapTheFolder->storage() ))->getFolder();
+ }
+
+ if ( didOpen )
+ theFolder->close( "composer" );
+ return sentOk;
+}
+
+void KMComposeWin::slotContinueDoSend( bool sentOk )
+{
+ kdDebug(5006) << "KMComposeWin::slotContinueDoSend( " << sentOk << " )"
+ << endl;
+ disconnect( this, SIGNAL( applyChangesDone( bool ) ),
+ this, SLOT( slotContinueDoSend( bool ) ) );
+
+ if ( !sentOk ) {
+ mDisableBreaking = false;
+ return;
+ }
+
+ for ( QValueVector<KMMessage*>::iterator it = mComposedMessages.begin() ; it != mComposedMessages.end() ; ++it ) {
+
+ // remove fields that contain no data (e.g. an empty Cc: or Bcc:)
+ (*it)->cleanupHeader();
+
+ // needed for imap
+ (*it)->setComplete( true );
+
+ if ( mSaveIn==KMComposeWin::Drafts ) {
+ sentOk = saveDraftOrTemplate( (*it)->drafts(), (*it) );
+ } else if ( mSaveIn==KMComposeWin::Templates ) {
+ sentOk = saveDraftOrTemplate( (*it)->templates(), (*it) );
+ } else {
+ (*it)->setTo( KMMessage::expandAliases( to() ));
+ (*it)->setCc( KMMessage::expandAliases( cc() ));
+ if( !mComposer->originalBCC().isEmpty() )
+ (*it)->setBcc( KMMessage::expandAliases( mComposer->originalBCC() ));
+ QString recips = (*it)->headerField( "X-KMail-Recipients" );
+ if( !recips.isEmpty() ) {
+ (*it)->setHeaderField( "X-KMail-Recipients", KMMessage::expandAliases( recips ), KMMessage::Address );
+ }
+ (*it)->cleanupHeader();
+ sentOk = kmkernel->msgSender()->send((*it), mSendMethod);
+ }
+
+ if (!sentOk)
+ return;
+
+ *it = 0; // don't kill it later...
+ }
+
+ RecentAddresses::self( KMKernel::config() )->add( bcc() );
+ RecentAddresses::self( KMKernel::config() )->add( cc() );
+ RecentAddresses::self( KMKernel::config() )->add( to() );
+
+ setModified( false );
+ mAutoDeleteMsg = false;
+ mFolder = 0;
+ cleanupAutoSave();
+ close();
+ return;
+}
+
+
+
+//----------------------------------------------------------------------------
+void KMComposeWin::slotSendLater()
+{
+ if ( mEditor->checkExternalEditorFinished() )
+ doSend( KMail::MessageSender::SendLater );
+}
+
+
+//----------------------------------------------------------------------------
+void KMComposeWin::slotSaveDraft() {
+ if ( mEditor->checkExternalEditorFinished() )
+ doSend( KMail::MessageSender::SendLater, KMComposeWin::Drafts );
+}
+
+//----------------------------------------------------------------------------
+void KMComposeWin::slotSaveTemplate() {
+ if ( mEditor->checkExternalEditorFinished() )
+ doSend( KMail::MessageSender::SendLater, KMComposeWin::Templates );
+}
+
+//----------------------------------------------------------------------------
+void KMComposeWin::slotSendNowVia( int item )
+{
+ QStringList availTransports= KMail::TransportManager::transportNames();
+ QString customTransport = availTransports[ item ];
+
+ mTransport->setCurrentText( customTransport );
+ slotSendNow();
+}
+
+//----------------------------------------------------------------------------
+void KMComposeWin::slotSendLaterVia( int item )
+{
+ QStringList availTransports= KMail::TransportManager::transportNames();
+ QString customTransport = availTransports[ item ];
+
+ mTransport->setCurrentText( customTransport );
+ slotSendLater();
+}
+
+
+//----------------------------------------------------------------------------
+void KMComposeWin::slotSendNow() {
+ if ( !mEditor->checkExternalEditorFinished() )
+ return;
+ if ( GlobalSettings::self()->confirmBeforeSend() )
+ {
+ int rc = KMessageBox::warningYesNoCancel( mMainWidget,
+ i18n("About to send email..."),
+ i18n("Send Confirmation"),
+ i18n("&Send Now"),
+ i18n("Send &Later") );
+
+ if ( rc == KMessageBox::Yes )
+ doSend( KMail::MessageSender::SendImmediate );
+ else if ( rc == KMessageBox::No )
+ doSend( KMail::MessageSender::SendLater );
+ }
+ else
+ doSend( KMail::MessageSender::SendImmediate );
+}
+
+//----------------------------------------------------------------------------
+void KMComposeWin::slotAppendSignature()
+{
+ insertSignature();
+}
+
+//----------------------------------------------------------------------------
+void KMComposeWin::slotPrependSignature()
+{
+ insertSignature( false );
+}
+
+//----------------------------------------------------------------------------
+void KMComposeWin::slotInsertSignatureAtCursor()
+{
+ insertSignature( false, mEditor->currentLine() );
+}
+
+//----------------------------------------------------------------------------
+void KMComposeWin::insertSignature( bool append, int pos )
+{
+ bool mod = mEditor->isModified();
+
+ const KPIM::Identity &ident =
+ kmkernel->identityManager()->
+ identityForUoidOrDefault( mIdentity->currentIdentity() );
+
+ mOldSigText = GlobalSettings::self()->prependSignature()? ident.signature().rawText() : ident.signatureText();
+
+ if( !mOldSigText.isEmpty() )
+ {
+ mEditor->sync();
+ if ( append ) {
+ mEditor->setText( mEditor->text() + mOldSigText );
+ } else {
+ mOldSigText = "\n\n"+mOldSigText+"\n";
+ mEditor->insertAt(mOldSigText, pos, 0);
+ }
+ mEditor->update();
+ mEditor->setModified(mod);
+
+ if ( mPreserveUserCursorPosition ) {
+ mEditor->setCursorPositionFromStart( (unsigned int) mMsg->getCursorPos() );
+ // Only keep the cursor from the mMsg *once* based on the
+ // preserve-cursor-position setting; this handles the case where
+ // the message comes from a template with a specific cursor
+ // position set and the signature is appended automatically.
+ mPreserveUserCursorPosition = false;
+ } 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 )
+ mEditor->setContentsPos( 0, 0 );
+ }
+ mEditor->sync();
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotHelp()
+{
+ kapp->invokeHelp();
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotCleanSpace()
+{
+ // Originally we simply used the KEdit::cleanWhiteSpace() method,
+ // but that code doesn't handle quoted-lines or signatures, so instead
+ // we now simply use regexp's to squeeze sequences of tabs and spaces
+ // into a single space, and make sure all our lines are single-spaced.
+ //
+ // Yes, extra space in a quote string is squeezed.
+ // Signatures are respected (i.e. not cleaned).
+
+ QString s;
+ if ( mEditor->hasMarkedText() ) {
+ s = mEditor->markedText();
+ if( s.isEmpty() )
+ return;
+ } else {
+ s = mEditor->text();
+ }
+
+ // Remove the signature for now.
+ QString sig;
+ bool restore = false;
+ const KPIM::Identity & ident =
+ kmkernel->identityManager()->identityForUoid( mId );
+ if ( !ident.isNull() ) {
+ sig = ident.signatureText();
+ if( !sig.isEmpty() ) {
+ if( s.endsWith( sig ) ) {
+ s.truncate( s.length() - sig.length() );
+ restore = true;
+ }
+ }
+ }
+
+ // Squeeze tabs and spaces
+ QRegExp squeeze( "[\t ]+" );
+ s.replace( squeeze, QChar( ' ' ) );
+
+ // Remove trailing whitespace
+ QRegExp trailing( "\\s+$" );
+ s.replace( trailing, QChar( '\n' ) );
+
+ // Single space lines
+ QRegExp singleSpace( "[\n]{2,}" );
+ s.replace( singleSpace, QChar( '\n' ) );
+
+ // Restore the signature
+ if ( restore )
+ s.append( sig );
+
+ // Put the new text in place.
+ // The lines below do not clear the undo history, but unfortuately cause
+ // the side-effect that you need to press Ctrl-Z twice (first Ctrl-Z will
+ // show cleared text area) to get back the original, pre-cleaned text.
+ // If you use mEditor->setText( s ) then the undo history is cleared so
+ // that isn't a good solution either.
+ // TODO: is Qt4 better at handling the undo history??
+ if ( !mEditor->hasMarkedText() )
+ mEditor->clear();
+ mEditor->insert( s );
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotToggleMarkup()
+{
+ if ( markupAction->isChecked() ) {
+ mHtmlMarkup = true;
+ toolBar("htmlToolBar")->show();
+ // markup will be toggled as soon as markup is actually used
+ fontChanged( mEditor->currentFont() ); // set buttons in correct position
+ mSaveFont = mEditor->currentFont();
+ }
+ else
+ toggleMarkup(false);
+
+}
+//-----------------------------------------------------------------------------
+void KMComposeWin::toggleMarkup(bool markup)
+{
+ if ( markup ) {
+ if ( !mUseHTMLEditor ) {
+ kdDebug(5006) << "setting RichText editor" << endl;
+ mUseHTMLEditor = true; // set it directly to true. setColor hits another toggleMarkup
+ mHtmlMarkup = true;
+
+ // set all highlighted text caused by spelling back to black
+ int paraFrom, indexFrom, paraTo, indexTo;
+ mEditor->getSelection ( &paraFrom, &indexFrom, &paraTo, &indexTo);
+ mEditor->selectAll();
+ // save the buttonstates because setColor calls fontChanged
+ bool _bold = textBoldAction->isChecked();
+ bool _italic = textItalicAction->isChecked();
+ mEditor->setColor(QColor(0,0,0));
+ textBoldAction->setChecked(_bold);
+ textItalicAction->setChecked(_italic);
+ mEditor->setSelection ( paraFrom, indexFrom, paraTo, indexTo);
+
+ mEditor->setTextFormat(Qt::RichText);
+ mEditor->setModified(true);
+ markupAction->setChecked(true);
+ toolBar( "htmlToolBar" )->show();
+ mEditor->deleteAutoSpellChecking();
+ mAutoSpellCheckingAction->setChecked(false);
+ slotAutoSpellCheckingToggled(false);
+ }
+ } else { // markup is to be turned off
+ kdDebug(5006) << "setting PlainText editor" << endl;
+ mHtmlMarkup = false;
+ toolBar("htmlToolBar")->hide();
+ if ( mUseHTMLEditor ) { // it was turned on
+ mUseHTMLEditor = false;
+ mEditor->setTextFormat(Qt::PlainText);
+ QString text = mEditor->text();
+ mEditor->setText(text); // otherwise the text still looks formatted
+ mEditor->setModified(true);
+ slotAutoSpellCheckingToggled(true);
+ }
+ }
+}
+
+void KMComposeWin::htmlToolBarVisibilityChanged( bool visible )
+{
+ // disable markup if the user hides the HTML toolbar
+ if ( !visible ) {
+ markupAction->setChecked( false );
+ toggleMarkup( false );
+ }
+}
+
+void KMComposeWin::slotSubjectTextSpellChecked()
+{
+ mSubjectTextWasSpellChecked = true;
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotAutoSpellCheckingToggled( bool on )
+{
+ if ( mEditor->autoSpellChecking(on) == -1 ) {
+ mAutoSpellCheckingAction->setChecked(false); // set it to false again
+ }
+
+ QString temp;
+ if ( on )
+ temp = i18n( "Spellcheck: on" );
+ else
+ temp = i18n( "Spellcheck: off" );
+ statusBar()->changeItem( temp, 3 );
+}
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotSpellcheck()
+{
+ if (mSpellCheckInProgress) return;
+ mSubjectTextWasSpellChecked = false;
+ mSpellCheckInProgress=true;
+ /*
+ connect (mEditor, SIGNAL (spellcheck_progress (unsigned)),
+ this, SLOT (spell_progress (unsigned)));
+ */
+
+ mEditor->spellcheck();
+}
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotUpdateSignatureActions()
+{
+ //Check if an identity has signature or not and turn on/off actions in the
+ //edit menu accordingly.
+ const KPIM::Identity & ident =
+ kmkernel->identityManager()->identityForUoidOrDefault( mIdentity->currentIdentity() );
+ QString sig = ident.signatureText();
+
+ if ( sig.isEmpty() ) {
+ mAppendSignatureAction->setEnabled( false );
+ mPrependSignatureAction->setEnabled( false );
+ mInsertSignatureAction->setEnabled( false );
+ }
+ else {
+ mAppendSignatureAction->setEnabled( true );
+ mPrependSignatureAction->setEnabled( true );
+ mInsertSignatureAction->setEnabled( true );
+ }
+}
+
+void KMComposeWin::polish()
+{
+ // Ensure the html toolbar is appropriately shown/hidden
+ markupAction->setChecked(mHtmlMarkup);
+ if (mHtmlMarkup)
+ toolBar("htmlToolBar")->show();
+ else
+ toolBar("htmlToolBar")->hide();
+ KMail::Composer::polish();
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotSpellcheckDone(int result)
+{
+ kdDebug(5006) << "spell check complete: result = " << result << endl;
+ mSpellCheckInProgress=false;
+
+ switch( result )
+ {
+ case KS_CANCEL:
+ statusBar()->changeItem(i18n(" Spell check canceled."),0);
+ break;
+ case KS_STOP:
+ statusBar()->changeItem(i18n(" Spell check stopped."),0);
+ break;
+ default:
+ statusBar()->changeItem(i18n(" Spell check complete."),0);
+ break;
+ }
+ QTimer::singleShot( 2000, this, SLOT(slotSpellcheckDoneClearStatus()) );
+}
+
+void KMComposeWin::slotSpellcheckDoneClearStatus()
+{
+ statusBar()->changeItem("", 0);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotIdentityChanged( uint uoid )
+{
+ const KPIM::Identity & ident =
+ kmkernel->identityManager()->identityForUoid( uoid );
+ if( ident.isNull() ) return;
+
+ //Turn on/off signature actions if identity has no signature.
+ slotUpdateSignatureActions();
+
+ if( !ident.fullEmailAddr().isNull() )
+ mEdtFrom->setText(ident.fullEmailAddr());
+ // make sure the From field is shown if it does not contain a valid email address
+ if ( KPIM::getFirstEmailAddress( from() ).isEmpty() )
+ mShowHeaders |= HDR_FROM;
+ if ( mEdtReplyTo ) mEdtReplyTo->setText(ident.replyToAddr());
+
+ if ( mRecipientsEditor ) {
+ // remove BCC of old identity and add BCC of new identity (if they differ)
+ const KPIM::Identity & oldIdentity =
+ kmkernel->identityManager()->identityForUoidOrDefault( mId );
+ if ( oldIdentity.bcc() != ident.bcc() ) {
+ mRecipientsEditor->removeRecipient( oldIdentity.bcc(), Recipient::Bcc );
+ mRecipientsEditor->addRecipient( ident.bcc(), Recipient::Bcc );
+ mRecipientsEditor->setFocusBottom();
+ }
+ }
+
+ // don't overwrite the BCC field under certain circomstances
+ // NOT edited and preset BCC from the identity
+ if( mEdtBcc && !mEdtBcc->edited() && !ident.bcc().isEmpty() ) {
+ // BCC NOT empty AND contains a diff adress then the preset BCC
+ // of the new identity
+ if( !mEdtBcc->text().isEmpty() && mEdtBcc->text() != ident.bcc() && !mEdtBcc->edited() ) {
+ mEdtBcc->setText( ident.bcc() );
+ } else {
+ // user type into the editbox an address that != to the preset bcc
+ // of the identity, we assume that since the user typed it
+ // they want to keep it
+ if ( mEdtBcc->text() != ident.bcc() && !mEdtBcc->text().isEmpty() ) {
+ QString temp_string( mEdtBcc->text() + QString::fromLatin1(",") + ident.bcc() );
+ mEdtBcc->setText( temp_string );
+ } else {
+ // if the user typed the same address as the preset BCC
+ // from the identity we will overwrite it to avoid duplicates.
+ mEdtBcc->setText( ident.bcc() );
+ }
+ }
+ }
+ // user edited the bcc box and has a preset bcc in the identity
+ // we will append whatever the user typed to the preset address
+ // allowing the user to keep all addresses
+ if( mEdtBcc && mEdtBcc->edited() && !ident.bcc().isEmpty() ) {
+ if( !mEdtBcc->text().isEmpty() ) {
+ QString temp_string ( mEdtBcc->text() + QString::fromLatin1(",") + ident.bcc() );
+ mEdtBcc->setText( temp_string );
+ } else {
+ mEdtBcc->setText( ident.bcc() );
+ }
+ }
+ // user typed nothing and the identity does not have a preset bcc
+ // we then reset the value to get rid of any previous
+ // values if the user changed identity mid way through.
+ if( mEdtBcc && !mEdtBcc->edited() && ident.bcc().isEmpty() ) {
+ mEdtBcc->setText( ident.bcc() );
+ }
+ // make sure the BCC field is shown because else it's ignored
+ if ( !ident.bcc().isEmpty() ) {
+ mShowHeaders |= HDR_BCC;
+ }
+
+ if ( ident.organization().isEmpty() )
+ mMsg->removeHeaderField("Organization");
+ else
+ mMsg->setHeaderField("Organization", ident.organization());
+
+ if (!ident.isXFaceEnabled() || ident.xface().isEmpty())
+ mMsg->removeHeaderField("X-Face");
+ else
+ {
+ QString xface = ident.xface();
+ if (!xface.isEmpty())
+ {
+ int numNL = ( xface.length() - 1 ) / 70;
+ for ( int i = numNL; i > 0; --i )
+ xface.insert( i*70, "\n\t" );
+ mMsg->setHeaderField("X-Face", xface);
+ }
+ }
+
+ if ( !mBtnTransport->isChecked() ) {
+ QString transp = ident.transport();
+ if ( transp.isEmpty() )
+ {
+ mMsg->removeHeaderField("X-KMail-Transport");
+ transp = GlobalSettings::self()->defaultTransport();
+ }
+ else
+ mMsg->setHeaderField("X-KMail-Transport", transp);
+ setTransport( transp );
+ }
+
+ mDictionaryCombo->setCurrentByDictionary( ident.dictionary() );
+
+ if ( !mBtnFcc->isChecked() ) {
+ setFcc( ident.fcc() );
+ }
+
+ QString edtText = mEditor->text();
+
+ if ( mOldSigText.isEmpty() ) {
+ const KPIM::Identity &id =
+ kmkernel->
+ identityManager()->
+ identityForUoidOrDefault( mMsg->headerField( "X-KMail-Identity" ).
+ stripWhiteSpace().toUInt() );
+ mOldSigText = 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() );
+
+ // now append the new sig
+ mOldSigText = ident.signatureText();
+ if( ( !mOldSigText.isEmpty() ) &&
+ ( GlobalSettings::self()->autoTextSignature() == "auto" ) ) {
+ edtText.append( mOldSigText );
+ }
+ mEditor->setText( edtText );
+
+ // disable certain actions if there is no PGP user identity set
+ // for this profile
+ bool bNewIdentityHasSigningKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty();
+ bool bNewIdentityHasEncryptionKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty();
+ mAttachMPK->setEnabled( Kleo::CryptoBackendFactory::instance()->openpgp() &&
+ !ident.pgpEncryptionKey().isEmpty() );
+ // save the state of the sign and encrypt button
+ if ( !bNewIdentityHasEncryptionKey && mLastIdentityHasEncryptionKey ) {
+ mLastEncryptActionState = mEncryptAction->isChecked();
+ setEncryption( false );
+ }
+ if ( !bNewIdentityHasSigningKey && mLastIdentityHasSigningKey ) {
+ mLastSignActionState = mSignAction->isChecked();
+ setSigning( false );
+ }
+ // restore the last state of the sign and encrypt button
+ if ( bNewIdentityHasEncryptionKey && !mLastIdentityHasEncryptionKey )
+ setEncryption( mLastEncryptActionState );
+ if ( bNewIdentityHasSigningKey && !mLastIdentityHasSigningKey )
+ setSigning( mLastSignActionState );
+
+ mLastIdentityHasSigningKey = bNewIdentityHasSigningKey;
+ mLastIdentityHasEncryptionKey = bNewIdentityHasEncryptionKey;
+
+ setModified( true );
+ mId = uoid;
+
+ // make sure the From and BCC fields are shown if necessary
+ rethinkFields( false );
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotSpellcheckConfig()
+{
+ KDialogBase dlg(KDialogBase::Plain, i18n("Spellchecker"),
+ KDialogBase::Ok|KDialogBase::Cancel, KDialogBase::Ok,
+ this, 0, true, true );
+ KWin kwin;
+ QTabDialog qtd (this, "tabdialog", true);
+ KSpellConfig mKSpellConfig (&qtd);
+ mKSpellConfig.layout()->setMargin( KDialog::marginHint() );
+
+ qtd.addTab (&mKSpellConfig, i18n("Spellchecker"));
+ qtd.setCancelButton ();
+
+ kwin.setIcons (qtd.winId(), kapp->icon(), kapp->miniIcon());
+ qtd.setCancelButton(KStdGuiItem::cancel().text());
+ qtd.setOkButton(KStdGuiItem::ok().text());
+
+ if (qtd.exec())
+ mKSpellConfig.writeGlobalSettings();
+}
+
+//-----------------------------------------------------------------------------
+void KMComposeWin::slotStatusMessage(const QString &message)
+{
+ statusBar()->changeItem( message, 0 );
+}
+
+void KMComposeWin::slotEditToolbars()
+{
+ saveMainWindowSettings(KMKernel::config(), "Composer");
+ KEditToolbar dlg(guiFactory(), this);
+
+ connect( &dlg, SIGNAL(newToolbarConfig()),
+ SLOT(slotUpdateToolbars()) );
+
+ dlg.exec();
+}
+
+void KMComposeWin::slotUpdateToolbars()
+{
+ createGUI("kmcomposerui.rc");
+ applyMainWindowSettings(KMKernel::config(), "Composer");
+}
+
+void KMComposeWin::slotEditKeys()
+{
+ KKeyDialog::configure( actionCollection(),
+ false /*don't allow one-letter shortcuts*/
+ );
+}
+
+void KMComposeWin::setReplyFocus( bool hasMessage )
+{
+ mEditor->setFocus();
+ if ( hasMessage ) {
+ if( mMsg->getCursorPos() ) {
+ mEditor->setCursorPositionFromStart( (unsigned int) mMsg->getCursorPos() );
+ } else {
+ mEditor->setCursorPosition( 1, 0 );
+ }
+ }
+}
+
+void KMComposeWin::setFocusToSubject()
+{
+ mEdtSubject->setFocus();
+}
+
+int KMComposeWin::autoSaveInterval() const
+{
+ return GlobalSettings::self()->autosaveInterval() * 1000 * 60;
+}
+
+void KMComposeWin::initAutoSave()
+{
+ kdDebug(5006) << k_funcinfo << endl;
+ // make sure the autosave folder exists
+ KMFolderMaildir::createMaildirFolders( KMKernel::localDataPath() + "autosave" );
+ if ( mAutoSaveFilename.isEmpty() ) {
+ mAutoSaveFilename = KMFolderMaildir::constructValidFileName();
+ }
+
+ updateAutoSave();
+}
+
+void KMComposeWin::updateAutoSave()
+{
+ if ( autoSaveInterval() == 0 ) {
+ delete mAutoSaveTimer; mAutoSaveTimer = 0;
+ }
+ else {
+ if ( !mAutoSaveTimer ) {
+ mAutoSaveTimer = new QTimer( this, "mAutoSaveTimer" );
+ connect( mAutoSaveTimer, SIGNAL( timeout() ),
+ this, SLOT( autoSaveMessage() ) );
+ }
+ mAutoSaveTimer->start( autoSaveInterval() );
+ }
+}
+
+void KMComposeWin::setAutoSaveFilename( const QString & filename )
+{
+ if ( !mAutoSaveFilename.isEmpty() )
+ KMFolderMaildir::removeFile( KMKernel::localDataPath() + "autosave",
+ mAutoSaveFilename );
+ mAutoSaveFilename = filename;
+}
+
+void KMComposeWin::cleanupAutoSave()
+{
+ delete mAutoSaveTimer; mAutoSaveTimer = 0;
+ if ( !mAutoSaveFilename.isEmpty() ) {
+ kdDebug(5006) << k_funcinfo << "deleting autosave file "
+ << mAutoSaveFilename << endl;
+ KMFolderMaildir::removeFile( KMKernel::localDataPath() + "autosave",
+ mAutoSaveFilename );
+ mAutoSaveFilename = QString();
+ }
+}
+
+void KMComposeWin::slotCompletionModeChanged( KGlobalSettings::Completion mode)
+{
+ GlobalSettings::self()->setCompletionMode( (int) mode );
+
+ // sync all the lineedits to the same completion mode
+ mEdtFrom->setCompletionMode( mode );
+ mEdtReplyTo->setCompletionMode( mode );
+ if ( mClassicalRecipients ) {
+ mEdtTo->setCompletionMode( mode );
+ mEdtCc->setCompletionMode( mode );
+ mEdtBcc->setCompletionMode( mode );
+ }else
+ mRecipientsEditor->setCompletionMode( mode );
+}
+
+void KMComposeWin::slotConfigChanged()
+{
+ readConfig( true /*reload*/);
+ updateAutoSave();
+ rethinkFields();
+ slotWordWrapToggled( mWordWrapAction->isChecked() );
+}
+
+/*
+* checks if the drafts-folder has been deleted
+* that is not nice so we set the system-drafts-folder
+*/
+void KMComposeWin::slotFolderRemoved(KMFolder* folder)
+{
+ // TODO: need to handle templates here?
+ if ( (mFolder) && (folder->idString() == mFolder->idString()) )
+ {
+ mFolder = kmkernel->draftsFolder();
+ kdDebug(5006) << "restoring drafts to " << mFolder->idString() << endl;
+ }
+ if (mMsg) mMsg->setParent(0);
+}
+
+
+void KMComposeWin::editorFocusChanged(bool gained)
+{
+ mPasteQuotation->setEnabled(gained);
+ mAddQuoteChars->setEnabled(gained);
+ mRemQuoteChars->setEnabled(gained);
+}
+
+void KMComposeWin::slotSetAlwaysSend( bool bAlways )
+{
+ mAlwaysSend = bAlways;
+}
+
+void KMComposeWin::slotListAction( const QString& style )
+{
+ toggleMarkup(true);
+ if ( style == i18n( "Standard" ) )
+ mEditor->setParagType( QStyleSheetItem::DisplayBlock, QStyleSheetItem::ListDisc );
+ else if ( style == i18n( "Bulleted List (Disc)" ) )
+ mEditor->setParagType( QStyleSheetItem::DisplayListItem, QStyleSheetItem::ListDisc );
+ else if ( style == i18n( "Bulleted List (Circle)" ) )
+ mEditor->setParagType( QStyleSheetItem::DisplayListItem, QStyleSheetItem::ListCircle );
+ else if ( style == i18n( "Bulleted List (Square)" ) )
+ mEditor->setParagType( QStyleSheetItem::DisplayListItem, QStyleSheetItem::ListSquare );
+ else if ( style == i18n( "Ordered List (Decimal)" ))
+ mEditor->setParagType( QStyleSheetItem::DisplayListItem, QStyleSheetItem::ListDecimal );
+ else if ( style == i18n( "Ordered List (Alpha lower)" ) )
+ mEditor->setParagType( QStyleSheetItem::DisplayListItem, QStyleSheetItem::ListLowerAlpha );
+ else if ( style == i18n( "Ordered List (Alpha upper)" ) )
+ mEditor->setParagType( QStyleSheetItem::DisplayListItem, QStyleSheetItem::ListUpperAlpha );
+ mEditor->viewport()->setFocus();
+}
+
+void KMComposeWin::slotFontAction( const QString& font)
+{
+ toggleMarkup(true);
+ mEditor->QTextEdit::setFamily( font );
+ mEditor->viewport()->setFocus();
+}
+
+void KMComposeWin::slotSizeAction( int size )
+{
+ toggleMarkup(true);
+ mEditor->setPointSize( size );
+ mEditor->viewport()->setFocus();
+}
+
+void KMComposeWin::slotAlignLeft()
+{
+ toggleMarkup(true);
+ mEditor->QTextEdit::setAlignment( AlignLeft );
+}
+
+void KMComposeWin::slotAlignCenter()
+{
+ toggleMarkup(true);
+ mEditor->QTextEdit::setAlignment( AlignHCenter );
+}
+
+void KMComposeWin::slotAlignRight()
+{
+ toggleMarkup(true);
+ mEditor->QTextEdit::setAlignment( AlignRight );
+}
+
+void KMComposeWin::slotTextBold()
+{
+ toggleMarkup(true);
+ mEditor->QTextEdit::setBold( textBoldAction->isChecked() );
+}
+
+void KMComposeWin::slotTextItalic()
+{
+ toggleMarkup(true);
+ mEditor->QTextEdit::setItalic( textItalicAction->isChecked() );
+}
+
+void KMComposeWin::slotTextUnder()
+{
+ toggleMarkup(true);
+ mEditor->QTextEdit::setUnderline( textUnderAction->isChecked() );
+}
+
+void KMComposeWin::slotFormatReset()
+{
+ mEditor->setColor(mForeColor);
+ mEditor->setCurrentFont( mSaveFont ); // fontChanged is called now
+}
+void KMComposeWin::slotTextColor()
+{
+ QColor color = mEditor->color();
+
+ if ( KColorDialog::getColor( color, this ) ) {
+ toggleMarkup(true);
+ mEditor->setColor( color );
+ }
+}
+
+void KMComposeWin::fontChanged( const QFont &f )
+{
+ QFont fontTemp = f;
+ fontTemp.setBold( true );
+ fontTemp.setItalic( true );
+ QFontInfo fontInfo( fontTemp );
+
+ if ( fontInfo.bold() ) {
+ textBoldAction->setChecked( f.bold() );
+ textBoldAction->setEnabled( true ) ;
+ } else {
+ textBoldAction->setEnabled( false );
+ }
+
+ if ( fontInfo.italic() ) {
+ textItalicAction->setChecked( f.italic() );
+ textItalicAction->setEnabled( true ) ;
+ } else {
+ textItalicAction->setEnabled( false );
+ }
+
+ textUnderAction->setChecked( f.underline() );
+
+ fontAction->setFont( f.family() );
+ fontSizeAction->setFontSize( f.pointSize() );
+}
+
+void KMComposeWin::alignmentChanged( int a )
+{
+ //toggleMarkup();
+ alignLeftAction->setChecked( ( a == AlignAuto ) || ( a & AlignLeft ) );
+ alignCenterAction->setChecked( ( a & AlignHCenter ) );
+ alignRightAction->setChecked( ( a & AlignRight ) );
+}
+
+namespace {
+ class KToggleActionResetter {
+ KToggleAction * mAction;
+ bool mOn;
+ public:
+ KToggleActionResetter( KToggleAction * action, bool on )
+ : mAction( action ), mOn( on ) {}
+ ~KToggleActionResetter() {
+ if ( mAction )
+ mAction->setChecked( mOn );
+ }
+ void disable() { mAction = 0; }
+ };
+}
+
+void KMComposeWin::slotEncryptChiasmusToggled( bool on ) {
+ mEncryptWithChiasmus = false;
+
+ if ( !on )
+ return;
+
+ KToggleActionResetter resetter( mEncryptChiasmusAction, false );
+
+ const Kleo::CryptoBackend::Protocol * chiasmus =
+ Kleo::CryptoBackendFactory::instance()->protocol( "Chiasmus" );
+
+ if ( !chiasmus ) {
+ const QString msg = Kleo::CryptoBackendFactory::instance()->knowsAboutProtocol( "Chiasmus" )
+ ? i18n( "Please configure a Crypto Backend to use for "
+ "Chiasmus encryption first.\n"
+ "You can do this in the Crypto Backends tab of "
+ "the configure dialog's Security page." )
+ : i18n( "It looks as though libkleopatra was compiled without "
+ "Chiasmus support. You might want to recompile "
+ "libkleopatra with --enable-chiasmus.");
+ KMessageBox::information( this, msg, i18n("No Chiasmus Backend Configured" ) );
+ return;
+ }
+
+ STD_NAMESPACE_PREFIX auto_ptr<Kleo::SpecialJob> job( chiasmus->specialJob( "x-obtain-keys", QMap<QString,QVariant>() ) );
+ if ( !job.get() ) {
+ const QString msg = i18n( "Chiasmus backend does not offer the "
+ "\"x-obtain-keys\" function. Please report this bug." );
+ KMessageBox::error( this, msg, i18n( "Chiasmus Backend Error" ) );
+ return;
+ }
+
+ if ( job->exec() ) {
+ job->showErrorDialog( this, i18n( "Chiasmus Backend Error" ) );
+ return;
+ }
+
+ const QVariant result = job->property( "result" );
+ if ( result.type() != QVariant::StringList ) {
+ const QString msg = i18n( "Unexpected return value from Chiasmus backend: "
+ "The \"x-obtain-keys\" function did not return a "
+ "string list. Please report this bug." );
+ KMessageBox::error( this, msg, i18n( "Chiasmus Backend Error" ) );
+ return;
+ }
+
+ const QStringList keys = result.toStringList();
+ if ( keys.empty() ) {
+ const QString msg = i18n( "No keys have been found. Please check that a "
+ "valid key path has been set in the Chiasmus "
+ "configuration." );
+ KMessageBox::information( this, msg, i18n( "No Chiasmus Keys Found" ) );
+ return;
+ }
+
+ ChiasmusKeySelector selectorDlg( this, i18n( "Chiasmus Encryption Key Selection" ),
+ keys, GlobalSettings::chiasmusKey(),
+ GlobalSettings::chiasmusOptions() );
+ if ( selectorDlg.exec() != QDialog::Accepted )
+ return;
+
+ GlobalSettings::setChiasmusOptions( selectorDlg.options() );
+ GlobalSettings::setChiasmusKey( selectorDlg.key() );
+ assert( !GlobalSettings::chiasmusKey().isEmpty() );
+ mEncryptWithChiasmus = true;
+ resetter.disable();
+}
+
+void KMComposeWin::slotEditDone(KMail::EditorWatcher * watcher)
+{
+ kdDebug(5006) << k_funcinfo << endl;
+ KMMessagePart *part = mEditorMap[ watcher ];
+ KTempFile *tf = mEditorTempFiles[ watcher ];
+ mEditorMap.remove( watcher );
+ mEditorTempFiles.remove( watcher );
+ if ( !watcher->fileChanged() )
+ return;
+
+ tf->file()->reset();
+ QByteArray data = tf->file()->readAll();
+ part->setBodyEncodedBinary( data );
+}
+
+
+void KMComposeWin::slotUpdateSignatureAndEncrypionStateIndicators()
+{
+ const bool showIndicatorsAlways = false; // FIXME config option?
+ mSignatureStateIndicator->setText( mSignAction->isChecked()? i18n("Message will be signed") : i18n("Message will not be signed") );
+ mEncryptionStateIndicator->setText( mEncryptAction->isChecked()? i18n("Message will be encrypted") : i18n("Message will not be encrypted") );
+ if ( !showIndicatorsAlways ) {
+ mSignatureStateIndicator->setShown( mSignAction->isChecked() );
+ mEncryptionStateIndicator->setShown( mEncryptAction->isChecked() );
+ }
+}
+
+void KMComposeWin::slotAttachmentDragStarted()
+{
+ kdDebug(5006) << k_funcinfo << endl;
+ int idx = 0;
+ QStringList filenames;
+ for ( QPtrListIterator<QListViewItem> it(mAtmItemList); *it; ++it, ++idx ) {
+ if ( (*it)->isSelected() ) {
+ KMMessagePart* msgPart = mAtmList.at(idx);
+ KTempDir * tempDir = new KTempDir(); // will be deleted on composer close
+ tempDir->setAutoDelete( true );
+ mTempDirs.insert( tempDir );
+ const QString fileName = tempDir->name() + "/" + msgPart->name();
+ KPIM::kByteArrayToFile(msgPart->bodyDecodedBinary(),
+ fileName,
+ false, false, false);
+ KURL url;
+ url.setPath( fileName );
+ filenames << url.path();
+ }
+ }
+ if ( filenames.isEmpty() ) return;
+
+ QUriDrag *drag = new QUriDrag( mAtmListView );
+ drag->setFileNames( filenames );
+ drag->dragCopy();
+}
+
+void KMComposeWin::recipientEditorSizeHintChanged()
+{
+ QTimer::singleShot( 1, this, SLOT(setMaximumHeaderSize()) );
+}
+
+void KMComposeWin::setMaximumHeaderSize()
+{
+ mHeadersArea->setMaximumHeight( mHeadersArea->sizeHint().height() );
+}
+
diff --git a/kmail/kmcomposewin.h b/kmail/kmcomposewin.h
new file mode 100644
index 00000000..5241786b
--- /dev/null
+++ b/kmail/kmcomposewin.h
@@ -0,0 +1,911 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ * KMComposeWin Header File
+ * Author: Markus Wuebben <markus.wuebben@kde.org>
+ */
+#ifndef __KMComposeWin
+#define __KMComposeWin
+
+#ifndef KDE_USE_FINAL
+# ifndef REALLY_WANT_KMCOMPOSEWIN_H
+# error Do not include kmcomposewin.h anymore. Include composer.h instead.
+# endif
+#endif
+
+#include "composer.h"
+#include "messagesender.h"
+
+#include <set>
+
+#include <qlabel.h>
+#include <qlistview.h>
+
+#include <qcheckbox.h>
+#include <qpushbutton.h>
+#include <qclipboard.h>
+#include <qpalette.h>
+#include <qfont.h>
+#include <qptrlist.h>
+#include <qvaluevector.h>
+#include <qsplitter.h>
+
+#include <kio/job.h>
+#include <kglobalsettings.h>
+#include <kdeversion.h>
+#include <keditcl.h>
+#include <ktempdir.h>
+
+#include "mailcomposerIface.h"
+
+#include <libkdepim/addresseelineedit.h>
+#include <mimelib/mediatyp.h>
+
+#include <kleo/enum.h>
+
+class QCloseEvent;
+class QComboBox;
+class QFrame;
+class QGridLayout;
+class QListView;
+class QPopupMenu;
+class QPushButton;
+class QCString;
+class KCompletion;
+class KMEdit;
+class KMComposeWin;
+class KMFolderComboBox;
+class KMFolder;
+class KMMessage;
+class KMMessagePart;
+class KProcess;
+class KDirWatch;
+class KSelectAction;
+class KFontAction;
+class KFontSizeAction;
+class KSelectAction;
+class KStatusBar;
+class KAction;
+class KToggleAction;
+class KTempFile;
+class KToolBar;
+class KToggleAction;
+class KSelectColorAction;
+class KURL;
+class KRecentFilesAction;
+class SpellingFilter;
+class MessageComposer;
+class RecipientsEditor;
+class KMLineEdit;
+class KMLineEditSpell;
+class KMAtmListViewItem;
+class SnippetWidget;
+
+namespace KPIM {
+ class IdentityCombo;
+ class Identity;
+}
+
+namespace KMail {
+ class AttachmentListView;
+ class DictionaryComboBox;
+ class EditorWatcher;
+}
+
+namespace GpgME {
+ class Error;
+}
+
+//-----------------------------------------------------------------------------
+class KMComposeWin : public KMail::Composer, virtual public MailComposerIface
+{
+ Q_OBJECT
+ friend class ::KMEdit;
+ friend class ::MessageComposer;
+
+private: // mailserviceimpl, kmkernel, kmcommands, callback, kmmainwidget
+ KMComposeWin( KMMessage* msg=0, uint identity=0 );
+ ~KMComposeWin();
+public:
+ static Composer * create( KMMessage * msg = 0, uint identity = 0 );
+
+ MailComposerIface * asMailComposerIFace() { return this; }
+ const MailComposerIface * asMailComposerIFace() const { return this; }
+
+public: // mailserviceimpl
+ /**
+ * From MailComposerIface
+ */
+ void send(int how);
+ void addAttachmentsAndSend(const KURL::List &urls, const QString &comment, int how);
+ void addAttachment(KURL url,QString comment);
+ void addAttachment(const QString &name,
+ const QCString &cte,
+ const QByteArray &data,
+ const QCString &type,
+ const QCString &subType,
+ const QCString &paramAttr,
+ const QString &paramValue,
+ const QCString &contDisp);
+public: // kmcommand
+ void setBody (QString body);
+
+private:
+ /**
+ * To catch palette changes
+ */
+ virtual bool event(QEvent *e);
+
+ /**
+ * update colors
+ */
+ void readColorConfig();
+
+ /**
+ * Write settings to app's config file.
+ */
+ void writeConfig(void);
+
+ /**
+ * If necessary increases the word wrap of the editor so that it will
+ * not wrap the body string
+ */
+ void verifyWordWrapLengthIsAdequate(const QString&);
+
+public: // kmkernel, kmcommands, callback
+ /**
+ * Set the message the composer shall work with. This discards
+ * previous messages without calling applyChanges() on them before.
+ */
+ void setMsg(KMMessage* newMsg, bool mayAutoSign=TRUE,
+ bool allowDecryption=FALSE, bool isModified=FALSE);
+
+ void disableWordWrap();
+
+private: // kmedit
+ /**
+ * Returns message of the composer. To apply the user changes to the
+ * message, call applyChanges() first.
+ */
+ KMMessage* msg() const { return mMsg; }
+
+public: // kmkernel
+ /**
+ * Set the filename which is used for autosaving.
+ */
+ void setAutoSaveFilename( const QString & filename );
+
+private:
+ /**
+ * Returns true if the message was modified by the user.
+ */
+ bool isModified() const;
+
+ /**
+ * Set whether the message should be treated as modified or not.
+ */
+ void setModified( bool modified );
+
+public: // kmkernel, callback
+ /**
+ * If this flag is set the message of the composer is deleted when
+ * the composer is closed and the message was not sent. Default: FALSE
+ */
+ inline void setAutoDelete(bool f) { mAutoDeleteMsg = f; }
+
+ /**
+ * If this flag is set, the compose window will delete itself after
+ * the window has been closed.
+ */
+ void setAutoDeleteWindow( bool f );
+
+public: // kmcommand
+ /**
+ * If this folder is set, the original message is inserted back after
+ * cancelling
+ */
+ void setFolder(KMFolder* aFolder) { mFolder = aFolder; }
+public: // kmkernel, kmcommand, mailserviceimpl
+ /**
+ * Recode to the specified charset
+ */
+ void setCharset(const QCString& aCharset, bool forceDefault = FALSE);
+
+public: // kmcommand
+ /**
+ * Sets the focus to the edit-widget and the cursor below the
+ * "On ... you wrote" line when hasMessage is true.
+ * Make sure you call this _after_ setMsg().
+ */
+ void setReplyFocus( bool hasMessage = true );
+
+ /**
+ * Sets the focus to the subject line edit. For use when creating a
+ * message to a known recipient.
+ */
+ void setFocusToSubject();
+
+private:
+ /**
+ * determines whether inline signing/encryption is selected
+ */
+ bool inlineSigningEncryptionSelected();
+
+ /**
+ * Tries to find the given mimetype @p type in the KDE Mimetype registry.
+ * If found, returns its localized description, otherwise the @p type
+ * in lowercase.
+ */
+ static QString prettyMimeType( const QString& type );
+ QString quotePrefixName() const;
+
+private: // kmedit:
+ KMLineEditSpell *sujectLineWidget() const { return mEdtSubject;}
+ void setSubjectTextWasSpellChecked( bool _spell ) {
+ mSubjectTextWasSpellChecked = _spell;
+ }
+ bool subjectTextWasSpellChecked() const { return mSubjectTextWasSpellChecked; }
+
+ void paste( QClipboard::Mode mode );
+
+public: // callback
+ /** Disabled signing and encryption completely for this composer window. */
+ void setSigningAndEncryptionDisabled( bool v )
+ {
+ mSigningAndEncryptionExplicitlyDisabled = v;
+ }
+
+private slots:
+ void polish();
+ /**
+ * Actions:
+ */
+ void slotPrint();
+ void slotAttachFile();
+ void slotInsertRecentFile(const KURL&);
+ void slotAttachedFile(const KURL&);
+public slots: // kmkernel, callback
+ void slotSendNow();
+private slots:
+ void slotSendNowVia( int item );
+ void slotSendLater();
+ void slotSendLaterVia( int item );
+
+ void getTransportMenu();
+
+ /**
+ * Returns true when saving was successful.
+ */
+ void slotSaveDraft();
+ void slotSaveTemplate();
+ void slotNewComposer();
+ void slotNewMailReader();
+ void slotClose();
+ void slotHelp();
+
+ void slotFind();
+ void slotSearchAgain();
+ void slotReplace();
+ void slotUndo();
+ void slotRedo();
+ void slotCut();
+ void slotCopy();
+ void slotPasteClipboard();
+ void slotPasteClipboardAsQuotation();
+ void slotPasteClipboardAsAttachment();
+ void slotAddQuotes();
+ void slotRemoveQuotes();
+ void slotAttachPNGImageData(const QByteArray &image);
+
+ void slotMarkAll();
+
+ void slotFolderRemoved(KMFolder*);
+
+ void slotEditDone( KMail::EditorWatcher* watcher );
+
+public slots: // kmkernel
+ /**
+ Tell the composer to always send the message, even if the user
+ hasn't changed the next. This is useful if a message is
+ autogenerated (e.g., via a DCOP call), and the user should
+ simply be able to confirm the message and send it.
+ */
+ void slotSetAlwaysSend( bool bAlwaysSend );
+private slots:
+ /**
+ * toggle fixed width font.
+ */
+ void slotUpdateFont();
+
+ /**
+ * Open addressbook editor dialog.
+ */
+ void slotAddrBook();
+ /**
+ * Insert a file to the end of the text in the editor.
+ */
+ void slotInsertFile();
+
+ void slotSetCharset();
+ /**
+ * Check spelling of text.
+ */
+ void slotSpellcheck();
+ void slotSpellcheckConfig();
+ void slotSubjectTextSpellChecked();
+
+ /**
+ * Change crypto plugin to be used for signing/encrypting messages,
+ * or switch to built-in OpenPGP code.
+ */
+ void slotSelectCryptoModule( bool init = false );
+
+ /**
+ * XML-GUI stuff
+ */
+ void slotStatusMessage(const QString &message);
+ void slotEditToolbars();
+ void slotUpdateToolbars();
+ void slotEditKeys();
+ /**
+ * Read settings from app's config file.
+ */
+ void readConfig( bool reload = false );
+ /**
+ * Change window title to given string.
+ */
+ void slotUpdWinTitle(const QString& );
+
+ /**
+ * Switch the icon to lock or unlock respectivly.
+ * Change states of all encrypt check boxes in the attachments listview
+ */
+ void slotEncryptToggled(bool);
+
+ /**
+ * Change states of all sign check boxes in the attachments listview
+ */
+ void slotSignToggled(bool);
+
+public slots: // kmkernel, callback
+ /**
+ * Switch wordWrap on/off
+ */
+ void slotWordWrapToggled(bool);
+
+private slots:
+ /**
+ * Append signature file to the end of the text in the editor.
+ */
+ void slotAppendSignature();
+
+ /**
+ * Prepend signature file at the beginning of the text in the editor.
+ */
+ void slotPrependSignature();
+
+ /**
+ * Insert signature file at the cursor position of the text in the editor.
+ */
+ void slotInsertSignatureAtCursor();
+
+ /**
+ * Attach sender's public key.
+ */
+ void slotInsertMyPublicKey();
+
+ /**
+ * Insert arbitary public key from public keyring in the editor.
+ */
+ void slotInsertPublicKey();
+
+ /**
+ * Enable/disable some actions in the Attach menu
+ */
+ void slotUpdateAttachActions();
+
+ /**
+ * Open a popup-menu in the attachments-listbox.
+ */
+ void slotAttachPopupMenu(QListViewItem *, const QPoint &, int);
+
+ /**
+ * Returns the number of the current attachment in the listbox,
+ * or -1 if there is no current attachment
+ */
+ int currentAttachmentNum();
+
+ /**
+ * Attachment operations.
+ */
+ void slotAttachOpen();
+ void slotAttachView();
+ void slotAttachRemove();
+ void slotAttachSave();
+ void slotAttachProperties();
+ void slotAttachOpenWith();
+ void slotAttachEdit();
+ void slotAttachEditWith();
+ void slotAttachmentDragStarted();
+
+ /**
+ * Select an email from the addressbook and add it to the line
+ * the pressed button belongs to.
+ */
+ void slotAddrBookTo();
+ void slotAddrBookFrom();
+ void slotAddrBookReplyTo();
+
+ void slotCleanSpace();
+
+ void slotToggleMarkup();
+ void toggleMarkup(bool markup);
+ void htmlToolBarVisibilityChanged( bool visible );
+
+// void slotSpellConfigure();
+ void slotSpellcheckDone(int result);
+ void slotSpellcheckDoneClearStatus();
+
+public slots: // kmkernel
+ void autoSaveMessage();
+
+private slots:
+ void updateCursorPosition();
+
+ void slotView();
+
+ /**
+ * Update composer field to reflect new identity
+ */
+ void slotIdentityChanged(uint);
+
+ /**
+ * KIO slots for attachment insertion
+ */
+ void slotAttachFileData(KIO::Job *, const QByteArray &);
+ void slotAttachFileResult(KIO::Job *);
+
+ void slotListAction(const QString &);
+ void slotFontAction(const QString &);
+ void slotSizeAction(int);
+ void slotAlignLeft();
+ void slotAlignCenter();
+ void slotAlignRight();
+ void slotTextBold();
+ void slotTextItalic();
+ void slotTextUnder();
+ void slotFormatReset();
+ void slotTextColor();
+ void fontChanged( const QFont & );
+ void alignmentChanged( int );
+
+public: // kmkernel, attachmentlistview
+ bool addAttach(const KURL url);
+
+public: // kmcommand
+ /**
+ * Add an attachment to the list.
+ */
+ void addAttach(const KMMessagePart* msgPart);
+
+private:
+ const KPIM::Identity & identity() const;
+ uint identityUid() const;
+ Kleo::CryptoMessageFormat cryptoMessageFormat() const;
+ bool encryptToSelf() const;
+
+signals:
+ void applyChangesDone( bool );
+ void attachmentAdded( const KURL&, bool success );
+
+private:
+ /**
+ * Applies the user changes to the message object of the composer
+ * and signs/encrypts the message if activated. Returns FALSE in
+ * case of an error (e.g. if PGP encryption fails).
+ * Disables the controls of the composer window unless @dontDisable
+ * is true.
+ */
+ void applyChanges( bool dontSignNorEncrypt, bool dontDisable=false );
+
+ /**
+ * Install grid management and header fields. If fields exist that
+ * should not be there they are removed. Those that are needed are
+ * created if necessary.
+ */
+ void rethinkFields(bool fromslot=false);
+
+ /**
+ Connect signals for moving focus by arrow keys. Returns next edit.
+ */
+ QWidget *connectFocusMoving( QWidget *prev, QWidget *next );
+
+ /**
+ * Show or hide header lines
+ */
+
+ void rethinkHeaderLine( int aValue, int aMask, int& aRow,
+ const QString &aLabelStr, QLabel* aLbl,
+ QLineEdit* aEdt, QPushButton* aBtn = 0,
+ const QString &toolTip = QString::null,
+ const QString &whatsThis = QString::null );
+
+ void rethinkHeaderLine( int value, int mask, int& row,
+ const QString& labelStr, QLabel* lbl,
+ QComboBox* cbx, QCheckBox *chk );
+
+ /**
+ * Initialization methods
+ */
+ void setupActions();
+ void setupStatusBar();
+ void setupEditor();
+
+
+ /**
+ * Header fields.
+ */
+ QString subject() const;
+ QString to() const;
+ QString cc() const;
+ QString bcc() const;
+ QString from() const;
+ QString replyTo() const;
+
+ /**
+ * Use the given folder as sent-mail folder if the given folder exists.
+ * Else show an error message and use the default sent-mail folder as
+ * sent-mail folder.
+ */
+ void setFcc( const QString &idString );
+
+ /**
+ * Ask for confirmation if the message was changed before close.
+ */
+ virtual bool queryClose ();
+ /**
+ * prevent kmail from exiting when last window is deleted (kernel rules)
+ */
+ virtual bool queryExit ();
+
+ /**
+ * Open the attachment with the given index and with ("Open with")
+ */
+ void openAttach( int index, bool with );
+
+ /**
+ * View the attachment with the given index.
+ */
+ void viewAttach( int index );
+
+ /**
+ Edit the attachment with the given index.
+ */
+ void editAttach( int index, bool openWith );
+
+ /**
+ * Remove an attachment from the list.
+ */
+ void removeAttach(const QString &url);
+ void removeAttach(int idx);
+
+ /**
+ * Updates an item in the QListView to represnet a given message part
+ */
+ void msgPartToItem(const KMMessagePart* msgPart, KMAtmListViewItem *lvi,
+ bool loadDefaults = true );
+
+ /**
+ * Open addressbook and append selected addresses to the given
+ * edit field.
+ */
+ void addrBookSelInto();
+
+ void addrBookSelIntoOld();
+ void addrBookSelIntoNew();
+
+private:
+ /**
+ * Turn encryption on/off. If setByUser is true then a message box is shown
+ * in case encryption isn't possible.
+ */
+ void setEncryption( bool encrypt, bool setByUser = false );
+
+ /**
+ * Turn signing on/off. If setByUser is true then a message box is shown
+ * in case signing isn't possible.
+ */
+ void setSigning( bool sign, bool setByUser = false );
+
+ /**
+ Returns true if the user forgot to attach something.
+ */
+ bool userForgotAttachment();
+
+ /**
+ * Retrieve encrypt flag of an attachment
+ * ( == state of it's check box in the attachments list view )
+ */
+ bool encryptFlagOfAttachment(int idx);
+
+ /**
+ * Retrieve sign flag of an attachment
+ * ( == state of it's check box in the attachments list view )
+ */
+ bool signFlagOfAttachment(int idx);
+
+
+ /**
+ * Decrypt an OpenPGP block or strip off the OpenPGP envelope of a text
+ * block with a clear text signature. This is only done if the given
+ * string contains exactly one OpenPGP block.
+ * This function is for example used to restore the unencrypted/unsigned
+ * message text for editting.
+ */
+ static void decryptOrStripOffCleartextSignature( QCString& );
+
+ /**
+ * Save the message into the Drafts or Templates folder.
+ */
+ bool saveDraftOrTemplate( const QString &folderName, KMMessage *msg );
+
+ /**
+ * Send the message. Returns true if the message was sent successfully.
+ */
+ enum SaveIn { None, Drafts, Templates };
+ void doSend( KMail::MessageSender::SendMethod method=KMail::MessageSender::SendDefault,
+ KMComposeWin::SaveIn saveIn = KMComposeWin::None );
+
+ /**
+ * Returns the autosave interval in milliseconds (as needed for QTimer).
+ */
+ int autoSaveInterval() const;
+
+ /**
+ * Initialize autosaving (timer and filename).
+ */
+ void initAutoSave();
+
+ /**
+ * Enables/disables autosaving depending on the value of the autosave
+ * interval.
+ */
+ void updateAutoSave();
+
+ /**
+ * Stop autosaving and delete the autosaved message.
+ */
+ void cleanupAutoSave();
+
+ /**
+ * Validates a list of email addresses.
+ * @return true if all addresses are valid.
+ * @return false if one or several addresses are invalid.
+ */
+ static bool validateAddresses( QWidget * parent, const QString & addresses );
+
+ /**
+ * Sets the transport combobox to @p transport. If @p transport is empty
+ * then the combobox remains unchanged. If @p transport is neither a known transport
+ * nor a custom transport then the combobox is set to the default transport.
+ * @param transport the transport the combobox should be set to
+ */
+ void setTransport( const QString & transport );
+
+ /**
+ * 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 );
+private slots:
+ /**
+ * Compress an attachemnt with the given index
+ */
+ void compressAttach(int idx);
+ void uncompressAttach(int idx);
+ void editorFocusChanged(bool gained);
+ void recipientEditorSizeHintChanged();
+ void setMaximumHeaderSize();
+
+private:
+ QWidget *mMainWidget;
+ QComboBox *mTransport;
+ KMail::DictionaryComboBox *mDictionaryCombo;
+ KPIM::IdentityCombo *mIdentity;
+ KMFolderComboBox *mFcc;
+ KMLineEdit *mEdtFrom, *mEdtReplyTo, *mEdtTo, *mEdtCc, *mEdtBcc;
+ KMLineEditSpell *mEdtSubject;
+ QLabel *mLblIdentity, *mLblTransport, *mLblFcc;
+ QLabel *mLblFrom, *mLblReplyTo, *mLblTo, *mLblCc, *mLblBcc, *mLblSubject;
+ QLabel *mDictionaryLabel;
+ QCheckBox *mBtnIdentity, *mBtnTransport, *mBtnFcc;
+ QPushButton *mBtnTo, *mBtnCc, *mBtnBcc, /* *mBtnFrom, */ *mBtnReplyTo;
+ bool mSpellCheckInProgress;
+ bool mDone;
+ bool mAtmModified;
+
+ KMEdit* mEditor;
+ QGridLayout* mGrid;
+ KMMessage *mMsg;
+ QValueVector<KMMessage*> mComposedMessages;
+ KMail::AttachmentListView* mAtmListView;
+ int mAtmColEncrypt;
+ int mAtmColSign;
+ int mAtmColCompress;
+ int mAtmEncryptColWidth;
+ int mAtmSignColWidth;
+ int mAtmCompressColWidth;
+ QPtrList<QListViewItem> mAtmItemList;
+ QPtrList<KMMessagePart> mAtmList;
+ QPopupMenu *mAttachMenu;
+ int mOpenId, mOpenWithId, mViewId, mRemoveId, mSaveAsId, mPropertiesId, mEditId, mEditWithId;
+ bool mAutoDeleteMsg;
+ bool mSigningAndEncryptionExplicitlyDisabled;
+ bool mLastSignActionState, mLastEncryptActionState;
+ bool mLastIdentityHasSigningKey, mLastIdentityHasEncryptionKey;
+ KMFolder *mFolder;
+ long mShowHeaders;
+ bool mConfirmSend;
+ bool mDisableBreaking; // Move
+ int mNumHeaders;
+ bool mUseHTMLEditor;
+ bool mHtmlMarkup;
+ QFont mBodyFont, mFixedFont;
+ QPtrList<KTempFile> mAtmTempList;
+ QPalette mPalette;
+ uint mId;
+ QString mOldSigText;
+
+ KAction *mAttachPK, *mAttachMPK,
+ *mAttachRemoveAction, *mAttachSaveAction, *mAttachPropertiesAction,
+ *mPasteQuotation, *mAddQuoteChars, *mRemQuoteChars;
+ KRecentFilesAction *mRecentAction;
+
+ KAction *mAppendSignatureAction, *mPrependSignatureAction, *mInsertSignatureAction;
+
+ KToggleAction *mSignAction, *mEncryptAction, *mRequestMDNAction;
+ KToggleAction *mUrgentAction, *mAllFieldsAction, *mFromAction;
+ KToggleAction *mReplyToAction, *mToAction, *mCcAction, *mBccAction;
+ KToggleAction *mSubjectAction;
+ KToggleAction *mIdentityAction, *mTransportAction, *mFccAction;
+ KToggleAction *mWordWrapAction, *mFixedFontAction, *mAutoSpellCheckingAction;
+ KToggleAction *mDictionaryAction, *mSnippetAction;
+
+ KSelectAction *listAction;
+ KFontAction *fontAction;
+ KFontSizeAction *fontSizeAction;
+ KToggleAction *alignLeftAction, *alignCenterAction, *alignRightAction;
+ KToggleAction *textBoldAction, *textItalicAction, *textUnderAction;
+ KToggleAction *plainTextAction, *markupAction;
+ KAction *actionFormatColor, *actionFormatReset;
+ KAction *mHtmlToolbar;
+
+ KSelectAction *mEncodingAction;
+ KSelectAction *mCryptoModuleAction;
+
+ QCString mCharset;
+ QCString mDefCharset;
+ QStringList mCharsets;
+ bool mAutoCharset;
+
+ bool mAlwaysSend;
+
+ QStringList mFolderNames;
+ QValueList<QGuardedPtr<KMFolder> > mFolderList;
+ QMap<KIO::Job*, KURL> mAttachJobs;
+ KURL::List mAttachFilesPending;
+ int mAttachFilesSend;
+
+private:
+ // helper method for slotInsert(My)PublicKey()
+ void startPublicKeyExport();
+ bool canSignEncryptAttachments() const {
+ return cryptoMessageFormat() != Kleo::InlineOpenPGPFormat;
+ }
+
+ bool mSubjectTextWasSpellChecked;
+
+ QString addQuotesToText( const QString &inputText );
+ QString removeQuotesFromText( const QString &inputText );
+ // helper method for rethinkFields
+ int calcColumnWidth(int which, long allShowing, int width);
+
+private slots:
+ void slotCompletionModeChanged( KGlobalSettings::Completion );
+ void slotConfigChanged();
+
+ void slotComposerDone( bool );
+
+ void slotContinueDoSend( bool );
+ void slotContinuePrint( bool );
+ void slotContinueAutoSave();
+
+ void slotEncryptChiasmusToggled( bool );
+
+ /**
+ * Helper method (you could call is a bottom-half :) for
+ * startPublicKeyExport()
+ */
+ void slotPublicKeyExportResult( const GpgME::Error & err, const QByteArray & keydata );
+
+ /**
+ * toggle automatic spellchecking
+ */
+ void slotAutoSpellCheckingToggled(bool);
+
+ /**
+ * Updates signature actions when identity changes.
+ */
+ void slotUpdateSignatureActions();
+
+ /**
+ * Updates the visibility and text of the signature and encryption state indicators.
+ */
+ void slotUpdateSignatureAndEncrypionStateIndicators();
+private:
+ QColor mForeColor,mBackColor;
+ QFont mSaveFont;
+ QSplitter *mHeadersToEditorSplitter;
+ QWidget* mHeadersArea;
+ QSplitter *mSplitter;
+ QSplitter *mSnippetSplitter;
+ struct atmLoadData
+ {
+ KURL url;
+ QByteArray data;
+ bool insert;
+ QCString encoding;
+ };
+ QMap<KIO::Job *, atmLoadData> mMapAtmLoadData;
+
+ // These are for passing on methods over the applyChanges calls
+ KMail::MessageSender::SendMethod mSendMethod;
+ KMComposeWin::SaveIn mSaveIn;
+
+ KToggleAction *mEncryptChiasmusAction;
+ bool mEncryptWithChiasmus;
+
+ // This is the temporary object that constructs the message out of the
+ // window
+ MessageComposer* mComposer;
+
+ // Temp var for slotPrint:
+ bool mMessageWasModified;
+
+ // Temp var for slotInsert(My)PublicKey():
+ QString mFingerprint;
+
+ // Temp ptr for saving image from clipboard
+ KTempDir *mTempDir;
+
+ bool mClassicalRecipients;
+
+ RecipientsEditor *mRecipientsEditor;
+ int mLabelWidth;
+
+ QTimer *mAutoSaveTimer;
+ QString mAutoSaveFilename;
+ int mLastAutoSaveErrno; // holds the errno of the last try to autosave
+
+ QPopupMenu *mActNowMenu;
+ QPopupMenu *mActLaterMenu;
+
+ QMap<KMail::EditorWatcher*, KMMessagePart*> mEditorMap;
+ QMap<KMail::EditorWatcher*, KTempFile*> mEditorTempFiles;
+
+ QLabel *mSignatureStateIndicator;
+ QLabel *mEncryptionStateIndicator;
+
+ SnippetWidget *mSnippetWidget;
+ std::set<KTempDir*> mTempDirs;
+
+ /** If the message in this composer has a cursor position set (for
+ * instance because it comes from a template containing %CURSOR)
+ * then we need to preserve that cursor position even when auto-
+ * appending (or prepending) the signature during composer setup.
+ * Set to true *once* (and only in setMsg() at that) to avoid
+ * accidentally moving the cursor.
+ */
+ bool mPreserveUserCursorPosition;
+};
+
+#endif
+
diff --git a/kmail/kmdebug.h b/kmail/kmdebug.h
new file mode 100644
index 00000000..e36621cf
--- /dev/null
+++ b/kmail/kmdebug.h
@@ -0,0 +1,62 @@
+// -*- c++ -*- convenience wrappers around kdDebug/kdWarning/etc
+
+#ifndef __KMAIL_KMDEBUG_H__
+#define __KMAIL_KMDEBUG_H__
+
+#include <kdebug.h>
+
+// Enable this to debug timing
+// #define DEBUG_TIMING
+
+// return type of kmDebug() depends on NDEBUG define:
+#ifdef NDEBUG
+# define kmail_dbgstream kndbgstream
+#else
+# define kmail_dbgstream kdbgstream
+#endif
+
+/** KMail's debug area code */
+static const int kmail_debug_area = 5006;
+
+static inline kmail_dbgstream kmDebug() { return kdDebug( kmail_debug_area ); }
+static inline kmail_dbgstream kmDebug( bool cond ) { return kdDebug( cond, kmail_debug_area ); }
+
+static inline kdbgstream kmWarning() { return kdWarning( kmail_debug_area ); }
+static inline kdbgstream kmWarning( bool cond ) { return kdWarning( cond, kmail_debug_area ); }
+
+static inline kdbgstream kmError() { return kdError( kmail_debug_area ); }
+static inline kdbgstream kmError( bool cond ) { return kdError( cond, kmail_debug_area ); }
+
+static inline kdbgstream kmFatal() { return kdFatal( kmail_debug_area ); }
+static inline kdbgstream kmFatal( bool cond ) { return kdFatal( cond, kmail_debug_area ); }
+
+// timing utilities
+#if !defined( NDEBUG ) && defined( DEBUG_TIMING )
+#include <qdatetime.h>
+#define CREATE_TIMER(x) int x=0, x ## _tmp=0; QTime x ## _tmp2
+#define START_TIMER(x) x ## _tmp2 = QTime::currentTime()
+#define GRAB_TIMER(x) x ## _tmp2.msecsTo(QTime::currentTime())
+#define END_TIMER(x) x += GRAB_TIMER(x); x ## _tmp++
+#define SHOW_TIMER(x) kdDebug(5006) << #x " == " << x << "(" << x ## _tmp << ")\n"
+#else
+#define CREATE_TIMER(x)
+#define START_TIMER(x)
+#define GRAB_TIMER(x)
+#define END_TIMER(x)
+#define SHOW_TIMER(x)
+#endif
+
+// profiling utilities
+#if !defined( NDEBUG )
+#define CREATE_COUNTER(x) int x ## _cnt=0
+#define RESET_COUNTER(x) x ## _cnt=0
+#define INC_COUNTER(x) x ## _cnt++
+#define SHOW_COUNTER(x) kdDebug(5006) << #x " == " << x ## _cnt << endl
+#else
+#define CREATE_COUNTER(x)
+#define RESET_COUNTER(x)
+#define INC_COUNTER(x)
+#define SHOW_COUNTER(x)
+#endif
+
+#endif // __KMAIL_KMDEBUG_H__
diff --git a/kmail/kmdict.cpp b/kmail/kmdict.cpp
new file mode 100644
index 00000000..3386f16a
--- /dev/null
+++ b/kmail/kmdict.cpp
@@ -0,0 +1,117 @@
+/* simple hash table for kmail. inspired by QDict */
+/* Author: Ronen Tzur <rtzur@shani.net> */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "kmdict.h"
+#include "kmglobal.h"
+#include <kdebug.h>
+
+#include <string.h>
+//-----------------------------------------------------------------------------
+
+KMDict::KMDict( int size )
+{
+ init( ( int ) KMail::nextPrime( size ) );
+ //kdDebug( 5006 ) << "KMMDict::KMDict Size: " << mSize << endl;
+}
+
+//-----------------------------------------------------------------------------
+
+KMDict::~KMDict()
+{
+ clear();
+}
+
+//-----------------------------------------------------------------------------
+
+void KMDict::init(int size)
+{
+ mSize = size;
+ mVecs = new KMDictItem *[mSize];
+ memset(mVecs, 0, mSize * sizeof(KMDictItem *));
+}
+
+//-----------------------------------------------------------------------------
+
+void KMDict::clear()
+{
+ if (!mVecs)
+ return;
+ for (int i = 0; i < mSize; i++) {
+ KMDictItem *item = mVecs[i];
+ while (item) {
+ KMDictItem *nextItem = item->next;
+ delete item;
+ item = nextItem;
+ }
+ }
+ delete [] mVecs;
+ mVecs = 0;
+}
+
+//-----------------------------------------------------------------------------
+
+void KMDict::replace( long key, KMDictItem *item )
+{
+ insert( key, item );
+ removeFollowing( item, key ); // remove other items with same key
+}
+
+//-----------------------------------------------------------------------------
+
+
+void KMDict::insert( long key, KMDictItem *item )
+{
+ item->key = key;
+ int idx = (unsigned long)key % mSize; // insert in
+ item->next = mVecs[idx]; // appropriate
+ mVecs[idx] = item; // column
+}
+
+//-----------------------------------------------------------------------------
+
+void KMDict::remove(long key)
+{
+ int idx = (unsigned long)key % mSize;
+ KMDictItem *item = mVecs[idx];
+
+ if (item) {
+ if (item->key == key) { // if first in the column
+ mVecs[idx] = item->next;
+ delete item;
+ } else
+ removeFollowing(item, key); // if deep in the column
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+void KMDict::removeFollowing(KMDictItem *item, long key)
+{
+ while (item) {
+ KMDictItem *itemNext = item->next;
+ if (itemNext && itemNext->key == key) {
+ KMDictItem *itemNextNext = itemNext->next;
+ delete itemNext;
+ item->next = itemNextNext;
+ } else
+ item = itemNext;
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+KMDictItem *KMDict::find(long key)
+{
+ int idx = (unsigned long)key % mSize;
+ KMDictItem *item = mVecs[idx];
+ while (item) {
+ if (item->key == key)
+ break;
+ item = item->next;
+ }
+ return item;
+}
diff --git a/kmail/kmdict.h b/kmail/kmdict.h
new file mode 100644
index 00000000..520315ad
--- /dev/null
+++ b/kmail/kmdict.h
@@ -0,0 +1,68 @@
+/*
+ * simple hash table for kmail. inspired by QDict
+ */
+
+#ifndef __KMDICT
+#define __KMDICT
+
+/**
+ * @short Class representing items in a KMDict
+ */
+class KMDictItem
+{
+public:
+ long key;
+ KMDictItem *next;
+};
+
+/**
+ * @short KMDict implements a lightweight dictionary with serial numbers as keys.
+ *
+ * KMDict is a leightweight dictionary used exclusively by KMMsgDict. It uses
+ * serial numbers as keys.
+ *
+ * @author Ronen Tzur <rtzur@shani.net>
+ */
+class KMDict
+{
+ friend class MessageDictTester;
+public:
+ /** Creates a hash table with @p size columns. */
+ KMDict(int size = 17);
+
+ /** Destroys the hash table object. */
+ ~KMDict();
+
+ /** Clears the hash table, removing all items. */
+ void clear();
+
+ /** Returns the size of the hash table. */
+ int size() { return mSize; }
+
+ /** Inserts an item, replacing old ones with the same key. */
+ void replace(long key, KMDictItem *item);
+
+ /** Inserts an item without replacing ones with the same key. */
+ void insert(long key, KMDictItem *item);
+
+ /** Removes an item. */
+ void remove(long key);
+
+ /** Find an item by key. Returns pointer to it, or 0 if not found. */
+ KMDictItem *find(long key);
+
+private:
+ /** Removes all items _following_ @p item with key @p key. */
+ void removeFollowing(KMDictItem *item, long key);
+
+ /** Initializes the hash table to @p size colums. */
+ void init(int size);
+
+ /** The size of the hash. */
+ int mSize;
+
+ /** The buckets. */
+ KMDictItem **mVecs;
+};
+
+#endif /* __KMDICT */
diff --git a/kmail/kmedit.cpp b/kmail/kmedit.cpp
new file mode 100644
index 00000000..b20570f4
--- /dev/null
+++ b/kmail/kmedit.cpp
@@ -0,0 +1,766 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+// kmcomposewin.cpp
+// Author: Markus Wuebben <markus.wuebben@kde.org>
+// This code is published under the GPL.
+
+#include <config.h>
+
+#include "kmedit.h"
+#include "kmlineeditspell.h"
+
+#define REALLY_WANT_KMCOMPOSEWIN_H
+#include "kmcomposewin.h"
+#undef REALLY_WANT_KMCOMPOSEWIN_H
+#include "kmmsgdict.h"
+#include "kmfolder.h"
+#include "kmcommands.h"
+
+#include <maillistdrag.h>
+using KPIM::MailListDrag;
+
+#include <libkdepim/kfileio.h>
+#include <libemailfunctions/email.h>
+
+#include <kcursor.h>
+#include <kprocess.h>
+
+#include <kpopupmenu.h>
+#include <kdebug.h>
+#include <kmessagebox.h>
+#include <kurldrag.h>
+
+#include <ktempfile.h>
+#include <klocale.h>
+#include <kapplication.h>
+#include <kdirwatch.h>
+#include <kiconloader.h>
+
+#include "globalsettings.h"
+#include "replyphrases.h"
+
+#include <kspell.h>
+#include <kspelldlg.h>
+#include <spellingfilter.h>
+#include <ksyntaxhighlighter.h>
+
+#include <qregexp.h>
+#include <qbuffer.h>
+#include <qevent.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+
+
+void KMEdit::contentsDragEnterEvent(QDragEnterEvent *e)
+{
+ if (e->provides(MailListDrag::format()))
+ e->accept(true);
+ else if (e->provides("image/png"))
+ e->accept();
+ else
+ return KEdit::contentsDragEnterEvent(e);
+}
+
+void KMEdit::contentsDragMoveEvent(QDragMoveEvent *e)
+{
+ if (e->provides(MailListDrag::format()))
+ e->accept();
+ else if (e->provides("image/png"))
+ e->accept();
+ else
+ return KEdit::contentsDragMoveEvent(e);
+}
+
+void KMEdit::keyPressEvent( QKeyEvent* e )
+{
+ if( e->key() == Key_Return ) {
+ int line, col;
+ getCursorPosition( &line, &col );
+ QString lineText = text( line );
+ // returns line with additional trailing space (bug in Qt?), cut it off
+ lineText.truncate( lineText.length() - 1 );
+ // special treatment of quoted lines only if the cursor is neither at
+ // the begin nor at the end of the line
+ if( ( col > 0 ) && ( col < int( lineText.length() ) ) ) {
+ bool isQuotedLine = false;
+ uint bot = 0; // bot = begin of text after quote indicators
+ while( bot < lineText.length() ) {
+ if( ( lineText[bot] == '>' ) || ( lineText[bot] == '|' ) ) {
+ isQuotedLine = true;
+ ++bot;
+ }
+ else if( lineText[bot].isSpace() ) {
+ ++bot;
+ }
+ else {
+ break;
+ }
+ }
+
+ KEdit::keyPressEvent( e );
+
+ // duplicate quote indicators of the previous line before the new
+ // line if the line actually contained text (apart from the quote
+ // indicators) and the cursor is behind the quote indicators
+ if( isQuotedLine
+ && ( bot != lineText.length() )
+ && ( col >= int( bot ) ) ) {
+
+ // The cursor position might have changed unpredictably if there was selected
+ // text which got replaced by a new line, so we query it again:
+ getCursorPosition( &line, &col );
+ QString newLine = text( line );
+ // remove leading white space from the new line and instead
+ // add the quote indicators of the previous line
+ unsigned int leadingWhiteSpaceCount = 0;
+ while( ( leadingWhiteSpaceCount < newLine.length() )
+ && newLine[leadingWhiteSpaceCount].isSpace() ) {
+ ++leadingWhiteSpaceCount;
+ }
+ newLine = newLine.replace( 0, leadingWhiteSpaceCount,
+ lineText.left( bot ) );
+ removeParagraph( line );
+ insertParagraph( newLine, line );
+ // place the cursor at the begin of the new line since
+ // we assume that the user split the quoted line in order
+ // to add a comment to the first part of the quoted line
+ setCursorPosition( line, 0 );
+ }
+ }
+ else
+ KEdit::keyPressEvent( e );
+ }
+ else
+ KEdit::keyPressEvent( e );
+}
+
+void KMEdit::contentsDropEvent(QDropEvent *e)
+{
+ if (e->provides(MailListDrag::format())) {
+ // Decode the list of serial numbers stored as the drag data
+ QByteArray serNums;
+ MailListDrag::decode( e, serNums );
+ QBuffer serNumBuffer(serNums);
+ serNumBuffer.open(IO_ReadOnly);
+ QDataStream serNumStream(&serNumBuffer);
+ Q_UINT32 serNum;
+ KMFolder *folder = 0;
+ int idx;
+ QPtrList<KMMsgBase> messageList;
+ while (!serNumStream.atEnd()) {
+ KMMsgBase *msgBase = 0;
+ serNumStream >> serNum;
+ KMMsgDict::instance()->getLocation(serNum, &folder, &idx);
+ if (folder)
+ msgBase = folder->getMsgBase(idx);
+ if (msgBase)
+ messageList.append( msgBase );
+ }
+ serNumBuffer.close();
+ uint identity = folder ? folder->identity() : 0;
+ KMCommand *command =
+ new KMForwardAttachedCommand(mComposer, messageList,
+ identity, mComposer);
+ command->start();
+ }
+ else if( e->provides("image/png") ) {
+ emit attachPNGImageData(e->encodedData("image/png"));
+ }
+ else if( KURLDrag::canDecode( e ) ) {
+ KURL::List urlList;
+ if( KURLDrag::decode( e, urlList ) ) {
+ KPopupMenu p;
+ p.insertItem( i18n("Add as Text"), 0 );
+ p.insertItem( i18n("Add as Attachment"), 1 );
+ int id = p.exec( mapToGlobal( e->pos() ) );
+ switch ( id) {
+ case 0:
+ for ( KURL::List::Iterator it = urlList.begin();
+ it != urlList.end(); ++it ) {
+ insert( (*it).url() );
+ }
+ break;
+ case 1:
+ for ( KURL::List::Iterator it = urlList.begin();
+ it != urlList.end(); ++it ) {
+ mComposer->addAttach( *it );
+ }
+ break;
+ }
+ }
+ else if ( QTextDrag::canDecode( e ) ) {
+ QString s;
+ if ( QTextDrag::decode( e, s ) )
+ insert( s );
+ }
+ else
+ kdDebug(5006) << "KMEdit::contentsDropEvent, unable to add dropped object" << endl;
+ }
+ else if( e->provides("text/x-textsnippet") ) {
+ emit insertSnippet();
+ }
+ else {
+ KEdit::contentsDropEvent(e);
+ }
+}
+
+KMEdit::KMEdit(QWidget *parent, KMComposeWin* composer,
+ KSpellConfig* autoSpellConfig,
+ const char *name)
+ : KEdit( parent, name ),
+ mComposer( composer ),
+ mKSpell( 0 ),
+ mSpellConfig( autoSpellConfig ),
+ mSpellingFilter( 0 ),
+ mExtEditorTempFile( 0 ),
+ mExtEditorTempFileWatcher( 0 ),
+ mExtEditorProcess( 0 ),
+ mUseExtEditor( false ),
+ mWasModifiedBeforeSpellCheck( false ),
+ mSpellChecker( 0 ),
+ mSpellLineEdit( false ),
+ mPasteMode( QClipboard::Clipboard )
+{
+ installEventFilter(this);
+ KCursor::setAutoHideCursor( this, true, true );
+ setOverwriteEnabled( true );
+}
+
+
+void KMEdit::initializeAutoSpellChecking()
+{
+ if ( mSpellChecker )
+ return; // already initialized
+ QColor defaultColor1( 0x00, 0x80, 0x00 ); // defaults from kmreaderwin.cpp
+ QColor defaultColor2( 0x00, 0x70, 0x00 );
+ QColor defaultColor3( 0x00, 0x60, 0x00 );
+ QColor defaultForeground( kapp->palette().active().text() );
+
+ QColor c = Qt::red;
+ KConfigGroup readerConfig( KMKernel::config(), "Reader" );
+ QColor col1;
+ if ( !readerConfig.readBoolEntry( "defaultColors", true ) )
+ col1 = readerConfig.readColorEntry( "ForegroundColor", &defaultForeground );
+ else
+ col1 = defaultForeground;
+ QColor col2 = readerConfig.readColorEntry( "QuotedText3", &defaultColor3 );
+ QColor col3 = readerConfig.readColorEntry( "QuotedText2", &defaultColor2 );
+ QColor col4 = readerConfig.readColorEntry( "QuotedText1", &defaultColor1 );
+ QColor misspelled = readerConfig.readColorEntry( "MisspelledColor", &c );
+ mSpellChecker = new KDictSpellingHighlighter( this, /*active*/ true,
+ /*autoEnabled*/ false,
+ /*spellColor*/ misspelled,
+ /*colorQuoting*/ true,
+ col1, col2, col3, col4,
+ mSpellConfig );
+
+ connect( mSpellChecker, SIGNAL(newSuggestions(const QString&, const QStringList&, unsigned int)),
+ this, SLOT(addSuggestion(const QString&, const QStringList&, unsigned int)) );
+}
+
+
+QPopupMenu *KMEdit::createPopupMenu( const QPoint& pos )
+{
+ enum { IdUndo, IdRedo, IdSep1, IdCut, IdCopy, IdPaste, IdClear, IdSep2, IdSelectAll };
+
+ QPopupMenu *menu = KEdit::createPopupMenu( pos );
+ if ( !QApplication::clipboard()->image().isNull() ) {
+ int id = menu->idAt(0);
+ menu->setItemEnabled( id - IdPaste, true);
+ }
+
+ return menu;
+}
+
+void KMEdit::deleteAutoSpellChecking()
+{ // because the highlighter doesn't support RichText, delete its instance.
+ delete mSpellChecker;
+ mSpellChecker =0;
+}
+
+void KMEdit::addSuggestion(const QString& text, const QStringList& lst, unsigned int )
+{
+ mReplacements[text] = lst;
+}
+
+void KMEdit::setSpellCheckingActive(bool spellCheckingActive)
+{
+ if ( mSpellChecker ) {
+ mSpellChecker->setActive(spellCheckingActive);
+ }
+}
+
+
+KMEdit::~KMEdit()
+{
+ removeEventFilter(this);
+
+ delete mKSpell;
+ delete mSpellChecker;
+ mSpellChecker = 0;
+
+}
+
+
+
+QString KMEdit::brokenText()
+{
+ QString temp, line;
+
+ int num_lines = numLines();
+ for (int i = 0; i < num_lines; ++i)
+ {
+ int lastLine = 0;
+ line = textLine(i);
+ for (int j = 0; j < (int)line.length(); ++j)
+ {
+ if (lineOfChar(i, j) > lastLine)
+ {
+ lastLine = lineOfChar(i, j);
+ temp += '\n';
+ }
+ temp += line[j];
+ }
+ if (i + 1 < num_lines) temp += '\n';
+ }
+
+ return temp;
+}
+
+
+unsigned int KMEdit::lineBreakColumn() const
+{
+ unsigned int lineBreakColumn = 0;
+ unsigned int numlines = numLines();
+ while ( numlines-- ) {
+ lineBreakColumn = QMAX( lineBreakColumn, textLine( numlines ).length() );
+ }
+ return lineBreakColumn;
+}
+
+
+bool KMEdit::eventFilter(QObject*o, QEvent* e)
+{
+ if (o == this)
+ KCursor::autoHideEventFilter(o, e);
+
+ if (e->type() == QEvent::KeyPress)
+ {
+ QKeyEvent *k = (QKeyEvent*)e;
+
+ if (mUseExtEditor) {
+ if (k->key() == Key_Up)
+ {
+ emit focusUp();
+ return true;
+ }
+
+ // ignore modifier keys (cf. bug 48841)
+ if ( (k->key() == Key_Shift) || (k->key() == Key_Control) ||
+ (k->key() == Key_Meta) || (k->key() == Key_Alt) )
+ return true;
+ if (mExtEditorTempFile) return true;
+ QString sysLine = mExtEditor;
+ mExtEditorTempFile = new KTempFile();
+
+ mExtEditorTempFile->setAutoDelete(true);
+
+ (*mExtEditorTempFile->textStream()) << text();
+
+ mExtEditorTempFile->close();
+ // replace %f in the system line
+ sysLine.replace( "%f", mExtEditorTempFile->name() );
+ mExtEditorProcess = new KProcess();
+ mExtEditorProcess->setUseShell( true );
+ sysLine += " ";
+ while (!sysLine.isEmpty())
+ {
+ *mExtEditorProcess << sysLine.left(sysLine.find(" ")).local8Bit();
+ sysLine.remove(0, sysLine.find(" ") + 1);
+ }
+ connect(mExtEditorProcess, SIGNAL(processExited(KProcess*)),
+ SLOT(slotExternalEditorDone(KProcess*)));
+ if (!mExtEditorProcess->start())
+ {
+ KMessageBox::error( topLevelWidget(),
+ i18n("Unable to start external editor.") );
+ killExternalEditor();
+ } else {
+ mExtEditorTempFileWatcher = new KDirWatch( this, "mExtEditorTempFileWatcher" );
+ connect( mExtEditorTempFileWatcher, SIGNAL(dirty(const QString&)),
+ SLOT(slotExternalEditorTempFileChanged(const QString&)) );
+ mExtEditorTempFileWatcher->addFile( mExtEditorTempFile->name() );
+ }
+ return true;
+ } else {
+ // ---sven's Arrow key navigation start ---
+ // Key Up in first line takes you to Subject line.
+ if (k->key() == Key_Up && k->state() != ShiftButton && currentLine() == 0
+ && lineOfChar(0, currentColumn()) == 0)
+ {
+ deselect();
+ emit focusUp();
+ return true;
+ }
+ // ---sven's Arrow key navigation end ---
+
+ if (k->key() == Key_Backtab && k->state() == ShiftButton)
+ {
+ deselect();
+ emit focusUp();
+ return true;
+ }
+
+ }
+ } else if ( e->type() == QEvent::ContextMenu ) {
+ QContextMenuEvent *event = (QContextMenuEvent*) e;
+
+ int para = 1, charPos, firstSpace, lastSpace;
+
+ //Get the character at the position of the click
+ charPos = charAt( viewportToContents(event->pos()), &para );
+ QString paraText = text( para );
+
+ if( !paraText.at(charPos).isSpace() )
+ {
+ //Get word right clicked on
+ const QRegExp wordBoundary( "[\\s\\W]" );
+ firstSpace = paraText.findRev( wordBoundary, charPos ) + 1;
+ lastSpace = paraText.find( wordBoundary, charPos );
+ if( lastSpace == -1 )
+ lastSpace = paraText.length();
+ QString word = paraText.mid( firstSpace, lastSpace - firstSpace );
+ //Continue if this word was misspelled
+ if( !word.isEmpty() && mReplacements.contains( word ) )
+ {
+ KPopupMenu p;
+ p.insertTitle( i18n("Suggestions") );
+
+ //Add the suggestions to the popup menu
+ QStringList reps = mReplacements[word];
+ if( reps.count() > 0 )
+ {
+ int listPos = 0;
+ for ( QStringList::Iterator it = reps.begin(); it != reps.end(); ++it ) {
+ p.insertItem( *it, listPos );
+ listPos++;
+ }
+ }
+ else
+ {
+ p.insertItem( QString::fromLatin1("No Suggestions"), -2 );
+ }
+
+ //Execute the popup inline
+ int id = p.exec( mapToGlobal( event->pos() ) );
+
+ if( id > -1 )
+ {
+ //Save the cursor position
+ int parIdx = 1, txtIdx = 1;
+ getCursorPosition(&parIdx, &txtIdx);
+ setSelection(para, firstSpace, para, lastSpace);
+ insert(mReplacements[word][id]);
+ // Restore the cursor position; if the cursor was behind the
+ // misspelled word then adjust the cursor position
+ if ( para == parIdx && txtIdx >= lastSpace )
+ txtIdx += mReplacements[word][id].length() - word.length();
+ setCursorPosition(parIdx, txtIdx);
+ }
+ //Cancel original event
+ return true;
+ }
+ }
+ } else if ( e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut ) {
+ QFocusEvent *fe = static_cast<QFocusEvent*>(e);
+ if(! (fe->reason() == QFocusEvent::ActiveWindow || fe->reason() == QFocusEvent::Popup) )
+ emit focusChanged( fe->gotFocus() );
+ }
+
+ return KEdit::eventFilter(o, e);
+}
+
+
+int KMEdit::autoSpellChecking( bool on )
+{
+ if ( textFormat() == Qt::RichText ) {
+ // syntax highlighter doesn't support extended text properties
+ if ( on )
+ KMessageBox::sorry(this, i18n("Automatic spellchecking is not possible on text with markup."));
+ return -1;
+ }
+ if ( mSpellChecker ) {
+ // don't autoEnable spell checking if the user turned spell checking off
+ mSpellChecker->setAutomatic( on );
+ mSpellChecker->setActive( on );
+ }
+ return 1;
+}
+
+
+void KMEdit::slotExternalEditorTempFileChanged( const QString & fileName ) {
+ if ( !mExtEditorTempFile )
+ return;
+ if ( fileName != mExtEditorTempFile->name() )
+ return;
+ // read data back in from file
+ setAutoUpdate(false);
+ clear();
+
+ insertLine(QString::fromLocal8Bit(KPIM::kFileToString( fileName, true, false )), -1);
+ setAutoUpdate(true);
+ repaint();
+}
+
+void KMEdit::slotExternalEditorDone( KProcess * proc ) {
+ assert(proc == mExtEditorProcess);
+ // make sure, we update even when KDirWatcher is too slow:
+ slotExternalEditorTempFileChanged( mExtEditorTempFile->name() );
+ killExternalEditor();
+}
+
+void KMEdit::killExternalEditor() {
+ delete mExtEditorTempFileWatcher; mExtEditorTempFileWatcher = 0;
+ delete mExtEditorTempFile; mExtEditorTempFile = 0;
+ delete mExtEditorProcess; mExtEditorProcess = 0;
+}
+
+
+bool KMEdit::checkExternalEditorFinished() {
+ if ( !mExtEditorProcess )
+ return true;
+ switch ( KMessageBox::warningYesNoCancel( topLevelWidget(),
+ i18n("The external editor is still running.\n"
+ "Abort the external editor or leave it open?"),
+ i18n("External Editor"),
+ i18n("Abort Editor"), i18n("Leave Editor Open") ) ) {
+ case KMessageBox::Yes:
+ killExternalEditor();
+ return true;
+ case KMessageBox::No:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void KMEdit::spellcheck()
+{
+ if ( mKSpell )
+ 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,
+// SLOT(slotSpellcheck2(KSpell*)),0,true,false,KSpell::HTML);
+// }
+// else {
+ mKSpell = new KSpell(this, i18n("Spellcheck - KMail"), this,
+ SLOT(slotSpellcheck2(KSpell*)));
+// }
+
+ QStringList l = KSpellingHighlighter::personalWords();
+ for ( QStringList::Iterator it = l.begin(); it != l.end(); ++it ) {
+ mKSpell->addPersonal( *it );
+ }
+ connect (mKSpell, SIGNAL( death()),
+ this, SLOT (slotSpellDone()));
+ connect (mKSpell, SIGNAL (misspelling (const QString &, const QStringList &, unsigned int)),
+ this, SLOT (slotMisspelling (const QString &, const QStringList &, unsigned int)));
+ connect (mKSpell, SIGNAL (corrected (const QString &, const QString &, unsigned int)),
+ this, SLOT (slotCorrected (const QString &, const QString &, unsigned int)));
+ connect (mKSpell, SIGNAL (done(const QString &)),
+ this, SLOT (slotSpellResult (const QString&)));
+}
+
+void KMEdit::cut()
+{
+ KEdit::cut();
+ if ( textFormat() != Qt::RichText && mSpellChecker )
+ mSpellChecker->restartBackgroundSpellCheck();
+}
+
+void KMEdit::clear()
+{
+ KEdit::clear();
+ if ( textFormat() != Qt::RichText && mSpellChecker )
+ mSpellChecker->restartBackgroundSpellCheck();
+}
+
+void KMEdit::del()
+{
+ KEdit::del();
+ if ( textFormat() != Qt::RichText && mSpellChecker )
+ mSpellChecker->restartBackgroundSpellCheck();
+}
+
+void KMEdit::paste()
+{
+ mComposer->paste( mPasteMode );
+}
+
+// KMEdit indirectly inherits from QTextEdit, which has virtual paste() method,
+// but it controls whether it pastes clipboard or selection by an internal
+// flag that is not accessible in any way, so paste() being virtual is actually
+// useless, because reimplementations can't known where to paste from anyway.
+// Roll our own internal flag.
+void KMEdit::contentsMouseReleaseEvent( QMouseEvent * e )
+{
+ if( e->button() != Qt::MidButton )
+ return KEdit::contentsMouseReleaseEvent( e );
+ mPasteMode = QClipboard::Selection;
+ KEdit::contentsMouseReleaseEvent( e );
+ mPasteMode = QClipboard::Clipboard;
+}
+
+void KMEdit::slotMisspelling(const QString &text, const QStringList &lst, unsigned int pos)
+{
+ kdDebug(5006)<<"void KMEdit::slotMisspelling(const QString &text, const QStringList &lst, unsigned int pos) : "<<text <<endl;
+ if( mSpellLineEdit )
+ mComposer->sujectLineWidget()->spellCheckerMisspelling( text, lst, pos);
+ else
+ misspelling(text, lst, pos);
+}
+
+void KMEdit::slotCorrected (const QString &oldWord, const QString &newWord, unsigned int pos)
+{
+ kdDebug(5006)<<"slotCorrected (const QString &oldWord, const QString &newWord, unsigned int pos) : "<<oldWord<<endl;
+ if( mSpellLineEdit )
+ mComposer->sujectLineWidget()->spellCheckerCorrected( oldWord, newWord, pos);
+ else {
+ unsigned int l = 0;
+ unsigned int cnt = 0;
+ bool _bold,_underline,_italic;
+ QColor _color;
+ QFont _font;
+ posToRowCol (pos, l, cnt);
+ setCursorPosition(l, cnt+1); // the new word will get the same markup now as the first character of the word
+ _bold = bold();
+ _underline = underline();
+ _italic = italic();
+ _color = color();
+ _font = currentFont();
+ corrected(oldWord, newWord, pos);
+ setSelection (l, cnt, l, cnt+newWord.length());
+ setBold(_bold);
+ setItalic(_italic);
+ setUnderline(_underline);
+ setColor(_color);
+ setCurrentFont(_font);
+ }
+
+}
+
+void KMEdit::slotSpellcheck2(KSpell*)
+{
+ if( !mSpellLineEdit)
+ {
+ spellcheck_start();
+
+ QString quotePrefix;
+ if(mComposer && mComposer->msg())
+ {
+ int languageNr = GlobalSettings::self()->replyCurrentLanguage();
+ ReplyPhrases replyPhrases( QString::number(languageNr) );
+ replyPhrases.readConfig();
+
+ quotePrefix = mComposer->msg()->formatString(
+ replyPhrases.indentPrefix() );
+ }
+
+ kdDebug(5006) << "spelling: new SpellingFilter with prefix=\"" << quotePrefix << "\"" << endl;
+ QTextEdit plaintext;
+ plaintext.setText(text());
+ plaintext.setTextFormat(Qt::PlainText);
+ mSpellingFilter = new SpellingFilter(plaintext.text(), quotePrefix, SpellingFilter::FilterUrls,
+ SpellingFilter::FilterEmailAddresses);
+
+ mKSpell->check(mSpellingFilter->filteredText());
+ }
+ else if( mComposer )
+ mKSpell->check( mComposer->sujectLineWidget()->text());
+}
+
+void KMEdit::slotSpellResult(const QString &s)
+{
+ if( !mSpellLineEdit)
+ spellcheck_stop();
+
+ int dlgResult = mKSpell->dlgResult();
+ if ( dlgResult == KS_CANCEL )
+ {
+ if( mSpellLineEdit)
+ {
+ //stop spell check
+ mSpellLineEdit = false;
+ QString tmpText( s );
+ tmpText = tmpText.remove('\n');
+
+ if( tmpText != mComposer->sujectLineWidget()->text() )
+ mComposer->sujectLineWidget()->setText( tmpText );
+ }
+ else
+ {
+ setModified(true);
+ }
+ }
+ mKSpell->cleanUp();
+ KDictSpellingHighlighter::dictionaryChanged();
+
+ emit spellcheck_done( dlgResult );
+}
+
+void KMEdit::slotSpellDone()
+{
+ kdDebug(5006)<<" void KMEdit::slotSpellDone()\n";
+ KSpell::spellStatus status = mKSpell->status();
+ delete mKSpell;
+ mKSpell = 0;
+
+ kdDebug(5006) << "spelling: delete SpellingFilter" << endl;
+ delete mSpellingFilter;
+ mSpellingFilter = 0;
+ mComposer->sujectLineWidget()->deselect();
+ if (status == KSpell::Error)
+ {
+ KMessageBox::sorry( topLevelWidget(),
+ i18n("ISpell/Aspell could not be started. Please "
+ "make sure you have ISpell or Aspell properly "
+ "configured and in your PATH.") );
+ emit spellcheck_done( KS_CANCEL );
+ }
+ else if (status == KSpell::Crashed)
+ {
+ spellcheck_stop();
+ KMessageBox::sorry( topLevelWidget(),
+ i18n("ISpell/Aspell seems to have crashed.") );
+ emit spellcheck_done( KS_CANCEL );
+ }
+ else
+ {
+ if( mSpellLineEdit )
+ spellcheck();
+ else if( !mComposer->subjectTextWasSpellChecked() && status == KSpell::FinishedNoMisspellingsEncountered )
+ KMessageBox::information( topLevelWidget(),
+ i18n("No misspellings encountered.") );
+ }
+}
+
+void KMEdit::setCursorPositionFromStart( unsigned int pos ) {
+ unsigned int l = 0;
+ unsigned int c = 0;
+ posToRowCol( pos, l, c );
+ // kdDebug() << "Num lines: " << numLines() << endl;
+ // kdDebug() << "Position " << pos << " converted to " << l << ":" << c << endl;
+ setCursorPosition( l, c );
+ ensureCursorVisible();
+}
+
+#include "kmedit.moc"
diff --git a/kmail/kmedit.h b/kmail/kmedit.h
new file mode 100644
index 00000000..384b36f3
--- /dev/null
+++ b/kmail/kmedit.h
@@ -0,0 +1,131 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ * KMComposeWin Header File
+ * Author: Markus Wuebben <markus.wuebben@kde.org>
+ */
+#ifndef __KMAIL_KMEDIT_H__
+#define __KMAIL_KMEDIT_H__
+
+#include <kdeversion.h>
+#include <keditcl.h>
+#include <qmap.h>
+#include <qstringlist.h>
+#include <qclipboard.h>
+
+class KMComposeWin;
+class KSpellConfig;
+class KSpell;
+class SpellingFilter;
+class KTempFile;
+class KDictSpellingHighlighter;
+class KDirWatch;
+class KProcess;
+class QPopupMenu;
+
+
+class KMEdit : public KEdit {
+ Q_OBJECT
+public:
+ KMEdit(QWidget *parent=0,KMComposeWin* composer=0,
+ KSpellConfig* spellConfig = 0,
+ const char *name=0);
+ ~KMEdit();
+
+ /**
+ * Start the spell checker.
+ */
+ void spellcheck();
+
+ /**
+ * Text with lines breaks inserted after every row
+ */
+ QString brokenText();
+
+ /**
+ * Toggle automatic spellchecking
+ */
+ int autoSpellChecking( bool );
+
+ /**
+ * For the external editor
+ */
+ void setUseExternalEditor( bool use ) { mUseExtEditor = use; }
+ void setExternalEditorPath( const QString & path ) { mExtEditor = path; }
+
+ /**
+ * Check that the external editor has finished and output a warning
+ * if it hasn't.
+ * @return false if the user chose to cancel whatever operation
+ * called this method.
+ */
+ bool checkExternalEditorFinished();
+
+ QPopupMenu* createPopupMenu(const QPoint&);
+ void setSpellCheckingActive(bool spellCheckingActive);
+
+ /** Drag and drop methods */
+ void contentsDragEnterEvent(QDragEnterEvent *e);
+ void contentsDragMoveEvent(QDragMoveEvent *e);
+ void contentsDropEvent(QDropEvent *e);
+
+ void deleteAutoSpellChecking();
+
+ unsigned int lineBreakColumn() const;
+
+ /** set cursor to absolute position pos */
+ void setCursorPositionFromStart(unsigned int pos);
+
+signals:
+ void spellcheck_done(int result);
+ void attachPNGImageData(const QByteArray &image);
+ void pasteImage();
+ void focusUp();
+ void focusChanged( bool );
+ void insertSnippet();
+public slots:
+ void initializeAutoSpellChecking();
+ void slotSpellcheck2(KSpell*);
+ void slotSpellResult(const QString&);
+ void slotSpellDone();
+ void slotExternalEditorDone(KProcess*);
+ void slotMisspelling(const QString &, const QStringList &, unsigned int);
+ void slotCorrected (const QString &, const QString &, unsigned int);
+ void addSuggestion(const QString& text, const QStringList& lst, unsigned int );
+ void cut();
+ void clear();
+ void del();
+ void paste();
+protected:
+ /**
+ * Event filter that does Tab-key handling.
+ */
+ bool eventFilter(QObject*, QEvent*);
+ void keyPressEvent( QKeyEvent* );
+
+ void contentsMouseReleaseEvent( QMouseEvent * e );
+
+private slots:
+ void slotExternalEditorTempFileChanged( const QString & fileName );
+
+private:
+ void killExternalEditor();
+
+private:
+ KMComposeWin* mComposer;
+
+ KSpell *mKSpell;
+ KSpellConfig *mSpellConfig;
+ QMap<QString,QStringList> mReplacements;
+ SpellingFilter* mSpellingFilter;
+ KTempFile *mExtEditorTempFile;
+ KDirWatch *mExtEditorTempFileWatcher;
+ KProcess *mExtEditorProcess;
+ bool mUseExtEditor;
+ QString mExtEditor;
+ bool mWasModifiedBeforeSpellCheck;
+ KDictSpellingHighlighter *mSpellChecker;
+ bool mSpellLineEdit;
+ QClipboard::Mode mPasteMode;
+};
+
+#endif // __KMAIL_KMEDIT_H__
+
diff --git a/kmail/kmfawidgets.cpp b/kmail/kmfawidgets.cpp
new file mode 100644
index 00000000..558fbe0d
--- /dev/null
+++ b/kmail/kmfawidgets.cpp
@@ -0,0 +1,157 @@
+// kmfawidgets.h - KMFilterAction parameter widgets
+// Copyright: (c) 2001 Marc Mutz <mutz@kde.org>
+// License: GNU Genaral Public License
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "kmfawidgets.h"
+
+#include <kabc/addresseedialog.h> // for the button in KMFilterActionWithAddress
+#include <kiconloader.h>
+#include <klocale.h>
+#include <kaudioplayer.h>
+#include <kurlrequester.h>
+#include <kfiledialog.h>
+#include <kstandarddirs.h>
+
+#include <qlayout.h>
+
+//=============================================================================
+//
+// class KMFilterActionWithAddressWidget
+//
+//=============================================================================
+
+KMFilterActionWithAddressWidget::KMFilterActionWithAddressWidget( QWidget* parent, const char* name )
+ : QWidget( parent, name )
+{
+ QHBoxLayout *hbl = new QHBoxLayout(this);
+ hbl->setSpacing(4);
+ mLineEdit = new KLineEdit(this);
+ hbl->addWidget( mLineEdit, 1 /*stretch*/ );
+ mBtn = new QPushButton( QString::null ,this );
+ mBtn->setPixmap( BarIcon( "contents", KIcon::SizeSmall ) );
+ mBtn->setFixedHeight( mLineEdit->sizeHint().height() );
+ hbl->addWidget( mBtn );
+
+ connect( mBtn, SIGNAL(clicked()),
+ this, SLOT(slotAddrBook()) );
+}
+
+void KMFilterActionWithAddressWidget::slotAddrBook()
+{
+ KABC::Addressee::List lst = KABC::AddresseeDialog::getAddressees( this );
+
+ if ( lst.empty() )
+ return;
+
+ QStringList addrList;
+
+ for( KABC::Addressee::List::const_iterator it = lst.begin(); it != lst.end(); ++it )
+ addrList << (*it).fullEmail();
+
+ QString txt = mLineEdit->text().stripWhiteSpace();
+
+ if ( !txt.isEmpty() ) {
+ if ( !txt.endsWith( "," ) )
+ txt += ", ";
+ else
+ txt += ' ';
+ }
+
+ mLineEdit->setText( txt + addrList.join(",") );
+}
+
+KMSoundTestWidget::KMSoundTestWidget(QWidget *parent, const char *name)
+ : QWidget( parent, name)
+{
+ QHBoxLayout *lay1 = new QHBoxLayout( this );
+ m_playButton = new QPushButton( this, "m_playButton" );
+ m_playButton->setPixmap( SmallIcon( "1rightarrow" ) );
+ connect( m_playButton, SIGNAL( clicked() ), SLOT( playSound() ));
+ lay1->addWidget( m_playButton );
+
+ m_urlRequester = new KURLRequester( this );
+ lay1->addWidget( m_urlRequester );
+ connect( m_urlRequester, SIGNAL( openFileDialog( KURLRequester * )),
+ SLOT( openSoundDialog( KURLRequester * )));
+ connect( m_urlRequester->lineEdit(), SIGNAL( textChanged ( const QString & )), SLOT( slotUrlChanged(const QString & )));
+ slotUrlChanged(m_urlRequester->lineEdit()->text() );
+}
+
+KMSoundTestWidget::~KMSoundTestWidget()
+{
+}
+
+void KMSoundTestWidget::slotUrlChanged(const QString &_text )
+{
+ m_playButton->setEnabled( !_text.isEmpty());
+}
+
+void KMSoundTestWidget::openSoundDialog( KURLRequester * )
+{
+ static bool init = true;
+ if ( !init )
+ return;
+
+ init = false;
+
+ KFileDialog *fileDialog = m_urlRequester->fileDialog();
+ fileDialog->setCaption( i18n("Select Sound File") );
+ QStringList filters;
+ filters << "audio/x-wav" << "audio/x-mp3" << "application/x-ogg"
+ << "audio/x-adpcm";
+ fileDialog->setMimeFilter( filters );
+
+ QStringList soundDirs = KGlobal::dirs()->resourceDirs( "sound" );
+
+ if ( !soundDirs.isEmpty() ) {
+ KURL soundURL;
+ QDir dir;
+ dir.setFilter( QDir::Files | QDir::Readable );
+ QStringList::ConstIterator it = soundDirs.begin();
+ while ( it != soundDirs.end() ) {
+ dir = *it;
+ if ( dir.isReadable() && dir.count() > 2 ) {
+ soundURL.setPath( *it );
+ fileDialog->setURL( soundURL );
+ break;
+ }
+ ++it;
+ }
+ }
+
+}
+
+void KMSoundTestWidget::playSound()
+{
+ QString parameter= m_urlRequester->lineEdit()->text();
+ if ( parameter.isEmpty() )
+ return ;
+ QString play = parameter;
+ QString file = QString::fromLatin1("file:");
+ if (parameter.startsWith(file))
+ play = parameter.mid(file.length());
+ KAudioPlayer::play(QFile::encodeName(play));
+}
+
+
+QString KMSoundTestWidget::url() const
+{
+ return m_urlRequester->lineEdit()->text();
+}
+
+void KMSoundTestWidget::setUrl(const QString & url)
+{
+ m_urlRequester->lineEdit()->setText(url);
+}
+
+void KMSoundTestWidget::clear()
+{
+ m_urlRequester->lineEdit()->clear();
+}
+
+//--------------------------------------------
+#include "kmfawidgets.moc"
diff --git a/kmail/kmfawidgets.h b/kmail/kmfawidgets.h
new file mode 100644
index 00000000..9b2a05b9
--- /dev/null
+++ b/kmail/kmfawidgets.h
@@ -0,0 +1,57 @@
+// kmfawidgets.h - KMFilterAction parameter widgets
+// Copyright: (c) 2001 Marc Mutz <Marc@Mutz.com>
+// License: GPL
+
+#ifndef _kmfawidgets_h_
+#define _kmfawidgets_h_
+
+#include <klineedit.h>
+#include <qstring.h>
+
+/** The param widget for KMFilterActionWithAddress..
+ @author Marc Mutz <mutz@kde.org>
+*/
+
+class QPushButton;
+class KURLRequester;
+
+class KMFilterActionWithAddressWidget : public QWidget
+{
+ Q_OBJECT
+public:
+ KMFilterActionWithAddressWidget( QWidget* parent=0, const char* name=0 );
+
+ void clear() { mLineEdit->clear(); }
+ QString text() const { return mLineEdit->text(); }
+ void setText( const QString & aString ) { mLineEdit->setText( aString ); }
+
+protected slots:
+ void slotAddrBook();
+
+private:
+ QPushButton* mBtn;
+ QLineEdit* mLineEdit;
+};
+
+class KMSoundTestWidget : public QWidget
+{
+ Q_OBJECT
+public:
+ KMSoundTestWidget( QWidget * parent, const char * name=0 );
+ ~KMSoundTestWidget();
+ QString url() const;
+ void setUrl( const QString & url );
+ void clear();
+signals:
+ void testPressed();
+protected slots:
+ void playSound();
+ void openSoundDialog( KURLRequester * );
+ void slotUrlChanged( const QString & );
+
+private:
+ KURLRequester *m_urlRequester;
+ QPushButton *m_playButton;
+};
+
+#endif /*_kmfawidget_h_*/
diff --git a/kmail/kmfilter.cpp b/kmail/kmfilter.cpp
new file mode 100644
index 00000000..d4456234
--- /dev/null
+++ b/kmail/kmfilter.cpp
@@ -0,0 +1,430 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ *
+ * 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.
+ *
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "kmfilter.h"
+#include "kmkernel.h"
+#include "accountmanager.h"
+using KMail::AccountManager;
+#include "kmacctimap.h"
+#include "kmfilteraction.h"
+#include "kmglobal.h"
+#include "filterlog.h"
+using KMail::FilterLog;
+
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+#include <kconfig.h>
+
+#include <assert.h>
+
+
+KMFilter::KMFilter( KConfig* aConfig, bool popFilter )
+ : bPopFilter(popFilter)
+{
+ if (!bPopFilter)
+ mActions.setAutoDelete( true );
+
+ if ( aConfig )
+ readConfig( aConfig );
+ else if ( bPopFilter )
+ mAction = Down;
+ else {
+ bApplyOnInbound = true;
+ bApplyOnOutbound = false;
+ bApplyOnExplicit = true;
+ bStopProcessingHere = true;
+ bConfigureShortcut = false;
+ bConfigureToolbar = false;
+ bAutoNaming = true;
+ mApplicability = All;
+ }
+}
+
+
+KMFilter::KMFilter( const KMFilter & aFilter )
+{
+ bPopFilter = aFilter.isPopFilter();
+
+ if ( !bPopFilter )
+ mActions.setAutoDelete( true );
+
+ mPattern = aFilter.mPattern;
+
+ if ( bPopFilter ){
+ mAction = aFilter.mAction;
+ } else {
+ bApplyOnInbound = aFilter.applyOnInbound();
+ bApplyOnOutbound = aFilter.applyOnOutbound();
+ bApplyOnExplicit = aFilter.applyOnExplicit();
+ bStopProcessingHere = aFilter.stopProcessingHere();
+ bConfigureShortcut = aFilter.configureShortcut();
+ bConfigureToolbar = aFilter.configureToolbar();
+ mApplicability = aFilter.applicability();
+ mIcon = aFilter.icon();
+ mShortcut = aFilter.shortcut();
+
+ QPtrListIterator<KMFilterAction> it( aFilter.mActions );
+ for ( it.toFirst() ; it.current() ; ++it ) {
+ KMFilterActionDesc *desc = (*kmkernel->filterActionDict())[ (*it)->name() ];
+ if ( desc ) {
+ KMFilterAction *f = desc->create();
+ if ( f ) {
+ f->argsFromString( (*it)->argsAsString() );
+ mActions.append( f );
+ }
+ }
+ }
+
+ mAccounts.clear();
+ QValueListConstIterator<int> it2;
+ for ( it2 = aFilter.mAccounts.begin() ; it2 != aFilter.mAccounts.end() ; ++it2 )
+ mAccounts.append( *it2 );
+ }
+}
+
+// only for !bPopFilter
+KMFilter::ReturnCode KMFilter::execActions( KMMessage* msg, bool& stopIt ) const
+{
+ ReturnCode status = NoResult;
+
+ QPtrListIterator<KMFilterAction> it( mActions );
+ for ( it.toFirst() ; it.current() ; ++it ) {
+
+ if ( FilterLog::instance()->isLogging() ) {
+ QString logText( i18n( "<b>Applying filter action:</b> %1" )
+ .arg( (*it)->displayString() ) );
+ FilterLog::instance()->add( logText, FilterLog::appliedAction );
+ }
+
+ KMFilterAction::ReturnCode result = (*it)->process( msg );
+
+ switch ( result ) {
+ case KMFilterAction::CriticalError:
+ if ( FilterLog::instance()->isLogging() ) {
+ QString logText = QString( "<font color=#FF0000>%1</font>" )
+ .arg( i18n( "A critical error occurred. Processing stops here." ) );
+ FilterLog::instance()->add( logText, FilterLog::appliedAction );
+ }
+ // in case it's a critical error: return immediately!
+ return CriticalError;
+ case KMFilterAction::ErrorButGoOn:
+ if ( FilterLog::instance()->isLogging() ) {
+ QString logText = QString( "<font color=#FF0000>%1</font>" )
+ .arg( i18n( "A problem was found while applying this action." ) );
+ FilterLog::instance()->add( logText, FilterLog::appliedAction );
+ }
+ default:
+ break;
+ }
+ }
+
+ if ( status == NoResult ) // No filters matched, keep copy of message
+ status = GoOn;
+
+ stopIt = stopProcessingHere();
+
+ return status;
+}
+
+bool KMFilter::requiresBody( KMMsgBase* msg )
+{
+ if (pattern() && pattern()->requiresBody())
+ return true; // no pattern means always matches?
+ QPtrListIterator<KMFilterAction> it( *actions() );
+ for ( it.toFirst() ; it.current() ; ++it )
+ if ((*it)->requiresBody( msg ))
+ return true;
+ return false;
+}
+
+/** No descriptions */
+// only for bPopFilter
+void KMFilter::setAction(const KMPopFilterAction aAction)
+{
+ mAction = aAction;
+}
+
+// only for bPopFilter
+KMPopFilterAction KMFilter::action()
+{
+ return mAction;
+}
+
+// only for !bPopFilter
+bool KMFilter::folderRemoved( KMFolder* aFolder, KMFolder* aNewFolder )
+{
+ bool rem = false;
+
+ QPtrListIterator<KMFilterAction> it( mActions );
+ for ( it.toFirst() ; it.current() ; ++it )
+ if ( (*it)->folderRemoved( aFolder, aNewFolder ) )
+ rem = true;
+
+ return rem;
+}
+
+void KMFilter::setApplyOnAccount( uint id, bool aApply )
+{
+ if (aApply && !mAccounts.contains( id )) {
+ mAccounts.append( id );
+ } else if (!aApply && mAccounts.contains( id )) {
+ mAccounts.remove( id );
+ }
+}
+
+bool KMFilter::applyOnAccount( uint id ) const
+{
+ if ( applicability() == All )
+ return true;
+ if ( applicability() == ButImap ) {
+ KMAccount *account = kmkernel->acctMgr()->find( id );
+ bool result = account && !dynamic_cast<KMAcctImap*>(account);
+ return result;
+ }
+ if ( applicability() == Checked )
+ return mAccounts.contains( id );
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFilter::readConfig(KConfig* config)
+{
+ // MKSearchPattern::readConfig ensures
+ // that the pattern is purified.
+ mPattern.readConfig(config);
+
+ if (bPopFilter) {
+ // get the action description...
+ QString action = config->readEntry( "action" );
+ if ( action == "down" )
+ mAction = Down;
+ else if ( action == "later" )
+ mAction = Later;
+ else if ( action == "delete" )
+ mAction = Delete;
+ else
+ mAction = NoAction;
+ }
+ else {
+ QStringList sets = config->readListEntry("apply-on");
+ if ( sets.isEmpty() && !config->hasKey("apply-on") ) {
+ bApplyOnOutbound = false;
+ bApplyOnInbound = true;
+ bApplyOnExplicit = true;
+ mApplicability = ButImap;
+ } else {
+ bApplyOnInbound = bool(sets.contains("check-mail"));
+ bApplyOnOutbound = bool(sets.contains("send-mail"));
+ bApplyOnExplicit = bool(sets.contains("manual-filtering"));
+ mApplicability = (AccountType)config->readNumEntry( "Applicability", ButImap );
+ }
+
+ bStopProcessingHere = config->readBoolEntry("StopProcessingHere", true);
+ bConfigureShortcut = config->readBoolEntry("ConfigureShortcut", false);
+ QString shortcut( config->readEntry( "Shortcut" ) );
+ if ( !shortcut.isEmpty() ) {
+ KShortcut sc( shortcut );
+ setShortcut( sc );
+ }
+ bConfigureToolbar = config->readBoolEntry("ConfigureToolbar", false);
+ bConfigureToolbar = bConfigureToolbar && bConfigureShortcut;
+ mIcon = config->readEntry( "Icon", "gear" );
+ bAutoNaming = config->readBoolEntry("AutomaticName", false);
+
+ int i, numActions;
+ QString actName, argsName;
+
+ mActions.clear();
+
+ numActions = config->readNumEntry("actions",0);
+ if (numActions > FILTER_MAX_ACTIONS) {
+ numActions = FILTER_MAX_ACTIONS ;
+ KMessageBox::information( 0, i18n("<qt>Too many filter actions in filter rule <b>%1</b>.</qt>").arg( mPattern.name() ) );
+ }
+
+ for ( i=0 ; i < numActions ; i++ ) {
+ actName.sprintf("action-name-%d", i);
+ argsName.sprintf("action-args-%d", i);
+ // get the action description...
+ KMFilterActionDesc *desc = (*kmkernel->filterActionDict())[ config->readEntry( actName ) ];
+ if ( desc ) {
+ //...create an instance...
+ KMFilterAction *fa = desc->create();
+ if ( fa ) {
+ //...load it with it's parameter...
+ fa->argsFromString( config->readEntry( argsName ) );
+ //...check if it's emoty and...
+ if ( !fa->isEmpty() )
+ //...append it if it's not and...
+ mActions.append( fa );
+ else
+ //...delete is else.
+ delete fa;
+ }
+ } else
+ KMessageBox::information( 0 /* app-global modal dialog box */,
+ i18n("<qt>Unknown filter action <b>%1</b><br>in filter rule <b>%2</b>.<br>Ignoring it.</qt>")
+ .arg( config->readEntry( actName ) ).arg( mPattern.name() ) );
+ }
+
+ mAccounts = config->readIntListEntry( "accounts-set" );
+ }
+}
+
+
+void KMFilter::writeConfig(KConfig* config) const
+{
+ mPattern.writeConfig(config);
+
+ if (bPopFilter) {
+ switch ( mAction ) {
+ case Down:
+ config->writeEntry( "action", "down" );
+ break;
+ case Later:
+ config->writeEntry( "action", "later" );
+ break;
+ case Delete:
+ config->writeEntry( "action", "delete" );
+ break;
+ default:
+ config->writeEntry( "action", "" );
+ }
+ } else {
+ QStringList sets;
+ if ( bApplyOnInbound )
+ sets.append( "check-mail" );
+ if ( bApplyOnOutbound )
+ sets.append( "send-mail" );
+ if ( bApplyOnExplicit )
+ sets.append( "manual-filtering" );
+ config->writeEntry( "apply-on", sets );
+
+ config->writeEntry( "StopProcessingHere", bStopProcessingHere );
+ config->writeEntry( "ConfigureShortcut", bConfigureShortcut );
+ if ( !mShortcut.isNull() )
+ config->writeEntry( "Shortcut", mShortcut.toString() );
+ config->writeEntry( "ConfigureToolbar", bConfigureToolbar );
+ config->writeEntry( "Icon", mIcon );
+ config->writeEntry( "AutomaticName", bAutoNaming );
+ config->writeEntry( "Applicability", mApplicability );
+
+ QString key;
+ int i;
+
+ QPtrListIterator<KMFilterAction> it( mActions );
+ for ( i=0, it.toFirst() ; it.current() ; ++it, ++i ) {
+ config->writeEntry( key.sprintf("action-name-%d", i),
+ (*it)->name() );
+ config->writeEntry( key.sprintf("action-args-%d", i),
+ (*it)->argsAsString() );
+ }
+ config->writeEntry( "actions", i );
+ config->writeEntry( "accounts-set", mAccounts );
+ }
+}
+
+void KMFilter::purify()
+{
+ mPattern.purify();
+
+ if (!bPopFilter) {
+ QPtrListIterator<KMFilterAction> it( mActions );
+ it.toLast();
+ while ( it.current() )
+ if ( (*it)->isEmpty() )
+ mActions.remove ( (*it) );
+ else
+ --it;
+
+ // Remove invalid accounts from mAccounts - just to be tidy
+ QValueListIterator<int> it2 = mAccounts.begin();
+ while ( it2 != mAccounts.end() ) {
+ if ( !kmkernel->acctMgr()->find( *it2 ) )
+ it2 = mAccounts.remove( it2 );
+ else
+ ++it2;
+ }
+ }
+}
+
+bool KMFilter::isEmpty() const
+{
+ if (bPopFilter)
+ return mPattern.isEmpty();
+ else
+ return mPattern.isEmpty() && mActions.isEmpty() && mAccounts.isEmpty();
+}
+
+#ifndef NDEBUG
+const QString KMFilter::asString() const
+{
+ QString result;
+
+ result += mPattern.asString();
+
+ if (bPopFilter){
+ result += " action: ";
+ result += mAction;
+ result += "\n";
+ }
+ else {
+ QPtrListIterator<KMFilterAction> it( mActions );
+ for ( it.toFirst() ; it.current() ; ++it ) {
+ result += " action: ";
+ result += (*it)->label();
+ result += " ";
+ result += (*it)->argsAsString();
+ result += "\n";
+ }
+ result += "This filter belongs to the following sets:";
+ if ( bApplyOnInbound )
+ result += " Inbound";
+ if ( bApplyOnOutbound )
+ result += " Outbound";
+ if ( bApplyOnExplicit )
+ result += " Explicit";
+ result += "\n";
+ if ( bApplyOnInbound && mApplicability == All ) {
+ result += "This filter applies to all accounts.\n";
+ } else if ( bApplyOnInbound && mApplicability == ButImap ) {
+ result += "This filter applies to all but online IMAP accounts.\n";
+ } else if ( bApplyOnInbound ) {
+ QValueListConstIterator<int> it2;
+ result += "This filter applies to the following accounts:";
+ if ( mAccounts.isEmpty() )
+ result += " None";
+ else for ( it2 = mAccounts.begin() ; it2 != mAccounts.end() ; ++it2 )
+ if ( kmkernel->acctMgr()->find( *it2 ) )
+ result += " " + kmkernel->acctMgr()->find( *it2 )->name();
+ result += "\n";
+ }
+ if ( bStopProcessingHere )
+ result += "If it matches, processing stops at this filter.\n";
+ }
+ return result;
+}
+#endif
diff --git a/kmail/kmfilter.h b/kmail/kmfilter.h
new file mode 100644
index 00000000..767343ec
--- /dev/null
+++ b/kmail/kmfilter.h
@@ -0,0 +1,312 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ *
+ * 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 kmfilter_h
+#define kmfilter_h
+
+#include "kmsearchpattern.h"
+#include "kmpopheaders.h"
+
+#include <kshortcut.h>
+
+#include <qptrlist.h>
+
+class QString;
+class KConfig;
+class KMMessage;
+class KMFilterAction;
+class KMFolder;
+
+// maximum number of filter actions per filter
+const int FILTER_MAX_ACTIONS = 8;
+
+
+class KMFilter
+{
+public:
+ /** Result codes returned by process. They mean:
+
+ @param GoOn Everything OK. You are still the owner of the
+ message and you should continue applying filter actions to this
+ message.
+
+ @param CriticalError A critical error occurred (e.g. "disk full").
+
+ @param NoResult For internal use only!
+
+ */
+ enum ReturnCode { NoResult, GoOn, CriticalError };
+
+ /** Account type codes used by setApplicability. They mean:
+
+ @param All Apply to all accounts
+
+ @param ButImap Apply to all but online-IMAP accounts
+
+ @param Checked apply to all accounts specified by setApplyOnAccount
+
+ */
+ enum AccountType { All, ButImap, Checked };
+
+ /** Constructor that initializes from given config file, if given.
+ * Filters are stored one by one in config groups, i.e. one filter, one group.
+ * The config group has to be preset if config is not 0. */
+ KMFilter( KConfig* aConfig=0 , bool popFilter = false);
+
+ /** Copy constructor. Constructs a deep copy of @p aFilter. */
+ KMFilter( const KMFilter & other );
+
+ /** Cleanup. */
+ ~KMFilter() {}
+
+ /** Equivalent to @pattern()->name(). @return name of the filter */
+ QString name() const {
+ return mPattern.name();
+ }
+
+ /** Execute the filter action(s) on the given message.
+ Returns:
+ @li 2 if a critical error occurred,
+ @li 1 if the caller is still
+ the owner of the message,
+ @li 0 if processed successfully.
+ @param msg The message to which the actions should be applied.
+ @param stopIt Contains
+ true if the caller may apply other filters and false if he shall
+ stop the filtering of this message.
+ */
+ ReturnCode execActions( KMMessage* msg, bool& stopIt ) const ;
+
+ /** Determines if the filter depends on the body of the message
+ */
+ bool requiresBody(KMMsgBase* msgBase);
+
+ /** No descriptions */
+ KMPopFilterAction action();
+
+ /** No descriptions */
+ void setAction(const KMPopFilterAction aAction);
+
+ /** Write contents to given config file. The config group (see the
+ constructor above) has to be preset. The config object will be
+ deleted by higher levels, so it is not allowed to store a
+ pointer to it anywhere inside this function. */
+ void writeConfig( KConfig* config ) const;
+
+ /** Initialize from given config file. The config group (see
+ constructor above) has to be preset. The config object will be
+ deleted by higher levels, so it is not allowed to store a
+ pointer to it anywhere inside this function. */
+ void readConfig( KConfig* config );
+
+ /** Remove empty rules (and actions one day). */
+ void purify();
+
+ /** Check for empty pattern and action list. */
+ bool isEmpty() const;
+
+ /** Provides a reference to the internal action list. If your used
+ the @p setAction() and @p action() functions before, please
+ convert to using myFilter->actions()->at() and friends now. */
+ QPtrList<KMFilterAction>* actions() { return &mActions; }
+
+ /** Provides a reference to the internal action list. Const version. */
+ const QPtrList<KMFilterAction>* actions() const { return &mActions; }
+
+ /** Provides a reference to the internal pattern. If you used the
+ @p matches() function before, please convert to using
+ myFilter->pattern()->matches() now. */
+ KMSearchPattern* pattern() { return &mPattern; }
+
+ /** Provides a reference to the internal pattern. If you used the
+ @p matches() function before, please convert to using
+ myFilter->pattern()->matches() now. */
+ const KMSearchPattern* pattern() const { return &mPattern; }
+
+ /** Set whether this filter should be applied on
+ outbound messages (@p aApply == true) or not.
+ See applyOnOutbound applyOnInbound setApplyOnInbound
+ */
+ void setApplyOnOutbound( bool aApply=true ) { bApplyOnOutbound = aApply; }
+
+ /** @return true if this filter should be applied on
+ outbound messages, false otherwise.
+ @see setApplyOnOutbound applyOnInbound setApplyOnInbound
+ */
+ bool applyOnOutbound() const { return bApplyOnOutbound; }
+
+ /** Set whether this filter should be applied on
+ inbound messages (@p aApply == true) or not.
+ @see setApplyOnOutbound applyOnInbound applyOnOutbound
+ */
+ void setApplyOnInbound( bool aApply=true ) { bApplyOnInbound = aApply; }
+
+ /** @return true if this filter should be applied on
+ inbound messages, false otherwise.
+ @see setApplyOnOutbound applyOnOutbound setApplyOnInbound
+ */
+ bool applyOnInbound() const { return bApplyOnInbound; }
+
+ /** Set whether this filter should be applied on
+ explicit (CTRL-J) filtering (@p aApply == true) or not.
+ @see setApplyOnOutbound applyOnInbound applyOnOutbound
+ */
+ void setApplyOnExplicit( bool aApply=true ) { bApplyOnExplicit = aApply; }
+
+ /** @return true if this filter should be applied on
+ explicit (CTRL-J) filtering, false otherwise.
+ @see setApplyOnOutbound applyOnOutbound setApplyOnInbound
+ */
+ bool applyOnExplicit() const { return bApplyOnExplicit; }
+
+ /** Set whether this filter should be applied on
+ inbound messages for all accounts (@p aApply == All) or
+ inbound messages for all but nline IMAP accounts (@p aApply == ButImap) or
+ for a specified set of accounts only.
+ Only applicable to filters that are applied on inbound messages.
+ @see setApplyOnInbound setApplyOnAccount
+ */
+ void setApplicability( AccountType aApply=All ) { mApplicability = aApply; }
+
+ /** @return true if this filter should be applied on
+ inbound messages for all accounts, or false if this filter
+ is to be applied on a specified set of accounts only.
+ Only applicable to filters that are applied on inbound messages.
+ @see setApplicability
+ */
+ AccountType applicability() const { return mApplicability; }
+
+ /** Set whether this filter should be applied on
+ inbound messages for the account with id (@p id).
+ Only applicable to filters that are only applied to a specified
+ set of accounts.
+ @see setApplicability applyOnAccount
+ */
+ void setApplyOnAccount( uint id, bool aApply=true );
+
+ /** @return true if this filter should be applied on
+ inbound messages from the account with id (@p id), false otherwise.
+ @see setApplicability
+ */
+ bool applyOnAccount( uint id ) const;
+
+ void setStopProcessingHere( bool aStop ) { bStopProcessingHere = aStop; }
+ bool stopProcessingHere() const { return bStopProcessingHere; }
+
+ /** Set whether this filter should be plugged into the filter menu.
+ */
+ void setConfigureShortcut( bool aShort ) {
+ bConfigureShortcut = aShort;
+ bConfigureToolbar = bConfigureToolbar && bConfigureShortcut;
+ }
+
+ /** @return true if this filter should be plugged into the filter menu,
+ false otherwise.
+ @see setConfigureShortcut
+ */
+ bool configureShortcut() const { return bConfigureShortcut; }
+
+ /** Set whether this filter should be plugged into the toolbar.
+ This can be done only if a shortcut is defined.
+ @see setConfigureShortcut
+ */
+ void setConfigureToolbar( bool aTool ) {
+ bConfigureToolbar = aTool && bConfigureShortcut;
+ }
+
+ /** @return true if this filter should be plugged into the toolbar,
+ false otherwise.
+ @see setConfigureToolbar
+ */
+ bool configureToolbar() const { return bConfigureToolbar; }
+
+ /** Set the shortcut to be used if plugged into the filter menu
+ or toolbar. Default is no shortcut.
+ @see setConfigureShortcut setConfigureToolbar
+ */
+ void setShortcut( const KShortcut & shortcut ) { mShortcut = shortcut; };
+
+ /** @return The shortcut assigned to the filter.
+ @see setShortcut
+ */
+ const KShortcut & shortcut() const { return mShortcut; }
+
+ /** Set the icon to be used if plugged into the filter menu
+ or toolbar. Default is the gear icon.
+ @see setConfigureShortcut setConfigureToolbar
+ */
+ void setIcon( QString icon ) { mIcon = icon; }
+
+ /** @return The name of the icon to be used.
+ @see setIcon
+ */
+ QString icon() const { return mIcon; }
+
+ /**
+ * Called from the filter manager when a folder is moved.
+ * Tests if the folder aFolder is used in any action. Changes it
+ * to aNewFolder folder in this case.
+ * @return true if a change in some action occurred,
+ * false if no action was affected.
+ */
+ bool folderRemoved( KMFolder* aFolder, KMFolder* aNewFolder );
+
+ /** Returns the filter in a human-readable form. useful for
+ debugging but not much else. Don't use, as it may well go away
+ in the future... */
+#ifndef NDEBUG
+ const QString asString() const;
+#endif
+
+ /** No descriptions */
+ bool isPopFilter() const {
+ return bPopFilter;
+ }
+
+ /** Set the mode for using automatic naming for the filter.
+ If the feature is enabled, the name is derived from the
+ first filter rule.
+ */
+ void setAutoNaming( bool useAutomaticNames ) {
+ bAutoNaming = useAutomaticNames;
+ }
+
+ /** @return Tells, if an automatic name is used for the filter
+ */
+ bool isAutoNaming() const { return bAutoNaming; }
+
+private:
+ KMSearchPattern mPattern;
+ QPtrList<KMFilterAction> mActions;
+ QValueList<int> mAccounts;
+ KMPopFilterAction mAction;
+ QString mIcon;
+ KShortcut mShortcut;
+ bool bPopFilter : 1;
+ bool bApplyOnInbound : 1;
+ bool bApplyOnOutbound : 1;
+ bool bApplyOnExplicit : 1;
+ bool bStopProcessingHere : 1;
+ bool bConfigureShortcut : 1;
+ bool bConfigureToolbar : 1;
+ bool bAutoNaming : 1;
+ AccountType mApplicability;
+};
+
+#endif /*kmfilter_h*/
diff --git a/kmail/kmfilteraction.cpp b/kmail/kmfilteraction.cpp
new file mode 100644
index 00000000..ea77e14b
--- /dev/null
+++ b/kmail/kmfilteraction.cpp
@@ -0,0 +1,1930 @@
+// kmfilteraction.cpp
+// The process methods really should use an enum instead of an int
+// -1 -> status unchanged, 0 -> success, 1 -> failure, 2-> critical failure
+// (GoOn), (Ok), (ErrorButGoOn), (CriticalError)
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "kmfilteraction.h"
+
+#include "kmcommands.h"
+#include "kmmsgpart.h"
+#include "kmfiltermgr.h"
+#include "kmfolderindex.h"
+#include "kmfoldermgr.h"
+#include "messagesender.h"
+#include "kmmainwidget.h"
+#include <libkpimidentities/identity.h>
+#include <libkpimidentities/identitymanager.h>
+#include <libkpimidentities/identitycombo.h>
+#include <libkdepim/kfileio.h>
+#include <libkdepim/collectingprocess.h>
+using KPIM::CollectingProcess;
+#include <mimelib/message.h>
+#include "kmfawidgets.h"
+#include "folderrequester.h"
+using KMail::FolderRequester;
+#include "kmmsgbase.h"
+#include "templateparser.h"
+#include "messageproperty.h"
+#include "actionscheduler.h"
+using KMail::MessageProperty;
+using KMail::ActionScheduler;
+#include "regexplineedit.h"
+using KMail::RegExpLineEdit;
+#include <kregexp3.h>
+#include <ktempfile.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kprocess.h>
+#include <kaudioplayer.h>
+#include <kurlrequester.h>
+
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qtextcodec.h>
+#include <qtimer.h>
+#include <qobject.h>
+#include <qstylesheet.h>
+#include <assert.h>
+
+
+//=============================================================================
+//
+// KMFilterAction
+//
+//=============================================================================
+
+KMFilterAction::KMFilterAction( const char* aName, const QString aLabel )
+{
+ mName = aName;
+ mLabel = aLabel;
+}
+
+KMFilterAction::~KMFilterAction()
+{
+}
+
+void KMFilterAction::processAsync(KMMessage* msg) const
+{
+ ActionScheduler *handler = MessageProperty::filterHandler( msg );
+ ReturnCode result = process( msg );
+ if (handler)
+ handler->actionMessage( result );
+}
+
+bool KMFilterAction::requiresBody(KMMsgBase*) const
+{
+ return true;
+}
+
+KMFilterAction* KMFilterAction::newAction()
+{
+ return 0;
+}
+
+QWidget* KMFilterAction::createParamWidget(QWidget* parent) const
+{
+ return new QWidget(parent);
+}
+
+void KMFilterAction::applyParamWidgetValue(QWidget*)
+{
+}
+
+void KMFilterAction::setParamWidgetValue( QWidget * ) const
+{
+}
+
+void KMFilterAction::clearParamWidget( QWidget * ) const
+{
+}
+
+bool KMFilterAction::folderRemoved(KMFolder*, KMFolder*)
+{
+ return false;
+}
+
+int KMFilterAction::tempOpenFolder(KMFolder* aFolder)
+{
+ return kmkernel->filterMgr()->tempOpenFolder(aFolder);
+}
+
+void KMFilterAction::sendMDN( KMMessage * msg, KMime::MDN::DispositionType d,
+ const QValueList<KMime::MDN::DispositionModifier> & m ) {
+ if ( !msg ) return;
+
+ /* createMDN requires Return-Path and Disposition-Notification-To
+ * if it is not set in the message we assume that the notification should go to the
+ * sender
+ */
+ const QString returnPath = msg->headerField( "Return-Path" );
+ const QString dispNoteTo = msg->headerField( "Disposition-Notification-To" );
+ if ( returnPath.isEmpty() )
+ msg->setHeaderField( "Return-Path", msg->from() );
+ if ( dispNoteTo.isEmpty() )
+ msg->setHeaderField( "Disposition-Notification-To", msg->from() );
+
+ KMMessage * mdn = msg->createMDN( KMime::MDN::AutomaticAction, d, false, m );
+ if ( mdn && !kmkernel->msgSender()->send( mdn, KMail::MessageSender::SendLater ) ) {
+ kdDebug(5006) << "KMFilterAction::sendMDN(): sending failed." << endl;
+ //delete mdn;
+ }
+
+ //restore orignial header
+ if ( returnPath.isEmpty() )
+ msg->removeHeaderField( "Return-Path" );
+ if ( dispNoteTo.isEmpty() )
+ msg->removeHeaderField( "Disposition-Notification-To" );
+}
+
+
+//=============================================================================
+//
+// KMFilterActionWithNone
+//
+//=============================================================================
+
+KMFilterActionWithNone::KMFilterActionWithNone( const char* aName, const QString aLabel )
+ : KMFilterAction( aName, aLabel )
+{
+}
+
+const QString KMFilterActionWithNone::displayString() const
+{
+ return label();
+}
+
+
+//=============================================================================
+//
+// KMFilterActionWithUOID
+//
+//=============================================================================
+
+KMFilterActionWithUOID::KMFilterActionWithUOID( const char* aName, const QString aLabel )
+ : KMFilterAction( aName, aLabel ), mParameter( 0 )
+{
+}
+
+void KMFilterActionWithUOID::argsFromString( const QString argsStr )
+{
+ mParameter = argsStr.stripWhiteSpace().toUInt();
+}
+
+const QString KMFilterActionWithUOID::argsAsString() const
+{
+ return QString::number( mParameter );
+}
+
+const QString KMFilterActionWithUOID::displayString() const
+{
+ // FIXME after string freeze:
+ // return i18n("").arg( );
+ return label() + " \"" + QStyleSheet::escape( argsAsString() ) + "\"";
+}
+
+
+//=============================================================================
+//
+// KMFilterActionWithString
+//
+//=============================================================================
+
+KMFilterActionWithString::KMFilterActionWithString( const char* aName, const QString aLabel )
+ : KMFilterAction( aName, aLabel )
+{
+}
+
+QWidget* KMFilterActionWithString::createParamWidget( QWidget* parent ) const
+{
+ QLineEdit *le = new KLineEdit(parent);
+ le->setText( mParameter );
+ return le;
+}
+
+void KMFilterActionWithString::applyParamWidgetValue( QWidget* paramWidget )
+{
+ mParameter = ((QLineEdit*)paramWidget)->text();
+}
+
+void KMFilterActionWithString::setParamWidgetValue( QWidget* paramWidget ) const
+{
+ ((QLineEdit*)paramWidget)->setText( mParameter );
+}
+
+void KMFilterActionWithString::clearParamWidget( QWidget* paramWidget ) const
+{
+ ((QLineEdit*)paramWidget)->clear();
+}
+
+void KMFilterActionWithString::argsFromString( const QString argsStr )
+{
+ mParameter = argsStr;
+}
+
+const QString KMFilterActionWithString::argsAsString() const
+{
+ return mParameter;
+}
+
+const QString KMFilterActionWithString::displayString() const
+{
+ // FIXME after string freeze:
+ // return i18n("").arg( );
+ return label() + " \"" + QStyleSheet::escape( argsAsString() ) + "\"";
+}
+
+//=============================================================================
+//
+// class KMFilterActionWithStringList
+//
+//=============================================================================
+
+KMFilterActionWithStringList::KMFilterActionWithStringList( const char* aName, const QString aLabel )
+ : KMFilterActionWithString( aName, aLabel )
+{
+}
+
+QWidget* KMFilterActionWithStringList::createParamWidget( QWidget* parent ) const
+{
+ QComboBox *cb = new QComboBox( false, parent );
+ cb->insertStringList( mParameterList );
+ setParamWidgetValue( cb );
+ return cb;
+}
+
+void KMFilterActionWithStringList::applyParamWidgetValue( QWidget* paramWidget )
+{
+ mParameter = ((QComboBox*)paramWidget)->currentText();
+}
+
+void KMFilterActionWithStringList::setParamWidgetValue( QWidget* paramWidget ) const
+{
+ int idx = mParameterList.findIndex( mParameter );
+ ((QComboBox*)paramWidget)->setCurrentItem( idx >= 0 ? idx : 0 );
+}
+
+void KMFilterActionWithStringList::clearParamWidget( QWidget* paramWidget ) const
+{
+ ((QComboBox*)paramWidget)->setCurrentItem(0);
+}
+
+void KMFilterActionWithStringList::argsFromString( const QString argsStr )
+{
+ int idx = mParameterList.findIndex( argsStr );
+ if ( idx < 0 ) {
+ mParameterList.append( argsStr );
+ idx = mParameterList.count() - 1;
+ }
+ mParameter = *mParameterList.at( idx );
+}
+
+
+//=============================================================================
+//
+// class KMFilterActionWithFolder
+//
+//=============================================================================
+
+KMFilterActionWithFolder::KMFilterActionWithFolder( const char* aName, const QString aLabel )
+ : KMFilterAction( aName, aLabel )
+{
+ mFolder = 0;
+}
+
+QWidget* KMFilterActionWithFolder::createParamWidget( QWidget* parent ) const
+{
+ FolderRequester *req = new FolderRequester( parent,
+ kmkernel->getKMMainWidget()->folderTree() );
+ setParamWidgetValue( req );
+ return req;
+}
+
+void KMFilterActionWithFolder::applyParamWidgetValue( QWidget* paramWidget )
+{
+ mFolder = ((FolderRequester *)paramWidget)->folder();
+ mFolderName = ((FolderRequester *)paramWidget)->folderId();
+}
+
+void KMFilterActionWithFolder::setParamWidgetValue( QWidget* paramWidget ) const
+{
+ if ( mFolder )
+ ((FolderRequester *)paramWidget)->setFolder( mFolder );
+ else
+ ((FolderRequester *)paramWidget)->setFolder( mFolderName );
+}
+
+void KMFilterActionWithFolder::clearParamWidget( QWidget* paramWidget ) const
+{
+ ((FolderRequester *)paramWidget)->setFolder( kmkernel->draftsFolder() );
+}
+
+void KMFilterActionWithFolder::argsFromString( const QString argsStr )
+{
+ mFolder = kmkernel->folderMgr()->findIdString( argsStr );
+ if (!mFolder)
+ mFolder = kmkernel->dimapFolderMgr()->findIdString( argsStr );
+ if (!mFolder)
+ mFolder = kmkernel->imapFolderMgr()->findIdString( argsStr );
+ if (mFolder)
+ mFolderName = mFolder->idString();
+ else
+ mFolderName = argsStr;
+}
+
+const QString KMFilterActionWithFolder::argsAsString() const
+{
+ QString result;
+ if ( mFolder )
+ result = mFolder->idString();
+ else
+ result = mFolderName;
+ return result;
+}
+
+const QString KMFilterActionWithFolder::displayString() const
+{
+ QString result;
+ if ( mFolder )
+ result = mFolder->prettyURL();
+ else
+ result = mFolderName;
+ return label() + " \"" + QStyleSheet::escape( result ) + "\"";
+}
+
+bool KMFilterActionWithFolder::folderRemoved( KMFolder* aFolder, KMFolder* aNewFolder )
+{
+ if ( aFolder == mFolder ) {
+ mFolder = aNewFolder;
+ if ( aNewFolder )
+ mFolderName = mFolder->idString();
+ return true;
+ } else
+ return false;
+}
+
+//=============================================================================
+//
+// class KMFilterActionWithAddress
+//
+//=============================================================================
+
+KMFilterActionWithAddress::KMFilterActionWithAddress( const char* aName, const QString aLabel )
+ : KMFilterActionWithString( aName, aLabel )
+{
+}
+
+QWidget* KMFilterActionWithAddress::createParamWidget( QWidget* parent ) const
+{
+ KMFilterActionWithAddressWidget *w = new KMFilterActionWithAddressWidget(parent);
+ w->setText( mParameter );
+ return w;
+}
+
+void KMFilterActionWithAddress::applyParamWidgetValue( QWidget* paramWidget )
+{
+ mParameter = ((KMFilterActionWithAddressWidget*)paramWidget)->text();
+}
+
+void KMFilterActionWithAddress::setParamWidgetValue( QWidget* paramWidget ) const
+{
+ ((KMFilterActionWithAddressWidget*)paramWidget)->setText( mParameter );
+}
+
+void KMFilterActionWithAddress::clearParamWidget( QWidget* paramWidget ) const
+{
+ ((KMFilterActionWithAddressWidget*)paramWidget)->clear();
+}
+
+//=============================================================================
+//
+// class KMFilterActionWithCommand
+//
+//=============================================================================
+
+KMFilterActionWithCommand::KMFilterActionWithCommand( const char* aName, const QString aLabel )
+ : KMFilterActionWithUrl( aName, aLabel )
+{
+}
+
+QWidget* KMFilterActionWithCommand::createParamWidget( QWidget* parent ) const
+{
+ return KMFilterActionWithUrl::createParamWidget( parent );
+}
+
+void KMFilterActionWithCommand::applyParamWidgetValue( QWidget* paramWidget )
+{
+ KMFilterActionWithUrl::applyParamWidgetValue( paramWidget );
+}
+
+void KMFilterActionWithCommand::setParamWidgetValue( QWidget* paramWidget ) const
+{
+ KMFilterActionWithUrl::setParamWidgetValue( paramWidget );
+}
+
+void KMFilterActionWithCommand::clearParamWidget( QWidget* paramWidget ) const
+{
+ KMFilterActionWithUrl::clearParamWidget( paramWidget );
+}
+
+QString KMFilterActionWithCommand::substituteCommandLineArgsFor( KMMessage *aMsg, QPtrList<KTempFile> & aTempFileList ) const
+{
+ QString result = mParameter;
+ QValueList<int> argList;
+ QRegExp r( "%[0-9-]+" );
+
+ // search for '%n'
+ int start = -1;
+ while ( ( start = r.search( result, start + 1 ) ) > 0 ) {
+ int len = r.matchedLength();
+ // and save the encountered 'n' in a list.
+ bool OK = false;
+ int n = result.mid( start + 1, len - 1 ).toInt( &OK );
+ if ( OK )
+ argList.append( n );
+ }
+
+ // sort the list of n's
+ qHeapSort( argList );
+
+ // and use QString::arg to substitute filenames for the %n's.
+ int lastSeen = -2;
+ QString tempFileName;
+ for ( QValueList<int>::Iterator it = argList.begin() ; it != argList.end() ; ++it ) {
+ // setup temp files with check for duplicate %n's
+ if ( (*it) != lastSeen ) {
+ KTempFile *tf = new KTempFile();
+ if ( tf->status() != 0 ) {
+ tf->close();
+ delete tf;
+ kdDebug(5006) << "KMFilterActionWithCommand: Could not create temp file!" << endl;
+ return QString::null;
+ }
+ tf->setAutoDelete(true);
+ aTempFileList.append( tf );
+ tempFileName = tf->name();
+ if ((*it) == -1)
+ KPIM::kCStringToFile( aMsg->asString(), tempFileName, //###
+ false, false, false );
+ else if (aMsg->numBodyParts() == 0)
+ KPIM::kByteArrayToFile( aMsg->bodyDecodedBinary(), tempFileName,
+ false, false, false );
+ else {
+ KMMessagePart msgPart;
+ aMsg->bodyPart( (*it), &msgPart );
+ KPIM::kByteArrayToFile( msgPart.bodyDecodedBinary(), tempFileName,
+ false, false, false );
+ }
+ tf->close();
+ }
+ // QString( "%0 and %1 and %1" ).arg( 0 ).arg( 1 )
+ // returns "0 and 1 and %1", so we must call .arg as
+ // many times as there are %n's, regardless of their multiplicity.
+ if ((*it) == -1) result.replace( "%-1", tempFileName );
+ else result = result.arg( tempFileName );
+ }
+
+ // And finally, replace the %{foo} with the content of the foo
+ // header field:
+ QRegExp header_rx( "%\\{([a-z0-9-]+)\\}", false );
+ int idx = 0;
+ while ( ( idx = header_rx.search( result, idx ) ) != -1 ) {
+ QString replacement = KProcess::quote( aMsg->headerField( header_rx.cap(1).latin1() ) );
+ result.replace( idx, header_rx.matchedLength(), replacement );
+ idx += replacement.length();
+ }
+
+ return result;
+}
+
+
+KMFilterAction::ReturnCode KMFilterActionWithCommand::genericProcess(KMMessage* aMsg, bool withOutput) const
+{
+ Q_ASSERT( aMsg );
+
+ if ( mParameter.isEmpty() )
+ return ErrorButGoOn;
+
+ // KProcess doesn't support a QProcess::launch() equivalent, so
+ // we must use a temp file :-(
+ KTempFile * inFile = new KTempFile;
+ inFile->setAutoDelete(true);
+
+ QPtrList<KTempFile> atmList;
+ atmList.setAutoDelete(true);
+ atmList.append( inFile );
+
+ QString commandLine = substituteCommandLineArgsFor( aMsg , atmList );
+ if ( commandLine.isEmpty() )
+ return ErrorButGoOn;
+
+ // The parentheses force the creation of a subshell
+ // in which the user-specified command is executed.
+ // This is to really catch all output of the command as well
+ // as to avoid clashes of our redirection with the ones
+ // the user may have specified. In the long run, we
+ // shouldn't be using tempfiles at all for this class, due
+ // to security aspects. (mmutz)
+ commandLine = "(" + commandLine + ") <" + inFile->name();
+
+ // write message to file
+ QString tempFileName = inFile->name();
+ KPIM::kCStringToFile( aMsg->asString(), tempFileName, //###
+ false, false, false );
+ inFile->close();
+
+ CollectingProcess shProc;
+ shProc.setUseShell(true);
+ shProc << commandLine;
+
+ // run process:
+ if ( !shProc.start( KProcess::Block,
+ withOutput ? KProcess::Stdout
+ : KProcess::NoCommunication ) )
+ return ErrorButGoOn;
+
+ if ( !shProc.normalExit() || shProc.exitStatus() != 0 ) {
+ return ErrorButGoOn;
+ }
+
+ if ( withOutput ) {
+ // read altered message:
+ QByteArray msgText = shProc.collectedStdout();
+
+ if ( !msgText.isEmpty() ) {
+ /* If the pipe through alters the message, it could very well
+ happen that it no longer has a X-UID header afterwards. That is
+ unfortunate, as we need to removed the original from the folder
+ using that, and look it up in the message. When the (new) message
+ is uploaded, the header is stripped anyhow. */
+ QString uid = aMsg->headerField("X-UID");
+ aMsg->fromByteArray( msgText );
+ aMsg->setHeaderField("X-UID",uid);
+ }
+ else
+ return ErrorButGoOn;
+ }
+ return GoOn;
+}
+
+
+//=============================================================================
+//
+// Specific Filter Actions
+//
+//=============================================================================
+
+//=============================================================================
+// KMFilterActionSendReceipt - send receipt
+// Return delivery receipt.
+//=============================================================================
+class KMFilterActionSendReceipt : public KMFilterActionWithNone
+{
+public:
+ KMFilterActionSendReceipt();
+ virtual ReturnCode process(KMMessage* msg) const;
+ static KMFilterAction* newAction(void);
+};
+
+KMFilterAction* KMFilterActionSendReceipt::newAction(void)
+{
+ return (new KMFilterActionSendReceipt);
+}
+
+KMFilterActionSendReceipt::KMFilterActionSendReceipt()
+ : KMFilterActionWithNone( "confirm delivery", i18n("Confirm Delivery") )
+{
+}
+
+KMFilterAction::ReturnCode KMFilterActionSendReceipt::process(KMMessage* msg) const
+{
+ KMMessage *receipt = msg->createDeliveryReceipt();
+ if ( !receipt ) return ErrorButGoOn;
+
+ // Queue message. This is a) so that the user can check
+ // the receipt before sending and b) for speed reasons.
+ kmkernel->msgSender()->send( receipt, KMail::MessageSender::SendLater );
+
+ return GoOn;
+}
+
+
+
+//=============================================================================
+// KMFilterActionSetTransport - set transport to...
+// Specify mail transport (smtp server) to be used when replying to a message
+//=============================================================================
+class KMFilterActionTransport: public KMFilterActionWithString
+{
+public:
+ KMFilterActionTransport();
+ virtual ReturnCode process(KMMessage* msg) const;
+ static KMFilterAction* newAction(void);
+};
+
+KMFilterAction* KMFilterActionTransport::newAction(void)
+{
+ return (new KMFilterActionTransport);
+}
+
+KMFilterActionTransport::KMFilterActionTransport()
+ : KMFilterActionWithString( "set transport", i18n("Set Transport To") )
+{
+}
+
+KMFilterAction::ReturnCode KMFilterActionTransport::process(KMMessage* msg) const
+{
+ if ( mParameter.isEmpty() )
+ return ErrorButGoOn;
+ msg->setHeaderField( "X-KMail-Transport", mParameter );
+ return GoOn;
+}
+
+
+//=============================================================================
+// KMFilterActionReplyTo - set Reply-To to
+// Set the Reply-to header in a message
+//=============================================================================
+class KMFilterActionReplyTo: public KMFilterActionWithString
+{
+public:
+ KMFilterActionReplyTo();
+ virtual ReturnCode process(KMMessage* msg) const;
+ static KMFilterAction* newAction(void);
+};
+
+KMFilterAction* KMFilterActionReplyTo::newAction(void)
+{
+ return (new KMFilterActionReplyTo);
+}
+
+KMFilterActionReplyTo::KMFilterActionReplyTo()
+ : KMFilterActionWithString( "set Reply-To", i18n("Set Reply-To To") )
+{
+ mParameter = "";
+}
+
+KMFilterAction::ReturnCode KMFilterActionReplyTo::process(KMMessage* msg) const
+{
+ msg->setHeaderField( "Reply-To", mParameter );
+ return GoOn;
+}
+
+
+
+//=============================================================================
+// KMFilterActionIdentity - set identity to
+// Specify Identity to be used when replying to a message
+//=============================================================================
+class KMFilterActionIdentity: public KMFilterActionWithUOID
+{
+public:
+ KMFilterActionIdentity();
+ virtual ReturnCode process(KMMessage* msg) const;
+ static KMFilterAction* newAction();
+
+ QWidget * createParamWidget( QWidget * parent ) const;
+ void applyParamWidgetValue( QWidget * parent );
+ void setParamWidgetValue( QWidget * parent ) const;
+ void clearParamWidget( QWidget * param ) const;
+};
+
+KMFilterAction* KMFilterActionIdentity::newAction()
+{
+ return (new KMFilterActionIdentity);
+}
+
+KMFilterActionIdentity::KMFilterActionIdentity()
+ : KMFilterActionWithUOID( "set identity", i18n("Set Identity To") )
+{
+ mParameter = kmkernel->identityManager()->defaultIdentity().uoid();
+}
+
+KMFilterAction::ReturnCode KMFilterActionIdentity::process(KMMessage* msg) const
+{
+ msg->setHeaderField( "X-KMail-Identity", QString::number( mParameter ) );
+ return GoOn;
+}
+
+QWidget * KMFilterActionIdentity::createParamWidget( QWidget * parent ) const
+{
+ KPIM::IdentityCombo * ic = new KPIM::IdentityCombo( kmkernel->identityManager(), parent );
+ ic->setCurrentIdentity( mParameter );
+ return ic;
+}
+
+void KMFilterActionIdentity::applyParamWidgetValue( QWidget * paramWidget )
+{
+ KPIM::IdentityCombo * ic = dynamic_cast<KPIM::IdentityCombo*>( paramWidget );
+ assert( ic );
+ mParameter = ic->currentIdentity();
+}
+
+void KMFilterActionIdentity::clearParamWidget( QWidget * paramWidget ) const
+{
+ KPIM::IdentityCombo * ic = dynamic_cast<KPIM::IdentityCombo*>( paramWidget );
+ assert( ic );
+ ic->setCurrentItem( 0 );
+ //ic->setCurrentIdentity( kmkernel->identityManager()->defaultIdentity() );
+}
+
+void KMFilterActionIdentity::setParamWidgetValue( QWidget * paramWidget ) const
+{
+ KPIM::IdentityCombo * ic = dynamic_cast<KPIM::IdentityCombo*>( paramWidget );
+ assert( ic );
+ ic->setCurrentIdentity( mParameter );
+}
+
+//=============================================================================
+// KMFilterActionSetStatus - set status to
+// Set the status of messages
+//=============================================================================
+class KMFilterActionSetStatus: public KMFilterActionWithStringList
+{
+public:
+ KMFilterActionSetStatus();
+ virtual ReturnCode process(KMMessage* msg) const;
+ virtual bool requiresBody(KMMsgBase*) const;
+
+ static KMFilterAction* newAction();
+
+ virtual bool isEmpty() const { return false; }
+
+ virtual void argsFromString( const QString argsStr );
+ virtual const QString argsAsString() const;
+ virtual const QString displayString() const;
+};
+
+
+static const KMMsgStatus stati[] =
+{
+ KMMsgStatusFlag,
+ KMMsgStatusRead,
+ KMMsgStatusUnread,
+ KMMsgStatusReplied,
+ KMMsgStatusForwarded,
+ KMMsgStatusOld,
+ KMMsgStatusNew,
+ KMMsgStatusWatched,
+ KMMsgStatusIgnored,
+ KMMsgStatusSpam,
+ KMMsgStatusHam
+};
+static const int StatiCount = sizeof( stati ) / sizeof( KMMsgStatus );
+
+KMFilterAction* KMFilterActionSetStatus::newAction()
+{
+ return (new KMFilterActionSetStatus);
+}
+
+KMFilterActionSetStatus::KMFilterActionSetStatus()
+ : KMFilterActionWithStringList( "set status", i18n("Mark As") )
+{
+ // if you change this list, also update
+ // KMFilterActionSetStatus::stati above
+ mParameterList.append( "" );
+ mParameterList.append( i18n("msg status","Important") );
+ mParameterList.append( i18n("msg status","Read") );
+ mParameterList.append( i18n("msg status","Unread") );
+ mParameterList.append( i18n("msg status","Replied") );
+ mParameterList.append( i18n("msg status","Forwarded") );
+ mParameterList.append( i18n("msg status","Old") );
+ mParameterList.append( i18n("msg status","New") );
+ mParameterList.append( i18n("msg status","Watched") );
+ mParameterList.append( i18n("msg status","Ignored") );
+ mParameterList.append( i18n("msg status","Spam") );
+ mParameterList.append( i18n("msg status","Ham") );
+
+ mParameter = *mParameterList.at(0);
+}
+
+KMFilterAction::ReturnCode KMFilterActionSetStatus::process(KMMessage* msg) const
+{
+ int idx = mParameterList.findIndex( mParameter );
+ if ( idx < 1 ) return ErrorButGoOn;
+
+ KMMsgStatus status = stati[idx-1] ;
+ msg->setStatus( status );
+ return GoOn;
+}
+
+bool KMFilterActionSetStatus::requiresBody(KMMsgBase*) const
+{
+ return false;
+}
+
+void KMFilterActionSetStatus::argsFromString( const QString argsStr )
+{
+ if ( argsStr.length() == 1 ) {
+ for ( int i = 0 ; i < StatiCount ; i++ )
+ if ( KMMsgBase::statusToStr(stati[i])[0] == argsStr[0] ) {
+ mParameter = *mParameterList.at(i+1);
+ return;
+ }
+ }
+ mParameter = *mParameterList.at(0);
+}
+
+const QString KMFilterActionSetStatus::argsAsString() const
+{
+ int idx = mParameterList.findIndex( mParameter );
+ if ( idx < 1 ) return QString::null;
+
+ KMMsgStatus status = stati[idx-1];
+ return KMMsgBase::statusToStr(status);
+}
+
+const QString KMFilterActionSetStatus::displayString() const
+{
+ // FIXME after string freeze:
+ // return i18n("").arg( );
+ return label() + " \"" + QStyleSheet::escape( argsAsString() ) + "\"";
+}
+
+//=============================================================================
+// KMFilterActionFakeDisposition - send fake MDN
+// Sends a fake MDN or forces an ignore.
+//=============================================================================
+class KMFilterActionFakeDisposition: public KMFilterActionWithStringList
+{
+public:
+ KMFilterActionFakeDisposition();
+ virtual ReturnCode process(KMMessage* msg) const;
+ static KMFilterAction* newAction() {
+ return (new KMFilterActionFakeDisposition);
+ }
+
+ virtual bool isEmpty() const { return false; }
+
+ virtual void argsFromString( const QString argsStr );
+ virtual const QString argsAsString() const;
+ virtual const QString displayString() const;
+};
+
+
+// if you change this list, also update
+// the count in argsFromString
+static const KMime::MDN::DispositionType mdns[] =
+{
+ KMime::MDN::Displayed,
+ KMime::MDN::Deleted,
+ KMime::MDN::Dispatched,
+ KMime::MDN::Processed,
+ KMime::MDN::Denied,
+ KMime::MDN::Failed,
+};
+static const int numMDNs = sizeof mdns / sizeof *mdns;
+
+
+KMFilterActionFakeDisposition::KMFilterActionFakeDisposition()
+ : KMFilterActionWithStringList( "fake mdn", i18n("Send Fake MDN") )
+{
+ // if you change this list, also update
+ // mdns above
+ mParameterList.append( "" );
+ mParameterList.append( i18n("MDN type","Ignore") );
+ mParameterList.append( i18n("MDN type","Displayed") );
+ mParameterList.append( i18n("MDN type","Deleted") );
+ mParameterList.append( i18n("MDN type","Dispatched") );
+ mParameterList.append( i18n("MDN type","Processed") );
+ mParameterList.append( i18n("MDN type","Denied") );
+ mParameterList.append( i18n("MDN type","Failed") );
+
+ mParameter = *mParameterList.at(0);
+}
+
+KMFilterAction::ReturnCode KMFilterActionFakeDisposition::process(KMMessage* msg) const
+{
+ int idx = mParameterList.findIndex( mParameter );
+ if ( idx < 1 ) return ErrorButGoOn;
+
+ if ( idx == 1 ) // ignore
+ msg->setMDNSentState( KMMsgMDNIgnore );
+ else // send
+ sendMDN( msg, mdns[idx-2] ); // skip first two entries: "" and "ignore"
+ return GoOn;
+}
+
+void KMFilterActionFakeDisposition::argsFromString( const QString argsStr )
+{
+ if ( argsStr.length() == 1 ) {
+ if ( argsStr[0] == 'I' ) { // ignore
+ mParameter = *mParameterList.at(1);
+ return;
+ }
+ for ( int i = 0 ; i < numMDNs ; i++ )
+ if ( char(mdns[i]) == argsStr[0] ) { // send
+ mParameter = *mParameterList.at(i+2);
+ return;
+ }
+ }
+ mParameter = *mParameterList.at(0);
+}
+
+const QString KMFilterActionFakeDisposition::argsAsString() const
+{
+ int idx = mParameterList.findIndex( mParameter );
+ if ( idx < 1 ) return QString::null;
+
+ return QString( QChar( idx < 2 ? 'I' : char(mdns[idx-2]) ) );
+}
+
+const QString KMFilterActionFakeDisposition::displayString() const
+{
+ // FIXME after string freeze:
+ // return i18n("").arg( );
+ return label() + " \"" + QStyleSheet::escape( argsAsString() ) + "\"";
+}
+
+//=============================================================================
+// KMFilterActionRemoveHeader - remove header
+// Remove all instances of the given header field.
+//=============================================================================
+class KMFilterActionRemoveHeader: public KMFilterActionWithStringList
+{
+public:
+ KMFilterActionRemoveHeader();
+ virtual ReturnCode process(KMMessage* msg) const;
+ virtual QWidget* createParamWidget( QWidget* parent ) const;
+ virtual void setParamWidgetValue( QWidget* paramWidget ) const;
+
+ static KMFilterAction* newAction();
+};
+
+KMFilterAction* KMFilterActionRemoveHeader::newAction()
+{
+ return (new KMFilterActionRemoveHeader);
+}
+
+KMFilterActionRemoveHeader::KMFilterActionRemoveHeader()
+ : KMFilterActionWithStringList( "remove header", i18n("Remove Header") )
+{
+ mParameterList << ""
+ << "Reply-To"
+ << "Delivered-To"
+ << "X-KDE-PR-Message"
+ << "X-KDE-PR-Package"
+ << "X-KDE-PR-Keywords";
+ mParameter = *mParameterList.at(0);
+}
+
+QWidget* KMFilterActionRemoveHeader::createParamWidget( QWidget* parent ) const
+{
+ QComboBox *cb = new QComboBox( true/*editable*/, parent );
+ cb->setInsertionPolicy( QComboBox::AtBottom );
+ setParamWidgetValue( cb );
+ return cb;
+}
+
+KMFilterAction::ReturnCode KMFilterActionRemoveHeader::process(KMMessage* msg) const
+{
+ if ( mParameter.isEmpty() ) return ErrorButGoOn;
+
+ while ( !msg->headerField( mParameter.latin1() ).isEmpty() )
+ msg->removeHeaderField( mParameter.latin1() );
+ return GoOn;
+}
+
+void KMFilterActionRemoveHeader::setParamWidgetValue( QWidget* paramWidget ) const
+{
+ QComboBox * cb = dynamic_cast<QComboBox*>(paramWidget);
+ Q_ASSERT( cb );
+
+ int idx = mParameterList.findIndex( mParameter );
+ cb->clear();
+ cb->insertStringList( mParameterList );
+ if ( idx < 0 ) {
+ cb->insertItem( mParameter );
+ cb->setCurrentItem( cb->count() - 1 );
+ } else {
+ cb->setCurrentItem( idx );
+ }
+}
+
+
+//=============================================================================
+// KMFilterActionAddHeader - add header
+// Add a header with the given value.
+//=============================================================================
+class KMFilterActionAddHeader: public KMFilterActionWithStringList
+{
+public:
+ KMFilterActionAddHeader();
+ virtual ReturnCode process(KMMessage* msg) const;
+ virtual QWidget* createParamWidget( QWidget* parent ) const;
+ virtual void setParamWidgetValue( QWidget* paramWidget ) const;
+ virtual void applyParamWidgetValue( QWidget* paramWidget );
+ virtual void clearParamWidget( QWidget* paramWidget ) const;
+
+ virtual const QString argsAsString() const;
+ virtual void argsFromString( const QString argsStr );
+
+ virtual const QString displayString() const;
+
+ static KMFilterAction* newAction()
+ {
+ return (new KMFilterActionAddHeader);
+ }
+private:
+ QString mValue;
+};
+
+KMFilterActionAddHeader::KMFilterActionAddHeader()
+ : KMFilterActionWithStringList( "add header", i18n("Add Header") )
+{
+ mParameterList << ""
+ << "Reply-To"
+ << "Delivered-To"
+ << "X-KDE-PR-Message"
+ << "X-KDE-PR-Package"
+ << "X-KDE-PR-Keywords";
+ mParameter = *mParameterList.at(0);
+}
+
+KMFilterAction::ReturnCode KMFilterActionAddHeader::process(KMMessage* msg) const
+{
+ if ( mParameter.isEmpty() ) return ErrorButGoOn;
+
+ msg->setHeaderField( mParameter.latin1(), mValue );
+ return GoOn;
+}
+
+QWidget* KMFilterActionAddHeader::createParamWidget( QWidget* parent ) const
+{
+ QWidget *w = new QWidget( parent );
+ QHBoxLayout *hbl = new QHBoxLayout( w );
+ hbl->setSpacing( 4 );
+ QComboBox *cb = new QComboBox( true, w, "combo" );
+ cb->setInsertionPolicy( QComboBox::AtBottom );
+ hbl->addWidget( cb, 0 /* stretch */ );
+ QLabel *l = new QLabel( i18n("With value:"), w );
+ l->setFixedWidth( l->sizeHint().width() );
+ hbl->addWidget( l, 0 );
+ QLineEdit *le = new KLineEdit( w, "ledit" );
+ hbl->addWidget( le, 1 );
+ setParamWidgetValue( w );
+ return w;
+}
+
+void KMFilterActionAddHeader::setParamWidgetValue( QWidget* paramWidget ) const
+{
+ int idx = mParameterList.findIndex( mParameter );
+ QComboBox *cb = (QComboBox*)paramWidget->child("combo");
+ Q_ASSERT( cb );
+ cb->clear();
+ cb->insertStringList( mParameterList );
+ if ( idx < 0 ) {
+ cb->insertItem( mParameter );
+ cb->setCurrentItem( cb->count() - 1 );
+ } else {
+ cb->setCurrentItem( idx );
+ }
+ QLineEdit *le = (QLineEdit*)paramWidget->child("ledit");
+ Q_ASSERT( le );
+ le->setText( mValue );
+}
+
+void KMFilterActionAddHeader::applyParamWidgetValue( QWidget* paramWidget )
+{
+ QComboBox *cb = (QComboBox*)paramWidget->child("combo");
+ Q_ASSERT( cb );
+ mParameter = cb->currentText();
+
+ QLineEdit *le = (QLineEdit*)paramWidget->child("ledit");
+ Q_ASSERT( le );
+ mValue = le->text();
+}
+
+void KMFilterActionAddHeader::clearParamWidget( QWidget* paramWidget ) const
+{
+ QComboBox *cb = (QComboBox*)paramWidget->child("combo");
+ Q_ASSERT( cb );
+ cb->setCurrentItem(0);
+ QLineEdit *le = (QLineEdit*)paramWidget->child("ledit");
+ Q_ASSERT( le );
+ le->clear();
+}
+
+const QString KMFilterActionAddHeader::argsAsString() const
+{
+ QString result = mParameter;
+ result += '\t';
+ result += mValue;
+
+ return result;
+}
+
+const QString KMFilterActionAddHeader::displayString() const
+{
+ // FIXME after string freeze:
+ // return i18n("").arg( );
+ return label() + " \"" + QStyleSheet::escape( argsAsString() ) + "\"";
+}
+
+void KMFilterActionAddHeader::argsFromString( const QString argsStr )
+{
+ QStringList l = QStringList::split( '\t', argsStr, true /*allow empty entries*/ );
+ QString s;
+ if ( l.count() < 2 ) {
+ s = l[0];
+ mValue = "";
+ } else {
+ s = l[0];
+ mValue = l[1];
+ }
+
+ int idx = mParameterList.findIndex( s );
+ if ( idx < 0 ) {
+ mParameterList.append( s );
+ idx = mParameterList.count() - 1;
+ }
+ mParameter = *mParameterList.at( idx );
+}
+
+
+//=============================================================================
+// KMFilterActionRewriteHeader - rewrite header
+// Rewrite a header using a regexp.
+//=============================================================================
+class KMFilterActionRewriteHeader: public KMFilterActionWithStringList
+{
+public:
+ KMFilterActionRewriteHeader();
+ virtual ReturnCode process(KMMessage* msg) const;
+ virtual QWidget* createParamWidget( QWidget* parent ) const;
+ virtual void setParamWidgetValue( QWidget* paramWidget ) const;
+ virtual void applyParamWidgetValue( QWidget* paramWidget );
+ virtual void clearParamWidget( QWidget* paramWidget ) const;
+
+ virtual const QString argsAsString() const;
+ virtual void argsFromString( const QString argsStr );
+
+ virtual const QString displayString() const;
+
+ static KMFilterAction* newAction()
+ {
+ return (new KMFilterActionRewriteHeader);
+ }
+private:
+ KRegExp3 mRegExp;
+ QString mReplacementString;
+};
+
+KMFilterActionRewriteHeader::KMFilterActionRewriteHeader()
+ : KMFilterActionWithStringList( "rewrite header", i18n("Rewrite Header") )
+{
+ mParameterList << ""
+ << "Subject"
+ << "Reply-To"
+ << "Delivered-To"
+ << "X-KDE-PR-Message"
+ << "X-KDE-PR-Package"
+ << "X-KDE-PR-Keywords";
+ mParameter = *mParameterList.at(0);
+}
+
+KMFilterAction::ReturnCode KMFilterActionRewriteHeader::process(KMMessage* msg) const
+{
+ if ( mParameter.isEmpty() || !mRegExp.isValid() )
+ return ErrorButGoOn;
+
+ KRegExp3 rx = mRegExp; // KRegExp3::replace is not const.
+
+ QString newValue = rx.replace( msg->headerField( mParameter.latin1() ),
+ mReplacementString );
+
+ msg->setHeaderField( mParameter.latin1(), newValue );
+ return GoOn;
+}
+
+QWidget* KMFilterActionRewriteHeader::createParamWidget( QWidget* parent ) const
+{
+ QWidget *w = new QWidget( parent );
+ QHBoxLayout *hbl = new QHBoxLayout( w );
+ hbl->setSpacing( 4 );
+
+ QComboBox *cb = new QComboBox( true, w, "combo" );
+ cb->setInsertionPolicy( QComboBox::AtBottom );
+ hbl->addWidget( cb, 0 /* stretch */ );
+
+ QLabel *l = new QLabel( i18n("Replace:"), w );
+ l->setFixedWidth( l->sizeHint().width() );
+ hbl->addWidget( l, 0 );
+
+ RegExpLineEdit *rele = new RegExpLineEdit( w, "search" );
+ hbl->addWidget( rele, 1 );
+
+ l = new QLabel( i18n("With:"), w );
+ l->setFixedWidth( l->sizeHint().width() );
+ hbl->addWidget( l, 0 );
+
+ QLineEdit *le = new KLineEdit( w, "replace" );
+ hbl->addWidget( le, 1 );
+
+ setParamWidgetValue( w );
+ return w;
+}
+
+void KMFilterActionRewriteHeader::setParamWidgetValue( QWidget* paramWidget ) const
+{
+ int idx = mParameterList.findIndex( mParameter );
+ QComboBox *cb = (QComboBox*)paramWidget->child("combo");
+ Q_ASSERT( cb );
+
+ cb->clear();
+ cb->insertStringList( mParameterList );
+ if ( idx < 0 ) {
+ cb->insertItem( mParameter );
+ cb->setCurrentItem( cb->count() - 1 );
+ } else {
+ cb->setCurrentItem( idx );
+ }
+
+ RegExpLineEdit *rele = (RegExpLineEdit*)paramWidget->child("search");
+ Q_ASSERT( rele );
+ rele->setText( mRegExp.pattern() );
+
+ QLineEdit *le = (QLineEdit*)paramWidget->child("replace");
+ Q_ASSERT( le );
+ le->setText( mReplacementString );
+}
+
+void KMFilterActionRewriteHeader::applyParamWidgetValue( QWidget* paramWidget )
+{
+ QComboBox *cb = (QComboBox*)paramWidget->child("combo");
+ Q_ASSERT( cb );
+ mParameter = cb->currentText();
+
+ RegExpLineEdit *rele = (RegExpLineEdit*)paramWidget->child("search");
+ Q_ASSERT( rele );
+ mRegExp.setPattern( rele->text() );
+
+ QLineEdit *le = (QLineEdit*)paramWidget->child("replace");
+ Q_ASSERT( le );
+ mReplacementString = le->text();
+}
+
+void KMFilterActionRewriteHeader::clearParamWidget( QWidget* paramWidget ) const
+{
+ QComboBox *cb = (QComboBox*)paramWidget->child("combo");
+ Q_ASSERT( cb );
+ cb->setCurrentItem(0);
+
+ RegExpLineEdit *rele = (RegExpLineEdit*)paramWidget->child("search");
+ Q_ASSERT( rele );
+ rele->clear();
+
+ QLineEdit *le = (QLineEdit*)paramWidget->child("replace");
+ Q_ASSERT( le );
+ le->clear();
+}
+
+const QString KMFilterActionRewriteHeader::argsAsString() const
+{
+ QString result = mParameter;
+ result += '\t';
+ result += mRegExp.pattern();
+ result += '\t';
+ result += mReplacementString;
+
+ return result;
+}
+
+const QString KMFilterActionRewriteHeader::displayString() const
+{
+ // FIXME after string freeze:
+ // return i18n("").arg( );
+ return label() + " \"" + QStyleSheet::escape( argsAsString() ) + "\"";
+}
+
+void KMFilterActionRewriteHeader::argsFromString( const QString argsStr )
+{
+ QStringList l = QStringList::split( '\t', argsStr, true /*allow empty entries*/ );
+ QString s;
+
+ s = l[0];
+ mRegExp.setPattern( l[1] );
+ mReplacementString = l[2];
+
+ int idx = mParameterList.findIndex( s );
+ if ( idx < 0 ) {
+ mParameterList.append( s );
+ idx = mParameterList.count() - 1;
+ }
+ mParameter = *mParameterList.at( idx );
+}
+
+
+//=============================================================================
+// KMFilterActionMove - move into folder
+// File message into another mail folder
+//=============================================================================
+class KMFilterActionMove: public KMFilterActionWithFolder
+{
+public:
+ KMFilterActionMove();
+ virtual ReturnCode process(KMMessage* msg) const;
+ virtual bool requiresBody(KMMsgBase*) const;
+ static KMFilterAction* newAction(void);
+};
+
+KMFilterAction* KMFilterActionMove::newAction(void)
+{
+ return (new KMFilterActionMove);
+}
+
+KMFilterActionMove::KMFilterActionMove()
+ : KMFilterActionWithFolder( "transfer", i18n("Move Into Folder") )
+{
+}
+
+KMFilterAction::ReturnCode KMFilterActionMove::process(KMMessage* msg) const
+{
+ if ( !mFolder )
+ return ErrorButGoOn;
+
+ ActionScheduler *handler = MessageProperty::filterHandler( msg );
+ if (handler) {
+ MessageProperty::setFilterFolder( msg, mFolder );
+ } else {
+ // The old filtering system does not support online imap targets.
+ // Skip online imap targets when using the old system.
+ KMFolder *check;
+ check = kmkernel->imapFolderMgr()->findIdString( argsAsString() );
+ if (mFolder && (check != mFolder)) {
+ MessageProperty::setFilterFolder( msg, mFolder );
+ }
+ }
+ return GoOn;
+}
+
+bool KMFilterActionMove::requiresBody(KMMsgBase*) const
+{
+ return false; //iff mFolder->folderMgr == msgBase->parent()->folderMgr;
+}
+
+
+//=============================================================================
+// KMFilterActionCopy - copy into folder
+// Copy message into another mail folder
+//=============================================================================
+class KMFilterActionCopy: public KMFilterActionWithFolder
+{
+public:
+ KMFilterActionCopy();
+ virtual ReturnCode process(KMMessage* msg) const;
+ virtual void processAsync(KMMessage* msg) const;
+ virtual bool requiresBody(KMMsgBase*) const;
+ static KMFilterAction* newAction(void);
+};
+
+KMFilterAction* KMFilterActionCopy::newAction(void)
+{
+ return (new KMFilterActionCopy);
+}
+
+KMFilterActionCopy::KMFilterActionCopy()
+ : KMFilterActionWithFolder( "copy", i18n("Copy Into Folder") )
+{
+}
+
+KMFilterAction::ReturnCode KMFilterActionCopy::process(KMMessage* msg) const
+{
+ // TODO opening and closing the folder is a trade off.
+ // Perhaps Copy is a seldomly used action for now,
+ // but I gonna look at improvements ASAP.
+ if ( !mFolder )
+ return ErrorButGoOn;
+ if ( mFolder && mFolder->open( "filtercopy" ) != 0 )
+ return ErrorButGoOn;
+
+ // copy the message 1:1
+ KMMessage* msgCopy = new KMMessage( new DwMessage( *msg->asDwMessage() ) );
+
+ int index;
+ int rc = mFolder->addMsg(msgCopy, &index);
+ if (rc == 0 && index != -1)
+ mFolder->unGetMsg( index );
+ mFolder->close("filtercopy");
+
+ return GoOn;
+}
+
+void KMFilterActionCopy::processAsync(KMMessage* msg) const
+{
+ // FIXME remove the debug output
+ kdDebug(5006) << "##### KMFilterActionCopy::processAsync(KMMessage* msg)" << endl;
+ ActionScheduler *handler = MessageProperty::filterHandler( msg );
+
+ KMCommand *cmd = new KMCopyCommand( mFolder, msg );
+ QObject::connect( cmd, SIGNAL( completed( KMCommand * ) ),
+ handler, SLOT( copyMessageFinished( KMCommand * ) ) );
+ cmd->start();
+}
+
+bool KMFilterActionCopy::requiresBody(KMMsgBase*) const
+{
+ return true;
+}
+
+
+//=============================================================================
+// KMFilterActionForward - forward to
+// Forward message to another user
+//=============================================================================
+class KMFilterActionForward: public KMFilterActionWithAddress
+{
+public:
+ KMFilterActionForward();
+ virtual ReturnCode process(KMMessage* msg) const;
+ static KMFilterAction* newAction(void);
+};
+
+KMFilterAction* KMFilterActionForward::newAction(void)
+{
+ return (new KMFilterActionForward);
+}
+
+KMFilterActionForward::KMFilterActionForward()
+ : KMFilterActionWithAddress( "forward", i18n("Forward To") )
+{
+}
+
+KMFilterAction::ReturnCode KMFilterActionForward::process(KMMessage* aMsg) const
+{
+ if ( mParameter.isEmpty() )
+ return ErrorButGoOn;
+
+ // avoid endless loops when this action is used in a filter
+ // which applies to sent messages
+ if ( KMMessage::addressIsInAddressList( mParameter, aMsg->to() ) )
+ return ErrorButGoOn;
+
+ // 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.
+
+ KMMessage* msg = new KMMessage;
+
+ msg->initFromMessage( aMsg );
+
+ // QString st = QString::fromUtf8( aMsg->createForwardBody() );
+
+ TemplateParser parser( msg, TemplateParser::Forward,
+ aMsg->body(), false, false, false, false);
+ parser.process( aMsg );
+
+ QCString
+ encoding = KMMsgBase::autoDetectCharset( aMsg->charset(),
+ KMMessage::preferredCharsets(),
+ msg->body() );
+ if( encoding.isEmpty() )
+ encoding = "utf-8";
+ QCString str = KMMsgBase::codecForName( encoding )->fromUnicode( msg->body() );
+
+ msg->setCharset( encoding );
+ msg->setTo( mParameter );
+ msg->setSubject( "Fwd: " + aMsg->subject() );
+
+ bool isQP = kmkernel->msgSender()->sendQuotedPrintable();
+
+ if( aMsg->numBodyParts() == 0 )
+ {
+ msg->setAutomaticFields( true );
+ msg->setHeaderField( "Content-Type", "text/plain" );
+ // msg->setCteStr( isQP ? "quoted-printable": "8bit" );
+ QValueList<int> dummy;
+ msg->setBodyAndGuessCte(str, dummy, !isQP);
+ msg->setCharset( encoding );
+ if( isQP )
+ msg->setBodyEncoded( str );
+ else
+ msg->setBody( str );
+ }
+ 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" );
+ QValueList<int> 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 );
+ }
+ }
+ msg->cleanupHeader();
+ msg->link( aMsg, KMMsgStatusForwarded );
+
+ sendMDN( aMsg, KMime::MDN::Dispatched );
+
+ if ( !kmkernel->msgSender()->send( msg, KMail::MessageSender::SendLater ) ) {
+ kdDebug(5006) << "KMFilterAction: could not forward message (sending failed)" << endl;
+ return ErrorButGoOn; // error: couldn't send
+ }
+ return GoOn;
+}
+
+
+//=============================================================================
+// KMFilterActionRedirect - redirect to
+// Redirect message to another user
+//=============================================================================
+class KMFilterActionRedirect: public KMFilterActionWithAddress
+{
+public:
+ KMFilterActionRedirect();
+ virtual ReturnCode process(KMMessage* msg) const;
+ static KMFilterAction* newAction(void);
+};
+
+KMFilterAction* KMFilterActionRedirect::newAction(void)
+{
+ return (new KMFilterActionRedirect);
+}
+
+KMFilterActionRedirect::KMFilterActionRedirect()
+ : KMFilterActionWithAddress( "redirect", i18n("Redirect To") )
+{
+}
+
+KMFilterAction::ReturnCode KMFilterActionRedirect::process(KMMessage* aMsg) const
+{
+ KMMessage* msg;
+ if ( mParameter.isEmpty() )
+ return ErrorButGoOn;
+
+ msg = aMsg->createRedirect( mParameter );
+
+ sendMDN( aMsg, KMime::MDN::Dispatched );
+
+ if ( !kmkernel->msgSender()->send( msg, KMail::MessageSender::SendLater ) ) {
+ kdDebug(5006) << "KMFilterAction: could not redirect message (sending failed)" << endl;
+ return ErrorButGoOn; // error: couldn't send
+ }
+ return GoOn;
+}
+
+
+//=============================================================================
+// KMFilterActionExec - execute command
+// Execute a shell command
+//=============================================================================
+class KMFilterActionExec : public KMFilterActionWithCommand
+{
+public:
+ KMFilterActionExec();
+ virtual ReturnCode process(KMMessage* msg) const;
+ static KMFilterAction* newAction(void);
+};
+
+KMFilterAction* KMFilterActionExec::newAction(void)
+{
+ return (new KMFilterActionExec());
+}
+
+KMFilterActionExec::KMFilterActionExec()
+ : KMFilterActionWithCommand( "execute", i18n("Execute Command") )
+{
+}
+
+KMFilterAction::ReturnCode KMFilterActionExec::process(KMMessage *aMsg) const
+{
+ return KMFilterActionWithCommand::genericProcess( aMsg, false ); // ignore output
+}
+
+//=============================================================================
+// KMFilterActionExtFilter - use external filter app
+// External message filter: executes a shell command with message
+// on stdin; altered message is expected on stdout.
+//=============================================================================
+
+#include <weaver.h>
+class PipeJob : public KPIM::ThreadWeaver::Job
+{
+ public:
+ PipeJob(QObject* parent = 0 , const char* name = 0, KMMessage* aMsg = 0, QString cmd = 0, QString tempFileName = 0 )
+ : Job (parent, name),
+ mTempFileName(tempFileName),
+ mCmd(cmd),
+ mMsg( aMsg )
+ {
+ }
+
+ ~PipeJob() {}
+ virtual void processEvent( KPIM::ThreadWeaver::Event *ev )
+ {
+ KPIM::ThreadWeaver::Job::processEvent( ev );
+ if ( ev->action() == KPIM::ThreadWeaver::Event::JobFinished )
+ deleteLater( );
+ }
+ protected:
+ void run()
+ {
+ KPIM::ThreadWeaver::debug (1, "PipeJob::run: doing it .\n");
+ FILE *p;
+ QByteArray ba;
+
+ // backup the serial number in case the header gets lost
+ QString origSerNum = mMsg->headerField( "X-KMail-Filtered" );
+
+ p = popen(QFile::encodeName(mCmd), "r");
+ int len =100;
+ char buffer[100];
+ // append data to ba:
+ while (true) {
+ if (! fgets( buffer, len, p ) ) break;
+ int oldsize = ba.size();
+ ba.resize( oldsize + strlen(buffer) );
+ qmemmove( ba.begin() + oldsize, buffer, strlen(buffer) );
+ }
+ pclose(p);
+ if ( !ba.isEmpty() ) {
+ KPIM::ThreadWeaver::debug (1, "PipeJob::run: %s", QString(ba).latin1() );
+ KMFolder *filterFolder = mMsg->parent();
+ ActionScheduler *handler = MessageProperty::filterHandler( mMsg->getMsgSerNum() );
+
+ mMsg->fromByteArray( ba );
+ if ( !origSerNum.isEmpty() )
+ mMsg->setHeaderField( "X-KMail-Filtered", origSerNum );
+ if ( filterFolder && handler ) {
+ bool oldStatus = handler->ignoreChanges( true );
+ filterFolder->take( filterFolder->find( mMsg ) );
+ filterFolder->addMsg( mMsg );
+ handler->ignoreChanges( oldStatus );
+ } else {
+ kdDebug(5006) << "Warning: Cannot refresh the message from the external filter." << endl;
+ }
+ }
+
+ KPIM::ThreadWeaver::debug (1, "PipeJob::run: done.\n" );
+ // unlink the tempFile
+ QFile::remove(mTempFileName);
+ }
+ QString mTempFileName;
+ QString mCmd;
+ KMMessage *mMsg;
+};
+
+class KMFilterActionExtFilter: public KMFilterActionWithCommand
+{
+public:
+ KMFilterActionExtFilter();
+ virtual ReturnCode process(KMMessage* msg) const;
+ virtual void processAsync(KMMessage* msg) const;
+ static KMFilterAction* newAction(void);
+};
+
+KMFilterAction* KMFilterActionExtFilter::newAction(void)
+{
+ return (new KMFilterActionExtFilter);
+}
+
+KMFilterActionExtFilter::KMFilterActionExtFilter()
+ : KMFilterActionWithCommand( "filter app", i18n("Pipe Through") )
+{
+}
+KMFilterAction::ReturnCode KMFilterActionExtFilter::process(KMMessage* aMsg) const
+{
+ return KMFilterActionWithCommand::genericProcess( aMsg, true ); // use output
+}
+
+void KMFilterActionExtFilter::processAsync(KMMessage* aMsg) const
+{
+
+ ActionScheduler *handler = MessageProperty::filterHandler( aMsg->getMsgSerNum() );
+ KTempFile * inFile = new KTempFile;
+ inFile->setAutoDelete(false);
+
+ QPtrList<KTempFile> atmList;
+ atmList.setAutoDelete(true);
+ atmList.append( inFile );
+
+ QString commandLine = substituteCommandLineArgsFor( aMsg , atmList );
+ if ( commandLine.isEmpty() )
+ handler->actionMessage( ErrorButGoOn );
+
+ // The parentheses force the creation of a subshell
+ // in which the user-specified command is executed.
+ // This is to really catch all output of the command as well
+ // as to avoid clashes of our redirection with the ones
+ // the user may have specified. In the long run, we
+ // shouldn't be using tempfiles at all for this class, due
+ // to security aspects. (mmutz)
+ commandLine = "(" + commandLine + ") <" + inFile->name();
+
+ // write message to file
+ QString tempFileName = inFile->name();
+ KPIM::kCStringToFile( aMsg->asString(), tempFileName, //###
+ false, false, false );
+ inFile->close();
+
+ PipeJob *job = new PipeJob(0, 0, aMsg, commandLine, tempFileName);
+ QObject::connect ( job, SIGNAL( done() ), handler, SLOT( actionMessage() ) );
+ kmkernel->weaver()->enqueue(job);
+}
+
+//=============================================================================
+// KMFilterActionExecSound - execute command
+// Execute a sound
+//=============================================================================
+class KMFilterActionExecSound : public KMFilterActionWithTest
+{
+public:
+ KMFilterActionExecSound();
+ virtual ReturnCode process(KMMessage* msg) const;
+ virtual bool requiresBody(KMMsgBase*) const;
+ static KMFilterAction* newAction(void);
+};
+
+KMFilterActionWithTest::KMFilterActionWithTest( const char* aName, const QString aLabel )
+ : KMFilterAction( aName, aLabel )
+{
+}
+
+KMFilterActionWithTest::~KMFilterActionWithTest()
+{
+}
+
+QWidget* KMFilterActionWithTest::createParamWidget( QWidget* parent ) const
+{
+ KMSoundTestWidget *le = new KMSoundTestWidget(parent);
+ le->setUrl( mParameter );
+ return le;
+}
+
+
+void KMFilterActionWithTest::applyParamWidgetValue( QWidget* paramWidget )
+{
+ mParameter = ((KMSoundTestWidget*)paramWidget)->url();
+}
+
+void KMFilterActionWithTest::setParamWidgetValue( QWidget* paramWidget ) const
+{
+ ((KMSoundTestWidget*)paramWidget)->setUrl( mParameter );
+}
+
+void KMFilterActionWithTest::clearParamWidget( QWidget* paramWidget ) const
+{
+ ((KMSoundTestWidget*)paramWidget)->clear();
+}
+
+void KMFilterActionWithTest::argsFromString( const QString argsStr )
+{
+ mParameter = argsStr;
+}
+
+const QString KMFilterActionWithTest::argsAsString() const
+{
+ return mParameter;
+}
+
+const QString KMFilterActionWithTest::displayString() const
+{
+ // FIXME after string freeze:
+ // return i18n("").arg( );
+ return label() + " \"" + QStyleSheet::escape( argsAsString() ) + "\"";
+}
+
+
+KMFilterActionExecSound::KMFilterActionExecSound()
+ : KMFilterActionWithTest( "play sound", i18n("Play Sound") )
+{
+}
+
+KMFilterAction* KMFilterActionExecSound::newAction(void)
+{
+ return (new KMFilterActionExecSound());
+}
+
+KMFilterAction::ReturnCode KMFilterActionExecSound::process(KMMessage*) const
+{
+ if ( mParameter.isEmpty() )
+ return ErrorButGoOn;
+ QString play = mParameter;
+ QString file = QString::fromLatin1("file:");
+ if (mParameter.startsWith(file))
+ play = mParameter.mid(file.length());
+ KAudioPlayer::play(QFile::encodeName(play));
+ return GoOn;
+}
+
+bool KMFilterActionExecSound::requiresBody(KMMsgBase*) const
+{
+ return false;
+}
+
+KMFilterActionWithUrl::KMFilterActionWithUrl( const char* aName, const QString aLabel )
+ : KMFilterAction( aName, aLabel )
+{
+}
+
+KMFilterActionWithUrl::~KMFilterActionWithUrl()
+{
+}
+
+QWidget* KMFilterActionWithUrl::createParamWidget( QWidget* parent ) const
+{
+ KURLRequester *le = new KURLRequester(parent);
+ le->setURL( mParameter );
+ return le;
+}
+
+
+void KMFilterActionWithUrl::applyParamWidgetValue( QWidget* paramWidget )
+{
+ mParameter = ((KURLRequester*)paramWidget)->url();
+}
+
+void KMFilterActionWithUrl::setParamWidgetValue( QWidget* paramWidget ) const
+{
+ ((KURLRequester*)paramWidget)->setURL( mParameter );
+}
+
+void KMFilterActionWithUrl::clearParamWidget( QWidget* paramWidget ) const
+{
+ ((KURLRequester*)paramWidget)->clear();
+}
+
+void KMFilterActionWithUrl::argsFromString( const QString argsStr )
+{
+ mParameter = argsStr;
+}
+
+const QString KMFilterActionWithUrl::argsAsString() const
+{
+ return mParameter;
+}
+
+const QString KMFilterActionWithUrl::displayString() const
+{
+ // FIXME after string freeze:
+ // return i18n("").arg( );
+ return label() + " \"" + QStyleSheet::escape( argsAsString() ) + "\"";
+}
+
+
+//=============================================================================
+//
+// Filter Action Dictionary
+//
+//=============================================================================
+void KMFilterActionDict::init(void)
+{
+ insert( KMFilterActionMove::newAction );
+ insert( KMFilterActionCopy::newAction );
+ insert( KMFilterActionIdentity::newAction );
+ insert( KMFilterActionSetStatus::newAction );
+ insert( KMFilterActionFakeDisposition::newAction );
+ insert( KMFilterActionTransport::newAction );
+ insert( KMFilterActionReplyTo::newAction );
+ insert( KMFilterActionForward::newAction );
+ insert( KMFilterActionRedirect::newAction );
+ insert( KMFilterActionSendReceipt::newAction );
+ insert( KMFilterActionExec::newAction );
+ insert( KMFilterActionExtFilter::newAction );
+ insert( KMFilterActionRemoveHeader::newAction );
+ insert( KMFilterActionAddHeader::newAction );
+ insert( KMFilterActionRewriteHeader::newAction );
+ insert( KMFilterActionExecSound::newAction );
+ // Register custom filter actions below this line.
+}
+// The int in the QDict constructor (41) must be a prime
+// and should be greater than the double number of KMFilterAction types
+KMFilterActionDict::KMFilterActionDict()
+ : QDict<KMFilterActionDesc>(41)
+{
+ mList.setAutoDelete(true);
+ init();
+}
+
+void KMFilterActionDict::insert( KMFilterActionNewFunc aNewFunc )
+{
+ KMFilterAction *action = aNewFunc();
+ KMFilterActionDesc* desc = new KMFilterActionDesc;
+ desc->name = action->name();
+ desc->label = action->label();
+ desc->create = aNewFunc;
+ QDict<KMFilterActionDesc>::insert( desc->name, desc );
+ QDict<KMFilterActionDesc>::insert( desc->label, desc );
+ mList.append( desc );
+ delete action;
+}
diff --git a/kmail/kmfilteraction.h b/kmail/kmfilteraction.h
new file mode 100644
index 00000000..abd42719
--- /dev/null
+++ b/kmail/kmfilteraction.h
@@ -0,0 +1,700 @@
+/*
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ *
+ * 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 kmfilteraction_h
+#define kmfilteraction_h
+
+#include <kmime_mdn.h>
+
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qdict.h>
+#include <qptrlist.h>
+#include <qvaluelist.h>
+#include <qguardedptr.h>
+#include <qwidget.h>
+
+class KMMsgBase;
+class KMMessage;
+class QWidget;
+class KMFolder;
+class KTempFile;
+
+//=========================================================
+//
+// class KMFilterAction
+//
+//=========================================================
+
+
+/** Abstract base class for KMail's filter actions. All it can do is
+ hold a name (ie. type-string). There are several sub-classes that
+ inherit form this and are capable of providing parameter handling
+ (import/export as string, a widget to allow editing, etc.)
+
+ @short Abstract base class for KMail's filter actions.
+ @author Marc Mutz <Marc@Mutz.com>, based on work by Stefan Taferner <taferner@kde.org>.
+ @see KMFilter KMFilterMgr
+*/
+class KMFilterAction
+{
+public:
+ /** Possible return codes of process:
+
+ @li @p ErrorNeedComplete: Could not process because a
+ complete message is needed.
+
+ @li @p GoOn: Go on with applying filter actions.
+
+ @li @p ErrorButGoOn: There was a non-critical error (e.g. an
+ invalid address in the 'forward' action), but the processing
+ should continue.
+
+ @li @p CriticalError: A critical error has occurred during
+ processing (e.g. "disk full").
+
+ */
+ enum ReturnCode { ErrorNeedComplete = 0x1, GoOn = 0x2, ErrorButGoOn = 0x4,
+ CriticalError = 0x8 };
+ /** Initialize filter action with (english) name @p aName and
+ (internationalized) label @p aLabel. */
+ KMFilterAction(const char* aName, const QString aLabel);
+ virtual ~KMFilterAction();
+
+ /** Returns nationalized label, ie. the one which is presented in
+ the filter dialog. */
+ const QString label() const { return mLabel; }
+
+ /** Returns english name, ie. the one under which it is known in the
+ config. */
+ const QString name() const { return mName; }
+
+ /** Execute action on given message. Returns @p CriticalError if a
+ critical error has occurred (eg. disk full), @p ErrorButGoOn if
+ there was a non-critical error (e.g. invalid address in
+ 'forward' action), @p ErrorNeedComplete if a complete message
+ is required, @p GoOn if the message shall be processed by
+ further filters and @p Ok otherwise.
+ */
+ virtual ReturnCode process(KMMessage* msg) const = 0;
+
+ /** Execute an action on given message asynchronously.
+ Emits a result signal on completion.
+ */
+ virtual void processAsync(KMMessage* msg) const;
+
+ /** Determines if the action depends on the body of the message
+ */
+ virtual bool requiresBody(KMMsgBase* msgBase) const;
+
+ /** Determines whether this action is valid. But this is just a
+ quick test. Eg., actions that have a mail address as parameter
+ shouldn't try real address validation, but only check if the
+ string representation is empty. */
+ virtual bool isEmpty() const { return FALSE; }
+
+ /** Creates a widget for setting the filter action parameter. Also
+ sets the value of the widget. */
+ virtual QWidget* createParamWidget(QWidget* parent) const;
+
+ /** The filter action shall set it's parameter from the widget's
+ contents. It is allowed that the value is read by the action
+ before this function is called. */
+ virtual void applyParamWidgetValue(QWidget* paramWidget);
+
+ /** The filter action shall set it's widget's contents from it's
+ parameter. */
+ virtual void setParamWidgetValue(QWidget* paramWidget) const;
+
+ /** The filter action shall clear it's parameter widget's
+ contents. */
+ virtual void clearParamWidget(QWidget* paramWidget) const;
+
+ /** Read extra arguments from given string. */
+ virtual void argsFromString(const QString argsStr) = 0;
+
+ /** Return extra arguments as string. Must not contain newlines. */
+ virtual const QString argsAsString() const = 0;
+
+ /** Returns a translated string describing this filter for visualization
+ purposes, e.g. in the filter log. */
+ virtual const QString displayString() const = 0;
+
+ /** Called from the filter when a folder is removed. Tests if the
+ folder @p aFolder is used and changes to @p aNewFolder in this
+ case. Returns TRUE if a change was made. */
+ virtual bool folderRemoved(KMFolder* aFolder, KMFolder* aNewFolder);
+
+ /** Static function that creates a filter action of this type. */
+ static KMFilterAction* newAction();
+
+ /** Temporarily open folder. Will be closed by the next
+ KMFilterMgr::cleanup() call. */
+ static int tempOpenFolder(KMFolder* aFolder);
+
+ /** Automates the sending of MDNs from filter actions. */
+ static void sendMDN( KMMessage * msg, KMime::MDN::DispositionType d,
+ const QValueList<KMime::MDN::DispositionModifier> & m
+ =QValueList<KMime::MDN::DispositionModifier>() );
+
+private:
+ QString mName;
+ QString mLabel;
+};
+
+//=========================================================
+//
+// class KMFilterActionWithNone
+//
+//=========================================================
+
+
+/** Abstract base class for KMail's filter actions that need no
+ parameter, e.g. "Confirm Delivery". Creates an (empty) QWidget as
+ parameter widget. A subclass of this must provide at least
+ implementations for the following methods:
+
+ @li virtual KMFilterAction::ReturnCodes KMFilterAction::process
+ @li static KMFilterAction::newAction
+
+ @short Abstract base class for filter actions with no parameter.
+ @author Marc Mutz <Marc@Mutz.com>, based upon work by Stefan Taferner <taferner@kde.org>
+ @see KMFilterAction KMFilter
+
+*/
+class KMFilterActionWithNone : public KMFilterAction
+{
+public:
+ /** Initialize filter action with (english) name @p aName. This is
+ the name under which this action is known in the config file. */
+ KMFilterActionWithNone(const char* aName, const QString aLabel);
+
+ /** Read extra arguments from given string. This type of filter
+ action has no parameters, so this is a no-op. */
+ virtual void argsFromString(const QString) {};
+
+ /** Return extra arguments as string. Must not contain newlines. We
+ return QString::null, because we have no parameter. */
+ virtual const QString argsAsString() const { return QString::null; }
+
+ /** Returns a translated string describing this filter for visualization
+ purposes, e.g. in the filter log. */
+ virtual const QString displayString() const;
+};
+
+
+//=========================================================
+//
+// class KMFilterActionWithString
+//
+//=========================================================
+
+
+/** Abstract base class for KMail's filter actions that need a
+ free-form parameter, e.g. 'set transport' or 'set reply to'. Can
+ create a QLineEdit as parameter widget. A subclass of this
+ must provide at least implementations for the following methods:
+
+ @li virtual KMFilterAction::ReturnCodes KMFilterAction::process
+ @li static KMFilterAction::newAction
+
+ @short Abstract base class for filter actions with a free-form string as parameter.
+ @author Marc Mutz <Marc@Mutz.com>, based upon work by Stefan Taferner <taferner@kde.org>
+ @see KMFilterAction KMFilter
+
+*/
+class KMFilterActionWithString : public KMFilterAction
+{
+public:
+ /** Initialize filter action with (english) name @p aName. This is
+ the name under which this action is known in the config file. */
+ KMFilterActionWithString(const char* aName, const QString aLabel);
+
+ /** Determines whether this action is valid. But this is just a
+ quick test. Eg., actions that have a mail address as parameter
+ shouldn't try real address validation, but only check if the
+ string representation is empty. */
+ virtual bool isEmpty() const { return mParameter.stripWhiteSpace().isEmpty(); }
+
+ /** Creates a widget for setting the filter action parameter. Also
+ sets the value of the widget. */
+ virtual QWidget* createParamWidget(QWidget* parent) const;
+
+ /** The filter action shall set it's parameter from the widget's
+ contents. It is allowed that the value is read by the action
+ before this function is called. */
+ virtual void applyParamWidgetValue(QWidget* paramWidget);
+
+ /** The filter action shall set it's widget's contents from it's
+ parameter. */
+ virtual void setParamWidgetValue(QWidget* paramWidget) const;
+
+ /** The filter action shall clear it's parameter widget's
+ contents. */
+ virtual void clearParamWidget(QWidget* paramWidget) const;
+
+ /** Read extra arguments from given string. */
+ virtual void argsFromString(const QString argsStr);
+
+ /** Return extra arguments as string. Must not contain newlines. */
+ virtual const QString argsAsString() const;
+
+ /** Returns a translated string describing this filter for visualization
+ purposes, e.g. in the filter log. */
+ virtual const QString displayString() const;
+
+protected:
+ QString mParameter;
+};
+
+//=========================================================
+//
+// class KMFilterActionWithUOID
+//
+//=========================================================
+
+
+/** Abstract base class for KMail's filter actions that need a
+ parameter that has a UOID, e.g. "set identity". A subclass of this
+ must provide at least implementations for the following methods:
+
+ @li virtual KMFilterAction::ReturnCodes KMFilterAction::process
+ @li static KMFilterAction::newAction
+ @li the *ParamWidget* methods.
+
+ @short Abstract base class for filter actions with a free-form string as parameter.
+ @author Marc Mutz <Marc@Mutz.com>, based upon work by Stefan Taferner <taferner@kde.org>
+ @see KMFilterAction KMFilter
+
+*/
+class KMFilterActionWithUOID : public KMFilterAction
+{
+public:
+ /** Initialize filter action with (english) name @p aName. This is
+ the name under which this action is known in the config file. */
+ KMFilterActionWithUOID(const char* aName, const QString aLabel);
+
+ /** Determines whether this action is valid. But this is just a
+ quick test. Eg., actions that have a mail address as parameter
+ shouldn't try real address validation, but only check if the
+ string representation is empty. */
+ virtual bool isEmpty() const { return mParameter == 0; }
+
+ /** Read extra arguments from given string. */
+ virtual void argsFromString(const QString argsStr);
+
+ /** Return extra arguments as string. Must not contain newlines. */
+ virtual const QString argsAsString() const;
+
+ /** Returns a translated string describing this filter for visualization
+ purposes, e.g. in the filter log. */
+ virtual const QString displayString() const;
+
+protected:
+ uint mParameter;
+};
+
+//=========================================================
+//
+// class KMFilterActionWithStringList
+//
+//=========================================================
+
+
+/** Abstract base class for KMail's filter actions that need a
+ parameter which can be chosen from a fixed set, e.g. 'set
+ identity'. Can create a QComboBox as parameter widget. A
+ subclass of this must provide at least implementations for the
+ following methods:
+
+ @li virtual KMFilterAction::ReturnCodes KMFilterAction::process
+ @li static KMFilterAction::newAction
+
+ Additionally, it's constructor should populate the
+ QStringList @p mParameterList with the valid parameter
+ strings. The combobox will then contain be populated automatically
+ with those strings. The default string will be the first one.
+
+ @short Abstract base class for filter actions with a fixed set of string parameters.
+ @author Marc Mutz <Marc@Mutz.com>, based upon work by Stefan Taferner <taferner@kde.org>
+ @see KMFilterActionWithString KMFilterActionWithFolder KMFilterAction KMFilter
+
+*/
+class KMFilterActionWithStringList : public KMFilterActionWithString
+{
+public:
+ /** Initialize filter action with (english) name @p aName. This is
+ the name under which this action is known in the config file. */
+ KMFilterActionWithStringList(const char* aName, const QString aLabel);
+
+ /** Creates a widget for setting the filter action parameter. Also
+ sets the value of the widget. */
+ virtual QWidget* createParamWidget(QWidget* parent) const;
+
+ /** The filter action shall set it's parameter from the widget's
+ contents. It is allowed that the value is read by the action
+ before this function is called. */
+ virtual void applyParamWidgetValue(QWidget* paramWidget);
+
+ /** The filter action shall set it's widget's contents from it's
+ parameter. */
+ virtual void setParamWidgetValue(QWidget* paramWidget) const;
+
+ /** The filter action shall clear it's parameter widget's
+ contents. */
+ virtual void clearParamWidget(QWidget* paramWidget) const;
+
+ /** Read extra arguments from given string. */
+ virtual void argsFromString(const QString argsStr);
+
+protected:
+ QStringList mParameterList;
+};
+
+
+//=========================================================
+//
+// class KMFilterActionWithFolder
+//
+//=========================================================
+
+
+/** Abstract base class for KMail's filter actions that need a
+ mail folder as parameter, e.g. 'move into folder'. Can
+ create a QComboBox as parameter widget. A subclass of this
+ must provide at least implementations for the following methods:
+
+ @li virtual KMFilterAction::ReturnCodes KMFilterAction::process
+ @li static KMFilterAction::newAction
+
+ @short Abstract base class for filter actions with a mail folder as parameter.
+ @author Marc Mutz <Marc@Mutz.com>, based upon work by Stefan Taferner <taferner@kde.org>
+ @see KMFilterActionWithStringList KMFilterAction KMFilter
+
+*/
+
+class KMFilterActionWithFolder : public KMFilterAction
+{
+public:
+ /** Initialize filter action with (english) name @p aName. This is
+ the name under which this action is known in the config file. */
+ KMFilterActionWithFolder(const char* aName, const QString aLabel);
+
+ /** Determines whether this action is valid. But this is just a
+ quick test. Eg., actions that have a mail address as parameter
+ shouldn't try real address validation, but only check if the
+ string representation is empty. */
+ virtual bool isEmpty() const { return (!mFolder && mFolderName.isEmpty()); }
+
+ /** Creates a widget for setting the filter action parameter. Also
+ sets the value of the widget. */
+ virtual QWidget* createParamWidget(QWidget* parent) const;
+
+ /** The filter action shall set it's parameter from the widget's
+ contents. It is allowed that the value is read by the action
+ before this function is called. */
+ virtual void applyParamWidgetValue(QWidget* paramWidget);
+
+ /** The filter action shall set it's widget's contents from it's
+ parameter. */
+ virtual void setParamWidgetValue(QWidget* paramWidget) const;
+
+ /** The filter action shall clear it's parameter widget's
+ contents. */
+ virtual void clearParamWidget(QWidget* paramWidget) const;
+
+ /** Read extra arguments from given string. */
+ virtual void argsFromString(const QString argsStr);
+
+ /** Return extra arguments as string. Must not contain newlines. */
+ virtual const QString argsAsString() const;
+
+ /** Returns a translated string describing this filter for visualization
+ purposes, e.g. in the filter log. */
+ virtual const QString displayString() const;
+
+ /** Called from the filter when a folder is removed. Tests if the
+ folder @p aFolder is used and changes to @p aNewFolder in this
+ case. Returns TRUE if a change was made. */
+ virtual bool folderRemoved(KMFolder* aFolder, KMFolder* aNewFolder);
+
+protected:
+ QGuardedPtr<KMFolder> mFolder;
+ QString mFolderName;
+};
+
+//=========================================================
+//
+// class KMFilterActionWithAddress
+//
+//=========================================================
+
+
+/** Abstract base class for KMail's filter actions that need a mail
+ address as parameter, e.g. 'forward to'. Can create a
+ QComboBox (capable of completion from the address book) as
+ parameter widget. A subclass of this must provide at least
+ implementations for the following methods:
+
+ @li virtual KMFilterAction::ReturnCodes KMFilterAction::process
+ @li static KMFilterAction::newAction
+
+ @short Abstract base class for filter actions with a mail address as parameter.
+ @author Marc Mutz <Marc@Mutz.com>, based upon work by Stefan Taferner <taferner@kde.org>
+ @see KMFilterActionWithString KMFilterAction KMFilter
+
+*/
+class KMFilterActionWithAddress : public KMFilterActionWithString
+{
+public:
+ /** Initialize filter action with (english) name @p aName. This is
+ the name under which this action is known in the config file. */
+ KMFilterActionWithAddress(const char* aName, const QString aLabel);
+
+ /** Creates a widget for setting the filter action parameter. Also
+ sets the value of the widget. */
+ virtual QWidget* createParamWidget(QWidget* parent) const;
+
+ /** The filter action shall set it's parameter from the widget's
+ contents. It is allowed that the value is read by the action
+ before this function is called. */
+ virtual void applyParamWidgetValue(QWidget* paramWidget);
+
+ /** The filter action shall set it's widget's contents from it's
+ parameter. */
+ virtual void setParamWidgetValue(QWidget* paramWidget) const;
+
+ /** The filter action shall clear it's parameter widget's
+ contents. */
+ virtual void clearParamWidget(QWidget* paramWidget) const;
+};
+
+//=========================================================
+//
+// class KMFilterActionWithCommand
+//
+//=========================================================
+
+
+/** Abstract base class for KMail's filter actions that need a command
+ line as parameter, e.g. 'forward to'. Can create a QLineEdit
+ (are there better widgets in the depths of the kdelibs?) as
+ parameter widget. A subclass of this must provide at least
+ implementations for the following methods:
+
+ @li virtual KMFilterAction::ReturnCodes KMFilterAction::process
+ @li static KMFilterAction::newAction
+
+ The implementation of KMFilterAction::process should take the
+ command line specified in mParameter, make all required
+ modifications and stream the resulting command line into @p
+ mProcess. Then you can start the command with @p mProcess.start().
+
+ @short Abstract base class for filter actions with a command line as parameter.
+ @author Marc Mutz <Marc@Mutz.com>, based upon work by Stefan Taferner <taferner@kde.org>
+ @see KMFilterActionWithString KMFilterAction KMFilter KProcess
+
+*/
+class KMFilterActionWithUrl : public KMFilterAction
+{
+public:
+ /** Initialize filter action with (english) name @p aName. This is
+ the name under which this action is known in the config file. */
+ KMFilterActionWithUrl(const char* aName, const QString aLabel);
+ ~KMFilterActionWithUrl();
+ /** Determines whether this action is valid. But this is just a
+ quick test. Eg., actions that have a mail address as parameter
+ shouldn't try real address validation, but only check if the
+ string representation is empty. */
+ virtual bool isEmpty() const { return mParameter.stripWhiteSpace().isEmpty(); }
+
+ /** Creates a widget for setting the filter action parameter. Also
+ sets the value of the widget. */
+ virtual QWidget* createParamWidget(QWidget* parent) const;
+
+ /** The filter action shall set it's parameter from the widget's
+ contents. It is allowed that the value is read by the action
+ before this function is called. */
+ virtual void applyParamWidgetValue(QWidget* paramWidget);
+
+ /** The filter action shall set it's widget's contents from it's
+ parameter. */
+ virtual void setParamWidgetValue(QWidget* paramWidget) const;
+
+ /** The filter action shall clear it's parameter widget's
+ contents. */
+ virtual void clearParamWidget(QWidget* paramWidget) const;
+
+ /** Read extra arguments from given string. */
+ virtual void argsFromString(const QString argsStr);
+
+ /** Return extra arguments as string. Must not contain newlines. */
+ virtual const QString argsAsString() const;
+
+ /** Returns a translated string describing this filter for visualization
+ purposes, e.g. in the filter log. */
+ virtual const QString displayString() const;
+
+protected:
+ QString mParameter;
+};
+
+
+class KMFilterActionWithCommand : public KMFilterActionWithUrl
+{
+public:
+ /** Initialize filter action with (english) name @p aName. This is
+ the name under which this action is known in the config file. */
+ KMFilterActionWithCommand(const char* aName, const QString aLabel);
+
+ /** Creates a widget for setting the filter action parameter. Also
+ sets the value of the widget. */
+ virtual QWidget* createParamWidget(QWidget* parent) const;
+
+ /** The filter action shall set it's parameter from the widget's
+ contents. It is allowed that the value is read by the action
+ before this function is called. */
+ virtual void applyParamWidgetValue(QWidget* paramWidget);
+
+ /** The filter action shall set it's widget's contents from it's
+ parameter. */
+ virtual void setParamWidgetValue(QWidget* paramWidget) const;
+
+ /** The filter action shall clear it's parameter widget's
+ contents. */
+ virtual void clearParamWidget(QWidget* paramWidget) const;
+
+ /** Substitutes various placeholders for data from the message
+ resp. for filenames containing that data. Currently, only %n is
+ supported, where n in an integer >= 0. %n gets substituted for
+ the name of a tempfile holding the n'th message part, with n=0
+ meaning the body of the message. */
+ virtual QString substituteCommandLineArgsFor( KMMessage *aMsg, QPtrList<KTempFile> & aTempFileList ) const;
+
+ virtual ReturnCode genericProcess( KMMessage * aMsg, bool filtering ) const;
+};
+
+
+
+class KMFilterActionWithTest : public KMFilterAction
+{
+public:
+ /** Initialize filter action with (english) name @p aName. This is
+ the name under which this action is known in the config file. */
+ KMFilterActionWithTest(const char* aName, const QString aLabel);
+ ~KMFilterActionWithTest();
+ /** Determines whether this action is valid. But this is just a
+ quick test. Eg., actions that have a mail address as parameter
+ shouldn't try real address validation, but only check if the
+ string representation is empty. */
+ virtual bool isEmpty() const { return mParameter.stripWhiteSpace().isEmpty(); }
+
+ /** Creates a widget for setting the filter action parameter. Also
+ sets the value of the widget. */
+ virtual QWidget* createParamWidget(QWidget* parent) const;
+
+ /** The filter action shall set it's parameter from the widget's
+ contents. It is allowed that the value is read by the action
+ before this function is called. */
+ virtual void applyParamWidgetValue(QWidget* paramWidget);
+
+ /** The filter action shall set it's widget's contents from it's
+ parameter. */
+ virtual void setParamWidgetValue(QWidget* paramWidget) const;
+
+ /** The filter action shall clear it's parameter widget's
+ contents. */
+ virtual void clearParamWidget(QWidget* paramWidget) const;
+
+ /** Read extra arguments from given string. */
+ virtual void argsFromString(const QString argsStr);
+
+ /** Return extra arguments as string. Must not contain newlines. */
+ virtual const QString argsAsString() const;
+
+ /** Returns a translated string describing this filter for visualization
+ purposes, e.g. in the filter log. */
+ virtual const QString displayString() const;
+
+protected:
+ QString mParameter;
+};
+
+
+typedef KMFilterAction* (*KMFilterActionNewFunc)(void);
+
+
+//-----------------------------------------------------------------------------
+/** Auxiliary struct to KMFilterActionDict. */
+struct KMFilterActionDesc
+{
+ QString label, name;
+ KMFilterActionNewFunc create;
+};
+
+/** Dictionary that contains a list of all registered filter actions
+ with their creation functions. They are hard-coded into the
+ constructor. If you want to add a new KMFilterAction, make
+ sure you add the details of it in init, too.
+
+ You will be able to find a description of a KMFilterAction by
+ looking up either it's (english) name or it's (i18n) label:
+ <pre>
+ KMFilterActionDict dict;
+ // get name of the action with label "move into folder":
+ dict[i18n("move into folder")]->name; // == "transfer"
+ // create one such action:
+ KMFilterAction *action = dict["transfer"]->create();
+ </pre>
+
+ You can iterate over all known filter actions by using list.
+
+ @short List of known KMFilterAction-types.
+ @author Marc Mutz <Marc@Mutz.com>, based on work by Stefan Taferner <taferner@kde.org>
+ @see KMFilterAction KMFilterActionDesc KMFilter
+
+*/
+class KMFilterActionDict: public QDict<KMFilterActionDesc>
+{
+public:
+ KMFilterActionDict();
+
+ /** Overloaded member function, provided for convenience. Thin
+ wrapper around QDict::insert and QPtrList::insert.
+ Inserts the resulting KMFilterActionDesc
+ thrice: First with the name, then with the label as key into the
+ QDict, then into the QPtrList. For that, it creates an
+ instance of the action internally and deletes it again after
+ querying it for name and label. */
+ void insert(KMFilterActionNewFunc aNewFunc);
+
+ /** Provides read-only access to a list of all known filter
+ actions. */
+ const QPtrList<KMFilterActionDesc>& list() const { return mList; }
+
+protected:
+ /** Populate the dictionary with all known KMFilterAction
+ types. Called automatically from the constructor. */
+ virtual void init(void);
+
+private:
+ QPtrList<KMFilterActionDesc> mList;
+};
+
+#endif /*kmfilteraction_h*/
diff --git a/kmail/kmfilterdlg.cpp b/kmail/kmfilterdlg.cpp
new file mode 100644
index 00000000..29bbe037
--- /dev/null
+++ b/kmail/kmfilterdlg.cpp
@@ -0,0 +1,1354 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+// kmfilterdlg.cpp
+// Author: Marc Mutz <Marc@Mutz.com>
+// based on work by Stefan Taferner <taferner@kde.org>
+// This code is under the GPL
+
+#include <config.h>
+#include "kmfilterdlg.h"
+
+// other KMail headers:
+#include "kmsearchpatternedit.h"
+#include "kmfiltermgr.h"
+#include "kmmainwidget.h"
+#include "accountmanager.h"
+using KMail::AccountManager;
+#include "filterimporterexporter.h"
+using KMail::FilterImporterExporter;
+
+// other KDE headers:
+#include <kmessagebox.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kinputdialog.h>
+#include <kiconloader.h>
+#include <kapplication.h>
+#include <kwin.h>
+#include <kconfig.h>
+#include <kicondialog.h>
+#include <kkeybutton.h>
+#include <klistview.h>
+#include <kpushbutton.h>
+
+// other Qt headers:
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qcombobox.h>
+#include <qwidgetstack.h>
+#include <qtooltip.h>
+#include <qwhatsthis.h>
+#include <qcheckbox.h>
+#include <qhbox.h>
+#include <qvalidator.h>
+#include <qtabwidget.h>
+
+// other headers:
+#include <assert.h>
+
+
+// What's this help texts
+const char * _wt_filterlist =
+I18N_NOOP( "<qt><p>This is the list of defined filters. "
+ "They are processed top-to-bottom.</p>"
+ "<p>Click on any filter to edit it "
+ "using the controls in the right-hand half "
+ "of the dialog.</p></qt>" );
+const char * _wt_filterlist_new =
+I18N_NOOP( "<qt><p>Click this button to create a new filter.</p>"
+ "<p>The filter will be inserted just before the currently-"
+ "selected one, but you can always change that "
+ "later on.</p>"
+ "<p>If you have clicked this button accidentally, you can undo this "
+ "by clicking on the <em>Delete</em> button.</p></qt>" );
+const char * _wt_filterlist_copy =
+I18N_NOOP( "<qt><p>Click this button to copy a filter.</p>"
+ "<p>If you have clicked this button accidentally, you can undo this "
+ "by clicking on the <em>Delete</em> button.</p></qt>" );
+const char * _wt_filterlist_delete =
+I18N_NOOP( "<qt><p>Click this button to <em>delete</em> the currently-"
+ "selected filter from the list above.</p>"
+ "<p>There is no way to get the filter back once "
+ "it is deleted, but you can always leave the "
+ "dialog by clicking <em>Cancel</em> to discard the "
+ "changes made.</p></qt>" );
+const char * _wt_filterlist_top =
+I18N_NOOP( "<qt><p>Click this button to move the currently-"
+ "selected filter to the <em>top</em> of the list above.</p>"
+ "<p>This is useful since the order of the filters in the list "
+ "determines the order in which they are tried on messages: "
+ "The topmost filter gets tried first.</p></qt>" );
+const char * _wt_filterlist_up =
+I18N_NOOP( "<qt><p>Click this button to move the currently-"
+ "selected filter <em>up</em> one in the list above.</p>"
+ "<p>This is useful since the order of the filters in the list "
+ "determines the order in which they are tried on messages: "
+ "The topmost filter gets tried first.</p>"
+ "<p>If you have clicked this button accidentally, you can undo this "
+ "by clicking on the <em>Down</em> button.</p></qt>" );
+const char * _wt_filterlist_down =
+I18N_NOOP( "<qt><p>Click this button to move the currently-"
+ "selected filter <em>down</em> one in the list above.</p>"
+ "<p>This is useful since the order of the filters in the list "
+ "determines the order in which they are tried on messages: "
+ "The topmost filter gets tried first.</p>"
+ "<p>If you have clicked this button accidentally, you can undo this "
+ "by clicking on the <em>Up</em> button.</p></qt>" );
+const char * _wt_filterlist_bot =
+I18N_NOOP( "<qt><p>Click this button to move the currently-"
+ "selected filter to the <em>bottom</em> of the list above.</p>"
+ "<p>This is useful since the order of the filters in the list "
+ "determines the order in which they are tried on messages: "
+ "The topmost filter gets tried first.</p></qt>" );
+const char * _wt_filterlist_rename =
+I18N_NOOP( "<qt><p>Click this button to rename the currently-selected filter.</p>"
+ "<p>Filters are named automatically, as long as they start with "
+ "\"&lt;\".</p>"
+ "<p>If you have renamed a filter accidentally and want automatic "
+ "naming back, click this button and select <em>Clear</em> followed "
+ "by <em>OK</em> in the appearing dialog.</p></qt>" );
+const char * _wt_filterdlg_showLater =
+I18N_NOOP( "<qt><p>Check this button to force the confirmation dialog to be "
+ "displayed.</p><p>This is useful if you have defined a ruleset that tags "
+ "messages to be downloaded later. Without the possibility to force "
+ "the dialog popup, these messages could never be downloaded if no "
+ "other large messages were waiting on the server, or if you wanted to "
+ "change the ruleset to tag the messages differently.</p></qt>" );
+
+// The anchor of the filter dialog's help.
+const char * KMFilterDlgHelpAnchor = "filters-id" ;
+const char * KMPopFilterDlgHelpAnchor = "popfilters-id" ;
+
+//=============================================================================
+//
+// class KMFilterDlg (the filter dialog)
+//
+//=============================================================================
+
+KMFilterDlg::KMFilterDlg(QWidget* parent, const char* name, bool popFilter, bool createDummyFilter )
+ : KDialogBase( parent, name, false /* modality */,
+ (popFilter)? i18n("POP3 Filter Rules"): i18n("Filter Rules") /* caption*/,
+ Help|Ok|Apply|Cancel|User1|User2 /* button mask */,
+ Ok /* default btn */, false /* separator */),
+ bPopFilter(popFilter)
+{
+ KWin::setIcons( winId(), kapp->icon(), kapp->miniIcon() );
+ setHelp( (bPopFilter)? KMPopFilterDlgHelpAnchor: KMFilterDlgHelpAnchor );
+ setButtonText( User1, i18n("Import") );
+ setButtonText( User2, i18n("Export") );
+ connect( this, SIGNAL(user1Clicked()),
+ this, SLOT( slotImportFilters()) );
+ connect( this, SIGNAL(user2Clicked()),
+ this, SLOT( slotExportFilters()) );
+
+ QWidget *w = new QWidget( this );
+ setMainWidget( w );
+ QHBoxLayout *topLayout = new QHBoxLayout( w, 0, spacingHint(), "topLayout" );
+ QHBoxLayout *hbl = topLayout;
+ QVBoxLayout *vbl2 = 0;
+ QWidget *page1 = 0;
+ QWidget *page2 = 0;
+
+ mFilterList = new KMFilterListBox( i18n("Available Filters"), w, 0, bPopFilter);
+ topLayout->addWidget( mFilterList, 1 /*stretch*/ );
+
+ if(!bPopFilter) {
+ QTabWidget *tabWidget = new QTabWidget( w, "kmfd_tab" );
+ tabWidget->setMargin( KDialog::marginHint() );
+ topLayout->addWidget( tabWidget );
+
+ page1 = new QWidget( tabWidget );
+ tabWidget->addTab( page1, i18n("&General") );
+ hbl = new QHBoxLayout( page1, 0, spacingHint(), "kmfd_hbl" );
+
+ page2 = new QWidget( tabWidget );
+ tabWidget->addTab( page2, i18n("A&dvanced") );
+ vbl2 = new QVBoxLayout( page2, 0, spacingHint(), "kmfd_vbl2" );
+ }
+
+ QVBoxLayout *vbl = new QVBoxLayout( hbl, spacingHint(), "kmfd_vbl" );
+ hbl->setStretchFactor( vbl, 2 );
+
+ mPatternEdit = new KMSearchPatternEdit( i18n("Filter Criteria"), bPopFilter ? w : page1 , "spe", bPopFilter);
+ vbl->addWidget( mPatternEdit, 0, Qt::AlignTop );
+
+ if(bPopFilter){
+ mActionGroup = new KMPopFilterActionWidget( i18n("Filter Action"), w );
+ vbl->addWidget( mActionGroup, 0, Qt::AlignTop );
+
+ mGlobalsBox = new QVGroupBox(i18n("Global Options"), w);
+ mShowLaterBtn = new QCheckBox(i18n("Always &show matched 'Download Later' messages in confirmation dialog"), mGlobalsBox);
+ QWhatsThis::add( mShowLaterBtn, i18n(_wt_filterdlg_showLater) );
+ vbl->addWidget( mGlobalsBox, 0, Qt::AlignTop );
+ }
+ else {
+ QGroupBox *agb = new QGroupBox( 1 /*column*/, Vertical, i18n("Filter Actions"), page1 );
+ mActionLister = new KMFilterActionWidgetLister( agb );
+ vbl->addWidget( agb, 0, Qt::AlignTop );
+
+ mAdvOptsGroup = new QGroupBox ( 1 /*columns*/, Vertical,
+ i18n("Advanced Options"), page2);
+ {
+ QWidget *adv_w = new QWidget( mAdvOptsGroup );
+ QGridLayout *gl = new QGridLayout( adv_w, 8 /*rows*/, 3 /*cols*/,
+ 0 /*border*/, spacingHint() );
+
+ QVBoxLayout *vbl3 = new QVBoxLayout( gl, spacingHint(), "vbl3" );
+ vbl3->addStretch( 1 );
+ mApplyOnIn = new QCheckBox( i18n("Apply this filter to incoming messages:"), adv_w );
+ vbl3->addWidget( mApplyOnIn );
+ QButtonGroup *bg = new QButtonGroup( 0, "bg" );
+ bg->setExclusive( true );
+ mApplyOnForAll = new QRadioButton( i18n("from all accounts"), adv_w );
+ bg->insert( mApplyOnForAll );
+ vbl3->addWidget( mApplyOnForAll );
+ mApplyOnForTraditional = new QRadioButton( i18n("from all but online IMAP accounts"), adv_w );
+ bg->insert( mApplyOnForTraditional );
+ vbl3->addWidget( mApplyOnForTraditional );
+ mApplyOnForChecked = new QRadioButton( i18n("from checked accounts only"), adv_w );
+ bg->insert( mApplyOnForChecked );
+ vbl3->addWidget( mApplyOnForChecked );
+ vbl3->addStretch( 2 );
+
+ mAccountList = new KListView( adv_w, "accountList" );
+ mAccountList->addColumn( i18n("Account Name") );
+ mAccountList->addColumn( i18n("Type") );
+ mAccountList->setAllColumnsShowFocus( true );
+ mAccountList->setFrameStyle( QFrame::WinPanel + QFrame::Sunken );
+ mAccountList->setSorting( -1 );
+ gl->addMultiCellWidget( mAccountList, 0, 3, 1, 3 );
+
+ mApplyOnOut = new QCheckBox( i18n("Apply this filter to &sent messages"), adv_w );
+ gl->addMultiCellWidget( mApplyOnOut, 4, 4, 0, 3 );
+
+ mApplyOnCtrlJ = new QCheckBox( i18n("Apply this filter on manual &filtering"), adv_w );
+ gl->addMultiCellWidget( mApplyOnCtrlJ, 5, 5, 0, 3 );
+
+ mStopProcessingHere = new QCheckBox( i18n("If this filter &matches, stop processing here"), adv_w );
+ gl->addMultiCellWidget( mStopProcessingHere,
+ 6, 6, /*from to row*/
+ 0, 3 /*from to col*/ );
+ mConfigureShortcut = new QCheckBox( i18n("Add this filter to the Apply Filter menu"), adv_w );
+ gl->addMultiCellWidget( mConfigureShortcut, 7, 7, 0, 1 );
+ QLabel *keyButtonLabel = new QLabel( i18n( "Shortcut:" ), adv_w );
+ keyButtonLabel->setAlignment( AlignVCenter | AlignRight );
+ gl->addMultiCellWidget( keyButtonLabel, 7, 7, 2, 2 );
+ mKeyButton = new KKeyButton( adv_w, "FilterShortcutSelector" );
+ gl->addMultiCellWidget( mKeyButton, 7, 7, 3, 3 );
+ mKeyButton->setEnabled( false );
+ mConfigureToolbar = new QCheckBox( i18n("Additionally add this filter to the toolbar"), adv_w );
+ gl->addMultiCellWidget( mConfigureToolbar, 8, 8, 0, 3 );
+ mConfigureToolbar->setEnabled( false );
+
+ QHBox *hbox = new QHBox( adv_w );
+ mFilterActionLabel = new QLabel( i18n( "Icon for this filter:" ),
+ hbox );
+ mFilterActionLabel->setEnabled( false );
+
+ mFilterActionIconButton = new KIconButton( hbox );
+ mFilterActionLabel->setBuddy( mFilterActionIconButton );
+ mFilterActionIconButton->setIconType( KIcon::NoGroup, KIcon::Any, true );
+ mFilterActionIconButton->setIconSize( 16 );
+ mFilterActionIconButton->setIcon( "gear" );
+ mFilterActionIconButton->setEnabled( false );
+
+ gl->addMultiCellWidget( hbox, 9, 9, 0, 3 );
+ }
+ vbl2->addWidget( mAdvOptsGroup, 0, Qt::AlignTop );
+ }
+ // spacer:
+ vbl->addStretch( 1 );
+
+ // load the filter parts into the edit widgets
+ connect( mFilterList, SIGNAL(filterSelected(KMFilter*)),
+ this, SLOT(slotFilterSelected(KMFilter*)) );
+
+ if (bPopFilter){
+ // set the state of the global setting 'show later msgs'
+ connect( mShowLaterBtn, SIGNAL(toggled(bool)),
+ mFilterList, SLOT(slotShowLaterToggled(bool)));
+
+ // set the action in the filter when changed
+ connect( mActionGroup, SIGNAL(actionChanged(const KMPopFilterAction)),
+ this, SLOT(slotActionChanged(const KMPopFilterAction)) );
+ } else {
+ // transfer changes from the 'Apply this filter on...'
+ // combo box to the filter
+ connect( mApplyOnIn, SIGNAL(clicked()),
+ this, SLOT(slotApplicabilityChanged()) );
+ connect( mApplyOnForAll, SIGNAL(clicked()),
+ this, SLOT(slotApplicabilityChanged()) );
+ connect( mApplyOnForTraditional, SIGNAL(clicked()),
+ this, SLOT(slotApplicabilityChanged()) );
+ connect( mApplyOnForChecked, SIGNAL(clicked()),
+ this, SLOT(slotApplicabilityChanged()) );
+ connect( mApplyOnOut, SIGNAL(clicked()),
+ this, SLOT(slotApplicabilityChanged()) );
+ connect( mApplyOnCtrlJ, SIGNAL(clicked()),
+ this, SLOT(slotApplicabilityChanged()) );
+ connect( mAccountList, SIGNAL(clicked(QListViewItem*)),
+ this, SLOT(slotApplicableAccountsChanged()) );
+ connect( mAccountList, SIGNAL(spacePressed(QListViewItem*)),
+ this, SLOT(slotApplicableAccountsChanged()) );
+
+ // transfer changes from the 'stop processing here'
+ // check box to the filter
+ connect( mStopProcessingHere, SIGNAL(toggled(bool)),
+ this, SLOT(slotStopProcessingButtonToggled(bool)) );
+
+ connect( mConfigureShortcut, SIGNAL(toggled(bool)),
+ this, SLOT(slotConfigureShortcutButtonToggled(bool)) );
+
+ connect( mKeyButton, SIGNAL( capturedShortcut( const KShortcut& ) ),
+ this, SLOT( slotCapturedShortcutChanged( const KShortcut& ) ) );
+
+ connect( mConfigureToolbar, SIGNAL(toggled(bool)),
+ this, SLOT(slotConfigureToolbarButtonToggled(bool)) );
+
+ connect( mFilterActionIconButton, SIGNAL( iconChanged( QString ) ),
+ this, SLOT( slotFilterActionIconChanged( QString ) ) );
+ }
+
+ // reset all widgets here
+ connect( mFilterList, SIGNAL(resetWidgets()),
+ this, SLOT(slotReset()) );
+
+ connect( mFilterList, SIGNAL( applyWidgets() ),
+ this, SLOT( slotUpdateFilter() ) );
+
+ // support auto-naming the filter
+ connect( mPatternEdit, SIGNAL(maybeNameChanged()),
+ mFilterList, SLOT(slotUpdateFilterName()) );
+
+ // apply changes on 'Apply'
+ connect( this, SIGNAL(applyClicked()),
+ mFilterList, SLOT(slotApplyFilterChanges()) );
+
+ // apply changes on 'OK'
+ connect( this, SIGNAL(okClicked()),
+ mFilterList, SLOT(slotApplyFilterChanges()) );
+
+ // save dialog size on 'OK'
+ connect( this, SIGNAL(okClicked()),
+ this, SLOT(slotSaveSize()) );
+
+ // destruct the dialog on OK, close and Cancel
+ connect( this, SIGNAL(finished()),
+ this, SLOT(slotFinished()) );
+
+ KConfigGroup geometry( KMKernel::config(), "Geometry");
+ const char * configKey
+ = bPopFilter ? "popFilterDialogSize" : "filterDialogSize";
+ if ( geometry.hasKey( configKey ) )
+ resize( geometry.readSizeEntry( configKey ) );
+ else
+ adjustSize();
+
+ // load the filter list (emits filterSelected())
+ mFilterList->loadFilterList( createDummyFilter );
+}
+
+void KMFilterDlg::slotFinished() {
+ delayedDestruct();
+}
+
+void KMFilterDlg::slotSaveSize() {
+ KConfigGroup geometry( KMKernel::config(), "Geometry" );
+ geometry.writeEntry( bPopFilter ? "popFilterDialogSize" : "filterDialogSize", size() );
+}
+
+/** Set action of popFilter */
+void KMFilterDlg::slotActionChanged(const KMPopFilterAction aAction)
+{
+ mFilter->setAction(aAction);
+}
+
+void KMFilterDlg::slotFilterSelected( KMFilter* aFilter )
+{
+ assert( aFilter );
+
+ if (bPopFilter){
+ mActionGroup->setAction( aFilter->action() );
+ mGlobalsBox->setEnabled( true );
+ mShowLaterBtn->setChecked(mFilterList->showLaterMsgs());
+ } else {
+ mActionLister->setActionList( aFilter->actions() );
+
+ mAdvOptsGroup->setEnabled( true );
+ }
+
+ mPatternEdit->setSearchPattern( aFilter->pattern() );
+ mFilter = aFilter;
+
+ if (!bPopFilter) {
+ kdDebug(5006) << "apply on inbound == "
+ << aFilter->applyOnInbound() << endl;
+ kdDebug(5006) << "apply on outbound == "
+ << aFilter->applyOnOutbound() << endl;
+ kdDebug(5006) << "apply on explicit == "
+ << aFilter->applyOnExplicit() << endl;
+
+ // NOTE: setting these values activates the slot that sets them in
+ // the filter! So make sure we have the correct values _before_ we
+ // set the first one:
+ const bool applyOnIn = aFilter->applyOnInbound();
+ const bool applyOnForAll = aFilter->applicability() == KMFilter::All;
+ const bool applyOnTraditional = aFilter->applicability() == KMFilter::ButImap;
+ const bool applyOnOut = aFilter->applyOnOutbound();
+ const bool applyOnExplicit = aFilter->applyOnExplicit();
+ const bool stopHere = aFilter->stopProcessingHere();
+ const bool configureShortcut = aFilter->configureShortcut();
+ const bool configureToolbar = aFilter->configureToolbar();
+ const QString icon = aFilter->icon();
+ const KShortcut shortcut( aFilter->shortcut() );
+
+ mApplyOnIn->setChecked( applyOnIn );
+ mApplyOnForAll->setEnabled( applyOnIn );
+ mApplyOnForTraditional->setEnabled( applyOnIn );
+ mApplyOnForChecked->setEnabled( applyOnIn );
+ mApplyOnForAll->setChecked( applyOnForAll );
+ mApplyOnForTraditional->setChecked( applyOnTraditional );
+ mApplyOnForChecked->setChecked( !applyOnForAll && !applyOnTraditional );
+ mAccountList->setEnabled( mApplyOnForChecked->isEnabled() && mApplyOnForChecked->isChecked() );
+ slotUpdateAccountList();
+ mApplyOnOut->setChecked( applyOnOut );
+ mApplyOnCtrlJ->setChecked( applyOnExplicit );
+ mStopProcessingHere->setChecked( stopHere );
+ mConfigureShortcut->setChecked( configureShortcut );
+ mKeyButton->setShortcut( shortcut, false );
+ mConfigureToolbar->setChecked( configureToolbar );
+ mFilterActionIconButton->setIcon( icon );
+ }
+}
+
+void KMFilterDlg::slotReset()
+{
+ mFilter = 0;
+ mPatternEdit->reset();
+
+ if(bPopFilter) {
+ mActionGroup->reset();
+ mGlobalsBox->setEnabled( false );
+ } else {
+ mActionLister->reset();
+ mAdvOptsGroup->setEnabled( false );
+ slotUpdateAccountList();
+ }
+}
+
+void KMFilterDlg::slotUpdateFilter()
+{
+ mPatternEdit->updateSearchPattern();
+ if ( !bPopFilter ) {
+ mActionLister->updateActionList();
+ }
+}
+
+void KMFilterDlg::slotApplicabilityChanged()
+{
+ if ( mFilter ) {
+ mFilter->setApplyOnInbound( mApplyOnIn->isChecked() );
+ mFilter->setApplyOnOutbound( mApplyOnOut->isChecked() );
+ mFilter->setApplyOnExplicit( mApplyOnCtrlJ->isChecked() );
+ if ( mApplyOnForAll->isChecked() )
+ mFilter->setApplicability( KMFilter::All );
+ else if ( mApplyOnForTraditional->isChecked() )
+ mFilter->setApplicability( KMFilter::ButImap );
+ else if ( mApplyOnForChecked->isChecked() )
+ mFilter->setApplicability( KMFilter::Checked );
+
+ mApplyOnForAll->setEnabled( mApplyOnIn->isChecked() );
+ mApplyOnForTraditional->setEnabled( mApplyOnIn->isChecked() );
+ mApplyOnForChecked->setEnabled( mApplyOnIn->isChecked() );
+ mAccountList->setEnabled( mApplyOnForChecked->isEnabled() && mApplyOnForChecked->isChecked() );
+
+ // Advanced tab functionality - Update list of accounts this filter applies to
+ QListViewItemIterator it( mAccountList );
+ while ( it.current() ) {
+ QCheckListItem *item = dynamic_cast<QCheckListItem*>( it.current() );
+ if (item) {
+ int id = item->text( 2 ).toInt();
+ item->setOn( mFilter->applyOnAccount( id ) );
+ }
+ ++it;
+ }
+
+ kdDebug(5006) << "KMFilterDlg: setting filter to be applied at "
+ << ( mFilter->applyOnInbound() ? "incoming " : "" )
+ << ( mFilter->applyOnOutbound() ? "outgoing " : "" )
+ << ( mFilter->applyOnExplicit() ? "explicit CTRL-J" : "" )
+ << endl;
+ }
+}
+
+void KMFilterDlg::slotApplicableAccountsChanged()
+{
+ if ( mFilter && mApplyOnForChecked->isEnabled() && mApplyOnForChecked->isChecked() ) {
+ // Advanced tab functionality - Update list of accounts this filter applies to
+ QListViewItemIterator it( mAccountList );
+ while ( it.current() ) {
+ QCheckListItem *item = dynamic_cast<QCheckListItem*>( it.current() );
+ if (item) {
+ int id = item->text( 2 ).toInt();
+ mFilter->setApplyOnAccount( id, item->isOn() );
+ }
+ ++it;
+ }
+ }
+}
+
+void KMFilterDlg::slotStopProcessingButtonToggled( bool aChecked )
+{
+ if ( mFilter )
+ mFilter->setStopProcessingHere( aChecked );
+}
+
+void KMFilterDlg::slotConfigureShortcutButtonToggled( bool aChecked )
+{
+ if ( mFilter ) {
+ mFilter->setConfigureShortcut( aChecked );
+ mKeyButton->setEnabled( aChecked );
+ mConfigureToolbar->setEnabled( aChecked );
+ mFilterActionIconButton->setEnabled( aChecked );
+ mFilterActionLabel->setEnabled( aChecked );
+ }
+}
+
+void KMFilterDlg::slotCapturedShortcutChanged( const KShortcut& sc )
+{
+ KShortcut mySc(sc);
+ if ( mySc == mKeyButton->shortcut() ) return;
+ // FIXME work around a problem when reseting the shortcut via the shortcut dialog
+ // somehow the returned shortcut does not evaluate to true in KShortcut::isNull(),
+ // so we additionally have to check for an empty string
+ if ( mySc.isNull() || mySc.toString().isEmpty() )
+ mySc.clear();
+ if ( !mySc.isNull() && !( kmkernel->getKMMainWidget()->shortcutIsValid( mySc ) ) ) {
+ QString msg( i18n( "The selected shortcut is already used, "
+ "please select a different one." ) );
+ KMessageBox::sorry( this, msg );
+ } else {
+ mKeyButton->setShortcut( mySc, false );
+ if ( mFilter )
+ mFilter->setShortcut( mKeyButton->shortcut() );
+ }
+}
+
+void KMFilterDlg::slotConfigureToolbarButtonToggled( bool aChecked )
+{
+ if ( mFilter )
+ mFilter->setConfigureToolbar( aChecked );
+}
+
+void KMFilterDlg::slotFilterActionIconChanged( QString icon )
+{
+ if ( mFilter )
+ mFilter->setIcon( icon );
+}
+
+void KMFilterDlg::slotUpdateAccountList()
+{
+ mAccountList->clear();
+ QListViewItem *top = 0;
+ for( KMAccount *a = kmkernel->acctMgr()->first(); a!=0;
+ a = kmkernel->acctMgr()->next() ) {
+ QCheckListItem *listItem =
+ new QCheckListItem( mAccountList, top, a->name(), QCheckListItem::CheckBox );
+ listItem->setText( 1, a->type() );
+ listItem->setText( 2, QString( "%1" ).arg( a->id() ) );
+ if ( mFilter )
+ listItem->setOn( mFilter->applyOnAccount( a->id() ) );
+ top = listItem;
+ }
+
+ QListViewItem *listItem = mAccountList->firstChild();
+ if ( listItem ) {
+ mAccountList->setCurrentItem( listItem );
+ mAccountList->setSelected( listItem, true );
+ }
+}
+
+//=============================================================================
+//
+// class KMFilterListBox (the filter list manipulator)
+//
+//=============================================================================
+
+KMFilterListBox::KMFilterListBox( const QString & title, QWidget *parent, const char* name, bool popFilter )
+ : QGroupBox( 1, Horizontal, title, parent, name ),
+ bPopFilter(popFilter)
+{
+ mFilterList.setAutoDelete( true );
+ mIdxSelItem = -1;
+
+ //----------- the list box
+ mListBox = new QListBox(this);
+ mListBox->setMinimumWidth(150);
+ QWhatsThis::add( mListBox, i18n(_wt_filterlist) );
+
+ //----------- the first row of buttons
+ QHBox *hb = new QHBox(this);
+ hb->setSpacing(4);
+ mBtnTop = new KPushButton( QString::null, hb );
+ mBtnTop->setAutoRepeat( true );
+ mBtnTop->setIconSet( BarIconSet( "top", KIcon::SizeSmall ) );
+ mBtnTop->setMinimumSize( mBtnTop->sizeHint() * 1.2 );
+ mBtnUp = new KPushButton( QString::null, hb );
+ mBtnUp->setAutoRepeat( true );
+ mBtnUp->setIconSet( BarIconSet( "up", KIcon::SizeSmall ) );
+ mBtnUp->setMinimumSize( mBtnUp->sizeHint() * 1.2 );
+ mBtnDown = new KPushButton( QString::null, hb );
+ mBtnDown->setAutoRepeat( true );
+ mBtnDown->setIconSet( BarIconSet( "down", KIcon::SizeSmall ) );
+ mBtnDown->setMinimumSize( mBtnDown->sizeHint() * 1.2 );
+ mBtnBot = new KPushButton( QString::null, hb );
+ mBtnBot->setAutoRepeat( true );
+ mBtnBot->setIconSet( BarIconSet( "bottom", KIcon::SizeSmall ) );
+ mBtnBot->setMinimumSize( mBtnBot->sizeHint() * 1.2 );
+ QToolTip::add( mBtnTop, i18n("Top") );
+ QToolTip::add( mBtnUp, i18n("Up") );
+ QToolTip::add( mBtnDown, i18n("Down") );
+ QToolTip::add( mBtnBot, i18n("Bottom") );
+ QWhatsThis::add( mBtnTop, i18n(_wt_filterlist_top) );
+ QWhatsThis::add( mBtnUp, i18n(_wt_filterlist_up) );
+ QWhatsThis::add( mBtnDown, i18n(_wt_filterlist_down) );
+ QWhatsThis::add( mBtnBot, i18n(_wt_filterlist_bot) );
+
+ //----------- the second row of buttons
+ hb = new QHBox(this);
+ hb->setSpacing(4);
+ mBtnNew = new QPushButton( QString::null, hb );
+ mBtnNew->setPixmap( BarIcon( "filenew", KIcon::SizeSmall ) );
+ mBtnNew->setMinimumSize( mBtnNew->sizeHint() * 1.2 );
+ mBtnCopy = new QPushButton( QString::null, hb );
+ mBtnCopy->setIconSet( BarIconSet( "editcopy", KIcon::SizeSmall ) );
+ mBtnCopy->setMinimumSize( mBtnCopy->sizeHint() * 1.2 );
+ mBtnDelete = new QPushButton( QString::null, hb );
+ mBtnDelete->setIconSet( BarIconSet( "editdelete", KIcon::SizeSmall ) );
+ mBtnDelete->setMinimumSize( mBtnDelete->sizeHint() * 1.2 );
+ mBtnRename = new QPushButton( i18n("Rename..."), hb );
+ QToolTip::add( mBtnNew, i18n("New") );
+ QToolTip::add( mBtnCopy, i18n("Copy") );
+ QToolTip::add( mBtnDelete, i18n("Delete"));
+ QWhatsThis::add( mBtnNew, i18n(_wt_filterlist_new) );
+ QWhatsThis::add( mBtnCopy, i18n(_wt_filterlist_copy) );
+ QWhatsThis::add( mBtnDelete, i18n(_wt_filterlist_delete) );
+ QWhatsThis::add( mBtnRename, i18n(_wt_filterlist_rename) );
+
+
+ //----------- now connect everything
+ connect( mListBox, SIGNAL(highlighted(int)),
+ this, SLOT(slotSelected(int)) );
+ connect( mListBox, SIGNAL( doubleClicked ( QListBoxItem * )),
+ this, SLOT( slotRename()) );
+ connect( mBtnTop, SIGNAL(clicked()),
+ this, SLOT(slotTop()) );
+ connect( mBtnUp, SIGNAL(clicked()),
+ this, SLOT(slotUp()) );
+ connect( mBtnDown, SIGNAL(clicked()),
+ this, SLOT(slotDown()) );
+ connect( mBtnBot, SIGNAL(clicked()),
+ this, SLOT(slotBottom()) );
+ connect( mBtnNew, SIGNAL(clicked()),
+ this, SLOT(slotNew()) );
+ connect( mBtnCopy, SIGNAL(clicked()),
+ this, SLOT(slotCopy()) );
+ connect( mBtnDelete, SIGNAL(clicked()),
+ this, SLOT(slotDelete()) );
+ connect( mBtnRename, SIGNAL(clicked()),
+ this, SLOT(slotRename()) );
+
+ // the dialog should call loadFilterList()
+ // when all signals are connected.
+ enableControls();
+}
+
+
+void KMFilterListBox::createFilter( const QCString & field,
+ const QString & value )
+{
+ KMSearchRule *newRule = KMSearchRule::createInstance( field, KMSearchRule::FuncContains, value );
+
+ KMFilter *newFilter = new KMFilter(0, bPopFilter);
+ newFilter->pattern()->append( newRule );
+ newFilter->pattern()->setName( QString("<%1>:%2").arg( field ).arg( value) );
+
+ KMFilterActionDesc *desc = (*kmkernel->filterActionDict())["transfer"];
+ if ( desc )
+ newFilter->actions()->append( desc->create() );
+
+ insertFilter( newFilter );
+ enableControls();
+}
+
+bool KMFilterListBox::showLaterMsgs()
+{
+ return mShowLater;
+}
+
+void KMFilterListBox::slotUpdateFilterName()
+{
+ KMSearchPattern *p = mFilterList.at(mIdxSelItem)->pattern();
+ if ( !p ) return;
+
+ QString shouldBeName = p->name();
+ QString displayedName = mListBox->text( mIdxSelItem );
+
+ if ( shouldBeName.stripWhiteSpace().isEmpty() ) {
+ mFilterList.at(mIdxSelItem)->setAutoNaming( true );
+ }
+
+ if ( mFilterList.at(mIdxSelItem)->isAutoNaming() ) {
+ // auto-naming of patterns
+ if ( p->first() && !p->first()->field().stripWhiteSpace().isEmpty() )
+ shouldBeName = QString( "<%1>: %2" ).arg( p->first()->field() ).arg( p->first()->contents() );
+ else
+ shouldBeName = "<" + i18n("unnamed") + ">";
+ p->setName( shouldBeName );
+ }
+
+ if ( displayedName == shouldBeName ) return;
+
+ mListBox->blockSignals( true );
+ mListBox->changeItem( shouldBeName, mIdxSelItem );
+ mListBox->blockSignals( false );
+}
+
+void KMFilterListBox::slotShowLaterToggled(bool aOn)
+{
+ mShowLater = aOn;
+}
+
+void KMFilterListBox::slotApplyFilterChanges()
+{
+ if ( mIdxSelItem >= 0 ) {
+ emit applyWidgets();
+ slotSelected( mListBox->currentItem() );
+ }
+
+ // by now all edit widgets should have written back
+ // their widget's data into our filter list.
+
+ KMFilterMgr *fm;
+ if (bPopFilter)
+ fm = kmkernel->popFilterMgr();
+ else
+ fm = kmkernel->filterMgr();
+
+ QValueList<KMFilter*> newFilters = filtersForSaving();
+
+ if (bPopFilter)
+ fm->setShowLaterMsgs(mShowLater);
+
+ fm->setFilters( newFilters );
+ if (fm->atLeastOneOnlineImapFolderTarget()) {
+ QString str = i18n("At least one filter targets a folder on an online "
+ "IMAP account. Such filters will only be applied "
+ "when manually filtering and when filtering "
+ "incoming online IMAP mail.");
+ KMessageBox::information( this, str, QString::null,
+ "filterDlgOnlineImapCheck" );
+ }
+}
+
+QValueList<KMFilter*> KMFilterListBox::filtersForSaving() const
+{
+ const_cast<KMFilterListBox*>( this )->applyWidgets(); // signals aren't const
+ QValueList<KMFilter*> filters;
+ QStringList emptyFilters;
+ QPtrListIterator<KMFilter> it( mFilterList );
+ for ( it.toFirst() ; it.current() ; ++it ) {
+ KMFilter *f = new KMFilter( **it ); // deep copy
+ f->purify();
+ if ( !f->isEmpty() )
+ // the filter is valid:
+ filters.append( f );
+ else {
+ // the filter is invalid:
+ emptyFilters << f->name();
+ delete f;
+ }
+ }
+
+ // report on invalid filters:
+ if ( !emptyFilters.empty() ) {
+ QString msg = i18n("The following filters have not been saved because they "
+ "were invalid (e.g. containing no actions or no search "
+ "rules).");
+ KMessageBox::informationList( 0, msg, emptyFilters, QString::null,
+ "ShowInvalidFilterWarning" );
+ }
+ return filters;
+}
+
+void KMFilterListBox::slotSelected( int aIdx )
+{
+ mIdxSelItem = aIdx;
+ // QPtrList::at(i) will return 0 if i is out of range.
+ KMFilter *f = mFilterList.at(aIdx);
+ if ( f )
+ emit filterSelected( f );
+ else
+ emit resetWidgets();
+ enableControls();
+}
+
+void KMFilterListBox::slotNew()
+{
+ // just insert a new filter.
+ insertFilter( new KMFilter(0, bPopFilter) );
+ enableControls();
+}
+
+void KMFilterListBox::slotCopy()
+{
+ if ( mIdxSelItem < 0 ) {
+ kdDebug(5006) << "KMFilterListBox::slotCopy called while no filter is selected, ignoring." << endl;
+ return;
+ }
+
+ // make sure that all changes are written to the filter before we copy it
+ emit applyWidgets();
+
+ KMFilter *filter = mFilterList.at( mIdxSelItem );
+
+ // enableControls should make sure this method is
+ // never called when no filter is selected.
+ assert( filter );
+
+ // inserts a copy of the current filter.
+ insertFilter( new KMFilter( *filter ) );
+ enableControls();
+}
+
+void KMFilterListBox::slotDelete()
+{
+ if ( mIdxSelItem < 0 ) {
+ kdDebug(5006) << "KMFilterListBox::slotDelete called while no filter is selected, ignoring." << endl;
+ return;
+ }
+
+ int oIdxSelItem = mIdxSelItem;
+ mIdxSelItem = -1;
+ // unselect all
+ mListBox->selectAll( false );
+ // broadcast that all widgets let go
+ // of the filter
+ emit resetWidgets();
+
+ // remove the filter from both the filter list...
+ mFilterList.remove( oIdxSelItem );
+ // and the listbox
+ mListBox->removeItem( oIdxSelItem );
+
+ int count = (int)mListBox->count();
+ // and set the new current item.
+ if ( count > oIdxSelItem )
+ // oIdxItem is still a valid index
+ mListBox->setSelected( oIdxSelItem, true );
+ else if ( count )
+ // oIdxSelIdx is no longer valid, but the
+ // list box isn't empty
+ mListBox->setSelected( count - 1, true );
+ // the list is empty - keep index -1
+
+ enableControls();
+}
+
+void KMFilterListBox::slotTop()
+{
+ if ( mIdxSelItem < 0 ) {
+ kdDebug(5006) << "KMFilterListBox::slotTop called while no filter is selected, ignoring." << endl;
+ return;
+ }
+ if ( mIdxSelItem == 0 ) {
+ kdDebug(5006) << "KMFilterListBox::slotTop called while the _topmost_ filter is selected, ignoring." << endl;
+ return;
+ }
+
+ swapFilters( mIdxSelItem, 0 );
+ enableControls();
+}
+
+void KMFilterListBox::slotUp()
+{
+ if ( mIdxSelItem < 0 ) {
+ kdDebug(5006) << "KMFilterListBox::slotUp called while no filter is selected, ignoring." << endl;
+ return;
+ }
+ if ( mIdxSelItem == 0 ) {
+ kdDebug(5006) << "KMFilterListBox::slotUp called while the _topmost_ filter is selected, ignoring." << endl;
+ return;
+ }
+
+ swapNeighbouringFilters( mIdxSelItem, mIdxSelItem - 1 );
+ enableControls();
+}
+
+void KMFilterListBox::slotDown()
+{
+ if ( mIdxSelItem < 0 ) {
+ kdDebug(5006) << "KMFilterListBox::slotDown called while no filter is selected, ignoring." << endl;
+ return;
+ }
+ if ( mIdxSelItem == (int)mListBox->count() - 1 ) {
+ kdDebug(5006) << "KMFilterListBox::slotDown called while the _last_ filter is selected, ignoring." << endl;
+ return;
+ }
+
+ swapNeighbouringFilters( mIdxSelItem, mIdxSelItem + 1);
+ enableControls();
+}
+
+void KMFilterListBox::slotBottom()
+{
+ if ( mIdxSelItem < 0 ) {
+ kdDebug(5006) << "KMFilterListBox::slotBottom called while no filter is selected, ignoring." << endl;
+ return;
+ }
+ if ( mIdxSelItem == (int)mListBox->count() - 1 ) {
+ kdDebug(5006) << "KMFilterListBox::slotBottom called while the _last_ filter is selected, ignoring." << endl;
+ return;
+ }
+
+ swapFilters( mIdxSelItem, mListBox->count()-1 );
+ enableControls();
+}
+
+void KMFilterListBox::slotRename()
+{
+ if ( mIdxSelItem < 0 ) {
+ kdDebug(5006) << "KMFilterListBox::slotRename called while no filter is selected, ignoring." << endl;
+ return;
+ }
+
+ bool okPressed = false ;
+ KMFilter *filter = mFilterList.at( mIdxSelItem );
+
+ // enableControls should make sure this method is
+ // never called when no filter is selected.
+ assert( filter );
+
+ // allow empty names - those will turn auto-naming on again
+ QValidator *validator = new QRegExpValidator( QRegExp( ".*" ), 0 );
+ QString newName = KInputDialog::getText
+ (
+ i18n("Rename Filter"),
+ i18n("Rename filter \"%1\" to:\n(leave the field empty for automatic naming)")
+ .arg( filter->pattern()->name() ) /*label*/,
+ filter->pattern()->name() /* initial value */,
+ &okPressed, topLevelWidget(), 0, validator
+ );
+ delete validator;
+
+ if ( !okPressed ) return;
+
+ if ( newName.isEmpty() ) {
+ // bait for slotUpdateFilterName to
+ // use automatic naming again.
+ filter->pattern()->setName( "<>" );
+ filter->setAutoNaming( true );
+ } else {
+ filter->pattern()->setName( newName );
+ filter->setAutoNaming( false );
+ }
+
+ slotUpdateFilterName();
+}
+
+void KMFilterListBox::enableControls()
+{
+ bool theFirst = ( mIdxSelItem == 0 );
+ bool theLast = ( mIdxSelItem >= (int)mFilterList.count() - 1 );
+ bool aFilterIsSelected = ( mIdxSelItem >= 0 );
+
+ mBtnTop->setEnabled( aFilterIsSelected && !theFirst );
+ mBtnUp->setEnabled( aFilterIsSelected && !theFirst );
+ mBtnDown->setEnabled( aFilterIsSelected && !theLast );
+ mBtnBot->setEnabled( aFilterIsSelected && !theLast );
+ mBtnCopy->setEnabled( aFilterIsSelected );
+ mBtnDelete->setEnabled( aFilterIsSelected );
+ mBtnRename->setEnabled( aFilterIsSelected );
+
+ if ( aFilterIsSelected )
+ mListBox->ensureCurrentVisible();
+}
+
+void KMFilterListBox::loadFilterList( bool createDummyFilter )
+{
+ assert(mListBox);
+ setEnabled( false );
+ emit resetWidgets();
+ // we don't want the insertion to
+ // cause flicker in the edit widgets.
+ blockSignals( true );
+
+ // clear both lists
+ mFilterList.clear();
+ mListBox->clear();
+
+ const KMFilterMgr *manager = 0;
+ if(bPopFilter)
+ {
+ mShowLater = kmkernel->popFilterMgr()->showLaterMsgs();
+ manager = kmkernel->popFilterMgr();
+ }
+ else
+ {
+ manager = kmkernel->filterMgr();
+ }
+ Q_ASSERT( manager );
+
+ QValueListConstIterator<KMFilter*> it;
+ for ( it = manager->filters().constBegin() ; it != manager->filters().constEnd() ; ++it ) {
+ mFilterList.append( new KMFilter( **it ) ); // deep copy
+ mListBox->insertItem( (*it)->pattern()->name() );
+ }
+
+ blockSignals( false );
+ setEnabled( true );
+
+ // create an empty filter when there's none, to avoid a completely
+ // disabled dialog (usability tests indicated that the new-filter
+ // button is too hard to find that way):
+ if ( !mListBox->count() && createDummyFilter )
+ slotNew();
+
+ if ( mListBox->count() > 0 )
+ mListBox->setSelected( 0, true );
+
+ enableControls();
+}
+
+void KMFilterListBox::insertFilter( KMFilter* aFilter )
+{
+ // must be really a filter...
+ assert( aFilter );
+
+ // if mIdxSelItem < 0, QListBox::insertItem will append.
+ mListBox->insertItem( aFilter->pattern()->name(), mIdxSelItem );
+ if ( mIdxSelItem < 0 ) {
+ // none selected -> append
+ mFilterList.append( aFilter );
+ mListBox->setSelected( mListBox->count() - 1, true );
+ // slotSelected( mListBox->count() - 1 );
+ } else {
+ // insert just before selected
+ mFilterList.insert( mIdxSelItem, aFilter );
+ mListBox->setSelected( mIdxSelItem, true );
+ // slotSelected( mIdxSelItem );
+ }
+
+}
+
+void KMFilterListBox::appendFilter( KMFilter* aFilter )
+{
+ mFilterList.append( aFilter );
+ mListBox->insertItem( aFilter->pattern()->name(), -1 );
+}
+
+void KMFilterListBox::swapNeighbouringFilters( int untouchedOne, int movedOne )
+{
+ // must be neighbours...
+ assert( untouchedOne - movedOne == 1 || movedOne - untouchedOne == 1 );
+
+ // untouchedOne is at idx. to move it down(up),
+ // remove item at idx+(-)1 w/o deleting it.
+ QListBoxItem *item = mListBox->item( movedOne );
+ mListBox->takeItem( item );
+ // now selected item is at idx(idx-1), so
+ // insert the other item at idx, ie. above(below).
+ mListBox->insertItem( item, untouchedOne );
+
+ KMFilter* filter = mFilterList.take( movedOne );
+ mFilterList.insert( untouchedOne, filter );
+
+ mIdxSelItem += movedOne - untouchedOne;
+}
+
+void KMFilterListBox::swapFilters( int from, int to )
+{
+ QListBoxItem *item = mListBox->item( from );
+ mListBox->takeItem( item );
+ mListBox->insertItem( item, to );
+
+ KMFilter* filter = mFilterList.take( from );
+ mFilterList.insert( to, filter );
+
+ mIdxSelItem = to;
+ mListBox->setCurrentItem( mIdxSelItem );
+ mListBox->setSelected( mIdxSelItem, true );
+}
+
+//=============================================================================
+//
+// class KMFilterActionWidget
+//
+//=============================================================================
+
+KMFilterActionWidget::KMFilterActionWidget( QWidget *parent, const char* name )
+ : QHBox( parent, name )
+{
+ int i;
+ mActionList.setAutoDelete( true );
+
+ mComboBox = new QComboBox( false , this );
+ assert( mComboBox );
+ mWidgetStack = new QWidgetStack(this);
+ assert( mWidgetStack );
+
+ setSpacing( 4 );
+
+ QPtrListIterator<KMFilterActionDesc> it ( kmkernel->filterActionDict()->list() );
+ for ( i=0, it.toFirst() ; it.current() ; ++it, ++i ) {
+ //create an instance:
+ KMFilterAction *a = (*it)->create();
+ // append to the list of actions:
+ mActionList.append( a );
+ // add parameter widget to widget stack:
+ mWidgetStack->addWidget( a->createParamWidget( mWidgetStack ), i );
+ // add (i18n-ized) name to combo box
+ mComboBox->insertItem( (*it)->label );
+ }
+ // widget for the case where no action is selected.
+ mWidgetStack->addWidget( new QLabel( i18n("Please select an action."), mWidgetStack ), i );
+ mWidgetStack->raiseWidget(i);
+ mComboBox->insertItem( " " );
+ mComboBox->setCurrentItem(i);
+
+ // don't show scroll bars.
+ mComboBox->setSizeLimit( mComboBox->count() );
+ // layout management:
+ // o the combo box is not to be made larger than it's sizeHint(),
+ // the parameter widget should grow instead.
+ // o the whole widget takes all space horizontally, but is fixed vertically.
+ mComboBox->adjustSize();
+ mComboBox->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
+ setSizePolicy( QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ) );
+ updateGeometry();
+
+ // redirect focus to the filter action combo box
+ setFocusProxy( mComboBox );
+
+ // now connect the combo box and the widget stack
+ connect( mComboBox, SIGNAL(activated(int)),
+ mWidgetStack, SLOT(raiseWidget(int)) );
+}
+
+void KMFilterActionWidget::setAction( const KMFilterAction* aAction )
+{
+ int i=0;
+ bool found = false ;
+ int count = mComboBox->count() - 1 ; // last entry is the empty one
+ QString label = ( aAction ) ? aAction->label() : QString::null ;
+
+ // find the index of typeOf(aAction) in mComboBox
+ // and clear the other widgets on the way.
+ for ( ; i < count ; i++ )
+ if ( aAction && mComboBox->text(i) == label ) {
+ //...set the parameter widget to the settings
+ // of aAction...
+ aAction->setParamWidgetValue( mWidgetStack->widget(i) );
+ //...and show the correct entry of
+ // the combo box
+ mComboBox->setCurrentItem(i); // (mm) also raise the widget, but doesn't
+ mWidgetStack->raiseWidget(i);
+ found = true;
+ } else // clear the parameter widget
+ mActionList.at(i)->clearParamWidget( mWidgetStack->widget(i) );
+ if ( found ) return;
+
+ // not found, so set the empty widget
+ mComboBox->setCurrentItem( count ); // last item
+ mWidgetStack->raiseWidget( count) ;
+}
+
+KMFilterAction * KMFilterActionWidget::action()
+{
+ // look up the action description via the label
+ // returned by QComboBox::currentText()...
+ KMFilterActionDesc *desc = (*kmkernel->filterActionDict())[ mComboBox->currentText() ];
+ if ( desc ) {
+ // ...create an instance...
+ KMFilterAction *fa = desc->create();
+ if ( fa ) {
+ // ...and apply the setting of the parameter widget.
+ fa->applyParamWidgetValue( mWidgetStack->visibleWidget() );
+ return fa;
+ }
+ }
+
+ return 0;
+}
+
+//=============================================================================
+//
+// class KMFilterActionWidgetLister (the filter action editor)
+//
+//=============================================================================
+
+KMFilterActionWidgetLister::KMFilterActionWidgetLister( QWidget *parent, const char* name )
+ : KWidgetLister( 1, FILTER_MAX_ACTIONS, parent, name )
+{
+ mActionList = 0;
+}
+
+KMFilterActionWidgetLister::~KMFilterActionWidgetLister()
+{
+}
+
+void KMFilterActionWidgetLister::setActionList( QPtrList<KMFilterAction> *aList )
+{
+ assert ( aList );
+
+ if ( mActionList )
+ regenerateActionListFromWidgets();
+
+ mActionList = aList;
+
+ ((QWidget*)parent())->setEnabled( true );
+
+ if ( aList->count() == 0 ) {
+ slotClear();
+ return;
+ }
+
+ int superfluousItems = (int)mActionList->count() - mMaxWidgets ;
+ if ( superfluousItems > 0 ) {
+ kdDebug(5006) << "KMFilterActionWidgetLister: Clipping action list to "
+ << mMaxWidgets << " items!" << endl;
+
+ for ( ; superfluousItems ; superfluousItems-- )
+ mActionList->removeLast();
+ }
+
+ // set the right number of widgets
+ setNumberOfShownWidgetsTo( mActionList->count() );
+
+ // load the actions into the widgets
+ QPtrListIterator<KMFilterAction> aIt( *mActionList );
+ QPtrListIterator<QWidget> wIt( mWidgetList );
+ for ( aIt.toFirst(), wIt.toFirst() ;
+ aIt.current() && wIt.current() ; ++aIt, ++wIt )
+ ((KMFilterActionWidget*)(*wIt))->setAction( (*aIt) );
+}
+
+void KMFilterActionWidgetLister::reset()
+{
+ if ( mActionList )
+ regenerateActionListFromWidgets();
+
+ mActionList = 0;
+ slotClear();
+ ((QWidget*)parent())->setEnabled( false );
+}
+
+QWidget* KMFilterActionWidgetLister::createWidget( QWidget *parent )
+{
+ return new KMFilterActionWidget(parent);
+}
+
+void KMFilterActionWidgetLister::clearWidget( QWidget *aWidget )
+{
+ if ( aWidget )
+ ((KMFilterActionWidget*)aWidget)->setAction(0);
+}
+
+void KMFilterActionWidgetLister::regenerateActionListFromWidgets()
+{
+ if ( !mActionList ) return;
+
+ mActionList->clear();
+
+ QPtrListIterator<QWidget> it( mWidgetList );
+ for ( it.toFirst() ; it.current() ; ++it ) {
+ KMFilterAction *a = ((KMFilterActionWidget*)(*it))->action();
+ if ( a )
+ mActionList->append( a );
+ }
+
+}
+
+//=============================================================================
+//
+// class KMPopFilterActionWidget
+//
+//=============================================================================
+
+KMPopFilterActionWidget::KMPopFilterActionWidget( const QString& title, QWidget *parent, const char* name )
+ : QVButtonGroup( title, parent, name )
+{
+ mActionMap[Down] = new QRadioButton( i18n("&Download mail"), this );
+ mActionMap[Later] = new QRadioButton( i18n("Download mail la&ter"), this );
+ mActionMap[Delete] = new QRadioButton( i18n("D&elete mail from server"), this );
+ mIdMap[id(mActionMap[Later])] = Later;
+ mIdMap[id(mActionMap[Down])] = Down;
+ mIdMap[id(mActionMap[Delete])] = Delete;
+
+ connect( this, SIGNAL(clicked(int)),
+ this, SLOT( slotActionClicked(int)) );
+}
+
+void KMPopFilterActionWidget::setAction( KMPopFilterAction aAction )
+{
+ if( aAction == NoAction)
+ {
+ aAction = Later;
+ }
+
+ mAction = aAction;
+
+ blockSignals( true );
+ if(!mActionMap[aAction]->isChecked())
+ {
+ mActionMap[aAction]->setChecked( true );
+ }
+ blockSignals( false );
+
+ setEnabled( true );
+}
+
+KMPopFilterAction KMPopFilterActionWidget::action()
+{
+ return mAction;
+}
+
+void KMPopFilterActionWidget::slotActionClicked(int aId)
+{
+ emit actionChanged(mIdMap[aId]);
+ setAction(mIdMap[aId]);
+}
+
+void KMPopFilterActionWidget::reset()
+{
+ blockSignals( true );
+ mActionMap[Down]->setChecked( true );
+ blockSignals( false );
+
+ setEnabled( false );
+}
+
+void KMFilterDlg::slotImportFilters()
+{
+ FilterImporterExporter importer( this, bPopFilter );
+ QValueList<KMFilter*> filters = importer.importFilters();
+ // FIXME message box how many were imported?
+ if (filters.isEmpty()) return;
+
+ QValueListConstIterator<KMFilter*> it;
+
+ for ( it = filters.constBegin() ; it != filters.constEnd() ; ++it ) {
+ mFilterList->appendFilter( *it ); // no need to deep copy, ownership passes to the list
+ }
+}
+
+void KMFilterDlg::slotExportFilters()
+{
+ FilterImporterExporter exporter( this, bPopFilter );
+ QValueList<KMFilter*> filters = mFilterList->filtersForSaving();
+ exporter.exportFilters( filters );
+ QValueList<KMFilter*>::iterator it;
+ for ( it = filters.begin(); it != filters.end(); ++it )
+ delete *it;
+}
+
+#include "kmfilterdlg.moc"
diff --git a/kmail/kmfilterdlg.h b/kmail/kmfilterdlg.h
new file mode 100644
index 00000000..9a12b6be
--- /dev/null
+++ b/kmail/kmfilterdlg.h
@@ -0,0 +1,422 @@
+/* Filter Dialog
+ * Author: Marc Mutz <Marc@Mutz.com>,
+ * based upon work by Stefan Taferner <taferner@kde.org>
+ * This code is under GPL
+ */
+#ifndef kmfilterdlg_h
+#define kmfilterdlg_h
+
+#include "kmfilter.h"
+#include "kmfilteraction.h"
+
+#include <kwidgetlister.h>
+
+#include <kdialogbase.h>
+
+#include <qvgroupbox.h>
+#include <qgroupbox.h>
+#include <qhbox.h>
+#include <qstring.h>
+#include <qptrlist.h>
+#include <qradiobutton.h>
+#include <qvbuttongroup.h>
+#include <qmap.h>
+
+class KMSearchPatternEdit;
+class QListBox;
+class QPushButton;
+class QComboBox;
+class QWidgetStack;
+class QCheckBox;
+class KIconButton;
+class KKeyButton;
+class KListView;
+
+
+/** This is a complex widget that is used to manipulate KMail's filter
+ list. It consists of an internal list of filters, which is a deep
+ copy of the list KMFilterMgr manages, a QListBox displaying that list,
+ and a few buttons used to create new filters, delete them, rename them
+ and change the order of filters.
+
+ It does not provide means to change the actual filter (besides the
+ name), but relies on auxiliary widgets (KMSearchPatternEdit
+ and KMFilterActionEdit) to do that.
+
+ Communication with this widget is quite easy: simply create an
+ instance, connect the signals filterSelected, resetWidgets
+ and applyWidgets with a slot that does the right thing and there
+ you go...
+
+ This widget will operate on it's own copy of the filter list as
+ long as you don't call slotApplyFilterChanges. It will then
+ transfer the altered filter list back to KMFilterMgr.
+
+ @short A complex widget that allows managing a list of KMFilter's.
+ @author Marc Mutz <Marc@Mutz.com>, based upon work by Stefan Taferner <taferner@kde.org>.
+ @see KMFilter KMFilterDlg KMFilterActionEdit KMSearchPatternEdit
+
+ */
+class KMFilterListBox : public QGroupBox
+{
+ Q_OBJECT
+public:
+ /** Constuctor. */
+ KMFilterListBox( const QString & title, QWidget* parent=0, const char* name=0, bool popFilter = false);
+
+ /** Called from KMFilterDlg. Creates a new filter and presets
+ the first rule with "field equals value". It's there mainly to
+ support "rapid filter creation" from a context menu. You should
+ instead call KMFilterMgr::createFilter.
+ @see KMFilterMgr::createFilter KMFilterDlg::createFilter
+ */
+ void createFilter( const QCString & field, const QString & value );
+
+ /** Loads the filter list and selects the first filter. Should be
+ called when all signals are connected properly. If createDummyFilter
+ is true, an empty filter is created to improve the usability of the
+ dialog in case no filter has been defined so far.*/
+ void loadFilterList( bool createDummyFilter );
+
+ /** Returns wheather the global option 'Show Later Msgs' is set or not */
+ bool showLaterMsgs();
+
+ void insertFilter( KMFilter* aFilter );
+
+ void appendFilter( KMFilter* aFilter );
+
+ /** Returns a list of _copies_ of the current list of filters.
+ * The list owns the contents and thus the caller needs to clean them
+ * up. */
+ QValueList<KMFilter*> filtersForSaving() const;
+
+signals:
+ /** Emitted when a new filter has been selected by the user or if
+ the current filter has changed after a 'new' or 'delete'
+ operation. */
+ void filterSelected( KMFilter* filter );
+
+ /** Emitted when this widget wants the edit widgets to let go of
+ their filter reference. Everyone holding a reference to a filter
+ should update it from the contents of the widgets used to edit
+ it and set their internal reference to 0. */
+ void resetWidgets();
+
+ /** Emitted when this widget wants the edit widgets to apply the changes
+ to the current filter. */
+ void applyWidgets();
+
+public slots:
+ /** Called when the name of a filter might have changed (e.g.
+ through changing the first rule in KMSearchPatternEdit).
+ Updates the corresponding entry in the
+ listbox and (if necessary) auto-names the filter. */
+ void slotUpdateFilterName();
+ /** Called when the user clicks either 'Apply' or 'OK' in
+ KMFilterDlg. Updates the filter list in the KMFilterMgr. */
+ void slotApplyFilterChanges();
+ /** Called when the user toggles the 'Show Download Later Msgs'
+ Checkbox in the Global Options section */
+ void slotShowLaterToggled(bool aOn);
+
+protected slots:
+ /** Called when the user clicks on a filter in the filter
+ list. Calculates the corresponding filter and emits the
+ filterSelected signal. */
+ void slotSelected( int aIdx );
+ /** Called when the user clicks the 'New' button. Creates a new
+ empty filter just before the current one. */
+ void slotNew();
+ /** Called when the user clicks the 'Copy' button. Creates a copy
+ of the current filter and inserts it just before the current one. */
+ void slotCopy();
+ /** Called when the user clicks the 'Delete' button. Deletes the
+ current filter. */
+ void slotDelete();
+ /** Called when the user clicks the 'Top' button. Moves the current
+ filter to the top line. */
+ void slotTop();
+ /** Called when the user clicks the 'Up' button. Moves the current
+ filter up one line. */
+ void slotUp();
+ /** Called when the user clicks the 'Down' button. Moves the current
+ filter down one line. */
+ void slotDown();
+ /** Called when the user clicks the 'Bottom' button. Moves the current
+ filter to the bottom line. */
+ void slotBottom();
+ /** Called when the user clicks the 'Rename' button. Pops up a
+ dialog prompting to enter the new name. */
+ void slotRename();
+
+protected:
+ /** The deep copy of the filter list. */
+ QPtrList<KMFilter> mFilterList;
+ /** The listbox displaying the filter list. */
+ QListBox *mListBox;
+ /** The various action buttons. */
+ QPushButton *mBtnNew, *mBtnCopy, *mBtnDelete, *mBtnRename;
+ QPushButton *mBtnTop, *mBtnUp, *mBtnDown, *mBtnBot;
+ /** The index of the currently selected item. */
+ int mIdxSelItem;
+ bool mShowLater;
+private:
+ void enableControls();
+
+ void swapFilters( int from, int to );
+ void swapNeighbouringFilters( int untouchedOne, int movedOne );
+ bool bPopFilter;
+};
+
+
+/** This widgets allows to edit a single KMFilterAction (in fact
+ any derived class that is registered in
+ KMFilterActionDict). It consists of a combo box which allows to
+ select the type of actions this widget should act upon and a
+ QWidgetStack, which holds the parameter widgets for the different
+ rule types.
+
+ You can load a KMFilterAction into this widget with setAction,
+ and retrieve the result of user action with action.
+ The widget will copy it's setting into the corresponding
+ parameter widget. For that, it internally creates an instance of
+ every KMFilterAction in KMFilterActionDict and asks each
+ one to create a parameter widget. The parameter widgets are put on
+ the widget stack and are raised when their corresponding action
+ type is selected in the combo box.
+
+ @short A widget to edit a single KMFilterAction.
+ @author Marc Mutz <Marc@Mutz.com>
+ @see KMFilterAction KMFilter KMFilterActionWidgetLister
+
+ */
+class KMFilterActionWidget : public QHBox
+{
+ Q_OBJECT
+public:
+ /** Constructor. Creates a filter action widget with no type
+ selected. */
+ KMFilterActionWidget( QWidget* parent=0, const char* name=0 );
+
+ /** Set an action. The action's type is determined and the
+ corresponding widget it loaded with @p aAction's parameters and
+ then raised. If @ aAction is 0, the widget is cleared. */
+ void setAction( const KMFilterAction * aAction );
+ /** Retrieve the action. This method is necessary because the type
+ of actions can change during editing. Therefore the widget
+ always creates a new action object from the data in the combo
+ box and the widget stack and returns that. */
+ KMFilterAction *action();
+
+private:
+ /** This list holds an instance of every KMFilterAction
+ subclass. The only reason that these 'slave' actions exist is
+ that they are 'forced' to create parameter widgets for the
+ widget stack and to clear them on setAction. */
+ QPtrList<KMFilterAction> mActionList;
+ /** The combo box that contains the labels of all KMFilterActions.
+ It's @p activated(int) signal is internally
+ connected to the @p raiseWidget(int) slot of @p mWidgetStack. */
+ QComboBox *mComboBox;
+ /** The widget stack that holds all the parameter widgets for the
+ filter actions. */
+ QWidgetStack *mWidgetStack;
+};
+
+class KMPopFilterActionWidget : public QVButtonGroup
+{
+ Q_OBJECT
+public:
+ KMPopFilterActionWidget( const QString &title, QWidget* parent=0, const char* name=0 );
+ void setAction( KMPopFilterAction aAction );
+ KMPopFilterAction action();
+
+public slots:
+ void reset();
+
+private slots:
+ void slotActionClicked(int aId);
+
+private:
+ KMPopFilterAction mAction;
+ KMFilter mFilter;
+ QMap<KMPopFilterAction, QRadioButton*> mActionMap;
+ QMap<int, KMPopFilterAction> mIdMap;
+
+signals: // Signals
+ void actionChanged(const KMPopFilterAction aAction);
+};
+
+class KMFilterActionWidgetLister : public KWidgetLister
+{
+ Q_OBJECT
+public:
+ KMFilterActionWidgetLister( QWidget *parent=0, const char* name=0 );
+
+ virtual ~KMFilterActionWidgetLister();
+
+ void setActionList( QPtrList<KMFilterAction> * aList );
+
+ /** Updates the action list according to the current widget values */
+ void updateActionList() { regenerateActionListFromWidgets(); }
+
+public slots:
+ void reset();
+
+protected:
+ virtual void clearWidget( QWidget *aWidget );
+ virtual QWidget* createWidget( QWidget *parent );
+
+private:
+ void regenerateActionListFromWidgets();
+ QPtrList<KMFilterAction> *mActionList;
+
+};
+
+
+
+/** The filter dialog. This is a non-modal dialog used to manage
+ KMail's filters. It should only be called through KMFilterMgr::openDialog.
+ The dialog consists of three main parts:
+
+ @li The KMFilterListBox in the left half allows the user to
+ select a filter to be displayed using the widgets on the right
+ half. It also has buttons to delete filters, add new ones, to
+ rename them and to change their order (maybe you will be able to
+ move the filters around by dragging later, and to optimise the
+ filters by trying to apply them to all locally available
+ KMMessage in turn and thus profiling which filters (and which
+ rules of the search patterns) matches most often and sorting the
+ filter/rules list according to the results, but I first want the
+ basic functionality in place).
+
+ @li The KMSearchPatternEdit in the upper-right quarter allows
+ the user to modify the filter criteria.
+
+ @li The KMFilterActionEdit in the lower-right quarter allows
+ the user to select the actions that will be executed for any
+ message that matches the search pattern.
+
+ @li (tbi) There will be another widget that will allow the user to
+ select to which folders the filter may be applied and whether it
+ should be applied on outbound or inbound message transfers or both
+ or none (meaning the filter is only applied when the user
+ explicitly hits CTRL-J). I'm not sure whether there should be a
+ per-folder filter list or a single list where you can select the
+ names of folders this rule will be applied to.
+
+ Upon creating the dialog, a (deep) copy of the current filter list
+ is made by KMFilterListBox. The changed filters are local to
+ KMFilterListBox until the user clicks the 'Apply' button.
+
+ NOTE: Though this dialog is non-modal, it completely ignores all
+ the stuff that goes on behind the scenes with folders esp. folder
+ creation, move and create. The widgets that depend on the filter
+ list and the filters that use folders as parameters are not
+ updated as you expect. I hope this will change sometime soon.
+
+ KMFilterDlg supports the creation of new filters through context
+ menues, dubbed "rapid filters". Call KMFilterMgr::createFilter
+ to use this. That call will be delivered to this dialog, which in
+ turn delivers it to the KMFilterListBox.
+
+ If you change the (DocBook) anchor for the filter dialog help,
+ make sure to change @p const @p QString @p KMFilterDlgHelpAnchor
+ in kmfilterdlg.cpp accordingly.
+
+ @short The filter dialog.
+ @author Marc Mutz <Marc@Mutz.com>, based upon work by Stefan Taferner <taferner@kde.org>.
+ @see KMFilter KMFilterActionEdit KMSearchPatternEdit KMFilterListBox
+
+ */
+
+class KMFilterDlg: public KDialogBase
+{
+ Q_OBJECT
+public:
+ /** Create the filter dialog. The only class which should be able to
+ do this is KMFilterMgr. This ensures that there is only a
+ single filter dialog */
+ KMFilterDlg( QWidget* parent=0, const char* name=0, bool popFilter=false,
+ bool createDummyFilter=true );
+
+ /** Called from KMFilterMgr. Creates a new filter and presets
+ the first rule with "field equals value". Internally forwarded
+ to KMFilterListBox::createFilter. You should instead call
+ KMFilterMgr::createFilter. */
+ void createFilter( const QCString & field, const QString & value )
+ { mFilterList->createFilter( field, value ); }
+
+public slots:
+ /** Internally connected to KMFilterListBox::filterSelected.
+ Just does a simple check and then calls
+ KMSearchPatternEdit::setSearchPattern and
+ KMFilterActionEdit::setActionList. */
+ void slotFilterSelected(KMFilter * aFilter);
+ /** Action for popFilter */
+ void slotActionChanged(const KMPopFilterAction aAction);
+
+protected slots:
+ void slotApplicabilityChanged();
+ void slotApplicableAccountsChanged();
+ void slotStopProcessingButtonToggled( bool aChecked );
+ void slotConfigureShortcutButtonToggled( bool aChecked );
+ void slotCapturedShortcutChanged( const KShortcut& );
+ void slotConfigureToolbarButtonToggled( bool aChecked );
+ void slotFilterActionIconChanged( QString icon );
+ void slotReset();
+ void slotUpdateFilter();
+ void slotSaveSize();
+ // called when the dialog is closed (finished)
+ void slotFinished();
+ // update the list of accounts shown in the advanced tab
+ void slotUpdateAccountList();
+
+
+ /** Called when a user clicks the import filters button. Pops up
+ * a dialog asking the user which file to import from and which
+ * of the filters in that file to import. */
+ void slotImportFilters();
+
+ /** Called when a user clicks the export filters button. Pops up
+ * a dialog asking the user which filters to export and which
+ * file to export to. */
+ void slotExportFilters();
+
+protected:
+ /** The widget that contains the ListBox showing the filters, and
+ the controls to remove filters, add new ones and to change their
+ order. */
+ KMFilterListBox *mFilterList;
+ /** The widget that allows editing of the filter pattern. */
+ KMSearchPatternEdit *mPatternEdit;
+ /** The widget that allows editing of the filter actions. */
+ KMFilterActionWidgetLister *mActionLister;
+ /** The widget that allows editing the popFilter actions. */
+ KMPopFilterActionWidget *mActionGroup;
+ /** Lets the user select whether to apply this filter on
+ inbound/outbound messages, both, or only on explicit CTRL-J. */
+ QCheckBox *mApplyOnIn, *mApplyOnOut, *mApplyOnCtrlJ;
+ /** For a filter applied to inbound messages selects whether to apply
+ this filter to all accounts or to selected accounts only. */
+ QRadioButton *mApplyOnForAll, *mApplyOnForTraditional, *mApplyOnForChecked;
+ /** ListView that shows the accounts in the advanced tab */
+ KListView *mAccountList;
+
+ QCheckBox *mStopProcessingHere;
+ QCheckBox *mConfigureShortcut;
+ QCheckBox *mConfigureToolbar;
+ QLabel *mFilterActionLabel;
+ KIconButton *mFilterActionIconButton;
+ KKeyButton *mKeyButton;
+ QGroupBox *mAdvOptsGroup;
+ QVGroupBox *mGlobalsBox;
+ QCheckBox *mShowLaterBtn;
+
+ KMFilter *mFilter;
+ bool bPopFilter;
+};
+
+
+
+#endif /*kmfilterdlg_h*/
diff --git a/kmail/kmfiltermgr.cpp b/kmail/kmfiltermgr.cpp
new file mode 100644
index 00000000..c75404f8
--- /dev/null
+++ b/kmail/kmfiltermgr.cpp
@@ -0,0 +1,509 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+// kmfiltermgr.cpp
+
+// my header
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "kmfiltermgr.h"
+
+// other kmail headers
+#include "filterlog.h"
+using KMail::FilterLog;
+#include "kmfilterdlg.h"
+#include "kmfolderindex.h"
+#include "filterimporterexporter.h"
+using KMail::FilterImporterExporter;
+#include "kmfoldermgr.h"
+#include "kmmsgdict.h"
+#include "messageproperty.h"
+using KMail::MessageProperty;
+
+// other KDE headers
+#include <kdebug.h>
+#include <klocale.h>
+#include <kconfig.h>
+
+// other Qt headers
+#include <qregexp.h>
+#include <qvaluevector.h>
+
+// other headers
+#include <assert.h>
+
+
+//-----------------------------------------------------------------------------
+KMFilterMgr::KMFilterMgr( bool popFilter )
+ : mEditDialog( 0 ),
+ bPopFilter( popFilter ),
+ mShowLater( false ),
+ mDirtyBufferedFolderTarget( true ),
+ mBufferedFolderTarget( true ),
+ mRefCount( 0 )
+{
+ if (bPopFilter)
+ kdDebug(5006) << "pPopFilter set" << endl;
+ connect( kmkernel, SIGNAL( folderRemoved( KMFolder* ) ),
+ this, SLOT( slotFolderRemoved( KMFolder* ) ) );
+}
+
+
+//-----------------------------------------------------------------------------
+KMFilterMgr::~KMFilterMgr()
+{
+ deref( true );
+ writeConfig( false );
+ clear();
+}
+
+void KMFilterMgr::clear()
+{
+ mDirtyBufferedFolderTarget = true;
+ for ( QValueListIterator<KMFilter*> it = mFilters.begin() ;
+ it != mFilters.end() ; ++it ) {
+ delete *it;
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMFilterMgr::readConfig(void)
+{
+ KConfig* config = KMKernel::config();
+ clear();
+
+ if (bPopFilter) {
+ KConfigGroupSaver saver(config, "General");
+ mShowLater = config->readNumEntry("popshowDLmsgs",0);
+ }
+ mFilters = FilterImporterExporter::readFiltersFromConfig( config, bPopFilter );
+}
+
+//-----------------------------------------------------------------------------
+void KMFilterMgr::writeConfig(bool withSync)
+{
+ KConfig* config = KMKernel::config();
+
+ // Now, write out the new stuff:
+ FilterImporterExporter::writeFiltersToConfig( mFilters, config, bPopFilter );
+ KConfigGroupSaver saver(config, "General");
+ if (bPopFilter)
+ config->writeEntry("popshowDLmsgs", mShowLater);
+
+ if (withSync) config->sync();
+}
+
+int KMFilterMgr::processPop( KMMessage * msg ) const {
+ for ( QValueListConstIterator<KMFilter*> it = mFilters.constBegin();
+ it != mFilters.constEnd() ; ++it )
+ if ( (*it)->pattern()->matches( msg ) )
+ return (*it)->action();
+ return NoAction;
+}
+
+bool KMFilterMgr::beginFiltering(KMMsgBase *msgBase) const
+{
+ if (MessageProperty::filtering( msgBase ))
+ return false;
+ MessageProperty::setFiltering( msgBase, true );
+ MessageProperty::setFilterFolder( msgBase, 0 );
+ if ( FilterLog::instance()->isLogging() ) {
+ FilterLog::instance()->addSeparator();
+ }
+ return true;
+}
+
+int KMFilterMgr::moveMessage(KMMessage *msg) const
+{
+ if (MessageProperty::filterFolder(msg)->moveMsg( msg ) == 0) {
+ if ( kmkernel->folderIsTrash( MessageProperty::filterFolder( msg )))
+ KMFilterAction::sendMDN( msg, KMime::MDN::Deleted );
+ } else {
+ kdDebug(5006) << "KMfilterAction - couldn't move msg" << endl;
+ return 2;
+ }
+ return 0;
+}
+
+void KMFilterMgr::endFiltering(KMMsgBase *msgBase) const
+{
+ KMFolder *parent = msgBase->parent();
+ if ( parent ) {
+ if ( parent == MessageProperty::filterFolder( msgBase ) ) {
+ parent->take( parent->find( msgBase ) );
+ }
+ else if ( ! MessageProperty::filterFolder( msgBase ) ) {
+ int index = parent->find( msgBase );
+ KMMessage *msg = parent->getMsg( index );
+ parent->take( index );
+ parent->addMsgKeepUID( msg );
+ }
+ }
+ MessageProperty::setFiltering( msgBase, false );
+}
+
+int KMFilterMgr::process( KMMessage * msg, const KMFilter * filter ) {
+ if ( !msg || !filter || !beginFiltering( msg ))
+ return 1;
+ bool stopIt = false;
+ int result = 1;
+
+ if ( FilterLog::instance()->isLogging() ) {
+ QString logText( i18n( "<b>Evaluating filter rules:</b> " ) );
+ logText.append( filter->pattern()->asString() );
+ FilterLog::instance()->add( logText, FilterLog::patternDesc );
+ }
+
+ if (filter->pattern()->matches( msg )) {
+ if ( FilterLog::instance()->isLogging() ) {
+ FilterLog::instance()->add( i18n( "<b>Filter rules have matched.</b>" ),
+ FilterLog::patternResult );
+ }
+ if (filter->execActions( msg, stopIt ) == KMFilter::CriticalError)
+ return 2;
+
+ KMFolder *folder = MessageProperty::filterFolder( msg );
+
+ endFiltering( msg );
+ if (folder) {
+ tempOpenFolder( folder );
+ result = folder->moveMsg( msg );
+ }
+ } else {
+ endFiltering( msg );
+ result = 1;
+ }
+ return result;
+}
+
+int KMFilterMgr::process( Q_UINT32 serNum, const KMFilter *filter )
+{
+ bool stopIt = false;
+ int result = 1;
+
+ if ( !filter )
+ return 1;
+
+ if ( isMatching( serNum, filter ) ) {
+ KMFolder *folder = 0;
+ int idx = -1;
+ // get the message with the serNum
+ KMMsgDict::instance()->getLocation( serNum, &folder, &idx );
+ if ( !folder || ( idx == -1 ) || ( idx >= folder->count() ) ) {
+ return 1;
+ }
+ KMFolderOpener openFolder(folder, "filtermgr");
+ KMMsgBase *msgBase = folder->getMsgBase( idx );
+ bool unGet = !msgBase->isMessage();
+ KMMessage *msg = folder->getMsg( idx );
+ // do the actual filtering stuff
+ if ( !msg || !beginFiltering( msg ) ) {
+ if ( unGet )
+ folder->unGetMsg( idx );
+ return 1;
+ }
+ if ( filter->execActions( msg, stopIt ) == KMFilter::CriticalError ) {
+ if ( unGet )
+ folder->unGetMsg( idx );
+ return 2;
+ }
+
+ KMFolder *targetFolder = MessageProperty::filterFolder( msg );
+
+ endFiltering( msg );
+ if ( targetFolder ) {
+ tempOpenFolder( targetFolder );
+ msg->setTransferInProgress( false );
+ result = targetFolder->moveMsg( msg );
+ msg->setTransferInProgress( true );
+ }
+ if ( unGet )
+ folder->unGetMsg( idx );
+ } else {
+ result = 1;
+ }
+ return result;
+}
+
+int KMFilterMgr::process( KMMessage * msg, FilterSet set,
+ bool account, uint accountId ) {
+ if ( bPopFilter )
+ return processPop( msg );
+
+ if ( set == NoSet ) {
+ kdDebug(5006) << "KMFilterMgr: process() called with not filter set selected"
+ << endl;
+ return 1;
+ }
+
+ bool stopIt = false;
+ bool atLeastOneRuleMatched = false;
+
+ if (!beginFiltering( msg ))
+ return 1;
+ for ( QValueListConstIterator<KMFilter*> it = mFilters.constBegin();
+ !stopIt && it != mFilters.constEnd() ; ++it ) {
+
+ if ( ( ( (set&Inbound) && (*it)->applyOnInbound() ) &&
+ ( !account ||
+ ( account && (*it)->applyOnAccount( accountId ) ) ) ) ||
+ ( (set&Outbound) && (*it)->applyOnOutbound() ) ||
+ ( (set&Explicit) && (*it)->applyOnExplicit() ) ) {
+ // filter is applicable
+
+ if ( FilterLog::instance()->isLogging() ) {
+ QString logText( i18n( "<b>Evaluating filter rules:</b> " ) );
+ logText.append( (*it)->pattern()->asString() );
+ FilterLog::instance()->add( logText, FilterLog::patternDesc );
+ }
+ if ( (*it)->pattern()->matches( msg ) ) {
+ // filter matches
+ if ( FilterLog::instance()->isLogging() ) {
+ FilterLog::instance()->add( i18n( "<b>Filter rules have matched.</b>" ),
+ FilterLog::patternResult );
+ }
+ atLeastOneRuleMatched = true;
+ // execute actions:
+ if ( (*it)->execActions(msg, stopIt) == KMFilter::CriticalError )
+ return 2;
+ }
+ }
+ }
+
+ KMFolder *folder = MessageProperty::filterFolder( msg );
+ /* endFilter does a take() and addButKeepUID() to ensure the changed
+ * message is on disk. This is unnessecary if nothing matched, so just
+ * reset state and don't update the listview at all. */
+ if ( atLeastOneRuleMatched )
+ endFiltering( msg );
+ else
+ MessageProperty::setFiltering( msg, false );
+ if (folder) {
+ tempOpenFolder( folder );
+ folder->moveMsg(msg);
+ return 0;
+ }
+ return 1;
+}
+
+bool KMFilterMgr::isMatching( Q_UINT32 serNum, const KMFilter *filter )
+{
+ bool result = false;
+ if ( FilterLog::instance()->isLogging() ) {
+ QString logText( i18n( "<b>Evaluating filter rules:</b> " ) );
+ logText.append( filter->pattern()->asString() );
+ FilterLog::instance()->add( logText, FilterLog::patternDesc );
+ }
+ if ( filter->pattern()->matches( serNum ) ) {
+ if ( FilterLog::instance()->isLogging() ) {
+ FilterLog::instance()->add( i18n( "<b>Filter rules have matched.</b>" ),
+ FilterLog::patternResult );
+ }
+ result = true;
+ }
+ return result;
+}
+
+bool KMFilterMgr::atLeastOneFilterAppliesTo( unsigned int accountID ) const
+{
+ QValueListConstIterator<KMFilter*> it = mFilters.constBegin();
+ for ( ; it != mFilters.constEnd() ; ++it ) {
+ if ( (*it)->applyOnAccount( accountID ) ) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool KMFilterMgr::atLeastOneIncomingFilterAppliesTo( unsigned int accountID ) const
+{
+ QValueListConstIterator<KMFilter*> it = mFilters.constBegin();
+ for ( ; it != mFilters.constEnd() ; ++it ) {
+ if ( (*it)->applyOnInbound() && (*it)->applyOnAccount( accountID ) ) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool KMFilterMgr::atLeastOneOnlineImapFolderTarget()
+{
+ if (!mDirtyBufferedFolderTarget)
+ return mBufferedFolderTarget;
+
+ mDirtyBufferedFolderTarget = false;
+
+ QValueListConstIterator<KMFilter*> it = mFilters.constBegin();
+ for ( ; it != mFilters.constEnd() ; ++it ) {
+ KMFilter *filter = *it;
+ QPtrListIterator<KMFilterAction> jt( *filter->actions() );
+ for ( jt.toFirst() ; jt.current() ; ++jt ) {
+ KMFilterActionWithFolder *f = dynamic_cast<KMFilterActionWithFolder*>(*jt);
+ if (!f)
+ continue;
+ QString name = f->argsAsString();
+ KMFolder *folder = kmkernel->imapFolderMgr()->findIdString( name );
+ if (folder) {
+ mBufferedFolderTarget = true;
+ return true;
+ }
+ }
+ }
+ mBufferedFolderTarget = false;
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+void KMFilterMgr::ref(void)
+{
+ mRefCount++;
+}
+
+//-----------------------------------------------------------------------------
+void KMFilterMgr::deref(bool force)
+{
+ if (!force)
+ mRefCount--;
+ if (mRefCount < 0)
+ mRefCount = 0;
+ if (mRefCount && !force)
+ return;
+ QValueVector< KMFolder *>::const_iterator it;
+ for ( it = mOpenFolders.constBegin(); it != mOpenFolders.constEnd(); ++it )
+ (*it)->close("filtermgr");
+ mOpenFolders.clear();
+}
+
+
+//-----------------------------------------------------------------------------
+int KMFilterMgr::tempOpenFolder(KMFolder* aFolder)
+{
+ assert( aFolder );
+
+ int rc = aFolder->open("filermgr");
+ if (rc) return rc;
+
+ mOpenFolders.append( aFolder );
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFilterMgr::openDialog( QWidget *, bool checkForEmptyFilterList )
+{
+ if( !mEditDialog )
+ {
+ //
+ // We can't use the parent as long as the dialog is modeless
+ // and there is one shared dialog for all top level windows.
+ //
+ mEditDialog = new KMFilterDlg( 0, "filterdialog", bPopFilter,
+ checkForEmptyFilterList );
+ }
+ mEditDialog->show();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFilterMgr::createFilter( const QCString & field, const QString & value )
+{
+ openDialog( 0, false );
+ mEditDialog->createFilter( field, value );
+}
+
+
+//-----------------------------------------------------------------------------
+const QString KMFilterMgr::createUniqueName( const QString & name )
+{
+ QString uniqueName = name;
+ int counter = 0;
+ bool found = true;
+
+ while ( found ) {
+ found = false;
+ for ( QValueListConstIterator<KMFilter*> it = mFilters.constBegin();
+ it != mFilters.constEnd(); ++it ) {
+ if ( !( (*it)->name().compare( uniqueName ) ) ) {
+ found = true;
+ ++counter;
+ uniqueName = name;
+ uniqueName += QString( " (" ) + QString::number( counter )
+ + QString( ")" );
+ break;
+ }
+ }
+ }
+ return uniqueName;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFilterMgr::appendFilters( const QValueList<KMFilter*> &filters,
+ bool replaceIfNameExists )
+{
+ mDirtyBufferedFolderTarget = true;
+ beginUpdate();
+ if ( replaceIfNameExists ) {
+ QValueListConstIterator<KMFilter*> it1 = filters.constBegin();
+ for ( ; it1 != filters.constEnd() ; ++it1 ) {
+ QValueListConstIterator<KMFilter*> it2 = mFilters.constBegin();
+ for ( ; it2 != mFilters.constEnd() ; ++it2 ) {
+ if ( (*it1)->name() == (*it2)->name() ) {
+ mFilters.remove( (*it2) );
+ it2 = mFilters.constBegin();
+ }
+ }
+ }
+ }
+ mFilters += filters;
+ writeConfig( true );
+ endUpdate();
+}
+
+void KMFilterMgr::setFilters( const QValueList<KMFilter*> &filters )
+{
+ beginUpdate();
+ clear();
+ mFilters = filters;
+ writeConfig( true );
+ endUpdate();
+}
+
+void KMFilterMgr::slotFolderRemoved( KMFolder * aFolder )
+{
+ folderRemoved( aFolder, 0 );
+}
+
+//-----------------------------------------------------------------------------
+bool KMFilterMgr::folderRemoved(KMFolder* aFolder, KMFolder* aNewFolder)
+{
+ mDirtyBufferedFolderTarget = true;
+ bool rem = false;
+ QValueListConstIterator<KMFilter*> it = mFilters.constBegin();
+ for ( ; it != mFilters.constEnd() ; ++it )
+ if ( (*it)->folderRemoved(aFolder, aNewFolder) )
+ rem = true;
+
+ return rem;
+}
+
+
+//-----------------------------------------------------------------------------
+#ifndef NDEBUG
+void KMFilterMgr::dump(void) const
+{
+
+ QValueListConstIterator<KMFilter*> it = mFilters.constBegin();
+ for ( ; it != mFilters.constEnd() ; ++it ) {
+ kdDebug(5006) << (*it)->asString() << endl;
+ }
+}
+#endif
+
+//-----------------------------------------------------------------------------
+void KMFilterMgr::endUpdate(void)
+{
+ emit filterListUpdated();
+}
+
+#include "kmfiltermgr.moc"
diff --git a/kmail/kmfiltermgr.h b/kmail/kmfiltermgr.h
new file mode 100644
index 00000000..0c38b4a8
--- /dev/null
+++ b/kmail/kmfiltermgr.h
@@ -0,0 +1,198 @@
+/*
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ *
+ * 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 kmfiltermgr_h
+#define kmfiltermgr_h
+
+#include "kmfilteraction.h" // for KMFilterAction::ReturnCode
+#include "kmfolder.h"
+
+#include <qguardedptr.h>
+#include <qobject.h>
+
+class KMFilter;
+class KMFilterDlg;
+template <typename T> class QValueVector;
+template <typename T> class QValueList;
+
+class KMFilterMgr: public QObject
+{
+ Q_OBJECT
+
+public:
+ KMFilterMgr(bool popFilter = false);
+ virtual ~KMFilterMgr();
+
+ /** Clears the list of filters and deletes them. */
+ void clear();
+
+ enum FilterSet { NoSet = 0x0, Inbound = 0x1, Outbound = 0x2, Explicit = 0x4,
+ All = Inbound|Outbound|Explicit };
+
+ /** Reload filter rules from config file. */
+ void readConfig(void);
+
+ /** Store filter rules in config file. */
+ void writeConfig(bool withSync=TRUE);
+
+ /** Open an edit dialog. If checkForEmptyFilterList is true, an empty filter
+ is created to improve the visibility of the dialog in case no filter
+ has been defined so far. */
+ void openDialog( QWidget *parent, bool checkForEmptyFilterList = true );
+
+ /** Open an edit dialog, create a new filter and preset the first
+ rule with "field equals value" */
+ void createFilter( const QCString & field, const QString & value );
+
+ bool beginFiltering(KMMsgBase *msgBase) const;
+ int moveMessage(KMMessage *msg) const;
+ void endFiltering(KMMsgBase *msgBase) const;
+
+ /**
+ * Returns whether at least one filter applies to this account,
+ * which means that mail must be downloaded in order to be filtered,
+ * for example;
+ * */
+ bool atLeastOneFilterAppliesTo( unsigned int accountID ) const;
+ /**
+ * Returns whether at least one incoming filter applies to this account,
+ * which means that mail must be downloaded in order to be filtered,
+ * for example;
+ * */
+ bool atLeastOneIncomingFilterAppliesTo( unsigned int accountID ) const;
+ /** Returns whether at least one filter targets a folder on an
+ * online IMAP account.
+ * */
+ bool atLeastOneOnlineImapFolderTarget();
+
+ /** Check for existing filters with the &p name and extend the
+ "name" to "name (i)" until no match is found for i=1..n */
+ const QString createUniqueName( const QString & name );
+
+ /** Append the list of filters to the current list of filters and
+ write everything back into the configuration. The filter manager
+ takes ownership of the filters in the list. */
+ void appendFilters( const QValueList<KMFilter*> &filters,
+ bool replaceIfNameExists = false );
+
+ /** Replace the list of filters under control of the filter manager.
+ * The manager takes ownershipt of the filters. */
+ void setFilters( const QValueList<KMFilter*> &filters );
+
+ /** @return the list of filters managed by this object */
+ const QValueList<KMFilter*> & filters() const { return mFilters; }
+
+ /** Process given message by applying the filter rules one by
+ one. You can select which set of filters (incoming or outgoing)
+ should be used.
+
+ @param msg The message to process.
+ @param aSet Select the filter set to use.
+ @param account true if an account id is specified else false
+ @param accountId The id of the KMAccount that the message was
+ retrieved from
+ @return 2 if a critical error occurred (eg out of disk space)
+ 1 if the caller is still owner of the message and
+ 0 otherwise. If the caller does not any longer own the message
+ he *must* not delete the message or do similar stupid things. ;-)
+ */
+ int process( KMMessage * msg, FilterSet aSet = Inbound,
+ bool account = false, uint accountId = 0 );
+
+ /** For ad-hoc filters. Applies @p filter to @p msg. Return codes
+ are as with the above method.
+ @deprecated Use int process( quint32, const KMFilter * )
+ */
+ int process( KMMessage * msg, const KMFilter * filter );
+
+ /** For ad-hoc filters. Applies @p filter to message with @p serNum .
+ Return codes are as with the above method. */
+ int process( Q_UINT32 serNum, const KMFilter * filter );
+
+ void cleanup();
+ /** Increment the reference count for the filter manager.
+ Call this method before processing messages with process() */
+ void ref();
+ /** Decrement the reference count for the filter manager.
+ Call this method after processing messages with process().
+ Shall be called after all messages are processed.
+ If the reference count is zero then this method closes all folders
+ that have been temporarily opened with tempOpenFolder(). */
+ void deref(bool force = false);
+
+ /** Open given folder and mark it as temporarily open. The folder
+ will be closed upon next call of cleanip(). This method is
+ usually only called from within filter actions during process().
+ Returns returncode from KMFolder::open() call. */
+ int tempOpenFolder(KMFolder* aFolder);
+
+ /** Called at the beginning of an filter list update. Currently a
+ no-op */
+ void beginUpdate() {}
+
+ /** Called at the end of an filter list update. */
+ void endUpdate();
+
+ /** Output all rules to stdout */
+#ifndef NDEBUG
+ void dump() const;
+#endif
+
+ /** Called from the folder manager when a folder is removed.
+ Tests if the folder aFolder is used in any action. Changes
+ to aNewFolder folder in this case. Returns TRUE if a change
+ occurred. */
+ bool folderRemoved(KMFolder* aFolder, KMFolder* aNewFolder);
+
+ /** Called from the folder manager when a new folder has been
+ created. Forwards this to the filter dialog if that is open. */
+ void folderCreated(KMFolder*) {}
+
+ /** Set the global option 'Show Download Later Messages' */
+ void setShowLaterMsgs( bool show ) {
+ mShowLater = show;
+ }
+
+ /** Get the global option 'Show Download Later Messages' */
+ bool showLaterMsgs() const {
+ return mShowLater;
+ }
+public slots:
+ void slotFolderRemoved( KMFolder *aFolder );
+
+signals:
+ void filterListUpdated();
+
+private:
+ int processPop( KMMessage *msg ) const;
+ /** Find out if a message matches the filter criteria */
+ bool isMatching( Q_UINT32 serNum, const KMFilter *filter );
+
+ QGuardedPtr<KMFilterDlg> mEditDialog;
+ QValueVector<KMFolder *> mOpenFolders;
+ QValueList<KMFilter *> mFilters;
+ bool bPopFilter;
+ bool mShowLater;
+ bool mDirtyBufferedFolderTarget;
+ bool mBufferedFolderTarget;
+
+ int mRefCount;
+};
+
+#endif /*kmfiltermgr_h*/
diff --git a/kmail/kmfolder.cpp b/kmail/kmfolder.cpp
new file mode 100644
index 00000000..71b9cfc8
--- /dev/null
+++ b/kmail/kmfolder.cpp
@@ -0,0 +1,881 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ *
+ * 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 <config.h>
+
+#include "kmfolder.h"
+#include "kmfolderdir.h"
+#include "kmfoldermbox.h"
+#include "folderstorage.h"
+#include "kmfoldercachedimap.h"
+#include "kmfoldersearch.h"
+#include "kmfolderimap.h"
+#include "kmfoldermgr.h"
+#include <libkpimidentities/identitymanager.h>
+#include <libkpimidentities/identity.h>
+#include "expirejob.h"
+#include "compactionjob.h"
+#include "kmfoldertree.h"
+#include "kmailicalifaceimpl.h"
+#include "kmaccount.h"
+
+#include <errno.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kshortcut.h>
+#include <kmessagebox.h>
+#include <qfile.h>
+#include <qfileinfo.h>
+
+
+KMFolder::KMFolder( KMFolderDir* aParent, const QString& aFolderName,
+ KMFolderType aFolderType, bool withIndex, bool exportedSernums )
+ : KMFolderNode( aParent, aFolderName ), mStorage(0),
+ mChild( 0 ),
+ mIsSystemFolder( false ),
+ mHasIndex( withIndex ),
+ mExportsSernums( exportedSernums ),
+ mMoveInProgress( false ),
+ mExpireMessages( false ), mUnreadExpireAge( 28 ),
+ mReadExpireAge( 14 ), mUnreadExpireUnits( expireNever ),
+ mReadExpireUnits( expireNever ),
+ mExpireAction( ExpireDelete ),
+ mUseCustomIcons( false ), mMailingListEnabled( false ),
+ mAcctList( 0 ),
+ mIdentity( 0 ), // default identity
+ mPutRepliesInSameFolder( false ),
+ mIgnoreNewMail( false )
+{
+ if( aFolderType == KMFolderTypeCachedImap )
+ mStorage = new KMFolderCachedImap( this, aFolderName.latin1() );
+ else if( aFolderType == KMFolderTypeImap )
+ mStorage = new KMFolderImap( this, aFolderName.latin1() );
+ else if( aFolderType == KMFolderTypeMaildir )
+ mStorage = new KMFolderMaildir( this, aFolderName.latin1() );
+ else if( aFolderType == KMFolderTypeSearch )
+ mStorage = new KMFolderSearch( this, aFolderName.latin1() );
+ else
+ mStorage = new KMFolderMbox( this, aFolderName.latin1() );
+
+ assert( mStorage );
+
+ QFileInfo dirinfo;
+ dirinfo.setFile( mStorage->location() );
+ if ( !dirinfo.exists() ) {
+ int rc = mStorage->create();
+ QString msg = i18n("<qt>Error while creating file <b>%1</b>:<br>%2</qt>").arg(aFolderName).arg(strerror(rc));
+ if ( rc ) {
+ KMessageBox::information(0, msg);
+ }
+ }
+
+ if ( aParent ) {
+ connect( mStorage, SIGNAL( msgAdded( KMFolder*, Q_UINT32 ) ),
+ aParent->manager(), SIGNAL( msgAdded( KMFolder*, Q_UINT32 ) ) );
+ connect( mStorage, SIGNAL( msgRemoved( KMFolder*, Q_UINT32 ) ),
+ parent()->manager(), SIGNAL( msgRemoved( KMFolder*, Q_UINT32 ) ) );
+ connect( this, SIGNAL( msgChanged( KMFolder*, Q_UINT32, int ) ),
+ parent()->manager(), SIGNAL( msgChanged( KMFolder*, Q_UINT32, int ) ) );
+ connect( this, SIGNAL( msgHeaderChanged( KMFolder*, int ) ),
+ parent()->manager(), SIGNAL( msgHeaderChanged( KMFolder*, int ) ) );
+ connect( mStorage, SIGNAL( invalidated( KMFolder* ) ),
+ parent()->manager(), SIGNAL( folderInvalidated( KMFolder* ) ) );
+ }
+
+ // Resend all mStorage signals
+ connect( mStorage, SIGNAL( changed() ), SIGNAL( changed() ) );
+ connect( mStorage, SIGNAL( cleared() ), SIGNAL( cleared() ) );
+ connect( mStorage, SIGNAL( expunged( KMFolder* ) ),
+ SIGNAL( expunged( KMFolder* ) ) );
+ connect( mStorage, SIGNAL( nameChanged() ), SIGNAL( nameChanged() ) );
+ connect( mStorage, SIGNAL( msgRemoved( KMFolder*, Q_UINT32 ) ),
+ SIGNAL( msgRemoved( KMFolder*, Q_UINT32 ) ) );
+ connect( mStorage, SIGNAL( msgRemoved( int, QString ) ),
+ SIGNAL( msgRemoved( int, QString ) ) );
+ connect( mStorage, SIGNAL( msgRemoved( KMFolder* ) ),
+ SIGNAL( msgRemoved( KMFolder* ) ) );
+ connect( mStorage, SIGNAL( msgAdded( int ) ), SIGNAL( msgAdded( int ) ) );
+ connect( mStorage, SIGNAL( msgAdded( KMFolder*, Q_UINT32 ) ),
+ SIGNAL( msgAdded( KMFolder*, Q_UINT32 ) ) );
+ connect( mStorage, SIGNAL( msgChanged( KMFolder*, Q_UINT32 , int ) ),
+ SIGNAL( msgChanged( KMFolder*, Q_UINT32 , int ) ) );
+ connect( mStorage, SIGNAL( msgHeaderChanged( KMFolder*, int ) ),
+ SIGNAL( msgHeaderChanged( KMFolder*, int ) ) );
+ connect( mStorage, SIGNAL( statusMsg( const QString& ) ),
+ SIGNAL( statusMsg( const QString& ) ) );
+ connect( mStorage, SIGNAL( numUnreadMsgsChanged( KMFolder* ) ),
+ SIGNAL( numUnreadMsgsChanged( KMFolder* ) ) );
+ connect( mStorage, SIGNAL( removed( KMFolder*, bool ) ),
+ SIGNAL( removed( KMFolder*, bool ) ) );
+
+ connect( mStorage, SIGNAL( contentsTypeChanged( KMail::FolderContentsType ) ),
+ this, SLOT( slotContentsTypeChanged( KMail::FolderContentsType ) ) );
+
+ connect( mStorage, SIGNAL( folderSizeChanged() ),
+ this, SLOT( slotFolderSizeChanged() ) );
+
+ //FIXME: Centralize all the readConfig calls somehow - Zack
+ // Meanwhile, readConfig must be done before registerWithMessageDict, since
+ // that one can call writeConfig in some circumstances - David
+ mStorage->readConfig();
+
+ // trigger from here, since it needs a fully constructed FolderStorage
+ if ( mExportsSernums )
+ mStorage->registerWithMessageDict();
+ if ( !mHasIndex )
+ mStorage->setAutoCreateIndex( false );
+
+ if ( mId == 0 && aParent )
+ mId = aParent->manager()->createId();
+}
+
+KMFolder::~KMFolder()
+{
+ mStorage->close( "~KMFolder", true );
+ delete mAcctList;
+ if ( mHasIndex ) mStorage->deregisterFromMessageDict();
+ delete mStorage;
+}
+
+void KMFolder::readConfig( KConfig* config )
+{
+ if ( !config->readEntry("SystemLabel").isEmpty() )
+ mSystemLabel = config->readEntry("SystemLabel");
+ mExpireMessages = config->readBoolEntry("ExpireMessages", false);
+ mReadExpireAge = config->readNumEntry("ReadExpireAge", 3);
+ mReadExpireUnits = (ExpireUnits)config->readNumEntry("ReadExpireUnits", expireMonths);
+ mUnreadExpireAge = config->readNumEntry("UnreadExpireAge", 12);
+ mUnreadExpireUnits = (ExpireUnits)config->readNumEntry("UnreadExpireUnits", expireNever);
+ mExpireAction = config->readEntry("ExpireAction", "Delete") == "Move" ? ExpireMove : ExpireDelete;
+ mExpireToFolderId = config->readEntry("ExpireToFolder");
+
+ mUseCustomIcons = config->readBoolEntry("UseCustomIcons", false );
+ mNormalIconPath = config->readEntry("NormalIconPath" );
+ mUnreadIconPath = config->readEntry("UnreadIconPath" );
+
+ mMailingListEnabled = config->readBoolEntry("MailingListEnabled");
+ mMailingList.readConfig( config );
+
+ mIdentity = config->readUnsignedNumEntry("Identity",0);
+
+ setUserWhoField( config->readEntry("WhoField"), false );
+ uint savedId = config->readUnsignedNumEntry("Id", 0);
+ // make sure that we don't overwrite a valid id
+ if ( savedId != 0 && mId == 0 )
+ mId = savedId;
+ mPutRepliesInSameFolder = config->readBoolEntry( "PutRepliesInSameFolder", false );
+ mIgnoreNewMail = config->readBoolEntry( "IgnoreNewMail", false );
+
+ if ( mUseCustomIcons )
+ emit iconsChanged();
+
+ QString shortcut( config->readEntry( "Shortcut" ) );
+ if ( !shortcut.isEmpty() ) {
+ KShortcut sc( shortcut );
+ setShortcut( sc );
+ }
+}
+
+void KMFolder::writeConfig( KConfig* config ) const
+{
+ config->writeEntry("SystemLabel", mSystemLabel);
+ config->writeEntry("ExpireMessages", mExpireMessages);
+ config->writeEntry("ReadExpireAge", mReadExpireAge);
+ config->writeEntry("ReadExpireUnits", mReadExpireUnits);
+ config->writeEntry("UnreadExpireAge", mUnreadExpireAge);
+ config->writeEntry("UnreadExpireUnits", mUnreadExpireUnits);
+ config->writeEntry("ExpireAction", mExpireAction == ExpireDelete ? "Delete" : "Move");
+ config->writeEntry("ExpireToFolder", mExpireToFolderId);
+
+ config->writeEntry("UseCustomIcons", mUseCustomIcons);
+ config->writeEntry("NormalIconPath", mNormalIconPath);
+ config->writeEntry("UnreadIconPath", mUnreadIconPath);
+
+ config->writeEntry("MailingListEnabled", mMailingListEnabled);
+ mMailingList.writeConfig( config );
+
+ if ( mIdentity != 0 && ( !mStorage || !mStorage->account() || mIdentity != mStorage->account()->identityId() ) )
+ config->writeEntry("Identity", mIdentity);
+ else
+ config->deleteEntry("Identity");
+
+ config->writeEntry("WhoField", mUserWhoField);
+ config->writeEntry("Id", mId);
+ config->writeEntry( "PutRepliesInSameFolder", mPutRepliesInSameFolder );
+ config->writeEntry( "IgnoreNewMail", mIgnoreNewMail );
+ if ( !mShortcut.isNull() )
+ config->writeEntry( "Shortcut", mShortcut.toString() );
+ else
+ config->deleteEntry( "Shortcut" );
+}
+
+KMFolderType KMFolder::folderType() const
+{
+ return mStorage ? mStorage->folderType() : KMFolderTypeUnknown;
+}
+
+QString KMFolder::fileName() const
+{
+ return mStorage ? mStorage->fileName() : QString::null;
+}
+
+QString KMFolder::location() const
+{
+ return mStorage ? mStorage->location() : QString::null;
+}
+
+QString KMFolder::indexLocation() const
+{
+ return mStorage ? mStorage->indexLocation() : QString::null;
+}
+
+QString KMFolder::subdirLocation() const
+{
+ QString sLocation( path() );
+
+ if( !sLocation.isEmpty() )
+ sLocation += '/';
+ sLocation += '.' + FolderStorage::dotEscape( fileName() ) + ".directory";
+
+ return sLocation;
+}
+
+KMFolderDir* KMFolder::createChildFolder()
+{
+ if( mChild )
+ return mChild;
+
+ QString childName = "." + fileName() + ".directory";
+ QString childDir = path() + "/" + childName;
+ if (access(QFile::encodeName(childDir), W_OK) != 0) // Not there or not writable
+ {
+ if (mkdir(QFile::encodeName(childDir), S_IRWXU) != 0
+ && chmod(QFile::encodeName(childDir), S_IRWXU) != 0) {
+ QString wmsg = QString(" '%1': %2").arg(childDir).arg(strerror(errno));
+ KMessageBox::information(0,i18n("Failed to create folder") + wmsg);
+ return 0;
+ }
+ }
+
+ KMFolderDirType newType = KMStandardDir;
+ if( folderType() == KMFolderTypeCachedImap )
+ newType = KMDImapDir;
+ else if( folderType() == KMFolderTypeImap )
+ newType = KMImapDir;
+
+ mChild = new KMFolderDir( this, parent(), childName, newType );
+ if( !mChild )
+ return 0;
+ mChild->reload();
+ parent()->append( mChild );
+ return mChild;
+}
+
+void KMFolder::setChild( KMFolderDir* aChild )
+{
+ mChild = aChild;
+ mStorage->updateChildrenState();
+}
+
+bool KMFolder::noContent() const
+{
+ return mStorage ? mStorage->noContent() : true;
+}
+
+void KMFolder::setNoContent( bool aNoContent )
+{
+ mStorage->setNoContent( aNoContent );
+}
+
+bool KMFolder::noChildren() const
+{
+ return mStorage->noChildren();
+}
+
+void KMFolder::setNoChildren( bool aNoChildren )
+{
+ mStorage->setNoChildren( aNoChildren );
+}
+
+KMMessage* KMFolder::getMsg( int idx )
+{
+ return mStorage->getMsg( idx );
+}
+
+KMMsgInfo* KMFolder::unGetMsg( int idx )
+{
+ return mStorage->unGetMsg( idx );
+}
+
+bool KMFolder::isMessage( int idx )
+{
+ return mStorage->isMessage( idx );
+}
+
+DwString KMFolder::getDwString( int idx )
+{
+ return mStorage->getDwString( idx );
+}
+
+void KMFolder::ignoreJobsForMessage( KMMessage* m )
+{
+ mStorage->ignoreJobsForMessage( m );
+}
+
+FolderJob* KMFolder::createJob( KMMessage *msg, FolderJob::JobType jt,
+ KMFolder *folder, QString partSpecifier,
+ const AttachmentStrategy *as ) const
+{
+ return mStorage->createJob( msg, jt, folder, partSpecifier, as );
+}
+
+FolderJob* KMFolder::createJob( QPtrList<KMMessage>& msgList,
+ const QString& sets,
+ FolderJob::JobType jt, KMFolder *folder ) const
+{
+ return mStorage->createJob( msgList, sets, jt, folder );
+}
+
+const KMMsgBase* KMFolder::getMsgBase( int idx ) const
+{
+ return mStorage->getMsgBase( idx );
+}
+
+KMMsgBase* KMFolder::getMsgBase( int idx )
+{
+ return mStorage->getMsgBase( idx );
+}
+
+const KMMsgBase* KMFolder::operator[]( int idx ) const
+{
+ return mStorage->operator[]( idx );
+}
+
+KMMsgBase* KMFolder::operator[]( int idx )
+{
+ return mStorage->operator[]( idx );
+}
+
+KMMessage* KMFolder::take( int idx )
+{
+ return mStorage->take( idx );
+}
+
+void KMFolder::take( QPtrList<KMMessage> msgList ) // TODO const ref
+{
+ mStorage->take( msgList );
+}
+
+int KMFolder::addMsg( KMMessage* msg, int* index_return )
+{
+ return mStorage->addMsg( msg, index_return );
+}
+
+int KMFolder::addMsgKeepUID( KMMessage* msg, int* index_return )
+{
+ return mStorage->addMsgKeepUID( msg, index_return );
+}
+
+int KMFolder::addMsg( QPtrList<KMMessage>& list, QValueList<int>& index_return )
+{
+ return mStorage->addMsg( list, index_return );
+}
+
+void KMFolder::emitMsgAddedSignals( int idx )
+{
+ mStorage->emitMsgAddedSignals( idx );
+}
+
+void KMFolder::removeMsg( int i, bool imapQuiet )
+{
+ mStorage->removeMsg( i, imapQuiet );
+}
+
+void KMFolder::removeMsg( QPtrList<KMMessage> msgList, bool imapQuiet ) // TODO const ref
+{
+ mStorage->removeMsg( msgList, imapQuiet );
+}
+
+int KMFolder::expungeOldMsg( int days )
+{
+ return mStorage->expungeOldMsg( days );
+}
+
+int KMFolder::moveMsg( KMMessage* msg, int* index_return )
+{
+ return mStorage->moveMsg( msg, index_return );
+}
+
+int KMFolder::moveMsg(QPtrList<KMMessage> q, int* index_return )
+{
+ return mStorage->moveMsg( q, index_return );
+}
+
+int KMFolder::find( const KMMsgBase* msg ) const
+{
+ return mStorage ? mStorage->find( msg ) : -1;
+}
+
+int KMFolder::find( const KMMessage* msg ) const
+{
+ return mStorage ? mStorage->find( msg ) : -1;
+}
+
+int KMFolder::count( bool cache ) const
+{
+ return mStorage->count( cache );
+}
+
+int KMFolder::countUnread()
+{
+ return mStorage->countUnread();
+}
+
+int KMFolder::countUnreadRecursive()
+{
+ KMFolder *folder;
+ int count = countUnread();
+ KMFolderDir *dir = child();
+ if (!dir)
+ return count;
+
+ QPtrListIterator<KMFolderNode> it(*dir);
+ for ( ; it.current(); ++it )
+ if (!it.current()->isDir()) {
+ folder = static_cast<KMFolder*>(it.current());
+ count += folder->countUnreadRecursive();
+ }
+
+ return count;
+}
+
+void KMFolder::msgStatusChanged( const KMMsgStatus oldStatus,
+ const KMMsgStatus newStatus, int idx )
+{
+ mStorage->msgStatusChanged( oldStatus, newStatus, idx );
+}
+
+int KMFolder::open(const char *owner)
+{
+ return mStorage->open(owner);
+}
+
+int KMFolder::canAccess()
+{
+ return mStorage->canAccess();
+}
+
+void KMFolder::close( const char *owner, bool force )
+{
+ // do not emit closed() in here - as this would regain too early
+ mStorage->close( owner, force );
+}
+
+void KMFolder::sync()
+{
+ mStorage->sync();
+}
+
+bool KMFolder::isOpened() const
+{
+ return mStorage->isOpened();
+}
+
+void KMFolder::markNewAsUnread()
+{
+ mStorage->markNewAsUnread();
+}
+
+void KMFolder::markUnreadAsRead()
+{
+ mStorage->markUnreadAsRead();
+}
+
+void KMFolder::remove()
+{
+ /* The storage needs to be open before remove is called, otherwise
+ it will not unregister the corresponding serial numbers from
+ the message dict, since its message list is empty, and the .ids
+ file contents are not loaded. That can lead to lookups in the
+ dict returning stale pointers to the folder later. */
+ mStorage->open("kmfolder_remove");
+ mStorage->remove();
+}
+
+int KMFolder::expunge()
+{
+ return mStorage->expunge();
+}
+
+int KMFolder::rename( const QString& newName, KMFolderDir *aParent )
+{
+ return mStorage->rename( newName, aParent );
+}
+
+bool KMFolder::dirty() const
+{
+ return mStorage->dirty();
+}
+
+void KMFolder::setDirty( bool f )
+{
+ mStorage->setDirty( f );
+}
+
+bool KMFolder::needsCompacting() const
+{
+ return mStorage->needsCompacting();
+}
+
+void KMFolder::setNeedsCompacting( bool f )
+{
+ mStorage->setNeedsCompacting( f );
+}
+
+void KMFolder::quiet( bool beQuiet )
+{
+ mStorage->quiet( beQuiet );
+}
+
+bool KMFolder::isReadOnly() const
+{
+ return mStorage->isReadOnly();
+}
+
+QString KMFolder::label() const
+{
+ if ( !mSystemLabel.isEmpty() )
+ return mSystemLabel;
+ if ( !mLabel.isEmpty() )
+ return mLabel;
+ if ( isSystemFolder() )
+ return i18n( name().utf8() );
+ return name();
+}
+
+//-----------------------------------------------------------------------------
+QString KMFolder::prettyURL() const
+{
+ QString parentUrl;
+ if ( parent() )
+ parentUrl = parent()->prettyURL();
+ if ( !parentUrl.isEmpty() )
+ return parentUrl + '/' + label();
+ else
+ return label();
+}
+
+//--------------------------------------------------------------------------
+QString KMFolder::mailingListPostAddress() const
+{
+ if ( mMailingList.features() & MailingList::Post ) {
+ KURL::List::const_iterator it;
+ KURL::List post = mMailingList.postURLS();
+ for( it = post.begin(); it != post.end(); ++it ) {
+ // We check for isEmpty because before 3.3 postAddress was just an
+ // email@kde.org and that leaves protocol() field in the kurl class
+ if ( (*it).protocol() == "mailto" || (*it).protocol().isEmpty() )
+ return (*it).path();
+ }
+ }
+ return QString::null;
+}
+
+void KMFolder::setMailingListEnabled( bool enabled )
+{
+ mMailingListEnabled = enabled;
+ mStorage->writeConfig();
+}
+
+void KMFolder::setMailingList( const MailingList& mlist )
+{
+ mMailingList = mlist;
+ mStorage->writeConfig();
+}
+
+void KMFolder::setIdentity( uint identity )
+{
+ mIdentity = identity;
+ kmkernel->slotRequestConfigSync();
+}
+
+uint KMFolder::identity() const
+{
+ // if we don't have one set ourselves, check our account
+ kdDebug() << "FOO: " << mIdentity << " :: " << mStorage << endl;
+ if ( !mIdentity && mStorage )
+ if ( KMAccount *act = mStorage->account() )
+ return act->identityId();
+ return mIdentity;
+}
+
+void KMFolder::setWhoField(const QString& aWhoField )
+{
+ mWhoField = aWhoField;
+#if 0
+ // This isn't saved in the config anyway
+ mStorage->writeConfig();
+#endif
+}
+
+void KMFolder::setUserWhoField( const QString& whoField, bool writeConfig )
+{
+ if ( mUserWhoField == whoField )
+ return;
+ if ( whoField.isEmpty() )
+ {
+ // default setting
+ const KPIM::Identity & identity =
+ kmkernel->identityManager()->identityForUoidOrDefault( mIdentity );
+
+ if ( isSystemFolder() && folderType() != KMFolderTypeImap ) {
+ // local system folders
+ if ( this == kmkernel->inboxFolder() ||
+ this == kmkernel->trashFolder() )
+ mWhoField = "From";
+ if ( this == kmkernel->outboxFolder() ||
+ this == kmkernel->sentFolder() ||
+ this == kmkernel->draftsFolder() ||
+ this == kmkernel->templatesFolder() )
+ mWhoField = "To";
+ } else if ( identity.drafts() == idString() ||
+ identity.templates() == idString() ||
+ identity.fcc() == idString() )
+ // drafts, templates or sent of the identity
+ mWhoField = "To";
+ else
+ mWhoField = "From";
+ } else if ( whoField == "From" || whoField == "To" )
+ // set the whoField according to the user-setting
+ mWhoField = whoField;
+ else {
+ // this should not happen...
+ kdDebug(5006) << "Illegal setting " << whoField << " for userWhoField!"
+ << endl;
+ return; // don't use the value
+ }
+ mUserWhoField = whoField;
+
+ if (writeConfig)
+ mStorage->writeConfig();
+ emit viewConfigChanged();
+}
+
+void KMFolder::correctUnreadMsgsCount()
+{
+ mStorage->correctUnreadMsgsCount();
+}
+
+QString KMFolder::idString() const
+{
+ KMFolderNode* folderNode = parent();
+ if (!folderNode)
+ return "";
+ while ( folderNode->parent() )
+ folderNode = folderNode->parent();
+ QString myPath = path();
+ int pathLen = myPath.length() - folderNode->path().length();
+ QString relativePath = myPath.right( pathLen );
+ if (!relativePath.isEmpty())
+ relativePath = relativePath.right( relativePath.length() - 1 ) + "/";
+ QString escapedName = name();
+ /* Escape [ and ] as they are disallowed for kconfig sections and that is
+ what the idString is primarily used for. */
+ escapedName.replace( "[", "%(" );
+ escapedName.replace( "]", "%)" );
+ return relativePath + escapedName;
+}
+
+void KMFolder::setAutoExpire( bool enabled )
+{
+ if( enabled != mExpireMessages ) {
+ mExpireMessages = enabled;
+ mStorage->writeConfig();
+ }
+}
+
+void KMFolder::setUnreadExpireAge( int age )
+{
+ if( age >= 0 && age != mUnreadExpireAge ) {
+ mUnreadExpireAge = age;
+ mStorage->writeConfig();
+ }
+}
+
+void KMFolder::setUnreadExpireUnits( ExpireUnits units )
+{
+ if (units >= expireNever && units < expireMaxUnits)
+ mUnreadExpireUnits = units;
+ mStorage->writeConfig();
+}
+
+void KMFolder::setReadExpireAge( int age )
+{
+ if( age >= 0 && age != mReadExpireAge ) {
+ mReadExpireAge = age;
+ mStorage->writeConfig();
+ }
+}
+
+void KMFolder::setReadExpireUnits( ExpireUnits units )
+{
+ if (units >= expireNever && units <= expireMaxUnits)
+ mReadExpireUnits = units;
+ mStorage->writeConfig();
+}
+
+
+void KMFolder::setExpireAction( ExpireAction a )
+{
+ if ( a != mExpireAction ) {
+ mExpireAction = a;
+ mStorage->writeConfig();
+ }
+}
+
+void KMFolder::setExpireToFolderId( const QString& id )
+{
+ if ( id != mExpireToFolderId ) {
+ mExpireToFolderId = id;
+ mStorage->writeConfig();
+ }
+}
+
+
+static int daysToExpire( int number, ExpireUnits units )
+{
+ switch (units) {
+ case expireDays: // Days
+ return number;
+ case expireWeeks: // Weeks
+ return number * 7;
+ case expireMonths: // Months - this could be better rather than assuming 31day months.
+ return number * 31;
+ default: // this avoids a compiler warning (not handled enumeration values)
+ ;
+ }
+ return -1;
+}
+
+void KMFolder::daysToExpire(int& unreadDays, int& readDays) {
+ unreadDays = ::daysToExpire( getUnreadExpireAge(), getUnreadExpireUnits() );
+ readDays = ::daysToExpire( getReadExpireAge(), getReadExpireUnits() );
+}
+
+void KMFolder::expireOldMessages( bool immediate )
+{
+ KMail::ScheduledExpireTask* task = new KMail::ScheduledExpireTask(this, immediate);
+ kmkernel->jobScheduler()->registerTask( task );
+ if ( immediate ) {
+ // #82259: compact after expiring.
+ compact( CompactLater );
+ }
+}
+
+void KMFolder::compact( CompactOptions options )
+{
+ if ( options == CompactLater ) {
+ KMail::ScheduledCompactionTask* task = new KMail::ScheduledCompactionTask(this, false);
+ kmkernel->jobScheduler()->registerTask( task );
+ } else {
+ mStorage->compact( options == CompactSilentlyNow );
+ }
+}
+
+KMFolder* KMFolder::trashFolder() const
+{
+ return mStorage ? mStorage->trashFolder() : 0;
+}
+
+int KMFolder::writeIndex( bool createEmptyIndex )
+{
+ return mStorage->writeIndex( createEmptyIndex );
+}
+
+void KMFolder::setStatus( int idx, KMMsgStatus status, bool toggle )
+{
+ mStorage->setStatus( idx, status, toggle );
+}
+
+void KMFolder::setStatus( QValueList<int>& ids, KMMsgStatus status,
+ bool toggle )
+{
+ mStorage->setStatus( ids, status, toggle);
+}
+
+void KMFolder::setIconPaths( const QString &normalPath,
+ const QString &unreadPath )
+{
+ mNormalIconPath = normalPath;
+ mUnreadIconPath = unreadPath;
+ mStorage->writeConfig();
+ emit iconsChanged();
+}
+
+void KMFolder::removeJobs()
+{
+ mStorage->removeJobs();
+}
+
+int KMFolder::updateIndex()
+{
+ return mStorage->updateIndex();
+}
+
+void KMFolder::reallyAddMsg( KMMessage* aMsg )
+{
+ mStorage->reallyAddMsg( aMsg );
+}
+
+void KMFolder::reallyAddCopyOfMsg( KMMessage* aMsg )
+{
+ mStorage->reallyAddCopyOfMsg( aMsg );
+}
+
+void KMFolder::setShortcut( const KShortcut &sc )
+{
+ if ( mShortcut != sc ) {
+ mShortcut = sc;
+ emit shortcutChanged( this );
+ }
+}
+
+bool KMFolder::isMoveable() const
+{
+ return !isSystemFolder();
+}
+
+void KMFolder::slotContentsTypeChanged( KMail::FolderContentsType type )
+{
+ kmkernel->iCalIface().folderContentsTypeChanged( this, type );
+ emit iconsChanged();
+}
+
+void KMFolder::slotFolderSizeChanged()
+{
+ emit folderSizeChanged( this );
+ KMFolder* papa = parent()->manager()->parentFolder( this );
+ if ( papa && papa != this ) {
+ papa->slotFolderSizeChanged();
+ }
+}
+
+
+#include "kmfolder.moc"
diff --git a/kmail/kmfolder.h b/kmail/kmfolder.h
new file mode 100644
index 00000000..db13ae8c
--- /dev/null
+++ b/kmail/kmfolder.h
@@ -0,0 +1,694 @@
+/* -*- mode: C++ -*-
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ *
+ * 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 kmfolder_h
+#define kmfolder_h
+
+// for large file support
+#include <config.h>
+
+#include "kmfoldernode.h"
+#include "kmfoldertype.h"
+#include "kmmsginfo.h"
+#include "kmglobal.h"
+#include "kmkernel.h"
+#include "folderjob.h"
+using KMail::FolderJob;
+#include "mailinglist-magic.h"
+using KMail::MailingList;
+#include "kmaccount.h" // for AccountList
+
+#include "mimelib/string.h"
+
+#include <qptrvector.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <kshortcut.h>
+
+class KMMessage;
+class KMFolderDir;
+class QTimer;
+class FolderStorage;
+class KMFolderTreeItem;
+class KMFolderJob;
+
+namespace KMail {
+ class AttachmentStrategy;
+}
+using KMail::AttachmentStrategy;
+
+typedef QValueList<Q_UINT32> SerNumList;
+
+/** Mail folder.
+ * (description will be here).
+ *
+ * @section Accounts
+ * The accounts (of KMail) that are fed into the folder are
+ * represented as the children of the folder. They are only stored here
+ * during runtime to have a reference for which accounts point to a
+ * specific folder.
+ */
+
+class KMFolder: public KMFolderNode
+{
+ Q_OBJECT
+ friend class ::KMFolderJob;
+public:
+
+ /**
+ * Constructs a new Folder object.
+ * @param parent The directory in the folder storage hierarchy under which
+ * the folder's storage will be found or created.
+ * @param name If name of the folder. In case there is no parent directory, because
+ * the folder is free-standing (/var/spool/mail/foo), this is used for the full path to
+ * the folder's storage location.
+ * @param aFolderType The type of folder to create.
+ * @param withIndex Wether this folder has an index. No-index folders are
+ * those used by KMail internally, the Outbox, and those of local spool accounts,
+ * for example.
+ * @param exportedSernums whether this folder exports its serial numbers to
+ * the global MsgDict for lookup.
+ * @return A new folder instance.
+ */
+ KMFolder( KMFolderDir* parent, const QString& name,
+ KMFolderType aFolderType, bool withIndex = true,
+ bool exportedSernums = true );
+ ~KMFolder();
+
+ /** Returns true if this folder is the inbox on the local disk */
+ bool isMainInbox() {
+ return this == KMKernel::self()->inboxFolder();
+ }
+ /** Returns true only if this is the outbox for outgoing mail */
+ bool isOutbox() {
+ return this == KMKernel::self()->outboxFolder();
+ }
+ /** Returns true if this folder is the sent-mail box of the local account,
+ or is configured to be the sent mail box of any of the users identities */
+ bool isSent() {
+ return KMKernel::self()->folderIsSentMailFolder( this );
+ }
+ /** Returns true if this folder is configured as a trash folder, locally or
+ for one of the accounts. */
+ bool isTrash() {
+ return KMKernel::self()->folderIsTrash( this );
+ }
+ /** Returns true if this folder is the drafts box of the local account,
+ or is configured to be the drafts box of any of the users identities */
+ bool isDrafts() {
+ return KMKernel::self()->folderIsDrafts( this );
+ }
+ /** Returns true if this folder is the templates folder of the local account,
+ or is configured to be the templates folder of any of the users identities */
+ bool isTemplates() {
+ return KMKernel::self()->folderIsTemplates( this );
+ }
+
+ void setAcctList( AccountList* list ) { mAcctList = list; }
+ AccountList* acctList() { return mAcctList; }
+
+ /** Returns TRUE if accounts are associated with this folder. */
+ bool hasAccounts() const { return (mAcctList != 0); }
+
+ /** This is used by the storage to read the folder specific configuration */
+ void readConfig( KConfig* config );
+
+ /** This is used by the storage to save the folder specific configuration */
+ void writeConfig( KConfig* config ) const;
+
+ FolderStorage* storage() { return mStorage; }
+ /** if the folder is const, the storage should be as well */
+ const FolderStorage* storage() const { return mStorage; }
+
+ /** Returns the type of this folder */
+ KMFolderType folderType() const;
+
+ /** Returns the filename of the folder (reimplemented in KMFolderImap) */
+ QString fileName() const;
+
+ /** Returns full path to folder file */
+ QString location() const;
+
+ /** Returns full path to index file */
+ QString indexLocation() const;
+
+ /** Returns full path to sub directory file */
+ QString subdirLocation() const;
+
+ /** Returns the folder directory associated with this node or
+ 0 if no such directory exists */
+ KMFolderDir* child() const
+ { return mChild; }
+
+ /** Create a child folder directory and associates it with this folder */
+ KMFolderDir* createChildFolder();
+
+ /** Set the folder directory associated with this node */
+ void setChild( KMFolderDir* aChild );
+
+ /** Returns, if the folder can't contain mails, but only subfolder */
+ bool noContent() const;
+
+ /** Specify, that the folder can't contain mails. */
+ void setNoContent(bool aNoContent);
+
+ /** Returns, if the folder can't have children */
+ bool noChildren() const;
+
+ /** Specify, that the folder can't have children */
+ void setNoChildren(bool aNoChildren);
+
+ /** Read message at given index. Indexing starts at zero */
+ KMMessage* getMsg(int idx);
+
+ /** Replace KMMessage with KMMsgInfo and delete KMMessage */
+ KMMsgInfo* unGetMsg(int idx);
+
+ /** Checks if the message is already "gotten" with getMsg */
+ bool isMessage(int idx);
+
+ /** Read a message and returns a DwString */
+ DwString getDwString(int idx);
+
+ /**
+ * Removes and deletes all jobs associated with the particular message
+ */
+ void ignoreJobsForMessage( KMMessage* );
+
+ /**
+ * These methods create respective FolderJob (You should derive FolderJob
+ * for each derived KMFolder).
+ */
+ FolderJob* createJob( KMMessage *msg, FolderJob::JobType jt = FolderJob::tGetMessage,
+ KMFolder *folder = 0, QString partSpecifier = QString::null,
+ const AttachmentStrategy *as = 0 ) const;
+ FolderJob* createJob( QPtrList<KMMessage>& msgList, const QString& sets,
+ FolderJob::JobType jt = FolderJob::tGetMessage,
+ KMFolder *folder = 0 ) const;
+
+ /** Provides access to the basic message fields that are also stored
+ in the index. Whenever you only need subject, from, date, status
+ you should use this method instead of getMsg() because getMsg()
+ will load the message if necessary and this method does not. */
+ const KMMsgBase* getMsgBase(int idx) const;
+ KMMsgBase* getMsgBase(int idx);
+
+ /** Same as getMsgBase(int). */
+ const KMMsgBase* operator[](int idx) const;
+
+ /** Same as getMsgBase(int). This time non-const. */
+ KMMsgBase* operator[](int idx);
+
+ /** Detach message from this folder. Usable to call addMsg() afterwards.
+ Loads the message if it is not loaded up to now. */
+ KMMessage* take(int idx);
+ void take(QPtrList<KMMessage> msgList);
+
+ /** Add the given message to the folder. Usually the message
+ is added at the end of the folder. Returns zero on success and
+ an errno error code on failure. The index of the new message
+ is stored in index_return if given.
+ Please note that the message is added as is to the folder and the folder
+ takes ownership of the message (deleting it in the destructor).*/
+ int addMsg(KMMessage* msg, int* index_return = 0);
+
+ /** (Note(bo): This needs to be fixed better at a later point.)
+ This is overridden by dIMAP because addMsg strips the X-UID
+ header from the mail. */
+ int addMsgKeepUID(KMMessage* msg, int* index_return = 0);
+
+ /**
+ * Adds the given messages to the folder. Behaviour is identical
+ * to addMsg(msg)
+ */
+ int addMsg(QPtrList<KMMessage>&, QValueList<int>& index_return);
+
+ /** Called by derived classes implementation of addMsg.
+ Emits msgAdded signals */
+ void emitMsgAddedSignals(int idx);
+
+ /** Remove (first occurrence of) given message from the folder. */
+ void removeMsg(int i, bool imapQuiet = false);
+ void removeMsg(QPtrList<KMMessage> msgList, bool imapQuiet = false);
+
+ /** Delete messages in the folder that are older than days. Return the
+ * number of deleted messages. */
+ int expungeOldMsg(int days);
+
+ /** Detaches the given message from it's current folder and
+ adds it to this folder. Returns zero on success and an errno error
+ code on failure. The index of the new message is stored in index_return
+ if given. */
+ int moveMsg(KMMessage* msg, int* index_return = 0);
+ int moveMsg(QPtrList<KMMessage>, int* index_return = 0);
+
+ /** Returns the index of the given message or -1 if not found. */
+ int find(const KMMsgBase* msg) const;
+ int find( const KMMessage * msg ) const;
+
+ /** Number of messages in this folder. */
+ int count(bool cache = false) const;
+
+ /** Number of new or unread messages in this folder. */
+ int countUnread();
+
+ /** Number of new or unread messages in this folder and all folders
+ contained by this folder */
+ int countUnreadRecursive();
+
+ /** Called by KMMsgBase::setStatus when status of a message has changed
+ required to keep the number unread messages variable current. */
+ void msgStatusChanged( const KMMsgStatus oldStatus,
+ const KMMsgStatus newStatus,
+ int idx);
+
+ /** Open folder for access.
+ Does nothing if the folder is already opened. To reopen a folder
+ call close() first.
+ Returns zero on success and an error code equal to the c-library
+ fopen call otherwise (errno). */
+ int open(const char *owner);
+
+ /** Check folder for permissions
+ Returns zero if readable and writable. */
+ int canAccess();
+
+ /** Close folder. If force is true the files are closed even if
+ others still use it (e.g. other mail reader windows). */
+ void close(const char *owner, bool force=false);
+
+ /** fsync buffers to disk */
+ void sync();
+
+ /** Test if folder is opened. */
+ bool isOpened() const;
+
+ /** Mark all new messages as unread. */
+ void markNewAsUnread();
+
+ /** Mark all new and unread messages as read. */
+ void markUnreadAsRead();
+
+ /** Removes the folder physically from disk and empties the contents
+ of the folder in memory. Note that the folder is closed during this
+ process, whether there are others using it or not.
+ see KMFolder::removeContents */
+ void remove();
+
+ /** Delete entire folder. Forces a close *but* opens the
+ folder again afterwards. Returns errno(3) error code or zero on
+ success. see KMFolder::expungeContents */
+ int expunge();
+
+ enum CompactOptions { CompactLater, CompactNow, CompactSilentlyNow };
+ /**
+ * Compact this folder. Options:
+ * CompactLater: schedule it as a background task
+ * CompactNow: do it now, and inform the user of the result (manual compaction)
+ * CompactSilentlyNow: do it now, and keep silent about it (e.g. for outbox)
+ */
+ void compact( CompactOptions options );
+
+ /** Physically rename the folder. Returns zero on success and an errno
+ on failure. */
+ int rename(const QString& newName, KMFolderDir *aParent = 0);
+
+ /** Returns true if the table of contents is dirty. This happens when
+ a message is deleted from the folder. The toc will then be re-created
+ when the folder is closed. */
+ bool dirty() const;
+
+ /** Change the dirty flag. */
+ void setDirty(bool f);
+
+ /** Returns true if the folder contains deleted messages */
+ bool needsCompacting() const;
+ void setNeedsCompacting(bool f);
+
+ /** If set to quiet the folder will not emit msgAdded(idx) signal.
+ This is necessary because adding the messages to the listview
+ one by one as they come in ( as happens on msgAdded(idx) ) is
+ very slow for large ( >10000 ) folders. For pop, where whole
+ bodies are downloaded this is not an issue, but for imap, where
+ we only download headers it becomes a bottleneck. We therefore
+ set the folder quiet() and rebuild the listview completely once
+ the complete folder has been checked. */
+ void quiet(bool beQuiet);
+
+ /** Is the folder read-only? */
+ bool isReadOnly() const;
+
+ /** Returns true if the folder is a kmail system folder. These are
+ the folders 'inbox', 'outbox', 'sent', 'trash', 'drafts', 'templates'.
+ The name of these folders is nationalized in the folder display and
+ they cannot have accounts associated. Deletion is also forbidden. Etc. */
+ bool isSystemFolder() const { return mIsSystemFolder; }
+ void setSystemFolder(bool itIs) { mIsSystemFolder=itIs; }
+
+ /** Returns the label of the folder for visualization. */
+ virtual QString label() const;
+ void setLabel( const QString& l ) { mLabel = l; }
+
+ /** Set the label that is used as a system default */
+ virtual QString systemLabel() const { return mSystemLabel; }
+ void setSystemLabel( const QString& l ) { mSystemLabel = l; }
+
+ /** URL of the node for visualization purposes. */
+ virtual QString prettyURL() const;
+
+ /** Returns true if this folder is associated with a mailing-list. */
+ void setMailingListEnabled( bool enabled );
+ bool isMailingListEnabled() const { return mMailingListEnabled; }
+
+ void setMailingList( const MailingList& mlist );
+ MailingList mailingList() const
+ { return mMailingList; }
+ QString mailingListPostAddress() const;
+
+ void setIdentity(uint identity);
+ uint identity() const;
+
+ /** Get / set the name of the field that is used for the Sender/Receiver column in the headers (From/To) */
+ QString whoField() const { return mWhoField; }
+ void setWhoField(const QString& aWhoField);
+
+ /** Get / set the user-settings for the WhoField (From/To/Empty) */
+ QString userWhoField(void) { return mUserWhoField; }
+ void setUserWhoField(const QString &whoField,bool writeConfig=true);
+
+ /** A cludge to help make sure the count of unread messges is kept in sync */
+ void correctUnreadMsgsCount();
+
+ /** Returns a string that can be used to identify this folder */
+ QString idString() const;
+
+ /**
+ * Set whether this folder automatically expires messages.
+ */
+ void setAutoExpire(bool enabled);
+
+ /**
+ * Does this folder automatically expire old messages?
+ */
+ bool isAutoExpire() const { return mExpireMessages; }
+
+ /**
+ * Set the maximum age for unread messages in this folder.
+ * Age should not be negative. Units are set using
+ * setUnreadExpireUnits().
+ */
+ void setUnreadExpireAge(int age);
+
+ /**
+ * Set units to use for expiry of unread messages.
+ * Values are 1 = days, 2 = weeks, 3 = months.
+ */
+ void setUnreadExpireUnits(ExpireUnits units);
+
+ /**
+ * Set the maximum age for read messages in this folder.
+ * Age should not be negative. Units are set using
+ * setReadExpireUnits().
+ */
+ void setReadExpireAge(int age);
+
+ /**
+ * Set units to use for expiry of read messages.
+ * Values are 1 = days, 2 = weeks, 3 = months.
+ */
+ void setReadExpireUnits(ExpireUnits units);
+
+ /**
+ * Get the age at which unread messages are expired.
+ * Units are determined by getUnreadExpireUnits().
+ */
+ int getUnreadExpireAge() const { return mUnreadExpireAge; }
+
+ /**
+ * Get the age at which read messages are expired.
+ * Units are determined by getReadExpireUnits().
+ */
+ int getReadExpireAge() const { return mReadExpireAge; }
+
+ /**
+ * Units getUnreadExpireAge() is returned in.
+ * 1 = days, 2 = weeks, 3 = months.
+ */
+ ExpireUnits getUnreadExpireUnits() const { return mUnreadExpireUnits; }
+
+ /**
+ * Units getReadExpireAge() is returned in.
+ * 1 = days, 2 = weeks, 3 = months.
+ */
+ ExpireUnits getReadExpireUnits() const { return mReadExpireUnits; }
+
+ enum ExpireAction { ExpireDelete, ExpireMove };
+ /**
+ * What should expiry do? Delete or move to another folder?
+ */
+ ExpireAction expireAction() const { return mExpireAction; }
+ void setExpireAction( ExpireAction a );
+
+ /**
+ * If expiry should move to folder, return the ID of that folder
+ */
+ QString expireToFolderId() const { return mExpireToFolderId; }
+ void setExpireToFolderId( const QString& id );
+
+ /**
+ * Expire old messages in this folder.
+ * If immediate is true, do it immediately; otherwise schedule it for later
+ */
+ void expireOldMessages( bool immediate );
+
+ /** Write index to index-file. Returns 0 on success and errno error on
+ failure. */
+ int writeIndex( bool createEmptyIndex = false );
+
+ /** Set the status of the message at index @p idx to @p status. */
+ void setStatus(int idx, KMMsgStatus status, bool toggle=false);
+
+ /** Set the status of the message(s) in the QValueList @p ids to @p status. */
+ void setStatus(QValueList<int>& ids, KMMsgStatus status, bool toggle=false);
+
+ /** Icon related methods */
+ bool useCustomIcons() const { return mUseCustomIcons; }
+ void setUseCustomIcons(bool useCustomIcons) { mUseCustomIcons = useCustomIcons; }
+ QString normalIconPath() const { return mNormalIconPath; }
+ QString unreadIconPath() const { return mUnreadIconPath; }
+ void setIconPaths(const QString &normalPath, const QString &unreadPath);
+
+ void removeJobs();
+
+ void daysToExpire( int& unreadDays, int& readDays );
+
+ /**
+ * If this folder has a special trash folder set, return it. Otherwise
+ * return 0.
+ */
+ KMFolder* trashFolder() const;
+
+ /**
+ * Returns true if the replies to mails from this folder should be
+ * put in the same folder.
+ */
+ bool putRepliesInSameFolder() const { return mPutRepliesInSameFolder; }
+ void setPutRepliesInSameFolder( bool b ) { mPutRepliesInSameFolder = b; }
+
+ /**
+ * Returns true if the user doesn't want to get notified about new mail
+ * in this folder.
+ */
+ bool ignoreNewMail() const { return mIgnoreNewMail; }
+ void setIgnoreNewMail( bool b ) { mIgnoreNewMail = b; }
+
+ const KShortcut &shortcut() const { return mShortcut; }
+ void setShortcut( const KShortcut& );
+
+ /** Returns true if this folder can be moved */
+ bool isMoveable() const;
+
+ /** Returns true if there is currently a move or copy operation going
+ on with this folder as target.
+ */
+ bool moveInProgress() const { return mMoveInProgress; }
+
+ /** Sets the move-in-progress flag. */
+ void setMoveInProgress( bool b ) { mMoveInProgress = b; }
+
+signals:
+ /** Emitted when the status, name, or associated accounts of this
+ folder changed. */
+ void changed();
+
+ /** Emitted when the folder is closed for real - ticket holders should
+ * discard any messages */
+ void closed();
+
+ /** Emitted when the contents of a folder have been cleared
+ (new search in a search folder, for example) */
+ void cleared();
+
+ /** Emitted after an expunge. If not quiet, changed() will be
+ emmitted first. */
+ void expunged( KMFolder* );
+
+ /** Emitted when the icon paths are set. */
+ void iconsChanged();
+
+ /** Emitted when the name of the folder changes. */
+ void nameChanged();
+
+ /** Emitted when the shortcut associated with this folder changes. */
+ void shortcutChanged( KMFolder * );
+
+ /** Emitted before a message is removed from the folder. */
+ void msgRemoved(KMFolder*, Q_UINT32 sernum);
+
+ /** Emitted after a message is removed from the folder. */
+ void msgRemoved( int idx, QString msgIdMD5 );
+ void msgRemoved(KMFolder*);
+
+ /** Emitted when a message is added from the folder. */
+ void msgAdded(int idx);
+ void msgAdded(KMFolder*, Q_UINT32 sernum);
+
+ /** Emitted, when the status of a message is changed */
+ void msgChanged(KMFolder*, Q_UINT32 sernum, int delta);
+
+ /** Emitted when a field of the header of a specific message changed. */
+ void msgHeaderChanged(KMFolder*, int);
+
+ /** Emmited to display a message somewhere in a status line. */
+ void statusMsg(const QString&);
+
+ /** Emitted when number of unread messages has changed. */
+ void numUnreadMsgsChanged( KMFolder* );
+
+ /** Emitted when a folder was removed */
+ void removed(KMFolder*, bool);
+
+ /** Emitted when the variables for the config of the view have changed */
+ void viewConfigChanged();
+
+ /** Emitted when the folder's size changes. */
+ void folderSizeChanged( KMFolder * );
+
+public slots:
+ /** Incrementally update the index if possible else call writeIndex */
+ int updateIndex();
+
+ /** Add the message to the folder after it has been retrieved from an IMAP
+ server */
+ void reallyAddMsg(KMMessage* aMsg);
+
+ /** Add a copy of the message to the folder after it has been retrieved
+ from an IMAP server */
+ void reallyAddCopyOfMsg(KMMessage* aMsg);
+
+private slots:
+ /** The type of contents of this folder changed. Do what is needed. */
+ void slotContentsTypeChanged( KMail::FolderContentsType type );
+ /** Triggered by the storage when its size changed. */
+ void slotFolderSizeChanged();
+
+private:
+ FolderStorage* mStorage;
+ KMFolderDir* mChild;
+ bool mIsSystemFolder;
+ bool mHasIndex :1;
+ bool mExportsSernums :1;
+ bool mMoveInProgress :1;
+
+ /** nationalized label or QString::null (then name() should be used) */
+ QString mLabel;
+ QString mSystemLabel;
+
+ /** Support for automatic expiry of old messages */
+ bool mExpireMessages; // true if old messages are expired
+ int mUnreadExpireAge; // Given in unreadExpireUnits
+ int mReadExpireAge; // Given in readExpireUnits
+ ExpireUnits mUnreadExpireUnits;
+ ExpireUnits mReadExpireUnits;
+ ExpireAction mExpireAction;
+ QString mExpireToFolderId;
+
+ /** Icon related variables */
+ bool mUseCustomIcons;
+ QString mNormalIconPath;
+ QString mUnreadIconPath;
+
+ /** Mailing list attributes */
+ bool mMailingListEnabled;
+ MailingList mMailingList;
+
+ AccountList* mAcctList;
+
+ uint mIdentity;
+
+ /** name of the field that is used for "From" in listbox */
+ QString mWhoField, mUserWhoField;
+
+ /** Should replies to messages in this folder be put in here? */
+ bool mPutRepliesInSameFolder;
+
+ /** Should new mail in this folder be ignored? */
+ bool mIgnoreNewMail;
+
+ /** shortcut associated with this folder or null, if none is configured. */
+ KShortcut mShortcut;
+};
+
+
+/**
+ RAII for KMFolder::open() / close().
+
+ Usage: const KMFolderOpener opener( folder );
+*/
+class KMFolderOpener {
+ KMFolder* mFolder;
+ const char* const mOwner;
+ int mOpenRc;
+
+public:
+ KMFolderOpener( KMFolder* folder, const char* const owner )
+ : mFolder( folder )
+ , mOwner( owner )
+ {
+ assert( folder ); //change if that's not what you want
+ mOpenRc = folder->open( owner );
+ }
+
+ ~KMFolderOpener()
+ {
+ if ( !mOpenRc )
+ mFolder->close( mOwner );
+ }
+
+ KMFolder* folder() const { return mFolder; }
+
+ int openResult() const { return mOpenRc; }
+
+private:
+ //we forbid construction on the heap as good as we can
+ void* operator new( size_t size );
+};
+
+
+#endif /*kmfolder_h*/
diff --git a/kmail/kmfoldercachedimap.cpp b/kmail/kmfoldercachedimap.cpp
new file mode 100644
index 00000000..405176c4
--- /dev/null
+++ b/kmail/kmfoldercachedimap.cpp
@@ -0,0 +1,2992 @@
+/**
+ * kmfoldercachedimap.cpp
+ *
+ * Copyright (c) 2002-2004 Bo Thorsen <bo@sonofthor.dk>
+ * Copyright (c) 2002-2003 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+
+#include <qvaluevector.h>
+
+#include "kmkernel.h"
+#include "kmfoldercachedimap.h"
+#include "undostack.h"
+#include "kmfoldermgr.h"
+#include "kmacctcachedimap.h"
+#include "accountmanager.h"
+using KMail::AccountManager;
+#include "kmailicalifaceimpl.h"
+#include "kmfolder.h"
+#include "kmglobal.h"
+#include "acljobs.h"
+#include "broadcaststatus.h"
+using KPIM::BroadcastStatus;
+#include "progressmanager.h"
+
+using KMail::CachedImapJob;
+#include "imapaccountbase.h"
+using KMail::ImapAccountBase;
+#include "listjob.h"
+using KMail::ListJob;
+
+#include "kmfolderseldlg.h"
+#include "kmcommands.h"
+#include "kmmainwidget.h"
+
+#include <kapplication.h>
+#include <kmessagebox.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kconfig.h>
+#include <kio/global.h>
+#include <kio/scheduler.h>
+#include <qbuffer.h>
+#include <qbuttongroup.h>
+#include <qcombobox.h>
+#include <qfile.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qradiobutton.h>
+#include <qvaluelist.h>
+#include "annotationjobs.h"
+#include "quotajobs.h"
+using namespace KMail;
+#include <globalsettings.h>
+
+#define UIDCACHE_VERSION 1
+#define MAIL_LOSS_DEBUGGING 0
+
+static QString incidencesForToString( KMFolderCachedImap::IncidencesFor r ) {
+ switch (r) {
+ case KMFolderCachedImap::IncForNobody: return "nobody";
+ case KMFolderCachedImap::IncForAdmins: return "admins";
+ case KMFolderCachedImap::IncForReaders: return "readers";
+ }
+ return QString::null; // can't happen
+}
+
+static KMFolderCachedImap::IncidencesFor incidencesForFromString( const QString& str ) {
+ if ( str == "nobody" ) return KMFolderCachedImap::IncForNobody;
+ if ( str == "admins" ) return KMFolderCachedImap::IncForAdmins;
+ if ( str == "readers" ) return KMFolderCachedImap::IncForReaders;
+ return KMFolderCachedImap::IncForAdmins; // by default
+}
+
+DImapTroubleShootDialog::DImapTroubleShootDialog( QWidget* parent,
+ const char* name )
+ : KDialogBase( Plain, i18n( "Troubleshooting IMAP Cache" ),
+ Ok | Cancel, Cancel, parent, name, true ),
+ rc( None )
+{
+ QFrame* page = plainPage();
+ QVBoxLayout *topLayout = new QVBoxLayout( page, 0 );
+ // spell "lose" correctly. but don't cause a fuzzy.
+ QString txt = i18n( "<p><b>Troubleshooting the IMAP cache.</b></p>"
+ "<p>If you have problems with synchronizing an IMAP "
+ "folder, you should first try rebuilding the index "
+ "file. This will take some time to rebuild, but will "
+ "not cause any problems.</p><p>If that is not enough, "
+ "you can try refreshing the IMAP cache. If you do this, "
+ "you will loose all your local changes for this folder "
+ "and all its subfolders.</p>",
+ "<p><b>Troubleshooting the IMAP cache.</b></p>"
+ "<p>If you have problems with synchronizing an IMAP "
+ "folder, you should first try rebuilding the index "
+ "file. This will take some time to rebuild, but will "
+ "not cause any problems.</p><p>If that is not enough, "
+ "you can try refreshing the IMAP cache. If you do this, "
+ "you will lose all your local changes for this folder "
+ "and all its subfolders.</p>" );
+ topLayout->addWidget( new QLabel( txt, page ) );
+
+ QButtonGroup *group = new QButtonGroup( 0 );
+
+ mIndexButton = new QRadioButton( page );
+ mIndexButton->setText( i18n( "Rebuild &Index" ) );
+ group->insert( mIndexButton );
+ topLayout->addWidget( mIndexButton );
+
+ QHBox *hbox = new QHBox( page );
+ QLabel *scopeLabel = new QLabel( i18n( "Scope:" ), hbox );
+ scopeLabel->setEnabled( false );
+ mIndexScope = new QComboBox( hbox );
+ mIndexScope->insertItem( i18n( "Only current folder" ) );
+ mIndexScope->insertItem( i18n( "Current folder and all subfolders" ) );
+ mIndexScope->insertItem( i18n( "All folders of this account" ) );
+ mIndexScope->setEnabled( false );
+ topLayout->addWidget( hbox );
+
+ mCacheButton = new QRadioButton( page );
+ mCacheButton->setText( i18n( "Refresh &Cache" ) );
+ group->insert( mCacheButton );
+ topLayout->addWidget( mCacheButton );
+
+ enableButtonSeparator( true );
+
+ connect ( mIndexButton, SIGNAL(toggled(bool)), mIndexScope, SLOT(setEnabled(bool)) );
+ connect ( mIndexButton, SIGNAL(toggled(bool)), scopeLabel, SLOT(setEnabled(bool)) );
+
+ connect( this, SIGNAL( okClicked () ), this, SLOT( slotDone() ) );
+}
+
+int DImapTroubleShootDialog::run()
+{
+ DImapTroubleShootDialog d;
+ d.exec();
+ return d.rc;
+}
+
+void DImapTroubleShootDialog::slotDone()
+{
+ rc = None;
+ if ( mIndexButton->isOn() )
+ rc = mIndexScope->currentItem();
+ else if ( mCacheButton->isOn() )
+ rc = RefreshCache;
+ done( Ok );
+}
+
+KMFolderCachedImap::KMFolderCachedImap( KMFolder* folder, const char* aName )
+ : KMFolderMaildir( folder, aName ),
+ mSyncState( SYNC_STATE_INITIAL ), mContentState( imapNoInformation ),
+ mSubfolderState( imapNoInformation ),
+ mIncidencesFor( IncForAdmins ),
+ mIsSelected( false ),
+ mCheckFlags( true ), mReadOnly( false ), mAccount( NULL ), uidMapDirty( true ),
+ uidWriteTimer( -1 ), mLastUid( 0 ), mTentativeHighestUid( 0 ),
+ mFoundAnIMAPDigest( false ),
+ mUserRights( 0 ), mOldUserRights( 0 ), mSilentUpload( false ),
+ /*mHoldSyncs( false ),*/
+ mFolderRemoved( false ),
+ mRecurse( true ),
+ mStatusChangedLocally( false ), mAnnotationFolderTypeChanged( false ),
+ mIncidencesForChanged( false ), mPersonalNamespacesCheckDone( true ),
+ mQuotaInfo(), mAlarmsBlocked( false ),
+ mRescueCommandCount( 0 ),
+ mPermanentFlags( 31 ) // assume standard flags by default (see imap4/imapinfo.h for bit fields values)
+{
+ setUidValidity("");
+ // if we fail to read a uid file but there is one, nuke it
+ if ( readUidCache() == -1 ) {
+ if ( QFile::exists( uidCacheLocation() ) ) {
+ KMessageBox::error( 0,
+ i18n( "The UID cache file for folder %1 could not be read. There "
+ "could be a problem with file system permission, or it is corrupted."
+ ).arg( folder->prettyURL() ) );
+ // try to unlink it, in case it was corruped. If it couldn't be read
+ // because of permissions, this will fail, which is fine
+ unlink( QFile::encodeName( uidCacheLocation() ) );
+ }
+ }
+
+ mProgress = 0;
+}
+
+KMFolderCachedImap::~KMFolderCachedImap()
+{
+ if (kmkernel->undoStack()) kmkernel->undoStack()->folderDestroyed( folder() );
+}
+
+void KMFolderCachedImap::reallyDoClose( const char* owner )
+{
+ if( !mFolderRemoved ) {
+ writeUidCache();
+ }
+ KMFolderMaildir::reallyDoClose( owner );
+}
+
+void KMFolderCachedImap::initializeFrom( KMFolderCachedImap* parent )
+{
+ setAccount( parent->account() );
+ // 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() );
+}
+
+void KMFolderCachedImap::readConfig()
+{
+ KConfig* config = KMKernel::config();
+ KConfigGroupSaver saver( config, "Folder-" + folder()->idString() );
+ if( mImapPath.isEmpty() ) mImapPath = config->readEntry( "ImapPath" );
+ if( QString( name() ).upper() == "INBOX" && mImapPath == "/INBOX/" )
+ {
+ folder()->setLabel( i18n( "inbox" ) );
+ // for the icon
+ folder()->setSystemFolder( true );
+ }
+ mNoContent = config->readBoolEntry( "NoContent", false );
+ mReadOnly = config->readBoolEntry( "ReadOnly", false );
+ if ( !config->readEntry( "FolderAttributes" ).isEmpty() )
+ mFolderAttributes = config->readEntry( "FolderAttributes" );
+
+ if ( mAnnotationFolderType != "FROMSERVER" ) {
+ mAnnotationFolderType = config->readEntry( "Annotation-FolderType" );
+ // if there is an annotation, it has to be XML
+ if ( !mAnnotationFolderType.isEmpty() && !mAnnotationFolderType.startsWith( "mail" ) )
+ kmkernel->iCalIface().setStorageFormat( folder(), KMailICalIfaceImpl::StorageXML );
+// kdDebug(5006) << ( mImapPath.isEmpty() ? label() : mImapPath )
+// << " readConfig: mAnnotationFolderType=" << mAnnotationFolderType << endl;
+ }
+ mIncidencesFor = incidencesForFromString( config->readEntry( "IncidencesFor" ) );
+ mAlarmsBlocked = config->readBoolEntry( "AlarmsBlocked", false );
+// kdDebug(5006) << ( mImapPath.isEmpty() ? label() : mImapPath )
+// << " readConfig: mIncidencesFor=" << mIncidencesFor << endl;
+
+ mUserRights = config->readNumEntry( "UserRights", 0 ); // default is we don't know
+ mOldUserRights = mUserRights;
+
+ int storageQuotaUsage = config->readNumEntry( "StorageQuotaUsage", -1 );
+ int storageQuotaLimit = config->readNumEntry( "StorageQuotaLimit", -1 );
+ QString storageQuotaRoot = config->readEntry( "StorageQuotaRoot", QString::null );
+ if ( !storageQuotaRoot.isNull() ) { // isEmpty() means we know there is no quota set
+ mQuotaInfo.setName( "STORAGE" );
+ mQuotaInfo.setRoot( storageQuotaRoot );
+
+ if ( storageQuotaUsage > -1 )
+ mQuotaInfo.setCurrent( storageQuotaUsage );
+ if ( storageQuotaLimit > -1 )
+ mQuotaInfo.setMax( storageQuotaLimit );
+ }
+
+ KMFolderMaildir::readConfig();
+
+ mStatusChangedLocally =
+ config->readBoolEntry( "StatusChangedLocally", false );
+
+ mAnnotationFolderTypeChanged = config->readBoolEntry( "AnnotationFolderTypeChanged", false );
+ mIncidencesForChanged = config->readBoolEntry( "IncidencesForChanged", false );
+ if ( mImapPath.isEmpty() ) {
+ mImapPathCreation = config->readEntry("ImapPathCreation");
+ }
+
+ QStringList uids = config->readListEntry( "UIDSDeletedSinceLastSync" );
+#if MAIL_LOSS_DEBUGGING
+ kdDebug( 5006 ) << "READING IN UIDSDeletedSinceLastSync: " << folder()->prettyURL() << endl << uids << endl;
+#endif
+ for ( QStringList::iterator it = uids.begin(); it != uids.end(); it++ ) {
+ mDeletedUIDsSinceLastSync.insert( (*it).toULong(), 0);
+ }
+}
+
+void KMFolderCachedImap::writeConfig()
+{
+ // don't re-write the config of a removed folder, this has just been deleted in
+ // the folder manager
+ if ( mFolderRemoved )
+ return;
+
+ KConfigGroup configGroup( KMKernel::config(), "Folder-" + folder()->idString() );
+ configGroup.writeEntry( "ImapPath", mImapPath );
+ configGroup.writeEntry( "NoContent", mNoContent );
+ configGroup.writeEntry( "ReadOnly", mReadOnly );
+ configGroup.writeEntry( "FolderAttributes", mFolderAttributes );
+ configGroup.writeEntry( "StatusChangedLocally", mStatusChangedLocally );
+ if ( !mImapPathCreation.isEmpty() ) {
+ if ( mImapPath.isEmpty() ) {
+ configGroup.writeEntry( "ImapPathCreation", mImapPathCreation );
+ } else {
+ configGroup.deleteEntry( "ImapPathCreation" );
+ }
+ }
+ if ( !mDeletedUIDsSinceLastSync.isEmpty() ) {
+ QValueList<ulong> uids = mDeletedUIDsSinceLastSync.keys();
+ QStringList uidstrings;
+ for( QValueList<ulong>::iterator it = uids.begin(); it != uids.end(); it++ ) {
+ uidstrings.append( QString::number( (*it) ) );
+ }
+ configGroup.writeEntry( "UIDSDeletedSinceLastSync", uidstrings );
+#if MAIL_LOSS_DEBUGGING
+ kdDebug( 5006 ) << "WRITING OUT UIDSDeletedSinceLastSync in: " << folder( )->prettyURL( ) << endl << uidstrings << endl;
+#endif
+ } else {
+ configGroup.deleteEntry( "UIDSDeletedSinceLastSync" );
+ }
+ writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
+ KMFolderMaildir::writeConfig();
+}
+
+void KMFolderCachedImap::writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig()
+{
+ KConfigGroup configGroup( KMKernel::config(), "Folder-" + folder()->idString() );
+ if ( !folder()->noContent() )
+ {
+ configGroup.writeEntry( "AnnotationFolderTypeChanged", mAnnotationFolderTypeChanged );
+ configGroup.writeEntry( "Annotation-FolderType", mAnnotationFolderType );
+ configGroup.writeEntry( "IncidencesForChanged", mIncidencesForChanged );
+ configGroup.writeEntry( "IncidencesFor", incidencesForToString( mIncidencesFor ) );
+ configGroup.writeEntry( "AlarmsBlocked", mAlarmsBlocked );
+ configGroup.writeEntry( "UserRights", mUserRights );
+
+ configGroup.deleteEntry( "StorageQuotaUsage");
+ configGroup.deleteEntry( "StorageQuotaRoot");
+ configGroup.deleteEntry( "StorageQuotaLimit");
+
+ if ( mQuotaInfo.isValid() ) {
+ if ( mQuotaInfo.current().isValid() ) {
+ configGroup.writeEntry( "StorageQuotaUsage", mQuotaInfo.current().toInt() );
+ }
+ if ( mQuotaInfo.max().isValid() ) {
+ configGroup.writeEntry( "StorageQuotaLimit", mQuotaInfo.max().toInt() );
+ }
+ configGroup.writeEntry( "StorageQuotaRoot", mQuotaInfo.root() );
+ }
+ }
+}
+
+int KMFolderCachedImap::create()
+{
+ int rc = KMFolderMaildir::create();
+ // FIXME why the below? - till
+ readConfig();
+ mUnreadMsgs = -1;
+ return rc;
+}
+
+void KMFolderCachedImap::remove()
+{
+ mFolderRemoved = true;
+
+ QString part1 = folder()->path() + "/." + dotEscape(name());
+ QString uidCacheFile = part1 + ".uidcache";
+ // This is the account folder of an account that was just removed
+ // When this happens, be sure to delete all traces of the cache
+ if( QFile::exists(uidCacheFile) )
+ unlink( QFile::encodeName( uidCacheFile ) );
+
+ FolderStorage::remove();
+}
+
+QString KMFolderCachedImap::uidCacheLocation() const
+{
+ QString sLocation(folder()->path());
+ if (!sLocation.isEmpty()) sLocation += '/';
+ return sLocation + '.' + dotEscape(fileName()) + ".uidcache";
+}
+
+int KMFolderCachedImap::readUidCache()
+{
+ QFile uidcache( uidCacheLocation() );
+ if( uidcache.open( IO_ReadOnly ) ) {
+ char buf[1024];
+ int len = uidcache.readLine( buf, sizeof(buf) );
+ if( len > 0 ) {
+ int cacheVersion;
+ sscanf( buf, "# KMail-UidCache V%d\n", &cacheVersion );
+ if( cacheVersion == UIDCACHE_VERSION ) {
+ len = uidcache.readLine( buf, sizeof(buf) );
+ if( len > 0 ) {
+ setUidValidity( QString::fromLocal8Bit(buf).stripWhiteSpace() );
+ len = uidcache.readLine( buf, sizeof(buf) );
+ if( len > 0 ) {
+#if MAIL_LOSS_DEBUGGING
+ kdDebug(5006) << "Reading in last uid from cache: " << QString::fromLocal8Bit(buf).stripWhiteSpace() << " in " << folder()->prettyURL() << endl;
+#endif
+ // load the last known highest uid from the on disk cache
+ setLastUid( QString::fromLocal8Bit(buf).stripWhiteSpace().toULong() );
+ return 0;
+ }
+ }
+ }
+ }
+ }
+ return -1;
+}
+
+int KMFolderCachedImap::writeUidCache()
+{
+ if( uidValidity().isEmpty() || uidValidity() == "INVALID" ) {
+ // No info from the server yet, remove the file.
+ if( QFile::exists( uidCacheLocation() ) )
+ return unlink( QFile::encodeName( uidCacheLocation() ) );
+ return 0;
+ }
+#if MAIL_LOSS_DEBUGGING
+ kdDebug(5006) << "Writing out UID cache lastuid: " << lastUid() << " in: " << folder()->prettyURL() << endl;
+#endif
+ QFile uidcache( uidCacheLocation() );
+ if( uidcache.open( IO_WriteOnly ) ) {
+ QTextStream str( &uidcache );
+ str << "# KMail-UidCache V" << UIDCACHE_VERSION << endl;
+ str << uidValidity() << endl;
+ str << lastUid() << endl;
+ uidcache.flush();
+ if ( uidcache.status() == IO_Ok ) {
+ fsync( uidcache.handle() ); /* this is probably overkill */
+ uidcache.close();
+ if ( uidcache.status() == IO_Ok )
+ return 0;
+ }
+ }
+ KMessageBox::error( 0,
+ i18n( "The UID cache file for folder %1 could not be written. There "
+ "could be a problem with file system permission." ).arg( folder()->prettyURL() ) );
+
+ return -1;
+}
+
+void KMFolderCachedImap::reloadUidMap()
+{
+ //kdDebug(5006) << "Reloading Uid Map " << endl;
+ uidMap.clear();
+ open("reloadUdi");
+ for( int i = 0; i < count(); ++i ) {
+ KMMsgBase *msg = getMsgBase( i );
+ if( !msg ) continue;
+ ulong uid = msg->UID();
+ //kdDebug(5006) << "Inserting: " << i << " with uid: " << uid << endl;
+ uidMap.insert( uid, i );
+ }
+ close("reloadUdi");
+ uidMapDirty = false;
+}
+
+KMMessage* KMFolderCachedImap::take(int idx)
+{
+ uidMapDirty = true;
+ rememberDeletion( idx );
+ return KMFolderMaildir::take(idx);
+}
+
+void KMFolderCachedImap::takeTemporarily( int idx )
+{
+ KMFolderMaildir::take( idx );
+}
+
+// Add a message without clearing it's X-UID field.
+int KMFolderCachedImap::addMsgInternal( KMMessage* msg, bool newMail,
+ int* index_return )
+{
+ // Possible optimization: Only dirty if not filtered below
+ ulong uid = msg->UID();
+ if( uid != 0 ) {
+ uidMapDirty = true;
+ }
+
+ KMFolderOpener openThis(folder(), "KMFolderCachedImap::addMsgInternal");
+ int rc = openThis.openResult();
+ if ( rc ) {
+ kdDebug(5006) << k_funcinfo << "open: " << rc << " of folder: " << label() << endl;
+ return rc;
+ }
+
+ // Add the message
+ rc = KMFolderMaildir::addMsg(msg, index_return);
+
+ if( newMail && ( imapPath() == "/INBOX/" || ( !GlobalSettings::self()->filterOnlyDIMAPInbox()
+ && (userRights() <= 0 || userRights() & ACLJobs::Administer )
+ && (contentsType() == ContentsTypeMail || GlobalSettings::self()->filterGroupwareFolders()) ) ) )
+ // This is a new message. Filter it
+ mAccount->processNewMsg( msg );
+
+ return rc;
+}
+
+/* Reimplemented from KMFolderMaildir */
+int KMFolderCachedImap::addMsg(KMMessage* msg, int* index_return)
+{
+ if ( !canAddMsgNow( msg, index_return ) ) return 0;
+ // Add it to storage
+ int rc = KMFolderMaildir::addMsgInternal(msg, index_return, true /*stripUID*/);
+ return rc;
+}
+
+void KMFolderCachedImap::rememberDeletion( int idx )
+{
+ KMMsgBase *msg = getMsgBase( idx );
+ assert(msg);
+ long uid = msg->UID();
+ assert(uid>=0);
+ mDeletedUIDsSinceLastSync.insert(uid, 0);
+ kdDebug(5006) << "Explicit delete of UID " << uid << " at index: " << idx << " in " << folder()->prettyURL() << endl;
+}
+
+/* Reimplemented from KMFolderMaildir */
+void KMFolderCachedImap::removeMsg(int idx, bool imapQuiet)
+{
+ uidMapDirty = true;
+ rememberDeletion( idx );
+ // Remove it from disk
+ KMFolderMaildir::removeMsg(idx,imapQuiet);
+}
+
+bool KMFolderCachedImap::canRemoveFolder() const {
+ // If this has subfolders it can't be removed
+ if( folder() && folder()->child() && folder()->child()->count() > 0 )
+ return false;
+
+#if 0
+ // No special condition here, so let base class decide
+ return KMFolderMaildir::canRemoveFolder();
+#endif
+ return true;
+}
+
+/* Reimplemented from KMFolderDir */
+int KMFolderCachedImap::rename( const QString& aName,
+ KMFolderDir* /*aParent*/ )
+{
+ QString 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
+ QString 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,
+ // don't rename, but also make sure the rename is reset, in the case of
+ // A -> B -> A renames.
+ if ( name() != aName )
+ mAccount->addRenamedFolder( imapPath(), folder()->label(), aName );
+ else
+ mAccount->removeRenamedFolder( imapPath() );
+
+ folder()->setLabel( aName );
+ emit nameChanged(); // for kmailicalifaceimpl
+
+ return 0;
+}
+
+KMFolder* KMFolderCachedImap::trashFolder() const
+{
+ QString trashStr = account()->trash();
+ return kmkernel->dimapFolderMgr()->findIdString( trashStr );
+}
+
+void KMFolderCachedImap::setLastUid( ulong uid )
+{
+#if MAIL_LOSS_DEBUGGING
+ kdDebug(5006) << "Setting mLastUid to: " << uid << " in " << folder()->prettyURL() << endl;
+#endif
+ mLastUid = uid;
+ if( uidWriteTimer == -1 )
+ // Write in one minute
+ uidWriteTimer = startTimer( 60000 );
+}
+
+void KMFolderCachedImap::timerEvent( QTimerEvent* )
+{
+ killTimer( uidWriteTimer );
+ uidWriteTimer = -1;
+ if ( writeUidCache() == -1 )
+ unlink( QFile::encodeName( uidCacheLocation() ) );
+}
+
+ulong KMFolderCachedImap::lastUid()
+{
+ return mLastUid;
+}
+
+KMMsgBase* KMFolderCachedImap::findByUID( ulong uid )
+{
+ bool mapReloaded = false;
+ if( uidMapDirty ) {
+ reloadUidMap();
+ mapReloaded = true;
+ }
+
+ QMap<ulong,int>::Iterator it = uidMap.find( uid );
+ if( it != uidMap.end() ) {
+ KMMsgBase *msg = getMsgBase( *it );
+#if MAIL_LOSS_DEBUGGING
+ kdDebug(5006) << "Folder: " << folder()->prettyURL() << endl;
+ kdDebug(5006) << "UID " << uid << " is supposed to be in the map" << endl;
+ kdDebug(5006) << "UID's index is to be " << *it << endl;
+ kdDebug(5006) << "There is a message there? " << (msg != 0) << endl;
+ if ( msg ) {
+ kdDebug(5006) << "Its UID is: " << msg->UID() << endl;
+ }
+#endif
+
+ if( msg && msg->UID() == uid )
+ return msg;
+ kdDebug(5006) << "########## Didn't find uid: " << uid << "in cache athough it's supposed to be there!" << endl;
+ } else {
+#if MAIL_LOSS_DEBUGGING
+ kdDebug(5006) << "Didn't find uid: " << uid << "in cache!" << endl;
+#endif
+ }
+ // Not found by now
+ // if( mapReloaded )
+ // Not here then
+ return 0;
+ // There could be a problem in the maps. Rebuild them and try again
+ reloadUidMap();
+ it = uidMap.find( uid );
+ if( it != uidMap.end() )
+ // Since the uid map is just rebuilt, no need for the sanity check
+ return getMsgBase( *it );
+#if MAIL_LOSS_DEBUGGING
+ else
+ kdDebug(5006) << "Reloaded, but stil didn't find uid: " << uid << endl;
+#endif
+ // Then it's not here
+ return 0;
+}
+
+// This finds and sets the proper account for this folder if it has
+// not been done
+KMAcctCachedImap *KMFolderCachedImap::account() const
+{
+ if( (KMAcctCachedImap *)mAccount == 0 && kmkernel && kmkernel->acctMgr() ) {
+ // Find the account
+ mAccount = static_cast<KMAcctCachedImap *>( kmkernel->acctMgr()->findByName( name() ) );
+ }
+
+ return mAccount;
+}
+
+void KMFolderCachedImap::slotTroubleshoot()
+{
+ const int rc = DImapTroubleShootDialog::run();
+
+ if( rc == DImapTroubleShootDialog::RefreshCache ) {
+ // Refresh cache
+ if( !account() ) {
+ KMessageBox::sorry( 0, i18n("No account setup for this folder.\n"
+ "Please try running a sync before this.") );
+ return;
+ }
+ QString str = i18n("Are you sure you want to refresh the IMAP cache of "
+ "the folder %1 and all its subfolders?\nThis will "
+ "remove all changes you have done locally to your "
+ "folders.").arg( label() );
+ QString s1 = i18n("Refresh IMAP Cache");
+ QString s2 = i18n("&Refresh");
+ if( KMessageBox::warningContinueCancel( 0, str, s1, s2 ) ==
+ KMessageBox::Continue )
+ account()->invalidateIMAPFolders( this );
+ } else {
+ // Rebuild index file
+ switch ( rc ) {
+ case DImapTroubleShootDialog::ReindexAll:
+ {
+ KMFolderCachedImap *rootStorage = dynamic_cast<KMFolderCachedImap*>( account()->rootFolder() );
+ if ( rootStorage )
+ rootStorage->createIndexFromContentsRecursive();
+ break;
+ }
+ case DImapTroubleShootDialog::ReindexCurrent:
+ createIndexFromContents();
+ break;
+ case DImapTroubleShootDialog::ReindexRecursive:
+ createIndexFromContentsRecursive();
+ break;
+ default:
+ return;
+ }
+ KMessageBox::information( 0, i18n( "The index of this folder has been "
+ "recreated." ) );
+ writeIndex();
+ kmkernel->getKMMainWidget()->folderSelected();
+ }
+}
+
+void KMFolderCachedImap::serverSync( bool recurse )
+{
+ 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 ), QString::null, i18n("Reset && Sync"), KStdGuiItem::cancel() ) == KMessageBox::Yes ) {
+ mSyncState = SYNC_STATE_INITIAL;
+ } else return;
+ }
+
+ mRecurse = recurse;
+ assert( account() );
+
+ ProgressItem *progressItem = mAccount->mailCheckProgressItem();
+ if ( progressItem ) {
+ progressItem->reset();
+ progressItem->setTotalItems( 100 );
+ }
+ mProgress = 0;
+
+#if 0
+ if( mHoldSyncs ) {
+ // All done for this folder.
+ account()->mailCheckProgressItem()->setProgress( 100 );
+ mProgress = 100; // all done
+ newState( mProgress, i18n("Synchronization skipped"));
+ mSyncState = SYNC_STATE_INITIAL;
+ emit folderComplete( this, true );
+ return;
+ }
+#endif
+ mTentativeHighestUid = 0; // reset, last sync could have been canceled
+
+ serverSyncInternal();
+}
+
+QString 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";
+ }
+}
+
+/*
+ Progress calculation: each step is assigned a span. Initially the total is 100.
+ But if we skip a step, don't increase the progress.
+ This leaves more room for the step a with variable size (get_messages)
+ connecting 5
+ getuserrights 5
+ rename 5
+ check_uidvalidity 5
+ create_subfolders 5
+ put_messages 10 (but it can take a very long time, with many messages....)
+ upload_flags 5
+ list_subfolders 5
+ list_subfolders2 0 (all local)
+ delete_subfolders 5
+ list_messages 10
+ delete_messages 10
+ expunge_messages 5
+ get_messages variable (remaining-5) i.e. minimum 15.
+ check_annotations 0 (rare)
+ set_annotations 0 (rare)
+ get_annotations 2
+ set_acls 0 (rare)
+ get_acls 3
+
+ noContent folders have only a few of the above steps
+ (permissions, and all subfolder stuff), so its steps should be given more span
+
+ */
+
+// While the server synchronization is running, mSyncState will hold
+// the state that should be executed next
+void KMFolderCachedImap::serverSyncInternal()
+{
+ // This is used to stop processing when we're about to exit
+ // and the current job wasn't cancellable.
+ // For user-requested abort, we'll use signalAbortRequested instead.
+ if( kmkernel->mailCheckAborted() ) {
+ resetSyncState();
+ emit folderComplete( this, false );
+ return;
+ }
+
+ //kdDebug(5006) << label() << ": " << state2String( mSyncState ) << endl;
+ switch( mSyncState ) {
+ case SYNC_STATE_INITIAL:
+ {
+ mProgress = 0;
+ foldersForDeletionOnServer.clear();
+ newState( mProgress, i18n("Synchronizing"));
+
+ open("cachedimap");
+ if ( !noContent() )
+ mAccount->addLastUnreadMsgCount( this, countUnread() );
+
+ // Connect to the server (i.e. prepare the slave)
+ ImapAccountBase::ConnectionState cs = mAccount->makeConnection();
+ if ( cs == ImapAccountBase::Error ) {
+ // Cancelled by user, or slave can't start
+ // kdDebug(5006) << "makeConnection said Error, aborting." << endl;
+ // We stop here. We're already in SYNC_STATE_INITIAL for the next time.
+ newState( mProgress, i18n( "Error connecting to server %1" ).arg( mAccount->host() ) );
+ close("cachedimap");
+ emit folderComplete(this, false);
+ break;
+ } else if ( cs == ImapAccountBase::Connecting ) {
+ mAccount->setAnnotationCheckPassed( false );
+ // kdDebug(5006) << "makeConnection said Connecting, waiting for signal." << endl;
+ newState( mProgress, i18n("Connecting to %1").arg( mAccount->host() ) );
+ // We'll wait for the connectionResult signal from the account.
+ connect( mAccount, SIGNAL( connectionResult(int, const QString&) ),
+ this, SLOT( slotConnectionResult(int, const QString&) ) );
+ break;
+ } else {
+ // Connected
+ // kdDebug(5006) << "makeConnection said Connected, proceeding." << endl;
+ mSyncState = SYNC_STATE_GET_USERRIGHTS;
+ // Fall through to next state
+ }
+ }
+
+
+ case SYNC_STATE_GET_USERRIGHTS:
+ //kdDebug(5006) << "===== Syncing " << ( mImapPath.isEmpty() ? label() : mImapPath ) << endl;
+
+ mSyncState = SYNC_STATE_RENAME_FOLDER;
+
+ if( !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"));
+ connect( mAccount, SIGNAL( receivedUserRights( KMFolder* ) ),
+ this, SLOT( slotReceivedUserRights( KMFolder* ) ) );
+ mAccount->getUserRights( folder(), imapPath() ); // after connecting, due to the INBOX case
+ break;
+ }
+
+ case SYNC_STATE_RENAME_FOLDER:
+ {
+ mSyncState = SYNC_STATE_CHECK_UIDVALIDITY;
+ // Returns the new name if the folder was renamed, empty otherwise.
+ bool isResourceFolder = kmkernel->iCalIface().isStandardResourceFolder( folder() );
+ QString newName = mAccount->renamedFolder( imapPath() );
+ if ( !newName.isEmpty() && !folder()->isSystemFolder() && !isResourceFolder ) {
+ newState( mProgress, i18n("Renaming folder") );
+ CachedImapJob *job = new CachedImapJob( newName, CachedImapJob::tRenameFolder, this );
+ connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) );
+ connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
+ job->start();
+ break;
+ }
+ }
+
+ case SYNC_STATE_CHECK_UIDVALIDITY:
+ mSyncState = SYNC_STATE_CREATE_SUBFOLDERS;
+ if( !noContent() ) {
+ checkUidValidity();
+ break;
+ }
+ // Else carry on
+
+ case SYNC_STATE_CREATE_SUBFOLDERS:
+ mSyncState = SYNC_STATE_PUT_MESSAGES;
+ createNewFolders();
+ break;
+
+ case SYNC_STATE_PUT_MESSAGES:
+ mSyncState = SYNC_STATE_UPLOAD_FLAGS;
+ if( !noContent() ) {
+ uploadNewMessages();
+ break;
+ }
+ // Else carry on
+ case SYNC_STATE_UPLOAD_FLAGS:
+ mSyncState = SYNC_STATE_LIST_NAMESPACES;
+ if( !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 ) {
+ uploadFlags();
+ break;
+ } else {
+ //kdDebug(5006) << "Skipping flags upload, folder unchanged: " << label() << endl;
+ }
+ } else if ( mUserRights & KMail::ACLJobs::WriteSeenFlag ) {
+ if ( mStatusChangedLocally ) {
+ uploadSeenFlags();
+ break;
+ }
+ }
+ }
+ // Else carry on
+
+ case SYNC_STATE_LIST_NAMESPACES:
+ if ( this == mAccount->rootFolder() ) {
+ listNamespaces();
+ break;
+ }
+ mSyncState = SYNC_STATE_LIST_SUBFOLDERS;
+ // Else carry on
+
+ 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"));
+ }
+ break;
+
+ case SYNC_STATE_LIST_SUBFOLDERS2:
+ mSyncState = SYNC_STATE_DELETE_SUBFOLDERS;
+ mProgress += 10;
+ newState( mProgress, i18n("Retrieving subfolders"));
+ listDirectory2();
+ break;
+
+ case SYNC_STATE_DELETE_SUBFOLDERS:
+ mSyncState = SYNC_STATE_LIST_MESSAGES;
+ if( !foldersForDeletionOnServer.isEmpty() ) {
+ newState( mProgress, i18n("Deleting folders from server"));
+ CachedImapJob* job = new CachedImapJob( foldersForDeletionOnServer,
+ CachedImapJob::tDeleteFolders, this );
+ connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) );
+ connect( job, SIGNAL( finished() ), this, SLOT( slotFolderDeletionOnServerFinished() ) );
+ job->start();
+ break;
+ }
+ // Not needed, the next step emits newState very quick
+ //newState( mProgress, i18n("No folders to delete from server"));
+ // Carry on
+
+ case SYNC_STATE_LIST_MESSAGES:
+ mSyncState = SYNC_STATE_DELETE_MESSAGES;
+ if( !noContent() ) {
+ newState( mProgress, i18n("Retrieving message list"));
+ listMessages();
+ break;
+ }
+ // Else carry on
+
+ case SYNC_STATE_DELETE_MESSAGES:
+ mSyncState = SYNC_STATE_EXPUNGE_MESSAGES;
+ if( !noContent() ) {
+ if( deleteMessages() ) {
+ // Fine, we will continue with the next state
+ } else {
+ // No messages to delete, skip to GET_MESSAGES
+ newState( mProgress, i18n("No messages to delete..."));
+ mSyncState = SYNC_STATE_GET_MESSAGES;
+ serverSyncInternal();
+ }
+ break;
+ }
+ // Else carry on
+
+ case SYNC_STATE_EXPUNGE_MESSAGES:
+ mSyncState = SYNC_STATE_GET_MESSAGES;
+ if( !noContent() ) {
+ newState( mProgress, i18n("Expunging deleted messages"));
+ CachedImapJob *job = new CachedImapJob( QString::null,
+ CachedImapJob::tExpungeFolder, this );
+ connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) );
+ connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
+ job->start();
+ break;
+ }
+ // Else carry on
+
+ case SYNC_STATE_GET_MESSAGES:
+ mSyncState = SYNC_STATE_HANDLE_INBOX;
+ if( !noContent() ) {
+ if( !mMsgsForDownload.isEmpty() ) {
+ newState( mProgress, i18n("Retrieving new messages"));
+ CachedImapJob *job = new CachedImapJob( mMsgsForDownload,
+ CachedImapJob::tGetMessage,
+ this );
+ connect( job, SIGNAL( progress(unsigned long, unsigned long) ),
+ this, SLOT( slotProgress(unsigned long, unsigned long) ) );
+ connect( job, SIGNAL( finished() ), this, SLOT( slotUpdateLastUid() ) );
+ connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
+ job->start();
+ mMsgsForDownload.clear();
+ break;
+ } else {
+ newState( mProgress, i18n("No new messages from server"));
+ /* There were no messages to download, but it could be that we uploaded some
+ which we didn't need to download again because we already knew the uid.
+ Now that we are sure there is nothing to download, and everything that had
+ to be deleted on the server has been deleted, adjust our local notion of the
+ highes uid seen thus far. */
+ slotUpdateLastUid();
+ if( mLastUid == 0 && uidWriteTimer == -1 ) {
+ // This is probably a new and empty folder. Write the UID cache
+ if ( writeUidCache() == -1 ) {
+ resetSyncState();
+ emit folderComplete( this, false );
+ return;
+ }
+ }
+ }
+ }
+
+ // Else carry on
+
+ case SYNC_STATE_HANDLE_INBOX:
+ // Wrap up the 'download emails' stage. We always end up at 95 here.
+ mProgress = 95;
+ mSyncState = SYNC_STATE_TEST_ANNOTATIONS;
+
+ #define KOLAB_FOLDERTEST "/vendor/kolab/folder-test"
+ 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 ) )
+ && !imapPath().isEmpty() && imapPath() != "/" ) {
+ kdDebug(5006) << "Setting test attribute on folder: "<< folder()->prettyURL() << endl;
+ newState( mProgress, i18n("Checking annotation support"));
+
+ KURL url = mAccount->getUrl();
+ url.setPath( imapPath() );
+ KMail::AnnotationList annotations; // to be set
+
+ KMail::AnnotationAttribute attr( KOLAB_FOLDERTEST, "value.shared", "true" );
+ annotations.append( attr );
+
+ kdDebug(5006) << "Setting test attribute to "<< url << endl;
+ KIO::Job* job = AnnotationJobs::multiSetAnnotation( mAccount->slave(),
+ url, annotations );
+ ImapAccountBase::jobData jd( url.url(), folder() );
+ jd.cancellable = true; // we can always do so later
+ mAccount->insertJob(job, jd);
+ connect(job, SIGNAL(result(KIO::Job *)),
+ SLOT(slotTestAnnotationResult(KIO::Job *)));
+ break;
+ }
+
+ case SYNC_STATE_GET_ANNOTATIONS: {
+#define KOLAB_FOLDERTYPE "/vendor/kolab/folder-type"
+#define KOLAB_INCIDENCESFOR "/vendor/kolab/incidences-for"
+//#define KOLAB_FOLDERTYPE "/comment" //for testing, while cyrus-imap doesn't support /vendor/*
+ mSyncState = SYNC_STATE_SET_ANNOTATIONS;
+
+ bool needToGetInitialAnnotations = false;
+ if ( !noContent() ) {
+ // for a folder we didn't create ourselves: get annotation from server
+ if ( mAnnotationFolderType == "FROMSERVER" ) {
+ needToGetInitialAnnotations = true;
+ mAnnotationFolderType = QString::null;
+ } else {
+ updateAnnotationFolderType();
+ }
+ }
+
+ // 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() &&
+ ( kmkernel->iCalIface().isEnabled() || needToGetInitialAnnotations ) ) {
+ QStringList annotations; // list of annotations to be fetched
+ if ( !mAnnotationFolderTypeChanged || mAnnotationFolderType.isEmpty() )
+ annotations << KOLAB_FOLDERTYPE;
+ if ( !mIncidencesForChanged )
+ annotations << KOLAB_INCIDENCESFOR;
+ if ( !annotations.isEmpty() ) {
+ newState( mProgress, i18n("Retrieving annotations"));
+ KURL url = mAccount->getUrl();
+ url.setPath( imapPath() );
+ AnnotationJobs::MultiGetAnnotationJob* job =
+ AnnotationJobs::multiGetAnnotation( mAccount->slave(), url, annotations );
+ ImapAccountBase::jobData jd( url.url(), folder() );
+ jd.cancellable = true;
+ mAccount->insertJob(job, jd);
+
+ connect( job, SIGNAL(annotationResult(const QString&, const QString&, bool)),
+ SLOT(slotAnnotationResult(const QString&, const QString&, bool)) );
+ connect( job, SIGNAL(result(KIO::Job *)),
+ SLOT(slotGetAnnotationResult(KIO::Job *)) );
+ break;
+ }
+ }
+ } // case
+ case SYNC_STATE_SET_ANNOTATIONS:
+
+ mSyncState = SYNC_STATE_SET_ACLS;
+ if ( !noContent() && mAccount->hasAnnotationSupport() &&
+ ( mUserRights <= 0 || ( mUserRights & ACLJobs::Administer ) ) ) {
+ newState( mProgress, i18n("Setting annotations"));
+ KURL url = mAccount->getUrl();
+ url.setPath( imapPath() );
+ KMail::AnnotationList annotations; // to be set
+ if ( mAnnotationFolderTypeChanged && !mAnnotationFolderType.isEmpty() ) {
+ KMail::AnnotationAttribute attr( KOLAB_FOLDERTYPE, "value.shared", mAnnotationFolderType );
+ annotations.append( attr );
+ kdDebug(5006) << "Setting folder-type annotation for " << label() << " to " << mAnnotationFolderType << endl;
+ }
+ if ( mIncidencesForChanged ) {
+ const QString val = incidencesForToString( mIncidencesFor );
+ KMail::AnnotationAttribute attr( KOLAB_INCIDENCESFOR, "value.shared", val );
+ annotations.append( attr );
+ kdDebug(5006) << "Setting incidences-for annotation for " << label() << " to " << val << endl;
+ }
+ if ( !annotations.isEmpty() ) {
+ KIO::Job* job =
+ AnnotationJobs::multiSetAnnotation( mAccount->slave(), url, annotations );
+ ImapAccountBase::jobData jd( url.url(), folder() );
+ jd.cancellable = true; // we can always do so later
+ mAccount->insertJob(job, jd);
+
+ connect(job, SIGNAL(annotationChanged( const QString&, const QString&, const QString& ) ),
+ SLOT( slotAnnotationChanged( const QString&, const QString&, const QString& ) ));
+ connect(job, SIGNAL(result(KIO::Job *)),
+ SLOT(slotSetAnnotationResult(KIO::Job *)));
+ break;
+ }
+ }
+
+ case SYNC_STATE_SET_ACLS:
+ mSyncState = SYNC_STATE_GET_ACLS;
+
+ if( !noContent() && mAccount->hasACLSupport() &&
+ ( mUserRights <= 0 || ( mUserRights & ACLJobs::Administer ) ) ) {
+ bool hasChangedACLs = false;
+ ACLList::ConstIterator it = mACLList.begin();
+ for ( ; it != mACLList.end() && !hasChangedACLs; ++it ) {
+ hasChangedACLs = (*it).changed;
+ }
+ if ( hasChangedACLs ) {
+ newState( mProgress, i18n("Setting permissions"));
+ KURL url = mAccount->getUrl();
+ url.setPath( imapPath() );
+ KIO::Job* job = KMail::ACLJobs::multiSetACL( mAccount->slave(), url, mACLList );
+ ImapAccountBase::jobData jd( url.url(), folder() );
+ mAccount->insertJob(job, jd);
+
+ connect(job, SIGNAL(result(KIO::Job *)),
+ SLOT(slotMultiSetACLResult(KIO::Job *)));
+ connect(job, SIGNAL(aclChanged( const QString&, int )),
+ SLOT(slotACLChanged( const QString&, int )) );
+ break;
+ }
+ }
+
+ case SYNC_STATE_GET_ACLS:
+ mSyncState = SYNC_STATE_GET_QUOTA;
+
+ if( !noContent() && mAccount->hasACLSupport() ) {
+ newState( mProgress, i18n( "Retrieving permissions" ) );
+ mAccount->getACL( folder(), mImapPath );
+ connect( mAccount, SIGNAL(receivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )),
+ this, SLOT(slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )) );
+ break;
+ }
+ case SYNC_STATE_GET_QUOTA:
+ // Continue with the subfolders
+ mSyncState = SYNC_STATE_FIND_SUBFOLDERS;
+ if( !noContent() && mAccount->hasQuotaSupport() ) {
+ newState( mProgress, i18n("Getting quota information"));
+ KURL url = mAccount->getUrl();
+ url.setPath( imapPath() );
+ KIO::Job* job = KMail::QuotaJobs::getStorageQuota( mAccount->slave(), url );
+ ImapAccountBase::jobData jd( url.url(), folder() );
+ mAccount->insertJob(job, jd);
+ connect( job, SIGNAL( storageQuotaResult( const QuotaInfo& ) ),
+ SLOT( slotStorageQuotaResult( const QuotaInfo& ) ) );
+ connect( job, SIGNAL(result(KIO::Job *)),
+ SLOT(slotQuotaResult(KIO::Job *)) );
+ break;
+ }
+ case SYNC_STATE_FIND_SUBFOLDERS:
+ {
+ 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<KMFolderCachedImap*>(static_cast<KMFolder*>(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"));
+ 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, SIGNAL( folderComplete(KMFolderCachedImap*, bool) ),
+ this, 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, SIGNAL( folderComplete(KMFolderCachedImap*, bool) ),
+ this, 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 );
+ }
+ }
+ break;
+
+ default:
+ kdDebug(5006) << "KMFolderCachedImap::serverSyncInternal() WARNING: no such state "
+ << mSyncState << endl;
+ }
+}
+
+/* Connected to the imap account's connectionResult signal.
+ Emitted when the slave connected or failed to connect.
+*/
+void KMFolderCachedImap::slotConnectionResult( int errorCode, const QString& errorMsg )
+{
+ disconnect( mAccount, SIGNAL( connectionResult(int, const QString&) ),
+ this, SLOT( slotConnectionResult(int, const QString&) ) );
+ if ( !errorCode ) {
+ // Success
+ mSyncState = SYNC_STATE_GET_USERRIGHTS;
+ mProgress += 5;
+ serverSyncInternal();
+ } else {
+ // Error (error message already shown by the account)
+ newState( mProgress, KIO::buildErrorString( errorCode, errorMsg ));
+ emit folderComplete(this, false);
+ }
+}
+
+/* find new messages (messages without a UID) */
+QValueList<unsigned long> KMFolderCachedImap::findNewMessages()
+{
+ QValueList<unsigned long> result;
+ for( int i = 0; i < count(); ++i ) {
+ KMMsgBase *msg = getMsgBase( i );
+ if( !msg ) continue; /* what goes on if getMsg() returns 0? */
+ if ( msg->UID() == 0 )
+ result.append( msg->getMsgSerNum() );
+ }
+ return result;
+}
+
+/* Upload new messages to server */
+void KMFolderCachedImap::uploadNewMessages()
+{
+ QValueList<unsigned long> newMsgs = findNewMessages();
+ if( !newMsgs.isEmpty() ) {
+ if ( mUserRights <= 0 || ( mUserRights & ( KMail::ACLJobs::Insert ) ) ) {
+ newState( mProgress, i18n("Uploading messages to server"));
+ CachedImapJob *job = new CachedImapJob( newMsgs, CachedImapJob::tPutMessage, this );
+ connect( job, SIGNAL( progress( unsigned long, unsigned long) ),
+ this, SLOT( slotPutProgress(unsigned long, unsigned long) ) );
+ connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
+ job->start();
+ return;
+ } else {
+ KMCommand *command = rescueUnsyncedMessages();
+ connect( command, SIGNAL( completed( KMCommand * ) ),
+ this, SLOT( serverSyncInternal() ) );
+ }
+ } else { // nothing to upload
+ if ( mUserRights != mOldUserRights && (mOldUserRights & KMail::ACLJobs::Insert)
+ && !(mUserRights & KMail::ACLJobs::Insert) ) {
+ // write access revoked
+ KMessageBox::information( 0, i18n("<p>Your access rights to folder <b>%1</b> have been restricted, "
+ "it will no longer be possible to add messages to this folder.</p>").arg( folder()->prettyURL() ),
+ i18n("Acces rights revoked"), "KMailACLRevocationNotification" );
+ }
+ }
+ newState( mProgress, i18n("No messages to upload to server"));
+ serverSyncInternal();
+}
+
+/* Progress info during uploadNewMessages */
+void KMFolderCachedImap::slotPutProgress( unsigned long done, unsigned long total )
+{
+ // (going from mProgress to mProgress+10)
+ int progressSpan = 10;
+ newState( mProgress + (progressSpan * done) / total, QString::null );
+ if ( done == total ) // we're done
+ mProgress += progressSpan;
+}
+
+/* Upload message flags to server */
+void KMFolderCachedImap::uploadFlags()
+{
+ if ( !uidMap.isEmpty() ) {
+ mStatusFlagsJobs = 0;
+ newState( mProgress, i18n("Uploading status of messages to server"));
+
+ // FIXME DUPLICATED FROM KMFOLDERIMAP
+ QMap< QString, QStringList > groups;
+ //open(); //already done
+ for( int i = 0; i < count(); ++i ) {
+ KMMsgBase* msg = getMsgBase( i );
+ if( !msg || msg->UID() == 0 )
+ // Either not a valid message or not one that is on the server yet
+ continue;
+
+ QString flags = KMFolderImap::statusToFlags(msg->status(), mPermanentFlags);
+ // Collect uids for each typem of flags.
+ QString uid;
+ uid.setNum( msg->UID() );
+ groups[flags].append(uid);
+ }
+ QMapIterator< QString, QStringList > dit;
+ for( dit = groups.begin(); dit != groups.end(); ++dit ) {
+ QCString flags = dit.key().latin1();
+ QStringList sets = KMFolderImap::makeSets( (*dit), true );
+ mStatusFlagsJobs += sets.count(); // ### that's not in kmfolderimap....
+ // Send off a status setting job for each set.
+ for( QStringList::Iterator slit = sets.begin(); slit != sets.end(); ++slit ) {
+ QString imappath = imapPath() + ";UID=" + ( *slit );
+ mAccount->setImapStatus(folder(), imappath, flags);
+ }
+ }
+ // FIXME END DUPLICATED FROM KMFOLDERIMAP
+
+ if ( mStatusFlagsJobs ) {
+ connect( mAccount, SIGNAL( imapStatusChanged(KMFolder*, const QString&, bool) ),
+ this, SLOT( slotImapStatusChanged(KMFolder*, const QString&, bool) ) );
+ return;
+ }
+ }
+ newState( mProgress, i18n("No messages to upload to server"));
+ serverSyncInternal();
+}
+
+void KMFolderCachedImap::uploadSeenFlags()
+{
+ if ( !uidMap.isEmpty() ) {
+ mStatusFlagsJobs = 0;
+ newState( mProgress, i18n("Uploading status of messages to server"));
+
+ QValueList<ulong> seenUids, unseenUids;
+ for( int i = 0; i < count(); ++i ) {
+ KMMsgBase* msg = getMsgBase( i );
+ if( !msg || msg->UID() == 0 )
+ // Either not a valid message or not one that is on the server yet
+ continue;
+
+ if ( msg->status() & KMMsgStatusOld || msg->status() & KMMsgStatusRead )
+ seenUids.append( msg->UID() );
+ else
+ unseenUids.append( msg->UID() );
+ }
+ if ( !seenUids.isEmpty() ) {
+ QStringList sets = KMFolderImap::makeSets( seenUids, true );
+ mStatusFlagsJobs += sets.count();
+ for( QStringList::Iterator it = sets.begin(); it != sets.end(); ++it ) {
+ QString imappath = imapPath() + ";UID=" + ( *it );
+ mAccount->setImapSeenStatus( folder(), imappath, true );
+ }
+ }
+ if ( !unseenUids.isEmpty() ) {
+ QStringList sets = KMFolderImap::makeSets( unseenUids, true );
+ mStatusFlagsJobs += sets.count();
+ for( QStringList::Iterator it = sets.begin(); it != sets.end(); ++it ) {
+ QString imappath = imapPath() + ";UID=" + ( *it );
+ mAccount->setImapSeenStatus( folder(), imappath, false );
+ }
+ }
+
+ if ( mStatusFlagsJobs ) {
+ connect( mAccount, SIGNAL( imapStatusChanged(KMFolder*, const QString&, bool) ),
+ this, SLOT( slotImapStatusChanged(KMFolder*, const QString&, bool) ) );
+ return;
+ }
+ }
+ newState( mProgress, i18n("No messages to upload to server"));
+ serverSyncInternal();
+}
+
+void KMFolderCachedImap::slotImapStatusChanged(KMFolder* folder, const QString&, bool cont)
+{
+ if ( mSyncState == SYNC_STATE_INITIAL ){
+ //kdDebug(5006) << "IMAP status changed but reset " << endl;
+ return; // we were reset
+ }
+ //kdDebug(5006) << "IMAP status changed for folder: " << folder->prettyURL() << endl;
+ if ( folder->storage() == this ) {
+ --mStatusFlagsJobs;
+ if ( mStatusFlagsJobs == 0 || !cont ) // done or aborting
+ disconnect( mAccount, SIGNAL( imapStatusChanged(KMFolder*, const QString&, bool) ),
+ this, SLOT( slotImapStatusChanged(KMFolder*, const QString&, bool) ) );
+ if ( mStatusFlagsJobs == 0 && cont ) {
+ mProgress += 5;
+ serverSyncInternal();
+ //kdDebug(5006) << "Proceeding with mailcheck." << endl;
+ }
+ }
+}
+
+// This is not perfect, what if the status didn't really change? Oh well ...
+void KMFolderCachedImap::setStatus( int idx, KMMsgStatus status, bool toggle)
+{
+ KMFolderMaildir::setStatus( idx, status, toggle );
+ mStatusChangedLocally = true;
+}
+
+void KMFolderCachedImap::setStatus(QValueList<int>& ids, KMMsgStatus status, bool toggle)
+{
+ KMFolderMaildir::setStatus(ids, status, toggle);
+ mStatusChangedLocally = true;
+}
+
+/* Upload new folders to server */
+void KMFolderCachedImap::createNewFolders()
+{
+ QValueList<KMFolderCachedImap*> newFolders = findNewFolders();
+ //kdDebug(5006) << label() << " createNewFolders:" << newFolders.count() << " new folders." << endl;
+ if( !newFolders.isEmpty() ) {
+ newState( mProgress, i18n("Creating subfolders on server"));
+ CachedImapJob *job = new CachedImapJob( newFolders, CachedImapJob::tAddSubfolders, this );
+ connect( job, SIGNAL( result(KMail::FolderJob *) ), this, SLOT( slotIncreaseProgress() ) );
+ connect( job, SIGNAL( finished() ), this, SLOT( serverSyncInternal() ) );
+ job->start();
+ } else {
+ serverSyncInternal();
+ }
+}
+
+QValueList<KMFolderCachedImap*> KMFolderCachedImap::findNewFolders()
+{
+ QValueList<KMFolderCachedImap*> newFolders;
+ if( folder() && folder()->child() ) {
+ KMFolderNode *node = folder()->child()->first();
+ while( node ) {
+ if( !node->isDir() ) {
+ if( static_cast<KMFolder*>(node)->folderType() != KMFolderTypeCachedImap ) {
+ kdError(5006) << "KMFolderCachedImap::findNewFolders(): ARGH!!! "
+ << node->name() << " is not an IMAP folder\n";
+ node = folder()->child()->next();
+ assert(0);
+ }
+ KMFolderCachedImap* folder = static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
+ if( folder->imapPath().isEmpty() ) {
+ newFolders << folder;
+ }
+ }
+ node = folder()->child()->next();
+ }
+ }
+ return newFolders;
+}
+
+bool KMFolderCachedImap::deleteMessages()
+{
+ /* Delete messages from cache that are gone from the server */
+ QPtrList<KMMessage> 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
+ // us. So use msg pointers instead
+
+ QStringList uids;
+ QMap<ulong,int>::const_iterator it = uidMap.constBegin();
+ for( ; it != uidMap.end(); it++ ) {
+ ulong uid ( it.key() );
+ if( uid!=0 && !uidsOnServer.find( uid ) ) {
+ uids << QString::number( uid );
+ msgsForDeletion.append( getMsg( *it ) );
+ }
+ }
+
+ if( !msgsForDeletion.isEmpty() ) {
+#if MAIL_LOSS_DEBUGGING
+ if ( KMessageBox::warningYesNo(
+ 0, i18n( "<qt><p>Mails on the server in folder <b>%1</b> were deleted. "
+ "Do you want to delete them locally?<br>UIDs: %2</p></qt>" )
+ .arg( folder()->prettyURL() ).arg( uids.join(",") ) ) == KMessageBox::Yes )
+#endif
+ removeMsg( msgsForDeletion );
+ }
+
+ if ( mUserRights > 0 && !( mUserRights & KMail::ACLJobs::Delete ) )
+ return false;
+
+ /* Delete messages from the server that we dont have anymore */
+ if( !uidsForDeletionOnServer.isEmpty() ) {
+ newState( mProgress, i18n("Deleting removed messages from server"));
+ QStringList sets = KMFolderImap::makeSets( uidsForDeletionOnServer, true );
+ uidsForDeletionOnServer.clear();
+ kdDebug(5006) << "Deleting " << sets.count() << " sets of messages from server folder " << imapPath() << endl;
+ CachedImapJob *job = new CachedImapJob( sets, CachedImapJob::tDeleteMessage, this );
+ connect( job, SIGNAL( result(KMail::FolderJob *) ),
+ this, SLOT( slotDeleteMessagesResult(KMail::FolderJob *) ) );
+ job->start();
+ return true;
+ } else {
+
+ // Nothing to delete on the server, make sure the map is clear again.
+ // Normally this wouldn't be necessary, but there can be stale maps because of
+ // https://issues.kolab.org/issue3833.
+ mDeletedUIDsSinceLastSync.clear();
+ return false;
+ }
+}
+
+void KMFolderCachedImap::slotDeleteMessagesResult( KMail::FolderJob* job )
+{
+ if ( job->error() ) {
+ // Skip the EXPUNGE state if deleting didn't work, no need to show two error messages
+ mSyncState = SYNC_STATE_GET_MESSAGES;
+ } else {
+ // deleting on the server went fine, clear the pending deletions cache
+ mDeletedUIDsSinceLastSync.clear();
+ }
+ mProgress += 10;
+ serverSyncInternal();
+}
+
+void KMFolderCachedImap::checkUidValidity() {
+ // IMAP root folders don't seem to have a UID validity setting.
+ // Also, don't try the uid validity on new folders
+ if( imapPath().isEmpty() || imapPath() == "/" )
+ // Just proceed
+ serverSyncInternal();
+ else {
+ newState( mProgress, i18n("Checking folder validity"));
+ CachedImapJob *job = new CachedImapJob( FolderJob::tCheckUidValidity, this );
+ connect( job, SIGNAL(permanentFlags(int)), SLOT(slotPermanentFlags(int)) );
+ connect( job, SIGNAL( result( KMail::FolderJob* ) ),
+ this, SLOT( slotCheckUidValidityResult( KMail::FolderJob* ) ) );
+ job->start();
+ }
+}
+
+void KMFolderCachedImap::slotCheckUidValidityResult( KMail::FolderJob* job )
+{
+ if ( job->error() ) { // there was an error and the user chose "continue"
+ // We can't continue doing anything in the same folder though, it would delete all mails.
+ // But we can continue to subfolders if any. Well we can also try annotation/acl stuff...
+ mSyncState = SYNC_STATE_HANDLE_INBOX;
+ }
+ mProgress += 5;
+ serverSyncInternal();
+}
+
+void KMFolderCachedImap::slotPermanentFlags(int flags)
+{
+ mPermanentFlags = flags;
+}
+
+/* This will only list the messages in a folder.
+ No directory listing done*/
+void KMFolderCachedImap::listMessages() {
+ bool groupwareOnly = GlobalSettings::self()->showOnlyGroupwareFoldersForGroupwareAccount()
+ && GlobalSettings::self()->theIMAPResourceAccount() == (int)mAccount->id()
+ && folder()->isSystemFolder()
+ && mImapPath == "/INBOX/";
+ // Don't list messages on the root folder, and skip the inbox, if this is
+ // the inbox of a groupware-only dimap account
+ if( imapPath() == "/" || groupwareOnly ) {
+ serverSyncInternal();
+ return;
+ }
+
+ if( !mAccount->slave() ) { // sync aborted
+ resetSyncState();
+ emit folderComplete( this, false );
+ return;
+ }
+ uidsOnServer.clear();
+ uidsOnServer.resize( count() * 2 );
+ uidsForDeletionOnServer.clear();
+ mMsgsForDownload.clear();
+ mUidsForDownload.clear();
+ // listing is only considered successful if saw a syntactically correct imapdigest
+ mFoundAnIMAPDigest = false;
+
+ CachedImapJob* job = new CachedImapJob( FolderJob::tListMessages, this );
+ connect( job, SIGNAL( result(KMail::FolderJob *) ),
+ this, SLOT( slotGetLastMessagesResult(KMail::FolderJob *) ) );
+ job->start();
+}
+
+void KMFolderCachedImap::slotGetLastMessagesResult(KMail::FolderJob *job)
+{
+ getMessagesResult(job, true);
+}
+
+// Connected to the listMessages job in CachedImapJob
+void KMFolderCachedImap::slotGetMessagesData(KIO::Job * job, const QByteArray & data)
+{
+ KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
+ if ( it == mAccount->jobsEnd() ) { // Shouldn't happen
+ kdDebug(5006) << "could not find job!?!?!" << endl;
+ // be sure to reset the sync state, if the listing was partial we would
+ // otherwise delete not-listed mail locally, and on the next sync on the server
+ // as well
+ mSyncState = SYNC_STATE_HANDLE_INBOX;
+ serverSyncInternal(); /* HACK^W Fix: we should at least try to keep going */
+ return;
+ }
+ (*it).cdata += QCString(data, data.size() + 1);
+ int pos = (*it).cdata.find("\r\n--IMAPDIGEST");
+ if (pos > 0) {
+ int a = (*it).cdata.find("\r\nX-uidValidity:");
+ if (a != -1) {
+ int b = (*it).cdata.find("\r\n", a + 17);
+ setUidValidity((*it).cdata.mid(a + 17, b - a - 17));
+ }
+ a = (*it).cdata.find("\r\nX-Access:");
+ // Only trust X-Access (i.e. the imap select info) if we don't know mUserRights.
+ // The latter is more accurate (checked on every sync) whereas X-Access is only
+ // 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 ) {
+ int b = (*it).cdata.find("\r\n", a + 12);
+ const QString access = (*it).cdata.mid(a + 12, b - a - 12);
+ setReadOnly( access == "Read only" );
+ }
+ (*it).cdata.remove(0, pos);
+ mFoundAnIMAPDigest = true;
+ }
+ pos = (*it).cdata.find("\r\n--IMAPDIGEST", 1);
+ // Start with something largish when rebuilding the cache
+ if ( uidsOnServer.size() == 0 )
+ uidsOnServer.resize( KMail::nextPrime( 2000 ) );
+ const int v = 42;
+ while (pos >= 0) {
+ /*
+ KMMessage msg;
+ msg.fromString((*it).cdata.mid(16, pos - 16));
+ const int flags = msg.headerField("X-Flags").toInt();
+ const ulong size = msg.headerField("X-Length").toULong();
+ const ulong uid = msg.UID();
+ */
+ // The below is optimized for speed, not prettiness. The commented out chunk
+ // above was the solution copied from kmfolderimap, and it's 15-20% slower.
+ const QCString& entry( (*it).cdata );
+ const int indexOfUID = entry.find("X-UID", 16);
+ const int startOfUIDValue = indexOfUID + 7;
+ const int indexOfLength = entry.find("X-Length", startOfUIDValue ); // we know length comes after UID
+ const int startOfLengthValue = indexOfLength + 10;
+ const int indexOfFlags = entry.find("X-Flags", startOfLengthValue ); // we know flags comes last
+ const int startOfFlagsValue = indexOfFlags + 9;
+
+ const int flags = entry.mid( startOfFlagsValue, entry.find( '\r', startOfFlagsValue ) - startOfFlagsValue ).toInt();
+ const ulong size = entry.mid( startOfLengthValue, entry.find( '\r', startOfLengthValue ) - startOfLengthValue ).toULong();
+ const ulong uid = entry.mid( startOfUIDValue, entry.find( '\r', startOfUIDValue ) - startOfUIDValue ).toULong();
+
+ const bool deleted = ( flags & 8 );
+ if ( !deleted ) {
+ if( uid != 0 ) {
+ if ( uidsOnServer.count() == uidsOnServer.size() ) {
+ uidsOnServer.resize( KMail::nextPrime( uidsOnServer.size() * 2 ) );
+ //kdDebug( 5006 ) << "Resizing to: " << uidsOnServer.size() << endl;
+ }
+ uidsOnServer.insert( uid, &v );
+ }
+ bool redownload = false;
+ if ( uid <= lastUid() ) {
+ /*
+ * If this message UID is not present locally, then it must
+ * have been deleted by the user, so we delete it on the
+ * server also. If we don't have delete permissions on the server,
+ * re-download the message, it must have vanished by some error, or
+ * while we still thought we were allowed to delete (ACL change).
+ *
+ * This relies heavily on lastUid() being correct at all times.
+ */
+ // kdDebug(5006) << "KMFolderCachedImap::slotGetMessagesData() : folder "<<label()<<" already has msg="<<msg->headerField("Subject") << ", UID="<<uid << ", lastUid = " << mLastUid << endl;
+ KMMsgBase *existingMessage = findByUID(uid);
+ if( !existingMessage ) {
+#if MAIL_LOSS_DEBUGGING
+ kdDebug(5006) << "Looking at uid " << uid << " high water is: " << lastUid() << " we should delete it" << endl;
+#endif
+ // double check we deleted it since the last sync
+ if ( mDeletedUIDsSinceLastSync.contains(uid) ) {
+ if ( mUserRights <= 0 || ( 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
+ uidsForDeletionOnServer << uid;
+ } else {
+ redownload = true;
+ }
+ } else {
+ kdDebug(5006) << "WARNING: ####### " << endl;
+ kdDebug(5006) << "Message locally missing but not deleted in folder: " << folder()->prettyURL() << endl;
+ kdDebug(5006) << "The missing UID: " << uid << ". It will be redownloaded " << endl;
+ redownload = true;
+ }
+
+ } else {
+ // if this is a read only folder, ignore status updates from the server
+ // since we can't write our status back our local version is what has to
+ // be considered correct.
+ if ( !mReadOnly || !GlobalSettings::allowLocalFlags() ) {
+ /* The message is OK, update flags */
+ KMFolderImap::flagsToStatus( existingMessage, flags, false, mReadOnly ? INT_MAX : mPermanentFlags );
+ } else if ( mUserRights & KMail::ACLJobs::WriteSeenFlag ) {
+ KMFolderImap::seenFlagToStatus( existingMessage, flags );
+ }
+ }
+ // kdDebug(5006) << "message with uid " << uid << " found in the local cache. " << endl;
+ }
+ if ( uid > lastUid() || redownload ) {
+#if MAIL_LOSS_DEBUGGING
+ kdDebug(5006) << "Looking at uid " << uid << " high water is: " << lastUid() << " we should download it" << endl;
+#endif
+ // The message is new since the last sync, but we might have just uploaded it, in which case
+ // the uid map already contains it.
+ if ( !uidMap.contains( uid ) ) {
+ mMsgsForDownload << KMail::CachedImapJob::MsgForDownload(uid, flags, size);
+ if( imapPath() == "/INBOX/" )
+ mUidsForDownload << uid;
+ }
+ // Remember the highest uid and once the download is completed, update mLastUid
+ if ( uid > mTentativeHighestUid ) {
+#if MAIL_LOSS_DEBUGGING
+ kdDebug(5006) << "Setting the tentative highest UID to: " << uid << endl;
+#endif
+ mTentativeHighestUid = uid;
+ }
+ }
+ }
+ (*it).cdata.remove(0, pos);
+ (*it).done++;
+ pos = (*it).cdata.find("\r\n--IMAPDIGEST", 1);
+ }
+}
+
+void KMFolderCachedImap::getMessagesResult( KMail::FolderJob *job, bool lastSet )
+{
+ mProgress += 10;
+ if ( !job->error() && !mFoundAnIMAPDigest ) {
+ kdWarning(5006) << "######## Folderlisting did not complete, but there was no error! "
+ "Aborting sync of folder: " << folder()->prettyURL() << endl;
+#if MAIL_LOSS_DEBUGGING
+ kmkernel->emergencyExit( i18n("Folder listing failed in interesting ways." ) );
+#endif
+ }
+ if( job->error() ) { // error listing messages but the user chose to continue
+ mContentState = imapNoInformation;
+ mSyncState = SYNC_STATE_HANDLE_INBOX; // be sure not to continue in this folder
+ } else {
+ if( lastSet ) { // always true here (this comes from online-imap...)
+ mContentState = imapFinished;
+ mStatusChangedLocally = false; // we are up to date again
+ }
+ }
+ serverSyncInternal();
+}
+
+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;
+ // Progress info while retrieving new emails
+ // (going from mProgress to mProgress+progressSpan)
+ newState( mProgress + (progressSpan * done) / total, QString::null );
+}
+
+void KMFolderCachedImap::setAccount(KMAcctCachedImap *aAccount)
+{
+ assert( aAccount->isA("KMAcctCachedImap") );
+ mAccount = aAccount;
+ if( imapPath()=="/" ) aAccount->setFolder( folder() );
+
+ // Folder was renamed in a previous session, and the user didn't sync yet
+ QString newName = mAccount->renamedFolder( imapPath() );
+ if ( !newName.isEmpty() )
+ folder()->setLabel( newName );
+
+ if( !folder() || !folder()->child() || !folder()->child()->count() ) return;
+ for( KMFolderNode* node = folder()->child()->first(); node;
+ node = folder()->child()->next() )
+ if (!node->isDir())
+ static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage())->setAccount(aAccount);
+}
+
+void KMFolderCachedImap::listNamespaces()
+{
+ ImapAccountBase::ListType type = ImapAccountBase::List;
+ if ( mAccount->onlySubscribedFolders() )
+ type = ImapAccountBase::ListSubscribed;
+
+ kdDebug(5006) << "listNamespaces " << mNamespacesToList << endl;
+ if ( mNamespacesToList.isEmpty() ) {
+ mSyncState = SYNC_STATE_DELETE_SUBFOLDERS;
+ mPersonalNamespacesCheckDone = true;
+
+ QStringList ns = mAccount->namespaces()[ImapAccountBase::OtherUsersNS];
+ ns += mAccount->namespaces()[ImapAccountBase::SharedNS];
+ mNamespacesToCheck = ns.count();
+ for ( QStringList::Iterator it = ns.begin(); it != ns.end(); ++it )
+ {
+ if ( (*it).isEmpty() ) {
+ // ignore empty listings as they have been listed before
+ --mNamespacesToCheck;
+ continue;
+ }
+ KMail::ListJob* job = new KMail::ListJob( mAccount, type, this, mAccount->addPathToNamespace( *it ) );
+ job->setHonorLocalSubscription( true );
+ connect( job, SIGNAL(receivedFolders(const QStringList&, const QStringList&,
+ const QStringList&, const QStringList&, const ImapAccountBase::jobData&)),
+ this, SLOT(slotCheckNamespace(const QStringList&, const QStringList&,
+ const QStringList&, const QStringList&, const ImapAccountBase::jobData&)));
+ job->start();
+ }
+ if ( mNamespacesToCheck == 0 ) {
+ serverSyncInternal();
+ }
+ return;
+ }
+ mPersonalNamespacesCheckDone = false;
+
+ QString ns = mNamespacesToList.front();
+ mNamespacesToList.pop_front();
+
+ mSyncState = SYNC_STATE_LIST_SUBFOLDERS2;
+ newState( mProgress, i18n("Retrieving folders for namespace %1").arg(ns));
+ KMail::ListJob* job = new KMail::ListJob( mAccount, type, this,
+ mAccount->addPathToNamespace( ns ) );
+ job->setNamespace( ns );
+ job->setHonorLocalSubscription( true );
+ connect( job, SIGNAL(receivedFolders(const QStringList&, const QStringList&,
+ const QStringList&, const QStringList&, const ImapAccountBase::jobData&)),
+ this, SLOT(slotListResult(const QStringList&, const QStringList&,
+ const QStringList&, const QStringList&, const ImapAccountBase::jobData&)));
+ job->start();
+}
+
+void KMFolderCachedImap::slotCheckNamespace( const QStringList& subfolderNames,
+ const QStringList& subfolderPaths,
+ const QStringList& subfolderMimeTypes,
+ const QStringList& subfolderAttributes,
+ const ImapAccountBase::jobData& jobData )
+{
+ Q_UNUSED( subfolderPaths );
+ Q_UNUSED( subfolderMimeTypes );
+ Q_UNUSED( subfolderAttributes );
+ --mNamespacesToCheck;
+ kdDebug(5006) << "slotCheckNamespace " << subfolderNames << ",remain=" <<
+ mNamespacesToCheck << endl;
+
+ // get a correct foldername:
+ // strip / and make sure it does not contain the delimiter
+ QString name = jobData.path.mid( 1, jobData.path.length()-2 );
+ name.remove( mAccount->delimiterForNamespace( name ) );
+ if ( name.isEmpty() ) {
+ // should not happen
+ kdWarning(5006) << "slotCheckNamespace: ignoring empty folder!" << endl;
+ return;
+ }
+
+ folder()->createChildFolder();
+ KMFolderNode *node = 0;
+ for ( node = folder()->child()->first(); node;
+ node = folder()->child()->next())
+ {
+ if ( !node->isDir() && node->name() == name )
+ break;
+ }
+ if ( !subfolderNames.isEmpty() ) {
+ if ( node ) {
+ // folder exists so we have nothing to do - it will be listed later
+ kdDebug(5006) << "found namespace folder " << name << endl;
+ } else
+ {
+ // create folder
+ kdDebug(5006) << "create namespace folder " << name << endl;
+ KMFolder* newFolder = folder()->child()->createFolder( name, false,
+ KMFolderTypeCachedImap );
+ if ( newFolder ) {
+ KMFolderCachedImap *f = static_cast<KMFolderCachedImap*>( newFolder->storage() );
+ f->setImapPath( mAccount->addPathToNamespace( name ) );
+ f->setNoContent( true );
+ f->setAccount( mAccount );
+ f->close("cachedimap");
+ kmkernel->dimapFolderMgr()->contentsChanged();
+ }
+ }
+ } else {
+ if ( node ) {
+ kdDebug(5006) << "delete namespace folder " << name << endl;
+ KMFolder* fld = static_cast<KMFolder*>(node);
+ kmkernel->dimapFolderMgr()->remove( fld );
+ }
+ }
+
+ if ( mNamespacesToCheck == 0 ) {
+ // all namespaces are done so continue with the next step
+ serverSyncInternal();
+ }
+}
+
+// This lists the subfolders on the server
+// and (in slotListResult) takes care of folders that have been removed on the server
+bool KMFolderCachedImap::listDirectory()
+{
+ if( !mAccount->slave() ) { // sync aborted
+ resetSyncState();
+ emit folderComplete( this, false );
+ return false;
+ }
+ mSubfolderState = imapInProgress;
+
+ // get the folders
+ ImapAccountBase::ListType type = ImapAccountBase::List;
+ if ( mAccount->onlySubscribedFolders() )
+ type = ImapAccountBase::ListSubscribed;
+ KMail::ListJob* job = new KMail::ListJob( mAccount, type, this );
+ job->setHonorLocalSubscription( true );
+ connect( job, SIGNAL(receivedFolders(const QStringList&, const QStringList&,
+ const QStringList&, const QStringList&, const ImapAccountBase::jobData&)),
+ this, SLOT(slotListResult(const QStringList&, const QStringList&,
+ const QStringList&, const QStringList&, const ImapAccountBase::jobData&)));
+ job->start();
+
+ return true;
+}
+
+void KMFolderCachedImap::slotListResult( const QStringList& folderNames,
+ const QStringList& folderPaths,
+ const QStringList& folderMimeTypes,
+ const QStringList& folderAttributes,
+ const ImapAccountBase::jobData& jobData )
+{
+ Q_UNUSED( jobData );
+ //kdDebug(5006) << label() << ": folderNames=" << folderNames << " folderPaths="
+ //<< folderPaths << " mimeTypes=" << folderMimeTypes << endl;
+ mSubfolderNames = folderNames;
+ mSubfolderPaths = folderPaths;
+ mSubfolderMimeTypes = folderMimeTypes;
+ mSubfolderState = imapFinished;
+ mSubfolderAttributes = folderAttributes;
+ kdDebug(5006) << "##### setting subfolder attributes: " << mSubfolderAttributes << endl;
+
+ folder()->createChildFolder();
+ KMFolderNode *node = folder()->child()->first();
+ bool root = ( this == mAccount->rootFolder() );
+
+ QPtrList<KMFolder> toRemove;
+ bool emptyList = ( root && mSubfolderNames.empty() );
+ if ( !emptyList ) {
+ while (node) {
+ if (!node->isDir() ) {
+ KMFolderCachedImap *f = static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
+
+ if ( mSubfolderNames.findIndex(node->name()) == -1 ) {
+ QString name = node->name();
+ // as more than one namespace can be listed in the root folder we need to make sure
+ // that the folder is within the current namespace
+ bool isInNamespace = ( jobData.curNamespace.isEmpty() ||
+ jobData.curNamespace == mAccount->namespaceForFolder( f ) );
+ // ignore some cases
+ bool ignore = root && ( f->imapPath() == "/INBOX/" ||
+ mAccount->isNamespaceFolder( name ) || !isInNamespace );
+
+ // This subfolder isn't present on the server
+ if( !f->imapPath().isEmpty() && !ignore ) {
+ // The folder has an imap path set, so it has been
+ // on the server before. Delete it locally.
+ toRemove.append( f->folder() );
+ kdDebug(5006) << node->name() << " isn't on the server. It has an imapPath -> delete it locally" << endl;
+ }
+ } else { // folder both local and on server
+ //kdDebug(5006) << node->name() << " is on the server." << endl;
+
+ /**
+ * Store the folder attributes for every subfolder.
+ */
+ int index = mSubfolderNames.findIndex( node->name() );
+ f->mFolderAttributes = folderAttributes[ index ];
+ }
+ } else {
+ //kdDebug(5006) << "skipping dir node:" << node->name() << endl;
+ }
+ node = folder()->child()->next();
+ }
+ }
+
+ for ( KMFolder* doomed=toRemove.first(); doomed; doomed = toRemove.next() ) {
+ rescueUnsyncedMessagesAndDeleteFolder( doomed );
+ }
+
+ mProgress += 5;
+
+ // just in case there is nothing to rescue
+ slotRescueDone( 0 );
+}
+
+// This synchronizes the local folders as needed (creation/deletion). No network communication here.
+void KMFolderCachedImap::listDirectory2()
+{
+ QString path = folder()->path();
+ kmkernel->dimapFolderMgr()->quiet(true);
+
+ bool root = ( this == mAccount->rootFolder() );
+ if ( root && !mAccount->hasInbox() )
+ {
+ KMFolderCachedImap *f = 0;
+ KMFolderNode *node;
+ // create the INBOX
+ for (node = folder()->child()->first(); node; node = folder()->child()->next())
+ if (!node->isDir() && node->name() == "INBOX") break;
+ if (node) {
+ f = static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
+ } else {
+ KMFolder* newFolder = folder()->child()->createFolder("INBOX", true, KMFolderTypeCachedImap);
+ if ( newFolder ) {
+ f = static_cast<KMFolderCachedImap*>(newFolder->storage());
+ }
+ }
+ if ( f ) {
+ f->setAccount( mAccount );
+ f->setImapPath( "/INBOX/" );
+ f->folder()->setLabel( i18n("inbox") );
+ }
+ if (!node) {
+ if ( f )
+ f->close("cachedimap");
+ kmkernel->dimapFolderMgr()->contentsChanged();
+ }
+ // so we have an INBOX
+ mAccount->setHasInbox( true );
+ }
+
+ if ( root && !mSubfolderNames.isEmpty() ) {
+ KMFolderCachedImap* parent =
+ findParent( mSubfolderPaths.first(), mSubfolderNames.first() );
+ if ( parent ) {
+ kdDebug(5006) << "KMFolderCachedImap::listDirectory2 - pass listing to "
+ << parent->label() << endl;
+ mSubfolderNames.clear();
+ }
+ }
+
+ // Find all subfolders present on server but not on disk
+ QValueVector<int> foldersNewOnServer;
+ for (uint i = 0; i < mSubfolderNames.count(); i++) {
+
+ // Find the subdir, if already present
+ KMFolderCachedImap *f = 0;
+ KMFolderNode *node = 0;
+ for (node = folder()->child()->first(); node;
+ node = folder()->child()->next())
+ if (!node->isDir() && node->name() == mSubfolderNames[i]) break;
+
+ if (!node) {
+ // This folder is not present here
+ // Either it's new on the server, or we just deleted it.
+ QString subfolderPath = mSubfolderPaths[i];
+ // The code used to look at the uidcache to know if it was "just deleted".
+ // But this breaks with noContent folders and with shared folders.
+ // So instead we keep a list in the account.
+ bool locallyDeleted = mAccount->isDeletedFolder( subfolderPath );
+ // That list is saved/restored across sessions, but to avoid any mistake,
+ // ask for confirmation if the folder was deleted in a previous session
+ // (could be that the folder was deleted & recreated meanwhile from another client...)
+ if ( !locallyDeleted && mAccount->isPreviouslyDeletedFolder( subfolderPath ) ) {
+ locallyDeleted = KMessageBox::warningYesNo(
+ 0, i18n( "<qt><p>It seems that the folder <b>%1</b> was deleted. Do you want to delete it from the server?</p></qt>" ).arg( mSubfolderNames[i] ), QString::null, KStdGuiItem::del(), KStdGuiItem::cancel() ) == KMessageBox::Yes;
+ }
+
+ if ( locallyDeleted ) {
+ kdDebug(5006) << subfolderPath << " was deleted locally => delete on server." << endl;
+ foldersForDeletionOnServer += mAccount->deletedFolderPaths( subfolderPath ); // grab all subsubfolders too
+ } else {
+ kdDebug(5006) << subfolderPath << " is a new folder on the server => create local cache" << endl;
+ foldersNewOnServer.append( i );
+ }
+ } else { // Folder found locally
+ if( static_cast<KMFolder*>(node)->folderType() == KMFolderTypeCachedImap )
+ f = dynamic_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
+ if( f ) {
+ // kdDebug(5006) << "folder("<<f->name()<<")->imapPath()=" << f->imapPath()
+ // << "\nSetting imapPath " << mSubfolderPaths[i] << endl;
+ // Write folder settings
+ f->setAccount(mAccount);
+ f->setNoContent(mSubfolderMimeTypes[i] == "inode/directory");
+ f->setNoChildren(mSubfolderMimeTypes[i] == "message/digest");
+ f->setImapPath(mSubfolderPaths[i]);
+ }
+ }
+ }
+
+ /* In case we are ignoring non-groupware folders, and this is the groupware
+ * main account, find out the contents types of folders that have newly
+ * appeared on the server. Otherwise just create them and finish listing.
+ * If a folder is already known to be locally unsubscribed, it won't be
+ * listed at all, on this level, so these are only folders that we are
+ * seeing for the first time. */
+
+ /* Note: We ask the globalsettings, and not the current state of the
+ * kmkernel->iCalIface().isEnabled(), since that is false during the
+ * very first sync, where we already want to filter. */
+ if ( GlobalSettings::self()->showOnlyGroupwareFoldersForGroupwareAccount()
+ && GlobalSettings::self()->theIMAPResourceAccount() == (int)mAccount->id()
+ && mAccount->hasAnnotationSupport()
+ && GlobalSettings::self()->theIMAPResourceEnabled()
+ && !foldersNewOnServer.isEmpty() ) {
+
+ QStringList paths;
+ for ( uint i = 0; i < foldersNewOnServer.count(); ++i )
+ paths << mSubfolderPaths[ foldersNewOnServer[i] ];
+
+ AnnotationJobs::MultiUrlGetAnnotationJob* job =
+ AnnotationJobs::multiUrlGetAnnotation( mAccount->slave(), mAccount->getUrl(), paths, KOLAB_FOLDERTYPE );
+ ImapAccountBase::jobData jd( QString::null, folder() );
+ jd.cancellable = true;
+ mAccount->insertJob(job, jd);
+ connect( job, SIGNAL(result(KIO::Job *)),
+ SLOT(slotMultiUrlGetAnnotationResult(KIO::Job *)) );
+
+ } else {
+ createFoldersNewOnServerAndFinishListing( foldersNewOnServer );
+ }
+}
+
+void KMFolderCachedImap::createFoldersNewOnServerAndFinishListing( const QValueVector<int> foldersNewOnServer )
+{
+ for ( uint i = 0; i < foldersNewOnServer.count(); ++i ) {
+ int idx = foldersNewOnServer[i];
+ KMFolder* newFolder = folder()->child()->createFolder( mSubfolderNames[idx], false, KMFolderTypeCachedImap);
+ if (newFolder) {
+ KMFolderCachedImap *f = dynamic_cast<KMFolderCachedImap*>(newFolder->storage());
+ kdDebug(5006) << " ####### Locally creating folder " << mSubfolderNames[idx] <<endl;
+ f->close("cachedimap");
+ f->setAccount(mAccount);
+ f->mAnnotationFolderType = "FROMSERVER";
+ f->setNoContent(mSubfolderMimeTypes[idx] == "inode/directory");
+ f->setNoChildren(mSubfolderMimeTypes[idx] == "message/digest");
+ f->setImapPath(mSubfolderPaths[idx]);
+ f->mFolderAttributes = mSubfolderAttributes[idx];
+ kdDebug(5006) << " ####### Attributes: " << f->mFolderAttributes <<endl;
+ //kdDebug(5006) << subfolderPath << ": mAnnotationFolderType set to FROMSERVER" << endl;
+ kmkernel->dimapFolderMgr()->contentsChanged();
+ } else {
+ kdDebug(5006) << "can't create folder " << mSubfolderNames[idx] <<endl;
+ }
+ }
+
+ kmkernel->dimapFolderMgr()->quiet(false);
+ emit listComplete(this);
+ if ( !mPersonalNamespacesCheckDone ) {
+ // we're not done with the namespaces
+ mSyncState = SYNC_STATE_LIST_NAMESPACES;
+ }
+ serverSyncInternal();
+}
+
+//-----------------------------------------------------------------------------
+KMFolderCachedImap* KMFolderCachedImap::findParent( const QString& path,
+ const QString& name )
+{
+ QString parent = path.left( path.length() - name.length() - 2 );
+ if ( parent.length() > 1 )
+ {
+ // extract name of the parent
+ parent = parent.right( parent.length() - 1 );
+ if ( parent != label() )
+ {
+ KMFolderNode *node = folder()->child()->first();
+ // look for a better parent
+ while ( node )
+ {
+ if ( node->name() == parent )
+ {
+ KMFolder* fld = static_cast<KMFolder*>(node);
+ KMFolderCachedImap* imapFld =
+ static_cast<KMFolderCachedImap*>( fld->storage() );
+ return imapFld;
+ }
+ node = folder()->child()->next();
+ }
+ }
+ }
+ return 0;
+}
+
+void KMFolderCachedImap::slotSubFolderComplete(KMFolderCachedImap* sub, bool success)
+{
+ Q_UNUSED(sub);
+ //kdDebug(5006) << label() << " slotSubFolderComplete: " << sub->label() << endl;
+ if ( success ) {
+ serverSyncInternal();
+ }
+ else
+ {
+ // success == false means the sync was aborted.
+ if ( mCurrentSubfolder ) {
+ Q_ASSERT( sub == mCurrentSubfolder );
+ disconnect( mCurrentSubfolder, SIGNAL( folderComplete(KMFolderCachedImap*, bool) ),
+ this, SLOT( slotSubFolderComplete(KMFolderCachedImap*, bool) ) );
+ mCurrentSubfolder = 0;
+ }
+
+ mSubfoldersForSync.clear();
+ mSyncState = SYNC_STATE_INITIAL;
+ close("cachedimap");
+ emit folderComplete( this, false );
+ }
+}
+
+void KMFolderCachedImap::slotSimpleData(KIO::Job * job, const QByteArray & data)
+{
+ KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
+ if (it == mAccount->jobsEnd()) return;
+ QBuffer buff((*it).data);
+ buff.open(IO_WriteOnly | IO_Append);
+ buff.writeBlock(data.data(), data.size());
+ buff.close();
+}
+
+FolderJob*
+KMFolderCachedImap::doCreateJob( KMMessage *msg, FolderJob::JobType jt, KMFolder *folder,
+ QString, const AttachmentStrategy* ) const
+{
+ QPtrList<KMMessage> msgList;
+ msgList.append( msg );
+ CachedImapJob *job = new CachedImapJob( msgList, jt, folder? static_cast<KMFolderCachedImap*>( folder->storage() ):0 );
+ job->setParentFolder( this );
+ return job;
+}
+
+FolderJob*
+KMFolderCachedImap::doCreateJob( QPtrList<KMMessage>& msgList, const QString& sets,
+ FolderJob::JobType jt, KMFolder *folder ) const
+{
+ //FIXME: how to handle sets here?
+ Q_UNUSED( sets );
+ CachedImapJob *job = new CachedImapJob( msgList, jt, folder? static_cast<KMFolderCachedImap*>( folder->storage() ):0 );
+ job->setParentFolder( this );
+ return job;
+}
+
+void
+KMFolderCachedImap::setUserRights( unsigned int userRights )
+{
+ mUserRights = userRights;
+ writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
+}
+
+void
+KMFolderCachedImap::slotReceivedUserRights( KMFolder* folder )
+{
+ if ( folder->storage() == this ) {
+ disconnect( mAccount, SIGNAL( receivedUserRights( KMFolder* ) ),
+ this, SLOT( slotReceivedUserRights( KMFolder* ) ) );
+ if ( mUserRights == 0 ) // didn't work
+ mUserRights = -1; // error code (used in folderdia)
+ else
+ setReadOnly( ( mUserRights & KMail::ACLJobs::Insert ) == 0 );
+ mProgress += 5;
+ serverSyncInternal();
+ }
+}
+
+void
+KMFolderCachedImap::setReadOnly( bool readOnly )
+{
+ if ( readOnly != mReadOnly ) {
+ mReadOnly = readOnly;
+ emit readOnlyChanged( folder() );
+ }
+}
+
+void
+KMFolderCachedImap::slotReceivedACL( KMFolder* folder, KIO::Job*, const KMail::ACLList& aclList )
+{
+ if ( folder->storage() == this ) {
+ disconnect( mAccount, SIGNAL(receivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )),
+ this, SLOT(slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& )) );
+ mACLList = aclList;
+ serverSyncInternal();
+ }
+}
+
+void
+KMFolderCachedImap::slotStorageQuotaResult( const QuotaInfo& info )
+{
+ setQuotaInfo( info );
+}
+
+void KMFolderCachedImap::setQuotaInfo( const QuotaInfo & info )
+{
+ if ( info != mQuotaInfo ) {
+ mQuotaInfo = info;
+ writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
+ emit folderSizeChanged();
+ }
+}
+
+void
+KMFolderCachedImap::setACLList( const ACLList& arr )
+{
+ mACLList = arr;
+}
+
+void
+KMFolderCachedImap::slotMultiSetACLResult(KIO::Job *job)
+{
+ KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
+ if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen
+ if ( (*it).parent != folder() ) return; // Shouldn't happen
+
+ if ( job->error() )
+ // Display error but don't abort the sync just for this
+ // PENDING(dfaure) reconsider using handleJobError now that it offers continue/cancel
+ job->showErrorDialog();
+ else
+ kmkernel->iCalIface().addFolderChange( folder(), KMailICalIfaceImpl::ACL );
+
+ if (mAccount->slave()) mAccount->removeJob(job);
+ serverSyncInternal();
+}
+
+void
+KMFolderCachedImap::slotACLChanged( const QString& userId, int permissions )
+{
+ // The job indicates success in changing the permissions for this user
+ // -> we note that it's been done.
+ for( ACLList::Iterator it = mACLList.begin(); it != mACLList.end(); ++it ) {
+ if ( (*it).userId == userId && (*it).permissions == permissions ) {
+ if ( permissions == -1 ) // deleted
+ mACLList.erase( it );
+ else // added/modified
+ (*it).changed = false;
+ return;
+ }
+ }
+}
+
+// called by KMAcctCachedImap::killAllJobs
+void KMFolderCachedImap::resetSyncState()
+{
+ if ( mSyncState == SYNC_STATE_INITIAL ) return;
+ mSubfoldersForSync.clear();
+ mSyncState = SYNC_STATE_INITIAL;
+ close("cachedimap");
+ // Don't use newState here, it would revert to mProgress (which is < current value when listing messages)
+ KPIM::ProgressItem *progressItem = mAccount->mailCheckProgressItem();
+ QString str = i18n("Aborted");
+ if (progressItem)
+ progressItem->setStatus( str );
+ emit statusMsg( str );
+}
+
+void KMFolderCachedImap::slotIncreaseProgress()
+{
+ mProgress += 5;
+}
+
+void KMFolderCachedImap::newState( int progress, const QString& syncStatus )
+{
+ //kdDebug() << k_funcinfo << folder() << " " << mProgress << " " << syncStatus << endl;
+ KPIM::ProgressItem *progressItem = mAccount->mailCheckProgressItem();
+ if( progressItem )
+ progressItem->setCompletedItems( progress );
+ if ( !syncStatus.isEmpty() ) {
+ QString str;
+ // For a subfolder, show the label. But for the main folder, it's already shown.
+ if ( mAccount->imapFolder() == this )
+ str = syncStatus;
+ else
+ str = QString( "%1: %2" ).arg( label() ).arg( syncStatus );
+ if( progressItem )
+ progressItem->setStatus( str );
+ emit statusMsg( str );
+ }
+ if( progressItem )
+ progressItem->updateProgress();
+}
+
+void KMFolderCachedImap::setSubfolderState( imapState state )
+{
+ mSubfolderState = state;
+ if ( state == imapNoInformation && folder()->child() )
+ {
+ // pass through to childs
+ KMFolderNode* node;
+ QPtrListIterator<KMFolderNode> it( *folder()->child() );
+ for ( ; (node = it.current()); )
+ {
+ ++it;
+ if (node->isDir()) continue;
+ KMFolder *folder = static_cast<KMFolder*>(node);
+ static_cast<KMFolderCachedImap*>(folder->storage())->setSubfolderState( state );
+ }
+ }
+}
+
+void KMFolderCachedImap::setImapPath(const QString &path)
+{
+ mImapPath = path;
+}
+
+// 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.
+// Don't do it earlier, e.g. from setContentsType:
+// on startup, it's too early there to know if this is a standard resource folder.
+void KMFolderCachedImap::updateAnnotationFolderType()
+{
+ QString oldType = mAnnotationFolderType;
+ QString oldSubType;
+ int dot = oldType.find( '.' );
+ if ( dot != -1 ) {
+ oldType.truncate( dot );
+ oldSubType = mAnnotationFolderType.mid( dot + 1 );
+ }
+
+ QString newType, newSubType;
+ // We want to store an annotation on the folder only if using the kolab storage.
+ if ( kmkernel->iCalIface().storageFormat( folder() ) == KMailICalIfaceImpl::StorageXML ) {
+ newType = KMailICalIfaceImpl::annotationForContentsType( mContentsType );
+ if ( kmkernel->iCalIface().isStandardResourceFolder( folder() ) )
+ newSubType = "default";
+ else
+ newSubType = oldSubType; // preserve unknown subtypes, like drafts etc. And preserve ".default" too.
+ }
+
+ //kdDebug(5006) << mImapPath << ": updateAnnotationFolderType: " << newType << " " << newSubType << endl;
+ if ( newType != oldType || newSubType != oldSubType ) {
+ mAnnotationFolderType = newType + ( newSubType.isEmpty() ? QString::null : "."+newSubType );
+ mAnnotationFolderTypeChanged = true; // force a "set annotation" on next sync
+ kdDebug(5006) << mImapPath << ": updateAnnotationFolderType: '" << mAnnotationFolderType << "', was (" << oldType << " " << oldSubType << ") => mAnnotationFolderTypeChanged set to TRUE" << endl;
+ }
+ // Ensure that further readConfig()s don't lose mAnnotationFolderType
+ writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
+}
+
+void KMFolderCachedImap::setIncidencesFor( IncidencesFor incfor )
+{
+ if ( mIncidencesFor != incfor ) {
+ mIncidencesFor = incfor;
+ mIncidencesForChanged = true;
+ }
+}
+
+void KMFolderCachedImap::slotAnnotationResult(const QString& entry, const QString& value, bool found)
+{
+ if ( entry == KOLAB_FOLDERTYPE ) {
+ // There are four cases.
+ // 1) no content-type on server -> set it
+ // 2) different content-type on server, locally changed -> set it (we don't even come here)
+ // 3) different (known) content-type on server, no local change -> get it
+ // 4) different unknown content-type on server, probably some older version -> set it
+ if ( found ) {
+ QString type = value;
+ QString subtype;
+ int dot = value.find( '.' );
+ if ( dot != -1 ) {
+ type.truncate( dot );
+ subtype = value.mid( dot + 1 );
+ }
+ bool foundKnownType = false;
+ for ( uint i = 0 ; i <= ContentsTypeLast; ++i ) {
+ FolderContentsType contentsType = static_cast<KMail::FolderContentsType>( i );
+ if ( type == KMailICalIfaceImpl::annotationForContentsType( contentsType ) ) {
+ // Case 3: known content-type on server, get it
+ //kdDebug(5006) << mImapPath << ": slotGetAnnotationResult: found known type of annotation" << endl;
+ if ( contentsType != ContentsTypeMail )
+ kmkernel->iCalIface().setStorageFormat( folder(), KMailICalIfaceImpl::StorageXML );
+ mAnnotationFolderType = value;
+ if ( folder()->parent()->owner()->idString() != GlobalSettings::self()->theIMAPResourceFolderParent()
+ && GlobalSettings::self()->theIMAPResourceEnabled()
+ && subtype == "default" ) {
+ // Truncate subtype if this folder can't be a default resource folder for us,
+ // although it apparently is for someone else.
+ mAnnotationFolderType = type;
+ kdDebug(5006) << mImapPath << ": slotGetAnnotationResult: parent folder is " << folder()->parent()->owner()->idString() << " => truncating annotation to " << value << endl;
+ }
+ setContentsType( contentsType );
+ mAnnotationFolderTypeChanged = false; // we changed it, not the user
+ foundKnownType = true;
+
+ // Users don't read events/contacts/etc. in kmail, so mark them all as read.
+ // This is done in cachedimapjob when getting new messages, but do it here too,
+ // for the initial set of messages when we didn't know this was a resource folder yet,
+ // for old folders, etc.
+ 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;
+ }
+ // TODO handle subtype (inbox, drafts, sentitems, junkemail)
+ }
+ else if ( !mReadOnly ) {
+ // Case 1: server doesn't have content-type, set it
+ //kdDebug(5006) << "slotGetAnnotationResult: no annotation found, will need to set it" << endl;
+ mAnnotationFolderTypeChanged = true;
+ }
+ } else if ( entry == KOLAB_INCIDENCESFOR ) {
+ if ( found ) {
+ mIncidencesFor = incidencesForFromString( value );
+ Q_ASSERT( mIncidencesForChanged == false );
+ }
+ }
+}
+
+void KMFolderCachedImap::slotGetAnnotationResult( KIO::Job* job )
+{
+ KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
+ Q_ASSERT( it != mAccount->jobsEnd() );
+ if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen
+ Q_ASSERT( (*it).parent == folder() );
+ if ( (*it).parent != folder() ) return; // Shouldn't happen
+
+ AnnotationJobs::GetAnnotationJob* annjob = static_cast<AnnotationJobs::GetAnnotationJob *>( job );
+ if ( annjob->error() ) {
+ if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION ) {
+ // that's when the imap server doesn't support annotations
+ if ( GlobalSettings::self()->theIMAPResourceStorageFormat() == GlobalSettings::EnumTheIMAPResourceStorageFormat::XML
+ && (uint)GlobalSettings::self()->theIMAPResourceAccount() == mAccount->id() )
+ KMessageBox::error( 0, i18n( "The IMAP server %1 does not have support for IMAP annotations. The XML storage cannot be used on this server; please re-configure KMail differently." ).arg( mAccount->host() ) );
+ mAccount->setHasNoAnnotationSupport();
+ }
+ else
+ kdWarning(5006) << "slotGetAnnotationResult: " << job->errorString() << endl;
+ }
+
+ if (mAccount->slave()) mAccount->removeJob(job);
+ mProgress += 2;
+ serverSyncInternal();
+}
+
+void KMFolderCachedImap::slotMultiUrlGetAnnotationResult( KIO::Job* job )
+{
+ KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
+ Q_ASSERT( it != mAccount->jobsEnd() );
+ if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen
+ Q_ASSERT( (*it).parent == folder() );
+ if ( (*it).parent != folder() ) return; // Shouldn't happen
+
+ QValueVector<int> folders;
+ AnnotationJobs::MultiUrlGetAnnotationJob* annjob
+ = static_cast<AnnotationJobs::MultiUrlGetAnnotationJob *>( job );
+ if ( annjob->error() ) {
+ if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION ) {
+ // that's when the imap server doesn't support annotations
+ if ( GlobalSettings::self()->theIMAPResourceStorageFormat() == GlobalSettings::EnumTheIMAPResourceStorageFormat::XML
+ && (uint)GlobalSettings::self()->theIMAPResourceAccount() == mAccount->id() )
+ KMessageBox::error( 0, i18n( "The IMAP server %1 doesn't have support for imap annotations. The XML storage cannot be used on this server, please re-configure KMail differently" ).arg( mAccount->host() ) );
+ mAccount->setHasNoAnnotationSupport();
+ }
+ else
+ kdWarning(5006) << "slotGetMultiUrlAnnotationResult: " << job->errorString() << endl;
+ } else {
+ // we got the annotation allright, let's filter out the ones with the wrong type
+ QMap<QString, QString> annotations = annjob->annotations();
+ QMap<QString, QString>::Iterator it = annotations.begin();
+ for ( ; it != annotations.end(); ++it ) {
+ const QString folderPath = it.key();
+ const QString annotation = it.data();
+ kdDebug(5006) << k_funcinfo << "Folder: " << folderPath << " has type: " << annotation << endl;
+ // we're only interested in the main type
+ QString type(annotation);
+ int dot = annotation.find( '.' );
+ if ( dot != -1 ) type.truncate( dot );
+ type = type.simplifyWhiteSpace();
+
+ const int idx = mSubfolderPaths.findIndex( folderPath );
+ const bool isNoContent = mSubfolderMimeTypes[idx] == "inode/directory";
+ if ( ( isNoContent && type.isEmpty() )
+ || ( !type.isEmpty() && type != KMailICalIfaceImpl::annotationForContentsType( ContentsTypeMail ) ) ) {
+ folders.append( idx );
+ kdDebug(5006) << k_funcinfo << " subscribing to: " << folderPath << endl;
+ } else {
+ kdDebug(5006) << k_funcinfo << " automatically unsubscribing from: " << folderPath << endl;
+ mAccount->changeLocalSubscription( folderPath, false );
+ }
+ }
+ }
+
+ if (mAccount->slave()) mAccount->removeJob(job);
+ createFoldersNewOnServerAndFinishListing( folders );
+}
+
+void KMFolderCachedImap::slotQuotaResult( KIO::Job* job )
+{
+ KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
+ Q_ASSERT( it != mAccount->jobsEnd() );
+ if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen
+ Q_ASSERT( (*it).parent == folder() );
+ if ( (*it).parent != folder() ) return; // Shouldn't happen
+
+ QuotaJobs::GetStorageQuotaJob* quotajob = static_cast<QuotaJobs::GetStorageQuotaJob *>( job );
+ QuotaInfo empty;
+ if ( quotajob->error() ) {
+ if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION ) {
+ // that's when the imap server doesn't support quota
+ mAccount->setHasNoQuotaSupport();
+ setQuotaInfo( empty );
+ }
+ else
+ kdWarning(5006) << "slotGetQuotaResult: " << job->errorString() << endl;
+ }
+
+ if (mAccount->slave()) mAccount->removeJob(job);
+ mProgress += 2;
+ serverSyncInternal();
+}
+
+void
+KMFolderCachedImap::slotAnnotationChanged( const QString& entry, const QString& attribute, const QString& value )
+{
+ Q_UNUSED( attribute );
+ Q_UNUSED( value );
+ //kdDebug(5006) << k_funcinfo << entry << " " << attribute << " " << value << endl;
+ if ( entry == KOLAB_FOLDERTYPE )
+ mAnnotationFolderTypeChanged = false;
+ else if ( entry == KOLAB_INCIDENCESFOR ) {
+ mIncidencesForChanged = false;
+ // 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 );
+ }
+}
+
+void KMFolderCachedImap::slotTestAnnotationResult(KIO::Job *job)
+{
+ KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
+ Q_ASSERT( it != mAccount->jobsEnd() );
+ if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen
+ Q_ASSERT( (*it).parent == folder() );
+ if ( (*it).parent != folder() ) return; // Shouldn't happen
+
+ mAccount->setAnnotationCheckPassed( true );
+ if ( job->error() ) {
+ kdDebug(5006) << "Test Annotation was not passed, disabling annotation support" << endl;
+ mAccount->setHasNoAnnotationSupport( );
+ } else {
+ kdDebug(5006) << "Test Annotation was passed OK" << endl;
+ }
+ if (mAccount->slave()) mAccount->removeJob(job);
+ serverSyncInternal();
+}
+
+void
+KMFolderCachedImap::slotSetAnnotationResult(KIO::Job *job)
+{
+ KMAcctCachedImap::JobIterator it = mAccount->findJob(job);
+ if ( it == mAccount->jobsEnd() ) return; // Shouldn't happen
+ if ( (*it).parent != folder() ) return; // Shouldn't happen
+
+ bool cont = true;
+ if ( job->error() ) {
+ // Don't show error if the server doesn't support ANNOTATEMORE and this folder only contains mail
+ if ( job->error() == KIO::ERR_UNSUPPORTED_ACTION && contentsType() == ContentsTypeMail ) {
+ if (mAccount->slave()) mAccount->removeJob(job);
+ } else {
+ cont = mAccount->handleJobError( job, i18n( "Error while setting annotation: " ) + '\n' );
+ }
+ } else {
+ if (mAccount->slave()) mAccount->removeJob(job);
+ }
+ if ( cont )
+ serverSyncInternal();
+}
+
+void KMFolderCachedImap::slotUpdateLastUid()
+{
+ if( mTentativeHighestUid != 0 ) {
+
+ // Sanity checking:
+ // By now all new mails should be downloaded, which means
+ // that iteration over the folder should yield only UIDs
+ // lower or equal to what we think the highes ist, and the
+ // highest one as well. If not, our notion of the highest
+ // uid we've seen thus far is wrong, which is dangerous, so
+ // don't update the mLastUid, then.
+ // Not entirely true though, mails might have been moved out
+ // of the folder already by filters, thus giving us a higher tentative
+ // uid than we actually observe here.
+ bool sane = count() == 0;
+
+ for (int i=0;i<count(); i++ ) {
+ ulong uid = getMsgBase(i)->UID();
+ if ( uid > mTentativeHighestUid && uid > lastUid() ) {
+ kdWarning(5006) << "DANGER: Either the server listed a wrong highest uid, "
+ "or we parsed it wrong. Send email to adam@kde.org, please, and include this log." << endl;
+ kdWarning(5006) << "uid: " << uid << " mTentativeHighestUid: " << mTentativeHighestUid << endl;
+ assert( false );
+ break;
+ } else {
+ sane = true;
+ }
+ }
+ if (sane) {
+#if MAIL_LOSS_DEBUGGING
+ kdDebug(5006) << "Tentative highest UID test was sane, writing out: " << mTentativeHighestUid << endl;
+#endif
+ setLastUid( mTentativeHighestUid );
+ }
+ }
+ mTentativeHighestUid = 0;
+}
+
+bool KMFolderCachedImap::isMoveable() const
+{
+ return ( hasChildren() == HasNoChildren &&
+ !folder()->isSystemFolder() ) ? true : false;
+}
+
+void KMFolderCachedImap::slotFolderDeletionOnServerFinished()
+{
+ for ( QStringList::const_iterator it = foldersForDeletionOnServer.constBegin();
+ it != foldersForDeletionOnServer.constEnd(); ++it ) {
+ KURL url( mAccount->getUrl() );
+ url.setPath( *it );
+ kmkernel->iCalIface().folderDeletedOnServer( url );
+ }
+ serverSyncInternal();
+}
+
+int KMFolderCachedImap::createIndexFromContentsRecursive()
+{
+ if ( !folder() || !folder()->child() )
+ return 0;
+
+ KMFolderNode *node = 0;
+ for( QPtrListIterator<KMFolderNode> it( *folder()->child() ); (node = it.current()); ++it ) {
+ if( !node->isDir() ) {
+ KMFolderCachedImap* storage = static_cast<KMFolderCachedImap*>(static_cast<KMFolder*>(node)->storage());
+ kdDebug() << k_funcinfo << "Re-indexing: " << storage->folder()->label() << endl;
+ int rv = storage->createIndexFromContentsRecursive();
+ if ( rv > 0 )
+ return rv;
+ }
+ }
+
+ return createIndexFromContents();
+}
+
+void KMFolderCachedImap::setAlarmsBlocked( bool blocked )
+{
+ mAlarmsBlocked = blocked;
+}
+
+bool KMFolderCachedImap::alarmsBlocked() const
+{
+ return mAlarmsBlocked;
+}
+
+bool KMFolderCachedImap::isCloseToQuota() const
+{
+ bool closeToQuota = false;
+ if ( mQuotaInfo.isValid() && mQuotaInfo.max().toInt() > 0 ) {
+ const int ratio = mQuotaInfo.current().toInt() * 100 / mQuotaInfo.max().toInt();
+ //kdDebug(5006) << "Quota ratio: " << ratio << "% " << mQuotaInfo.toString() << endl;
+ closeToQuota = ( ratio > 0 && ratio >= GlobalSettings::closeToQuotaThreshold() );
+ }
+ //kdDebug(5006) << "Folder: " << folder()->prettyURL() << " is over quota: " << closeToQuota << endl;
+ return closeToQuota;
+}
+
+KMCommand* KMFolderCachedImap::rescueUnsyncedMessages()
+{
+ QValueList<unsigned long> newMsgs = findNewMessages();
+ kdDebug() << k_funcinfo << newMsgs << " of " << count() << endl;
+ if ( newMsgs.isEmpty() )
+ return 0;
+ KMFolder *dest = 0;
+ bool manualMove = true;
+ while ( GlobalSettings::autoLostFoundMove() ) {
+ // find the inbox of this account
+ KMFolder *inboxFolder = kmkernel->findFolderById( QString(".%1.directory/INBOX").arg( account()->id() ) );
+ if ( !inboxFolder ) {
+ kdWarning(5006) << k_funcinfo << "inbox not found!" << endl;
+ break;
+ }
+ KMFolderDir *inboxDir = inboxFolder->child();
+ if ( !inboxDir && !inboxFolder->storage() )
+ break;
+ assert( inboxFolder->storage()->folderType() == KMFolderTypeCachedImap );
+
+ // create lost+found folder if needed
+ KMFolderNode *node;
+ KMFolder *lfFolder = 0;
+ if ( !(node = inboxDir->hasNamedFolder( i18n("lost+found") )) ) {
+ kdDebug(5006) << k_funcinfo << "creating lost+found folder" << endl;
+ KMFolder* folder = kmkernel->dimapFolderMgr()->createFolder(
+ i18n("lost+found"), false, KMFolderTypeCachedImap, inboxDir );
+ if ( !folder || !folder->storage() )
+ break;
+ static_cast<KMFolderCachedImap*>( folder->storage() )->initializeFrom(
+ static_cast<KMFolderCachedImap*>( inboxFolder->storage() ) );
+ folder->storage()->setContentsType( KMail::ContentsTypeMail );
+ folder->storage()->writeConfig();
+ lfFolder = folder;
+ } else {
+ kdDebug(5006) << k_funcinfo << "found lost+found folder" << endl;
+ lfFolder = dynamic_cast<KMFolder*>( node );
+ }
+ if ( !lfFolder || !lfFolder->createChildFolder() || !lfFolder->storage() )
+ break;
+
+ // create subfolder for this incident
+ QDate today = QDate::currentDate();
+ QString baseName = folder()->label() + "-" + QString::number( today.year() )
+ + (today.month() < 10 ? "0" : "" ) + QString::number( today.month() )
+ + (today.day() < 10 ? "0" : "" ) + QString::number( today.day() );
+ QString name = baseName;
+ int suffix = 0;
+ while ( (node = lfFolder->child()->hasNamedFolder( name )) ) {
+ ++suffix;
+ name = baseName + '-' + QString::number( suffix );
+ }
+ kdDebug(5006) << k_funcinfo << "creating lost+found folder " << name << endl;
+ dest = kmkernel->dimapFolderMgr()->createFolder( name, false, KMFolderTypeCachedImap, lfFolder->child() );
+ if ( !dest || !dest->storage() )
+ break;
+ static_cast<KMFolderCachedImap*>( dest->storage() )->initializeFrom(
+ static_cast<KMFolderCachedImap*>( lfFolder->storage() ) );
+ dest->storage()->setContentsType( contentsType() );
+ dest->storage()->writeConfig();
+
+ KMessageBox::sorry( 0, i18n("<p>There are new messages in folder <b>%1</b>, which "
+ "have not been uploaded to the server yet, but the folder has been deleted "
+ "on the server or you do not "
+ "have sufficient access rights on the folder to upload them.</p>"
+ "<p>All affected messages will therefore be moved to <b>%2</b> "
+ "to avoid data loss.</p>").arg( folder()->prettyURL() ).arg( dest->prettyURL() ),
+ i18n("Insufficient access rights") );
+ manualMove = false;
+ break;
+ }
+
+ if ( manualMove ) {
+ const QString msg ( i18n( "<p>There are new messages in this folder (%1), which "
+ "have not been uploaded to the server yet, but the folder has been deleted "
+ "on the server or you do not "
+ "have sufficient access rights on the folder now to upload them. "
+ "Please contact your administrator to allow upload of new messages "
+ "to you, or move them out of this folder.</p> "
+ "<p>Do you want to move these messages to another folder now?</p>").arg( folder()->prettyURL() ) );
+ if ( KMessageBox::warningYesNo( 0, msg, QString::null, i18n("Move"), i18n("Do Not Move") ) == KMessageBox::Yes ) {
+ KMail::KMFolderSelDlg dlg( kmkernel->getKMMainWidget(),
+ i18n("Move Messages to Folder"), true );
+ if ( dlg.exec() ) {
+ dest = dlg.folder();
+ }
+ }
+ }
+ if ( dest ) {
+ QPtrList<KMMsgBase> msgs;
+ for( int i = 0; i < count(); ++i ) {
+ KMMsgBase *msg = getMsgBase( i );
+ if( !msg ) continue; /* what goes on if getMsg() returns 0? */
+ if ( msg->UID() == 0 )
+ msgs.append( msg );
+ }
+ KMCommand *command = new KMMoveCommand( dest, msgs );
+ command->start();
+ return command;
+ }
+ return 0;
+}
+
+void KMFolderCachedImap::rescueUnsyncedMessagesAndDeleteFolder( KMFolder *folder, bool root )
+{
+ kdDebug() << k_funcinfo << folder << " " << root << endl;
+ if ( root )
+ mToBeDeletedAfterRescue.append( folder );
+ folder->open("cachedimap");
+ KMFolderCachedImap* storage = dynamic_cast<KMFolderCachedImap*>( folder->storage() );
+ if ( storage ) {
+ KMCommand *command = storage->rescueUnsyncedMessages();
+ if ( command ) {
+ connect( command, SIGNAL(completed(KMCommand*)),
+ SLOT(slotRescueDone(KMCommand*)) );
+ ++mRescueCommandCount;
+ } else {
+ // nothing to rescue, close folder
+ // (we don't need to close it in the other case, it will be deleted anyway)
+ folder->close("cachedimap");
+ }
+ }
+ if ( folder->child() ) {
+ KMFolderNode *node = folder->child()->first();
+ while (node) {
+ if (!node->isDir() ) {
+ KMFolder *subFolder = static_cast<KMFolder*>( node );
+ rescueUnsyncedMessagesAndDeleteFolder( subFolder, false );
+ }
+ node = folder->child()->next();
+ }
+ }
+}
+
+void KMFolderCachedImap::slotRescueDone(KMCommand * command)
+{
+ // FIXME: check command result
+ if ( command )
+ --mRescueCommandCount;
+ if ( mRescueCommandCount > 0 )
+ return;
+ for ( QValueList<KMFolder*>::ConstIterator it = mToBeDeletedAfterRescue.constBegin();
+ it != mToBeDeletedAfterRescue.constEnd(); ++it ) {
+ kmkernel->dimapFolderMgr()->remove( *it );
+ }
+ mToBeDeletedAfterRescue.clear();
+ serverSyncInternal();
+}
+
+#include "kmfoldercachedimap.moc"
diff --git a/kmail/kmfoldercachedimap.h b/kmail/kmfoldercachedimap.h
new file mode 100644
index 00000000..1ef82a3a
--- /dev/null
+++ b/kmail/kmfoldercachedimap.h
@@ -0,0 +1,567 @@
+/*
+ * kmfoldercachedimap.cpp
+ *
+ * Copyright (c) 2002-2004 Bo Thorsen <bo@sonofthor.dk>
+ * Copyright (c) 2002-2003 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#ifndef kmfoldercachedimap_h
+#define kmfoldercachedimap_h
+
+#include <kdialogbase.h>
+#include <kstandarddirs.h>
+#include <qvaluelist.h>
+#include <qvaluevector.h>
+#include <qptrlist.h>
+#include <qdialog.h>
+
+#include "kmfoldermaildir.h"
+#include "kmfolderimap.h"
+#include "kmacctcachedimap.h"
+#include "kmfoldertype.h"
+#include "folderjob.h"
+#include "cachedimapjob.h"
+#include "quotajobs.h"
+
+using KMail::FolderJob;
+using KMail::QuotaInfo;
+class KMCommand;
+
+class QComboBox;
+class QRadioButton;
+
+namespace KMail {
+ class AttachmentStrategy;
+ class ImapAccountBase;
+ struct ACLListEntry;
+}
+using KMail::AttachmentStrategy;
+
+class DImapTroubleShootDialog : public KDialogBase
+{
+ Q_OBJECT
+public:
+ enum SelectedOperation {
+ None = -1,
+ ReindexCurrent = 0,
+ ReindexRecursive = 1,
+ ReindexAll = 2,
+ RefreshCache
+ };
+
+ DImapTroubleShootDialog( QWidget* parent=0, const char* name=0 );
+
+ static int run();
+
+private slots:
+ void slotDone();
+
+private:
+ QRadioButton *mIndexButton, *mCacheButton;
+ QComboBox *mIndexScope;
+ int rc;
+};
+
+class KMFolderCachedImap : public KMFolderMaildir
+{
+ Q_OBJECT
+
+public:
+ static QString cacheLocation() {
+ return locateLocal("data", "kmail/dimap" );
+ }
+
+ /** Usually a parent is given. But in some cases there is no
+ fitting parent object available. Then the name of the folder
+ is used as the absolute path to the folder file. */
+ KMFolderCachedImap(KMFolder* folder, const char* name=0);
+ virtual ~KMFolderCachedImap();
+
+ /** @reimpl */
+ void reallyDoClose(const char* owner);
+
+ /** Initialize this storage from another one. Used when creating a child folder */
+ void initializeFrom( KMFolderCachedImap* parent );
+
+ virtual void readConfig();
+ virtual void writeConfig();
+
+ void writeConfigKeysWhichShouldNotGetOverwrittenByReadConfig();
+
+ /** Returns the type of this folder */
+ virtual KMFolderType folderType() const { return KMFolderTypeCachedImap; }
+
+ /** @reimpl */
+ virtual int create();
+
+ /** Remove this folder */
+ virtual void remove();
+
+ /** Synchronize this folder and it's subfolders with the server */
+ virtual void serverSync( bool recurse );
+
+ /** Force the sync state to be done. */
+ void resetSyncState( );
+
+ /** Block this folder from generating alarms, even if the annotations
+ * on it say otherwise. Used to override alarms for read-only folders.
+ * (Only useful for resource folders) */
+ void setAlarmsBlocked( bool blocked );
+ /** Should alarms for this folder be blocked? (Only useful for resource folders) */
+ bool alarmsBlocked() const;
+
+ void checkUidValidity();
+
+ enum imapState { imapNoInformation=0, imapInProgress=1, imapFinished=2 };
+
+ virtual imapState getContentState() { return mContentState; }
+ virtual void setContentState(imapState state) { mContentState = state; }
+
+ virtual imapState getSubfolderState() { return mSubfolderState; }
+ virtual void setSubfolderState(imapState state);
+
+ /** The path to the imap folder on the server */
+ void setImapPath(const QString &path);
+ QString imapPath() const { return mImapPath; }
+
+ /** The highest UID in the folder */
+ void setLastUid( ulong uid );
+ ulong lastUid();
+
+ /** Find message by UID. Returns NULL if it doesn't exist */
+ KMMsgBase* findByUID( ulong uid );
+
+ /** The uidvalidity of the last update */
+ void setUidValidity(const QString &validity) { mUidValidity = validity; }
+ QString uidValidity() const { return mUidValidity; }
+
+ /** Forget which mails are considered locally present. Needed when uidvalidity
+ * changes. */
+ void clearUidMap() { uidMap.clear(); }
+
+ /** The imap account associated with this folder */
+ void setAccount(KMAcctCachedImap *acct);
+ KMAcctCachedImap* account() const;
+
+ /** Returns the filename of the uidcache file */
+ QString uidCacheLocation() const;
+
+ /** Read the uidValitidy and lastUid values from disk */
+ int readUidCache();
+
+ /** Write the uidValitidy and lastUid values to disk */
+ int writeUidCache();
+
+ /** Current progress status (between 0 and 100) */
+ int progress() const { return mProgress; }
+
+ /* Reimplemented from KMFolder. Moving is not supported, so aParent must be 0 */
+ virtual int rename(const QString& aName, KMFolderDir *aParent=0);
+
+ /**
+ * Reimplemented from KMFolderMaildir
+ * This deletes the message permanently, also from the server. For this, rememberDeletion() is
+ * called, so that the message can be deleted from the server on the next sync.
+ */
+ virtual KMMessage* take(int idx);
+
+ /**
+ * Like take(), only that the deletion is not remembered, i.e. the message will not be deleted
+ * from the server.
+ * Calling this can cause inconsistencies, so make sure you re-add the message later!
+ */
+ void takeTemporarily( int idx );
+
+ /* Reimplemented from KMFolderMaildir */
+ virtual int addMsg(KMMessage* msg, int* index_return = 0);
+ /* internal version that doesn't remove the X-UID header */
+ virtual int addMsgInternal(KMMessage* msg, bool, int* index_return = 0);
+ virtual int addMsgKeepUID(KMMessage* msg, int* index_return = 0) {
+ return addMsgInternal(msg, false, index_return);
+ }
+
+ /* Reimplemented from KMFolderMaildir */
+ virtual void removeMsg(int i, bool imapQuiet = false);
+ virtual void removeMsg(QPtrList<KMMessage> msgList, bool imapQuiet = false)
+ { FolderStorage::removeMsg(msgList, imapQuiet); }
+
+ /// Is the folder readonly?
+ bool isReadOnly() const { return KMFolderMaildir::isReadOnly() || mReadOnly; }
+
+
+ /**
+ * Emit the folderComplete signal
+ */
+ void sendFolderComplete(bool success)
+ { emit folderComplete(this, success); }
+
+ /**
+ * The silentUpload can be set to remove the folder upload error dialog
+ */
+ void setSilentUpload( bool silent ) { mSilentUpload = silent; }
+ bool silentUpload() { return mSilentUpload; }
+
+ virtual int createIndexFromContents() {
+ const int result = KMFolderMaildir::createIndexFromContents();
+ reloadUidMap();
+ return result;
+ }
+
+ int createIndexFromContentsRecursive();
+
+ //virtual void holdSyncs( bool hold ) { mHoldSyncs = hold; }
+
+ /**
+ * List a directory and add the contents to kmfoldermgr
+ * It uses a ListJob to get the folders
+ * returns false if the connection failed
+ */
+ virtual bool listDirectory();
+
+ virtual void listNamespaces();
+
+ /** Return the trash folder. */
+ KMFolder* trashFolder() const;
+
+ /**
+ * 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
+ */
+ int userRights() const { return mUserRights; }
+
+ /// Set the user's rights on this folder - called by getUserRights
+ void setUserRights( unsigned int userRights );
+
+ /**
+ * The quota information for this folder.
+ * @return an invalid info if we haven't synced yet, or the server
+ * doesn't support quota. The difference can be figured out by
+ * asking the account whether it supports quota. If we have
+ * synced, the account supports quota, but there is no quota
+ * on the folder, the return info will be valid, but empty.
+ * @see QuotaInfo::isEmpty(), QuotaInfo::isValid()
+ */
+ const QuotaInfo quotaInfo() const { return mQuotaInfo; }
+ void setQuotaInfo( const QuotaInfo & );
+
+ /// Return the list of ACL for this folder
+ typedef QValueVector<KMail::ACLListEntry> ACLList;
+ const ACLList& aclList() const { return mACLList; }
+
+ /// Set the list of ACL for this folder (for FolderDiaACLTab)
+ void setACLList( const ACLList& arr );
+
+ // Reimplemented so the mStatusChangedLocally bool can be set
+ virtual void setStatus( int id, KMMsgStatus status, bool toggle );
+ virtual void setStatus( QValueList<int>& ids, KMMsgStatus status, bool toggle );
+
+ QString annotationFolderType() const { return mAnnotationFolderType; }
+
+ // For kmailicalifaceimpl only
+ void updateAnnotationFolderType();
+
+ /// Free-busy and alarms relevance of this folder, i.e. for whom should
+ /// events in this calendar lead to "busy" periods in their freebusy lists,
+ /// and who should get alarms for the incidences in this folder.
+ /// Applies to Calendar and Task folders only.
+ ///
+ /// IncForNobody: not relevant for free-busy and alarms to anybody
+ /// IncForAdmins: apply to persons with admin permissions on this calendar
+ /// IncForReaders: apply to all readers of this calendar
+ enum IncidencesFor { IncForNobody, IncForAdmins, IncForReaders };
+
+ IncidencesFor incidencesFor() const { return mIncidencesFor; }
+ /// For the folder properties dialog
+ void setIncidencesFor( IncidencesFor incfor );
+
+ /** Returns true if this folder can be moved */
+ virtual bool isMoveable() const;
+
+ /**
+ * List of namespaces that need to be queried
+ * Is set by the account for the root folder when the listing starts
+ */
+ QStringList namespacesToList() { return mNamespacesToList; }
+ void setNamespacesToList( QStringList list ) { mNamespacesToList = list; }
+
+ /**
+ * Specify an imap path that is used to create the folder on the server
+ * Otherwise the parent folder is used to construct the path
+ */
+ const QString& imapPathForCreation() { return mImapPathCreation; }
+ void setImapPathForCreation( const QString& path ) { mImapPathCreation = path; }
+
+ /** \reimp */
+ bool isCloseToQuota() const;
+
+ /** Flags that can be permanently stored on the server. */
+ int permanentFlags() const { return mPermanentFlags; }
+
+
+ QString folderAttributes() const { return mFolderAttributes; }
+
+protected slots:
+ void slotGetMessagesData(KIO::Job * job, const QByteArray & data);
+ void getMessagesResult(KMail::FolderJob *, bool lastSet);
+ void slotGetLastMessagesResult(KMail::FolderJob *);
+ void slotProgress(unsigned long done, unsigned long total);
+ void slotPutProgress( unsigned long, unsigned long );
+
+ //virtual void slotCheckValidityResult(KIO::Job * job);
+ void slotSubFolderComplete(KMFolderCachedImap*, bool);
+
+ // Connected to the imap account
+ void slotConnectionResult( int errorCode, const QString& errorMsg );
+
+ void slotCheckUidValidityResult( KMail::FolderJob* job );
+ void slotPermanentFlags( int flags );
+ void slotTestAnnotationResult(KIO::Job *job);
+ void slotGetAnnotationResult( KIO::Job* );
+ void slotMultiUrlGetAnnotationResult( KIO::Job* );
+ void slotSetAnnotationResult(KIO::Job *job);
+ void slotReceivedUserRights( KMFolder* );
+ void slotReceivedACL( KMFolder*, KIO::Job*, const KMail::ACLList& );
+
+ void slotMultiSetACLResult(KIO::Job *);
+ void slotACLChanged( const QString&, int );
+ void slotAnnotationResult(const QString& entry, const QString& value, bool found);
+ void slotAnnotationChanged( const QString& entry, const QString& attribute, const QString& value );
+ void slotDeleteMessagesResult(KMail::FolderJob *);
+ void slotImapStatusChanged(KMFolder* folder, const QString&, bool);
+ void slotStorageQuotaResult( const QuotaInfo& );
+ void slotQuotaResult( KIO::Job* job );
+
+protected:
+ /* returns true if there were messages to delete
+ on the server */
+ bool deleteMessages();
+ void listMessages();
+ void uploadNewMessages();
+ void uploadFlags();
+ void uploadSeenFlags();
+ void createNewFolders();
+
+ void listDirectory2();
+ void createFoldersNewOnServerAndFinishListing( const QValueVector<int> foldersNewOnServer );
+
+
+ /** Utility methods for syncing. Finds new messages
+ in the local cache that must be uploaded */
+ virtual QValueList<unsigned long> findNewMessages();
+ /** Utility methods for syncing. Finds new subfolders
+ in the local cache that must be created in the server */
+ virtual QValueList<KMFolderCachedImap*> findNewFolders();
+
+ /** This returns false if we have subfolders. Otherwise it returns ::canRemoveFolder() */
+ virtual bool canRemoveFolder() const;
+
+ /** Reimplemented from KMFolder */
+ virtual FolderJob* doCreateJob( KMMessage *msg, FolderJob::JobType jt, KMFolder *folder,
+ QString partSpecifier, const AttachmentStrategy *as ) const;
+ virtual FolderJob* doCreateJob( QPtrList<KMMessage>& msgList, const QString& sets,
+ FolderJob::JobType jt, KMFolder *folder ) const;
+
+ virtual void timerEvent( QTimerEvent* );
+
+ /* update progress status */
+ void newState( int progress, const QString& syncStatus );
+
+ /** See if there is a better parent then this folder */
+ KMFolderCachedImap* findParent( const QString& path, const QString& name );
+
+
+
+public slots:
+ /**
+ * Add the data a KIO::Job retrieves to the buffer
+ */
+ void slotSimpleData(KIO::Job * job, const QByteArray & data);
+
+ /**
+ * Troubleshoot the IMAP cache
+ */
+ void slotTroubleshoot();
+
+ /**
+ * Connected to ListJob::receivedFolders
+ * creates/removes folders
+ */
+ void slotListResult( const QStringList&, const QStringList&,
+ const QStringList&, const QStringList&, const ImapAccountBase::jobData& );
+
+ /**
+ * Connected to ListJob::receivedFolders
+ * creates namespace folders
+ */
+ void slotCheckNamespace( const QStringList&, const QStringList&,
+ const QStringList&, const QStringList&, const ImapAccountBase::jobData& );
+
+private slots:
+ void serverSyncInternal();
+ void slotIncreaseProgress();
+ void slotUpdateLastUid();
+ void slotFolderDeletionOnServerFinished();
+ void slotRescueDone( KMCommand* command );
+
+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 );
+
+private:
+ void setReadOnly( bool readOnly );
+ QString state2String( int state ) const;
+ void rememberDeletion( int );
+ /** Rescue not yet synced messages to a lost+found folder in case
+ syncing is not possible because the folder has been deleted on the
+ server or write access to this folder has been revoked.
+ */
+ KMCommand* rescueUnsyncedMessages();
+ /** Recursive helper function calling the above method. */
+ void rescueUnsyncedMessagesAndDeleteFolder( KMFolder *folder, bool root = true );
+
+ /** State variable for the synchronization mechanism */
+ enum {
+ SYNC_STATE_INITIAL,
+ SYNC_STATE_TEST_ANNOTATIONS,
+ SYNC_STATE_PUT_MESSAGES,
+ SYNC_STATE_UPLOAD_FLAGS,
+ SYNC_STATE_CREATE_SUBFOLDERS,
+ SYNC_STATE_LIST_NAMESPACES,
+ SYNC_STATE_LIST_SUBFOLDERS,
+ SYNC_STATE_LIST_SUBFOLDERS2,
+ SYNC_STATE_DELETE_SUBFOLDERS,
+ SYNC_STATE_LIST_MESSAGES,
+ SYNC_STATE_DELETE_MESSAGES,
+ SYNC_STATE_EXPUNGE_MESSAGES,
+ SYNC_STATE_GET_MESSAGES,
+ SYNC_STATE_HANDLE_INBOX,
+ SYNC_STATE_GET_USERRIGHTS,
+ SYNC_STATE_GET_ANNOTATIONS,
+ SYNC_STATE_SET_ANNOTATIONS,
+ SYNC_STATE_GET_ACLS,
+ SYNC_STATE_SET_ACLS,
+ SYNC_STATE_GET_QUOTA,
+ SYNC_STATE_FIND_SUBFOLDERS,
+ SYNC_STATE_SYNC_SUBFOLDERS,
+ SYNC_STATE_CHECK_UIDVALIDITY,
+ SYNC_STATE_RENAME_FOLDER
+ } mSyncState;
+
+ int mProgress;
+ int mStatusFlagsJobs;
+
+ QString mUidValidity;
+ QString mImapPath;
+ imapState mContentState, mSubfolderState;
+ QStringList mSubfolderNames, mSubfolderPaths,
+ mSubfolderMimeTypes, mSubfolderAttributes;
+ QString mFolderAttributes;
+ QString mAnnotationFolderType;
+ IncidencesFor mIncidencesFor;
+
+ bool mHasInbox;
+ bool mIsSelected;
+ bool mCheckFlags;
+ bool mReadOnly;
+ mutable QGuardedPtr<KMAcctCachedImap> mAccount;
+
+ QIntDict<int> uidsOnServer;
+ QValueList<ulong> uidsForDeletionOnServer;
+ QValueList<KMail::CachedImapJob::MsgForDownload> mMsgsForDownload;
+ QValueList<ulong> mUidsForDownload;
+ QStringList foldersForDeletionOnServer;
+
+ QValueList<KMFolderCachedImap*> mSubfoldersForSync;
+ KMFolderCachedImap* mCurrentSubfolder;
+
+ /** Mapping uid -> index
+ Keep updated in addMsg, take and removeMsg. This is used to lookup
+ whether a mail is present locally or not. */
+ QMap<ulong,int> uidMap;
+ bool uidMapDirty;
+ void reloadUidMap();
+ int uidWriteTimer;
+
+ /** This is the last uid that we have seen from the server on the last
+ sync. It is crucially important that this is correct at all times
+ and not bumped up permaturely, as it is the watermark which is used
+ to discern message which are not present locally, because they were
+ deleted locally and now need to be deleted from the server,
+ from those which are new and need to be downloaded. Sucessfull
+ downloading of all pending mail from the server sets this. Between
+ invocations it is stored on disk in the uidcache file. It must not
+ change during a sync. */
+ ulong mLastUid;
+ /** The highest id encountered while syncing. Once the sync process has
+ successfully downloaded all pending mail and deleted on the server
+ all messages that were removed locally, this will become the new
+ mLastUid. See above for details. */
+ ulong mTentativeHighestUid;
+
+ /** Used to determine whether listing messages yielded a sensible result.
+ * Only then is the deletion o messages (which relies on succesful
+ * listing) attempted, during the sync. */
+ bool mFoundAnIMAPDigest;
+
+ int mUserRights, mOldUserRights;
+ ACLList mACLList;
+
+ 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;
+ /// 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;
+
+ QStringList mNamespacesToList;
+ int mNamespacesToCheck;
+ bool mPersonalNamespacesCheckDone;
+ QString mImapPathCreation;
+
+ QuotaInfo mQuotaInfo;
+ QMap<ulong,void*> mDeletedUIDsSinceLastSync;
+ bool mAlarmsBlocked;
+
+ QValueList<KMFolder*> mToBeDeletedAfterRescue;
+ int mRescueCommandCount;
+
+ int mPermanentFlags;
+};
+
+#endif /*kmfoldercachedimap_h*/
diff --git a/kmail/kmfoldercombobox.cpp b/kmail/kmfoldercombobox.cpp
new file mode 100644
index 00000000..49e14b57
--- /dev/null
+++ b/kmail/kmfoldercombobox.cpp
@@ -0,0 +1,188 @@
+/* kmail folder-list combo-box */
+/* Author: Ronen Tzur <rtzur@shani.net> */
+
+#include <config.h>
+
+#include "kmfoldercombobox.h"
+#include "kmfoldermgr.h"
+
+//-----------------------------------------------------------------------------
+
+KMFolderComboBox::KMFolderComboBox( QWidget *parent, char *name )
+ : QComboBox( parent, name )
+{
+ init();
+}
+
+
+//-----------------------------------------------------------------------------
+
+KMFolderComboBox::KMFolderComboBox( bool rw, QWidget *parent, char *name )
+ : QComboBox( rw, parent, name )
+{
+ init();
+}
+
+
+//-----------------------------------------------------------------------------
+
+void KMFolderComboBox::init()
+{
+ mSpecialIdx = -1;
+ mOutboxShown = true;
+ mImapShown = true;
+ refreshFolders();
+ connect( this, SIGNAL( activated(int) ),
+ this, SLOT( slotActivated(int) ) );
+ connect( kmkernel->folderMgr(), SIGNAL(changed()),
+ this, SLOT(refreshFolders()) );
+ connect( kmkernel->dimapFolderMgr(), SIGNAL(changed()),
+ this, SLOT(refreshFolders()) );
+ if (mImapShown)
+ connect( kmkernel->imapFolderMgr(), SIGNAL(changed()),
+ this, SLOT(refreshFolders()) );
+}
+
+
+//-----------------------------------------------------------------------------
+
+void KMFolderComboBox::showOutboxFolder(bool shown)
+{
+ mOutboxShown = shown;
+ refreshFolders();
+}
+
+//-----------------------------------------------------------------------------
+
+void KMFolderComboBox::showImapFolders(bool shown)
+{
+ mImapShown = shown;
+ refreshFolders();
+ if (shown)
+ connect( kmkernel->imapFolderMgr(), SIGNAL(changed()),
+ this, SLOT(refreshFolders()) );
+ else
+ disconnect( kmkernel->imapFolderMgr(), SIGNAL(changed()),
+ this, SLOT(refreshFolders()) );
+}
+
+//-----------------------------------------------------------------------------
+
+void KMFolderComboBox::createFolderList(QStringList *names,
+ QValueList<QGuardedPtr<KMFolder> > *folders)
+{
+ kmkernel->folderMgr()->createI18nFolderList( names, folders );
+ if ( !mOutboxShown ) {
+ QValueList< QGuardedPtr<KMFolder> >::iterator folderIt = folders->begin();
+ QStringList::iterator namesIt = names->begin();
+ for ( ; folderIt != folders->end(); ++folderIt, ++namesIt ) {
+ KMFolder *folder = *folderIt;
+ if ( folder == kmkernel->outboxFolder() )
+ break;
+ }
+ if ( folderIt != folders->end() ) {
+ folders->remove( folderIt );
+ names->remove( namesIt );
+ }
+ }
+
+ if (mImapShown)
+ kmkernel->imapFolderMgr()->createI18nFolderList( names, folders );
+
+ kmkernel->dimapFolderMgr()->createI18nFolderList( names, folders );
+}
+
+//-----------------------------------------------------------------------------
+
+void KMFolderComboBox::refreshFolders()
+{
+ QStringList names;
+ QValueList<QGuardedPtr<KMFolder> > folders;
+ createFolderList( &names, &folders );
+
+ KMFolder *folder = getFolder();
+ this->clear();
+ insertStringList( names );
+ setFolder( folder );
+}
+
+//-----------------------------------------------------------------------------
+
+void KMFolderComboBox::setFolder( KMFolder *aFolder )
+{
+ QStringList names;
+ QValueList<QGuardedPtr<KMFolder> > folders;
+ createFolderList( &names, &folders );
+
+ int idx = folders.findIndex( aFolder );
+ if (idx == -1)
+ idx = folders.findIndex( kmkernel->draftsFolder() );
+ setCurrentItem( idx >= 0 ? idx : 0 );
+
+ mFolder = aFolder;
+}
+
+void KMFolderComboBox::setFolder( const QString &idString )
+{
+ KMFolder * folder = kmkernel->findFolderById( idString );
+ if (!folder && !idString.isEmpty())
+ {
+ if (mSpecialIdx >= 0)
+ removeItem(mSpecialIdx);
+ mSpecialIdx = count();
+ insertItem(idString, -1);
+ setCurrentItem(mSpecialIdx);
+
+ mFolder = 0;
+ return;
+ }
+ setFolder( folder );
+}
+
+//-----------------------------------------------------------------------------
+
+KMFolder *KMFolderComboBox::getFolder()
+{
+ if (mFolder)
+ return mFolder;
+
+ QStringList names;
+ QValueList<QGuardedPtr<KMFolder> > folders;
+ createFolderList( &names, &folders );
+
+ if (currentItem() == mSpecialIdx)
+ return 0;
+
+ QString text = currentText();
+ int idx = 0;
+ QStringList::Iterator it;
+ for ( it = names.begin(); it != names.end(); ++it ) {
+ if ( ! (*it).compare( text ) )
+ return *folders.at( idx );
+ idx++;
+ }
+
+ return kmkernel->draftsFolder();
+}
+
+//-----------------------------------------------------------------------------
+
+void KMFolderComboBox::slotActivated(int index)
+{
+ QStringList names;
+ QValueList<QGuardedPtr<KMFolder> > folders;
+ createFolderList( &names, &folders );
+
+ if (index == mSpecialIdx)
+ {
+ mFolder = 0;
+ }
+ else
+ {
+ mFolder = *folders.at( index );
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+#include "kmfoldercombobox.moc"
diff --git a/kmail/kmfoldercombobox.h b/kmail/kmfoldercombobox.h
new file mode 100644
index 00000000..1b46c104
--- /dev/null
+++ b/kmail/kmfoldercombobox.h
@@ -0,0 +1,51 @@
+/* kmail folder-list combo-box
+ * A specialized QComboBox widget that refreshes its contents when
+ * the folder list changes.
+ */
+
+#ifndef __KMFOLDERCOMBOBOX
+#define __KMFOLDERCOMBOBOX
+
+#include "kmfolder.h"
+
+#include <qcombobox.h>
+#include <qguardedptr.h>
+
+class KMFolderComboBox : public QComboBox
+{
+ Q_OBJECT
+
+public:
+ KMFolderComboBox( QWidget *parent = 0, char *name = 0 );
+ KMFolderComboBox( bool rw, QWidget *parent = 0, char *name = 0 );
+
+ /** Select whether the outbox folder is shown. Default is yes. */
+ void showOutboxFolder(bool shown);
+
+ /** Select whether the IMAP folders should be shown. Default is yes. */
+ void showImapFolders(bool shown);
+
+ void setFolder( KMFolder *aFolder );
+ void setFolder( const QString &idString );
+ KMFolder *getFolder();
+
+public slots:
+ /** Refresh list of folders in the combobox. */
+ void refreshFolders();
+
+private slots:
+ void slotActivated(int index);
+
+private:
+ /** Create folder list using the folder manager. */
+ void createFolderList(QStringList *names,
+ QValueList<QGuardedPtr<KMFolder> > *folders);
+ void init();
+
+ QGuardedPtr<KMFolder> mFolder;
+ bool mOutboxShown;
+ bool mImapShown;
+ int mSpecialIdx;
+};
+
+#endif /* __KMFOLDERCOMBOBOX */
diff --git a/kmail/kmfolderdia.cpp b/kmail/kmfolderdia.cpp
new file mode 100644
index 00000000..2e4044ab
--- /dev/null
+++ b/kmail/kmfolderdia.cpp
@@ -0,0 +1,792 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+/**
+ * kmfolderdia.cpp
+ *
+ * Copyright (c) 1997-2004 KMail Developers
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#include <config.h>
+
+#include "kmfolderdia.h"
+#include "kmacctfolder.h"
+#include "kmfoldermgr.h"
+#include <libkpimidentities/identitycombo.h>
+#include "kmfolderimap.h"
+#include "kmfoldercachedimap.h"
+#include "kmfolder.h"
+#include "kmheaders.h"
+#include "kmcommands.h"
+#include "kmfoldertree.h"
+#include "folderdiaacltab.h"
+#include "folderdiaquotatab.h"
+#include "kmailicalifaceimpl.h"
+#include "globalsettings.h"
+#include "folderrequester.h"
+
+#include <keditlistbox.h>
+#include <klineedit.h>
+#include <klocale.h>
+#include <knuminput.h>
+#include <kmessagebox.h>
+#include <kicondialog.h>
+#include <kconfig.h>
+#include <kdebug.h>
+#include <klistview.h>
+#include <kpushbutton.h>
+
+#include <qcheckbox.h>
+#include <qlayout.h>
+#include <qgroupbox.h>
+#include <qregexp.h>
+#include <qlabel.h>
+#include <qvbox.h>
+#include <qtooltip.h>
+#include <qwhatsthis.h>
+
+#include <assert.h>
+#include <qhbuttongroup.h>
+#include <qradiobutton.h>
+#include <qtextedit.h>
+
+#include "templatesconfiguration.h"
+#include "templatesconfiguration_kfg.h"
+
+#include "kmfolderdia.moc"
+
+using namespace KMail;
+
+static QString inCaseWeDecideToRenameTheTab( I18N_NOOP( "Permissions (ACL)" ) );
+
+//-----------------------------------------------------------------------------
+KMFolderDialog::KMFolderDialog(KMFolder *aFolder, KMFolderDir *aFolderDir,
+ KMFolderTree* aParent, const QString& aCap,
+ const QString& aName):
+ KDialogBase( KDialogBase::Tabbed,
+ aCap, KDialogBase::Ok|KDialogBase::Cancel,
+ KDialogBase::Ok, aParent, "KMFolderDialog", TRUE ),
+ mFolder( aFolder ),
+ mFolderDir( aFolderDir ),
+ mParentFolder( 0 ),
+ mIsNewFolder( aFolder == 0 ),
+ mFolderTree( aParent )
+{
+ kdDebug(5006)<<"KMFolderDialog::KMFolderDialog()" << endl;
+
+ QStringList folderNames;
+ QValueList<QGuardedPtr<KMFolder> > folders;
+ // get all folders but search and folders that can not have children
+ aParent->createFolderList(&folderNames, &folders, true, true,
+ true, false, true, false);
+
+ if( mFolderDir ) {
+ // search the parent folder of the folder
+ FolderList::ConstIterator it;
+ int i = 1;
+ for( it = folders.begin(); it != folders.end(); ++it, ++i ) {
+ if( (*it)->child() == mFolderDir ) {
+ mParentFolder = *it;
+ break;
+ }
+ }
+ }
+
+ FolderDiaTab* tab;
+ QVBox* box;
+
+ box = addVBoxPage( i18n("General") );
+ tab = new FolderDiaGeneralTab( this, aName, box );
+ addTab( tab );
+ box = addVBoxPage( i18n("Templates") );
+ tab = new FolderDiaTemplatesTab( this, box );
+ addTab( tab );
+
+ KMFolder* refFolder = mFolder ? mFolder : mParentFolder;
+ KMFolderType folderType = refFolder ? refFolder->folderType() : KMFolderTypeUnknown;
+ bool noContent = mFolder ? mFolder->storage()->noContent() : false;
+ if ( !noContent && refFolder && ( folderType == KMFolderTypeImap || folderType == KMFolderTypeCachedImap ) ) {
+ if ( FolderDiaACLTab::supports( refFolder ) ) {
+ box = addVBoxPage( i18n("Access Control") );
+ tab = new FolderDiaACLTab( this, box );
+ addTab( tab );
+ }
+ }
+ if ( !noContent && refFolder && ( folderType == KMFolderTypeImap || folderType == KMFolderTypeCachedImap ) ) {
+ if ( FolderDiaQuotaTab::supports( refFolder ) ) {
+ box = addVBoxPage( i18n("Quota") );
+ tab = new FolderDiaQuotaTab( this, box );
+ addTab( tab );
+ }
+ }
+
+ for ( unsigned int i = 0 ; i < mTabs.count() ; ++i )
+ mTabs[i]->load();
+}
+
+void KMFolderDialog::addTab( FolderDiaTab* tab )
+{
+ connect( tab, SIGNAL( readyForAccept() ),
+ this, SLOT( slotReadyForAccept() ) );
+ connect( tab, SIGNAL( cancelAccept() ),
+ this, SLOT( slotCancelAccept() ) );
+ //connect( tab, SIGNAL(changed( bool )),
+ // this, SLOT(slotChanged( bool )) );
+ mTabs.append( tab );
+}
+
+// Not used yet (no button), but ready to be used :)
+void KMFolderDialog::slotApply()
+{
+ if ( mFolder.isNull() && !mIsNewFolder ) { // deleted meanwhile?
+ KDialogBase::slotApply();
+ return;
+ }
+ for ( unsigned int i = 0 ; i < mTabs.count() ; ++i )
+ mTabs[i]->save();
+ if ( !mFolder.isNull() && mIsNewFolder ) // we just created it
+ mIsNewFolder = false; // so it's not new anymore :)
+ KDialogBase::slotApply();
+}
+
+// Called when pressing Ok
+// We want to apply the changes first (which is async), before closing the dialog,
+// in case of errors during the upload.
+void KMFolderDialog::slotOk()
+{
+ if ( mFolder.isNull() && !mIsNewFolder ) { // deleted meanwhile?
+ KDialogBase::slotOk();
+ return;
+ }
+
+ mDelayedSavingTabs = 0; // number of tabs which need delayed saving
+ for ( unsigned int i = 0 ; i < mTabs.count() ; ++i ) {
+ FolderDiaTab::AcceptStatus s = mTabs[i]->accept();
+ if ( s == FolderDiaTab::Canceled ) {
+ slotCancelAccept();
+ return;
+ }
+ else if ( s == FolderDiaTab::Delayed )
+ ++mDelayedSavingTabs;
+ }
+
+ if ( mDelayedSavingTabs )
+ enableButtonOK( false );
+ else
+ KDialogBase::slotOk();
+}
+
+void KMFolderDialog::slotReadyForAccept()
+{
+ --mDelayedSavingTabs;
+ if ( mDelayedSavingTabs == 0 )
+ KDialogBase::slotOk();
+}
+
+void KMFolderDialog::slotCancelAccept()
+{
+ mDelayedSavingTabs = -1;
+ enableButtonOK( true );
+ // Don't try to create it twice
+ if ( !mFolder.isNull() )
+ mIsNewFolder = false;
+
+ // Other tabs might call slotReadyForAccept. -1 ensures that it won't close the dialog,
+ // but the OK button being enabled means that people might succeed in running
+ // the same job from save more than once.
+ // Solution: mAcceptCanceled = true instead of -1.
+ // Bah for now we only have one tab which can delay saving -> later.
+}
+
+void KMFolderDialog::slotChanged( bool )
+{
+ // TODO, support for 'changed', and Apply button.
+ // sample code for here: KCMultiDialog calls bool changed() on every KCModuleProxy...
+}
+
+void KMFolderDialog::setFolder( KMFolder* folder )
+{
+ Q_ASSERT( mFolder.isNull() );
+ mFolder = folder;
+}
+
+static void addLine( QWidget *parent, QVBoxLayout* layout )
+{
+ QFrame *line = new QFrame( parent, "line" );
+ line->setGeometry( QRect( 80, 150, 250, 20 ) );
+ line->setFrameShape( QFrame::HLine );
+ line->setFrameShadow( QFrame::Sunken );
+ line->setFrameShape( QFrame::HLine );
+ layout->addWidget( line );
+}
+
+//----------------------------------------------------------------------------
+KMail::FolderDiaGeneralTab::FolderDiaGeneralTab( KMFolderDialog* dlg,
+ const QString& aName,
+ QWidget* parent, const char* name )
+ : FolderDiaTab( parent, name ), mDlg( dlg )
+{
+
+
+ mIsLocalSystemFolder = mDlg->folder()->isSystemFolder() &&
+ mDlg->folder()->folderType() != KMFolderTypeImap &&
+ mDlg->folder()->folderType() != KMFolderTypeCachedImap;
+
+ QLabel *label;
+
+ QVBoxLayout *topLayout = new QVBoxLayout( this, 0, KDialog::spacingHint() );
+
+ // Musn't be able to edit details for a system folder.
+ if ( !mIsLocalSystemFolder ) {
+
+ QHBoxLayout *hl = new QHBoxLayout( topLayout );
+ hl->setSpacing( KDialog::spacingHint() );
+
+ label = new QLabel( i18n("&Name:"), this );
+ hl->addWidget( label );
+
+ mNameEdit = new KLineEdit( this );
+ if( !mDlg->folder() )
+ mNameEdit->setFocus();
+ mNameEdit->setText( mDlg->folder() ? mDlg->folder()->label() : i18n("unnamed") );
+ if (!aName.isEmpty())
+ mNameEdit->setText(aName);
+ mNameEdit->setMinimumSize(mNameEdit->sizeHint());
+ // prevent renaming of IMAP inbox
+ if ( mDlg->folder() && mDlg->folder()->isSystemFolder() ) {
+ QString imapPath;
+ if ( mDlg->folder()->folderType() == KMFolderTypeImap )
+ imapPath = static_cast<KMFolderImap*>( mDlg->folder()->storage() )->imapPath();
+ if ( mDlg->folder()->folderType() == KMFolderTypeCachedImap )
+ imapPath = static_cast<KMFolderCachedImap*>( mDlg->folder()->storage() )->imapPath();
+ if ( imapPath == "/INBOX/" )
+ mNameEdit->setEnabled( false );
+ }
+ label->setBuddy( mNameEdit );
+ hl->addWidget( mNameEdit );
+ connect( mNameEdit, SIGNAL( textChanged( const QString & ) ),
+ this, SLOT( slotFolderNameChanged( const QString & ) ) );
+
+
+ //start icons group
+ QVBoxLayout *ivl = new QVBoxLayout( topLayout );
+ ivl->setSpacing( KDialog::spacingHint() );
+
+ QHBoxLayout *ihl = new QHBoxLayout( ivl );
+ mIconsCheckBox = new QCheckBox( i18n("Use custom &icons"), this );
+ mIconsCheckBox->setChecked( false );
+ ihl->addWidget( mIconsCheckBox );
+ ihl->addStretch( 2 );
+
+ mNormalIconLabel = new QLabel( i18n("&Normal:"), this );
+ mNormalIconLabel->setEnabled( false );
+ ihl->addWidget( mNormalIconLabel );
+
+ mNormalIconButton = new KIconButton( this );
+ mNormalIconLabel->setBuddy( mNormalIconButton );
+ mNormalIconButton->setIconType( KIcon::NoGroup , KIcon::Any, true );
+ mNormalIconButton->setIconSize( 16 );
+ mNormalIconButton->setStrictIconSize( true );
+ mNormalIconButton->setFixedSize( 28, 28 );
+ // Can't use iconset here
+ mNormalIconButton->setIcon( "folder" );
+ mNormalIconButton->setEnabled( false );
+ ihl->addWidget( mNormalIconButton );
+
+ mUnreadIconLabel = new QLabel( i18n("&Unread:"), this );
+ mUnreadIconLabel->setEnabled( false );
+ ihl->addWidget( mUnreadIconLabel );
+
+ mUnreadIconButton = new KIconButton( this );
+ mUnreadIconLabel->setBuddy( mUnreadIconButton );
+ mUnreadIconButton->setIconType( KIcon::NoGroup, KIcon::Any, true );
+ mUnreadIconButton->setIconSize( 16 );
+ mUnreadIconButton->setStrictIconSize( true );
+ mUnreadIconButton->setFixedSize( 28, 28 );
+ // Can't use iconset here
+ mUnreadIconButton->setIcon( "folder_open" );
+ mUnreadIconButton->setEnabled( false );
+ ihl->addWidget( mUnreadIconButton );
+ ihl->addStretch( 1 );
+
+ connect( mIconsCheckBox, SIGNAL(toggled(bool)),
+ mNormalIconButton, SLOT(setEnabled(bool)) );
+ connect( mIconsCheckBox, SIGNAL(toggled(bool)),
+ mUnreadIconButton, SLOT(setEnabled(bool)) );
+ connect( mIconsCheckBox, SIGNAL(toggled(bool)),
+ mNormalIconLabel, SLOT(setEnabled(bool)) );
+ connect( mIconsCheckBox, SIGNAL(toggled(bool)),
+ mUnreadIconLabel, SLOT(setEnabled(bool)) );
+
+ connect( mNormalIconButton, SIGNAL(iconChanged(QString)),
+ this, SLOT(slotChangeIcon(QString)) );
+
+ //end icons group
+ addLine( this, topLayout);
+ }
+
+
+ // should new mail in this folder be ignored?
+ QHBoxLayout *hbl = new QHBoxLayout( topLayout );
+ hbl->setSpacing( KDialog::spacingHint() );
+ mNotifyOnNewMailCheckBox =
+ new QCheckBox( i18n("Act on new/unread mail in this folder" ), this );
+ QWhatsThis::add( mNotifyOnNewMailCheckBox,
+ i18n( "<qt><p>If this option is enabled then you will be notified about "
+ "new/unread mail in this folder. Moreover, going to the "
+ "next/previous folder with unread messages will stop at this "
+ "folder.</p>"
+ "<p>Uncheck this option if you do not want to be notified about "
+ "new/unread mail in this folder and if you want this folder to "
+ "be skipped when going to the next/previous folder with unread "
+ "messages. This is useful for ignoring any new/unread mail in "
+ "your trash and spam folder.</p></qt>" ) );
+ hbl->addWidget( mNotifyOnNewMailCheckBox );
+
+ if ( mDlg->folder()->folderType() == KMFolderTypeImap ) {
+ // should this folder be included in new-mail-checks?
+
+ QHBoxLayout *nml = new QHBoxLayout( topLayout );
+ nml->setSpacing( KDialog::spacingHint() );
+ mNewMailCheckBox = new QCheckBox( i18n("Include this folder in mail checks"), this );
+ // default is on
+ mNewMailCheckBox->setChecked(true);
+ nml->addWidget( mNewMailCheckBox );
+ nml->addStretch( 1 );
+ }
+
+ // should replies to mails in this folder be kept in this same folder?
+ hbl = new QHBoxLayout( topLayout );
+ hbl->setSpacing( KDialog::spacingHint() );
+ mKeepRepliesInSameFolderCheckBox =
+ new QCheckBox( i18n("Keep replies in this folder" ), this );
+ QWhatsThis::add( mKeepRepliesInSameFolderCheckBox,
+ i18n( "Check this option if you want replies you write "
+ "to mails in this folder to be put in this same folder "
+ "after sending, instead of in the configured sent-mail folder." ) );
+ hbl->addWidget( mKeepRepliesInSameFolderCheckBox );
+ hbl->addStretch( 1 );
+
+ addLine( this, topLayout );
+
+ // use grid layout for the following combobox settings
+ QGridLayout *gl = new QGridLayout( topLayout, 3, 2, KDialog::spacingHint() );
+ gl->setColStretch( 1, 100 ); // make the second column use all available space
+ int row = -1;
+
+ // sender or receiver column?
+ ++row;
+ QString tip = i18n("Show Sender/Receiver Column in List of Messages");
+
+ QLabel *sender_label = new QLabel( i18n("Sho&w column:" ), this );
+ gl->addWidget( sender_label, row, 0 );
+ mShowSenderReceiverComboBox = new QComboBox( this );
+ QToolTip::add( mShowSenderReceiverComboBox, tip );
+ sender_label->setBuddy(mShowSenderReceiverComboBox);
+ gl->addWidget( mShowSenderReceiverComboBox, row, 1 );
+ mShowSenderReceiverComboBox->insertItem(i18n("Default"), 0);
+ mShowSenderReceiverComboBox->insertItem(i18n("Sender"), 1);
+ mShowSenderReceiverComboBox->insertItem(i18n("Receiver"), 2);
+
+ QString whoField;
+ if (mDlg->folder()) whoField = mDlg->folder()->userWhoField();
+ if (whoField.isEmpty()) mShowSenderReceiverComboBox->setCurrentItem(0);
+ else if (whoField == "From") mShowSenderReceiverComboBox->setCurrentItem(1);
+ else if (whoField == "To") mShowSenderReceiverComboBox->setCurrentItem(2);
+
+
+ // sender identity
+ ++row;
+ label = new QLabel( i18n("&Sender identity:"), this );
+ gl->addWidget( label, row, 0 );
+ mIdentityComboBox = new KPIM::IdentityCombo( kmkernel->identityManager(), this );
+ label->setBuddy( mIdentityComboBox );
+ gl->addWidget( mIdentityComboBox, row, 1 );
+ QWhatsThis::add( mIdentityComboBox,
+ i18n( "Select the sender identity to be used when writing new mail "
+ "or replying to mail in this folder. This means that if you are in "
+ "one of your work folders, you can make KMail use the corresponding "
+ "sender email address, signature and signing or encryption keys "
+ "automatically. Identities can be set up in the main configuration "
+ "dialog. (Settings -> Configure KMail)") );
+
+
+ // folder contents
+ if ( !mIsLocalSystemFolder && kmkernel->iCalIface().isEnabled() ) {
+ // Only do make this settable, if the IMAP resource is enabled
+ // and it's not the personal folders (those must not be changed)
+ ++row;
+ label = new QLabel( i18n("&Folder contents:"), this );
+ gl->addWidget( label, row, 0 );
+ mContentsComboBox = new QComboBox( this );
+ label->setBuddy( mContentsComboBox );
+ gl->addWidget( mContentsComboBox, row, 1 );
+
+ mContentsComboBox->insertItem( i18n( "Mail" ) );
+ mContentsComboBox->insertItem( i18n( "Calendar" ) );
+ mContentsComboBox->insertItem( i18n( "Contacts" ) );
+ mContentsComboBox->insertItem( i18n( "Notes" ) );
+ mContentsComboBox->insertItem( i18n( "Tasks" ) );
+ mContentsComboBox->insertItem( i18n( "Journal" ) );
+ if ( mDlg->folder() )
+ mContentsComboBox->setCurrentItem( mDlg->folder()->storage()->contentsType() );
+ connect ( mContentsComboBox, SIGNAL ( activated( int ) ),
+ this, SLOT( slotFolderContentsSelectionChanged( int ) ) );
+ if ( mDlg->folder()->isReadOnly() )
+ mContentsComboBox->setEnabled( false );
+ } else {
+ mContentsComboBox = 0;
+ }
+
+ mIncidencesForComboBox = 0;
+ mAlarmsBlockedCheckBox = 0;
+
+ // Kolab incidences-for annotation.
+ // Show incidences-for combobox if the contents type can be changed (new folder),
+ // or if it's set to calendar or task (existing folder)
+ if ( ( GlobalSettings::self()->theIMAPResourceStorageFormat() ==
+ GlobalSettings::EnumTheIMAPResourceStorageFormat::XML ) &&
+ mContentsComboBox ) {
+ ++row;
+
+ QLabel* label = new QLabel( i18n( "Generate free/&busy and activate alarms for:" ), this );
+ gl->addWidget( label, row, 0 );
+ mIncidencesForComboBox = new QComboBox( this );
+ label->setBuddy( mIncidencesForComboBox );
+ gl->addWidget( mIncidencesForComboBox, row, 1 );
+
+ const QString 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. "
+ "The setting applies to Calendar and Task folders only "
+ "(for tasks, this setting is only used for alarms).\n\n"
+ "Example use cases: if the boss shares a folder with his secretary, "
+ "only the boss should be marked as busy for his meetings, so he should "
+ "select \"Admins\", since the secretary has no admin rights on the folder.\n"
+ "On the other hand if a working group shares a Calendar for "
+ "group meetings, all readers of the folders should be marked "
+ "as busy for meetings.\n"
+ "A company-wide folder with optional events in it would use \"Nobody\" "
+ "since it is not known who will go to those events." );
+
+ QWhatsThis::add( mIncidencesForComboBox, whatsThisForMyOwnFolders );
+ mIncidencesForComboBox->insertItem( i18n( "Nobody" ) );
+ mIncidencesForComboBox->insertItem( i18n( "Admins of This Folder" ) );
+ mIncidencesForComboBox->insertItem( i18n( "All Readers of This Folder" ) );
+ ++row;
+ const QString whatsThisForReadOnlyFolders =
+ i18n( "This setting allows you to disable alarms for folders shared by "
+ "others. ");
+ mAlarmsBlockedCheckBox = new QCheckBox( this );
+ gl->addWidget( mAlarmsBlockedCheckBox, row, 0 );
+ label = new QLabel( i18n( "Block free/&busy and alarms locally" ), this );
+ gl->addWidget( label, row, 1 );
+ label->setBuddy( mAlarmsBlockedCheckBox );
+ QWhatsThis::add( mAlarmsBlockedCheckBox, whatsThisForReadOnlyFolders );
+
+ if ( mDlg->folder()->storage()->contentsType() != KMail::ContentsTypeCalendar
+ && mDlg->folder()->storage()->contentsType() != KMail::ContentsTypeTask ) {
+ mIncidencesForComboBox->setEnabled( false );
+ mAlarmsBlockedCheckBox->setEnabled( false );
+ }
+ }
+ topLayout->addStretch( 100 ); // eat all superfluous space
+
+ initializeWithValuesFromFolder( mDlg->folder() );
+}
+
+void FolderDiaGeneralTab::load()
+{
+ // Nothing here, all is done in the ctor
+}
+
+void FolderDiaGeneralTab::initializeWithValuesFromFolder( KMFolder* folder ) {
+ if ( !folder )
+ return;
+
+ if ( !mIsLocalSystemFolder ) {
+ // folder icons
+ mIconsCheckBox->setChecked( folder->useCustomIcons() );
+ mNormalIconLabel->setEnabled( folder->useCustomIcons() );
+ mNormalIconButton->setEnabled( folder->useCustomIcons() );
+ mUnreadIconLabel->setEnabled( folder->useCustomIcons() );
+ mUnreadIconButton->setEnabled( folder->useCustomIcons() );
+ QString iconPath = folder->normalIconPath();
+ if ( !iconPath.isEmpty() )
+ mNormalIconButton->setIcon( iconPath );
+ iconPath = folder->unreadIconPath();
+ if ( !iconPath.isEmpty() )
+ mUnreadIconButton->setIcon( iconPath );
+ }
+
+ // folder identity
+ mIdentityComboBox->setCurrentIdentity( folder->identity() );
+ // ignore new mail
+ mNotifyOnNewMailCheckBox->setChecked( !folder->ignoreNewMail() );
+
+ const bool keepInFolder = !folder->isReadOnly() && folder->putRepliesInSameFolder();
+ mKeepRepliesInSameFolderCheckBox->setChecked( keepInFolder );
+ mKeepRepliesInSameFolderCheckBox->setDisabled( folder->isReadOnly() );
+
+ if (folder->folderType() == KMFolderTypeImap)
+ {
+ KMFolderImap* imapFolder = static_cast<KMFolderImap*>(folder->storage());
+ bool checked = imapFolder->includeInMailCheck();
+ mNewMailCheckBox->setChecked(checked);
+ }
+
+ if ( mIncidencesForComboBox ) {
+ KMFolderCachedImap* dimap = static_cast<KMFolderCachedImap *>( folder->storage() );
+ mIncidencesForComboBox->setCurrentItem( dimap->incidencesFor() );
+ mIncidencesForComboBox->setDisabled( mDlg->folder()->isReadOnly() );
+ }
+ if ( mAlarmsBlockedCheckBox ) {
+ KMFolderCachedImap* dimap = static_cast<KMFolderCachedImap *>( folder->storage() );
+ mAlarmsBlockedCheckBox->setChecked( dimap->alarmsBlocked() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void FolderDiaGeneralTab::slotFolderNameChanged( const QString& str )
+{
+ mDlg->enableButtonOK( !str.isEmpty() );
+}
+
+//-----------------------------------------------------------------------------
+void FolderDiaGeneralTab::slotFolderContentsSelectionChanged( int )
+{
+ KMail::FolderContentsType type =
+ static_cast<KMail::FolderContentsType>( mContentsComboBox->currentItem() );
+ if( type != KMail::ContentsTypeMail && GlobalSettings::self()->hideGroupwareFolders() ) {
+ QString message = i18n("You have configured this folder to contain groupware information "
+ "and the general configuration option to hide groupware folders is "
+ "set. That means that this folder will disappear once the configuration "
+ "dialog is closed. If you want to remove the folder again, you will need "
+ "to temporarily disable hiding of groupware folders to be able to see it.");
+ KMessageBox::information( this, message );
+ }
+ const bool enable = type == KMail::ContentsTypeCalendar ||
+ type == KMail::ContentsTypeTask;
+ if ( mIncidencesForComboBox )
+ mIncidencesForComboBox->setEnabled( enable );
+ if ( mAlarmsBlockedCheckBox )
+ mAlarmsBlockedCheckBox->setEnabled( enable );
+}
+
+//-----------------------------------------------------------------------------
+bool FolderDiaGeneralTab::save()
+{
+ KMFolder* folder = mDlg->folder();
+ folder->setIdentity( mIdentityComboBox->currentIdentity() );
+ // set whoField
+ if (mShowSenderReceiverComboBox->currentItem() == 1)
+ folder->setUserWhoField("From");
+ else if (mShowSenderReceiverComboBox->currentItem() == 2)
+ folder->setUserWhoField("To");
+ else
+ folder->setUserWhoField("");
+
+ folder->setIgnoreNewMail( !mNotifyOnNewMailCheckBox->isChecked() );
+ folder->setPutRepliesInSameFolder( mKeepRepliesInSameFolderCheckBox->isChecked() );
+
+ QString fldName, oldFldName;
+ if ( !mIsLocalSystemFolder )
+ {
+ QString acctName;
+ oldFldName = mDlg->folder()->name();
+
+ if (!mNameEdit->text().isEmpty())
+ fldName = mNameEdit->text();
+ else
+ fldName = oldFldName;
+
+ if ( mDlg->parentFolder() &&
+ mDlg->parentFolder()->folderType() != KMFolderTypeImap &&
+ mDlg->parentFolder()->folderType() != KMFolderTypeCachedImap )
+ fldName.remove('/');
+ fldName.remove(QRegExp("^\\.*"));
+ if (fldName.isEmpty()) fldName = i18n("unnamed");
+
+
+ // Update the tree iff new icon paths are different and not empty or if
+ // useCustomIcons changed.
+ if ( folder->useCustomIcons() != mIconsCheckBox->isChecked() ) {
+ folder->setUseCustomIcons( mIconsCheckBox->isChecked() );
+ // Reset icons, useCustomIcons was turned off.
+ if ( !folder->useCustomIcons() ) {
+ folder->setIconPaths( "", "" );
+ }
+ }
+ if ( folder->useCustomIcons() &&
+ (( mNormalIconButton->icon() != folder->normalIconPath() ) &&
+ ( !mNormalIconButton->icon().isEmpty())) ||
+ (( mUnreadIconButton->icon() != folder->unreadIconPath() ) &&
+ ( !mUnreadIconButton->icon().isEmpty())) ) {
+ folder->setIconPaths( mNormalIconButton->icon(), mUnreadIconButton->icon() );
+ }
+
+ // Set type field
+ if ( mContentsComboBox ) {
+ KMail::FolderContentsType type =
+ static_cast<KMail::FolderContentsType>( mContentsComboBox->currentItem() );
+ folder->storage()->setContentsType( type );
+ }
+
+ if ( folder->folderType() == KMFolderTypeCachedImap ) {
+ KMFolderCachedImap* dimap = static_cast<KMFolderCachedImap *>( mDlg->folder()->storage() );
+ if ( mIncidencesForComboBox ) {
+ KMFolderCachedImap::IncidencesFor incfor = KMFolderCachedImap::IncForAdmins;
+ incfor = static_cast<KMFolderCachedImap::IncidencesFor>( mIncidencesForComboBox->currentItem() );
+ if ( dimap->incidencesFor() != incfor ) {
+ dimap->setIncidencesFor( incfor );
+ dimap->writeConfig();
+ }
+ }
+ if ( mAlarmsBlockedCheckBox && mAlarmsBlockedCheckBox->isChecked() != dimap->alarmsBlocked() ) {
+ dimap->setAlarmsBlocked( mAlarmsBlockedCheckBox->isChecked() );
+ dimap->writeConfig();
+ }
+ }
+
+ if( folder->folderType() == KMFolderTypeImap )
+ {
+ KMFolderImap* imapFolder = static_cast<KMFolderImap*>( folder->storage() );
+ imapFolder->setIncludeInMailCheck(
+ mNewMailCheckBox->isChecked() );
+ }
+ // make sure everything is on disk, connected slots will call readConfig()
+ // when creating a new folder.
+ folder->storage()->writeConfig();
+ // 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
+ // something like Calendar -> CalendarFoo -> Calendar inbetween syncs would
+ // fail. Therefor let the folder sort it out itself, whether the rename is
+ // a noop or not.
+ if ( !oldFldName.isEmpty() )
+ {
+ kmkernel->folderMgr()->renameFolder( folder, fldName );
+ } else {
+ kmkernel->folderMgr()->contentsChanged();
+ }
+ }
+ return true;
+}
+
+void FolderDiaGeneralTab::slotChangeIcon( QString icon ) // can't use a const-ref here, due to KIconButton's signal
+{
+ mUnreadIconButton->setIcon( icon );
+}
+
+//----------------------------------------------------------------------------
+KMail::FolderDiaTemplatesTab::FolderDiaTemplatesTab( KMFolderDialog* dlg,
+ QWidget* parent )
+ : FolderDiaTab( parent, 0 ), mDlg( dlg )
+{
+
+ mIsLocalSystemFolder = mDlg->folder()->isSystemFolder() &&
+ mDlg->folder()->folderType() != KMFolderTypeImap &&
+ mDlg->folder()->folderType() != KMFolderTypeCachedImap;
+
+ QVBoxLayout *topLayout = new QVBoxLayout( this, 0, KDialog::spacingHint() );
+
+ mCustom = new QCheckBox( i18n("&Use custom message templates"), this );
+ topLayout->addWidget( mCustom );
+
+ mWidget = new TemplatesConfiguration( this , "folder-templates" );
+ mWidget->setEnabled( false );
+ topLayout->addWidget( mWidget );
+
+ QHBoxLayout *btns = new QHBoxLayout( topLayout, KDialog::spacingHint() );
+ mCopyGlobal = new KPushButton( i18n("&Copy global templates"), this );
+ mCopyGlobal->setEnabled( false );
+ btns->addWidget( mCopyGlobal );
+
+ connect( mCustom, SIGNAL(toggled(bool)),
+ mWidget, SLOT(setEnabled(bool)) );
+ connect( mCustom, SIGNAL(toggled(bool)),
+ mCopyGlobal, SLOT(setEnabled(bool)) );
+
+ connect( mCopyGlobal, SIGNAL(clicked()),
+ this, SLOT(slotCopyGlobal()) );
+
+ initializeWithValuesFromFolder( mDlg->folder() );
+
+ connect( mWidget, SIGNAL( changed() ),
+ this, SLOT( slotEmitChanged( void ) ) );
+}
+
+void FolderDiaTemplatesTab::load()
+{
+
+}
+
+void FolderDiaTemplatesTab::initializeWithValuesFromFolder( KMFolder* folder ) {
+ if ( !folder )
+ return;
+
+ mFolder = folder;
+
+ QString fid = folder->idString();
+
+ Templates t( fid );
+
+ mCustom->setChecked(t.useCustomTemplates());
+
+ mIdentity = folder->identity();
+
+ mWidget->loadFromFolder( fid, mIdentity );
+}
+
+//-----------------------------------------------------------------------------
+bool FolderDiaTemplatesTab::save()
+{
+ KMFolder* folder = mDlg->folder();
+
+ QString fid = folder->idString();
+ Templates t(fid);
+
+ kdDebug() << "use custom templates for folder " << fid << ": " << mCustom->isChecked() << endl;
+ t.setUseCustomTemplates(mCustom->isChecked());
+ t.writeConfig();
+
+ mWidget->saveToFolder(fid);
+
+ return true;
+}
+
+
+void FolderDiaTemplatesTab::slotEmitChanged() {}
+
+void FolderDiaTemplatesTab::slotCopyGlobal() {
+ if ( mIdentity ) {
+ mWidget->loadFromIdentity( mIdentity );
+ }
+ else {
+ mWidget->loadFromGlobal();
+ }
+}
diff --git a/kmail/kmfolderdia.h b/kmail/kmfolderdia.h
new file mode 100644
index 00000000..c1df2b54
--- /dev/null
+++ b/kmail/kmfolderdia.h
@@ -0,0 +1,241 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+/**
+ * kmfolderdia.h
+ *
+ * Copyright (c) 1997-2004 KMail Developers
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+#ifndef __KMFOLDERDIA
+#define __KMFOLDERDIA
+
+#include <kdialogbase.h>
+#include "configuredialog_p.h"
+#include <qvaluevector.h>
+
+class QCheckBox;
+class QPushButton;
+class QLineEdit;
+class QListBox;
+class QComboBox;
+class KMFolder;
+class KMFolderTreeItem;
+class KMFolderDir;
+class KIntNumInput;
+class KIconButton;
+class KEditListBox;
+namespace KPIM { class IdentityCombo; }
+class KMFolderDialog;
+class KMFolderTree;
+template <typename T> class QGuardedPtr;
+class TemplatesConfiguration;
+class KPushButton;
+
+namespace KMail {
+ class FolderRequester;
+/**
+ * This is the base class for tabs in the folder dialog.
+ * It uses the API from ConfigModuleTab (basically: it's a widget that can load and save)
+ * but it also adds support for delayed-saving:
+ * when save() needs to use async jobs (e.g. KIO) for saving,
+ * we need to delay the closing until after the jobs are finished,
+ * and to cancel the saving on error.
+ *
+ * Feel free to rename and move this base class somewhere else if it
+ * can be useful for other dialogs.
+ */
+class FolderDiaTab : public QWidget
+{
+ Q_OBJECT
+public:
+ FolderDiaTab( QWidget *parent=0, const char* name=0 )
+ : QWidget( parent, name ) {}
+
+ virtual void load() = 0;
+
+ /// Unlike ConfigModuleTab, we return a bool from save.
+ /// This allows to cancel closing on error.
+ /// When called from the Apply button, the return value is ignored
+ /// @return whether save succeeded
+ virtual bool save() = 0;
+
+ enum AcceptStatus { Accepted, Canceled, Delayed };
+ /// Called when clicking OK.
+ /// If a module returns Delayed, the closing is cancelled for now,
+ /// and the module can close the dialog later on (i.e. after an async
+ /// operation like a KIO job).
+ virtual AcceptStatus accept() {
+ return save() ? Accepted : Canceled;
+ }
+
+signals:
+ /// Emit this to tell the dialog that you're done with the async jobs,
+ /// and that the dialog can be closed.
+ void readyForAccept();
+
+ /// Emit this, i.e. after a job had an error, to tell the dialog to cancel
+ /// the closing.
+ void cancelAccept();
+
+ /// Called when this module was changed [not really used yet]
+ void changed(bool);
+};
+
+/**
+ * "General" tab in the folder dialog
+ * Internal class, only used by KMFolderDialog
+ */
+class FolderDiaGeneralTab : public FolderDiaTab
+{
+ Q_OBJECT
+
+public:
+ FolderDiaGeneralTab( KMFolderDialog* dlg,
+ const QString& aName,
+ QWidget* parent, const char* name = 0 );
+
+ virtual void load();
+ virtual bool save();
+
+private slots:
+ void slotChangeIcon( QString icon );
+ /*
+ * is called if the folder dropdown changes
+ * then we update the other items to reflect the capabilities
+ */
+ void slotFolderNameChanged( const QString& );
+ void slotFolderContentsSelectionChanged( int );
+
+private:
+ void initializeWithValuesFromFolder( KMFolder* folder );
+
+private:
+ QComboBox *mShowSenderReceiverComboBox;
+ QComboBox *mContentsComboBox;
+ QComboBox *mIncidencesForComboBox;
+ QCheckBox *mAlarmsBlockedCheckBox;
+ QLabel *mNormalIconLabel;
+ KIconButton *mNormalIconButton;
+ QLabel *mUnreadIconLabel;
+ KIconButton *mUnreadIconButton;
+ QCheckBox *mIconsCheckBox;
+ QCheckBox *mNewMailCheckBox;
+ QCheckBox *mNotifyOnNewMailCheckBox;
+ QCheckBox *mKeepRepliesInSameFolderCheckBox;
+ KLineEdit *mNameEdit;
+
+ KPIM::IdentityCombo *mIdentityComboBox;
+
+ KMFolderDialog* mDlg;
+ bool mIsLocalSystemFolder;
+};
+
+/**
+ * "Templates" tab in the folder dialog
+ * Internal class, only used by KMFolderDialog
+ */
+class FolderDiaTemplatesTab : public FolderDiaTab
+{
+ Q_OBJECT
+
+public:
+ FolderDiaTemplatesTab( KMFolderDialog *dlg, QWidget *parent );
+
+ virtual void load();
+ virtual bool save();
+
+public slots:
+ void slotEmitChanged(); // do nothing for now
+
+ void slotCopyGlobal();
+
+private:
+ void initializeWithValuesFromFolder( KMFolder* folder );
+
+private:
+ QCheckBox* mCustom;
+ TemplatesConfiguration* mWidget;
+ KPushButton* mCopyGlobal;
+ KMFolder* mFolder;
+ uint mIdentity;
+
+ KMFolderDialog* mDlg;
+ bool mIsLocalSystemFolder;
+};
+
+} // end of namespace KMail
+
+/**
+ * Dialog for handling the properties of a mail folder
+ */
+class KMFolderDialog : public KDialogBase
+{
+ Q_OBJECT
+
+public:
+ KMFolderDialog( KMFolder *folder, KMFolderDir *aFolderDir,
+ KMFolderTree* parent, const QString& caption,
+ const QString& name = QString::null );
+
+ KMFolder* folder() const { return mFolder; }
+ void setFolder( KMFolder* folder );
+ // Was mFolder just created? (This only makes sense from save())
+ // If Apply is clicked, or OK proceeeds half-way, then next time "new folder" will be false.
+ bool isNewFolder() const { return mIsNewFolder; }
+
+ KMFolderDir* folderDir() const { return mFolderDir; }
+ typedef QValueList<QGuardedPtr<KMFolder> > FolderList;
+
+ KMFolder* parentFolder() const { return mParentFolder; }
+
+ KMFolderTree* folderTree() const { return mFolderTree; }
+
+protected slots:
+ void slotChanged( bool );
+ virtual void slotOk();
+ virtual void slotApply();
+
+ void slotReadyForAccept();
+ void slotCancelAccept();
+
+private:
+ void addTab( KMail::FolderDiaTab* tab );
+
+private:
+ // Can be 0 initially when creating a folder, but will be set by save() in the first tab.
+ QGuardedPtr<KMFolder> mFolder;
+ QGuardedPtr<KMFolderDir> mFolderDir;
+ QGuardedPtr<KMFolder> mParentFolder;
+
+ bool mIsNewFolder; // if true, save() did set mFolder.
+
+ QValueVector<KMail::FolderDiaTab*> mTabs;
+ int mDelayedSavingTabs; // this should go into a base class one day
+ KMFolderTree* mFolderTree;
+};
+
+#endif /*__KMFOLDERDIA*/
+
diff --git a/kmail/kmfolderdir.cpp b/kmail/kmfolderdir.cpp
new file mode 100644
index 00000000..1092e41c
--- /dev/null
+++ b/kmail/kmfolderdir.cpp
@@ -0,0 +1,313 @@
+// kmfolderdir.cpp
+
+#include <config.h>
+#include <qdir.h>
+
+#include "kmfolderdir.h"
+#include "kmfoldersearch.h"
+#include "kmfoldercachedimap.h"
+#include "kmfolder.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+#include <kstandarddirs.h>
+
+
+//=============================================================================
+//=============================================================================
+KMFolderRootDir::KMFolderRootDir(KMFolderMgr* manager, const QString& path,
+ KMFolderDirType dirType)
+ : KMFolderDir( 0, 0, path, dirType ),
+ mPath( path ),
+ mManager( manager )
+{
+}
+
+//-----------------------------------------------------------------------------
+KMFolderRootDir::~KMFolderRootDir()
+{
+ // WABA: We can't let KMFolderDir do this because by the time its
+ // desctructor gets called, KMFolderRootDir is already destructed
+ // Most notably the path.
+ clear();
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderRootDir::setPath(const QString& aPath)
+{
+ mPath = aPath;
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMFolderRootDir::path() const
+{
+ return mPath;
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMFolderRootDir::prettyURL() const
+{
+ if ( !mBaseURL.isEmpty() )
+ return i18n( mBaseURL.data() );
+ else
+ return QString::null;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFolderRootDir::setBaseURL( const QCString &baseURL )
+{
+ mBaseURL = baseURL;
+}
+
+
+//-----------------------------------------------------------------------------
+KMFolderMgr* KMFolderRootDir::manager() const
+{
+ return mManager;
+}
+
+
+//=============================================================================
+//=============================================================================
+KMFolderDir::KMFolderDir( KMFolder * owner, KMFolderDir* parent,
+ const QString& name, KMFolderDirType dirType )
+ : KMFolderNode( parent, name ), KMFolderNodeList(),
+ mOwner( owner ), mDirType( dirType )
+{
+ setAutoDelete( true );
+}
+
+
+//-----------------------------------------------------------------------------
+KMFolderDir::~KMFolderDir()
+{
+ clear();
+}
+
+
+//-----------------------------------------------------------------------------
+KMFolder* KMFolderDir::createFolder(const QString& aFolderName, bool aSysFldr, KMFolderType aFolderType)
+{
+ KMFolder* fld;
+
+ assert(!aFolderName.isEmpty());
+ // FIXME urgs, is this still needed
+ if (mDirType == KMImapDir)
+ fld = new KMFolder( this, aFolderName, KMFolderTypeImap );
+ else
+ fld = new KMFolder( this, aFolderName, aFolderType );
+
+ assert(fld != 0);
+ fld->setSystemFolder(aSysFldr);
+
+ KMFolderNode* fNode;
+ int index = 0;
+ for (fNode=first(); fNode; fNode=next()) {
+ if (fNode->name().lower() > fld->name().lower()) {
+ insert( index, fld );
+ break;
+ }
+ ++index;
+ }
+
+ if (!fNode)
+ append(fld);
+
+ fld->correctUnreadMsgsCount();
+ return fld;
+}
+
+
+//----------------------------------------------------------------------------
+QString KMFolderDir::path() const
+{
+ static QString p;
+
+ if (parent())
+ {
+ p = parent()->path();
+ p.append("/");
+ p.append(name());
+ }
+ else p = "";
+
+ return p;
+}
+
+
+//----------------------------------------------------------------------------
+QString KMFolderDir::label() const
+{
+ if ( mOwner )
+ return mOwner->label();
+ else
+ return QString::null;
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMFolderDir::prettyURL() const
+{
+ QString parentUrl;
+ if ( parent() )
+ parentUrl = parent()->prettyURL();
+ if ( !parentUrl.isEmpty() )
+ return parentUrl + '/' + label();
+ else
+ return label();
+}
+
+
+//-----------------------------------------------------------------------------
+bool KMFolderDir::reload(void)
+{
+ QDir dir;
+ KMFolder* folder;
+ QFileInfo* fileInfo;
+ QStringList diList;
+ QPtrList<KMFolder> folderList;
+
+ clear();
+
+ const QString fldPath = path();
+ dir.setFilter(QDir::Files | QDir::Dirs | QDir::Hidden);
+ dir.setNameFilter("*");
+
+ if (!dir.cd(fldPath, TRUE))
+ {
+ QString msg = i18n("<qt>Cannot enter folder <b>%1</b>.</qt>").arg(fldPath);
+ KMessageBox::information(0, msg);
+ return FALSE;
+ }
+
+ QFileInfoList* fiList=(QFileInfoList*)dir.entryInfoList();
+ if (!fiList)
+ {
+ QString msg = i18n("<qt>Folder <b>%1</b> is unreadable.</qt>").arg(fldPath);
+ KMessageBox::information(0, msg);
+ return FALSE;
+ }
+
+ for (fileInfo=fiList->first(); fileInfo; fileInfo=fiList->next())
+ {
+ const QString fname = fileInfo->fileName();
+ if( ( fname[0] == '.' ) && !fname.endsWith( ".directory" ) ) {
+ // ignore all hidden files except our subfolder containers
+ continue;
+ }
+ if( fname == ".directory" ) {
+ // ignore .directory files (not created by us)
+ continue;
+ }
+ // Collect subdirectories.
+ if ( fileInfo->isDir() &&
+ fname.startsWith( "." ) && fname.endsWith( ".directory" ) ) {
+ diList.append(fname);
+ continue;
+ }
+
+ if ( mDirType == KMImapDir
+ && path().startsWith( KMFolderImap::cacheLocation() ) )
+ {
+ // Is the below needed for dimap as well?
+ if ( KMFolderImap::encodeFileName(
+ KMFolderImap::decodeFileName( fname ) ) == fname )
+ {
+ folder = new KMFolder( this, KMFolderImap::decodeFileName( fname ),
+ KMFolderTypeImap );
+ append(folder);
+ folderList.append(folder);
+ }
+ }
+ else if ( mDirType == KMDImapDir
+ && path().startsWith( KMFolderCachedImap::cacheLocation() ) )
+ {
+ if (fileInfo->isDir()) // a directory
+ {
+ // For this to be a cached IMAP folder, it must be in the KMail dimap
+ // subdir and must be have a uidcache file or be a maildir folder
+ QString maildir(fname + "/new");
+ QString imapcachefile = QString::fromLatin1(".%1.uidcache").arg(fname);
+ if ( dir.exists( imapcachefile) || dir.exists( maildir ) )
+ {
+ folder = new KMFolder( this, fname, KMFolderTypeCachedImap );
+ append(folder);
+ folderList.append(folder);
+ }
+ }
+ }
+ else if ( mDirType == KMSearchDir)
+ {
+ folder = new KMFolder( this, fname, KMFolderTypeSearch );
+ append(folder);
+ folderList.append(folder);
+ }
+ else if ( mDirType == KMStandardDir )
+ {
+ // This is neither an imap, dimap nor a search folder. Can be either
+ // mbox or maildir.
+ if (fileInfo->isDir())
+ {
+ // Maildir folder
+ if( dir.exists( fname + "/new" ) )
+ {
+ folder = new KMFolder( this, fname, KMFolderTypeMaildir );
+ append(folder);
+ folderList.append(folder);
+ }
+ }
+ else
+ {
+ // all other files are folders (at the moment ;-)
+ folder = new KMFolder( this, fname, KMFolderTypeMbox );
+ append(folder);
+ folderList.append(folder);
+ }
+ }
+ }
+
+ for (folder=folderList.first(); folder; folder=folderList.next())
+ {
+ for(QStringList::Iterator it = diList.begin();
+ it != diList.end();
+ ++it)
+ if (*it == "." + folder->fileName() + ".directory")
+ {
+ KMFolderDir* folderDir = new KMFolderDir( folder, this, *it, mDirType);
+ folderDir->reload();
+ append(folderDir);
+ folder->setChild(folderDir);
+ break;
+ }
+ }
+ return TRUE;
+}
+
+
+//-----------------------------------------------------------------------------
+KMFolderNode* KMFolderDir::hasNamedFolder(const QString& aName)
+{
+ KMFolderNode* fNode;
+ for (fNode=first(); fNode; fNode=next()) {
+ if (fNode->name() == aName)
+ return fNode;
+ }
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+KMFolderMgr* KMFolderDir::manager() const
+{
+ return parent()->manager();
+}
+
+
+#include "kmfolderdir.moc"
+
diff --git a/kmail/kmfolderdir.h b/kmail/kmfolderdir.h
new file mode 100644
index 00000000..5fa30b86
--- /dev/null
+++ b/kmail/kmfolderdir.h
@@ -0,0 +1,92 @@
+#ifndef kmfolderdir_h
+#define kmfolderdir_h
+
+#include <qstring.h>
+#include "kmfoldernode.h"
+#include "kmfoldertype.h"
+
+class KMFolder;
+class KMFolderMgr;
+
+
+/** KMail list that manages the contents of one directory that may
+ * contain folders and/or other directories.
+ */
+class KMFolderDir: public KMFolderNode, public KMFolderNodeList
+{
+ Q_OBJECT
+
+public:
+ KMFolderDir( KMFolder * owner, KMFolderDir * parent = 0,
+ const QString& path = QString::null,
+ KMFolderDirType = KMStandardDir );
+ virtual ~KMFolderDir();
+
+ virtual bool isDir() const { return true; }
+
+ /** Read contents of directory. */
+ virtual bool reload();
+
+ /** Return full pathname of this directory. */
+ virtual QString path() const;
+
+ /** Returns the label of the folder for visualization. */
+ QString label() const;
+
+ /** URL of the node for visualization purposes. */
+ virtual QString prettyURL() const;
+
+ /** 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 QString& folderName,
+ bool sysFldr=false,
+ KMFolderType folderType=KMFolderTypeMbox);
+
+ /** Returns folder with given name or zero if it does not exist */
+ virtual KMFolderNode* hasNamedFolder(const QString& name);
+
+ /** Returns the folder manager that manages this folder */
+ virtual KMFolderMgr* manager() const;
+
+ /** Returns the folder whose children we are holding */
+ KMFolder* owner() const { return mOwner; }
+
+ virtual KMFolderDirType type() const { return mDirType; }
+
+protected:
+ KMFolder * mOwner;
+ KMFolderDirType mDirType;
+};
+
+
+//-----------------------------------------------------------------------------
+
+class KMFolderRootDir: public KMFolderDir
+{
+ Q_OBJECT
+
+public:
+ KMFolderRootDir(KMFolderMgr* manager,
+ const QString& path=QString::null,
+ KMFolderDirType dirType = KMStandardDir);
+ virtual ~KMFolderRootDir();
+ virtual QString path() const;
+
+ /** set the absolute path */
+ virtual void setPath(const QString&);
+
+ virtual QString prettyURL() const;
+
+ void setBaseURL( const QCString& baseURL );
+
+ virtual KMFolderMgr* manager() const;
+
+protected:
+ QString mPath;
+ KMFolderMgr *mManager;
+ QCString mBaseURL;
+};
+
+#endif /*kmfolderdir_h*/
+
diff --git a/kmail/kmfolderimap.cpp b/kmail/kmfolderimap.cpp
new file mode 100644
index 00000000..f30e11b6
--- /dev/null
+++ b/kmail/kmfolderimap.cpp
@@ -0,0 +1,2434 @@
+/**
+ * kmfolderimap.cpp
+ *
+ * Copyright (c) 2001 Kurt Granroth <granroth@kde.org>
+ * Copyright (c) 2000-2002 Michael Haeckel <haeckel@kde.org>
+ *
+ * This file is based on kmacctimap.coo by Michael Haeckel which was
+ * based on popaccount.cpp by Don Sanders
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "kmfolder.h"
+#include "kmfolderimap.h"
+#include "kmfoldermbox.h"
+#include "kmfoldertree.h"
+#include "kmmsgdict.h"
+#include "undostack.h"
+#include "kmfoldermgr.h"
+#include "kmfiltermgr.h"
+#include "kmmsgdict.h"
+#include "imapaccountbase.h"
+using KMail::ImapAccountBase;
+#include "imapjob.h"
+using KMail::ImapJob;
+#include "attachmentstrategy.h"
+using KMail::AttachmentStrategy;
+#include "progressmanager.h"
+using KPIM::ProgressItem;
+using KPIM::ProgressManager;
+#include "listjob.h"
+using KMail::ListJob;
+#include "kmsearchpattern.h"
+#include "searchjob.h"
+using KMail::SearchJob;
+#include "renamejob.h"
+using KMail::RenameJob;
+
+#include <kdebug.h>
+#include <kio/scheduler.h>
+#include <kconfig.h>
+
+#include <qbuffer.h>
+#include <qtextcodec.h>
+#include <qstylesheet.h>
+
+#include <assert.h>
+
+KMFolderImap::KMFolderImap(KMFolder* folder, const char* aName)
+ : KMFolderMbox(folder, aName),
+ mUploadAllFlags( false )
+{
+ mContentState = imapNoInformation;
+ mSubfolderState = imapNoInformation;
+ mAccount = 0;
+ mIsSelected = false;
+ mLastUid = 0;
+ mCheckFlags = true;
+ mCheckMail = true;
+ mCheckingValidity = false;
+ mUserRights = 0;
+ mAlreadyRemoved = false;
+ mHasChildren = ChildrenUnknown;
+ mMailCheckProgressItem = 0;
+ mListDirProgressItem = 0;
+ mAddMessageProgressItem = 0;
+ mReadOnly = false;
+
+ connect (this, SIGNAL( folderComplete( KMFolderImap*, bool ) ),
+ this, SLOT( slotCompleteMailCheckProgress()) );
+}
+
+KMFolderImap::~KMFolderImap()
+{
+ if (mAccount) {
+ mAccount->removeSlaveJobsForFolder( folder() );
+ /* Now that we've removed ourselves from the accounts jobs map, kill all
+ ongoing operations and reset mailcheck if we were deleted during an
+ ongoing mailcheck of our account. Not very gracefull, but safe, and the
+ only way I can see to reset the account state cleanly. */
+ if ( mAccount->checkingMail( folder() ) ) {
+ mAccount->killAllJobs();
+ }
+ }
+ writeConfig();
+ if (kmkernel->undoStack()) kmkernel->undoStack()->folderDestroyed( folder() );
+ mMetaDataMap.setAutoDelete( true );
+ mMetaDataMap.clear();
+ mUidMetaDataMap.setAutoDelete( true );
+ mUidMetaDataMap.clear();
+}
+
+
+//-----------------------------------------------------------------------------
+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() );
+ int idx = count();
+ while (--idx >= 0) {
+ if ( mMsgList[idx]->isMessage() ) {
+ KMMessage *msg = static_cast<KMMessage*>(mMsgList[idx]);
+ if (msg->transferInProgress())
+ msg->setTransferInProgress( false );
+ }
+ }
+ KMFolderMbox::reallyDoClose( owner );
+}
+
+KMFolder* KMFolderImap::trashFolder() const
+{
+ QString trashStr = account()->trash();
+ return kmkernel->imapFolderMgr()->findIdString( trashStr );
+}
+
+//-----------------------------------------------------------------------------
+KMMessage* KMFolderImap::getMsg(int idx)
+{
+ if(!(idx >= 0 && idx <= count()))
+ return 0;
+
+ KMMsgBase* mb = getMsgBase(idx);
+ if (!mb) return 0;
+ if (mb->isMessage())
+ {
+ return ((KMMessage*)mb);
+ } else {
+ KMMessage* msg = FolderStorage::getMsg( idx );
+ if ( msg ) // set it incomplete as the msg was not transferred from the server
+ msg->setComplete( false );
+ return msg;
+ }
+}
+
+//-----------------------------------------------------------------------------
+KMAcctImap* KMFolderImap::account() const
+{
+ if ( !mAccount ) {
+ KMFolderDir *parentFolderDir = dynamic_cast<KMFolderDir*>( folder()->parent() );
+ if ( !parentFolderDir ) {
+ kdWarning() << k_funcinfo << "No parent folder dir found for " << name() << endl;
+ return 0;
+ }
+ KMFolder *parentFolder = parentFolderDir->owner();
+ if ( !parentFolder ) {
+ kdWarning() << k_funcinfo << "No parent folder found for " << name() << endl;
+ return 0;
+ }
+ KMFolderImap *parentStorage = dynamic_cast<KMFolderImap*>( parentFolder->storage() );
+ if ( parentStorage )
+ mAccount = parentStorage->account();
+ }
+ return mAccount;
+}
+
+void KMFolderImap::setAccount(KMAcctImap *aAccount)
+{
+ mAccount = aAccount;
+ if( !folder() || !folder()->child() ) return;
+ KMFolderNode* node;
+ for (node = folder()->child()->first(); node;
+ node = folder()->child()->next())
+ {
+ if (!node->isDir())
+ static_cast<KMFolderImap*>(static_cast<KMFolder*>(node)->storage())->setAccount(aAccount);
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::readConfig()
+{
+ KConfig* config = KMKernel::config();
+ KConfigGroupSaver saver(config, "Folder-" + folder()->idString());
+ mCheckMail = config->readBoolEntry("checkmail", true);
+
+ mUidValidity = config->readEntry("UidValidity");
+ if ( mImapPath.isEmpty() ) {
+ setImapPath( config->readEntry("ImapPath") );
+ }
+ if (QString(name()).upper() == "INBOX" && mImapPath == "/INBOX/")
+ {
+ folder()->setSystemFolder( true );
+ folder()->setLabel( i18n("inbox") );
+ }
+ mNoContent = config->readBoolEntry("NoContent", false);
+ mReadOnly = config->readBoolEntry("ReadOnly", false);
+ mUploadAllFlags = config->readBoolEntry( "UploadAllFlags", true );
+ mPermanentFlags = config->readNumEntry( "PermanentFlags", 31 /* default flags */ );
+
+ KMFolderMbox::readConfig();
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::writeConfig()
+{
+ KConfig* config = KMKernel::config();
+ KConfigGroupSaver saver(config, "Folder-" + folder()->idString());
+ config->writeEntry("checkmail", mCheckMail);
+ config->writeEntry("UidValidity", mUidValidity);
+ config->writeEntry("ImapPath", mImapPath);
+ config->writeEntry("NoContent", mNoContent);
+ config->writeEntry("ReadOnly", mReadOnly);
+ config->writeEntry( "UploadAllFlags", mUploadAllFlags );
+ config->writeEntry( "PermanentFlags", mPermanentFlags );
+ KMFolderMbox::writeConfig();
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::remove()
+{
+ if ( mAlreadyRemoved || !account() )
+ {
+ // override
+ FolderStorage::remove();
+ return;
+ }
+ KURL url = account()->getUrl();
+ url.setPath(imapPath());
+ if ( account()->makeConnection() == ImapAccountBase::Error ||
+ imapPath().isEmpty() )
+ {
+ emit removed(folder(), false);
+ return;
+ }
+ KIO::SimpleJob *job = KIO::file_delete(url, false);
+ KIO::Scheduler::assignJobToSlave(account()->slave(), job);
+ ImapAccountBase::jobData jd(url.url());
+ jd.progressItem = ProgressManager::createProgressItem(
+ "ImapFolderRemove" + ProgressManager::getUniqueID(),
+ i18n("Removing folder"),
+ i18n( "URL: %1" ).arg( QStyleSheet::escape( folder()->prettyURL() ) ),
+ false,
+ account()->useSSL() || account()->useTLS() );
+ account()->insertJob(job, jd);
+ connect(job, SIGNAL(result(KIO::Job *)),
+ this, SLOT(slotRemoveFolderResult(KIO::Job *)));
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::slotRemoveFolderResult(KIO::Job *job)
+{
+ ImapAccountBase::JobIterator it = account()->findJob(job);
+ if ( it == account()->jobsEnd() ) return;
+ if (job->error())
+ {
+ account()->handleJobError( job, i18n("Error while removing a folder.") );
+ emit removed(folder(), false);
+ } else {
+ account()->removeJob(it);
+ FolderStorage::remove();
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::removeMsg(int idx, bool quiet)
+{
+ if (idx < 0)
+ return;
+
+ if (!quiet)
+ {
+ KMMessage *msg = getMsg(idx);
+ deleteMessage(msg);
+ }
+
+ mLastUid = 0;
+ KMFolderMbox::removeMsg(idx);
+}
+
+void KMFolderImap::removeMsg( const QPtrList<KMMessage>& msgList, bool quiet )
+{
+ if ( msgList.isEmpty() ) return;
+ if (!quiet)
+ deleteMessage(msgList);
+
+ mLastUid = 0;
+
+ /* Remove the messages from the local store as well.
+ We don't call KMFolderInherited::removeMsg(QPtrList<KMMessage>) but
+ iterate ourselves, as that would call KMFolderImap::removeMsg(int)
+ and not the one from the store we want to be used. */
+
+ QPtrListIterator<KMMessage> it( msgList );
+ KMMessage *msg;
+ while ( (msg = it.current()) != 0 ) {
+ ++it;
+ int idx = find(msg);
+ assert( idx != -1);
+ // ATTENTION port me to maildir
+ KMFolderMbox::removeMsg(idx, quiet);
+ }
+}
+
+//-----------------------------------------------------------------------------
+int KMFolderImap::rename( const QString& newName, KMFolderDir *aParent )
+{
+ if ( !aParent )
+ KMFolderMbox::rename( newName );
+ kmkernel->folderMgr()->contentsChanged();
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::addMsgQuiet(KMMessage* aMsg)
+{
+ KMFolder *aFolder = aMsg->parent();
+ Q_UINT32 serNum = 0;
+ aMsg->setTransferInProgress( false );
+ if (aFolder) {
+ serNum = aMsg->getMsgSerNum();
+ kmkernel->undoStack()->pushSingleAction( serNum, aFolder, folder() );
+ 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
+ // so it can be transfered to the new message
+ mMetaDataMap.insert( aMsg->msgIdMD5(),
+ new KMMsgMetaData(aMsg->status(), serNum) );
+ }
+
+ delete aMsg;
+ aMsg = 0;
+ getFolder();
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::addMsgQuiet(QPtrList<KMMessage> msgList)
+{
+ if ( mAddMessageProgressItem )
+ {
+ mAddMessageProgressItem->setComplete();
+ mAddMessageProgressItem = 0;
+ }
+ KMFolder *aFolder = msgList.first()->parent();
+ int undoId = -1;
+ bool uidplus = account()->hasCapability("uidplus");
+ for ( KMMessage* msg = msgList.first(); msg; msg = msgList.next() )
+ {
+ if ( undoId == -1 )
+ undoId = kmkernel->undoStack()->newUndoAction( aFolder, folder() );
+ if ( msg->getMsgSerNum() > 0 )
+ kmkernel->undoStack()->addMsgToAction( undoId, msg->getMsgSerNum() );
+ if ( !uidplus ) {
+ // Remember the status with the MD5 as key
+ // so it can be transfered to the new message
+ mMetaDataMap.insert( msg->msgIdMD5(),
+ new KMMsgMetaData(msg->status(), msg->getMsgSerNum()) );
+ }
+ msg->setTransferInProgress( false );
+ }
+ if ( aFolder ) {
+ aFolder->take( msgList );
+ } else {
+ kdDebug(5006) << k_funcinfo << "no parent" << endl;
+ }
+ msgList.setAutoDelete(true);
+ msgList.clear();
+ getFolder();
+}
+
+//-----------------------------------------------------------------------------
+int KMFolderImap::addMsg(KMMessage* aMsg, int* aIndex_ret)
+{
+ QPtrList<KMMessage> list;
+ list.append(aMsg);
+ QValueList<int> index;
+ int ret = addMsg(list, index);
+ aIndex_ret = &index.first();
+ return ret;
+}
+
+int KMFolderImap::addMsg(QPtrList<KMMessage>& msgList, QValueList<int>& aIndex_ret)
+{
+ KMMessage *aMsg = msgList.getFirst();
+ KMFolder *msgParent = aMsg->parent();
+
+ ImapJob *imapJob = 0;
+ if (msgParent)
+ {
+ if (msgParent->folderType() == KMFolderTypeImap)
+ {
+ if (static_cast<KMFolderImap*>(msgParent->storage())->account() == account())
+ {
+ // make sure the messages won't be deleted while we work with them
+ for ( KMMessage* msg = msgList.first(); msg; msg = msgList.next() )
+ msg->setTransferInProgress(true);
+
+ if (folder() == msgParent)
+ {
+ // transfer the whole message, e.g. a draft-message is canceled and re-added to the drafts-folder
+ for ( KMMessage* msg = msgList.first(); msg; msg = msgList.next() )
+ {
+ if (!msg->isComplete())
+ {
+ int idx = msgParent->find(msg);
+ assert(idx != -1);
+ msg = msgParent->getMsg(idx);
+ }
+ imapJob = new ImapJob(msg, ImapJob::tPutMessage, this);
+ connect(imapJob, SIGNAL(messageStored(KMMessage*)),
+ SLOT(addMsgQuiet(KMMessage*)));
+ connect(imapJob, SIGNAL(result(KMail::FolderJob*)),
+ SLOT(slotCopyMsgResult(KMail::FolderJob*)));
+ imapJob->start();
+ }
+
+ } else {
+
+ // get the messages and the uids
+ QValueList<ulong> uids;
+ getUids(msgList, uids);
+
+ // get the sets (do not sort the uids)
+ QStringList sets = makeSets(uids, false);
+
+ for ( QStringList::Iterator it = sets.begin(); it != sets.end(); ++it )
+ {
+ // we need the messages that belong to the current set to pass them to the ImapJob
+ QPtrList<KMMessage> temp_msgs = splitMessageList(*it, msgList);
+ if ( temp_msgs.isEmpty() ) kdDebug(5006) << "Wow! KMFolderImap::splitMessageList() returned an empty list!" << endl;
+ imapJob = new ImapJob(temp_msgs, *it, ImapJob::tMoveMessage, this);
+ connect(imapJob, SIGNAL(messageCopied(QPtrList<KMMessage>)),
+ SLOT(addMsgQuiet(QPtrList<KMMessage>)));
+ connect(imapJob, SIGNAL(result(KMail::FolderJob*)),
+ SLOT(slotCopyMsgResult(KMail::FolderJob*)));
+ imapJob->start();
+ }
+ }
+ return 0;
+ }
+ else
+ {
+ // different account, check if messages can be added
+ QPtrListIterator<KMMessage> it( msgList );
+ KMMessage *msg;
+ while ( (msg = it.current()) != 0 )
+ {
+ ++it;
+ int index;
+ if (!canAddMsgNow(msg, &index)) {
+ aIndex_ret << index;
+ msgList.remove(msg);
+ } else {
+ if (!msg->transferInProgress())
+ msg->setTransferInProgress(true);
+ }
+ }
+ }
+ } // if imap
+ }
+
+ if ( !msgList.isEmpty() )
+ {
+ // transfer from local folders or other accounts
+ QPtrListIterator<KMMessage> it( msgList );
+ KMMessage* msg;
+ while ( ( msg = it.current() ) != 0 )
+ {
+ ++it;
+ if ( !msg->transferInProgress() )
+ msg->setTransferInProgress( true );
+ }
+ imapJob = new ImapJob( msgList, QString::null, ImapJob::tPutMessage, this );
+ if ( !mAddMessageProgressItem && msgList.count() > 1 )
+ {
+ // use a parent progress if we have more than 1 message
+ // otherwise the normal progress is more accurate
+ mAddMessageProgressItem = ProgressManager::createProgressItem(
+ "Uploading"+ProgressManager::getUniqueID(),
+ i18n("Uploading message data"),
+ i18n("Destination folder: %1").arg( QStyleSheet::escape( folder()->prettyURL() ) ),
+ true,
+ account()->useSSL() || account()->useTLS() );
+ mAddMessageProgressItem->setTotalItems( msgList.count() );
+ connect ( mAddMessageProgressItem, SIGNAL( progressItemCanceled( KPIM::ProgressItem*)),
+ account(), SLOT( slotAbortRequested( KPIM::ProgressItem* ) ) );
+ imapJob->setParentProgressItem( mAddMessageProgressItem );
+ }
+ connect( imapJob, SIGNAL( messageCopied(QPtrList<KMMessage>) ),
+ SLOT( addMsgQuiet(QPtrList<KMMessage>) ) );
+ connect( imapJob, SIGNAL(result(KMail::FolderJob*)),
+ SLOT(slotCopyMsgResult(KMail::FolderJob*)) );
+ imapJob->start();
+ }
+
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::slotCopyMsgResult( KMail::FolderJob* job )
+{
+ kdDebug(5006) << k_funcinfo << job->error() << endl;
+ if ( job->error() ) // getFolder() will not be called in this case
+ emit folderComplete( this, false );
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::copyMsg(QPtrList<KMMessage>& msgList)
+{
+ if ( !account()->hasCapability("uidplus") ) {
+ for ( KMMessage *msg = msgList.first(); msg; msg = msgList.next() ) {
+ // Remember the status with the MD5 as key
+ // so it can be transfered to the new message
+ mMetaDataMap.insert( msg->msgIdMD5(), new KMMsgMetaData(msg->status()) );
+ }
+ }
+
+ QValueList<ulong> uids;
+ getUids(msgList, uids);
+ QStringList sets = makeSets(uids, false);
+ for ( QStringList::Iterator it = sets.begin(); it != sets.end(); ++it )
+ {
+ // we need the messages that belong to the current set to pass them to the ImapJob
+ QPtrList<KMMessage> temp_msgs = splitMessageList(*it, msgList);
+
+ ImapJob *job = new ImapJob(temp_msgs, *it, ImapJob::tCopyMessage, this);
+ connect(job, SIGNAL(result(KMail::FolderJob*)),
+ SLOT(slotCopyMsgResult(KMail::FolderJob*)));
+ job->start();
+ }
+}
+
+//-----------------------------------------------------------------------------
+QPtrList<KMMessage> KMFolderImap::splitMessageList(const QString& set,
+ QPtrList<KMMessage>& msgList)
+{
+ int lastcomma = set.findRev(",");
+ int lastdub = set.findRev(":");
+ int last = 0;
+ if (lastdub > lastcomma) last = lastdub;
+ else last = lastcomma;
+ last++;
+ if (last < 0) last = set.length();
+ // the last uid of the current set
+ const QString last_uid = set.right(set.length() - last);
+ QPtrList<KMMessage> temp_msgs;
+ QString uid;
+ if (!last_uid.isEmpty())
+ {
+ QPtrListIterator<KMMessage> it( msgList );
+ KMMessage* msg = 0;
+ while ( (msg = it.current()) != 0 )
+ {
+ // append the msg to the new list and delete it from the old
+ temp_msgs.append(msg);
+ uid.setNum( msg->UID() );
+ // remove modifies the current
+ msgList.remove(msg);
+ if (uid == last_uid) break;
+ }
+ }
+ else
+ {
+ // probably only one element
+ temp_msgs = msgList;
+ }
+
+ return temp_msgs;
+}
+
+//-----------------------------------------------------------------------------
+KMMessage* KMFolderImap::take(int idx)
+{
+ KMMsgBase* mb(mMsgList[idx]);
+ if (!mb) return 0;
+ if (!mb->isMessage()) readMsg(idx);
+
+ KMMessage *msg = static_cast<KMMessage*>(mb);
+ deleteMessage(msg);
+
+ mLastUid = 0;
+ return KMFolderMbox::take(idx);
+}
+
+void KMFolderImap::take(QPtrList<KMMessage> msgList)
+{
+ deleteMessage(msgList);
+
+ mLastUid = 0;
+ KMFolderMbox::take(msgList);
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::slotListNamespaces()
+{
+ disconnect( account(), SIGNAL( connectionResult(int, const QString&) ),
+ this, SLOT( slotListNamespaces() ) );
+ if ( account()->makeConnection() == ImapAccountBase::Error )
+ {
+ kdWarning(5006) << "slotListNamespaces - got no connection" << endl;
+ return;
+ } else if ( account()->makeConnection() == ImapAccountBase::Connecting )
+ {
+ // wait for the connectionResult
+ kdDebug(5006) << "slotListNamespaces - waiting for connection" << endl;
+ connect( account(), SIGNAL( connectionResult(int, const QString&) ),
+ this, SLOT( slotListNamespaces() ) );
+ return;
+ }
+ kdDebug(5006) << "slotListNamespaces" << endl;
+ // reset subfolder states recursively
+ setSubfolderState( imapNoInformation );
+ mSubfolderState = imapListingInProgress;
+ account()->setHasInbox( false );
+
+ ImapAccountBase::ListType type = ImapAccountBase::List;
+ if ( account()->onlySubscribedFolders() )
+ type = ImapAccountBase::ListSubscribed;
+
+ ImapAccountBase::nsMap map = account()->namespaces();
+ QStringList personal = map[ImapAccountBase::PersonalNS];
+ // start personal namespace listing and send it directly to slotListResult
+ for ( QStringList::Iterator it = personal.begin(); it != personal.end(); ++it )
+ {
+ KMail::ListJob* job = new KMail::ListJob( account(), type, this,
+ account()->addPathToNamespace( *it ) );
+ job->setNamespace( *it );
+ job->setHonorLocalSubscription( true );
+ connect( job, SIGNAL(receivedFolders(const QStringList&, const QStringList&,
+ const QStringList&, const QStringList&, const ImapAccountBase::jobData&)),
+ this, SLOT(slotListResult(const QStringList&, const QStringList&,
+ const QStringList&, const QStringList&, const ImapAccountBase::jobData&)));
+ job->start();
+ }
+
+ // and now we list all other namespaces and check them ourself
+ QStringList ns = map[ImapAccountBase::OtherUsersNS];
+ ns += map[ImapAccountBase::SharedNS];
+ for ( QStringList::Iterator it = ns.begin(); it != ns.end(); ++it )
+ {
+ KMail::ListJob* job = new KMail::ListJob( account(), type, this, account()->addPathToNamespace( *it ) );
+ job->setHonorLocalSubscription( true );
+ connect( job, SIGNAL(receivedFolders(const QStringList&, const QStringList&,
+ const QStringList&, const QStringList&, const ImapAccountBase::jobData&)),
+ this, SLOT(slotCheckNamespace(const QStringList&, const QStringList&,
+ const QStringList&, const QStringList&, const ImapAccountBase::jobData&)));
+ job->start();
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::slotCheckNamespace( const QStringList& subfolderNames,
+ const QStringList& subfolderPaths,
+ const QStringList& subfolderMimeTypes,
+ const QStringList& subfolderAttributes,
+ const ImapAccountBase::jobData& jobData )
+{
+ kdDebug(5006) << "slotCheckNamespace - " << subfolderNames.join(",") << endl;
+
+ // get a correct foldername:
+ // strip / and make sure it does not contain the delimiter
+ QString name = jobData.path.mid( 1, jobData.path.length()-2 );
+ name.remove( account()->delimiterForNamespace( name ) );
+ if ( name.isEmpty() ) {
+ // happens when an empty namespace is defined
+ slotListResult( subfolderNames, subfolderPaths,
+ subfolderMimeTypes, subfolderAttributes, jobData );
+ return;
+ }
+
+ folder()->createChildFolder();
+ KMFolderNode *node = 0;
+ for ( node = folder()->child()->first(); node;
+ node = folder()->child()->next())
+ {
+ if ( !node->isDir() && node->name() == name )
+ break;
+ }
+ if ( subfolderNames.isEmpty() )
+ {
+ if ( node )
+ {
+ kdDebug(5006) << "delete namespace folder " << name << endl;
+ KMFolder *fld = static_cast<KMFolder*>(node);
+ KMFolderImap* nsFolder = static_cast<KMFolderImap*>(fld->storage());
+ nsFolder->setAlreadyRemoved( true );
+ kmkernel->imapFolderMgr()->remove( fld );
+ }
+ } else {
+ if ( node )
+ {
+ // folder exists so pass on the attributes
+ kdDebug(5006) << "found namespace folder " << name << endl;
+ if ( !account()->listOnlyOpenFolders() )
+ {
+ KMFolderImap* nsFolder =
+ static_cast<KMFolderImap*>(static_cast<KMFolder*>(node)->storage());
+ nsFolder->slotListResult( subfolderNames, subfolderPaths,
+ subfolderMimeTypes, subfolderAttributes, jobData );
+ }
+ } else
+ {
+ // create folder
+ kdDebug(5006) << "create namespace folder " << name << endl;
+ KMFolder *fld = folder()->child()->createFolder( name );
+ if ( fld ) {
+ KMFolderImap* f = static_cast<KMFolderImap*> ( fld->storage() );
+ f->initializeFrom( this, account()->addPathToNamespace( name ),
+ "inode/directory" );
+ f->close( "kmfolderimap_create" );
+ if ( !account()->listOnlyOpenFolders() )
+ {
+ f->slotListResult( subfolderNames, subfolderPaths,
+ subfolderMimeTypes, subfolderAttributes, jobData );
+ }
+ }
+ kmkernel->imapFolderMgr()->contentsChanged();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+bool KMFolderImap::listDirectory()
+{
+ if ( !account() ||
+ ( account() && account()->makeConnection() == ImapAccountBase::Error ) )
+ {
+ kdDebug(5006) << "KMFolderImap::listDirectory - got no connection" << endl;
+ return false;
+ }
+
+ if ( this == account()->rootFolder() )
+ {
+ // a new listing started
+ slotListNamespaces();
+ return true;
+ }
+ mSubfolderState = imapListingInProgress;
+
+ // get the folders
+ ImapAccountBase::ListType type = ImapAccountBase::List;
+ if ( account()->onlySubscribedFolders() )
+ type = ImapAccountBase::ListSubscribed;
+ KMail::ListJob* job = new KMail::ListJob( account(), type, this );
+ job->setParentProgressItem( account()->listDirProgressItem() );
+ job->setHonorLocalSubscription( true );
+ connect( job, SIGNAL(receivedFolders(const QStringList&, const QStringList&,
+ const QStringList&, const QStringList&, const ImapAccountBase::jobData&)),
+ this, SLOT(slotListResult(const QStringList&, const QStringList&,
+ const QStringList&, const QStringList&, const ImapAccountBase::jobData&)));
+ job->start();
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::slotListResult( const QStringList& subfolderNames,
+ const QStringList& subfolderPaths,
+ const QStringList& subfolderMimeTypes,
+ const QStringList& subfolderAttributes,
+ const ImapAccountBase::jobData& jobData )
+{
+ mSubfolderState = imapFinished;
+ //kdDebug(5006) << label() << ": folderNames=" << subfolderNames << " folderPaths="
+ //<< subfolderPaths << " mimeTypes=" << subfolderMimeTypes << endl;
+
+ // don't react on changes
+ kmkernel->imapFolderMgr()->quiet(true);
+
+ bool root = ( this == account()->rootFolder() );
+ folder()->createChildFolder();
+ if ( root && !account()->hasInbox() )
+ {
+ // create the INBOX
+ initInbox();
+ }
+
+ // see if we have a better parent
+ // if you have a prefix that contains a folder (e.g "INBOX.") the folders
+ // need to be created underneath it
+ if ( root && !subfolderNames.empty() )
+ {
+ KMFolderImap* parent = findParent( subfolderPaths.first(), subfolderNames.first() );
+ if ( parent )
+ {
+ kdDebug(5006) << "KMFolderImap::slotListResult - pass listing to "
+ << parent->label() << endl;
+ parent->slotListResult( subfolderNames, subfolderPaths,
+ subfolderMimeTypes, subfolderAttributes, jobData );
+ // cleanup
+ QStringList list;
+ checkFolders( list, jobData.curNamespace );
+ // finish
+ emit directoryListingFinished( this );
+ kmkernel->imapFolderMgr()->quiet( false );
+ return;
+ }
+ }
+
+ bool emptyList = ( root && subfolderNames.empty() );
+ if ( !emptyList )
+ {
+ checkFolders( subfolderNames, jobData.curNamespace );
+ }
+
+ KMFolderImap *f = 0;
+ KMFolderNode *node = 0;
+ for ( uint i = 0; i < subfolderNames.count(); i++ )
+ {
+ bool settingsChanged = false;
+ // create folders if necessary
+ for ( node = folder()->child()->first(); node;
+ node = folder()->child()->next() ) {
+ if ( !node->isDir() && node->name() == subfolderNames[i] )
+ break;
+ }
+ if ( node ) {
+ f = static_cast<KMFolderImap*>(static_cast<KMFolder*>(node)->storage());
+ }
+ else if ( subfolderPaths[i].upper() != "/INBOX/" )
+ {
+ kdDebug(5006) << "create folder " << subfolderNames[i] << endl;
+ KMFolder *fld = folder()->child()->createFolder(subfolderNames[i]);
+ if ( fld ) {
+ f = static_cast<KMFolderImap*> ( fld->storage() );
+ f->close( "kmfolderimap_create" );
+ settingsChanged = true;
+ } else {
+ kdWarning(5006) << "can't create folder " << subfolderNames[i] << endl;
+ }
+ }
+ if ( f )
+ {
+ // sanity check
+ if ( f->imapPath().isEmpty() ) {
+ settingsChanged = true;
+ }
+ // update progress
+ account()->listDirProgressItem()->incCompletedItems();
+ account()->listDirProgressItem()->updateProgress();
+ account()->listDirProgressItem()->setStatus( folder()->prettyURL() + i18n(" completed") );
+
+ f->initializeFrom( this, subfolderPaths[i], subfolderMimeTypes[i] );
+ f->setChildrenState( subfolderAttributes[i] );
+ if ( account()->listOnlyOpenFolders() &&
+ f->hasChildren() != FolderStorage::ChildrenUnknown )
+ {
+ settingsChanged = true;
+ }
+
+ if ( settingsChanged )
+ {
+ // tell the tree our information changed
+ kmkernel->imapFolderMgr()->contentsChanged();
+ }
+ if ( ( subfolderMimeTypes[i] == "message/directory" ||
+ subfolderMimeTypes[i] == "inode/directory" ) &&
+ !account()->listOnlyOpenFolders() )
+ {
+ f->listDirectory();
+ }
+ } else {
+ kdWarning(5006) << "can't find folder " << subfolderNames[i] << endl;
+ }
+ } // for subfolders
+
+ // now others should react on the changes
+ kmkernel->imapFolderMgr()->quiet( false );
+ emit directoryListingFinished( this );
+ account()->listDirProgressItem()->setComplete();
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::initInbox()
+{
+ KMFolderImap *f = 0;
+ KMFolderNode *node = 0;
+
+ for (node = folder()->child()->first(); node;
+ node = folder()->child()->next()) {
+ if (!node->isDir() && node->name() == "INBOX") break;
+ }
+ if (node) {
+ f = static_cast<KMFolderImap*>(static_cast<KMFolder*>(node)->storage());
+ } else {
+ f = static_cast<KMFolderImap*>
+ (folder()->child()->createFolder("INBOX", true)->storage());
+ if ( f )
+ {
+ f->folder()->setLabel( i18n("inbox") );
+ f->close( "kmfolderimap" );
+ }
+ kmkernel->imapFolderMgr()->contentsChanged();
+ }
+ if ( f ) {
+ f->initializeFrom( this, "/INBOX/", "message/directory" );
+ f->setChildrenState( QString::null );
+ }
+ // so we have an INBOX
+ account()->setHasInbox( true );
+}
+
+//-----------------------------------------------------------------------------
+KMFolderImap* KMFolderImap::findParent( const QString& path, const QString& name )
+{
+ QString parent = path.left( path.length() - name.length() - 2 );
+ if ( parent.length() > 1 )
+ {
+ // extract name of the parent
+ parent = parent.right( parent.length() - 1 );
+ if ( parent != label() )
+ {
+ KMFolderNode *node = folder()->child()->first();
+ // look for a better parent
+ while ( node )
+ {
+ if ( node->name() == parent )
+ {
+ KMFolder* fld = static_cast<KMFolder*>(node);
+ KMFolderImap* imapFld = static_cast<KMFolderImap*>( fld->storage() );
+ return imapFld;
+ }
+ node = folder()->child()->next();
+ }
+ }
+ }
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::checkFolders( const QStringList& subfolderNames,
+ const QString& myNamespace )
+{
+ QPtrList<KMFolder> toRemove;
+ KMFolderNode *node = folder()->child()->first();
+ while ( node )
+ {
+ if ( !node->isDir() && subfolderNames.findIndex(node->name()) == -1 )
+ {
+ KMFolder* fld = static_cast<KMFolder*>(node);
+ KMFolderImap* imapFld = static_cast<KMFolderImap*>( fld->storage() );
+ // as more than one namespace can be listed in the root folder we need to make sure
+ // that the folder is within the current namespace
+ bool isInNamespace = ( myNamespace.isEmpty() ||
+ myNamespace == account()->namespaceForFolder( imapFld ) );
+ kdDebug(5006) << node->name() << " in namespace " << myNamespace << ":" <<
+ isInNamespace << endl;
+ // ignore some cases
+ QString name = node->name();
+ bool ignore = ( ( this == account()->rootFolder() ) &&
+ ( imapFld->imapPath() == "/INBOX/" ||
+ account()->isNamespaceFolder( name ) ||
+ !isInNamespace ) );
+ // additional sanity check for broken folders
+ if ( imapFld->imapPath().isEmpty() ) {
+ ignore = false;
+ }
+ if ( !ignore )
+ {
+ // remove the folder without server round trip
+ kdDebug(5006) << "checkFolders - " << node->name() << " disappeared" << endl;
+ imapFld->setAlreadyRemoved( true );
+ toRemove.append( fld );
+ } else {
+ kdDebug(5006) << "checkFolders - " << node->name() << " ignored" << endl;
+ }
+ }
+ node = folder()->child()->next();
+ }
+ // remove folders
+ for ( KMFolder* doomed=toRemove.first(); doomed; doomed = toRemove.next() )
+ kmkernel->imapFolderMgr()->remove( doomed );
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::initializeFrom( KMFolderImap* parent, QString folderPath,
+ QString mimeType )
+{
+ setAccount( parent->account() );
+ setImapPath( folderPath );
+ setNoContent( mimeType == "inode/directory" );
+ setNoChildren( mimeType == "message/digest" );
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::setChildrenState( QString attributes )
+{
+ // update children state
+ if ( attributes.find( "haschildren", 0, false ) != -1 )
+ {
+ setHasChildren( FolderStorage::HasChildren );
+ } else if ( attributes.find( "hasnochildren", 0, false ) != -1 ||
+ attributes.find( "noinferiors", 0, false ) != -1 )
+ {
+ setHasChildren( FolderStorage::HasNoChildren );
+ } else
+ {
+ if ( account()->listOnlyOpenFolders() ) {
+ setHasChildren( FolderStorage::HasChildren );
+ } else {
+ setHasChildren( FolderStorage::ChildrenUnknown );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::checkValidity()
+{
+ if (!account()) {
+ emit folderComplete(this, false);
+ close("checkvalidity");
+ return;
+ }
+ KURL url = account()->getUrl();
+ url.setPath(imapPath() + ";UID=0:0");
+ kdDebug(5006) << "KMFolderImap::checkValidity of: " << imapPath() << endl;
+
+ // Start with a clean slate
+ disconnect( account(), SIGNAL( connectionResult(int, const QString&) ),
+ this, SLOT( checkValidity() ) );
+
+ KMAcctImap::ConnectionState connectionState = account()->makeConnection();
+ if ( connectionState == ImapAccountBase::Error ) {
+ kdDebug(5006) << "KMFolderImap::checkValidity - got no connection" << endl;
+ emit folderComplete(this, false);
+ mContentState = imapNoInformation;
+ close("checkvalidity");
+ return;
+ } else if ( connectionState == ImapAccountBase::Connecting ) {
+ // We'll wait for the connectionResult signal from the account. If it
+ // errors, the above will catch it.
+ kdDebug(5006) << "CheckValidity - waiting for connection" << endl;
+ connect( account(), SIGNAL( connectionResult(int, const QString&) ),
+ this, SLOT( checkValidity() ) );
+ return;
+ }
+ // Only check once at a time.
+ if (mCheckingValidity) {
+ kdDebug(5006) << "KMFolderImap::checkValidity - already checking" << endl;
+ close("checkvalidity");
+ return;
+ }
+ // otherwise we already are inside a mailcheck
+ if ( !mMailCheckProgressItem ) {
+ ProgressItem* parent = ( account()->checkingSingleFolder() ? 0 :
+ account()->mailCheckProgressItem() );
+ mMailCheckProgressItem = ProgressManager::createProgressItem(
+ parent,
+ "MailCheck" + folder()->prettyURL(),
+ QStyleSheet::escape( folder()->prettyURL() ),
+ i18n("checking"),
+ false,
+ account()->useSSL() || account()->useTLS() );
+ } else {
+ mMailCheckProgressItem->setProgress(0);
+ }
+ if ( account()->mailCheckProgressItem() ) {
+ account()->mailCheckProgressItem()->setStatus( folder()->prettyURL() );
+ }
+ ImapAccountBase::jobData jd( url.url() );
+ KIO::SimpleJob *job = KIO::get(url, false, false);
+ KIO::Scheduler::assignJobToSlave(account()->slave(), job);
+ account()->insertJob(job, jd);
+ connect(job, SIGNAL(result(KIO::Job *)),
+ SLOT(slotCheckValidityResult(KIO::Job *)));
+ connect(job, SIGNAL(data(KIO::Job *, const QByteArray &)),
+ SLOT(slotSimpleData(KIO::Job *, const QByteArray &)));
+ // Only check once at a time.
+ mCheckingValidity = true;
+}
+
+
+//-----------------------------------------------------------------------------
+ulong KMFolderImap::lastUid()
+{
+ if ( mLastUid > 0 )
+ return mLastUid;
+ open("lastuid");
+ if (count() > 0)
+ {
+ KMMsgBase * base = getMsgBase(count()-1);
+ mLastUid = base->UID();
+ }
+ close("lastuid");
+ return mLastUid;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::slotCheckValidityResult(KIO::Job * job)
+{
+ kdDebug(5006) << "KMFolderImap::slotCheckValidityResult of: " << fileName() << endl;
+ mCheckingValidity = false;
+ ImapAccountBase::JobIterator it = account()->findJob(job);
+ if ( it == account()->jobsEnd() ) return;
+ if (job->error()) {
+ if ( job->error() != KIO::ERR_ACCESS_DENIED ) {
+ // we suppress access denied messages because they are normally a result of
+ // explicitely set ACLs. Do not save this information (e.g. setNoContent) so that
+ // we notice when this changes
+ account()->handleJobError( job, i18n("Error while querying the server status.") );
+ }
+ mContentState = imapNoInformation;
+ emit folderComplete(this, false);
+ close("checkvalidity");
+ } else {
+ QCString cstr((*it).data.data(), (*it).data.size() + 1);
+ int a = cstr.find("X-uidValidity: ");
+ int b = cstr.find("\r\n", a);
+ QString uidv;
+ if ( (b - a - 15) >= 0 )
+ uidv = cstr.mid(a + 15, b - a - 15);
+ a = cstr.find("X-Access: ");
+ b = cstr.find("\r\n", a);
+ QString access;
+ if ( (b - a - 10) >= 0 )
+ access = cstr.mid(a + 10, b - a - 10);
+ mReadOnly = access == "Read only";
+ a = cstr.find("X-Count: ");
+ b = cstr.find("\r\n", a);
+ int exists = -1;
+ bool ok = false;
+ if ( (b - a - 9) >= 0 )
+ exists = cstr.mid(a + 9, b - a - 9).toInt(&ok);
+ if ( !ok ) exists = -1;
+ a = cstr.find( "X-PermanentFlags: " );
+ b = cstr.find( "\r\n", a );
+ if ( a >= 0 && (b - a - 18) >= 0 )
+ mPermanentFlags = cstr.mid( a + 18, b - a - 18 ).toInt(&ok);
+ if ( !ok ) mPermanentFlags = 0;
+ QString startUid;
+ if (uidValidity() != uidv)
+ {
+ // uidValidity changed
+ kdDebug(5006) << k_funcinfo << "uidValidty changed from "
+ << uidValidity() << " to " << uidv << endl;
+ if ( !uidValidity().isEmpty() )
+ {
+ account()->ignoreJobsForFolder( folder() );
+ mUidMetaDataMap.clear();
+ }
+ mLastUid = 0;
+ setUidValidity(uidv);
+ writeConfig();
+ } else {
+ if (!mCheckFlags)
+ startUid = QString::number(lastUid() + 1);
+ }
+ account()->removeJob(it);
+ if ( mMailCheckProgressItem )
+ {
+ if ( startUid.isEmpty() ) {
+ // flags for all messages are loaded
+ mMailCheckProgressItem->setTotalItems( exists );
+ } else {
+ // only an approximation but doesn't hurt
+ int remain = exists - count();
+ if ( remain < 0 ) remain = 1;
+ mMailCheckProgressItem->setTotalItems( remain );
+ }
+ mMailCheckProgressItem->setCompletedItems( 0 );
+ }
+ reallyGetFolder(startUid);
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::getAndCheckFolder(bool force)
+{
+ if (mNoContent)
+ return getFolder(force);
+
+ if ( account() )
+ account()->processNewMailSingleFolder( folder() );
+ if (force) {
+ // force an update
+ mCheckFlags = true;
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::getFolder(bool force)
+{
+ mGuessedUnreadMsgs = -1;
+ if (mNoContent)
+ {
+ mContentState = imapFinished;
+ emit folderComplete(this, true);
+ return;
+ }
+ open("getfolder");
+ mContentState = imapListingInProgress;
+ if (force) {
+ // force an update
+ mCheckFlags = true;
+ }
+ checkValidity();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::reallyGetFolder(const QString &startUid)
+{
+ KURL url = account()->getUrl();
+ if ( account()->makeConnection() != ImapAccountBase::Connected )
+ {
+ mContentState = imapNoInformation;
+ emit folderComplete(this, false);
+ close("listfolder");
+ return;
+ }
+ quiet(true);
+ if (startUid.isEmpty())
+ {
+ if ( mMailCheckProgressItem )
+ mMailCheckProgressItem->setStatus( i18n("Retrieving message status") );
+ url.setPath(imapPath() + ";SECTION=UID FLAGS");
+ KIO::SimpleJob *job = KIO::listDir(url, false);
+ KIO::Scheduler::assignJobToSlave(account()->slave(), job);
+ ImapAccountBase::jobData jd( url.url(), folder() );
+ jd.cancellable = true;
+ account()->insertJob(job, jd);
+ connect(job, SIGNAL(result(KIO::Job *)),
+ this, SLOT(slotListFolderResult(KIO::Job *)));
+ connect(job, SIGNAL(entries(KIO::Job *, const KIO::UDSEntryList &)),
+ this, SLOT(slotListFolderEntries(KIO::Job *,
+ const KIO::UDSEntryList &)));
+ } else {
+ mContentState = imapDownloadInProgress;
+ if ( mMailCheckProgressItem )
+ mMailCheckProgressItem->setStatus( i18n("Retrieving messages") );
+ url.setPath(imapPath() + ";UID=" + startUid
+ + ":*;SECTION=ENVELOPE");
+ KIO::SimpleJob *newJob = KIO::get(url, false, false);
+ KIO::Scheduler::assignJobToSlave(account()->slave(), newJob);
+ ImapAccountBase::jobData jd( url.url(), folder() );
+ jd.cancellable = true;
+ account()->insertJob(newJob, jd);
+ connect(newJob, SIGNAL(result(KIO::Job *)),
+ this, SLOT(slotGetLastMessagesResult(KIO::Job *)));
+ connect(newJob, SIGNAL(data(KIO::Job *, const QByteArray &)),
+ this, SLOT(slotGetMessagesData(KIO::Job *, const QByteArray &)));
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::slotListFolderResult(KIO::Job * job)
+{
+ ImapAccountBase::JobIterator it = account()->findJob(job);
+ if ( it == account()->jobsEnd() ) return;
+ QString uids;
+ if (job->error())
+ {
+ account()->handleJobError( job,
+ i18n("Error while listing the contents of the folder %1.").arg( label() ) );
+ account()->removeJob(it);
+ finishMailCheck( "listfolder", imapNoInformation );
+ return;
+ }
+ mCheckFlags = false;
+ QStringList::Iterator uid;
+ /*
+ The code below does the following:
+ - for each mail in the local store and each entry we got from the server,
+ compare the local uid with the one from the server and update the status
+ flags of the mails
+ - for all mails that are not already locally present, start a job which
+ gets the envelope of each
+ - remove all locally present mails if the server does not list them anymore
+ */
+ if ( count() ) {
+ int idx = 0, c, serverFlags;
+ ulong mailUid, serverUid;
+ uid = (*it).items.begin();
+ while ( idx < count() && uid != (*it).items.end() ) {
+ KMMsgBase *msgBase = getMsgBase( idx );
+ mailUid = msgBase->UID();
+ // parse the uid from the server and the flags out of the list from
+ // the server. Format: 1234, 1
+ c = (*uid).find(",");
+ serverUid = (*uid).left( c ).toLong();
+ serverFlags = (*uid).mid( c+1 ).toInt();
+ if ( mailUid < serverUid ) {
+ removeMsg( idx, true );
+ } else if ( mailUid == serverUid ) {
+ // if this is a read only folder, ignore status updates from the server
+ // since we can't write our status back our local version is what has to
+ // be considered correct.
+ if ( !mReadOnly || !GlobalSettings::allowLocalFlags() ) {
+ int supportedFlags = mUploadAllFlags ? 31 : mPermanentFlags;
+ if ( mReadOnly )
+ supportedFlags = INT_MAX;
+ flagsToStatus( msgBase, serverFlags, false, supportedFlags );
+ } else
+ seenFlagToStatus( msgBase, serverFlags, false );
+ idx++;
+ uid = (*it).items.remove(uid);
+ if ( msgBase->getMsgSerNum() > 0 ) {
+ saveMsgMetaData( static_cast<KMMessage*>(msgBase) );
+ }
+ }
+ else break; // happens only, if deleted mails reappear on the server
+ }
+ // remove all remaining entries in the local cache, they are no longer
+ // present on the server
+ while (idx < count()) removeMsg(idx, true);
+ }
+ // strip the flags from the list of uids, so it can be reused
+ for (uid = (*it).items.begin(); uid != (*it).items.end(); ++uid)
+ (*uid).truncate((*uid).find(","));
+ ImapAccountBase::jobData jd( QString::null, (*it).parent );
+ jd.total = (*it).items.count();
+ if (jd.total == 0)
+ {
+ finishMailCheck( "listfolder", imapFinished );
+ account()->removeJob(it);
+ return;
+ }
+ if ( mMailCheckProgressItem )
+ {
+ // next step for the progressitem
+ mMailCheckProgressItem->setCompletedItems( 0 );
+ mMailCheckProgressItem->setTotalItems( jd.total );
+ mMailCheckProgressItem->setProgress( 0 );
+ mMailCheckProgressItem->setStatus( i18n("Retrieving messages") );
+ }
+
+ QStringList sets;
+ uid = (*it).items.begin();
+ if (jd.total == 1) sets.append(*uid + ":" + *uid);
+ else sets = makeSets( (*it).items );
+ account()->removeJob(it); // don't use *it below
+
+ // Now kick off the getting of envelopes for the new mails in the folder
+ for (QStringList::Iterator i = sets.begin(); i != sets.end(); ++i)
+ {
+ mContentState = imapDownloadInProgress;
+ KURL url = account()->getUrl();
+ url.setPath(imapPath() + ";UID=" + *i + ";SECTION=ENVELOPE");
+ KIO::SimpleJob *newJob = KIO::get(url, false, false);
+ jd.url = url.url();
+ KIO::Scheduler::assignJobToSlave(account()->slave(), newJob);
+ account()->insertJob(newJob, jd);
+ connect(newJob, SIGNAL(result(KIO::Job *)),
+ this, (i == sets.at(sets.count() - 1))
+ ? SLOT(slotGetLastMessagesResult(KIO::Job *))
+ : SLOT(slotGetMessagesResult(KIO::Job *)));
+ connect(newJob, SIGNAL(data(KIO::Job *, const QByteArray &)),
+ this, SLOT(slotGetMessagesData(KIO::Job *, const QByteArray &)));
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::slotListFolderEntries(KIO::Job * job,
+ const KIO::UDSEntryList & uds)
+{
+ ImapAccountBase::JobIterator it = account()->findJob(job);
+ if ( it == account()->jobsEnd() ) return;
+ QString mimeType, name;
+ long int flags = 0;
+ for (KIO::UDSEntryList::ConstIterator udsIt = uds.begin();
+ udsIt != uds.end(); udsIt++)
+ {
+ for (KIO::UDSEntry::ConstIterator eIt = (*udsIt).begin();
+ eIt != (*udsIt).end(); eIt++)
+ {
+ if ((*eIt).m_uds == KIO::UDS_NAME)
+ name = (*eIt).m_str;
+ else if ((*eIt).m_uds == KIO::UDS_MIME_TYPE)
+ mimeType = (*eIt).m_str;
+ else if ((*eIt).m_uds == KIO::UDS_ACCESS)
+ flags = (*eIt).m_long;
+ }
+ if ((mimeType == "message/rfc822-imap" || mimeType == "message/rfc822") &&
+ !(flags & 8)) {
+ (*it).items.append(name + "," + QString::number(flags));
+ if ( mMailCheckProgressItem ) {
+ mMailCheckProgressItem->incCompletedItems();
+ mMailCheckProgressItem->updateProgress();
+ }
+ }
+ }
+}
+
+
+// debugging helper
+//X static QString flagsToString( int flags )
+//X {
+//X QString str("(");
+//X if ( flags & 4 ) {
+//X str += "\\Flagged ";
+//X }
+//X if ( flags & 2 ) {
+//X str += "\\Answered ";
+//X }
+//X if ( flags & 1 ) {
+//X str += "\\Seen";
+//X }
+//X str += ")";
+//X return str;
+//X }
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::flagsToStatus(KMMsgBase *msg, int flags, bool newMsg, int supportedFlags )
+{
+ if ( !msg ) return;
+
+ // see imap4/imapinfo.h for the magic numbers
+ static const struct {
+ const int imapFlag;
+ const int kmFlag;
+ const bool standardFlag;
+ } imapFlagMap[] = {
+ { 2, KMMsgStatusReplied, true },
+ { 4, KMMsgStatusFlag, true },
+ { 128, KMMsgStatusForwarded, false },
+ { 256, KMMsgStatusTodo, false },
+ { 512, KMMsgStatusWatched, false },
+ { 1024, KMMsgStatusIgnored, false }
+ };
+ static const int numFlags = sizeof imapFlagMap / sizeof *imapFlagMap;
+
+ const KMMsgStatus oldStatus = msg->status();
+ for ( int i = 0; i < numFlags; ++i ) {
+ if ( ( (supportedFlags & imapFlagMap[i].imapFlag) == 0 && (supportedFlags & 64) == 0 )
+ && !imapFlagMap[i].standardFlag ) {
+ continue;
+ }
+ if ( ((flags & imapFlagMap[i].imapFlag) > 0) != ((oldStatus & imapFlagMap[i].kmFlag) > 0) ) {
+ msg->toggleStatus( imapFlagMap[i].kmFlag );
+ }
+ }
+
+ seenFlagToStatus( msg, flags, newMsg );
+}
+
+void KMFolderImap::seenFlagToStatus(KMMsgBase * msg, int flags, bool newMsg)
+{
+ if ( !msg ) return;
+
+ const KMMsgStatus oldStatus = msg->status();
+ if ( (flags & 1) && (oldStatus & KMMsgStatusOld) == 0 )
+ msg->setStatus( KMMsgStatusOld );
+
+ // In case the message does not have the seen flag set, override our local
+ // notion that it is read. Otherwise the count of unread messages and the
+ // number of messages which actually show up as read can go out of sync.
+ if ( msg->isOfUnknownStatus() || (!(flags&1) && !(oldStatus&(KMMsgStatusNew|KMMsgStatusUnread)) ) ) {
+ if (newMsg) {
+ if ( (oldStatus & KMMsgStatusNew) == 0 )
+ msg->setStatus( KMMsgStatusNew );
+ } else {
+ if ( (oldStatus & KMMsgStatusUnread) == 0 )
+ msg->setStatus( KMMsgStatusUnread );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMFolderImap::statusToFlags(KMMsgStatus status, int supportedFlags)
+{
+ QString flags;
+ if (status & KMMsgStatusDeleted)
+ flags = "\\DELETED";
+ else {
+ if (status & KMMsgStatusOld || status & KMMsgStatusRead)
+ flags = "\\SEEN ";
+ if (status & KMMsgStatusReplied)
+ flags += "\\ANSWERED ";
+ if (status & KMMsgStatusFlag)
+ flags += "\\FLAGGED ";
+ // non standard flags
+ if ( (status & KMMsgStatusForwarded) && ((supportedFlags & 64) || (supportedFlags & 128)) )
+ flags += "$FORWARDED ";
+ if ( (status & KMMsgStatusTodo) && ((supportedFlags & 64) || (supportedFlags & 256)) )
+ flags += "$TODO ";
+ if ( (status & KMMsgStatusWatched) && ((supportedFlags & 64) || (supportedFlags & 512)) )
+ flags += "$WATCHED ";
+ if ( (status & KMMsgStatusIgnored) && ((supportedFlags & 64) || (supportedFlags & 1024)) )
+ flags += "$IGNORED ";
+ }
+
+ return flags.simplifyWhiteSpace();
+}
+
+//-------------------------------------------------------------
+void
+KMFolderImap::ignoreJobsForMessage( KMMessage* msg )
+{
+ if ( !msg || msg->transferInProgress() ||
+ !msg->parent() || msg->parent()->folderType() != KMFolderTypeImap )
+ return;
+ KMAcctImap *account;
+ if ( !(account = static_cast<KMFolderImap*>(msg->storage())->account()) )
+ return;
+
+ account->ignoreJobsForMessage( msg );
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::slotGetMessagesData(KIO::Job * job, const QByteArray & data)
+{
+ if ( data.isEmpty() ) return; // optimization
+ ImapAccountBase::JobIterator it = account()->findJob(job);
+ if ( it == account()->jobsEnd() ) return;
+ (*it).cdata += QCString(data, data.size() + 1);
+ int pos = (*it).cdata.find("\r\n--IMAPDIGEST");
+ if ( pos == -1 ) {
+ // if we do not find the pattern in the complete string we will not find
+ // it in a substring.
+ return;
+ }
+ if (pos > 0)
+ {
+ int p = (*it).cdata.find("\r\nX-uidValidity:");
+ if (p != -1) setUidValidity((*it).cdata
+ .mid(p + 17, (*it).cdata.find("\r\n", p+1) - p - 17));
+ int c = (*it).cdata.find("\r\nX-Count:");
+ if ( c != -1 )
+ {
+ bool ok;
+ int exists = (*it).cdata.mid( c+10,
+ (*it).cdata.find("\r\n", c+1) - c-10 ).toInt(&ok);
+ if ( ok && exists < count() ) {
+ kdDebug(5006) << "KMFolderImap::slotGetMessagesData - server has less messages (" <<
+ exists << ") then folder (" << count() << "), so reload" << endl;
+ open("getMessage");
+ reallyGetFolder( QString::null );
+ (*it).cdata.remove(0, pos);
+ return;
+ } else if ( ok ) {
+ int delta = exists - count();
+ if ( mMailCheckProgressItem ) {
+ mMailCheckProgressItem->setTotalItems( delta );
+ }
+ }
+ }
+ (*it).cdata.remove(0, pos);
+ }
+ pos = (*it).cdata.find("\r\n--IMAPDIGEST", 1);
+ int flags;
+ while (pos >= 0)
+ {
+ KMMessage *msg = new KMMessage;
+ msg->setComplete( false );
+ msg->setReadyToShow( false );
+ // nothing between the boundaries, older UWs do that
+ if ( pos != 14 ) {
+ msg->fromString( (*it).cdata.mid(16, pos - 16) );
+ flags = msg->headerField("X-Flags").toInt();
+ ulong uid = msg->UID();
+ KMMsgMetaData *md = 0;
+ if ( mUidMetaDataMap.find( uid ) ) {
+ md = mUidMetaDataMap[uid];
+ }
+ ulong serNum = 0;
+ if ( md ) {
+ serNum = md->serNum();
+ }
+ bool ok = true;
+ if ( uid <= lastUid() && serNum > 0 ) {
+ // the UID is already known so no need to create it
+ ok = false;
+ }
+ // deleted flag
+ if ( flags & 8 )
+ ok = false;
+ if ( !ok ) {
+ delete msg;
+ msg = 0;
+ } else {
+ if ( serNum > 0 ) {
+ // assign the sernum from the cache
+ msg->setMsgSerNum( serNum );
+ }
+ // Transfer the status, if it is cached.
+ if ( md ) {
+ msg->setStatus( md->status() );
+ } else if ( !account()->hasCapability("uidplus") ) {
+ // see if we have cached the msgIdMD5 and get the status +
+ // serial number from there
+ QString id = msg->msgIdMD5();
+ if ( mMetaDataMap.find( id ) ) {
+ md = mMetaDataMap[id];
+ msg->setStatus( md->status() );
+ if ( md->serNum() != 0 && serNum == 0 ) {
+ msg->setMsgSerNum( md->serNum() );
+ }
+ mMetaDataMap.remove( id );
+ delete md;
+ }
+ }
+ KMFolderMbox::addMsg(msg, 0);
+ // Merge with the flags from the server.
+ flagsToStatus((KMMsgBase*)msg, flags, true, mUploadAllFlags ? 31 : mPermanentFlags);
+ // set the correct size
+ msg->setMsgSizeServer( msg->headerField("X-Length").toUInt() );
+ msg->setUID(uid);
+ if ( msg->getMsgSerNum() > 0 ) {
+ saveMsgMetaData( msg );
+ }
+ // Filter messages that have arrived in the inbox folder
+ if ( folder()->isSystemFolder() && imapPath() == "/INBOX/"
+ && kmkernel->filterMgr()->atLeastOneIncomingFilterAppliesTo( account()->id() ) )
+ account()->execFilters( msg->getMsgSerNum() );
+
+ if ( count() > 1 ) {
+ unGetMsg(count() - 1);
+ }
+ mLastUid = uid;
+ if ( mMailCheckProgressItem ) {
+ mMailCheckProgressItem->incCompletedItems();
+ mMailCheckProgressItem->updateProgress();
+ }
+ }
+ }
+ (*it).cdata.remove(0, pos);
+ (*it).done++;
+ pos = (*it).cdata.find("\r\n--IMAPDIGEST", 1);
+ } // while
+}
+
+//-------------------------------------------------------------
+FolderJob*
+KMFolderImap::doCreateJob( KMMessage *msg, FolderJob::JobType jt,
+ KMFolder *folder, QString partSpecifier,
+ const AttachmentStrategy *as ) const
+{
+ KMFolderImap* kmfi = folder? dynamic_cast<KMFolderImap*>(folder->storage()) : 0;
+ if ( jt == FolderJob::tGetMessage && partSpecifier == "STRUCTURE" &&
+ account() && account()->loadOnDemand() &&
+ ( msg->msgSizeServer() > 5000 || msg->msgSizeServer() == 0 ) &&
+ ( msg->signatureState() == KMMsgNotSigned ||
+ msg->signatureState() == KMMsgSignatureStateUnknown ) &&
+ ( msg->encryptionState() == KMMsgNotEncrypted ||
+ msg->encryptionState() == KMMsgEncryptionStateUnknown ) )
+ {
+ // load-on-demand: retrieve the BODYSTRUCTURE and to speed things up also the headers
+ // this is not activated for small or signed messages
+ ImapJob *job = new ImapJob( msg, jt, kmfi, "HEADER" );
+ job->start();
+ ImapJob *job2 = new ImapJob( msg, jt, kmfi, "STRUCTURE", as );
+ job2->start();
+ job->setParentFolder( this );
+ return job;
+ } else {
+ // download complete message or part (attachment)
+ if ( partSpecifier == "STRUCTURE" ) // hide from outside
+ partSpecifier = QString::null;
+
+ ImapJob *job = new ImapJob( msg, jt, kmfi, partSpecifier );
+ job->setParentFolder( this );
+ return job;
+ }
+}
+
+//-------------------------------------------------------------
+FolderJob*
+KMFolderImap::doCreateJob( QPtrList<KMMessage>& msgList, const QString& sets,
+ FolderJob::JobType jt, KMFolder *folder ) const
+{
+ KMFolderImap* kmfi = dynamic_cast<KMFolderImap*>(folder->storage());
+ ImapJob *job = new ImapJob( msgList, sets, jt, kmfi );
+ job->setParentFolder( this );
+ return job;
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::getMessagesResult(KIO::Job * job, bool lastSet)
+{
+ ImapAccountBase::JobIterator it = account()->findJob(job);
+ if ( it == account()->jobsEnd() ) return;
+ if (job->error()) {
+ account()->handleJobError( job, i18n("Error while retrieving messages.") );
+ finishMailCheck( "getMessage", imapNoInformation );
+ return;
+ }
+ if (lastSet) {
+ finishMailCheck( "getMessage", imapFinished );
+ account()->removeJob(it);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::slotGetLastMessagesResult(KIO::Job * job)
+{
+ getMessagesResult(job, true);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::slotGetMessagesResult(KIO::Job * job)
+{
+ getMessagesResult(job, false);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::createFolder(const QString &name, const QString& parentPath,
+ bool askUser)
+{
+ kdDebug(5006) << "KMFolderImap::createFolder - name=" << name << ",parent=" <<
+ parentPath << ",askUser=" << askUser << endl;
+ if ( account()->makeConnection() != ImapAccountBase::Connected ) {
+ kdWarning(5006) << "KMFolderImap::createFolder - got no connection" << endl;
+ return;
+ }
+ KURL url = account()->getUrl();
+ QString parent = ( parentPath.isEmpty() ? imapPath() : parentPath );
+ QString path = account()->createImapPath( parent, name );
+ if ( askUser ) {
+ path += "/;INFO=ASKUSER";
+ }
+ url.setPath( path );
+
+ KIO::SimpleJob *job = KIO::mkdir(url);
+ KIO::Scheduler::assignJobToSlave(account()->slave(), job);
+ ImapAccountBase::jobData jd( url.url(), folder() );
+ jd.items = name;
+ account()->insertJob(job, jd);
+ connect(job, SIGNAL(result(KIO::Job *)),
+ this, SLOT(slotCreateFolderResult(KIO::Job *)));
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::slotCreateFolderResult(KIO::Job * job)
+{
+ ImapAccountBase::JobIterator it = account()->findJob(job);
+ if ( it == account()->jobsEnd() ) return;
+
+ QString name;
+ if ( it.data().items.count() > 0 )
+ name = it.data().items.first();
+
+ if (job->error())
+ {
+ if ( job->error() == KIO::ERR_COULD_NOT_MKDIR ) {
+ // Creating a folder failed, remove it from the tree.
+ account()->listDirectory( );
+ }
+ account()->handleJobError( job, i18n("Error while creating a folder.") );
+ emit folderCreationResult( name, false );
+ } else {
+ listDirectory();
+ account()->removeJob(job);
+ emit folderCreationResult( name, true );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+static QTextCodec *sUtf7Codec = 0;
+
+QTextCodec * KMFolderImap::utf7Codec()
+{
+ if (!sUtf7Codec) sUtf7Codec = QTextCodec::codecForName("utf-7");
+ return sUtf7Codec;
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMFolderImap::encodeFileName(const QString &name)
+{
+ QString result = utf7Codec()->fromUnicode(name);
+ return KURL::encode_string_no_slash(result);
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMFolderImap::decodeFileName(const QString &name)
+{
+ QString result = KURL::decode_string(name);
+ return utf7Codec()->toUnicode(result.latin1());
+}
+
+//-----------------------------------------------------------------------------
+bool KMFolderImap::autoExpunge()
+{
+ if (account())
+ return account()->autoExpunge();
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::slotSimpleData(KIO::Job * job, const QByteArray & data)
+{
+ if ( data.isEmpty() ) return; // optimization
+ ImapAccountBase::JobIterator it = account()->findJob(job);
+ if ( it == account()->jobsEnd() ) return;
+ QBuffer buff((*it).data);
+ buff.open(IO_WriteOnly | IO_Append);
+ buff.writeBlock(data.data(), data.size());
+ buff.close();
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::deleteMessage(KMMessage * msg)
+{
+ mUidMetaDataMap.remove( msg->UID() );
+ mMetaDataMap.remove( msg->msgIdMD5() );
+ KURL url = account()->getUrl();
+ KMFolderImap *msg_parent = static_cast<KMFolderImap*>(msg->storage());
+ ulong uid = msg->UID();
+ /* If the uid is empty the delete job below will nuke all mail in the
+ folder, so we better safeguard against that. See ::expungeFolder, as
+ to why. :( */
+ if ( uid == 0 ) {
+ kdDebug( 5006 ) << "KMFolderImap::deleteMessage: Attempt to delete "
+ "an empty UID. Aborting." << endl;
+ return;
+ }
+ url.setPath(msg_parent->imapPath() + ";UID=" + QString::number(uid) );
+ if ( account()->makeConnection() != ImapAccountBase::Connected )
+ return;
+ KIO::SimpleJob *job = KIO::file_delete(url, false);
+ KIO::Scheduler::assignJobToSlave(account()->slave(), job);
+ ImapAccountBase::jobData jd( url.url(), 0 );
+ account()->insertJob(job, jd);
+ connect(job, SIGNAL(result(KIO::Job *)),
+ account(), SLOT(slotSimpleResult(KIO::Job *)));
+}
+
+void KMFolderImap::deleteMessage(const QPtrList<KMMessage>& msgList)
+{
+ QPtrListIterator<KMMessage> it( msgList );
+ KMMessage *msg;
+ while ( (msg = it.current()) != 0 ) {
+ ++it;
+ mUidMetaDataMap.remove( msg->UID() );
+ mMetaDataMap.remove( msg->msgIdMD5() );
+ }
+
+ QValueList<ulong> uids;
+ getUids(msgList, uids);
+ QStringList sets = makeSets(uids);
+
+ KURL url = account()->getUrl();
+ KMFolderImap *msg_parent = static_cast<KMFolderImap*>(msgList.getFirst()->storage());
+ for ( QStringList::Iterator it = sets.begin(); it != sets.end(); ++it )
+ {
+ QString uid = *it;
+ // Don't delete with no uid, that nukes the folder. Should not happen, but
+ // better safe than sorry.
+ if ( uid.isEmpty() ) continue;
+ url.setPath(msg_parent->imapPath() + ";UID=" + uid);
+ if ( account()->makeConnection() != ImapAccountBase::Connected )
+ return;
+ KIO::SimpleJob *job = KIO::file_delete(url, false);
+ KIO::Scheduler::assignJobToSlave(account()->slave(), job);
+ ImapAccountBase::jobData jd( url.url(), 0 );
+ account()->insertJob(job, jd);
+ connect(job, SIGNAL(result(KIO::Job *)),
+ account(), SLOT(slotSimpleResult(KIO::Job *)));
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::setStatus(int idx, KMMsgStatus status, bool toggle)
+{
+ QValueList<int> ids; ids.append(idx);
+ setStatus(ids, status, toggle);
+}
+
+void KMFolderImap::setStatus(QValueList<int>& _ids, KMMsgStatus status, bool toggle)
+{
+ FolderStorage::setStatus(_ids, status, toggle);
+ QValueList<int> ids;
+ if ( mUploadAllFlags ) {
+ kdDebug(5006) << k_funcinfo << "Migrating all flags to the server" << endl;
+ ids.clear();
+ for ( int i = 0; i < count(); ++i )
+ ids << i;
+ mUploadAllFlags = false;
+ } else {
+ ids = _ids;
+ }
+
+ /* The status has been already set in the local index. Update the flags on
+ * the server. To avoid doing that for each message individually, group them
+ * by the status string they will be assigned and make sets for each of those
+ * groups of mails. This is necessary because the imap kio_slave status job
+ * does not append flags but overwrites them. Example:
+ *
+ * 2 important mails and 3 unimportant mail, all unread. Mark all as read calls
+ * this method with a list of uids. The 2 important mails need to get the string
+ * \SEEN \FLAGGED while the others need to get just \SEEN. Build sets for each
+ * of those and sort them, so the server can handle them efficiently. */
+
+ if ( mReadOnly ) { // mUserRights is not available here
+ // FIXME duplicated code in KMFolderCachedImap
+ QValueList<ulong> seenUids, unseenUids;
+ for ( QValueList<int>::ConstIterator it = ids.constBegin(); it != ids.constEnd(); ++it ) {
+ KMMessage *msg = 0;
+ bool unget = !isMessage(*it);
+ msg = getMsg(*it);
+ if (!msg) continue;
+ if ( msg->status() & KMMsgStatusOld || msg->status() & KMMsgStatusRead )
+ seenUids.append( msg->UID() );
+ else
+ unseenUids.append( msg->UID() );
+ if (unget) unGetMsg(*it);
+ }
+ if ( !seenUids.isEmpty() ) {
+ QStringList sets = KMFolderImap::makeSets( seenUids, true );
+ for( QStringList::Iterator it = sets.begin(); it != sets.end(); ++it ) {
+ QString imappath = imapPath() + ";UID=" + ( *it );
+ account()->setImapSeenStatus( folder(), imappath, true );
+ }
+ }
+ if ( !unseenUids.isEmpty() ) {
+ QStringList sets = KMFolderImap::makeSets( unseenUids, true );
+ for( QStringList::Iterator it = sets.begin(); it != sets.end(); ++it ) {
+ QString imappath = imapPath() + ";UID=" + ( *it );
+ account()->setImapSeenStatus( folder(), imappath, false );
+ }
+ }
+ return;
+ }
+
+ QMap< QString, QStringList > groups;
+ for ( QValueList<int>::Iterator it = ids.begin(); it != ids.end(); ++it ) {
+ KMMessage *msg = 0;
+ bool unget = !isMessage(*it);
+ msg = getMsg(*it);
+ if (!msg) continue;
+ QString flags = statusToFlags(msg->status(), mPermanentFlags);
+ // Collect uids for each type of flags.
+ groups[flags].append(QString::number(msg->UID()));
+ if (unget) unGetMsg(*it);
+ }
+ QMapIterator< QString, QStringList > dit;
+ for ( dit = groups.begin(); dit != groups.end(); ++dit ) {
+ QCString flags = dit.key().latin1();
+ QStringList sets = makeSets( (*dit), true );
+ // Send off a status setting job for each set.
+ for ( QStringList::Iterator slit = sets.begin(); slit != sets.end(); ++slit ) {
+ QString imappath = imapPath() + ";UID=" + ( *slit );
+ account()->setImapStatus(folder(), imappath, flags);
+ }
+ }
+ if ( mContentState == imapListingInProgress ) {
+ // we're currently get'ing this folder
+ // to make sure that we get the latest flags abort the current listing and
+ // create a new one
+ kdDebug(5006) << "Set status during folder listing, restarting listing." << endl;
+ disconnect(this, SLOT(slotListFolderResult(KIO::Job *)));
+ quiet( false );
+ reallyGetFolder( QString::null );
+ }
+}
+
+//-----------------------------------------------------------------------------
+QStringList KMFolderImap::makeSets(const QStringList& uids, bool sort)
+{
+ QValueList<ulong> tmp;
+ for ( QStringList::ConstIterator it = uids.begin(); it != uids.end(); ++it )
+ tmp.append( (*it).toInt() );
+ return makeSets(tmp, sort);
+}
+
+QStringList KMFolderImap::makeSets( QValueList<ulong>& uids, bool sort )
+{
+ QStringList sets;
+ QString set;
+
+ if (uids.size() == 1)
+ {
+ sets.append(QString::number(uids.first()));
+ return sets;
+ }
+
+ if (sort) qHeapSort(uids);
+
+ ulong last = 0;
+ // needed to make a uid like 124 instead of 124:124
+ bool inserted = false;
+ /* iterate over uids and build sets like 120:122,124,126:150 */
+ for ( QValueList<ulong>::Iterator it = uids.begin(); it != uids.end(); ++it )
+ {
+ if (it == uids.begin() || set.isEmpty()) {
+ set = QString::number(*it);
+ inserted = true;
+ } else
+ {
+ if (last+1 != *it)
+ {
+ // end this range
+ if (inserted)
+ set += ',' + QString::number(*it);
+ else
+ set += ':' + QString::number(last) + ',' + QString::number(*it);
+ inserted = true;
+ if (set.length() > 100)
+ {
+ // just in case the server has a problem with longer lines..
+ sets.append(set);
+ set = "";
+ }
+ } else {
+ inserted = false;
+ }
+ }
+ last = *it;
+ }
+ // last element
+ if (!inserted)
+ set += ':' + QString::number(uids.last());
+
+ if (!set.isEmpty()) sets.append(set);
+
+ return sets;
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::getUids(QValueList<int>& ids, QValueList<ulong>& uids)
+{
+ KMMsgBase *msg = 0;
+ // get the uids
+ for ( QValueList<int>::Iterator it = ids.begin(); it != ids.end(); ++it )
+ {
+ msg = getMsgBase(*it);
+ if (!msg) continue;
+ uids.append(msg->UID());
+ }
+}
+
+void KMFolderImap::getUids(const QPtrList<KMMessage>& msgList, QValueList<ulong>& uids)
+{
+ KMMessage *msg = 0;
+
+ QPtrListIterator<KMMessage> it( msgList );
+ while ( (msg = it.current()) != 0 ) {
+ ++it;
+ if ( msg->UID() > 0 ) {
+ uids.append( msg->UID() );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::expungeFolder(KMFolderImap * aFolder, bool quiet)
+{
+ aFolder->setNeedsCompacting(false);
+ KURL url = account()->getUrl();
+ url.setPath(aFolder->imapPath() + ";UID=*");
+ if ( account()->makeConnection() != ImapAccountBase::Connected )
+ return;
+ KIO::SimpleJob *job = KIO::file_delete(url, false);
+ KIO::Scheduler::assignJobToSlave(account()->slave(), job);
+ ImapAccountBase::jobData jd( url.url(), 0 );
+ jd.quiet = quiet;
+ account()->insertJob(job, jd);
+ connect(job, SIGNAL(result(KIO::Job *)),
+ account(), SLOT(slotSimpleResult(KIO::Job *)));
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::slotProcessNewMail( int errorCode, const QString &errorMsg )
+{
+ Q_UNUSED( errorMsg );
+ disconnect( account(), SIGNAL( connectionResult(int, const QString&) ),
+ this, SLOT( slotProcessNewMail(int, const QString&) ) );
+ if ( !errorCode )
+ processNewMail( false );
+ else
+ emit numUnreadMsgsChanged( folder() );
+}
+
+//-----------------------------------------------------------------------------
+bool KMFolderImap::processNewMail(bool)
+{
+ // a little safety
+ if ( !account() ) {
+ kdDebug(5006) << "KMFolderImap::processNewMail - account is null!" << endl;
+ return false;
+ }
+ if ( imapPath().isEmpty() ) {
+ kdDebug(5006) << "KMFolderImap::processNewMail - imapPath of " << name() << " is empty!" << endl;
+ // remove it locally
+ setAlreadyRemoved( true );
+ kmkernel->imapFolderMgr()->remove( folder() );
+ return false;
+ }
+ // check the connection
+ if ( account()->makeConnection() == ImapAccountBase::Error ) {
+ kdDebug(5006) << "KMFolderImap::processNewMail - got no connection!" << endl;
+ return false;
+ } else if ( account()->makeConnection() == ImapAccountBase::Connecting )
+ {
+ // wait
+ kdDebug(5006) << "KMFolderImap::processNewMail - waiting for connection: " << label() << endl;
+ connect( account(), SIGNAL( connectionResult(int, const QString&) ),
+ this, SLOT( slotProcessNewMail(int, const QString&) ) );
+ return true;
+ }
+ KURL url = account()->getUrl();
+ if (mReadOnly)
+ url.setPath(imapPath() + ";SECTION=UIDNEXT");
+ else
+ url.setPath(imapPath() + ";SECTION=UNSEEN");
+
+ mMailCheckProgressItem = ProgressManager::createProgressItem(
+ "MailCheckAccount" + account()->name(),
+ "MailCheck" + folder()->prettyURL(),
+ QStyleSheet::escape( folder()->prettyURL() ),
+ i18n("updating message counts"),
+ false,
+ account()->useSSL() || account()->useTLS() );
+
+ KIO::SimpleJob *job = KIO::stat(url, false);
+ KIO::Scheduler::assignJobToSlave(account()->slave(), job);
+ ImapAccountBase::jobData jd(url.url(), folder() );
+ jd.cancellable = true;
+ account()->insertJob(job, jd);
+ connect(job, SIGNAL(result(KIO::Job *)),
+ SLOT(slotStatResult(KIO::Job *)));
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::slotStatResult(KIO::Job * job)
+{
+ slotCompleteMailCheckProgress();
+ ImapAccountBase::JobIterator it = account()->findJob(job);
+ if ( it == account()->jobsEnd() ) return;
+ account()->removeJob(it);
+ if (job->error())
+ {
+ account()->handleJobError( job, i18n("Error while getting folder information.") );
+ } else {
+ KIO::UDSEntry uds = static_cast<KIO::StatJob*>(job)->statResult();
+ for (KIO::UDSEntry::ConstIterator it = uds.begin(); it != uds.end(); it++)
+ {
+ if ((*it).m_uds == KIO::UDS_SIZE)
+ {
+ if (mReadOnly)
+ {
+ mGuessedUnreadMsgs = -1;
+ mGuessedUnreadMsgs = countUnread() + (*it).m_long - lastUid() - 1;
+ if (mGuessedUnreadMsgs < 0) mGuessedUnreadMsgs = 0;
+ } else {
+ mGuessedUnreadMsgs = (*it).m_long;
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+int KMFolderImap::create()
+{
+ readConfig();
+ mUnreadMsgs = -1;
+ return KMFolderMbox::create();
+}
+
+QValueList<ulong> KMFolderImap::splitSets(const QString uids)
+{
+ QValueList<ulong> uidlist;
+
+ // ex: 1205,1204,1203,1202,1236:1238
+ QString buffer = QString::null;
+ int setstart = -1;
+ // iterate over the uids
+ for (uint i = 0; i < uids.length(); i++)
+ {
+ QChar chr = uids[i];
+ if (chr == ',')
+ {
+ if (setstart > -1)
+ {
+ // a range (uid:uid) was before
+ for (int j = setstart; j <= buffer.toInt(); j++)
+ {
+ uidlist.append(j);
+ }
+ setstart = -1;
+ } else {
+ // single uid
+ uidlist.append(buffer.toInt());
+ }
+ buffer = "";
+ } else if (chr == ':') {
+ // remember the start of the range
+ setstart = buffer.toInt();
+ buffer = "";
+ } else if (chr.category() == QChar::Number_DecimalDigit) {
+ // digit
+ buffer += chr;
+ } else {
+ // ignore
+ }
+ }
+ // process the last data
+ if (setstart > -1)
+ {
+ for (int j = setstart; j <= buffer.toInt(); j++)
+ {
+ uidlist.append(j);
+ }
+ } else {
+ uidlist.append(buffer.toInt());
+ }
+
+ return uidlist;
+}
+
+//-----------------------------------------------------------------------------
+int KMFolderImap::expungeContents()
+{
+ // nuke the local cache
+ int rc = KMFolderMbox::expungeContents();
+
+ // set the deleted flag for all messages in the folder
+ KURL url = account()->getUrl();
+ url.setPath( imapPath() + ";UID=1:*");
+ if ( account()->makeConnection() == ImapAccountBase::Connected )
+ {
+ KIO::SimpleJob *job = KIO::file_delete(url, false);
+ KIO::Scheduler::assignJobToSlave(account()->slave(), job);
+ ImapAccountBase::jobData jd( url.url(), 0 );
+ jd.quiet = true;
+ account()->insertJob(job, jd);
+ connect(job, SIGNAL(result(KIO::Job *)),
+ account(), SLOT(slotSimpleResult(KIO::Job *)));
+ }
+ /* Is the below correct? If we are expunging (in the folder sense, not the imap sense),
+ why delete but not (imap-)expunge? Since the folder is not active there is no concept
+ of "leaving the folder", so the setting really has little to do with it. */
+ // if ( autoExpunge() )
+ expungeFolder(this, true);
+ getFolder();
+
+ return rc;
+}
+
+//-----------------------------------------------------------------------------
+void
+KMFolderImap::setUserRights( unsigned int userRights )
+{
+ mUserRights = userRights;
+ kdDebug(5006) << imapPath() << " setUserRights: " << userRights << endl;
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::slotCompleteMailCheckProgress()
+{
+ if ( mMailCheckProgressItem ) {
+ mMailCheckProgressItem->setComplete();
+ mMailCheckProgressItem = 0;
+ emit numUnreadMsgsChanged( folder() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::setSubfolderState( imapState state )
+{
+ mSubfolderState = state;
+ if ( state == imapNoInformation && folder()->child() )
+ {
+ // pass through to children
+ KMFolderNode* node;
+ QPtrListIterator<KMFolderNode> it( *folder()->child() );
+ for ( ; (node = it.current()); )
+ {
+ ++it;
+ if (node->isDir()) continue;
+ KMFolder *folder = static_cast<KMFolder*>(node);
+ static_cast<KMFolderImap*>(folder->storage())->setSubfolderState( state );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::setIncludeInMailCheck( bool check )
+{
+ bool changed = ( mCheckMail != check );
+ mCheckMail = check;
+ if ( changed )
+ account()->slotUpdateFolderList();
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::setAlreadyRemoved( bool removed )
+{
+ mAlreadyRemoved = removed;
+ if ( folder()->child() )
+ {
+ // pass through to childs
+ KMFolderNode* node;
+ QPtrListIterator<KMFolderNode> it( *folder()->child() );
+ for ( ; (node = it.current()); )
+ {
+ ++it;
+ if (node->isDir()) continue;
+ KMFolder *folder = static_cast<KMFolder*>(node);
+ static_cast<KMFolderImap*>(folder->storage())->setAlreadyRemoved( removed );
+ }
+ }
+}
+
+void KMFolderImap::slotCreatePendingFolders( int errorCode, const QString& errorMsg )
+{
+ Q_UNUSED( errorMsg );
+ disconnect( account(), SIGNAL( connectionResult( int, const QString& ) ),
+ this, SLOT( slotCreatePendingFolders( int, const QString& ) ) );
+ if ( !errorCode ) {
+ QStringList::Iterator it = mFoldersPendingCreation.begin();
+ for ( ; it != mFoldersPendingCreation.end(); ++it ) {
+ createFolder( *it );
+ }
+ }
+ mFoldersPendingCreation.clear();
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::search( const KMSearchPattern* pattern )
+{
+ if ( !pattern || pattern->isEmpty() )
+ {
+ // not much to do here
+ QValueList<Q_UINT32> serNums;
+ emit searchResult( folder(), serNums, pattern, true );
+ return;
+ }
+ SearchJob* job = new SearchJob( this, account(), pattern );
+ connect( job, SIGNAL( searchDone( QValueList<Q_UINT32>, const KMSearchPattern*, bool ) ),
+ this, SLOT( slotSearchDone( QValueList<Q_UINT32>, const KMSearchPattern*, bool ) ) );
+ job->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::slotSearchDone( QValueList<Q_UINT32> serNums,
+ const KMSearchPattern* pattern,
+ bool complete )
+{
+ emit searchResult( folder(), serNums, pattern, complete );
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::search( const KMSearchPattern* pattern, Q_UINT32 serNum )
+{
+ if ( !pattern || pattern->isEmpty() )
+ {
+ // not much to do here
+ emit searchDone( folder(), serNum, pattern, false );
+ return;
+ }
+ SearchJob* job = new SearchJob( this, account(), pattern, serNum );
+ connect( job, SIGNAL( searchDone( Q_UINT32, const KMSearchPattern*, bool ) ),
+ this, SLOT( slotSearchDone( Q_UINT32, const KMSearchPattern*, bool ) ) );
+ job->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::slotSearchDone( Q_UINT32 serNum, const KMSearchPattern* pattern,
+ bool matches )
+{
+ emit searchDone( folder(), serNum, pattern, matches );
+}
+
+//-----------------------------------------------------------------------------
+bool KMFolderImap::isMoveable() const
+{
+ return ( hasChildren() == HasNoChildren &&
+ !folder()->isSystemFolder() ) ? true : false;
+}
+
+//-----------------------------------------------------------------------------
+const ulong KMFolderImap::serNumForUID( ulong uid )
+{
+ if ( mUidMetaDataMap.find( uid ) ) {
+ KMMsgMetaData *md = mUidMetaDataMap[uid];
+ return md->serNum();
+ } else {
+ kdDebug(5006) << "serNumForUID: unknown uid " << uid << endl;
+ return 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::saveMsgMetaData( KMMessage* msg, ulong uid )
+{
+ if ( uid == 0 ) {
+ uid = msg->UID();
+ }
+ ulong serNum = msg->getMsgSerNum();
+ mUidMetaDataMap.replace( uid, new KMMsgMetaData(msg->status(), serNum) );
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderImap::setImapPath( const QString& path )
+{
+ if ( path.isEmpty() ) {
+ kdWarning(5006) << k_funcinfo << "ignoring empty path" << endl;
+ } else {
+ mImapPath = path;
+ }
+}
+
+void KMFolderImap::finishMailCheck( const char *dbg, imapState state )
+{
+ quiet( false );
+ mContentState = state;
+ emit folderComplete( this, mContentState == imapFinished );
+ close(dbg);
+}
+
+#include "kmfolderimap.moc"
diff --git a/kmail/kmfolderimap.h b/kmail/kmfolderimap.h
new file mode 100644
index 00000000..f7c027e0
--- /dev/null
+++ b/kmail/kmfolderimap.h
@@ -0,0 +1,548 @@
+/*
+ * kmfolderimap.h
+ *
+ * Copyright (c) 2001 Kurt Granroth <granroth@kde.org>
+ * Copyright (c) 2000-2002 Michael Haeckel <haeckel@kde.org>
+ *
+ * This file is based on kmacctimap.h by Michael Haeckel which is
+ * based on popaccount.h by Don Sanders
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef kmfolderimap_h
+#define kmfolderimap_h
+
+#include "kmacctimap.h"
+#include "kmfoldermbox.h"
+#include "kmmsgbase.h"
+
+#include "kio/job.h"
+#include "kio/global.h"
+
+#include <kstandarddirs.h>
+
+#include <qintdict.h>
+#include <qdict.h>
+template< typename T> class QPtrList;
+template< typename T> class QValueList;
+
+class KMFolderTreeItem;
+class KMFolderImap;
+class KMSearchPattern;
+class KMMessage;
+namespace KMail {
+ class FolderJob;
+ class ImapJob;
+ class AttachmentStrategy;
+ class ImapAccountBase;
+}
+namespace KPIM {
+ class ProgressItem;
+}
+using KMail::FolderJob;
+using KMail::ImapJob;
+using KMail::AttachmentStrategy;
+using KMail::ImapAccountBase;
+using KPIM::ProgressItem;
+
+class KMMsgMetaData
+{
+public:
+ KMMsgMetaData(KMMsgStatus aStatus)
+ :mStatus(aStatus), mSerNum(0) {}
+ KMMsgMetaData(KMMsgStatus aStatus, Q_UINT32 aSerNum)
+ :mStatus(aStatus), mSerNum(aSerNum) {}
+ ~KMMsgMetaData() {};
+ const KMMsgStatus status() const { return mStatus; }
+ const Q_UINT32 serNum() const { return mSerNum; }
+private:
+ KMMsgStatus mStatus;
+ Q_UINT32 mSerNum;
+};
+
+
+
+class KMFolderImap : public KMFolderMbox
+{
+ Q_OBJECT
+ friend class ::KMail::ImapJob;
+public:
+
+ static QString cacheLocation() {
+ return locateLocal("data", "kmail/imap" );
+ }
+
+ enum imapState {
+ imapNoInformation = 0,
+ imapListingInProgress = 1,
+ imapDownloadInProgress = 2,
+ imapFinished = 3
+ };
+
+ virtual imapState getContentState() { return mContentState; }
+ virtual void setContentState(imapState state) { mContentState = state; }
+
+ virtual imapState getSubfolderState() { return mSubfolderState; }
+ virtual void setSubfolderState(imapState state);
+
+ /** Usually a parent is given. But in some cases there is no
+ fitting parent object available. Then the name of the folder
+ is used as the absolute path to the folder file. */
+ KMFolderImap(KMFolder* folder, const char* name=0);
+ virtual ~KMFolderImap();
+
+ /** Returns the type of this folder */
+ virtual KMFolderType folderType() const { return KMFolderTypeImap; }
+
+ virtual KMMessage* getMsg(int idx);
+ /** The path to the imap folder on the server */
+ void setImapPath( const QString &path );
+ QString imapPath() const { return mImapPath; }
+
+ /** The highest UID in the folder */
+ ulong lastUid();
+
+ /** The uidvalidity of the last update */
+ void setUidValidity(const QString &validity) { mUidValidity = validity; }
+ QString uidValidity() { return mUidValidity; }
+
+ /** The imap account associated with this folder */
+ void setAccount(KMAcctImap *acct);
+ KMAcctImap* account() const;
+
+ /** Remove (first occurrence of) given message from the folder. */
+ virtual void removeMsg(int i, bool quiet = FALSE);
+ virtual void removeMsg(const QPtrList<KMMessage>& msgList, bool quiet = FALSE);
+
+ virtual int rename( const QString& newName, KMFolderDir *aParent = 0 );
+
+ /** Remove the IMAP folder on the server and if successful also locally */
+ virtual void remove();
+
+ /** Automatically expunge deleted messages when leaving the folder */
+ bool autoExpunge();
+
+ /** Write the config file */
+ virtual void writeConfig();
+
+ /** Read the config file */
+ virtual void readConfig();
+
+ /**
+ * List a directory and add the contents to kmfoldermgr
+ * It uses a ListJob to get the folders
+ * returns false if the connection failed
+ */
+ virtual bool listDirectory();
+
+ /**
+ * Retrieve all mails in a folder
+ */
+ void getFolder(bool force = FALSE);
+
+ /**
+ * same as above but also checks for new mails
+ */
+ void getAndCheckFolder(bool force = FALSE);
+
+ /**
+ * Get the whole message
+ */
+ void getMessage(KMFolder * folder, KMMessage * msg);
+
+ /**
+ * Create a new subfolder
+ * You may specify the root imap path or this folder will be used
+ * If you set askUser to false and the server can only handle folders
+ * that contain messages _or_ folders the new folder is set to "contains messages"
+ * by default
+ */
+ void createFolder(const QString &name,
+ const QString& imapPath = QString::null, bool askUser = true);
+
+ /**
+ * Delete a message
+ */
+ void deleteMessage(KMMessage * msg);
+ void deleteMessage(const QPtrList<KMMessage>& msgList);
+
+ /**
+ * Change the status of the message indicated by @p index
+ * Overloaded function for the following one
+ */
+ virtual void setStatus(int idx, KMMsgStatus status, bool toggle);
+
+ /**
+ * Change the status of several messages indicated by @p ids
+ */
+ virtual void setStatus(QValueList<int>& _ids, KMMsgStatus status, bool toggle);
+
+ /** generates sets of uids */
+ static QStringList makeSets( QValueList<ulong>&, bool sort = true);
+ static QStringList makeSets(const QStringList&, bool sort = true);
+
+ /** splits the message list according to sets. Modifies the @msgList. */
+ static QPtrList<KMMessage> splitMessageList(const QString& set,
+ QPtrList<KMMessage>& msgList);
+
+ /** gets the uids of the given ids */
+ void getUids(QValueList<int>& ids, QValueList<ulong>& uids);
+
+ /** same as above but accepts a Message-List */
+ void getUids(const QPtrList<KMMessage>& msgList, QValueList<ulong>& uids);
+
+ /**
+ * Expunge deleted messages from the folder
+ */
+ void expungeFolder(KMFolderImap * aFolder, bool quiet);
+
+ virtual int compact( bool ) { expungeFolder(this, false); return 0; };
+
+ /**
+ * Emit the folderComplete signal
+ */
+ void sendFolderComplete(bool success)
+ { emit folderComplete(this, success); }
+
+ /**
+ * Refresh the number of unseen mails
+ * Returns false in an error condition
+ */
+ bool processNewMail(bool interactive);
+
+ /**
+ * Tell the folder, this it is selected and shall also display new mails,
+ * not only their number, when checking for mail.
+ */
+ void setSelected(bool selected) { mIsSelected = selected; }
+ bool isSelected() { return mIsSelected; }
+
+ /**
+ * Encode the given string in a filename save 7 bit string
+ */
+ static QString encodeFileName(const QString &);
+ static QString decodeFileName(const QString &);
+ static QTextCodec * utf7Codec();
+
+ /**
+ * Convert message status to a list of IMAP flags
+ */
+ static QString statusToFlags(KMMsgStatus status, int supportedFalgs);
+
+ /**
+ * Return the filename of the folder (reimplemented from KFolder)
+ */
+ virtual QString fileName() const {
+ return encodeFileName( KMFolderMbox::fileName() ); }
+
+ /**
+ * Get the serial number for the given UID (if available)
+ */
+ const ulong serNumForUID( ulong uid );
+
+ /**
+ * Save the metadata for the UID
+ * If the UID is not supplied the one from the message is taken
+ */
+ void saveMsgMetaData( KMMessage* msg, ulong uid = 0 );
+
+ /**
+ * Splits a uid-set into single uids
+ */
+ static QValueList<ulong> splitSets(const QString);
+
+ virtual void ignoreJobsForMessage( KMMessage* );
+
+ /**
+ * If this folder should be included in new-mail-check
+ */
+ bool includeInMailCheck() { return mCheckMail; }
+ void setIncludeInMailCheck( bool check );
+
+ /** Inherited */
+ virtual int create();
+
+ /** imap folders cannot expire */
+ virtual bool isAutoExpire() const { return false; }
+
+ /** Closes and cancels all pending jobs. */
+ virtual void reallyDoClose(const char* owner);
+
+ void setCheckingValidity( bool val ) { mCheckingValidity = val; }
+
+ /** Return the trash folder. */
+ KMFolder* trashFolder() const;
+
+ /**
+ * Mark the folder as already removed from the server
+ * If set to true the folder will only be deleted locally
+ * This will recursively be applied to all children
+ */
+ void setAlreadyRemoved(bool removed);
+
+ /// Is the folder readonly?
+ bool isReadOnly() const { return KMFolderMbox::isReadOnly() || mReadOnly; }
+
+ /**
+ * The user's rights on this folder - see bitfield in ACLJobs namespace.
+ * @return 0 when not known yet
+ */
+ unsigned int userRights() const { return mUserRights; }
+
+ /** Set the user's rights on this folder - called by getUserRights */
+ void setUserRights( unsigned int userRights );
+
+ /**
+ * Search for messages
+ * The actual search is done in slotSearch and the end
+ * is signaled with searchDone()
+ */
+ virtual void search( const KMSearchPattern* );
+ virtual void search( const KMSearchPattern*, Q_UINT32 serNum );
+
+ /** Returns true if this folder can be moved */
+ virtual bool isMoveable() const;
+
+ /** Initialize this storage from another one. Used when creating a child folder */
+ void initializeFrom( KMFolderImap* parent, QString path, QString mimeType );
+
+ /** Returns the IMAP flags that can be stored on the server. */
+ int permanentFlags() const { return mPermanentFlags; }
+
+signals:
+ void folderComplete(KMFolderImap *folder, bool success);
+
+ /**
+ * Emitted, when the account is deleted
+ */
+ void deleted(KMFolderImap*);
+
+ /**
+ * Emitted at the end of the directory listing
+ */
+ void directoryListingFinished(KMFolderImap*);
+
+ /**
+ * Emitted when a folder creation has finished.
+ * @param name The name of the folder that should have been created.
+ * @param success True if the folder was created, false otherwise.
+ */
+ void folderCreationResult( const QString &name, bool success );
+
+public slots:
+ /** Add a message to a folder after is has been added on an IMAP server */
+ virtual void addMsgQuiet(KMMessage *);
+ virtual void addMsgQuiet(QPtrList<KMMessage>);
+
+ /** Add the given message to the folder. Usually the message
+ is added at the end of the folder. Returns zero on success and
+ an errno error code on failure. The index of the new message
+ is stored in index_return if given.
+ Please note that the message is added as is to the folder and the folder
+ takes ownership of the message (deleting it in the destructor).*/
+ virtual int addMsg(KMMessage* msg, int* index_return = 0);
+ virtual int addMsg(QPtrList<KMMessage>&, QValueList<int>& index_return);
+
+ /** Copy the messages to this folder */
+ void copyMsg(QPtrList<KMMessage>& msgList/*, KMFolder* parent*/);
+
+
+ /** Detach message from this folder. Usable to call addMsg() afterwards.
+ Loads the message if it is not loaded up to now. */
+ virtual KMMessage* take(int idx);
+ virtual void take(QPtrList<KMMessage>);
+
+ /**
+ * Add the data a KIO::Job retrieves to the buffer
+ */
+ void slotSimpleData(KIO::Job * job, const QByteArray & data);
+
+ /**
+ * Convert IMAP flags to a message status
+ * @param newMsg specifies whether unseen messages are new or unread
+ */
+ static void flagsToStatus(KMMsgBase *msg, int flags, bool newMsg = TRUE, int supportedFalgs = 31 );
+
+ /**
+ * Convert IMAP seen flag to a message status.
+ * @param newMsg specifies whether unseen messages are new or unread
+ */
+ static void seenFlagToStatus( KMMsgBase *msg, int flags, bool newMsg = true );
+
+ /**
+ * Connected to the result signal of the copy/move job
+ */
+ void slotCopyMsgResult( KMail::FolderJob* job );
+
+ /**
+ * Called from the SearchJob when the folder is done or messages where found
+ */
+ void slotSearchDone( QValueList<Q_UINT32> serNums,
+ const KMSearchPattern* pattern,
+ bool complete );
+
+ /**
+ * Called from the SearchJob when the message was searched
+ */
+ void slotSearchDone( Q_UINT32 serNum, const KMSearchPattern* pattern, bool matches );
+
+ /**
+ * Connected to ListJob::receivedFolders
+ * creates/removes folders
+ */
+ void slotListResult( const QStringList&, const QStringList&,
+ const QStringList&, const QStringList&, const ImapAccountBase::jobData& );
+
+ /**
+ * Connected to slotListNamespaces
+ * creates/removes namespace folders
+ */
+ void slotCheckNamespace( const QStringList&, const QStringList&,
+ const QStringList&, const QStringList&, const ImapAccountBase::jobData& );
+
+protected:
+ virtual FolderJob* doCreateJob( KMMessage *msg, FolderJob::JobType jt,
+ KMFolder *folder, QString partSpecifier,
+ const AttachmentStrategy *as ) const;
+ virtual FolderJob* doCreateJob( QPtrList<KMMessage>& msgList, const QString& sets,
+ FolderJob::JobType jt, KMFolder *folder ) const;
+
+ void getMessagesResult(KIO::Job * job, bool lastSet);
+
+ /** Called by KMFolder::expunge() to delete the actual contents.
+ At the time of the call the folder has already been closed, and
+ the various index files deleted. Returns 0 on success. */
+ virtual int expungeContents();
+
+ void setChildrenState( QString attributes );
+
+ /** Create or find the INBOX and initialize it */
+ void initInbox();
+
+ /** See if there is a better parent then this folder */
+ KMFolderImap* findParent( const QString& path, const QString& name );
+
+ /** See if all folders are still present on server, otherwise delete them */
+ void checkFolders( const QStringList& folderNames, const QString& ns );
+
+ void finishMailCheck( const char *func, imapState state );
+
+protected slots:
+
+ /**
+ * Retrieve the whole folder or only the changes
+ */
+ void checkValidity();
+ void slotCheckValidityResult(KIO::Job * job);
+
+ /**
+ * Get the folder now (internal)
+ */
+ void reallyGetFolder(const QString &startUid = QString::null);
+
+ /**
+ * For listing the contents of a folder
+ */
+ void slotListFolderResult(KIO::Job * job);
+ void slotListFolderEntries(KIO::Job * job, const KIO::UDSEntryList & uds);
+
+ /**
+ * For retrieving a message digest
+ */
+ void slotGetMessagesResult(KIO::Job * job);
+ void slotGetLastMessagesResult(KIO::Job * job);
+ void slotGetMessagesData(KIO::Job * job, const QByteArray & data);
+
+ /**
+ * For creating a new subfolder
+ */
+ void slotCreateFolderResult(KIO::Job * job);
+
+ /**
+ * Remove the folder also locally, if removing on the server succeeded
+ */
+ void slotRemoveFolderResult(KIO::Job *job);
+
+ /**
+ * Update the number of unseen messages
+ */
+ void slotStatResult(KIO::Job *job);
+
+ /**
+ * notify the progress item that the mail check for this folder is
+ * done.
+ */
+ void slotCompleteMailCheckProgress();
+
+ /**
+ * Is called when the slave is connected and triggers a newmail check
+ */
+ void slotProcessNewMail( int errorCode, const QString& errorMsg );
+
+ /**
+ * Is connected when there are folders to be created on startup and the
+ * account is still connecting. Once the account emits the connected
+ * signal this slot is called and the folders created.
+ */
+ void slotCreatePendingFolders( int errorCode, const QString& errorMsg );
+
+ /**
+ * Starts a namespace listing
+ */
+ void slotListNamespaces();
+
+protected:
+ QString mImapPath;
+ ulong mLastUid;
+ imapState mContentState, mSubfolderState;
+ bool mIsSelected;
+ bool mCheckFlags;
+ bool mReadOnly;
+ bool mCheckMail;
+ mutable QGuardedPtr<KMAcctImap> mAccount;
+ // the current uidvalidity
+ QString mUidValidity;
+ unsigned int mUserRights;
+
+private:
+ // if we're checking validity currently
+ bool mCheckingValidity;
+ // uid - metadata cache
+ QIntDict<KMMsgMetaData> mUidMetaDataMap;
+ // msgidMD5 - status map
+ QDict<KMMsgMetaData> mMetaDataMap;
+ // if the folder should be deleted without server roundtrip
+ bool mAlreadyRemoved;
+ // the progress for mailchecks
+ QGuardedPtr<ProgressItem> mMailCheckProgressItem;
+ // the progress for listings
+ ProgressItem *mListDirProgressItem;
+ // the progress for addMsg
+ ProgressItem *mAddMessageProgressItem;
+ // to-be-added folders
+ QStringList mFoldersPendingCreation;
+
+ // push all flags to the server instead of just the changed once
+ // when doing a flag change the next time
+ // this is needed for migrating local flags from the time where we didn't
+ // have the ability to store them on the server
+ bool mUploadAllFlags;
+
+ // PERMANENTFLAGS part of SELECT response, needed to determine if custom flags can be
+ // stored on the server
+ int mPermanentFlags;
+};
+
+#endif // kmfolderimap_h
diff --git a/kmail/kmfolderindex.cpp b/kmail/kmfolderindex.cpp
new file mode 100644
index 00000000..a76b74f5
--- /dev/null
+++ b/kmail/kmfolderindex.cpp
@@ -0,0 +1,499 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2000 Don Sanders <sanders@kde.org>
+
+ 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
+*/
+
+#include "kmfolderindex.h"
+#include "kmfolder.h"
+#include <config.h>
+#include <qfileinfo.h>
+#include <qtimer.h>
+#include <kdebug.h>
+
+
+#define HAVE_MMAP //need to get this into autoconf FIXME --Sam
+#include <unistd.h>
+#ifdef HAVE_MMAP
+#include <sys/mman.h>
+#endif
+
+// Current version of the table of contents (index) files
+#define INDEX_VERSION 1506
+
+#ifndef MAX_LINE
+#define MAX_LINE 4096
+#endif
+
+#ifndef INIT_MSGS
+#define INIT_MSGS 8
+#endif
+
+#include <errno.h>
+#include <assert.h>
+#include <utime.h>
+#include <fcntl.h>
+
+#ifdef HAVE_BYTESWAP_H
+#include <byteswap.h>
+#endif
+#include <kapplication.h>
+#include <kcursor.h>
+#include <kmessagebox.h>
+#include <klocale.h>
+#include "kmmsgdict.h"
+
+// We define functions as kmail_swap_NN so that we don't get compile errors
+// on platforms where bswap_NN happens to be a function instead of a define.
+
+/* Swap bytes in 32 bit value. */
+#ifdef bswap_32
+#define kmail_swap_32(x) bswap_32(x)
+#else
+#define kmail_swap_32(x) \
+ ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \
+ (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))
+#endif
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+
+KMFolderIndex::KMFolderIndex(KMFolder* folder, const char* name)
+ : FolderStorage(folder, name), mMsgList(INIT_MSGS)
+{
+ mIndexStream = 0;
+ mIndexStreamPtr = 0;
+ mIndexStreamPtrLength = 0;
+ mIndexSwapByteOrder = false;
+ mIndexSizeOfLong = sizeof(long);
+ mIndexId = 0;
+ mHeaderOffset = 0;
+}
+
+
+KMFolderIndex::~KMFolderIndex()
+{
+}
+
+
+QString KMFolderIndex::indexLocation() const
+{
+ QString sLocation(folder()->path());
+
+ if ( !sLocation.isEmpty() ) {
+ sLocation += '/';
+ sLocation += '.';
+ }
+ sLocation += dotEscape(fileName());
+ sLocation += ".index";
+
+ return sLocation;
+}
+
+int KMFolderIndex::updateIndex()
+{
+ if (!mAutoCreateIndex)
+ return 0;
+ bool dirty = mDirty;
+ mDirtyTimer->stop();
+ for (unsigned int i=0; !dirty && i<mMsgList.high(); i++)
+ if (mMsgList.at(i))
+ dirty = !mMsgList.at(i)->syncIndexString();
+ if (!dirty) { // Update successful
+ touchFolderIdsFile();
+ return 0;
+ }
+ return writeIndex();
+}
+
+int KMFolderIndex::writeIndex( bool createEmptyIndex )
+{
+ QString tempName;
+ QString indexName;
+ mode_t old_umask;
+ int len;
+ const uchar *buffer = 0;
+
+ indexName = indexLocation();
+ tempName = indexName + ".temp";
+ unlink(QFile::encodeName(tempName));
+
+ // We touch the folder, otherwise the index is regenerated, if KMail is
+ // running, while the clock switches from daylight savings time to normal time
+ utime(QFile::encodeName(location()), 0);
+
+ old_umask = umask(077);
+ FILE *tmpIndexStream = fopen(QFile::encodeName(tempName), "w");
+ umask(old_umask);
+ if (!tmpIndexStream)
+ return errno;
+
+ fprintf(tmpIndexStream, "# KMail-Index V%d\n", INDEX_VERSION);
+
+ // Header
+ Q_UINT32 byteOrder = 0x12345678;
+ Q_UINT32 sizeOfLong = sizeof(long);
+
+ Q_UINT32 header_length = sizeof(byteOrder)+sizeof(sizeOfLong);
+ char pad_char = '\0';
+ fwrite(&pad_char, sizeof(pad_char), 1, tmpIndexStream);
+ fwrite(&header_length, sizeof(header_length), 1, tmpIndexStream);
+
+ // Write header
+ fwrite(&byteOrder, sizeof(byteOrder), 1, tmpIndexStream);
+ fwrite(&sizeOfLong, sizeof(sizeOfLong), 1, tmpIndexStream);
+
+ off_t nho = ftell(tmpIndexStream);
+
+ if ( !createEmptyIndex ) {
+ KMMsgBase* msgBase;
+ for (unsigned int i=0; i<mMsgList.high(); i++)
+ {
+ if (!(msgBase = mMsgList.at(i))) continue;
+ buffer = msgBase->asIndexString(len);
+ fwrite(&len,sizeof(len), 1, tmpIndexStream);
+
+ off_t tmp = ftell(tmpIndexStream);
+ msgBase->setIndexOffset(tmp);
+ msgBase->setIndexLength(len);
+ if(fwrite(buffer, len, 1, tmpIndexStream) != 1)
+ kdDebug(5006) << "Whoa! " << __FILE__ << ":" << __LINE__ << endl;
+ }
+ }
+
+ int fError = ferror( tmpIndexStream );
+ if( fError != 0 ) {
+ fclose( tmpIndexStream );
+ return fError;
+ }
+ if( ( fflush( tmpIndexStream ) != 0 )
+ || ( fsync( fileno( tmpIndexStream ) ) != 0 ) ) {
+ int errNo = errno;
+ fclose( tmpIndexStream );
+ return errNo;
+ }
+ if( fclose( tmpIndexStream ) != 0 )
+ return errno;
+
+ ::rename(QFile::encodeName(tempName), QFile::encodeName(indexName));
+ mHeaderOffset = nho;
+ if (mIndexStream)
+ fclose(mIndexStream);
+
+ if ( createEmptyIndex )
+ return 0;
+
+ mIndexStream = fopen(QFile::encodeName(indexName), "r+"); // index file
+ assert( mIndexStream );
+ fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
+
+ updateIndexStreamPtr();
+
+ writeFolderIdsFile();
+
+ setDirty( false );
+ return 0;
+}
+
+
+bool KMFolderIndex::readIndex()
+{
+ Q_INT32 len;
+ KMMsgInfo* mi;
+
+ assert(mIndexStream != 0);
+ rewind(mIndexStream);
+
+ clearIndex();
+ int version;
+
+ setDirty( false );
+
+ if (!readIndexHeader(&version)) return false;
+
+ mUnreadMsgs = 0;
+ mTotalMsgs = 0;
+ mHeaderOffset = ftell(mIndexStream);
+
+ clearIndex();
+ while (!feof(mIndexStream))
+ {
+ mi = 0;
+ if(version >= 1505) {
+ if(!fread(&len, sizeof(len), 1, mIndexStream))
+ break;
+
+ if (mIndexSwapByteOrder)
+ len = kmail_swap_32(len);
+
+ off_t offs = ftell(mIndexStream);
+ if(fseek(mIndexStream, len, SEEK_CUR))
+ break;
+ mi = new KMMsgInfo(folder(), offs, len);
+ }
+ else
+ {
+ QCString line(MAX_LINE);
+ fgets(line.data(), MAX_LINE, mIndexStream);
+ if (feof(mIndexStream)) break;
+ if (*line.data() == '\0') {
+ fclose(mIndexStream);
+ mIndexStream = 0;
+ clearIndex();
+ return false;
+ }
+ mi = new KMMsgInfo(folder());
+ mi->compat_fromOldIndexString(line, mConvertToUtf8);
+ }
+ if(!mi)
+ break;
+
+ if (mi->isDeleted())
+ {
+ delete mi; // skip messages that are marked as deleted
+ setDirty( true );
+ needsCompact = true; //We have deleted messages - needs to be compacted
+ continue;
+ }
+#ifdef OBSOLETE
+ else if (mi->isNew())
+ {
+ mi->setStatus(KMMsgStatusUnread);
+ mi->setDirty(false);
+ }
+#endif
+ if ((mi->isNew()) || (mi->isUnread()) ||
+ (folder() == kmkernel->outboxFolder()))
+ {
+ ++mUnreadMsgs;
+ if (mUnreadMsgs == 0) ++mUnreadMsgs;
+ }
+ mMsgList.append(mi, false);
+ }
+ if( version < 1505)
+ {
+ mConvertToUtf8 = false;
+ setDirty( true );
+ writeIndex();
+ }
+ mTotalMsgs = mMsgList.count();
+ return true;
+}
+
+
+int KMFolderIndex::count(bool cache) const
+{
+ int res = FolderStorage::count(cache);
+ if (res == -1)
+ res = mMsgList.count();
+ return res;
+}
+
+
+bool KMFolderIndex::readIndexHeader(int *gv)
+{
+ int indexVersion;
+ assert(mIndexStream != 0);
+ mIndexSwapByteOrder = false;
+ mIndexSizeOfLong = sizeof(long);
+
+ int ret = fscanf(mIndexStream, "# KMail-Index V%d\n", &indexVersion);
+ if ( ret == EOF || ret == 0 )
+ return false; // index file has invalid header
+ if(gv)
+ *gv = indexVersion;
+ if (indexVersion < 1505 ) {
+ if(indexVersion == 1503) {
+ kdDebug(5006) << "Converting old index file " << indexLocation() << " to utf-8" << endl;
+ mConvertToUtf8 = true;
+ }
+ return true;
+ } else if (indexVersion == 1505) {
+ } else if (indexVersion < INDEX_VERSION) {
+ kdDebug(5006) << "Index file " << indexLocation() << " is out of date. Re-creating it." << endl;
+ createIndexFromContents();
+ return false;
+ } else if(indexVersion > INDEX_VERSION) {
+ kapp->setOverrideCursor(KCursor::arrowCursor());
+ int r = KMessageBox::questionYesNo(0,
+ i18n(
+ "The mail index for '%1' is from an unknown version of KMail (%2).\n"
+ "This index can be regenerated from your mail folder, but some "
+ "information, including status flags, may be lost. Do you wish "
+ "to downgrade your index file?") .arg(name()) .arg(indexVersion), QString::null, i18n("Downgrade"), i18n("Do Not Downgrade") );
+ kapp->restoreOverrideCursor();
+ if (r == KMessageBox::Yes)
+ createIndexFromContents();
+ return false;
+ }
+ else {
+ // Header
+ Q_UINT32 byteOrder = 0;
+ Q_UINT32 sizeOfLong = sizeof(long); // default
+
+ Q_UINT32 header_length = 0;
+ fseek(mIndexStream, sizeof(char), SEEK_CUR );
+ fread(&header_length, sizeof(header_length), 1, mIndexStream);
+ if (header_length > 0xFFFF)
+ header_length = kmail_swap_32(header_length);
+
+ off_t endOfHeader = ftell(mIndexStream) + header_length;
+
+ bool needs_update = true;
+ // Process available header parts
+ if (header_length >= sizeof(byteOrder))
+ {
+ fread(&byteOrder, sizeof(byteOrder), 1, mIndexStream);
+ mIndexSwapByteOrder = (byteOrder == 0x78563412);
+ header_length -= sizeof(byteOrder);
+
+ if (header_length >= sizeof(sizeOfLong))
+ {
+ fread(&sizeOfLong, sizeof(sizeOfLong), 1, mIndexStream);
+ if (mIndexSwapByteOrder)
+ sizeOfLong = kmail_swap_32(sizeOfLong);
+ mIndexSizeOfLong = sizeOfLong;
+ header_length -= sizeof(sizeOfLong);
+ needs_update = false;
+ }
+ }
+ if (needs_update || mIndexSwapByteOrder || (mIndexSizeOfLong != sizeof(long)))
+ setDirty( true );
+ // Seek to end of header
+ fseek(mIndexStream, endOfHeader, SEEK_SET );
+
+ if (mIndexSwapByteOrder)
+ kdDebug(5006) << "Index File has byte order swapped!" << endl;
+ if (mIndexSizeOfLong != sizeof(long))
+ kdDebug(5006) << "Index File sizeOfLong is " << mIndexSizeOfLong << " while sizeof(long) is " << sizeof(long) << " !" << endl;
+
+ }
+ return true;
+}
+
+
+#ifdef HAVE_MMAP
+bool KMFolderIndex::updateIndexStreamPtr(bool just_close)
+#else
+bool KMFolderIndex::updateIndexStreamPtr(bool)
+#endif
+{
+ // We touch the folder, otherwise the index is regenerated, if KMail is
+ // running, while the clock switches from daylight savings time to normal time
+ utime(QFile::encodeName(location()), 0);
+ utime(QFile::encodeName(indexLocation()), 0);
+ utime(QFile::encodeName( KMMsgDict::getFolderIdsLocation( *this ) ), 0);
+
+ mIndexSwapByteOrder = false;
+#ifdef HAVE_MMAP
+ if(just_close) {
+ if(mIndexStreamPtr)
+ munmap((char *)mIndexStreamPtr, mIndexStreamPtrLength);
+ mIndexStreamPtr = 0;
+ mIndexStreamPtrLength = 0;
+ return true;
+ }
+
+ assert(mIndexStream);
+ struct stat stat_buf;
+ if(fstat(fileno(mIndexStream), &stat_buf) == -1) {
+ if(mIndexStreamPtr)
+ munmap((char *)mIndexStreamPtr, mIndexStreamPtrLength);
+ mIndexStreamPtr = 0;
+ mIndexStreamPtrLength = 0;
+ return false;
+ }
+ if(mIndexStreamPtr)
+ munmap((char *)mIndexStreamPtr, mIndexStreamPtrLength);
+ mIndexStreamPtrLength = stat_buf.st_size;
+ mIndexStreamPtr = (uchar *)mmap(0, mIndexStreamPtrLength, PROT_READ, MAP_SHARED,
+ fileno(mIndexStream), 0);
+ if(mIndexStreamPtr == MAP_FAILED) {
+ mIndexStreamPtr = 0;
+ mIndexStreamPtrLength = 0;
+ return false;
+ }
+#endif
+ return true;
+}
+
+
+KMFolderIndex::IndexStatus KMFolderIndex::indexStatus()
+{
+ QFileInfo contInfo(location());
+ QFileInfo indInfo(indexLocation());
+
+ if (!contInfo.exists()) return KMFolderIndex::IndexOk;
+ if (!indInfo.exists()) return KMFolderIndex::IndexMissing;
+
+ return ( contInfo.lastModified() > indInfo.lastModified() )
+ ? KMFolderIndex::IndexTooOld
+ : KMFolderIndex::IndexOk;
+}
+
+void KMFolderIndex::clearIndex(bool autoDelete, bool syncDict)
+{
+ mMsgList.clear(autoDelete, syncDict);
+}
+
+
+void KMFolderIndex::truncateIndex()
+{
+ if ( mHeaderOffset )
+ truncate(QFile::encodeName(indexLocation()), mHeaderOffset);
+ else
+ // The index file wasn't opened, so we don't know the header offset.
+ // So let's just create a new empty index.
+ writeIndex( true );
+}
+
+void KMFolderIndex::fillMessageDict()
+{
+ open("fillDict");
+ for (unsigned int idx = 0; idx < mMsgList.high(); idx++)
+ if ( mMsgList.at( idx ) )
+ KMMsgDict::mutableInstance()->insert(0, mMsgList.at( idx ), idx);
+ close("fillDict");
+}
+
+
+KMMsgInfo* KMFolderIndex::setIndexEntry( int idx, KMMessage *msg )
+{
+ KMMsgInfo *msgInfo = msg->msgInfo();
+ if ( !msgInfo )
+ msgInfo = new KMMsgInfo( folder() );
+
+ *msgInfo = *msg;
+ mMsgList.set( idx, msgInfo );
+ msg->setMsgInfo( 0 );
+ delete msg;
+ return msgInfo;
+}
+
+void KMFolderIndex::recreateIndex()
+{
+ kapp->setOverrideCursor(KCursor::arrowCursor());
+ KMessageBox::error(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()));
+ kapp->restoreOverrideCursor();
+ createIndexFromContents();
+ readIndex();
+}
+
+
+#include "kmfolderindex.moc"
diff --git a/kmail/kmfolderindex.h b/kmail/kmfolderindex.h
new file mode 100644
index 00000000..bb294b23
--- /dev/null
+++ b/kmail/kmfolderindex.h
@@ -0,0 +1,130 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2000 Don Sanders <sanders@kde.org>
+
+ 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
+*/
+// Virtual base class for mail folder with .*.index style index
+
+#ifndef kmfolderindex_h
+#define kmfolderindex_h
+
+#include "folderstorage.h"
+#include "kmmsglist.h"
+
+/**
+ * @short A FolderStorage with an index for faster access to often used
+ * message properties..
+ *
+ * This class represents a message store which has an index for providing fast
+ * access to often used message properties, namely those displayed in the list
+ * of messages (KMHeaders).
+ *
+ * @author Don Sanders <sanders@kde.org>
+ */
+
+class KMFolderIndex: public FolderStorage
+{
+ Q_OBJECT
+ //TODO:Have to get rid of this friend declaration and add necessary pure
+ //virtuals to kmfolder.h so that KMMsgBase::parent() can be a plain KMFolder
+ //rather than a KMFolderIndex. Need this for database indices.
+ friend class ::KMMsgBase;
+public:
+
+ /** This enum indicates the status of the index file. It's returned by
+ indexStatus().
+ */
+ enum IndexStatus { IndexOk,
+ IndexMissing,
+ IndexTooOld
+ };
+
+ /** Usually a parent is given. But in some cases there is no
+ fitting parent object available. Then the name of the folder
+ is used as the absolute path to the folder file. */
+ KMFolderIndex(KMFolder* folder, const char* name=0);
+ virtual ~KMFolderIndex();
+ virtual int count(bool cache = false) const;
+
+ virtual KMMsgBase* takeIndexEntry( int idx ) { return mMsgList.take( idx ); }
+ virtual KMMsgInfo* setIndexEntry( int idx, KMMessage *msg );
+ virtual void clearIndex(bool autoDelete=true, bool syncDict = false);
+ virtual void truncateIndex();
+
+ virtual const KMMsgBase* getMsgBase(int idx) const { return mMsgList[idx]; }
+ virtual KMMsgBase* getMsgBase(int idx) { return mMsgList[idx]; }
+
+ virtual int find(const KMMsgBase* msg) const { return mMsgList.find((KMMsgBase*)msg); }
+ int find( const KMMessage * msg ) const { return FolderStorage::find( msg ); }
+
+ /** Registered unique serial number for the index file */
+ int serialIndexId() const { return mIndexId; }
+
+ uchar *indexStreamBasePtr() { return mIndexStreamPtr; }
+
+ bool indexSwapByteOrder() { return mIndexSwapByteOrder; }
+ int indexSizeOfLong() { return mIndexSizeOfLong; }
+
+ virtual QString indexLocation() const;
+ virtual int writeIndex( bool createEmptyIndex = false );
+
+ void recreateIndex();
+
+public slots:
+ /** Incrementally update the index if possible else call writeIndex */
+ virtual int updateIndex();
+
+protected:
+ bool readIndex();
+
+ /** Read index header. Called from within readIndex(). */
+ bool readIndexHeader(int *gv=0);
+
+ /** Create index file from messages file and fill the message-info list
+ mMsgList. Returns 0 on success and an errno value (see fopen) on
+ failure. */
+ virtual int createIndexFromContents() = 0;
+
+ 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
+ * when that has been invalidated. */
+ virtual void fillMessageDict();
+
+ /** table of contents file */
+ FILE* mIndexStream;
+ /** list of index entries or messages */
+ KMMsgList mMsgList;
+
+ /** offset of header of index file */
+ off_t mHeaderOffset;
+
+ uchar *mIndexStreamPtr;
+ int mIndexStreamPtrLength, mIndexId;
+ bool mIndexSwapByteOrder; // Index file was written with swapped byte order
+ int mIndexSizeOfLong; // Index file was written with longs of this size
+};
+
+#endif /*kmfolderindex_h*/
diff --git a/kmail/kmfoldermaildir.cpp b/kmail/kmfoldermaildir.cpp
new file mode 100644
index 00000000..f13bb676
--- /dev/null
+++ b/kmail/kmfoldermaildir.cpp
@@ -0,0 +1,1172 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+// kmfoldermaildir.cpp
+// Author: Kurt Granroth <granroth@kde.org>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <qdir.h>
+#include <qregexp.h>
+
+#include <libkdepim/kfileio.h>
+#include "kmfoldermaildir.h"
+#include "kmfoldermgr.h"
+#include "kmfolder.h"
+#include "undostack.h"
+#include "maildirjob.h"
+#include "kcursorsaver.h"
+#include "jobscheduler.h"
+using KMail::MaildirJob;
+#include "compactionjob.h"
+#include "kmmsgdict.h"
+#include "util.h"
+
+#include <kapplication.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kstaticdeleter.h>
+#include <kmessagebox.h>
+#include <kdirsize.h>
+
+#include <dirent.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <assert.h>
+#include <limits.h>
+#include <ctype.h>
+#include <fcntl.h>
+
+#ifndef MAX_LINE
+#define MAX_LINE 4096
+#endif
+#ifndef INIT_MSGS
+#define INIT_MSGS 8
+#endif
+
+// define the static member
+QValueList<KMFolderMaildir::DirSizeJobQueueEntry> KMFolderMaildir::s_DirSizeJobQueue;
+
+//-----------------------------------------------------------------------------
+KMFolderMaildir::KMFolderMaildir(KMFolder* folder, const char* name)
+ : KMFolderIndex(folder, name), mCurrentlyCheckingFolderSize(false)
+{
+
+}
+
+
+//-----------------------------------------------------------------------------
+KMFolderMaildir::~KMFolderMaildir()
+{
+ if (mOpenCount>0) close("~foldermaildir", true);
+ if (kmkernel->undoStack()) kmkernel->undoStack()->folderDestroyed( folder() );
+}
+
+//-----------------------------------------------------------------------------
+int KMFolderMaildir::canAccess()
+{
+
+ assert(!folder()->name().isEmpty());
+
+ QString sBadFolderName;
+ if (access(QFile::encodeName(location()), R_OK | W_OK | X_OK) != 0) {
+ sBadFolderName = location();
+ } else if (access(QFile::encodeName(location() + "/new"), R_OK | W_OK | X_OK) != 0) {
+ sBadFolderName = location() + "/new";
+ } else if (access(QFile::encodeName(location() + "/cur"), R_OK | W_OK | X_OK) != 0) {
+ sBadFolderName = location() + "/cur";
+ } else if (access(QFile::encodeName(location() + "/tmp"), R_OK | W_OK | X_OK) != 0) {
+ sBadFolderName = location() + "/tmp";
+ }
+
+ if ( !sBadFolderName.isEmpty() ) {
+ int nRetVal = QFile::exists(sBadFolderName) ? EPERM : ENOENT;
+ KCursorSaver idle(KBusyPtr::idle());
+ if ( nRetVal == ENOENT )
+ KMessageBox::sorry(0, i18n("Error opening %1; this folder is missing.")
+ .arg(sBadFolderName));
+ else
+ KMessageBox::sorry(0, i18n("Error opening %1; either this is not a valid "
+ "maildir folder, or you do not have sufficient access permissions.")
+ .arg(sBadFolderName));
+ return nRetVal;
+ }
+
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+int KMFolderMaildir::open(const char *)
+{
+ int rc = 0;
+
+ mOpenCount++;
+ kmkernel->jobScheduler()->notifyOpeningFolder( folder() );
+
+ if (mOpenCount > 1) return 0; // already open
+
+ assert(!folder()->name().isEmpty());
+
+ rc = canAccess();
+ if ( rc != 0 ) {
+ return rc;
+ }
+
+ if (!folder()->path().isEmpty())
+ {
+ if (KMFolderIndex::IndexOk != indexStatus()) // test if contents file has changed
+ {
+ QString str;
+ mIndexStream = 0;
+ str = i18n("Folder `%1' changed; recreating index.")
+ .arg(name());
+ emit statusMsg(str);
+ } else {
+ mIndexStream = fopen(QFile::encodeName(indexLocation()), "r+"); // index file
+ if ( mIndexStream ) {
+ fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
+ updateIndexStreamPtr();
+ }
+ }
+
+ if (!mIndexStream)
+ rc = createIndexFromContents();
+ else
+ readIndex();
+ }
+ else
+ {
+ mAutoCreateIndex = false;
+ rc = createIndexFromContents();
+ }
+
+ mChanged = false;
+
+ //readConfig();
+
+ return rc;
+}
+
+
+//-----------------------------------------------------------------------------
+int KMFolderMaildir::createMaildirFolders( const QString & folderPath )
+{
+ // Make sure that neither a new, cur or tmp subfolder exists already.
+ QFileInfo dirinfo;
+ dirinfo.setFile( folderPath + "/new" );
+ if ( dirinfo.exists() ) return EEXIST;
+ dirinfo.setFile( folderPath + "/cur" );
+ if ( dirinfo.exists() ) return EEXIST;
+ dirinfo.setFile( folderPath + "/tmp" );
+ if ( dirinfo.exists() ) return EEXIST;
+
+ // create the maildir directory structure
+ if ( ::mkdir( QFile::encodeName( folderPath ), S_IRWXU ) > 0 ) {
+ kdDebug(5006) << "Could not create folder " << folderPath << endl;
+ return errno;
+ }
+ if ( ::mkdir( QFile::encodeName( folderPath + "/new" ), S_IRWXU ) > 0 ) {
+ kdDebug(5006) << "Could not create folder " << folderPath << "/new" << endl;
+ return errno;
+ }
+ if ( ::mkdir( QFile::encodeName( folderPath + "/cur" ), S_IRWXU ) > 0 ) {
+ kdDebug(5006) << "Could not create folder " << folderPath << "/cur" << endl;
+ return errno;
+ }
+ if ( ::mkdir( QFile::encodeName( folderPath + "/tmp" ), S_IRWXU ) > 0 ) {
+ kdDebug(5006) << "Could not create folder " << folderPath << "/tmp" << endl;
+ return errno;
+ }
+
+ return 0; // no error
+}
+
+//-----------------------------------------------------------------------------
+int KMFolderMaildir::create()
+{
+ int rc;
+ int old_umask;
+
+ assert(!folder()->name().isEmpty());
+ assert(mOpenCount == 0);
+
+ rc = createMaildirFolders( location() );
+ if ( rc != 0 )
+ return rc;
+
+ // FIXME no path == no index? - till
+ if (!folder()->path().isEmpty())
+ {
+ old_umask = umask(077);
+ mIndexStream = fopen(QFile::encodeName(indexLocation()), "w+"); //sven; open RW
+ updateIndexStreamPtr(true);
+ umask(old_umask);
+
+ if (!mIndexStream) return errno;
+ fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
+ }
+ else
+ {
+ mAutoCreateIndex = false;
+ }
+
+ mOpenCount++;
+ mChanged = false;
+
+ rc = writeIndex();
+ return rc;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFolderMaildir::reallyDoClose(const char* owner)
+{
+ if (mAutoCreateIndex)
+ {
+ updateIndex();
+ writeConfig();
+ }
+
+ mMsgList.clear(true);
+
+ if (mIndexStream) {
+ fclose(mIndexStream);
+ updateIndexStreamPtr(true);
+ }
+
+ mOpenCount = 0;
+ mIndexStream = 0;
+ mUnreadMsgs = -1;
+
+ mMsgList.reset(INIT_MSGS);
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderMaildir::sync()
+{
+ if (mOpenCount > 0)
+ if (!mIndexStream || fsync(fileno(mIndexStream))) {
+ kmkernel->emergencyExit( i18n("Could not sync maildir folder.") );
+ }
+}
+
+//-----------------------------------------------------------------------------
+int KMFolderMaildir::expungeContents()
+{
+ // nuke all messages in this folder now
+ QDir d(location() + "/new");
+ // d.setFilter(QDir::Files); coolo: QFile::remove returns false for non-files
+ QStringList files(d.entryList());
+ QStringList::ConstIterator it(files.begin());
+ for ( ; it != files.end(); ++it)
+ QFile::remove(d.filePath(*it));
+
+ d.setPath(location() + "/cur");
+ files = d.entryList();
+ for (it = files.begin(); it != files.end(); ++it)
+ QFile::remove(d.filePath(*it));
+
+ return 0;
+}
+
+int KMFolderMaildir::compact( unsigned int startIndex, int nbMessages, const QStringList& entryList, bool& done )
+{
+ QString subdirNew(location() + "/new/");
+ QString subdirCur(location() + "/cur/");
+
+ unsigned int stopIndex = nbMessages == -1 ? mMsgList.count() :
+ QMIN( mMsgList.count(), startIndex + nbMessages );
+ //kdDebug(5006) << "KMFolderMaildir: compacting from " << startIndex << " to " << stopIndex << endl;
+ for(unsigned int idx = startIndex; idx < stopIndex; ++idx) {
+ KMMsgInfo* mi = (KMMsgInfo*)mMsgList.at(idx);
+ if (!mi)
+ continue;
+
+ QString filename(mi->fileName());
+ if (filename.isEmpty())
+ continue;
+
+ // first, make sure this isn't in the 'new' subdir
+ if ( entryList.contains( filename ) )
+ moveInternal(subdirNew + filename, subdirCur + filename, mi);
+
+ // construct a valid filename. if it's already valid, then
+ // nothing happens
+ filename = constructValidFileName( filename, mi->status() );
+
+ // if the name changed, then we need to update the actual filename
+ if (filename != mi->fileName())
+ {
+ moveInternal(subdirCur + mi->fileName(), subdirCur + filename, mi);
+ mi->setFileName(filename);
+ setDirty( true );
+ }
+
+#if 0
+ // we can't have any New messages at this point
+ if (mi->isNew())
+ {
+ mi->setStatus(KMMsgStatusUnread);
+ setDirty( true );
+ }
+#endif
+ }
+ done = ( stopIndex == mMsgList.count() );
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+int KMFolderMaildir::compact( bool silent )
+{
+ KMail::MaildirCompactionJob* job = new KMail::MaildirCompactionJob( folder(), true /*immediate*/ );
+ int rc = job->executeNow( silent );
+ // Note that job autodeletes itself.
+ return rc;
+}
+
+//-------------------------------------------------------------
+FolderJob*
+KMFolderMaildir::doCreateJob( KMMessage *msg, FolderJob::JobType jt,
+ KMFolder *folder, QString, const AttachmentStrategy* ) const
+{
+ MaildirJob *job = new MaildirJob( msg, jt, folder );
+ job->setParentFolder( this );
+ return job;
+}
+
+//-------------------------------------------------------------
+FolderJob*
+KMFolderMaildir::doCreateJob( QPtrList<KMMessage>& msgList, const QString& sets,
+ FolderJob::JobType jt, KMFolder *folder ) const
+{
+ MaildirJob *job = new MaildirJob( msgList, sets, jt, folder );
+ job->setParentFolder( this );
+ return job;
+}
+
+//-------------------------------------------------------------
+int KMFolderMaildir::addMsg(KMMessage* aMsg, int* index_return)
+{
+ if (!canAddMsgNow(aMsg, index_return)) return 0;
+ return addMsgInternal( aMsg, index_return );
+}
+
+//-------------------------------------------------------------
+int KMFolderMaildir::addMsgInternal( KMMessage* aMsg, int* index_return,
+ bool stripUid )
+{
+/*
+QFile fileD0( "testdat_xx-kmfoldermaildir-0" );
+if( fileD0.open( IO_WriteOnly ) ) {
+ QDataStream ds( &fileD0 );
+ ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() );
+ fileD0.close(); // If data is 0 we just create a zero length file.
+}
+*/
+ long len;
+ unsigned long size;
+ KMFolder* msgParent;
+ QCString msgText;
+ int idx(-1);
+ int rc;
+
+ // take message out of the folder it is currently in, if any
+ msgParent = aMsg->parent();
+ if (msgParent)
+ {
+ if (msgParent==folder() && !kmkernel->folderIsDraftOrOutbox(folder()))
+ return 0;
+
+ idx = msgParent->find(aMsg);
+ msgParent->getMsg( idx );
+ }
+
+ aMsg->setStatusFields();
+ if (aMsg->headerField("Content-Type").isEmpty()) // This might be added by
+ aMsg->removeHeaderField("Content-Type"); // the line above
+
+
+ const QString uidHeader = aMsg->headerField( "X-UID" );
+ if ( !uidHeader.isEmpty() && stripUid )
+ aMsg->removeHeaderField( "X-UID" );
+
+ msgText = aMsg->asString(); // TODO use asDwString instead
+ len = msgText.length();
+
+ // Re-add the uid so that the take can make use of it, in case the
+ // message is currently in an imap folder
+ if ( !uidHeader.isEmpty() && stripUid )
+ aMsg->setHeaderField( "X-UID", uidHeader );
+
+ if (len <= 0)
+ {
+ kdDebug(5006) << "Message added to folder `" << name() << "' contains no data. Ignoring it." << endl;
+ return 0;
+ }
+
+ // make sure the filename has the correct extension
+ QString filename = constructValidFileName( aMsg->fileName(), aMsg->status() );
+
+ QString tmp_file(location() + "/tmp/");
+ tmp_file += filename;
+
+ if (!KPIM::kCStringToFile(msgText, tmp_file, false, false, false))
+ kmkernel->emergencyExit( i18n("Message could not be added to the folder, possibly disk space is low.") );
+
+ QFile file(tmp_file);
+ size = msgText.length();
+
+ KMFolderOpener openThis(folder(), "maildir");
+ rc = openThis.openResult();
+ if (rc)
+ {
+ kdDebug(5006) << "KMFolderMaildir::addMsg-open: " << rc << " of folder: " << label() << endl;
+ return rc;
+ }
+
+ // now move the file to the correct location
+ QString new_loc(location() + "/cur/");
+ new_loc += filename;
+ if (moveInternal(tmp_file, new_loc, filename, aMsg->status()).isNull())
+ {
+ file.remove();
+ return -1;
+ }
+
+ if (msgParent && idx >= 0)
+ msgParent->take(idx);
+
+ // just to be sure it does not end up in the index
+ if ( stripUid ) aMsg->setUID( 0 );
+
+ if (filename != aMsg->fileName())
+ aMsg->setFileName(filename);
+
+ if (aMsg->isUnread() || aMsg->isNew() || folder() == kmkernel->outboxFolder())
+ {
+ if (mUnreadMsgs == -1)
+ mUnreadMsgs = 1;
+ else
+ ++mUnreadMsgs;
+ if ( !mQuiet ) {
+ kdDebug( 5006 ) << "FolderStorage::msgStatusChanged" << endl;
+ emit numUnreadMsgsChanged( folder() );
+ }else{
+ if ( !mEmitChangedTimer->isActive() ) {
+// kdDebug( 5006 )<< "QuietTimer started" << endl;
+ mEmitChangedTimer->start( 3000 );
+ }
+ mChanged = true;
+ }
+ }
+ ++mTotalMsgs;
+ mSize = -1;
+
+ if ( aMsg->attachmentState() == KMMsgAttachmentUnknown &&
+ aMsg->readyToShow() )
+ aMsg->updateAttachmentState();
+
+ // store information about the position in the folder file in the message
+ aMsg->setParent(folder());
+ aMsg->setMsgSize(size);
+ idx = mMsgList.append( &aMsg->toMsgBase(), mExportsSernums );
+ if (aMsg->getMsgSerNum() <= 0)
+ aMsg->setMsgSerNum();
+ else
+ replaceMsgSerNum( aMsg->getMsgSerNum(), &aMsg->toMsgBase(), idx );
+
+ // write index entry if desired
+ if (mAutoCreateIndex)
+ {
+ assert(mIndexStream != 0);
+ clearerr(mIndexStream);
+ fseek(mIndexStream, 0, SEEK_END);
+ off_t revert = ftell(mIndexStream);
+
+ int len;
+ KMMsgBase * mb = &aMsg->toMsgBase();
+ 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);
+ int error = ferror(mIndexStream);
+
+ if ( mExportsSernums )
+ error |= appendToFolderIdsFile( idx );
+
+ if (error) {
+ kdDebug(5006) << "Error: Could not add message to folder (No space left on device?)" << endl;
+ if (ftell(mIndexStream) > revert) {
+ kdDebug(5006) << "Undoing changes" << endl;
+ truncate( QFile::encodeName(indexLocation()), revert );
+ }
+ kmkernel->emergencyExit(i18n("KMFolderMaildir::addMsg: abnormally terminating to prevent data loss."));
+ // exit(1); // don't ever use exit(), use the above!
+
+ /* This code may not be 100% reliable
+ bool busy = kmkernel->kbp()->isBusy();
+ if (busy) kmkernel->kbp()->idle();
+ KMessageBox::sorry(0,
+ i18n("Unable to add message to folder.\n"
+ "(No space left on device or insufficient quota?)\n"
+ "Free space and sufficient quota are required to continue safely."));
+ if (busy) kmkernel->kbp()->busy();
+ */
+ return error;
+ }
+ }
+
+ if (index_return)
+ *index_return = idx;
+
+ emitMsgAddedSignals(idx);
+ needsCompact = true;
+
+/*
+QFile fileD1( "testdat_xx-kmfoldermaildir-1" );
+if( fileD1.open( IO_WriteOnly ) ) {
+ QDataStream ds( &fileD1 );
+ ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() );
+ fileD1.close(); // If data is 0 we just create a zero length file.
+}
+*/
+ return 0;
+}
+
+KMMessage* KMFolderMaildir::readMsg(int idx)
+{
+ KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];
+ KMMessage *msg = new KMMessage(*mi);
+ msg->setMsgInfo( mi ); // remember the KMMsgInfo object to that we can restore it when the KMMessage object is no longer needed
+ mMsgList.set(idx,&msg->toMsgBase()); // done now so that the serial number can be computed
+ msg->setComplete( true );
+ msg->fromDwString(getDwString(idx));
+ return msg;
+}
+
+DwString KMFolderMaildir::getDwString(int idx)
+{
+ KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];
+ QString abs_file(location() + "/cur/");
+ abs_file += mi->fileName();
+ QFileInfo fi( abs_file );
+
+ if (fi.exists() && fi.isFile() && fi.isWritable() && fi.size() > 0)
+ {
+ FILE* stream = fopen(QFile::encodeName(abs_file), "r+");
+ if (stream) {
+ size_t msgSize = fi.size();
+ char* msgText = new char[ msgSize + 1 ];
+ fread(msgText, msgSize, 1, stream);
+ fclose( stream );
+ msgText[msgSize] = '\0';
+ size_t newMsgSize = KMail::Util::crlf2lf( msgText, msgSize );
+ DwString str;
+ // the DwString takes possession of msgText, so we must not delete it
+ str.TakeBuffer( msgText, msgSize + 1, 0, newMsgSize );
+ return str;
+ }
+ }
+ kdDebug(5006) << "Could not open file r+ " << abs_file << endl;
+ return DwString();
+}
+
+
+void KMFolderMaildir::readFileHeaderIntern(const QString& dir, const QString& file, KMMsgStatus status)
+{
+ // we keep our current directory to restore it later
+ char path_buffer[PATH_MAX];
+ if(!::getcwd(path_buffer, PATH_MAX - 1))
+ return;
+
+ ::chdir(QFile::encodeName(dir));
+
+ // messages in the 'cur' directory are Read by default.. but may
+ // actually be some other state (but not New)
+ if (status == KMMsgStatusRead)
+ {
+ if (file.find(":2,") == -1)
+ status = KMMsgStatusUnread;
+ else if (file.right(5) == ":2,RS")
+ status |= KMMsgStatusReplied;
+ }
+
+ // open the file and get a pointer to it
+ QFile f(file);
+ if ( f.open( IO_ReadOnly ) == false ) {
+ kdWarning(5006) << "The file '" << QFile::encodeName(dir) << "/" << file
+ << "' could not be opened for reading the message. "
+ "Please check ownership and permissions."
+ << endl;
+ return;
+ }
+
+ char line[MAX_LINE];
+ bool atEof = false;
+ bool inHeader = true;
+ QCString *lastStr = 0;
+
+ QCString dateStr, fromStr, toStr, subjStr;
+ QCString xmarkStr, replyToIdStr, msgIdStr, referencesStr;
+ QCString statusStr, replyToAuxIdStr, uidStr;
+ QCString contentTypeStr, charset;
+
+ // iterate through this file until done
+ while (!atEof)
+ {
+ // if the end of the file has been reached or if there was an error
+ if ( f.atEnd() || ( -1 == f.readLine(line, MAX_LINE) ) )
+ atEof = true;
+
+ // are we done with this file? if so, compile our info and store
+ // it in a KMMsgInfo object
+ if (atEof || !inHeader)
+ {
+ msgIdStr = msgIdStr.stripWhiteSpace();
+ if( !msgIdStr.isEmpty() ) {
+ int rightAngle;
+ rightAngle = msgIdStr.find( '>' );
+ if( rightAngle != -1 )
+ msgIdStr.truncate( rightAngle + 1 );
+ }
+
+ replyToIdStr = replyToIdStr.stripWhiteSpace();
+ if( !replyToIdStr.isEmpty() ) {
+ int rightAngle;
+ rightAngle = replyToIdStr.find( '>' );
+ if( rightAngle != -1 )
+ replyToIdStr.truncate( rightAngle + 1 );
+ }
+
+ referencesStr = referencesStr.stripWhiteSpace();
+ if( !referencesStr.isEmpty() ) {
+ int leftAngle, rightAngle;
+ leftAngle = referencesStr.findRev( '<' );
+ if( ( leftAngle != -1 )
+ && ( replyToIdStr.isEmpty() || ( replyToIdStr[0] != '<' ) ) ) {
+ // use the last reference, instead of missing In-Reply-To
+ replyToIdStr = referencesStr.mid( leftAngle );
+ }
+
+ // find second last reference
+ leftAngle = referencesStr.findRev( '<', leftAngle - 1 );
+ if( leftAngle != -1 )
+ referencesStr = referencesStr.mid( leftAngle );
+ rightAngle = referencesStr.findRev( '>' );
+ if( rightAngle != -1 )
+ referencesStr.truncate( rightAngle + 1 );
+
+ // Store the second to last reference in the replyToAuxIdStr
+ // It is a good candidate for threading the message below if the
+ // message In-Reply-To points to is not kept in this folder,
+ // but e.g. in an Outbox
+ replyToAuxIdStr = referencesStr;
+ rightAngle = referencesStr.find( '>' );
+ if( rightAngle != -1 )
+ replyToAuxIdStr.truncate( rightAngle + 1 );
+ }
+
+ statusStr = statusStr.stripWhiteSpace();
+ if (!statusStr.isEmpty())
+ {
+ // only handle those states not determined by the file suffix
+ if (statusStr[0] == 'S')
+ status |= KMMsgStatusSent;
+ else if (statusStr[0] == 'F')
+ status |= KMMsgStatusForwarded;
+ else if (statusStr[0] == 'D')
+ status |= KMMsgStatusDeleted;
+ else if (statusStr[0] == 'Q')
+ status |= KMMsgStatusQueued;
+ else if (statusStr[0] == 'G')
+ status |= KMMsgStatusFlag;
+ }
+
+ contentTypeStr = contentTypeStr.stripWhiteSpace();
+ charset = "";
+ if ( !contentTypeStr.isEmpty() )
+ {
+ int cidx = contentTypeStr.find( "charset=" );
+ if ( cidx != -1 ) {
+ charset = contentTypeStr.mid( cidx + 8 );
+ if ( !charset.isEmpty() && ( charset[0] == '"' ) ) {
+ charset = charset.mid( 1 );
+ }
+ cidx = 0;
+ while ( (unsigned int) cidx < charset.length() ) {
+ if ( charset[cidx] == '"' || ( !isalnum(charset[cidx]) &&
+ charset[cidx] != '-' && charset[cidx] != '_' ) )
+ break;
+ ++cidx;
+ }
+ charset.truncate( cidx );
+ // kdDebug() << "KMFolderMaildir::readFileHeaderIntern() charset found: " <<
+ // charset << " from " << contentTypeStr << endl;
+ }
+ }
+
+ KMMsgInfo *mi = new KMMsgInfo(folder());
+ mi->init( subjStr.stripWhiteSpace(),
+ fromStr.stripWhiteSpace(),
+ toStr.stripWhiteSpace(),
+ 0, status,
+ xmarkStr.stripWhiteSpace(),
+ replyToIdStr, replyToAuxIdStr, msgIdStr,
+ file.local8Bit(),
+ KMMsgEncryptionStateUnknown, KMMsgSignatureStateUnknown,
+ KMMsgMDNStateUnknown, charset, f.size() );
+
+ dateStr = dateStr.stripWhiteSpace();
+ if (!dateStr.isEmpty())
+ mi->setDate(dateStr);
+ if ( !uidStr.isEmpty() )
+ mi->setUID( uidStr.toULong() );
+ mi->setDirty(false);
+ mMsgList.append( mi, mExportsSernums );
+
+ // if this is a New file and is in 'new', we move it to 'cur'
+ if (status & KMMsgStatusNew)
+ {
+ QString newDir(location() + "/new/");
+ QString curDir(location() + "/cur/");
+ moveInternal(newDir + file, curDir + file, mi);
+ }
+
+ break;
+ }
+
+ // Is this a long header line?
+ if (inHeader && line[0] == '\t' || line[0] == ' ')
+ {
+ int i = 0;
+ while (line[i] == '\t' || line[i] == ' ')
+ i++;
+ if (line[i] < ' ' && line[i] > 0)
+ inHeader = false;
+ else
+ if (lastStr)
+ *lastStr += line + i;
+ }
+ else
+ lastStr = 0;
+
+ if (inHeader && (line[0] == '\n' || line[0] == '\r'))
+ inHeader = false;
+ if (!inHeader)
+ continue;
+
+ if (strncasecmp(line, "Date:", 5) == 0)
+ {
+ dateStr = QCString(line+5);
+ lastStr = &dateStr;
+ }
+ else if (strncasecmp(line, "From:", 5) == 0)
+ {
+ fromStr = QCString(line+5);
+ lastStr = &fromStr;
+ }
+ else if (strncasecmp(line, "To:", 3) == 0)
+ {
+ toStr = QCString(line+3);
+ lastStr = &toStr;
+ }
+ else if (strncasecmp(line, "Subject:", 8) == 0)
+ {
+ subjStr = QCString(line+8);
+ lastStr = &subjStr;
+ }
+ else if (strncasecmp(line, "References:", 11) == 0)
+ {
+ referencesStr = QCString(line+11);
+ lastStr = &referencesStr;
+ }
+ else if (strncasecmp(line, "Message-Id:", 11) == 0)
+ {
+ msgIdStr = QCString(line+11);
+ lastStr = &msgIdStr;
+ }
+ else if (strncasecmp(line, "X-KMail-Mark:", 13) == 0)
+ {
+ xmarkStr = QCString(line+13);
+ }
+ else if (strncasecmp(line, "X-Status:", 9) == 0)
+ {
+ statusStr = QCString(line+9);
+ }
+ else if (strncasecmp(line, "In-Reply-To:", 12) == 0)
+ {
+ replyToIdStr = QCString(line+12);
+ lastStr = &replyToIdStr;
+ }
+ else if (strncasecmp(line, "X-UID:", 6) == 0)
+ {
+ uidStr = QCString(line+6);
+ lastStr = &uidStr;
+ }
+ else if (strncasecmp(line, "Content-Type:", 13) == 0)
+ {
+ contentTypeStr = QCString(line+13);
+ lastStr = &contentTypeStr;
+ }
+
+ }
+
+ if (status & KMMsgStatusNew || status & KMMsgStatusUnread ||
+ (folder() == kmkernel->outboxFolder()))
+ {
+ mUnreadMsgs++;
+ if (mUnreadMsgs == 0) ++mUnreadMsgs;
+ }
+
+ ::chdir(path_buffer);
+}
+
+int KMFolderMaildir::createIndexFromContents()
+{
+ mUnreadMsgs = 0;
+
+ mMsgList.clear(true);
+ mMsgList.reset(INIT_MSGS);
+
+ mChanged = false;
+
+ // first, we make sure that all the directories are here as they
+ // should be
+ QFileInfo dirinfo;
+
+ dirinfo.setFile(location() + "/new");
+ if (!dirinfo.exists() || !dirinfo.isDir())
+ {
+ kdDebug(5006) << "Directory " << location() << "/new doesn't exist or is a file"<< endl;
+ return 1;
+ }
+ QDir newDir(location() + "/new");
+ newDir.setFilter(QDir::Files);
+
+ dirinfo.setFile(location() + "/cur");
+ if (!dirinfo.exists() || !dirinfo.isDir())
+ {
+ kdDebug(5006) << "Directory " << location() << "/cur doesn't exist or is a file"<< endl;
+ return 1;
+ }
+ QDir curDir(location() + "/cur");
+ curDir.setFilter(QDir::Files);
+
+ // then, we look for all the 'cur' files
+ const QFileInfoList *list = curDir.entryInfoList();
+ QFileInfoListIterator it(*list);
+ QFileInfo *fi;
+
+ while ((fi = it.current()))
+ {
+ readFileHeaderIntern(curDir.path(), fi->fileName(), KMMsgStatusRead);
+ ++it;
+ }
+
+ // then, we look for all the 'new' files
+ list = newDir.entryInfoList();
+ it = *list;
+
+ while ((fi=it.current()))
+ {
+ readFileHeaderIntern(newDir.path(), fi->fileName(), KMMsgStatusNew);
+ ++it;
+ }
+
+ if ( autoCreateIndex() ) {
+ emit statusMsg(i18n("Writing index file"));
+ writeIndex();
+ }
+ else mHeaderOffset = 0;
+
+ correctUnreadMsgsCount();
+
+ if (kmkernel->outboxFolder() == folder() && count() > 0)
+ KMessageBox::information(0, i18n("Your outbox contains messages which were "
+ "most-likely not created by KMail;\nplease remove them from there if you "
+ "do not want KMail to send them."));
+
+ needsCompact = true;
+
+ invalidateFolder();
+ return 0;
+}
+
+KMFolderIndex::IndexStatus KMFolderMaildir::indexStatus()
+{
+ QFileInfo new_info(location() + "/new");
+ QFileInfo cur_info(location() + "/cur");
+ QFileInfo index_info(indexLocation());
+
+ if (!index_info.exists())
+ return KMFolderIndex::IndexMissing;
+
+ // Check whether the directories are more than 5 seconds newer than the index
+ // file. The 5 seconds are added to reduce the number of false alerts due
+ // to slightly out of sync clocks of the NFS server and the local machine.
+ return ((new_info.lastModified() > index_info.lastModified().addSecs(5)) ||
+ (cur_info.lastModified() > index_info.lastModified().addSecs(5)))
+ ? KMFolderIndex::IndexTooOld
+ : KMFolderIndex::IndexOk;
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderMaildir::removeMsg(int idx, bool)
+{
+ KMMsgBase* msg = mMsgList[idx];
+ if (!msg || !msg->fileName()) return;
+
+ removeFile(msg->fileName());
+
+ KMFolderIndex::removeMsg(idx);
+}
+
+//-----------------------------------------------------------------------------
+KMMessage* KMFolderMaildir::take(int idx)
+{
+ // first, we do the high-level stuff.. then delete later
+ KMMessage *msg = KMFolderIndex::take(idx);
+
+ if (!msg || !msg->fileName()) {
+ return 0;
+ }
+
+ if ( removeFile(msg->fileName()) ) {
+ return msg;
+ } else {
+ return 0;
+ }
+}
+
+// static
+bool KMFolderMaildir::removeFile( const QString & folderPath,
+ const QString & filename )
+{
+ // we need to look in both 'new' and 'cur' since it's possible to
+ // delete a message before the folder is compacted. Since the file
+ // naming and moving is done in ::compact, we can't assume any
+ // location at this point.
+ QCString abs_file( QFile::encodeName( folderPath + "/cur/" + filename ) );
+ if ( ::unlink( abs_file ) == 0 )
+ return true;
+
+ if ( errno == ENOENT ) { // doesn't exist
+ abs_file = QFile::encodeName( folderPath + "/new/" + filename );
+ if ( ::unlink( abs_file ) == 0 )
+ return true;
+ }
+
+ kdDebug(5006) << "Can't delete " << abs_file << " " << perror << endl;
+ return false;
+}
+
+bool KMFolderMaildir::removeFile( const QString & filename )
+{
+ return removeFile( location(), filename );
+}
+
+#include <sys/types.h>
+#include <dirent.h>
+static bool removeDirAndContentsRecursively( const QString & path )
+{
+ bool success = true;
+
+ QDir d;
+ d.setPath( path );
+ d.setFilter( QDir::Files | QDir::Dirs | QDir::Hidden | QDir::NoSymLinks );
+
+ const QFileInfoList *list = d.entryInfoList();
+ QFileInfoListIterator it( *list );
+ QFileInfo *fi;
+
+ while ( (fi = it.current()) != 0 ) {
+ if( fi->isDir() ) {
+ if ( fi->fileName() != "." && fi->fileName() != ".." )
+ success = success && removeDirAndContentsRecursively( fi->absFilePath() );
+ } else {
+ success = success && d.remove( fi->absFilePath() );
+ }
+ ++it;
+ }
+
+ if ( success ) {
+ success = success && d.rmdir( path ); // nuke ourselves, we should be empty now
+ }
+ return success;
+}
+
+//-----------------------------------------------------------------------------
+int KMFolderMaildir::removeContents()
+{
+ // NOTE: Don' use KIO::netaccess, it has reentrancy problems and multiple
+ // mailchecks going on trigger them, when removing dirs
+ if ( !removeDirAndContentsRecursively( location() + "/new/" ) ) return 1;
+ if ( !removeDirAndContentsRecursively( location() + "/cur/" ) ) return 1;
+ if ( !removeDirAndContentsRecursively( location() + "/tmp/" ) ) return 1;
+ /* The subdirs are removed now. Check if there is anything else in the dir
+ * and only if not delete the dir itself. The user could have data stored
+ * that would otherwise be deleted. */
+ QDir dir(location());
+ if ( dir.count() == 2 ) { // only . and ..
+ if ( !removeDirAndContentsRecursively( location() ), 0 ) return 1;
+ }
+ return 0;
+}
+
+static QRegExp *suffix_regex = 0;
+static KStaticDeleter<QRegExp> suffix_regex_sd;
+
+//-----------------------------------------------------------------------------
+// static
+QString KMFolderMaildir::constructValidFileName( const QString & filename,
+ KMMsgStatus status )
+{
+ QString aFileName( filename );
+
+ if (aFileName.isEmpty())
+ {
+ aFileName.sprintf("%ld.%d.", (long)time(0), getpid());
+ aFileName += KApplication::randomString(5);
+ }
+
+ if (!suffix_regex)
+ suffix_regex_sd.setObject(suffix_regex, new QRegExp(":2,?R?S?$"));
+
+ aFileName.truncate(aFileName.findRev(*suffix_regex));
+
+ // only add status suffix if the message is neither new nor unread
+ if (! ((status & KMMsgStatusNew) || (status & KMMsgStatusUnread)) )
+ {
+ QString suffix( ":2," );
+ if (status & KMMsgStatusReplied)
+ suffix += "RS";
+ else
+ suffix += "S";
+ aFileName += suffix;
+ }
+
+ return aFileName;
+}
+
+//-----------------------------------------------------------------------------
+QString KMFolderMaildir::moveInternal(const QString& oldLoc, const QString& newLoc, KMMsgInfo *mi)
+{
+ QString filename(mi->fileName());
+ QString ret(moveInternal(oldLoc, newLoc, filename, mi->status()));
+
+ if (filename != mi->fileName())
+ mi->setFileName(filename);
+
+ return ret;
+}
+
+//-----------------------------------------------------------------------------
+QString KMFolderMaildir::moveInternal(const QString& oldLoc, const QString& newLoc, QString& aFileName, KMMsgStatus status)
+{
+ QString dest(newLoc);
+ // make sure that our destination filename doesn't already exist
+ while (QFile::exists(dest))
+ {
+ aFileName = constructValidFileName( QString(), status );
+
+ QFileInfo fi(dest);
+ dest = fi.dirPath(true) + "/" + aFileName;
+ setDirty( true );
+ }
+
+ QDir d;
+ if (d.rename(oldLoc, dest) == false)
+ return QString::null;
+ else
+ return dest;
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderMaildir::msgStatusChanged(const KMMsgStatus oldStatus,
+ const KMMsgStatus newStatus, int idx)
+{
+ // if the status of any message changes, then we need to compact
+ needsCompact = true;
+
+ KMFolderIndex::msgStatusChanged(oldStatus, newStatus, idx);
+}
+
+/*virtual*/
+Q_INT64 KMFolderMaildir::doFolderSize() const
+{
+ if ( mCurrentlyCheckingFolderSize )
+ {
+ return -1;
+ }
+ mCurrentlyCheckingFolderSize = true;
+
+ KFileItemList list;
+ KFileItem *item = 0;
+ item = new KFileItem( S_IFDIR, -1, location() + "/cur" );
+ list.append( item );
+ item = new KFileItem( S_IFDIR, -1, location() + "/new" );
+ list.append( item );
+ item = new KFileItem( S_IFDIR, -1, location() + "/tmp" );
+ list.append( item );
+ s_DirSizeJobQueue.append(
+ qMakePair( QGuardedPtr<const KMFolderMaildir>( this ), list ) );
+
+ // if there's only one entry in the queue then we can start
+ // a dirSizeJob right away
+ if ( s_DirSizeJobQueue.size() == 1 )
+ {
+ //kdDebug(5006) << k_funcinfo << "Starting dirSizeJob for folder "
+ // << location() << endl;
+ KDirSize* job = KDirSize::dirSizeJob( list );
+ connect( job, SIGNAL( result( KIO::Job* ) ),
+ this, SLOT( slotDirSizeJobResult( KIO::Job* ) ) );
+ }
+
+ return -1;
+}
+
+void KMFolderMaildir::slotDirSizeJobResult( KIO::Job* job )
+{
+ mCurrentlyCheckingFolderSize = false;
+ KDirSize * dirsize = dynamic_cast<KDirSize*>( job );
+ if ( dirsize && ! dirsize->error() )
+ {
+ mSize = dirsize->totalSize();
+ //kdDebug(5006) << k_funcinfo << "dirSizeJob completed. Folder "
+ // << location() << " has size " << mSize << endl;
+ emit folderSizeChanged();
+ }
+ // remove the completed job from the queue
+ s_DirSizeJobQueue.pop_front();
+
+ // process the next entry in the queue
+ while ( s_DirSizeJobQueue.size() > 0 )
+ {
+ DirSizeJobQueueEntry entry = s_DirSizeJobQueue.first();
+ // check whether the entry is valid, i.e. whether the folder still exists
+ if ( entry.first )
+ {
+ // start the next dirSizeJob
+ //kdDebug(5006) << k_funcinfo << "Starting dirSizeJob for folder "
+ // << entry.first->location() << endl;
+ KDirSize* job = KDirSize::dirSizeJob( entry.second );
+ connect( job, SIGNAL( result( KIO::Job* ) ),
+ entry.first, SLOT( slotDirSizeJobResult( KIO::Job* ) ) );
+ break;
+ }
+ else
+ {
+ // remove the invalid entry from the queue
+ s_DirSizeJobQueue.pop_front();
+ }
+ }
+}
+
+#include "kmfoldermaildir.moc"
diff --git a/kmail/kmfoldermaildir.h b/kmail/kmfoldermaildir.h
new file mode 100644
index 00000000..8e09a6f3
--- /dev/null
+++ b/kmail/kmfoldermaildir.h
@@ -0,0 +1,169 @@
+#ifndef kmfoldermaildir_h
+#define kmfoldermaildir_h
+
+#include "kmfolderindex.h"
+
+#include <kfileitem.h>
+
+#include <qguardedptr.h>
+
+class KMFolderMaildir;
+namespace KMail {
+ class FolderJob;
+ class MaildirJob;
+ class AttachmentStrategy;
+}
+namespace KIO {
+ class Job;
+}
+
+using KMail::FolderJob;
+using KMail::MaildirJob;
+using KMail::AttachmentStrategy;
+
+class KMFolderMaildir : public KMFolderIndex
+{
+ Q_OBJECT
+ friend class ::KMail::MaildirJob;
+public:
+ /** Usually a parent is given. But in some cases there is no
+ fitting parent object available. Then the name of the folder
+ is used as the absolute path to the folder file. */
+ KMFolderMaildir(KMFolder* folder, const char* name=0);
+ virtual ~KMFolderMaildir();
+
+ /** Returns the type of this folder */
+ virtual KMFolderType folderType() const { return KMFolderTypeMaildir; }
+
+ /** Read a message and return it as a string */
+ virtual DwString getDwString(int idx);
+
+ /** Detach message from this folder. Usable to call addMsg() afterwards.
+ Loads the message if it is not loaded up to now. */
+ virtual KMMessage* take(int idx);
+
+ /** Add the given message to the folder. Usually the message
+ is added at the end of the folder. Returns zero on success and
+ an errno error code on failure. The index of the new message
+ is stored in index_return if given.
+ Please note that the message is added as is to the folder and the folder
+ takes ownership of the message (deleting it in the destructor).*/
+ virtual int addMsg(KMMessage* msg, int* index_return = 0);
+
+ /** Remove (first occurrence of) given message from the folder. */
+ virtual void removeMsg(int i, bool imapQuiet = FALSE);
+ virtual void removeMsg(QPtrList<KMMessage> msgList, bool imapQuiet = FALSE)
+ { return KMFolderIndex::removeMsg(msgList, imapQuiet); }
+
+ // Called by KMMsgBase::setStatus when status of a message has changed
+ // required to keep the number unread messages variable current.
+ virtual void msgStatusChanged( const KMMsgStatus oldStatus,
+ const KMMsgStatus newStatus,
+ int idx);
+
+ /** Open folder for access.
+ Does nothing if the folder is already opened. To reopen a folder
+ call close() first.
+ Returns zero on success and an error code equal to the c-library
+ fopen call otherwise (errno). */
+ virtual int open(const char *owner);
+
+ virtual int canAccess();
+
+ /** fsync buffers to disk */
+ virtual void sync();
+
+ /** Close folder. If force is TRUE the files are closed even if
+ others still use it (e.g. other mail reader windows). */
+ virtual void reallyDoClose(const char *owner);
+
+ /** Create the necessary folders for a maildir folder. Usually you will
+ want to use create() instead.
+
+ @param folderPath the full path of the folder as returned by location()
+ @return 0 on success and an error code (cf. man 3 errno) otherwise
+ */
+ static int createMaildirFolders( const QString & folderPath );
+
+ static QString constructValidFileName( const QString & filename = QString(),
+ KMMsgStatus status = KMMsgStatusNew );
+
+ static bool removeFile( const QString & folderPath,
+ const QString & filename );
+
+ /** @reimpl */
+ virtual int create();
+
+ /** Remove some deleted messages from the folder. Returns zero on success
+ and an errno on failure. This is only for use from MaildirCompactionJob. */
+ int compact( unsigned int startIndex, int nbMessages, const QStringList& entryList, bool& done );
+
+ /** Remove deleted messages from the folder. Returns zero on success
+ and an errno on failure. */
+ virtual int compact( bool silent );
+
+ /** Is the folder read-only? */
+ virtual bool isReadOnly() const { return false; }
+
+ /** reimp */
+ virtual Q_INT64 doFolderSize() const;
+
+protected:
+ virtual FolderJob* doCreateJob( KMMessage *msg, FolderJob::JobType jt, KMFolder *folder,
+ QString partSpecifier, const AttachmentStrategy *as ) const;
+ virtual FolderJob* doCreateJob( QPtrList<KMMessage>& msgList, const QString& sets,
+ FolderJob::JobType jt, KMFolder *folder ) const;
+ /** Load message from file and store it at given index. Returns 0
+ on failure. */
+ virtual KMMessage* readMsg(int idx);
+
+ /** Called by KMFolder::remove() to delete the actual contents.
+ At the time of the call the folder has already been closed, and
+ the various index files deleted. Returns 0 on success. */
+ virtual int removeContents();
+
+ /** Called by KMFolder::expunge() to delete the actual contents.
+ At the time of the call the folder has already been closed, and
+ the various index files deleted. Returns 0 on success. */
+ virtual int expungeContents();
+
+ /** Create index file from messages file and fill the message-info list
+ mMsgList. Returns 0 on success and an errno value (see fopen) on
+ failure. */
+ virtual int createIndexFromContents();
+
+ /**
+ * Internal helper called by addMsg. If stripUid is true it will remove any
+ * uid headers and uid index setting before writing. KMFolderCachedImap needs this
+ * but can't do it itself, since the final take() which removes the original mail
+ * from the source folder, in moves, needs to happen after the adding, for safety
+ * reasons, but needs the uid, in case the source folder was an imap folder, to
+ * delete the original.
+ * TODO: Avoid this by moving the take() out of the addMsg() methods and moving it
+ * into the KMMoveCommand, where it can safely happen at a much higher level. */
+ int addMsgInternal( KMMessage* msg, int* index_return = 0, bool stripUid=false );
+
+private slots:
+ void slotDirSizeJobResult( KIO::Job* job );
+
+private:
+ void readFileHeaderIntern(const QString& dir, const QString& file, KMMsgStatus status);
+ QString moveInternal(const QString& oldLoc, const QString& newLoc, KMMsgInfo* mi);
+ QString moveInternal(const QString& oldLoc, const QString& newLoc, QString& aFileName, KMMsgStatus status);
+ bool removeFile(const QString& filename);
+
+ /** Tests whether the contents of this folder is newer than the index.
+ Returns IndexTooOld if the index is older than the contents.
+ Returns IndexMissing if there is no index.
+ Returns IndexOk if the index is not older than the contents.
+ */
+ virtual IndexStatus indexStatus();
+
+ typedef QPair<QGuardedPtr<const KMFolderMaildir>,KFileItemList> DirSizeJobQueueEntry;
+ static QValueList<DirSizeJobQueueEntry> s_DirSizeJobQueue;
+
+ QStrList mIdxToFileList;
+ int mIdxCount;
+ mutable bool mCurrentlyCheckingFolderSize;
+};
+#endif /*kmfoldermaildir_h*/
diff --git a/kmail/kmfoldermbox.cpp b/kmail/kmfoldermbox.cpp
new file mode 100644
index 00000000..92f466a7
--- /dev/null
+++ b/kmail/kmfoldermbox.cpp
@@ -0,0 +1,1278 @@
+/* -*- c-basic-offset: 2 -*-
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ *
+ * 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 <config.h>
+#include <qfileinfo.h>
+#include <qregexp.h>
+
+#include "kmfoldermbox.h"
+#include "folderstorage.h"
+#include "kmfolder.h"
+#include "kmkernel.h"
+#include "kmmsgdict.h"
+#include "undostack.h"
+#include "kcursorsaver.h"
+#include "jobscheduler.h"
+#include "compactionjob.h"
+#include "util.h"
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <knotifyclient.h>
+#include <kprocess.h>
+#include <kconfig.h>
+
+#include <ctype.h>
+#include <stdio.h>
+#include <errno.h>
+#include <assert.h>
+#include <ctype.h>
+#include <unistd.h>
+
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include "broadcaststatus.h"
+using KPIM::BroadcastStatus;
+
+#ifndef MAX_LINE
+#define MAX_LINE 4096
+#endif
+#ifndef INIT_MSGS
+#define INIT_MSGS 8
+#endif
+
+// Regular expression to find the line that seperates messages in a mail
+// folder:
+#define MSG_SEPERATOR_START "From "
+#define MSG_SEPERATOR_START_LEN (sizeof(MSG_SEPERATOR_START) - 1)
+#define MSG_SEPERATOR_REGEX "^From .*[0-9][0-9]:[0-9][0-9]"
+
+
+//-----------------------------------------------------------------------------
+KMFolderMbox::KMFolderMbox(KMFolder* folder, const char* name)
+ : KMFolderIndex(folder, name)
+{
+ mStream = 0;
+ mFilesLocked = false;
+ mReadOnly = false;
+ mLockType = lock_none;
+}
+
+
+//-----------------------------------------------------------------------------
+KMFolderMbox::~KMFolderMbox()
+{
+ if (mOpenCount>0)
+ close("~kmfoldermbox", true);
+ if (kmkernel->undoStack())
+ kmkernel->undoStack()->folderDestroyed( folder() );
+}
+
+//-----------------------------------------------------------------------------
+int KMFolderMbox::open(const char *owner)
+{
+ int rc = 0;
+
+ mOpenCount++;
+ kmkernel->jobScheduler()->notifyOpeningFolder( folder() );
+
+ if (mOpenCount > 1) return 0; // already open
+
+ assert(!folder()->name().isEmpty());
+
+ mFilesLocked = false;
+ mStream = fopen(QFile::encodeName(location()), "r+"); // messages file
+ if (!mStream)
+ {
+ KNotifyClient::event( 0, "warning",
+ i18n("Cannot open file \"%1\":\n%2").arg(location()).arg(strerror(errno)));
+ kdDebug(5006) << "Cannot open folder `" << location() << "': " << strerror(errno) << endl;
+ mOpenCount = 0;
+ return errno;
+ }
+
+ lock();
+
+ if (!folder()->path().isEmpty())
+ {
+ KMFolderIndex::IndexStatus index_status = indexStatus();
+ // test if index file exists and is up-to-date
+ if (KMFolderIndex::IndexOk != index_status)
+ {
+ // only show a warning if the index file exists, otherwise it can be
+ // silently regenerated
+ if (KMFolderIndex::IndexTooOld == index_status) {
+ QString msg = i18n("<qt><p>The index of folder '%2' seems "
+ "to be out of date. To prevent message "
+ "corruption the index will be "
+ "regenerated. As a result deleted "
+ "messages might reappear and status "
+ "flags might be lost.</p>"
+ "<p>Please read the corresponding entry "
+ "in the <a href=\"%1\">FAQ section of the manual "
+ "of KMail</a> for "
+ "information about how to prevent this "
+ "problem from happening again.</p></qt>")
+ .arg("help:/kmail/faq.html#faq-index-regeneration")
+ .arg(name());
+ // When KMail is starting up we have to show a non-blocking message
+ // box so that the initialization can continue. We don't show a
+ // queued message box when KMail isn't starting up because queued
+ // message boxes don't have a "Don't ask again" checkbox.
+ if (kmkernel->startingUp())
+ {
+ KConfigGroup configGroup( KMKernel::config(), "Notification Messages" );
+ bool showMessage =
+ configGroup.readBoolEntry( "showIndexRegenerationMessage", true );
+ if (showMessage)
+ KMessageBox::queuedMessageBox( 0, KMessageBox::Information,
+ msg, i18n("Index Out of Date"),
+ KMessageBox::AllowLink );
+ }
+ else
+ {
+ KCursorSaver idle(KBusyPtr::idle());
+ KMessageBox::information( 0, msg, i18n("Index Out of Date"),
+ "showIndexRegenerationMessage",
+ KMessageBox::AllowLink );
+ }
+ }
+ QString str;
+ mIndexStream = 0;
+ str = i18n("Folder `%1' changed. Recreating index.")
+ .arg(name());
+ emit statusMsg(str);
+ } else {
+ mIndexStream = fopen(QFile::encodeName(indexLocation()), "r+"); // index file
+ if ( mIndexStream ) {
+ fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
+ updateIndexStreamPtr();
+ }
+ }
+
+ if (!mIndexStream)
+ rc = createIndexFromContents();
+ else
+ if (!readIndex())
+ rc = createIndexFromContents();
+ }
+ else
+ {
+ mAutoCreateIndex = false;
+ rc = createIndexFromContents();
+ }
+
+ mChanged = false;
+
+ fcntl(fileno(mStream), F_SETFD, FD_CLOEXEC);
+ if (mIndexStream)
+ fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
+
+ return rc;
+}
+
+//----------------------------------------------------------------------------
+int KMFolderMbox::canAccess()
+{
+ assert(!folder()->name().isEmpty());
+
+ if (access(QFile::encodeName(location()), R_OK | W_OK) != 0) {
+ kdDebug(5006) << "KMFolderMbox::access call to access function failed" << endl;
+ return 1;
+ }
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+int KMFolderMbox::create()
+{
+ int rc;
+ int old_umask;
+
+ assert(!folder()->name().isEmpty());
+ assert(mOpenCount == 0);
+
+ kdDebug(5006) << "Creating folder " << name() << endl;
+ if (access(QFile::encodeName(location()), F_OK) == 0) {
+ kdDebug(5006) << "KMFolderMbox::create call to access function failed." << endl;
+ kdDebug(5006) << "File:: " << endl;
+ kdDebug(5006) << "Error " << endl;
+ return EEXIST;
+ }
+
+ old_umask = umask(077);
+ mStream = fopen(QFile::encodeName(location()), "w+"); //sven; open RW
+ umask(old_umask);
+
+ if (!mStream) return errno;
+
+ fcntl(fileno(mStream), F_SETFD, FD_CLOEXEC);
+
+ if (!folder()->path().isEmpty())
+ {
+ old_umask = umask(077);
+ mIndexStream = fopen(QFile::encodeName(indexLocation()), "w+"); //sven; open RW
+ updateIndexStreamPtr(true);
+ umask(old_umask);
+
+ if (!mIndexStream) return errno;
+ fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
+ }
+ else
+ {
+ mAutoCreateIndex = false;
+ }
+
+ mOpenCount++;
+ mChanged = false;
+
+ rc = writeIndex();
+ if (!rc) lock();
+ return rc;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFolderMbox::reallyDoClose(const char* owner)
+{
+ if (mAutoCreateIndex)
+ {
+ if (KMFolderIndex::IndexOk != indexStatus()) {
+ kdDebug(5006) << "Critical error: " << location() <<
+ " has been modified by an external application while KMail was running." << endl;
+ // exit(1); backed out due to broken nfs
+ }
+
+ updateIndex();
+ writeConfig();
+ }
+
+ if (!noContent()) {
+ if (mStream) unlock();
+ mMsgList.clear(true);
+
+ if (mStream) fclose(mStream);
+ if (mIndexStream) {
+ fclose(mIndexStream);
+ updateIndexStreamPtr(true);
+ }
+ }
+ mOpenCount = 0;
+ mStream = 0;
+ mIndexStream = 0;
+ mFilesLocked = false;
+ mUnreadMsgs = -1;
+
+ mMsgList.reset(INIT_MSGS);
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderMbox::sync()
+{
+ if (mOpenCount > 0)
+ if (!mStream || fsync(fileno(mStream)) ||
+ !mIndexStream || fsync(fileno(mIndexStream))) {
+ kmkernel->emergencyExit( i18n("Could not sync index file <b>%1</b>: %2").arg( indexLocation() ).arg(errno ? QString::fromLocal8Bit(strerror(errno)) : i18n("Internal error. Please copy down the details and report a bug.")));
+ }
+}
+
+//-----------------------------------------------------------------------------
+int KMFolderMbox::lock()
+{
+ int rc;
+ struct flock fl;
+ fl.l_type=F_WRLCK;
+ fl.l_whence=0;
+ fl.l_start=0;
+ fl.l_len=0;
+ fl.l_pid=-1;
+ QCString cmd_str;
+ assert(mStream != 0);
+ mFilesLocked = false;
+ mReadOnly = false;
+
+ switch( mLockType )
+ {
+ case FCNTL:
+ rc = fcntl(fileno(mStream), F_SETLKW, &fl);
+
+ if (rc < 0)
+ {
+ kdDebug(5006) << "Cannot lock folder `" << location() << "': "
+ << strerror(errno) << " (" << errno << ")" << endl;
+ mReadOnly = true;
+ return errno;
+ }
+
+ if (mIndexStream)
+ {
+ rc = fcntl(fileno(mIndexStream), F_SETLK, &fl);
+
+ if (rc < 0)
+ {
+ kdDebug(5006) << "Cannot lock index of folder `" << location() << "': "
+ << strerror(errno) << " (" << errno << ")" << endl;
+ rc = errno;
+ fl.l_type = F_UNLCK;
+ /*rc =*/ fcntl(fileno(mIndexStream), F_SETLK, &fl);
+ mReadOnly = true;
+ return rc;
+ }
+ }
+ break;
+
+ case procmail_lockfile:
+ cmd_str = "lockfile -l20 -r5 ";
+ if (!mProcmailLockFileName.isEmpty())
+ cmd_str += QFile::encodeName(KProcess::quote(mProcmailLockFileName));
+ else
+ cmd_str += QFile::encodeName(KProcess::quote(location() + ".lock"));
+
+ rc = system( cmd_str.data() );
+ if( rc != 0 )
+ {
+ kdDebug(5006) << "Cannot lock folder `" << location() << "': "
+ << strerror(rc) << " (" << rc << ")" << endl;
+ mReadOnly = true;
+ return rc;
+ }
+ if( mIndexStream )
+ {
+ cmd_str = "lockfile -l20 -r5 " + QFile::encodeName(KProcess::quote(indexLocation() + ".lock"));
+ rc = system( cmd_str.data() );
+ if( rc != 0 )
+ {
+ kdDebug(5006) << "Cannot lock index of folder `" << location() << "': "
+ << strerror(rc) << " (" << rc << ")" << endl;
+ mReadOnly = true;
+ return rc;
+ }
+ }
+ break;
+
+ case mutt_dotlock:
+ cmd_str = "mutt_dotlock " + QFile::encodeName(KProcess::quote(location()));
+ rc = system( cmd_str.data() );
+ if( rc != 0 )
+ {
+ kdDebug(5006) << "Cannot lock folder `" << location() << "': "
+ << strerror(rc) << " (" << rc << ")" << endl;
+ mReadOnly = true;
+ return rc;
+ }
+ if( mIndexStream )
+ {
+ cmd_str = "mutt_dotlock " + QFile::encodeName(KProcess::quote(indexLocation()));
+ rc = system( cmd_str.data() );
+ if( rc != 0 )
+ {
+ kdDebug(5006) << "Cannot lock index of folder `" << location() << "': "
+ << strerror(rc) << " (" << rc << ")" << endl;
+ mReadOnly = true;
+ return rc;
+ }
+ }
+ break;
+
+ case mutt_dotlock_privileged:
+ cmd_str = "mutt_dotlock -p " + QFile::encodeName(KProcess::quote(location()));
+ rc = system( cmd_str.data() );
+ if( rc != 0 )
+ {
+ kdDebug(5006) << "Cannot lock folder `" << location() << "': "
+ << strerror(rc) << " (" << rc << ")" << endl;
+ mReadOnly = true;
+ return rc;
+ }
+ if( mIndexStream )
+ {
+ cmd_str = "mutt_dotlock -p " + QFile::encodeName(KProcess::quote(indexLocation()));
+ rc = system( cmd_str.data() );
+ if( rc != 0 )
+ {
+ kdDebug(5006) << "Cannot lock index of folder `" << location() << "': "
+ << strerror(rc) << " (" << rc << ")" << endl;
+ mReadOnly = true;
+ return rc;
+ }
+ }
+ break;
+
+ case lock_none:
+ default:
+ break;
+ }
+
+
+ mFilesLocked = true;
+ return 0;
+}
+
+//-------------------------------------------------------------
+FolderJob*
+KMFolderMbox::doCreateJob( KMMessage *msg, FolderJob::JobType jt,
+ KMFolder *folder, QString, const AttachmentStrategy* ) const
+{
+ MboxJob *job = new MboxJob( msg, jt, folder );
+ job->setParent( this );
+ return job;
+}
+
+//-------------------------------------------------------------
+FolderJob*
+KMFolderMbox::doCreateJob( QPtrList<KMMessage>& msgList, const QString& sets,
+ FolderJob::JobType jt, KMFolder *folder ) const
+{
+ MboxJob *job = new MboxJob( msgList, sets, jt, folder );
+ job->setParent( this );
+ return job;
+}
+
+//-----------------------------------------------------------------------------
+int KMFolderMbox::unlock()
+{
+ int rc;
+ struct flock fl;
+ fl.l_type=F_UNLCK;
+ fl.l_whence=0;
+ fl.l_start=0;
+ fl.l_len=0;
+ QCString cmd_str;
+
+ assert(mStream != 0);
+ mFilesLocked = false;
+
+ switch( mLockType )
+ {
+ case FCNTL:
+ if (mIndexStream) fcntl(fileno(mIndexStream), F_SETLK, &fl);
+ fcntl(fileno(mStream), F_SETLK, &fl);
+ rc = errno;
+ break;
+
+ case procmail_lockfile:
+ cmd_str = "rm -f ";
+ if (!mProcmailLockFileName.isEmpty())
+ cmd_str += QFile::encodeName(KProcess::quote(mProcmailLockFileName));
+ else
+ cmd_str += QFile::encodeName(KProcess::quote(location() + ".lock"));
+
+ rc = system( cmd_str.data() );
+ if( mIndexStream )
+ {
+ cmd_str = "rm -f " + QFile::encodeName(KProcess::quote(indexLocation() + ".lock"));
+ rc = system( cmd_str.data() );
+ }
+ break;
+
+ case mutt_dotlock:
+ cmd_str = "mutt_dotlock -u " + QFile::encodeName(KProcess::quote(location()));
+ rc = system( cmd_str.data() );
+ if( mIndexStream )
+ {
+ cmd_str = "mutt_dotlock -u " + QFile::encodeName(KProcess::quote(indexLocation()));
+ rc = system( cmd_str.data() );
+ }
+ break;
+
+ case mutt_dotlock_privileged:
+ cmd_str = "mutt_dotlock -p -u " + QFile::encodeName(KProcess::quote(location()));
+ rc = system( cmd_str.data() );
+ if( mIndexStream )
+ {
+ cmd_str = "mutt_dotlock -p -u " + QFile::encodeName(KProcess::quote(indexLocation()));
+ rc = system( cmd_str.data() );
+ }
+ break;
+
+ case lock_none:
+ default:
+ rc = 0;
+ break;
+ }
+
+ return rc;
+}
+
+
+//-----------------------------------------------------------------------------
+KMFolderIndex::IndexStatus KMFolderMbox::indexStatus()
+{
+ QFileInfo contInfo(location());
+ QFileInfo indInfo(indexLocation());
+
+ if (!contInfo.exists()) return KMFolderIndex::IndexOk;
+ if (!indInfo.exists()) return KMFolderIndex::IndexMissing;
+
+ // Check whether the mbox file is more than 5 seconds newer than the index
+ // file. The 5 seconds are added to reduce the number of false alerts due
+ // to slightly out of sync clocks of the NFS server and the local machine.
+ return ( contInfo.lastModified() > indInfo.lastModified().addSecs(5) )
+ ? KMFolderIndex::IndexTooOld
+ : KMFolderIndex::IndexOk;
+}
+
+
+//-----------------------------------------------------------------------------
+int KMFolderMbox::createIndexFromContents()
+{
+ char line[MAX_LINE];
+ char status[8], xstatus[8];
+ QCString subjStr, dateStr, fromStr, toStr, xmarkStr, *lastStr=0;
+ QCString replyToIdStr, replyToAuxIdStr, referencesStr, msgIdStr;
+ QCString sizeServerStr, uidStr;
+ QCString contentTypeStr, charset;
+ bool atEof = false;
+ bool inHeader = true;
+ KMMsgInfo* mi;
+ QString msgStr;
+ QRegExp regexp(MSG_SEPERATOR_REGEX);
+ int i, num, numStatus;
+ short needStatus;
+
+ assert(mStream != 0);
+ rewind(mStream);
+
+ mMsgList.clear();
+
+ num = -1;
+ numStatus= 11;
+ off_t offs = 0;
+ size_t size = 0;
+ dateStr = "";
+ fromStr = "";
+ toStr = "";
+ subjStr = "";
+ *status = '\0';
+ *xstatus = '\0';
+ xmarkStr = "";
+ replyToIdStr = "";
+ replyToAuxIdStr = "";
+ referencesStr = "";
+ msgIdStr = "";
+ needStatus = 3;
+ size_t sizeServer = 0;
+ ulong uid = 0;
+
+
+ while (!atEof)
+ {
+ off_t pos = ftell(mStream);
+ if (!fgets(line, MAX_LINE, mStream)) atEof = true;
+
+ if (atEof ||
+ (memcmp(line, MSG_SEPERATOR_START, MSG_SEPERATOR_START_LEN)==0 &&
+ regexp.search(line) >= 0))
+ {
+ size = pos - offs;
+ pos = ftell(mStream);
+
+ if (num >= 0)
+ {
+ if (numStatus <= 0)
+ {
+ msgStr = i18n("Creating index file: one message done", "Creating index file: %n messages done", num);
+ emit statusMsg(msgStr);
+ numStatus = 10;
+ }
+
+ if (size > 0)
+ {
+ msgIdStr = msgIdStr.stripWhiteSpace();
+ if( !msgIdStr.isEmpty() ) {
+ int rightAngle;
+ rightAngle = msgIdStr.find( '>' );
+ if( rightAngle != -1 )
+ msgIdStr.truncate( rightAngle + 1 );
+ }
+
+ replyToIdStr = replyToIdStr.stripWhiteSpace();
+ if( !replyToIdStr.isEmpty() ) {
+ int rightAngle;
+ rightAngle = replyToIdStr.find( '>' );
+ if( rightAngle != -1 )
+ replyToIdStr.truncate( rightAngle + 1 );
+ }
+
+ referencesStr = referencesStr.stripWhiteSpace();
+ if( !referencesStr.isEmpty() ) {
+ int leftAngle, rightAngle;
+ leftAngle = referencesStr.findRev( '<' );
+ if( ( leftAngle != -1 )
+ && ( replyToIdStr.isEmpty() || ( replyToIdStr[0] != '<' ) ) ) {
+ // use the last reference, instead of missing In-Reply-To
+ replyToIdStr = referencesStr.mid( leftAngle );
+ }
+
+ // find second last reference
+ leftAngle = referencesStr.findRev( '<', leftAngle - 1 );
+ if( leftAngle != -1 )
+ referencesStr = referencesStr.mid( leftAngle );
+ rightAngle = referencesStr.findRev( '>' );
+ if( rightAngle != -1 )
+ referencesStr.truncate( rightAngle + 1 );
+
+ // Store the second to last reference in the replyToAuxIdStr
+ // It is a good candidate for threading the message below if the
+ // message In-Reply-To points to is not kept in this folder,
+ // but e.g. in an Outbox
+ replyToAuxIdStr = referencesStr;
+ rightAngle = referencesStr.find( '>' );
+ if( rightAngle != -1 )
+ replyToAuxIdStr.truncate( rightAngle + 1 );
+ }
+
+ contentTypeStr = contentTypeStr.stripWhiteSpace();
+ charset = "";
+ if ( !contentTypeStr.isEmpty() )
+ {
+ int cidx = contentTypeStr.find( "charset=" );
+ if ( cidx != -1 ) {
+ charset = contentTypeStr.mid( cidx + 8 );
+ if ( !charset.isEmpty() && ( charset[0] == '"' ) ) {
+ charset = charset.mid( 1 );
+ }
+ cidx = 0;
+ while ( (unsigned int) cidx < charset.length() ) {
+ if ( charset[cidx] == '"' || ( !isalnum(charset[cidx]) &&
+ charset[cidx] != '-' && charset[cidx] != '_' ) )
+ break;
+ ++cidx;
+ }
+ charset.truncate( cidx );
+ // kdDebug() << "KMFolderMaildir::readFileHeaderIntern() charset found: " <<
+ // charset << " from " << contentTypeStr << endl;
+ }
+ }
+
+ mi = new KMMsgInfo(folder());
+ mi->init( subjStr.stripWhiteSpace(),
+ fromStr.stripWhiteSpace(),
+ toStr.stripWhiteSpace(),
+ 0, KMMsgStatusNew,
+ xmarkStr.stripWhiteSpace(),
+ replyToIdStr, replyToAuxIdStr, msgIdStr,
+ KMMsgEncryptionStateUnknown, KMMsgSignatureStateUnknown,
+ KMMsgMDNStateUnknown, charset, offs, size, sizeServer, uid );
+ mi->setStatus(status, xstatus);
+ mi->setDate( dateStr.stripWhiteSpace() );
+ mi->setDirty(false);
+ mMsgList.append(mi, mExportsSernums );
+
+ *status = '\0';
+ *xstatus = '\0';
+ needStatus = 3;
+ xmarkStr = "";
+ replyToIdStr = "";
+ replyToAuxIdStr = "";
+ referencesStr = "";
+ msgIdStr = "";
+ dateStr = "";
+ fromStr = "";
+ subjStr = "";
+ sizeServer = 0;
+ uid = 0;
+ }
+ else num--,numStatus++;
+ }
+
+ offs = ftell(mStream);
+ num++;
+ numStatus--;
+ inHeader = true;
+ continue;
+ }
+ // Is this a long header line?
+ if (inHeader && (line[0]=='\t' || line[0]==' '))
+ {
+ i = 0;
+ while (line [i]=='\t' || line [i]==' ') i++;
+ if (line [i] < ' ' && line [i]>0) inHeader = false;
+ else if (lastStr) *lastStr += line + i;
+ }
+ else lastStr = 0;
+
+ if (inHeader && (line [0]=='\n' || line [0]=='\r'))
+ inHeader = false;
+ if (!inHeader) continue;
+
+ /* -sanders Make all messages read when auto-recreating index */
+ /* Reverted, as it breaks reading the sent mail status, for example.
+ -till */
+ if ((needStatus & 1) && strncasecmp(line, "Status:", 7) == 0)
+ {
+ for(i=0; i<4 && line[i+8] > ' '; i++)
+ status[i] = line[i+8];
+ status[i] = '\0';
+ needStatus &= ~1;
+ }
+ else if ((needStatus & 2) && strncasecmp(line, "X-Status:", 9)==0)
+ {
+ for(i=0; i<4 && line[i+10] > ' '; i++)
+ xstatus[i] = line[i+10];
+ xstatus[i] = '\0';
+ needStatus &= ~2;
+ }
+ else if (strncasecmp(line,"X-KMail-Mark:",13)==0)
+ xmarkStr = QCString(line+13);
+ else if (strncasecmp(line,"In-Reply-To:",12)==0) {
+ replyToIdStr = QCString(line+12);
+ lastStr = &replyToIdStr;
+ }
+ else if (strncasecmp(line,"References:",11)==0) {
+ referencesStr = QCString(line+11);
+ lastStr = &referencesStr;
+ }
+ else if (strncasecmp(line,"Message-Id:",11)==0) {
+ msgIdStr = QCString(line+11);
+ lastStr = &msgIdStr;
+ }
+ else if (strncasecmp(line,"Date:",5)==0)
+ {
+ dateStr = QCString(line+5);
+ lastStr = &dateStr;
+ }
+ else if (strncasecmp(line,"From:", 5)==0)
+ {
+ fromStr = QCString(line+5);
+ lastStr = &fromStr;
+ }
+ else if (strncasecmp(line,"To:", 3)==0)
+ {
+ toStr = QCString(line+3);
+ lastStr = &toStr;
+ }
+ else if (strncasecmp(line,"Subject:",8)==0)
+ {
+ subjStr = QCString(line+8);
+ lastStr = &subjStr;
+ }
+ else if (strncasecmp(line,"X-Length:",9)==0)
+ {
+ sizeServerStr = QCString(line+9);
+ sizeServer = sizeServerStr.toULong();
+ lastStr = &sizeServerStr;
+ }
+ else if (strncasecmp(line,"X-UID:",6)==0)
+ {
+ uidStr = QCString(line+6);
+ uid = uidStr.toULong();
+ lastStr = &uidStr;
+ }
+ else if (strncasecmp(line, "Content-Type:", 13) == 0)
+ {
+ contentTypeStr = QCString(line+13);
+ lastStr = &contentTypeStr;
+ }
+ }
+
+ if (mAutoCreateIndex)
+ {
+ emit statusMsg(i18n("Writing index file"));
+ writeIndex();
+ }
+ else mHeaderOffset = 0;
+
+ correctUnreadMsgsCount();
+
+ if (kmkernel->outboxFolder() == folder() && count() > 0)
+ KMessageBox::queuedMessageBox(0, KMessageBox::Information,
+ i18n("Your outbox contains messages which were "
+ "most-likely not created by KMail;\nplease remove them from there if you "
+ "do not want KMail to send them."));
+
+ invalidateFolder();
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+KMMessage* KMFolderMbox::readMsg(int idx)
+{
+ KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];
+
+ assert(mi!=0 && !mi->isMessage());
+ assert(mStream != 0);
+
+ KMMessage *msg = new KMMessage(*mi);
+ msg->setMsgInfo( mi ); // remember the KMMsgInfo object to that we can restore it when the KMMessage object is no longer needed
+ mMsgList.set(idx,&msg->toMsgBase()); // done now so that the serial number can be computed
+ msg->fromDwString(getDwString(idx));
+ return msg;
+}
+
+
+#define STRDIM(x) (sizeof(x)/sizeof(*x)-1)
+// performs (\n|^)>{n}From_ -> \1>{n-1}From_ conversion
+static size_t unescapeFrom( char* str, size_t strLen ) {
+ if ( !str )
+ return 0;
+ if ( strLen <= STRDIM(">From ") )
+ return strLen;
+
+ // yes, *d++ = *s++ is a no-op as long as d == s (until after the
+ // first >From_), but writes are cheap compared to reads and the
+ // data is already in the cache from the read, so special-casing
+ // might even be slower...
+ const char * s = str;
+ char * d = str;
+ const char * const e = str + strLen - STRDIM(">From ");
+
+ while ( s < e ) {
+ if ( *s == '\n' && *(s+1) == '>' ) { // we can do the lookahead, since e is 6 chars from the end!
+ *d++ = *s++; // == '\n'
+ *d++ = *s++; // == '>'
+ while ( s < e && *s == '>' )
+ *d++ = *s++;
+ if ( qstrncmp( s, "From ", STRDIM("From ") ) == 0 )
+ --d;
+ }
+ *d++ = *s++; // yes, s might be e here, but e is not the end :-)
+ }
+ // copy the rest:
+ while ( s < str + strLen )
+ *d++ = *s++;
+ if ( d < s ) // only NUL-terminate if it's shorter
+ *d = 0;
+
+ return d - str;
+}
+
+//static
+QByteArray KMFolderMbox::escapeFrom( const DwString & str ) {
+ const unsigned int strLen = str.length();
+ if ( strLen <= STRDIM("From ") )
+ return KMail::Util::ByteArray( str );
+ // worst case: \nFrom_\nFrom_\nFrom_... => grows to 7/6
+ QByteArray result( int( strLen + 5 ) / 6 * 7 + 1 );
+
+ const char * s = str.data();
+ const char * const e = s + strLen - STRDIM("From ");
+ char * d = result.data();
+
+ bool onlyAnglesAfterLF = false; // dont' match ^From_
+ while ( s < e ) {
+ switch ( *s ) {
+ case '\n':
+ onlyAnglesAfterLF = true;
+ break;
+ case '>':
+ break;
+ case 'F':
+ if ( onlyAnglesAfterLF && qstrncmp( s+1, "rom ", STRDIM("rom ") ) == 0 )
+ *d++ = '>';
+ // fall through
+ default:
+ onlyAnglesAfterLF = false;
+ break;
+ }
+ *d++ = *s++;
+ }
+ while ( s < str.data() + strLen )
+ *d++ = *s++;
+
+ result.truncate( d - result.data() );
+ return result;
+}
+
+#undef STRDIM
+
+//-----------------------------------------------------------------------------
+DwString KMFolderMbox::getDwString(int idx)
+{
+ KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];
+
+ assert(mi!=0);
+ assert(mStream != 0);
+
+ size_t msgSize = mi->msgSize();
+ char* msgText = new char[ msgSize + 1 ];
+
+ fseek(mStream, mi->folderOffset(), SEEK_SET);
+ fread(msgText, msgSize, 1, mStream);
+ msgText[msgSize] = '\0';
+
+ size_t newMsgSize = unescapeFrom( msgText, msgSize );
+ newMsgSize = KMail::Util::crlf2lf( msgText, newMsgSize );
+
+ DwString msgStr;
+ // the DwString takes possession of msgText, so we must not delete msgText
+ msgStr.TakeBuffer( msgText, msgSize + 1, 0, newMsgSize );
+ return msgStr;
+}
+
+
+//-----------------------------------------------------------------------------
+int KMFolderMbox::addMsg( KMMessage* aMsg, int* aIndex_ret )
+{
+ if (!canAddMsgNow(aMsg, aIndex_ret)) return 0;
+ QByteArray msgText;
+ char endStr[3];
+ int idx = -1, rc;
+ KMFolder* msgParent;
+ bool editing = false;
+ int growth = 0;
+
+ KMFolderOpener openThis(folder(), "mboxaddMsg");
+ rc = openThis.openResult();
+ if (rc)
+ {
+ kdDebug(5006) << "KMFolderMbox::addMsg-open: " << rc << " of folder: " << label() << endl;
+ return rc;
+ }
+
+ // take message out of the folder it is currently in, if any
+ msgParent = aMsg->parent();
+ if (msgParent)
+ {
+ if ( msgParent== folder() )
+ {
+ if (kmkernel->folderIsDraftOrOutbox( folder() ))
+ //special case for Edit message.
+ {
+ kdDebug(5006) << "Editing message in outbox or drafts" << endl;
+ editing = true;
+ }
+ else
+ return 0;
+ }
+
+ idx = msgParent->find(aMsg);
+ msgParent->getMsg( idx );
+ }
+
+ if (folderType() != KMFolderTypeImap)
+ {
+/*
+QFile fileD0( "testdat_xx-kmfoldermbox-0" );
+if( fileD0.open( IO_WriteOnly ) ) {
+ QDataStream ds( &fileD0 );
+ ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() );
+ fileD0.close(); // If data is 0 we just create a zero length file.
+}
+*/
+ aMsg->setStatusFields();
+/*
+QFile fileD1( "testdat_xx-kmfoldermbox-1" );
+if( fileD1.open( IO_WriteOnly ) ) {
+ QDataStream ds( &fileD1 );
+ ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() );
+ fileD1.close(); // If data is 0 we just create a zero length file.
+}
+*/
+ if (aMsg->headerField("Content-Type").isEmpty()) // This might be added by
+ aMsg->removeHeaderField("Content-Type"); // the line above
+ }
+ msgText = escapeFrom( aMsg->asDwString() );
+ size_t len = msgText.size();
+
+ assert(mStream != 0);
+ clearerr(mStream);
+ if (len <= 0)
+ {
+ kdDebug(5006) << "Message added to folder `" << name() << "' contains no data. Ignoring it." << endl;
+ return 0;
+ }
+
+ // Make sure the file is large enough to check for an end
+ // character
+ fseek(mStream, 0, SEEK_END);
+ off_t revert = ftell(mStream);
+ if (ftell(mStream) >= 2) {
+ // write message to folder file
+ fseek(mStream, -2, SEEK_END);
+ fread(endStr, 1, 2, mStream); // ensure separating empty line
+ if (ftell(mStream) > 0 && endStr[0]!='\n') {
+ ++growth;
+ if (endStr[1]!='\n') {
+ //printf ("****endStr[1]=%c\n", endStr[1]);
+ fwrite("\n\n", 1, 2, mStream);
+ ++growth;
+ }
+ else fwrite("\n", 1, 1, mStream);
+ }
+ }
+ fseek(mStream,0,SEEK_END); // this is needed on solaris and others
+ int error = ferror(mStream);
+ if (error)
+ return error;
+
+ QCString messageSeparator( aMsg->mboxMessageSeparator() );
+ fwrite( messageSeparator.data(), messageSeparator.length(), 1, mStream );
+ off_t offs = ftell(mStream);
+ fwrite(msgText.data(), len, 1, mStream);
+ if (msgText[(int)len-1]!='\n') fwrite("\n\n", 1, 2, mStream);
+ fflush(mStream);
+ size_t size = ftell(mStream) - offs;
+
+ error = ferror(mStream);
+ if (error) {
+ kdDebug(5006) << "Error: Could not add message to folder: " << strerror(errno) << endl;
+ if (ftell(mStream) > revert) {
+ kdDebug(5006) << "Undoing changes" << endl;
+ truncate( QFile::encodeName(location()), revert );
+ }
+ kmkernel->emergencyExit( i18n("Could not add message to folder: ") + QString::fromLocal8Bit(strerror(errno)));
+
+ /* This code is not 100% reliable
+ bool busy = kmkernel->kbp()->isBusy();
+ if (busy) kmkernel->kbp()->idle();
+ KMessageBox::sorry(0,
+ i18n("Unable to add message to folder.\n"
+ "(No space left on device or insufficient quota?)\n"
+ "Free space and sufficient quota are required to continue safely."));
+ if (busy) kmkernel->kbp()->busy();
+ kmkernel->kbp()->idle();
+ */
+ return error;
+ }
+
+ if (msgParent) {
+ if (idx >= 0) msgParent->take(idx);
+ }
+// if (mAccount) aMsg->removeHeaderField("X-UID");
+
+ if (aMsg->isUnread() || aMsg->isNew() ||
+ (folder() == kmkernel->outboxFolder())) {
+ if (mUnreadMsgs == -1) mUnreadMsgs = 1;
+ else ++mUnreadMsgs;
+ if ( !mQuiet )
+ emit numUnreadMsgsChanged( folder() );
+ }
+ ++mTotalMsgs;
+ mSize = -1;
+
+ if ( aMsg->attachmentState() == KMMsgAttachmentUnknown &&
+ aMsg->readyToShow() )
+ aMsg->updateAttachmentState();
+
+ // store information about the position in the folder file in the message
+ aMsg->setParent(folder());
+ aMsg->setFolderOffset(offs);
+ aMsg->setMsgSize(size);
+ idx = mMsgList.append(&aMsg->toMsgBase(), mExportsSernums );
+ if ( aMsg->getMsgSerNum() <= 0 )
+ aMsg->setMsgSerNum();
+ else
+ replaceMsgSerNum( aMsg->getMsgSerNum(), &aMsg->toMsgBase(), idx );
+
+ // change the length of the previous message to encompass white space added
+ if ((idx > 0) && (growth > 0)) {
+ // don't grow if a deleted message claims space at the end of the file
+ if ((ulong)revert == mMsgList[idx - 1]->folderOffset() + mMsgList[idx - 1]->msgSize() )
+ mMsgList[idx - 1]->setMsgSize( mMsgList[idx - 1]->msgSize() + growth );
+ }
+
+ // write index entry if desired
+ if (mAutoCreateIndex)
+ {
+ assert(mIndexStream != 0);
+ clearerr(mIndexStream);
+ fseek(mIndexStream, 0, SEEK_END);
+ 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;
+
+ fflush(mIndexStream);
+ error = ferror(mIndexStream);
+
+ if ( mExportsSernums )
+ error |= appendToFolderIdsFile( idx );
+
+ if (error) {
+ kdWarning(5006) << "Error: Could not add message to folder (No space left on device?)" << endl;
+ if (ftell(mIndexStream) > revert) {
+ kdWarning(5006) << "Undoing changes" << endl;
+ truncate( QFile::encodeName(indexLocation()), revert );
+ }
+ if ( errno )
+ kmkernel->emergencyExit( i18n("Could not add message to folder:") + QString::fromLocal8Bit(strerror(errno)));
+ else
+ kmkernel->emergencyExit( i18n("Could not add message to folder (No space left on device?)") );
+
+ /* This code may not be 100% reliable
+ bool busy = kmkernel->kbp()->isBusy();
+ if (busy) kmkernel->kbp()->idle();
+ KMessageBox::sorry(0,
+ i18n("Unable to add message to folder.\n"
+ "(No space left on device or insufficient quota?)\n"
+ "Free space and sufficient quota are required to continue safely."));
+ if (busy) kmkernel->kbp()->busy();
+ */
+ return error;
+ }
+ }
+
+ if (aIndex_ret) *aIndex_ret = idx;
+ emitMsgAddedSignals(idx);
+
+ // All streams have been flushed without errors if we arrive here
+ // Return success!
+ // (Don't return status of stream, it may have been closed already.)
+ return 0;
+}
+
+int KMFolderMbox::compact( unsigned int startIndex, int nbMessages, FILE* tmpfile, off_t& offs, bool& done )
+{
+ int rc = 0;
+ QCString mtext;
+ unsigned int stopIndex = nbMessages == -1 ? mMsgList.count() :
+ QMIN( mMsgList.count(), startIndex + nbMessages );
+ //kdDebug(5006) << "KMFolderMbox: compacting from " << startIndex << " to " << stopIndex << endl;
+ for(unsigned int idx = startIndex; idx < stopIndex; ++idx) {
+ KMMsgInfo* mi = (KMMsgInfo*)mMsgList.at(idx);
+ size_t msize = mi->msgSize();
+ if (mtext.size() < msize + 2)
+ mtext.resize(msize+2);
+ off_t folder_offset = mi->folderOffset();
+
+ //now we need to find the separator! grr...
+ for(off_t i = folder_offset-25; true; i -= 20) {
+ off_t chunk_offset = i <= 0 ? 0 : i;
+ if(fseek(mStream, chunk_offset, SEEK_SET) == -1) {
+ rc = errno;
+ break;
+ }
+ if (mtext.size() < 20)
+ mtext.resize(20);
+ fread(mtext.data(), 20, 1, mStream);
+ if(i <= 0) { //woops we've reached the top of the file, last try..
+ if ( mtext.contains( "from ", false ) ) {
+ if (mtext.size() < (size_t)folder_offset)
+ mtext.resize(folder_offset);
+ if(fseek(mStream, chunk_offset, SEEK_SET) == -1 ||
+ !fread(mtext.data(), folder_offset, 1, mStream) ||
+ !fwrite(mtext.data(), folder_offset, 1, tmpfile)) {
+ rc = errno;
+ break;
+ }
+ offs += folder_offset;
+ } else {
+ rc = 666;
+ }
+ break;
+ } else {
+ int last_crlf = -1;
+ for(int i2 = 0; i2 < 20; i2++) {
+ if(*(mtext.data()+i2) == '\n')
+ last_crlf = i2;
+ }
+ if(last_crlf != -1) {
+ int size = folder_offset - (i + last_crlf+1);
+ if ((int)mtext.size() < size)
+ mtext.resize(size);
+ if(fseek(mStream, i + last_crlf+1, SEEK_SET) == -1 ||
+ !fread(mtext.data(), size, 1, mStream) ||
+ !fwrite(mtext.data(), size, 1, tmpfile)) {
+ rc = errno;
+ break;
+ }
+ offs += size;
+ break;
+ }
+ }
+ }
+ if (rc)
+ break;
+
+ //now actually write the message
+ if(fseek(mStream, folder_offset, SEEK_SET) == -1 ||
+ !fread(mtext.data(), msize, 1, mStream) || !fwrite(mtext.data(), msize, 1, tmpfile)) {
+ rc = errno;
+ break;
+ }
+ mi->setFolderOffset(offs);
+ offs += msize;
+ }
+ done = ( !rc && stopIndex == mMsgList.count() ); // finished without errors
+ return rc;
+}
+
+//-----------------------------------------------------------------------------
+int KMFolderMbox::compact( bool silent )
+{
+ // This is called only when the user explicitely requests compaction,
+ // so we don't check needsCompact.
+
+ KMail::MboxCompactionJob* job = new KMail::MboxCompactionJob( folder(), true /*immediate*/ );
+ int rc = job->executeNow( silent );
+ // Note that job autodeletes itself.
+
+ // If this is the current folder, the changed signal will ultimately call
+ // KMHeaders::setFolderInfoStatus which will override the message, so save/restore it
+ QString statusMsg = BroadcastStatus::instance()->statusMsg();
+ emit changed();
+ BroadcastStatus::instance()->setStatusMsg( statusMsg );
+ return rc;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFolderMbox::setLockType( LockType ltype )
+{
+ mLockType = ltype;
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderMbox::setProcmailLockFileName( const QString &fname )
+{
+ mProcmailLockFileName = fname;
+}
+
+//-----------------------------------------------------------------------------
+int KMFolderMbox::removeContents()
+{
+ int rc = 0;
+ rc = unlink(QFile::encodeName(location()));
+ return rc;
+}
+
+//-----------------------------------------------------------------------------
+int KMFolderMbox::expungeContents()
+{
+ int rc = 0;
+ if (truncate(QFile::encodeName(location()), 0))
+ rc = errno;
+ return rc;
+}
+
+//-----------------------------------------------------------------------------
+/*virtual*/
+Q_INT64 KMFolderMbox::doFolderSize() const
+{
+ QFileInfo info( location() );
+ return (Q_INT64)(info.size());
+}
+
+//-----------------------------------------------------------------------------
+#include "kmfoldermbox.moc"
diff --git a/kmail/kmfoldermbox.h b/kmail/kmfoldermbox.h
new file mode 100644
index 00000000..bcbe5980
--- /dev/null
+++ b/kmail/kmfoldermbox.h
@@ -0,0 +1,157 @@
+/*
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ *
+ * 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 kmfoldermbox_h
+#define kmfoldermbox_h
+
+#include "kmfolderindex.h"
+#include "mboxjob.h"
+
+#include <sys/types.h> // for size_t
+
+namespace KMail {
+ class FolderJob;
+ class MboxJob;
+ class AttachmentStrategy;
+}
+using KMail::FolderJob;
+using KMail::MboxJob;
+using KMail::AttachmentStrategy;
+
+/* Mail folder.
+ * (description will be here).
+ *
+ * Accounts:
+ * The accounts (of KMail) that are fed into the folder are
+ * represented as the children of the folder. They are only stored here
+ * during runtime to have a reference for which accounts point to a
+ * specific folder.
+ */
+
+class KMFolderMbox : public KMFolderIndex
+{
+ Q_OBJECT
+ friend class ::KMail::MboxJob;
+public:
+
+
+ /** Usually a parent is given. But in some cases there is no
+ fitting parent object available. Then the name of the folder
+ is used as the absolute path to the folder file. */
+ KMFolderMbox(KMFolder* folder, const char* name=0);
+ virtual ~KMFolderMbox();
+
+ /** Returns the type of this folder */
+ virtual KMFolderType folderType() const { return KMFolderTypeMbox; }
+
+ /** Read a message and return it as a string */
+ DwString getDwString(int idx);
+
+ /** Add the given message to the folder. Usually the message
+ is added at the end of the folder. Returns zero on success and
+ an errno error code on failure. The index of the new message
+ is stored in index_return if given.
+ Please note that the message is added as is to the folder and the folder
+ takes ownership of the message (deleting it in the destructor).*/
+ virtual int addMsg( KMMessage* msg, int* index_return = 0 );
+
+ /** Open folder for access.
+ Does nothing if the folder is already opened. To reopen a folder
+ call close() first.
+ Returns zero on success and an error code equal to the c-library
+ fopen call otherwise (errno). */
+ virtual int open(const char *owner);
+
+ /** @reimpl */
+ virtual void reallyDoClose(const char *owner);
+
+ virtual int canAccess();
+
+ /** fsync buffers to disk */
+ virtual void sync();
+
+ /** @reimpl */
+ virtual int create();
+
+ /** Remove deleted messages from the folder. Returns zero on success
+ and an errno on failure. */
+ virtual int compact( bool silent );
+
+ /** Remove some deleted messages from the folder. Returns zero on success
+ and an errno on failure. This is only for use from MboxCompactionJob. */
+ int compact( unsigned int startIndex, int nbMessages, FILE* tmpFile, off_t& offs, bool& done );
+
+ /** Is the folder read-only? */
+ virtual bool isReadOnly() const { return mReadOnly; }
+
+ /** Is the folder locked? */
+ bool isLocked() const { return mFilesLocked; }
+
+ void setLockType( LockType ltype=FCNTL );
+
+ void setProcmailLockFileName( const QString& );
+
+ static QByteArray escapeFrom( const DwString & str );
+
+ virtual IndexStatus indexStatus();
+
+ /** reimp */
+ virtual Q_INT64 doFolderSize() const;
+
+protected:
+ virtual FolderJob* doCreateJob( KMMessage *msg, FolderJob::JobType jt, KMFolder *folder,
+ QString partSpecifier, const AttachmentStrategy *as ) const;
+ virtual FolderJob* doCreateJob( QPtrList<KMMessage>& msgList, const QString& sets,
+ FolderJob::JobType jt, KMFolder *folder ) const;
+ /** Load message from file and store it at given index. Returns 0
+ on failure. */
+ virtual KMMessage* readMsg(int idx);
+
+ /** Create index file from messages file and fill the message-info list
+ mMsgList. Returns 0 on success and an errno value (see fopen) on
+ failure. */
+ virtual int createIndexFromContents();
+
+ /** Lock mail folder files. Called by ::open(). Returns 0 on success and
+ an errno error code on failure. */
+ virtual int lock();
+
+ /** Unlock mail folder files. Called by ::close(). Returns 0 on success
+ and an errno error code on failure. */
+ virtual int unlock();
+
+ /** Called by KMFolder::remove() to delete the actual contents.
+ At the time of the call the folder has already been closed, and
+ the various index files deleted. Returns 0 on success. */
+ virtual int removeContents();
+
+ /** Called by KMFolder::expunge() to delete the actual contents.
+ At the time of the call the folder has already been closed, and
+ the various index files deleted. Returns 0 on success. */
+ virtual int expungeContents();
+
+private:
+ FILE *mStream;
+ bool mFilesLocked; // TRUE if the files of the folder are locked (writable)
+ bool mReadOnly; // true if locking failed
+ LockType mLockType;
+ QString mProcmailLockFileName;
+};
+
+#endif // kmfoldermbox_h
diff --git a/kmail/kmfoldermgr.cpp b/kmail/kmfoldermgr.cpp
new file mode 100644
index 00000000..ece90c23
--- /dev/null
+++ b/kmail/kmfoldermgr.cpp
@@ -0,0 +1,604 @@
+// kmfoldermgr.cpp
+
+#ifdef HAVE_CONFIG_H
+ #include <config.h>
+#endif
+
+#include <sys/types.h>
+
+#ifdef HAVE_SYS_STAT_H
+ #include <sys/stat.h>
+#endif
+
+#include <assert.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+
+#include <qdir.h>
+
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kconfig.h>
+#include <kdebug.h>
+#include <kapplication.h>
+
+#include "kmmainwin.h"
+#include "kmfiltermgr.h"
+#include "kmfoldermgr.h"
+#include "folderstorage.h"
+#include "kmfolder.h"
+#include "kmfoldercachedimap.h"
+#include "kmacctcachedimap.h"
+#include "renamejob.h"
+#include "copyfolderjob.h"
+
+using KMail::RenameJob;
+using KMail::CopyFolderJob;
+
+//-----------------------------------------------------------------------------
+KMFolderMgr::KMFolderMgr(const QString& aBasePath, KMFolderDirType dirType):
+ QObject(), mDir(this, QString::null, dirType)
+{
+ if ( dirType == KMStandardDir )
+ mDir.setBaseURL( I18N_NOOP("Local Folders") );
+ mQuiet = 0;
+ mChanged = FALSE;
+ setBasePath(aBasePath);
+ mRemoveOrig = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+KMFolderMgr::~KMFolderMgr()
+{
+ mBasePath = QString::null;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFolderMgr::expireAll() {
+ KConfig *config = KMKernel::config();
+ KConfigGroupSaver saver(config, "General");
+ int ret = KMessageBox::Continue;
+
+ if (config->readBoolEntry("warn-before-expire", true)) {
+ ret = KMessageBox::warningContinueCancel(KMainWindow::memberList->first(),
+ i18n("Are you sure you want to expire old messages?"),
+ i18n("Expire Old Messages?"), i18n("Expire"));
+ }
+
+ if (ret == KMessageBox::Continue) {
+ expireAllFolders( true /*immediate*/ );
+ }
+
+}
+
+#define DO_FOR_ALL(function, folder_code) \
+ KMFolderNode* node; \
+ QPtrListIterator<KMFolderNode> it(*dir); \
+ for ( ; (node = it.current()); ) { \
+ ++it; \
+ if (node->isDir()) continue; \
+ KMFolder *folder = static_cast<KMFolder*>(node); \
+ folder_code \
+ KMFolderDir *child = folder->child(); \
+ if (child) \
+ function \
+ }
+
+int KMFolderMgr::folderCount(KMFolderDir *dir)
+{
+ int count = 0;
+ if (dir == 0)
+ dir = &mDir;
+ DO_FOR_ALL(
+ {
+ count += folderCount( child );
+ },
+ {
+ count++;
+ }
+ )
+
+ return count;
+}
+
+
+
+//-----------------------------------------------------------------------------
+void KMFolderMgr::compactAllFolders(bool immediate, KMFolderDir* dir)
+{
+ if (dir == 0)
+ dir = &mDir;
+ DO_FOR_ALL(
+ {
+ compactAllFolders( immediate, child );
+ },
+ {
+ if ( folder->needsCompacting() )
+ folder->compact( immediate ? KMFolder::CompactNow : KMFolder::CompactLater );
+ }
+ )
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFolderMgr::setBasePath(const QString& aBasePath)
+{
+ assert(!aBasePath.isNull());
+
+ if (aBasePath[0] == '~')
+ {
+ mBasePath = QDir::homeDirPath();
+ mBasePath.append("/");
+ mBasePath.append(aBasePath.mid(1));
+ }
+ else
+ mBasePath = aBasePath;
+
+ QFileInfo info( mBasePath );
+
+ // FIXME We should ask for an alternative dir, rather than bailing out,
+ // I guess - till
+ if ( info.exists() ) {
+ if ( !info.isDir() ) {
+ KMessageBox::sorry(0, i18n("'%1' does not appear to be a folder.\n"
+ "Please move the file out of the way.")
+ .arg( mBasePath ) );
+ ::exit(-1);
+ }
+ if ( !info.isReadable() || !info.isWritable() ) {
+ KMessageBox::sorry(0, i18n("The permissions of the folder '%1' are "
+ "incorrect;\n"
+ "please make sure that you can view and modify "
+ "the content of this folder.")
+ .arg( mBasePath ) );
+ ::exit(-1);
+ }
+ } else {
+ // ~/Mail (or whatever the user specified) doesn't exist, create it
+ if ( ::mkdir( QFile::encodeName( mBasePath ) , S_IRWXU ) == -1 ) {
+ KMessageBox::sorry(0, i18n("KMail could not create folder '%1';\n"
+ "please make sure that you can view and "
+ "modify the content of the folder '%2'.")
+ .arg( mBasePath ).arg( QDir::homeDirPath() ) );
+ ::exit(-1);
+ }
+ }
+ mDir.setPath(mBasePath);
+ mDir.reload();
+ contentsChanged();
+}
+
+
+//-----------------------------------------------------------------------------
+KMFolder* KMFolderMgr::createFolder(const QString& fName, bool sysFldr,
+ KMFolderType aFolderType,
+ KMFolderDir *aFolderDir)
+{
+ KMFolder* fld;
+ KMFolderDir *fldDir = aFolderDir;
+
+ if (!aFolderDir)
+ fldDir = &mDir;
+
+ // check if this is a dimap folder and the folder we want to create has been deleted
+ // since the last sync
+ if ( fldDir->owner() && fldDir->owner()->folderType() == KMFolderTypeCachedImap ) {
+ KMFolderCachedImap *storage = static_cast<KMFolderCachedImap*>( fldDir->owner()->storage() );
+ KMAcctCachedImap *account = storage->account();
+ // guess imap path
+ QString imapPath = storage->imapPath();
+ if ( !imapPath.endsWith( "/" ) )
+ imapPath += "/";
+ imapPath += fName;
+ if ( account->isDeletedFolder( imapPath ) || account->isDeletedFolder( imapPath + "/" )
+ || account->isPreviouslyDeletedFolder( imapPath )
+ || account->isPreviouslyDeletedFolder( imapPath + "/" ) ) {
+ KMessageBox::error( 0, i18n("A folder with the same name has been deleted since the last mail check."
+ "You need to check mails first before creating another folder with the same name."),
+ i18n("Could Not Create Folder") );
+ return 0;
+ }
+ }
+
+ fld = fldDir->createFolder(fName, sysFldr, aFolderType);
+ if (fld) {
+ if ( fld->id() == 0 )
+ fld->setId( createId() );
+ contentsChanged();
+ emit folderAdded(fld);
+ if (kmkernel->filterMgr())
+ kmkernel->filterMgr()->folderCreated(fld);
+ }
+
+ return fld;
+}
+
+
+//-----------------------------------------------------------------------------
+KMFolder* KMFolderMgr::find(const QString& folderName, bool foldersOnly)
+{
+ KMFolderNode* node;
+
+ for (node=mDir.first(); node; node=mDir.next())
+ {
+ if (node->isDir() && foldersOnly) continue;
+ if (node->name()==folderName) return (KMFolder*)node;
+ }
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+KMFolder* KMFolderMgr::findById(const uint id)
+{
+ return findIdString( QString::null, id );
+}
+
+//-----------------------------------------------------------------------------
+KMFolder* KMFolderMgr::findIdString( const QString& folderId,
+ const uint id,
+ KMFolderDir *dir )
+{
+ if (!dir)
+ dir = &mDir;
+
+ DO_FOR_ALL(
+ {
+ KMFolder *folder = findIdString( folderId, id, child );
+ if ( folder )
+ return folder;
+ },
+ {
+ if ( ( !folderId.isEmpty() && folder->idString() == folderId ) ||
+ ( id != 0 && folder->id() == id ) )
+ return folder;
+ }
+ )
+
+ return 0;
+}
+
+void KMFolderMgr::getFolderURLS( QStringList& flist, const QString& prefix,
+ KMFolderDir *adir )
+{
+ KMFolderDir* dir = adir ? adir : &mDir;
+
+ DO_FOR_ALL(
+ {
+ getFolderURLS( flist, prefix + "/" + folder->name(), child );
+ },
+ {
+ flist << prefix + "/" + folder->name();
+ }
+ )
+}
+
+KMFolder* KMFolderMgr::getFolderByURL( const QString& vpath,
+ const QString& prefix,
+ KMFolderDir *adir )
+{
+ KMFolderDir* dir = adir ? adir : &mDir;
+ DO_FOR_ALL(
+ {
+ QString a = prefix + "/" + folder->name();
+ KMFolder * mfolder = getFolderByURL( vpath, a,child );
+ if ( mfolder )
+ return mfolder;
+ },
+ {
+ QString comp = prefix + "/" + folder->name();
+ if ( comp == vpath )
+ return folder;
+ }
+ )
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+KMFolder* KMFolderMgr::findOrCreate(const QString& aFolderName, bool sysFldr,
+ const uint id)
+{
+ KMFolder* folder = 0;
+ if ( id == 0 )
+ folder = find(aFolderName);
+ else
+ folder = findById(id);
+
+ if (!folder)
+ {
+ static bool know_type = false;
+ static KMFolderType type = KMFolderTypeMaildir;
+ if (know_type == false)
+ {
+ know_type = true;
+ KConfig *config = KMKernel::config();
+ KConfigGroupSaver saver(config, "General");
+ if (config->hasKey("default-mailbox-format"))
+ {
+ if (config->readNumEntry("default-mailbox-format", 1) == 0)
+ type = KMFolderTypeMbox;
+
+ }
+ }
+
+ folder = createFolder(aFolderName, sysFldr, type);
+ if (!folder) {
+ KMessageBox::error(0,(i18n("Cannot create file `%1' in %2.\nKMail cannot start without it.").arg(aFolderName).arg(mBasePath)));
+ exit(-1);
+ }
+ if ( id > 0 )
+ folder->setId( id );
+ }
+ return folder;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFolderMgr::remove(KMFolder* aFolder)
+{
+ if (!aFolder) return;
+ // remember the original folder to trigger contentsChanged later
+ if (!mRemoveOrig) mRemoveOrig = aFolder;
+ if (aFolder->child())
+ {
+ // call remove for every child
+ KMFolderNode* node;
+ QPtrListIterator<KMFolderNode> it(*aFolder->child());
+ for ( ; (node = it.current()); )
+ {
+ ++it;
+ if (node->isDir()) continue;
+ KMFolder *folder = static_cast<KMFolder*>(node);
+ remove(folder);
+ }
+ }
+ emit folderRemoved(aFolder);
+ removeFolder(aFolder);
+}
+
+void KMFolderMgr::removeFolder(KMFolder* aFolder)
+{
+ connect(aFolder, SIGNAL(removed(KMFolder*, bool)),
+ this, SLOT(removeFolderAux(KMFolder*, bool)));
+ aFolder->remove();
+}
+
+KMFolder* KMFolderMgr::parentFolder( KMFolder* folder )
+{
+ // find the parent folder by stripping "." and ".directory" from the name
+ KMFolderDir* fdir = folder->parent();
+ QString parentName = fdir->name();
+ parentName = parentName.mid( 1, parentName.length()-11 );
+ KMFolderNode* parent = fdir->hasNamedFolder( parentName );
+ if ( !parent && fdir->parent() ) // dimap obviously has a different structure
+ parent = fdir->parent()->hasNamedFolder( parentName );
+
+ KMFolder* parentF = 0;
+ if ( parent )
+ parentF = dynamic_cast<KMFolder*>( parent );
+ return parentF;
+}
+
+void KMFolderMgr::removeFolderAux(KMFolder* aFolder, bool success)
+{
+ if (!success) {
+ mRemoveOrig = 0;
+ return;
+ }
+
+ KMFolderDir* fdir = aFolder->parent();
+ KMFolderNode* fN;
+ for (fN = fdir->first(); fN != 0; fN = fdir->next()) {
+ if (fN->isDir() && (fN->name() == "." + aFolder->fileName() + ".directory")) {
+ removeDirAux(static_cast<KMFolderDir*>(fN));
+ break;
+ }
+ }
+ KMFolder* parentF = parentFolder( aFolder );
+
+ // aFolder will be deleted by the next call!
+ aFolder->parent()->remove(aFolder);
+
+ // update the children state
+ if ( parentF )
+ {
+ if ( parentF != aFolder )
+ {
+ parentF->storage()->updateChildrenState();
+ }
+ }
+ else
+ kdWarning(5006) << "Can not find parent folder" << endl;
+
+ if (aFolder == mRemoveOrig) {
+ // call only if we're removing the original parent folder
+ contentsChanged();
+ mRemoveOrig = 0;
+ }
+}
+
+void KMFolderMgr::removeDirAux(KMFolderDir* aFolderDir)
+{
+ QDir dir;
+ QString folderDirLocation = aFolderDir->path();
+ aFolderDir->clear();
+ aFolderDir->parent()->remove(aFolderDir);
+ dir.rmdir(folderDirLocation);
+}
+
+//-----------------------------------------------------------------------------
+KMFolderRootDir& KMFolderMgr::dir(void)
+{
+ return mDir;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFolderMgr::contentsChanged(void)
+{
+ if (mQuiet) mChanged = TRUE;
+ else emit changed();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFolderMgr::reload(void)
+{
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderMgr::createFolderList(QStringList *str,
+ QValueList<QGuardedPtr<KMFolder> > *folders)
+{
+ createFolderList( str, folders, 0, "" );
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderMgr::createI18nFolderList(QStringList *str,
+ QValueList<QGuardedPtr<KMFolder> > *folders)
+{
+ createFolderList( str, folders, 0, QString::null, true );
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderMgr::createFolderList(QStringList *str,
+ QValueList<QGuardedPtr<KMFolder> > *folders,
+ KMFolderDir *adir,
+ const QString& prefix,
+ bool i18nized)
+{
+ KMFolderDir* dir = adir ? adir : &mDir;
+
+ DO_FOR_ALL(
+ {
+ createFolderList(str, folders, child, " " + prefix, i18nized );
+ },
+ {
+ if (i18nized)
+ str->append(prefix + folder->label());
+ else
+ str->append(prefix + folder->name());
+ folders->append( folder );
+ }
+ )
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderMgr::syncAllFolders( KMFolderDir *adir )
+{
+ KMFolderDir* dir = adir ? adir : &mDir;
+ DO_FOR_ALL(
+ {
+ syncAllFolders(child);
+ },
+ {
+ if (folder->isOpened())
+ folder->sync();
+ }
+ )
+}
+
+
+//-----------------------------------------------------------------------------
+/**
+ * Check each folder in turn to see if it is configured to
+ * AutoExpire. If so, expire old messages.
+ *
+ * Should be called with 0 first time around.
+ */
+void KMFolderMgr::expireAllFolders(bool immediate, KMFolderDir *adir) {
+ KMFolderDir *dir = adir ? adir : &mDir;
+
+ DO_FOR_ALL(
+ {
+ expireAllFolders(immediate, child);
+ },
+ {
+ if (folder->isAutoExpire()) {
+ folder->expireOldMessages( immediate );
+ }
+ }
+ )
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderMgr::quiet(bool beQuiet)
+{
+ if (beQuiet)
+ mQuiet++;
+ else {
+ mQuiet--;
+ if (mQuiet <= 0)
+ {
+ mQuiet = 0;
+ if (mChanged) emit changed();
+ mChanged = FALSE;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderMgr::tryReleasingFolder(KMFolder* f, KMFolderDir* adir)
+{
+ KMFolderDir* dir = adir ? adir : &mDir;
+ DO_FOR_ALL(
+ {
+ tryReleasingFolder(f, child);
+ },
+ {
+ if (folder->isOpened())
+ folder->storage()->tryReleasingFolder(f);
+ }
+ )
+}
+
+//-----------------------------------------------------------------------------
+uint KMFolderMgr::createId()
+{
+ int newId;
+ do
+ {
+ newId = kapp->random();
+ } while ( findById( newId ) != 0 );
+
+ return newId;
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderMgr::moveFolder( KMFolder* folder, KMFolderDir *newParent )
+{
+ renameFolder( folder, folder->name(), newParent );
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderMgr::renameFolder( KMFolder* folder, const QString& newName,
+ KMFolderDir *newParent )
+{
+ RenameJob* job = new RenameJob( folder->storage(), newName, newParent );
+ connect( job, SIGNAL( renameDone( QString, bool ) ),
+ this, SLOT( slotRenameDone( QString, bool ) ) );
+ connect( job, SIGNAL( renameDone( QString, bool ) ),
+ this, SIGNAL( folderMoveOrCopyOperationFinished() ) );
+ job->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderMgr::copyFolder( KMFolder* folder, KMFolderDir *newParent )
+{
+ kdDebug(5006) << "Copy folder: " << folder->prettyURL() << endl;
+ CopyFolderJob* job = new CopyFolderJob( folder->storage(), newParent );
+ connect( job, SIGNAL( folderCopyComplete( bool ) ),
+ this, SIGNAL( folderMoveOrCopyOperationFinished() ) );
+ job->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderMgr::slotRenameDone( QString, bool success )
+{
+ kdDebug(5006) << k_funcinfo << success << endl;
+}
+
+#include "kmfoldermgr.moc"
diff --git a/kmail/kmfoldermgr.h b/kmail/kmfoldermgr.h
new file mode 100644
index 00000000..29076942
--- /dev/null
+++ b/kmail/kmfoldermgr.h
@@ -0,0 +1,186 @@
+/* KMail Folder Manager
+ *
+ */
+#ifndef kmfoldermgr_h
+#define kmfoldermgr_h
+
+#include <qstring.h>
+#include <qvaluelist.h>
+#include <qobject.h>
+#include <qguardedptr.h>
+
+#include "kmfolderdir.h"
+
+class KMFolder;
+
+class KMFolderMgr: public QObject
+{
+ Q_OBJECT
+
+public:
+ KMFolderMgr(const QString& basePath, KMFolderDirType dirType = KMStandardDir);
+ virtual ~KMFolderMgr();
+
+ /** Returns path to directory where all the folders live. */
+ QString basePath() const { return mBasePath; }
+
+ /** Set base path. Also calls reload() on the base directory. */
+ virtual void setBasePath(const QString&);
+
+ /** Provides access to base directory */
+ KMFolderRootDir& dir();
+
+ /** Searches folder and returns it. Skips directories
+ (objects of type KMFolderDir) if foldersOnly is TRUE. */
+ virtual KMFolder* find(const QString& folderName, bool foldersOnly=TRUE);
+
+ /** Searches for a folder with the given id, recurses into directories */
+ virtual KMFolder* findIdString(const QString& folderId,
+ const uint id = 0, KMFolderDir *dir = 0);
+
+ /** Uses find() to find given folder. If not found the folder is
+ * created. Directories are skipped.
+ * If an id is passed this searches for it
+ */
+ virtual KMFolder* findOrCreate(const QString& folderName, bool sysFldr=TRUE,
+ const uint id = 0);
+
+ /** Searches folder by id and returns it. Skips directories
+ (objects of type KMFolderDir) */
+ virtual KMFolder* findById(const uint id);
+
+ virtual void getFolderURLS( QStringList& flist,
+ const QString& prefix=QString::null,
+ KMFolderDir *adir=0 );
+ virtual KMFolder* getFolderByURL( const QString& vpath,
+ const QString& prefix=QString::null,
+ KMFolderDir *adir=0 );
+
+ /** Create a mail folder in the root folder directory dir()
+ with given name. Returns Folder on success. */
+ virtual KMFolder* createFolder(const QString& fName, bool sysFldr=FALSE,
+ KMFolderType aFolderType=KMFolderTypeMbox,
+ KMFolderDir *aFolderDir = 0);
+
+ /** Physically remove given folder and delete the given folder object. */
+ virtual void remove(KMFolder* obsoleteFolder);
+
+ /** emits changed() signal */
+ virtual void contentsChanged(void);
+
+ /** Reloads all folders, discarding the existing ones. */
+ virtual void reload(void);
+
+ /** Create a list of formatted formatted folder labels and corresponding
+ folders*/
+ virtual void createFolderList( QStringList *str,
+ QValueList<QGuardedPtr<KMFolder> > *folders );
+
+ /** Auxillary function to facilitate creating a list of formatted
+ folder names, suitable for showing in QComboBox */
+ virtual void createFolderList( QStringList *str,
+ QValueList<QGuardedPtr<KMFolder> > *folders,
+ KMFolderDir *adir,
+ const QString& prefix,
+ bool i18nized=FALSE );
+
+ /** Create a list of formatted formatted folder labels and corresponding
+ folders. The system folder names are translated */
+ virtual void createI18nFolderList( QStringList *str,
+ QValueList<QGuardedPtr<KMFolder> > *folders );
+
+ /** fsync all open folders to disk */
+ void syncAllFolders( KMFolderDir *adir = 0 );
+
+ /** Compact all folders that need to be, either immediately or scheduled as a background task */
+ void compactAllFolders( bool immediate, KMFolderDir *adir = 0 );
+
+ /** Expire old messages in all folders, either immediately or scheduled as a background task */
+ void expireAllFolders( bool immediate, KMFolderDir *adir = 0 );
+
+ /** Enable, disable changed() signals */
+ void quiet(bool);
+
+ /** Number of folders for purpose of progres report */
+ int folderCount(KMFolderDir *dir=0);
+
+ /** Try closing @p folder if possible, something is attempting an exclusive access to it.
+ Currently used for KMFolderSearch and the background tasks like expiry */
+ void tryReleasingFolder(KMFolder* folder, KMFolderDir *Dir=0);
+
+ /** Create a new unique ID */
+ uint createId();
+
+ /** Move a folder */
+ void moveFolder( KMFolder* folder, KMFolderDir* newParent );
+
+ /** Rename or move a folder */
+ void renameFolder( KMFolder* folder, const QString& newName,
+ KMFolderDir* newParent = 0 );
+
+ /** Copy a folder */
+ void copyFolder( KMFolder* folder, KMFolderDir* newParent );
+
+ /** Returns the parent Folder for the given folder or 0 on failure. */
+ KMFolder* parentFolder( KMFolder* folder );
+
+public slots:
+ /** GUI action: compact all folders that need to be compacted */
+ void compactAll() { compactAllFolders( true ); }
+
+ /** GUI action: expire all folders configured as such */
+ void expireAll();
+
+ /** Called from KMFolder::remove when the folderstorage was removed */
+ void removeFolderAux(KMFolder* obsoleteFolder, bool success);
+
+ /** Called when the renaming of a folder is done */
+ void slotRenameDone( QString newName, bool success );
+
+signals:
+ /** Emitted when the list of folders has changed. This signal is a hook
+ where clients like the KMFolderTree tree-view can connect. The signal
+ is meant to be emitted whenever the code using the folder-manager
+ changed things. */
+ void changed();
+
+ /** Emitted, when a folder is about to be removed. */
+ void folderRemoved(KMFolder*);
+
+ /** Emitted, when a folder has been added. */
+ void folderAdded(KMFolder*);
+
+ /** Emitted, when serial numbers for a folder have been invalidated. */
+ void folderInvalidated(KMFolder*);
+
+ /** Emitted, when a message has been appended to a folder */
+ void msgAdded(KMFolder*, Q_UINT32);
+
+ /** Emitted, when a message has been removed from a folder */
+ void msgRemoved(KMFolder*, Q_UINT32);
+
+ /** Emitted, when the status of a message is changed */
+ void msgChanged(KMFolder*, Q_UINT32, int delta);
+
+ /** Emitted when a field of the header of a specific message changed. */
+ void msgHeaderChanged(KMFolder*, int idx);
+
+ /** Emitted when a folder has been moved or copied */
+ void folderMoveOrCopyOperationFinished();
+
+protected:
+
+ /** Auxillary function to facilitate removal of a folder */
+ void removeFolder(KMFolder* aFolder);
+
+ /** Auxillary function to facilitate removal of a folder directory */
+ void removeDirAux(KMFolderDir* aFolderDir);
+
+ QString mBasePath;
+ KMFolderRootDir mDir;
+ int mQuiet;
+ bool mChanged;
+ KMFolder* mRemoveOrig;
+};
+
+#endif /*kmfoldermgr_h*/
diff --git a/kmail/kmfoldernode.cpp b/kmail/kmfoldernode.cpp
new file mode 100644
index 00000000..49698daf
--- /dev/null
+++ b/kmail/kmfoldernode.cpp
@@ -0,0 +1,65 @@
+// kmfoldernode.cpp
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "kmfolderdir.h"
+
+//-----------------------------------------------------------------------------
+KMFolderNode::KMFolderNode( KMFolderDir * parent, const QString & name )
+ : mName( name ),
+ mParent( parent ),
+ mDir( false ),
+ mId( 0 )
+{
+}
+
+
+//-----------------------------------------------------------------------------
+KMFolderNode::~KMFolderNode()
+{
+}
+
+//-----------------------------------------------------------------------------
+bool KMFolderNode::isDir(void) const
+{
+ return mDir;
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMFolderNode::path() const
+{
+ if (parent()) return parent()->path();
+ return QString::null;
+}
+
+//-----------------------------------------------------------------------------
+QString KMFolderNode::label(void) const
+{
+ return name();
+}
+
+//-----------------------------------------------------------------------------
+KMFolderDir* KMFolderNode::parent(void) const
+{
+ return mParent;
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderNode::setParent( KMFolderDir* aParent )
+{
+ mParent = aParent;
+}
+
+//-----------------------------------------------------------------------------
+uint KMFolderNode::id() const
+{
+ if (mId > 0)
+ return mId;
+ // compatibility, returns 0 on error
+ return name().toUInt();
+}
+
+#include "kmfoldernode.moc"
diff --git a/kmail/kmfoldernode.h b/kmail/kmfoldernode.h
new file mode 100644
index 00000000..56f22140
--- /dev/null
+++ b/kmail/kmfoldernode.h
@@ -0,0 +1,81 @@
+/*
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ *
+ * 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 kmfoldernode_h
+#define kmfoldernode_h
+
+#include <qobject.h>
+#include <qstring.h>
+#include <qptrlist.h>
+
+class KMFolderDir;
+
+class KMFolderNode: public QObject
+{
+ Q_OBJECT
+
+public:
+ KMFolderNode( KMFolderDir * parent, const QString & name );
+ virtual ~KMFolderNode();
+
+ /** Is it a directory where mail folders are stored or is it a folder that
+ contains mail ?
+ Note that there are some kinds of mail folders like the type mh uses that
+ are directories on disk but are handled as folders here. */
+ virtual bool isDir(void) const;
+ virtual void setDir(bool aDir) { mDir = aDir; }
+
+ /** Returns ptr to owning directory object or 0 if none. This
+ is just a wrapper for convenient access. */
+ KMFolderDir* parent(void) const ;
+ void setParent( KMFolderDir* aParent );
+ // { return (KMFolderDir*)KMFolderNodeInherited::parent(); }
+
+ /** Returns full path to the directory where this node is stored or 0
+ if the node has no parent. Example: if this object represents a folder
+ ~joe/Mail/inbox then path() returns "/home/joe/Mail" and name() returns
+ "inbox". */
+ virtual QString path() const;
+
+ /** Name of the node. Also used as file name. */
+ QString name() const { return mName; }
+ void setName(const QString& aName) { mName = aName; }
+
+ /** Label of the node for visualzation purposes. Default the same as
+ the name. */
+ virtual QString label() const;
+
+ /** URL of the node for visualization purposes. */
+ virtual QString prettyURL() const = 0;
+
+ /** ID of the node */
+ uint id() const;
+ void setId( uint id ) { mId = id; }
+
+protected:
+ QString mName;
+ KMFolderDir *mParent;
+ bool mDir;
+ uint mId;
+};
+
+typedef QPtrList<KMFolderNode> KMFolderNodeList;
+
+
+#endif /*kmfoldernode_h*/
diff --git a/kmail/kmfoldersearch.cpp b/kmail/kmfoldersearch.cpp
new file mode 100644
index 00000000..896383b4
--- /dev/null
+++ b/kmail/kmfoldersearch.cpp
@@ -0,0 +1,1141 @@
+/*
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2000 Don Sanders <sanders@kde.org>
+
+ 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
+*/
+
+//Factor byteswap stuff into one header file
+
+#include <config.h>
+
+#include "kmfoldersearch.h"
+#include "kmfolderimap.h"
+#include "kmfoldermgr.h"
+#include "kmsearchpattern.h"
+#include "kmmsgdict.h"
+#include "index.h"
+#include "jobscheduler.h"
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kconfig.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <utime.h>
+
+#include <qfile.h>
+
+#ifdef HAVE_BYTESWAP_H
+#include <byteswap.h>
+#endif
+
+// We define functions as kmail_swap_NN so that we don't get compile errors
+// on platforms where bswap_NN happens to be a function instead of a define.
+
+/* Swap bytes in 32 bit value. */
+#ifndef kmail_swap_32
+#ifdef bswap_32
+#define kmail_swap_32(x) bswap_32(x)
+#else
+#define kmail_swap_32(x) \
+ ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \
+ (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))
+#endif
+#endif // kmail_swap_32
+
+// Current version of the .index.search files
+#define IDS_SEARCH_VERSION 1000
+// The asterisk at the end is important
+#define IDS_SEARCH_HEADER "# KMail-Search-IDs V%d\n*"
+#define IDS_SEARCH_HEADER_LEN 30
+
+
+KMSearch::KMSearch(QObject * parent, const char * name)
+ :QObject(parent, name)
+{
+ mRemainingFolders = -1;
+ mRecursive = true;
+ mRunByIndex = mRunning = false;
+ mRoot = 0;
+ mSearchPattern = 0;
+ mFoundCount = 0;
+ mSearchCount = 0;
+
+ mProcessNextBatchTimer = new QTimer(0, "mProcessNextBatchTimer");
+ connect(mProcessNextBatchTimer, SIGNAL(timeout()), this, SLOT(slotProcessNextBatch()));
+}
+
+KMSearch::~KMSearch()
+{
+ delete mProcessNextBatchTimer;
+ delete mSearchPattern;
+}
+
+bool KMSearch::write(QString location) const
+{
+ KConfig config(location);
+ config.setGroup("Search Folder");
+ if (mSearchPattern)
+ mSearchPattern->writeConfig(&config);
+ if (mRoot.isNull())
+ config.writeEntry("Base", "");
+ else
+ config.writeEntry("Base", mRoot->idString());
+ config.writeEntry("Recursive", recursive());
+ return true;
+}
+
+bool KMSearch::read(QString location)
+{
+ KConfig config( location );
+ config.setGroup( "Search Folder" );
+ if ( !mSearchPattern )
+ mSearchPattern = new KMSearchPattern();
+ mSearchPattern->readConfig( &config );
+ QString rootString = config.readEntry( "Base" );
+ mRoot = kmkernel->findFolderById( rootString );
+ mRecursive = config.readBoolEntry( "Recursive" );
+ return true;
+}
+
+void KMSearch::setSearchPattern(KMSearchPattern *searchPattern)
+{
+ if ( running() )
+ stop();
+ if ( mSearchPattern != searchPattern ) {
+ delete mSearchPattern;
+ mSearchPattern = searchPattern;
+ }
+}
+
+bool KMSearch::inScope(KMFolder* folder) const
+{
+ if ( mRoot.isNull() || folder == mRoot )
+ return true;
+ if ( !recursive() )
+ return false;
+
+ KMFolderDir *rootDir = mRoot->child();
+ KMFolderDir *ancestorDir = folder->parent();
+ while ( ancestorDir ) {
+ if ( ancestorDir == rootDir )
+ return true;
+ ancestorDir = ancestorDir->parent();
+ }
+ return false;
+}
+
+void KMSearch::start()
+{
+ //close all referenced folders
+ QValueListIterator<QGuardedPtr<KMFolder> > fit;
+ for (fit = mOpenedFolders.begin(); fit != mOpenedFolders.end(); ++fit) {
+ if (!(*fit))
+ continue;
+ (*fit)->close( "kmsearch" );
+ }
+ mOpenedFolders.clear();
+ mFolders.clear();
+
+ if ( running() )
+ return;
+
+ if ( !mSearchPattern ) {
+ emit finished(true);
+ return;
+ }
+
+ mFoundCount = 0;
+ mSearchCount = 0;
+ mRunning = true;
+ mRunByIndex = false;
+ // check if this query can be done with the index
+ if ( kmkernel->msgIndex() && kmkernel->msgIndex()->startQuery( this ) ) {
+ mRunByIndex = true;
+ return;
+ }
+
+ mFolders.append( mRoot );
+ if ( recursive() )
+ {
+ //Append all descendants to folders
+ KMFolderNode* node;
+ KMFolder* folder;
+ QValueListConstIterator<QGuardedPtr<KMFolder> > it;
+ for ( it = mFolders.begin(); it != mFolders.end(); ++it )
+ {
+ folder = *it;
+ KMFolderDir *dir = 0;
+ if ( folder )
+ dir = folder->child();
+ else
+ dir = &kmkernel->folderMgr()->dir();
+ if ( !dir )
+ continue;
+ QPtrListIterator<KMFolderNode> it(*dir);
+ while ( (node = it.current()) ) {
+ ++it;
+ if ( !node->isDir() ) {
+ KMFolder* kmf = dynamic_cast<KMFolder*>( node );
+ if ( kmf )
+ mFolders.append( kmf );
+ }
+ }
+ }
+ }
+
+ mRemainingFolders = mFolders.count();
+ mLastFolder = QString::null;
+ mProcessNextBatchTimer->start( 0, true );
+}
+
+void KMSearch::stop()
+{
+ if ( !running() )
+ return;
+ if ( mRunByIndex ) {
+ if ( kmkernel->msgIndex() )
+ kmkernel->msgIndex()->stopQuery( this );
+ } else {
+ mIncompleteFolders.clear();
+ QValueListConstIterator<QGuardedPtr<KMFolder> > jt;
+ for ( jt = mOpenedFolders.begin(); jt != mOpenedFolders.end(); ++jt ) {
+ KMFolder *folder = *jt;
+ if ( !folder )
+ continue;
+ // explicitely stop jobs for this folder as it will not be closed below
+ // when the folder is currently selected
+ if ( folder->folderType() == KMFolderTypeImap ) {
+ KMAcctImap *account =
+ static_cast<KMFolderImap*>( folder->storage() )->account();
+ account->ignoreJobsForFolder( folder );
+ }
+ folder->storage()->search( 0 );
+ mSearchCount += folder->count();
+ folder->close("kmsearch");
+ }
+ }
+ mRemainingFolders = -1;
+ mOpenedFolders.clear();
+ mFolders.clear();
+ mLastFolder = QString::null;
+ mRunByIndex = mRunning = false;
+ emit finished(false);
+}
+
+void KMSearch::indexFinished() {
+ mRunning = false;
+ mRunByIndex = false;
+}
+
+void KMSearch::slotProcessNextBatch()
+{
+ if ( !running() )
+ return;
+
+ if ( mFolders.count() != 0 )
+ {
+ KMFolder *folder = *( mFolders.begin() );
+ mFolders.erase( mFolders.begin() );
+ if ( folder )
+ {
+ mLastFolder = folder->label();
+ folder->open("kmsearch");
+ mOpenedFolders.append( folder );
+ connect( folder->storage(),
+ SIGNAL( searchResult( KMFolder*, QValueList<Q_UINT32>, const KMSearchPattern*, bool ) ),
+ this,
+ SLOT( slotSearchFolderResult( KMFolder*, QValueList<Q_UINT32>, const KMSearchPattern*, bool ) ) );
+ folder->storage()->search( mSearchPattern );
+ } else
+ --mRemainingFolders;
+ mProcessNextBatchTimer->start( 0, true );
+ return;
+ }
+}
+
+void KMSearch::slotSearchFolderResult( KMFolder* folder,
+ QValueList<Q_UINT32> serNums,
+ const KMSearchPattern* pattern,
+ bool complete )
+{
+ if ( pattern != mSearchPattern )
+ return;
+ kdDebug(5006) << k_funcinfo << folder->label() << " found " << serNums.count() << endl;
+ mLastFolder = folder->label();
+ QValueListIterator<Q_UINT32> it;
+ for ( it = serNums.begin(); it != serNums.end(); ++it )
+ {
+ emit found( *it );
+ ++mFoundCount;
+ }
+ if ( complete )
+ {
+ disconnect( folder->storage(),
+ SIGNAL( searchResult( KMFolder*, QValueList<Q_UINT32>,
+ const KMSearchPattern*, bool ) ),
+ this,
+ SLOT( slotSearchFolderResult( KMFolder*, QValueList<Q_UINT32>,
+ const KMSearchPattern*, bool ) ) );
+ --mRemainingFolders;
+ mSearchCount += folder->count();
+ folder->close("kmsearch");
+ mOpenedFolders.remove( folder );
+ if ( mRemainingFolders <= 0 )
+ {
+ mRemainingFolders = 0;
+ mRunning = false;
+ mLastFolder = QString::null;
+ mRemainingFolders = -1;
+ mFolders.clear();
+ emit finished( true );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+KMFolderSearch::KMFolderSearch(KMFolder* folder, const char* name)
+ : FolderStorage(folder, name)
+{
+ mIdsStream = 0;
+ mSearch = 0;
+ mInvalid = false;
+ mUnlinked = true;
+ mTempOpened = false;
+ setNoChildren(true);
+
+ //Hook up some slots for live updating of search folders
+ //TODO: Optimize folderInvalidated, folderAdded, folderRemoved
+ connect(kmkernel->folderMgr(), SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
+ this, SLOT(examineAddedMessage(KMFolder*, Q_UINT32)));
+ connect(kmkernel->folderMgr(), SIGNAL(msgRemoved(KMFolder*, Q_UINT32)),
+ this, SLOT(examineRemovedMessage(KMFolder*, Q_UINT32)));
+ connect(kmkernel->folderMgr(), SIGNAL(msgChanged(KMFolder*, Q_UINT32, int)),
+ this, SLOT(examineChangedMessage(KMFolder*, Q_UINT32, int)));
+ connect(kmkernel->folderMgr(), SIGNAL(folderInvalidated(KMFolder*)),
+ this, SLOT(examineInvalidatedFolder(KMFolder*)));
+ connect(kmkernel->folderMgr(), SIGNAL(folderAdded(KMFolder*)),
+ this, SLOT(examineInvalidatedFolder(KMFolder*)));
+ connect(kmkernel->folderMgr(), SIGNAL(folderRemoved(KMFolder*)),
+ this, SLOT(examineRemovedFolder(KMFolder*)));
+ connect(kmkernel->folderMgr(), SIGNAL(msgHeaderChanged(KMFolder*,int)),
+ this, SLOT(propagateHeaderChanged(KMFolder*,int)));
+
+ connect(kmkernel->imapFolderMgr(), SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
+ this, SLOT(examineAddedMessage(KMFolder*, Q_UINT32)));
+ connect(kmkernel->imapFolderMgr(), SIGNAL(msgRemoved(KMFolder*, Q_UINT32)),
+ this, SLOT(examineRemovedMessage(KMFolder*, Q_UINT32)));
+ connect(kmkernel->imapFolderMgr(), SIGNAL(msgChanged(KMFolder*, Q_UINT32, int)),
+ this, SLOT(examineChangedMessage(KMFolder*, Q_UINT32, int)));
+ connect(kmkernel->imapFolderMgr(), SIGNAL(folderInvalidated(KMFolder*)),
+ this, SLOT(examineInvalidatedFolder(KMFolder*)));
+ connect(kmkernel->imapFolderMgr(), SIGNAL(folderAdded(KMFolder*)),
+ this, SLOT(examineInvalidatedFolder(KMFolder*)));
+ connect(kmkernel->imapFolderMgr(), SIGNAL(folderRemoved(KMFolder*)),
+ this, SLOT(examineRemovedFolder(KMFolder*)));
+ connect(kmkernel->imapFolderMgr(), SIGNAL(msgHeaderChanged(KMFolder*,int)),
+ this, SLOT(propagateHeaderChanged(KMFolder*,int)));
+
+ connect(kmkernel->dimapFolderMgr(), SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
+ this, SLOT(examineAddedMessage(KMFolder*, Q_UINT32)));
+ connect(kmkernel->dimapFolderMgr(), SIGNAL(msgRemoved(KMFolder*, Q_UINT32)),
+ this, SLOT(examineRemovedMessage(KMFolder*, Q_UINT32)));
+ connect(kmkernel->dimapFolderMgr(), SIGNAL(msgChanged(KMFolder*, Q_UINT32, int)),
+ this, SLOT(examineChangedMessage(KMFolder*, Q_UINT32, int)));
+ connect(kmkernel->dimapFolderMgr(), SIGNAL(folderInvalidated(KMFolder*)),
+ this, SLOT(examineInvalidatedFolder(KMFolder*)));
+ connect(kmkernel->dimapFolderMgr(), SIGNAL(folderAdded(KMFolder*)),
+ this, SLOT(examineInvalidatedFolder(KMFolder*)));
+ connect(kmkernel->dimapFolderMgr(), SIGNAL(folderRemoved(KMFolder*)),
+ this, SLOT(examineRemovedFolder(KMFolder*)));
+ connect(kmkernel->dimapFolderMgr(), SIGNAL(msgHeaderChanged(KMFolder*,int)),
+ this, SLOT(propagateHeaderChanged(KMFolder*,int)));
+
+ mExecuteSearchTimer = new QTimer(0, "mExecuteSearchTimer");
+ connect(mExecuteSearchTimer, SIGNAL(timeout()),
+ this, SLOT(executeSearch()));
+}
+
+KMFolderSearch::~KMFolderSearch()
+{
+ delete mExecuteSearchTimer;
+ delete mSearch;
+ mSearch = 0;
+ if (mOpenCount > 0)
+ close("~foldersearch", TRUE);
+}
+
+void KMFolderSearch::setSearch(KMSearch *search)
+{
+ truncateIndex(); //new search old index is obsolete
+ emit cleared();
+ mInvalid = false;
+ setDirty( true ); //have to write the index
+ if (!mUnlinked) {
+ unlink(QFile::encodeName(indexLocation()));
+ mUnlinked = true;
+ }
+ if (mSearch != search) {
+ mSearch->stop();
+ delete mSearch;
+ mSearch = search; // take ownership
+ if (mSearch) {
+ QObject::connect(search, SIGNAL(found(Q_UINT32)),
+ SLOT(addSerNum(Q_UINT32)));
+ QObject::connect(search, SIGNAL(finished(bool)),
+ SLOT(searchFinished(bool)));
+ }
+ }
+ if (mSearch)
+ mSearch->write(location());
+ clearIndex();
+ mTotalMsgs = 0;
+ mUnreadMsgs = 0;
+ emit numUnreadMsgsChanged( folder() );
+ emit changed(); // really want a kmfolder cleared signal
+ /* TODO There is KMFolder::cleared signal now. Adjust. */
+ if (mSearch)
+ mSearch->start();
+ open("foldersearch"); // will be closed in searchFinished
+}
+
+void KMFolderSearch::executeSearch()
+{
+ if (mSearch)
+ mSearch->stop();
+ setSearch(mSearch);
+ invalidateFolder();
+}
+
+const KMSearch* KMFolderSearch::search() const
+{
+ return mSearch;
+}
+
+void KMFolderSearch::searchFinished(bool success)
+{
+ if (!success)
+ mSerNums.clear();
+ close("foldersearch");
+}
+
+void KMFolderSearch::addSerNum(Q_UINT32 serNum)
+{
+ if (mInvalid) // A new search is scheduled don't bother doing anything
+ return;
+ int idx = -1;
+ KMFolder *aFolder = 0;
+ KMMsgDict::instance()->getLocation(serNum, &aFolder, &idx);
+ // warn instead of assert() because of
+ // https://intevation.de/roundup/kolab/issue2216
+ if (!aFolder || (idx == -1)) {
+ kdDebug(5006) << "Not adding message with serNum " << serNum
+ << ": folder is " << aFolder << ", index is " << idx << endl;
+ return;
+ }
+ if(mFolders.findIndex(aFolder) == -1) {
+ aFolder->open("foldersearch");
+ mFolders.append(aFolder);
+ }
+ setDirty( true ); //TODO append a single entry to .ids file and sync.
+ if (!mUnlinked) {
+ unlink(QFile::encodeName(indexLocation()));
+ mUnlinked = true;
+ }
+ mSerNums.append(serNum);
+ KMMsgBase *mb = aFolder->getMsgBase(idx);
+ if (mb && (mb->isUnread() || mb->isNew())) {
+ if (mUnreadMsgs == -1)
+ mUnreadMsgs = 0;
+ ++mUnreadMsgs;
+ emit numUnreadMsgsChanged( folder() );
+ }
+ emitMsgAddedSignals(mSerNums.count()-1);
+}
+
+void KMFolderSearch::removeSerNum(Q_UINT32 serNum)
+{
+ QValueVector<Q_UINT32>::const_iterator it;
+ int i = 0;
+ for(it = mSerNums.begin(); it != mSerNums.end(); ++it, ++i)
+ if ((*it) == serNum) {
+ int idx = -1;
+ KMFolder *aFolder = 0;
+ KMMsgDict::instance()->getLocation(serNum, &aFolder, &idx);
+ assert(aFolder && (idx != -1));
+ emit msgRemoved(folder(), serNum);
+ removeMsg(i);
+ return;
+ }
+ if (!mUnlinked) {
+ unlink(QFile::encodeName(indexLocation()));
+ mUnlinked = true;
+ }
+}
+
+int KMFolderSearch::addMsg(KMMessage*, int* index_return)
+{
+ //Not supported search folder can't own messages
+ *index_return = -1;
+ return 0;
+}
+
+bool KMFolderSearch::readSearch()
+{
+ mSearch = new KMSearch;
+ QObject::connect(mSearch, SIGNAL(found(Q_UINT32)), SLOT(addSerNum(Q_UINT32)));
+ QObject::connect(mSearch, SIGNAL(finished(bool)), SLOT(searchFinished(bool)));
+ return mSearch->read(location());
+}
+
+int KMFolderSearch::open(const char *)
+{
+ mOpenCount++;
+ kmkernel->jobScheduler()->notifyOpeningFolder( folder() );
+ if (mOpenCount > 1)
+ return 0; // already open
+
+ readConfig();
+ if (!mSearch && !readSearch())
+ return -1;
+
+ emit cleared();
+ if (!mSearch || !search()->running())
+ if (!readIndex()) {
+ executeSearch();
+ }
+
+ return 0;
+}
+
+int KMFolderSearch::canAccess()
+{
+ assert(!folder()->name().isEmpty());
+
+ if (access(QFile::encodeName(location()), R_OK | W_OK | X_OK) != 0)
+ return 1;
+ return 0;
+}
+
+void KMFolderSearch::sync()
+{
+ if (mDirty) {
+ if (mSearch)
+ mSearch->write(location());
+ updateIndex();
+ }
+}
+
+void KMFolderSearch::reallyDoClose(const char* owner)
+{
+ if (mAutoCreateIndex) {
+ if (mSearch)
+ mSearch->write(location());
+ updateIndex();
+ if (mSearch && search()->running())
+ mSearch->stop();
+ writeConfig();
+ }
+
+ //close all referenced folders
+ QValueListIterator<QGuardedPtr<KMFolder> > fit;
+ for (fit = mFolders.begin(); fit != mFolders.end(); ++fit) {
+ if (!(*fit))
+ continue;
+ (*fit)->close("foldersearch");
+ }
+ mFolders.clear();
+
+ clearIndex(TRUE);
+
+ if (mIdsStream)
+ fclose(mIdsStream);
+
+ mOpenCount = 0;
+ mIdsStream = 0;
+ mUnreadMsgs = -1;
+}
+
+int KMFolderSearch::create()
+{
+ int old_umask;
+ int rc = unlink(QFile::encodeName(location()));
+ if (!rc)
+ return rc;
+ rc = 0;
+
+ assert(!folder()->name().isEmpty());
+ assert(mOpenCount == 0);
+
+ kdDebug(5006) << "Creating folder " << location() << endl;
+ if (access(QFile::encodeName(location()), F_OK) == 0) {
+ kdDebug(5006) << "KMFolderSearch::create call to access function failed."
+ << endl;
+ return EEXIST;
+ }
+
+ old_umask = umask(077);
+ FILE *mStream = fopen(QFile::encodeName(location()), "w+");
+ umask(old_umask);
+ if (!mStream) return errno;
+ fclose(mStream);
+
+ clearIndex();
+ if (!mSearch) {
+ mSearch = new KMSearch();
+ QObject::connect(mSearch, SIGNAL(found(Q_UINT32)), SLOT(addSerNum(Q_UINT32)));
+ QObject::connect(mSearch, SIGNAL(finished(bool)), SLOT(searchFinished(bool)));
+ }
+ mSearch->write(location());
+ mOpenCount++;
+ mChanged = false;
+ mUnreadMsgs = 0;
+ mTotalMsgs = 0;
+ return rc;
+}
+
+int KMFolderSearch::compact( bool )
+{
+ needsCompact = false;
+ return 0;
+}
+
+bool KMFolderSearch::isReadOnly() const
+{
+ return false; //TODO: Make it true and get that working ok
+}
+
+FolderJob* KMFolderSearch::doCreateJob(KMMessage*, FolderJob::JobType,
+ KMFolder*, QString, const AttachmentStrategy* ) const
+{
+ // Should never be called
+ assert(0);
+ return 0;
+}
+
+FolderJob* KMFolderSearch::doCreateJob(QPtrList<KMMessage>&, const QString&,
+ FolderJob::JobType, KMFolder*) const
+{
+ // Should never be called
+ assert(0);
+ return 0;
+}
+
+const KMMsgBase* KMFolderSearch::getMsgBase(int idx) const
+{
+ int folderIdx = -1;
+ KMFolder *folder = 0;
+ if (idx < 0 || (Q_UINT32)idx >= mSerNums.count())
+ return 0;
+ KMMsgDict::instance()->getLocation(mSerNums[idx], &folder, &folderIdx);
+ assert(folder && (folderIdx != -1));
+ return folder->getMsgBase(folderIdx);
+}
+
+KMMsgBase* KMFolderSearch::getMsgBase(int idx)
+{
+ int folderIdx = -1;
+ KMFolder *folder = 0;
+ if (idx < 0 || (Q_UINT32)idx >= mSerNums.count())
+ return 0;
+ KMMsgDict::instance()->getLocation(mSerNums[idx], &folder, &folderIdx);
+ if (!folder || folderIdx == -1)
+ return 0; //exceptional case
+ return folder->getMsgBase(folderIdx);
+}
+
+//-----------------------------------------------------------------------------
+KMMessage* KMFolderSearch::getMsg(int idx)
+{
+ int folderIdx = -1;
+ KMFolder *folder = 0;
+ if (idx < 0 || (Q_UINT32)idx >= mSerNums.count())
+ return 0;
+ KMMsgDict::instance()->getLocation(mSerNums[idx], &folder, &folderIdx);
+ assert(folder && (folderIdx != -1));
+ KMMessage* msg = folder->getMsg( folderIdx );
+ return msg;
+}
+
+//-------------------------------------------------------------
+void
+KMFolderSearch::ignoreJobsForMessage( KMMessage* msg )
+{
+ if ( !msg || msg->transferInProgress() )
+ return;
+ /* While non-imap folders manage their jobs themselves, imap ones let
+ their account manage them. Therefor first clear the jobs managed by
+ this folder via the inherited method, then clear the imap ones. */
+ FolderStorage::ignoreJobsForMessage( msg );
+
+ if (msg->parent()->folderType() == KMFolderTypeImap) {
+ KMAcctImap *account =
+ static_cast<KMFolderImap*>( msg->storage() )->account();
+ if( !account )
+ return;
+ account->ignoreJobsForMessage( msg );
+ }
+}
+
+
+int KMFolderSearch::find(const KMMsgBase* msg) const
+{
+ int pos = 0;
+ Q_UINT32 serNum = msg->getMsgSerNum();
+ QValueVector<Q_UINT32>::const_iterator it;
+ for(it = mSerNums.begin(); it != mSerNums.end(); ++it) {
+ if ((*it) == serNum)
+ return pos;
+ ++pos;
+ }
+ return -1;
+}
+
+QString KMFolderSearch::indexLocation() const
+{
+ QString sLocation(folder()->path());
+
+ if (!sLocation.isEmpty()) sLocation += '/';
+ sLocation += '.';
+ sLocation += dotEscape(fileName());
+ sLocation += ".index";
+ sLocation += ".search";
+
+ return sLocation;
+}
+
+int KMFolderSearch::updateIndex()
+{
+ if (mSearch && search()->running())
+ unlink(QFile::encodeName(indexLocation()));
+ else
+ if (dirty())
+ return writeIndex();
+ return 0;
+}
+
+int KMFolderSearch::writeIndex( bool )
+{
+ // TODO:If we fail to write the index we should panic the kernel
+ // TODO:and the same for other folder types too, and the msgDict.
+ QString filename = indexLocation();
+ int old_umask = umask(077);
+ QString tempName = filename + ".temp";
+ unlink(QFile::encodeName(tempName));
+
+ // We touch the folder, otherwise the index is regenerated, if KMail is
+ // running, while the clock switches from daylight savings time to normal time
+ utime(QFile::encodeName(location()), 0);
+
+ FILE *tmpIndexStream = fopen(QFile::encodeName(tempName), "w");
+ umask(old_umask);
+
+ if (!tmpIndexStream) {
+ kdDebug(5006) << "Cannot write '" << filename
+ << strerror(errno) << " (" << errno << ")" << endl;
+ truncate(QFile::encodeName(filename), 0);
+ return -1;
+ }
+ fprintf(tmpIndexStream, IDS_SEARCH_HEADER, IDS_SEARCH_VERSION);
+ Q_UINT32 byteOrder = 0x12345678;
+ fwrite(&byteOrder, sizeof(byteOrder), 1, tmpIndexStream);
+
+ Q_UINT32 count = mSerNums.count();
+ if (!fwrite(&count, sizeof(count), 1, tmpIndexStream)) {
+ fclose(tmpIndexStream);
+ truncate(QFile::encodeName(filename), 0);
+ return -1;
+ }
+
+ QValueVector<Q_UINT32>::iterator it;
+ for(it = mSerNums.begin(); it != mSerNums.end(); ++it) {
+ Q_UINT32 serNum = *it;
+ if (!fwrite(&serNum, sizeof(serNum), 1, tmpIndexStream))
+ return -1;
+ }
+ if (ferror(tmpIndexStream)) return ferror(tmpIndexStream);
+ if (fflush(tmpIndexStream) != 0) return errno;
+ if (fsync(fileno(tmpIndexStream)) != 0) return errno;
+ if (fclose(tmpIndexStream) != 0) return errno;
+
+ ::rename(QFile::encodeName(tempName), QFile::encodeName(indexLocation()));
+ mDirty = FALSE;
+ mUnlinked = FALSE;
+
+ return 0;
+}
+
+DwString KMFolderSearch::getDwString(int idx)
+{
+ return getMsgBase(idx)->parent()->getDwString( idx );
+}
+
+KMMessage* KMFolderSearch::readMsg(int idx)
+{
+ int folderIdx = -1;
+ KMFolder *folder = 0;
+ KMMsgDict::instance()->getLocation(mSerNums[idx], &folder, &folderIdx);
+ assert(folder && (folderIdx != -1));
+ return folder->getMsg( folderIdx );
+}
+
+bool KMFolderSearch::readIndex()
+{
+ clearIndex();
+ QString filename = indexLocation();
+ mIdsStream = fopen(QFile::encodeName(filename), "r+");
+ if (!mIdsStream)
+ return false;
+
+ int version = 0;
+ fscanf(mIdsStream, IDS_SEARCH_HEADER, &version);
+ if (version != IDS_SEARCH_VERSION) {
+ fclose(mIdsStream);
+ mIdsStream = 0;
+ return false;
+ }
+ bool swapByteOrder;
+ Q_UINT32 byte_order;
+ if (!fread(&byte_order, sizeof(byte_order), 1, mIdsStream)) {
+ fclose(mIdsStream);
+ mIdsStream = 0;
+ return false;
+ }
+ swapByteOrder = (byte_order == 0x78563412);
+
+ Q_UINT32 count;
+ if (!fread(&count, sizeof(count), 1, mIdsStream)) {
+ fclose(mIdsStream);
+ mIdsStream = 0;
+ return false;
+ }
+ if (swapByteOrder)
+ count = kmail_swap_32(count);
+
+ mUnreadMsgs = 0;
+ mSerNums.reserve(count);
+ for (unsigned int index = 0; index < count; index++) {
+ Q_UINT32 serNum;
+ int folderIdx = -1;
+ KMFolder *folder = 0;
+ bool readOk = fread(&serNum, sizeof(serNum), 1, mIdsStream);
+ if (!readOk) {
+ clearIndex();
+ fclose(mIdsStream);
+ mIdsStream = 0;
+ return false;
+ }
+ if (swapByteOrder)
+ serNum = kmail_swap_32(serNum);
+
+ KMMsgDict::instance()->getLocation( serNum, &folder, &folderIdx );
+ if (!folder || (folderIdx == -1)) {
+ clearIndex();
+ fclose(mIdsStream);
+ mIdsStream = 0;
+ return false;
+ }
+ mSerNums.push_back(serNum);
+ if(mFolders.findIndex(folder) == -1) {
+ if (mInvalid) //exceptional case for when folder has invalid ids
+ return false;
+ folder->open("foldersearch");
+ mFolders.append(folder);
+ }
+ KMMsgBase *mb = folder->getMsgBase(folderIdx);
+ if (!mb) //Exceptional case our .ids file is messed up
+ return false;
+ if (mb->isNew() || mb->isUnread()) {
+ if (mUnreadMsgs == -1) ++mUnreadMsgs;
+ ++mUnreadMsgs;
+ }
+ }
+ mTotalMsgs = mSerNums.count();
+ fclose(mIdsStream);
+ mIdsStream = 0;
+ mUnlinked = true;
+ return true;
+}
+
+int KMFolderSearch::removeContents()
+{
+ unlink(QFile::encodeName(location()));
+ unlink(QFile::encodeName(indexLocation()));
+ mUnlinked = true;
+ return 0;
+}
+
+int KMFolderSearch::expungeContents()
+{
+ setSearch(new KMSearch());
+ return 0;
+}
+
+int KMFolderSearch::count(bool cache) const
+{
+ Q_UNUSED(cache);
+ return mSerNums.count();
+}
+
+KMMsgBase* KMFolderSearch::takeIndexEntry(int idx)
+{
+ assert(idx >= 0 && idx < (int)mSerNums.count());
+ KMMsgBase *msgBase = getMsgBase(idx);
+ QValueVector<Q_UINT32>::iterator it = mSerNums.begin();
+ mSerNums.erase(&it[idx]);
+ return msgBase;
+}
+
+KMMsgInfo* KMFolderSearch::setIndexEntry(int idx, KMMessage *msg)
+{
+ assert(idx >= 0 && idx < (int)mSerNums.count());
+ Q_UNUSED( idx );
+ return msg->storage()->setIndexEntry(msg->parent()->find(msg), msg);
+}
+
+void KMFolderSearch::clearIndex(bool, bool)
+{
+ //close all referenced folders
+ QValueListIterator<QGuardedPtr<KMFolder> > fit;
+ for (fit = mFolders.begin(); fit != mFolders.end(); ++fit) {
+ if (!(*fit))
+ continue;
+ (*fit)->close("foldersearch");
+ }
+ mFolders.clear();
+
+ mSerNums.clear();
+}
+
+void KMFolderSearch::truncateIndex()
+{
+ truncate(QFile::encodeName(indexLocation()), IDS_SEARCH_HEADER_LEN);
+}
+
+void KMFolderSearch::examineAddedMessage(KMFolder *aFolder, Q_UINT32 serNum)
+{
+ if (!search() && !readSearch())
+ return;
+ if (!search()->inScope(aFolder))
+ return;
+ if (!mTempOpened) {
+ open("foldersearch");
+ mTempOpened = true;
+ }
+
+ if (!search()->searchPattern())
+ return;
+
+ int idx = -1;
+ KMFolder *folder = 0;
+ KMMsgDict::instance()->getLocation(serNum, &folder, &idx);
+ assert(folder && (idx != -1));
+ assert(folder == aFolder);
+ KMFolderOpener openFolder(folder, "foldersearch");
+
+ // if we are already checking this folder, refcount
+ if ( mFoldersCurrentlyBeingSearched.contains( folder ) ) {
+ unsigned int count = mFoldersCurrentlyBeingSearched[folder];
+ mFoldersCurrentlyBeingSearched.replace( folder, count+1 );
+ } else {
+ connect( folder->storage(),
+ SIGNAL( searchDone( KMFolder*, Q_UINT32, const KMSearchPattern*, bool ) ),
+ this,
+ SLOT( slotSearchExamineMsgDone( KMFolder*, Q_UINT32,
+ const KMSearchPattern*, bool ) ) );
+ mFoldersCurrentlyBeingSearched.insert( folder, 1 );
+ }
+ folder->storage()->search( search()->searchPattern(), serNum );
+}
+
+void KMFolderSearch::slotSearchExamineMsgDone( KMFolder* folder,
+ Q_UINT32 serNum,
+ const KMSearchPattern* pattern,
+ bool matches )
+{
+ if ( search()->searchPattern() != pattern ) return;
+ kdDebug(5006) << folder->label() << ": serNum " << serNum
+ << " matches?" << matches << endl;
+ KMFolderOpener openFolder(folder, "foldersearch");
+
+ Q_ASSERT( mFoldersCurrentlyBeingSearched.contains( folder ) );
+
+ unsigned int count = mFoldersCurrentlyBeingSearched[folder];
+ if ( count == 1 ) {
+ disconnect( folder->storage(),
+ SIGNAL( searchDone( KMFolder*, Q_UINT32,
+ const KMSearchPattern*, bool ) ),
+ this,
+ SLOT( slotSearchExamineMsgDone( KMFolder*, Q_UINT32,
+ const KMSearchPattern*, bool ) ) );
+ mFoldersCurrentlyBeingSearched.remove( folder );
+ } else {
+ mFoldersCurrentlyBeingSearched.replace( folder, count-1 );
+ }
+
+ if ( !matches ) {
+ QValueVector<Q_UINT32>::const_iterator it;
+ it = qFind( mSerNums.begin(), mSerNums.end(), serNum );
+ if (it != mSerNums.end()) {
+ removeSerNum( serNum );
+ }
+ return;
+ }
+
+// if (mSearch->running()) {
+// mSearch->stop();
+// mExecuteSearchTimer->start( 0, true );
+// } else {
+ QValueVector<Q_UINT32>::const_iterator it;
+ it = qFind( mSerNums.begin(), mSerNums.end(), serNum );
+ if (it == mSerNums.end()) {
+ addSerNum( serNum );
+ }
+// }
+}
+
+void KMFolderSearch::examineRemovedMessage(KMFolder *folder, Q_UINT32 serNum)
+{
+ if (!search() && !readSearch())
+ return;
+ if (!search()->inScope(folder))
+ return;
+ if (!mTempOpened) {
+ open("foldersearch");
+ mTempOpened = true;
+ }
+
+ if (mSearch->running()) {
+ mExecuteSearchTimer->start(0, true);
+ } else {
+ removeSerNum(serNum);
+ }
+}
+
+void KMFolderSearch::examineChangedMessage(KMFolder *aFolder, Q_UINT32 serNum, int delta)
+{
+ if (!search() && !readSearch())
+ return;
+ if (!search()->inScope(aFolder))
+ return;
+ if (!mTempOpened) {
+ open("foldersearch");
+ mTempOpened = true;
+ }
+ QValueVector<Q_UINT32>::const_iterator it;
+ it = qFind( mSerNums.begin(), mSerNums.end(), serNum );
+ if (it != mSerNums.end()) {
+ mUnreadMsgs += delta;
+ emit numUnreadMsgsChanged( folder() );
+ emit msgChanged( folder(), serNum, delta );
+ }
+}
+
+void KMFolderSearch::examineInvalidatedFolder(KMFolder *folder)
+{
+ if (!search() && !readSearch())
+ return;
+ if (!search()->inScope(folder))
+ return;
+ if (mTempOpened) {
+ close("foldersearch");
+ mTempOpened = false;
+ }
+
+ mInvalid = true;
+ if (mSearch)
+ mSearch->stop();
+
+ if (!mUnlinked) {
+ unlink(QFile::encodeName(indexLocation()));
+ mUnlinked = true;
+ }
+
+ if (!isOpened()) //give up, until the user manually opens the folder
+ return;
+
+ if (!mTempOpened) {
+ open("foldersearch");
+ mTempOpened = true;
+ }
+ mExecuteSearchTimer->start(0, true);
+}
+
+void KMFolderSearch::examineRemovedFolder(KMFolder *folder)
+{
+ examineInvalidatedFolder(folder);
+ if (mSearch->root() == folder) {
+ delete mSearch;
+ mSearch = 0;
+ }
+}
+
+void KMFolderSearch::propagateHeaderChanged(KMFolder *aFolder, int idx)
+{
+ int pos = 0;
+ if (!search() && !readSearch())
+ return;
+ if (!search()->inScope(aFolder))
+ return;
+ if (!mTempOpened) {
+ open("foldersearch");
+ mTempOpened = true;
+ }
+
+ Q_UINT32 serNum = KMMsgDict::instance()->getMsgSerNum(aFolder, idx);
+ QValueVector<Q_UINT32>::const_iterator it;
+ for(it = mSerNums.begin(); it != mSerNums.end(); ++it) {
+ if ((*it) == serNum) {
+ emit msgHeaderChanged(folder(), pos);
+ break;
+ }
+ ++pos;
+ }
+ // let's try if the message matches our search
+ KMFolderOpener openAFolder(aFolder, "foldersearch");
+
+ // if we are already checking this folder, refcount
+ if ( mFoldersCurrentlyBeingSearched.contains( aFolder ) ) {
+ unsigned int count = mFoldersCurrentlyBeingSearched[aFolder];
+ mFoldersCurrentlyBeingSearched.replace( aFolder, count+1 );
+ } else {
+ connect( aFolder->storage(),
+ SIGNAL( searchDone( KMFolder*, Q_UINT32, const KMSearchPattern*, bool ) ),
+ this,
+ SLOT( slotSearchExamineMsgDone( KMFolder*, Q_UINT32,
+ const KMSearchPattern*, bool ) ) );
+ mFoldersCurrentlyBeingSearched.insert( aFolder, 1 );
+ }
+ aFolder->storage()->search( search()->searchPattern(), serNum );
+}
+
+void KMFolderSearch::tryReleasingFolder(KMFolder* folder)
+{
+ // We'll succeed releasing the folder only if mTempOpened and mOpenCount==1.
+ // Otherwise if mOpenCount>1 (e.g while the search dialog is up), we would just keep closing/reopening for nothing
+ if ( mTempOpened && mOpenCount == 1 )
+ {
+ examineInvalidatedFolder( folder );
+ }
+}
+
+#include "kmfoldersearch.moc"
diff --git a/kmail/kmfoldersearch.h b/kmail/kmfoldersearch.h
new file mode 100644
index 00000000..9acb8072
--- /dev/null
+++ b/kmail/kmfoldersearch.h
@@ -0,0 +1,216 @@
+/*
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2000 Don Sanders <sanders@kde.org>
+
+ 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
+*/
+// Dynamic search folder
+
+#ifndef kmfoldersearch_h
+#define kmfoldersearch_h
+
+#include <qguardedptr.h>
+#include <qvaluelist.h>
+#include <qvaluevector.h>
+#include <qvaluestack.h>
+#include <qmap.h>
+#include "kmfolder.h"
+#include "folderstorage.h"
+
+/** A search folder is a folder that shows the result of evaluating a
+ search expression. This folder is dynamically updated as the
+ search expression is applied to new mail (from a pop or imap server
+ or from a local account).
+
+ The index for a search folder consists of a cache of serial
+ numbers of all messages that currently match the search.
+**/
+
+typedef QValueList<Q_UINT32> SerNumList;
+class KMSearchPattern;
+class KMFolderImap;
+class KMFolderSearchJob;
+class KMIndexSearchTarget;
+class QTimer;
+
+namespace KMail {
+ class AttachmentStrategy;
+}
+using KMail::AttachmentStrategy;
+
+class KMSearch: public QObject
+{
+ Q_OBJECT
+
+public:
+ KMSearch(QObject * parent = 0, const char * name = 0);
+ ~KMSearch();
+
+ bool write(QString location) const;
+ bool read(QString location);
+ bool recursive() const { return mRecursive; }
+ void setRecursive(bool recursive) { if (running()) stop(); mRecursive = recursive; }
+ KMFolder* root() const { return mRoot; }
+ void setRoot(KMFolder *folder) { if (running()) stop(); mRoot = folder; }
+ bool inScope(KMFolder* folder) const;
+ //Takes ownership of @searchPattern
+ void setSearchPattern(KMSearchPattern *searchPattern);
+ KMSearchPattern* searchPattern() const { return mSearchPattern; }
+ void start();
+ bool running() const { return mRunning; }
+ void stop();
+ int foundCount() const { return mFoundCount; }
+ int searchCount() const { return mSearchCount; }
+ QString currentFolder() const { return mLastFolder; }
+
+public slots:
+ void indexFinished();
+
+signals:
+ void found(Q_UINT32 serNum);
+ void finished(bool success);
+
+protected slots:
+ void slotProcessNextBatch();
+ void slotSearchFolderResult( KMFolder*, QValueList<Q_UINT32>,
+ const KMSearchPattern*, bool );
+
+protected:
+ friend class ::KMIndexSearchTarget;
+ void setRunning(bool b) { mRunning = b; }
+ void setFoundCount(int f) { mFoundCount = f; }
+ void setCurrentFolder(const QString &f) { mLastFolder = f; }
+
+private:
+ int mRemainingFolders;
+ bool mRecursive, mRunning, mIdle, mRunByIndex;
+ QGuardedPtr<KMFolder> mRoot;
+ KMSearchPattern* mSearchPattern;
+ QValueList<QGuardedPtr<KMFolder> > mFolders, mOpenedFolders;
+ QValueList<QGuardedPtr<KMFolderImap> > mIncompleteFolders;
+ SerNumList mSerNums;
+ QString mLastFolder;
+ int mFoundCount;
+ int mSearchCount;
+ QTimer *mProcessNextBatchTimer;
+};
+
+class KMFolderSearch: public FolderStorage
+{
+ Q_OBJECT
+ friend class ::KMFolderSearchJob;
+public:
+ KMFolderSearch(KMFolder* folder, const char* name=0);
+ virtual ~KMFolderSearch();
+
+ /** Returns the type of this folder */
+ virtual KMFolderType folderType() const { return KMFolderTypeSearch; }
+
+ // Sets and runs the search used by the folder
+ void setSearch(KMSearch *search);
+ // Returns the current search used by the folder
+ const KMSearch* search() const;
+ // Stops the current search
+ void stopSearch() { if (mSearch) mSearch->stop(); }
+
+ virtual KMMessage* getMsg(int idx);
+ virtual void ignoreJobsForMessage( KMMessage* );
+
+ virtual void tryReleasingFolder(KMFolder* folder);
+
+ /** Returns true if this folder can be moved */
+ virtual bool isMoveable() const { return false; }
+
+protected slots:
+ // Reads search definition for this folder and creates a KMSearch
+ bool readSearch();
+ // Runs the current search again
+ void executeSearch();
+ // Called when the search is finished
+ void searchFinished(bool success);
+ // Look at a new message and if it matches search() add it to the cache
+ void examineAddedMessage(KMFolder *folder, Q_UINT32 serNum);
+ // Look at a removed message and remove it from the cache
+ void examineRemovedMessage(KMFolder *folder, Q_UINT32 serNum);
+ // Look at a message whose status has changed
+ void examineChangedMessage(KMFolder *folder, Q_UINT32 serNum, int delta);
+ // The serial numbers for a folder have been invalidated, deal with it
+ void examineInvalidatedFolder(KMFolder *folder);
+ // A folder has been deleted, deal with it
+ void examineRemovedFolder(KMFolder *folder);
+ // Propagate the msgHeaderChanged signal
+ void propagateHeaderChanged(KMFolder *folder, int idx);
+
+public slots:
+ // Appends the serial number to the cached list of messages that match
+ // the search for this folder
+ void addSerNum(Q_UINT32 serNum);
+ // Removes the serial number from the cached list of messages that match
+ // the search for this folder
+ void removeSerNum(Q_UINT32 serNum);
+
+ /** Incrementally update the index if possible else call writeIndex */
+ virtual int updateIndex();
+
+ // Examine the message
+ void slotSearchExamineMsgDone( KMFolder*, Q_UINT32 serNum,
+ const KMSearchPattern*, bool );
+
+public:
+ //See base class for documentation
+ virtual int addMsg(KMMessage* msg, int* index_return = 0);
+ virtual int open(const char *owner);
+ virtual int canAccess();
+ virtual void sync();
+ virtual void reallyDoClose(const char* owner);
+ virtual int create();
+ virtual int compact( bool );
+ virtual bool isReadOnly() const;
+ virtual const KMMsgBase* getMsgBase(int idx) const;
+ virtual KMMsgBase* getMsgBase(int idx);
+ virtual int find(const KMMsgBase* msg) const;
+ virtual QString indexLocation() const;
+ virtual int writeIndex( bool createEmptyIndex = false );
+ DwString getDwString(int idx);
+ Q_UINT32 serNum(int idx) { return mSerNums[idx]; }
+
+protected:
+ virtual FolderJob* doCreateJob(KMMessage *msg, FolderJob::JobType jt,
+ KMFolder *folder, QString partSpecifier,
+ const AttachmentStrategy *as ) const;
+ virtual FolderJob* doCreateJob(QPtrList<KMMessage>& msgList, const QString& sets,
+ FolderJob::JobType jt, KMFolder *folder) const;
+ virtual KMMessage* readMsg(int idx);
+ virtual bool readIndex();
+ virtual int removeContents();
+ virtual int expungeContents();
+ virtual int count(bool cache = false) const;
+ virtual KMMsgBase* takeIndexEntry(int idx);
+ virtual KMMsgInfo* setIndexEntry(int idx, KMMessage *msg);
+ virtual void clearIndex(bool autoDelete=true, bool syncDict = false);
+ virtual void truncateIndex();
+
+private:
+ QValueVector<Q_UINT32> mSerNums;
+ QValueList<QGuardedPtr<KMFolder> > mFolders;
+ QValueStack<Q_UINT32> mUnexaminedMessages;
+ FILE *mIdsStream;
+ KMSearch *mSearch;
+ bool mInvalid, mUnlinked;
+ bool mTempOpened;
+ QTimer *mExecuteSearchTimer;
+ QMap<const KMFolder*, unsigned int>mFoldersCurrentlyBeingSearched;
+};
+#endif /*kmfoldersearch_h*/
+
diff --git a/kmail/kmfolderseldlg.cpp b/kmail/kmfolderseldlg.cpp
new file mode 100644
index 00000000..21fc5323
--- /dev/null
+++ b/kmail/kmfolderseldlg.cpp
@@ -0,0 +1,574 @@
+// kmfolderseldlg.cpp
+
+#include <config.h>
+#include "kmfolderseldlg.h"
+#include "kmfoldertree.h"
+#include "kmfolder.h"
+#include "kmmainwidget.h"
+#include "globalsettings.h"
+
+#include <kdebug.h>
+#include <klineedit.h>
+#include <kpopupmenu.h>
+#include <kiconloader.h>
+
+#include <qlayout.h>
+#include <qtoolbutton.h>
+
+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( QPainter *p, const QColorGroup &cg,
+ int column, int width, int alignment )
+ {
+ KListView* view = static_cast< KListView* >( listView() );
+
+ // Set alternate background to invalid
+ QColor nocolor;
+ QColor alt = view->alternateBackground();
+ view->setAlternateBackground( nocolor );
+
+ // Set the base and text to the appropriate colors
+ QColorGroup *cgroup = (QColorGroup *)&view->viewport()->colorGroup();
+ QColor base = cgroup->base();
+ QColor text = cgroup->text();
+ cgroup->setColor( QColorGroup::Base, isAlternate() ? alt : base );
+ cgroup->setColor( QColorGroup::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( QColorGroup::Base, base );
+ cgroup->setColor( QColorGroup::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( QWidget * parent,
+ KMFolderTree * folderTree,
+ const QString & preSelection,
+ bool mustBeReadWrite )
+ : KFolderTree( parent ), mFolderTree( folderTree )
+{
+ setSelectionModeExt( Single );
+ mFolderColumn = addColumn( i18n( "Folder" ), 0 );
+ mPathColumn = addColumn( i18n( "Path" ), 0 );
+ setAllColumnsShowFocus( true );
+ setAlternateBackground( QColor( 0xf0, 0xf0, 0xf0 ) );
+
+ reload( mustBeReadWrite, true, true, preSelection );
+ readColorConfig();
+
+ applyFilter( "" );
+
+ connect(this, SIGNAL(collapsed(QListViewItem*)), SLOT(recolorRows()));
+ connect(this, SIGNAL(expanded(QListViewItem*)), SLOT(recolorRows()));
+
+ connect( this, SIGNAL( contextMenuRequested( QListViewItem*, const QPoint &, int ) ),
+ this, SLOT( slotContextMenuRequested( QListViewItem*, const QPoint & ) ) );
+}
+
+//-----------------------------------------------------------------------------
+void SimpleFolderTree::reload( bool mustBeReadWrite, bool showOutbox,
+ bool showImapFolders, const QString& preSelection )
+{
+ mLastMustBeReadWrite = mustBeReadWrite;
+ mLastShowOutbox = showOutbox;
+ mLastShowImapFolders = showImapFolders;
+
+ clear();
+ FolderItem * lastItem = 0;
+ FolderItem * lastTopItem = 0;
+ FolderItem * selectedItem = 0;
+ int lastDepth = 0;
+
+ QString selected = preSelection;
+ if ( selected.isEmpty() && folder() )
+ selected = folder()->idString();
+
+ mFilter = "";
+ QString path;
+
+ for ( QListViewItemIterator it( mFolderTree ) ; it.current() ; ++it )
+ {
+ KMFolderTreeItem * fti = static_cast<KMFolderTreeItem *>( 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<FolderItem*>(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<FolderItem *>( lastItem->parent() );
+ }
+ if ( lastItem->parent() ) {
+ item = new FolderItem( static_cast<FolderItem*>(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
+{
+ QListViewItem * item = currentItem();
+ if ( item ) {
+ const KMFolder * folder = static_cast<FolderItem *>( item )->folder();
+ if( folder ) return folder;
+ }
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+void SimpleFolderTree::setFolder( KMFolder *folder )
+{
+ for ( QListViewItemIterator it( this ) ; it.current() ; ++it )
+ {
+ const KMFolder *fld = static_cast<FolderItem *>( it.current() )->folder();
+ if ( fld == folder )
+ {
+ setSelected( it.current(), true );
+ ensureItemVisible( it.current() );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void SimpleFolderTree::setFolder( const QString& 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( QListViewItem *lvi,
+ const QPoint &p )
+{
+ if (!lvi)
+ return;
+ setCurrentItem( lvi );
+ setSelected( lvi, TRUE );
+
+ const KMFolder * folder = static_cast<FolderItem *>( 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,
+ SLOT(addChildFolder()));
+ kmkernel->setContextMenuShown( true );
+ folderMenu->exec (p, 0);
+ kmkernel->setContextMenuShown( false );
+ delete folderMenu;
+ folderMenu = 0;
+}
+
+//-----------------------------------------------------------------------------
+void SimpleFolderTree::readColorConfig (void)
+{
+ QColor c1=QColor(kapp->palette().active().text());
+ QColor c2=QColor(kapp->palette().active().base());
+
+ mPaintInfo.colFore = c1;
+ mPaintInfo.colBack = c2;
+
+ QPalette newPal = kapp->palette();
+ newPal.setColor( QColorGroup::Base, mPaintInfo.colBack );
+ newPal.setColor( QColorGroup::Text, mPaintInfo.colFore );
+ setPalette( newPal );
+}
+
+
+//-----------------------------------------------------------------------------
+static int recurseFilter( QListViewItem * item, const QString& filter, int column )
+{
+ if ( item == 0 )
+ return 0;
+
+ QListViewItem * 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;
+ QListViewItemIterator it ( this );
+ while ( it.current() ) {
+ FolderItem * item = static_cast< FolderItem* >( it.current() );
+
+ if ( item->isVisible() ) {
+ bool visible = true;
+ QListViewItem * 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 QString& filter )
+{
+ // Reset all items to visible, enabled, and open
+ QListViewItemIterator clean( this );
+ while ( clean.current() ) {
+ QListViewItem * 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.
+ QListViewItemIterator it( this );
+ while ( it.current() ) {
+ QListViewItem * 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
+ QListViewItemIterator 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( QKeyEvent *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 );
+}
+
+
+//-----------------------------------------------------------------------------
+KMFolderSelDlg::KMFolderSelDlg( KMMainWidget * parent, const QString& caption,
+ bool mustBeReadWrite, bool useGlobalSettings )
+ : KDialogBase( parent, "folder dialog", true, caption,
+ Ok|Cancel|User1, Ok, true,
+ KGuiItem(i18n("&New Subfolder..."), "folder_new",
+ i18n("Create a new subfolder under the currently selected folder"))
+ ), // mainwin as parent, modal
+ mUseGlobalSettings( useGlobalSettings )
+{
+ KMFolderTree * ft = parent->folderTree();
+ assert( ft );
+
+ QString preSelection = mUseGlobalSettings ?
+ GlobalSettings::self()->lastSelectedFolder() : QString::null;
+ mTreeView = new KMail::SimpleFolderTree( makeVBoxMainWidget(), ft,
+ preSelection, mustBeReadWrite );
+ init();
+}
+
+//----------------------------------------------------------------------------
+KMFolderSelDlg::KMFolderSelDlg( QWidget * parent, KMFolderTree * tree,
+ const QString& caption, bool mustBeReadWrite, bool useGlobalSettings )
+ : KDialogBase( parent, "folder dialog", true, caption,
+ Ok|Cancel|User1, Ok, true,
+ KGuiItem(i18n("&New Subfolder..."), "folder_new",
+ i18n("Create a new subfolder under the currently selected folder"))
+ ), // mainwin as parent, modal
+ mUseGlobalSettings( useGlobalSettings )
+{
+ QString preSelection = mUseGlobalSettings ?
+ GlobalSettings::self()->lastSelectedFolder() : QString::null;
+ mTreeView = new KMail::SimpleFolderTree( makeVBoxMainWidget(), tree,
+ preSelection, mustBeReadWrite );
+ init();
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderSelDlg::init()
+{
+ mTreeView->setFocus();
+ connect( mTreeView, SIGNAL( doubleClicked( QListViewItem*, const QPoint&, int ) ),
+ this, SLOT( slotSelect() ) );
+ connect( mTreeView, SIGNAL( selectionChanged() ),
+ this, SLOT( slotUpdateBtnStatus() ) );
+
+ readConfig();
+}
+
+//-----------------------------------------------------------------------------
+KMFolderSelDlg::~KMFolderSelDlg()
+{
+ const KMFolder * cur = folder();
+ if ( cur && mUseGlobalSettings ) {
+ GlobalSettings::self()->setLastSelectedFolder( cur->idString() );
+ }
+
+ writeConfig();
+}
+
+
+//-----------------------------------------------------------------------------
+KMFolder * KMFolderSelDlg::folder( void )
+{
+ return ( KMFolder * ) mTreeView->folder();
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderSelDlg::setFolder( KMFolder* folder )
+{
+ mTreeView->setFolder( folder );
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderSelDlg::slotSelect()
+{
+ accept();
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderSelDlg::slotUser1()
+{
+ mTreeView->addChildFolder();
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderSelDlg::slotUpdateBtnStatus()
+{
+ enableButton( User1, folder() &&
+ ( !folder()->noContent() && !folder()->noChildren() ) );
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderSelDlg::setFlags( bool mustBeReadWrite, bool showOutbox,
+ bool showImapFolders )
+{
+ mTreeView->reload( mustBeReadWrite, showOutbox, showImapFolders );
+}
+
+void KMFolderSelDlg::readConfig()
+{
+ KConfig *config = KGlobal::config();
+ config->setGroup( "FolderSelectionDialog" );
+
+ QSize size = config->readSizeEntry( "Size" );
+ if ( !size.isEmpty() ) resize( size );
+ else resize( 500, 300 );
+
+ QValueList<int> widths = config->readIntListEntry( "ColumnWidths" );
+ if ( !widths.isEmpty() ) {
+ mTreeView->setColumnWidth(mTreeView->mFolderColumn, widths[0]);
+ mTreeView->setColumnWidth(mTreeView->mPathColumn, widths[1]);
+ }
+ else {
+ int colWidth = width() / 2;
+ mTreeView->setColumnWidth(mTreeView->mFolderColumn, colWidth);
+ mTreeView->setColumnWidth(mTreeView->mPathColumn, colWidth);
+ }
+}
+
+void KMFolderSelDlg::writeConfig()
+{
+ KConfig *config = KGlobal::config();
+ config->setGroup( "FolderSelectionDialog" );
+ config->writeEntry( "Size", size() );
+
+ QValueList<int> widths;
+ widths.push_back(mTreeView->columnWidth(mTreeView->mFolderColumn));
+ widths.push_back(mTreeView->columnWidth(mTreeView->mPathColumn));
+ config->writeEntry( "ColumnWidths", widths );
+}
+
+} // namespace KMail
+
+#include "kmfolderseldlg.moc"
diff --git a/kmail/kmfolderseldlg.h b/kmail/kmfolderseldlg.h
new file mode 100644
index 00000000..3adef415
--- /dev/null
+++ b/kmail/kmfolderseldlg.h
@@ -0,0 +1,119 @@
+/* KMail Folder Selection Dialog
+ * Pops up a small window with a list of folders and Ok/Cancel buttons.
+ * Author: Stefan Taferner <taferner@kde.org>
+ * Carsten Burghardt <burghardt@kde.org>
+ */
+#ifndef kmfolderseldlg_h
+#define kmfolderseldlg_h
+
+#include <kdialogbase.h>
+#include <kfoldertree.h>
+
+class KMFolder;
+class KMFolderTree;
+class KMMainWidget;
+
+namespace KMail {
+
+ class SimpleFolderTree : public KFolderTree
+ {
+ Q_OBJECT
+
+ public:
+ SimpleFolderTree( QWidget * parent, KMFolderTree * folderTree,
+ const QString & preSelection, bool mustBeReadWrite );
+
+ /** Reload the tree and select what folders to show and what not */
+ void reload( bool mustBeReadWrite, bool showOutbox, bool showImapFolders,
+ const QString& preSelection = QString::null );
+
+ /** Return the current folder */
+ const KMFolder * folder() const;
+
+ /** Set the current folder */
+ void setFolder( KMFolder* );
+ void setFolder( const QString& idString );
+
+ /** Apply the given filter. */
+ void applyFilter( const QString& filter );
+
+ public slots:
+ void addChildFolder();
+
+ protected slots:
+ void slotContextMenuRequested( QListViewItem *, const QPoint & );
+ virtual void recolorRows();
+
+ protected:
+ /** Read color options and set palette. */
+ virtual void readColorConfig(void);
+ virtual void keyPressEvent( QKeyEvent *e );
+
+ /** Folder and path column IDs. */
+ friend class KMFolderSelDlg;
+ int mFolderColumn;
+ int mPathColumn;
+
+ private:
+ KMFolderTree* mFolderTree;
+ QString mFilter;
+ bool mLastMustBeReadWrite;
+ bool mLastShowOutbox;
+ bool mLastShowImapFolders;
+};
+
+ //-----------------------------------------------------------------------------
+ class KMFolderSelDlg: public KDialogBase
+ {
+ Q_OBJECT
+
+ public:
+ /**
+ * Constructor with KMMainWidget
+ * @p parent @em must be a KMMainWin, because we
+ * need its foldertree.
+ * @param mustBeReadWrite if true, readonly folders are disabled
+ * @param useGlobalSettings if true, the current folder is read and
+ * written to GlobalSettings
+ */
+ KMFolderSelDlg( KMMainWidget * parent, const QString& caption,
+ bool mustBeReadWrite, bool useGlobalSettings = true );
+ /**
+ * Constructor with separate KMFolderTree
+ * @param mustBeReadWrite if true, readonly folders are disabled
+ * @param useGlobalSettings if true, the current folder is read and
+ * written to GlobalSettings
+ */
+ KMFolderSelDlg( QWidget * parent, KMFolderTree * tree,
+ const QString& caption, bool mustBeReadWrite,
+ bool useGlobalSettings = true );
+
+ virtual ~KMFolderSelDlg();
+
+ /** Returns selected folder */
+ virtual KMFolder* folder( void );
+
+ /** Set selected folder */
+ void setFolder( KMFolder* folder );
+
+ /** Set some flags what folders to show and what not */
+ void setFlags( bool mustBeReadWrite, bool showOutbox, bool showImapFolders );
+
+ protected slots:
+ void slotSelect();
+ void slotUser1();
+ void slotUpdateBtnStatus();
+
+ protected:
+ void readConfig();
+ void writeConfig();
+ /** Init the dialog */
+ void init();
+
+ SimpleFolderTree * mTreeView;
+ bool mUseGlobalSettings;
+ };
+
+} // namespace KMail
+
+#endif /*kmfolderseldlg_h*/
diff --git a/kmail/kmfoldertree.cpp b/kmail/kmfoldertree.cpp
new file mode 100644
index 00000000..6e67399c
--- /dev/null
+++ b/kmail/kmfoldertree.cpp
@@ -0,0 +1,2126 @@
+// kmfoldertree.cpp
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "kmfoldertree.h"
+
+#include "kmfoldermgr.h"
+#include "kmfolder.h"
+#include "kmfolderimap.h"
+#include "kmfoldercachedimap.h"
+#include "kmfolderdia.h"
+#include "kmheaders.h"
+#include "kmmainwidget.h"
+#include "kmailicalifaceimpl.h"
+#include "accountmanager.h"
+using KMail::AccountManager;
+#include "globalsettings.h"
+#include "kmcommands.h"
+#include "foldershortcutdialog.h"
+#include "expirypropertiesdialog.h"
+#include "newfolderdialog.h"
+#include "acljobs.h"
+#include "messagecopyhelper.h"
+using KMail::MessageCopyHelper;
+#include "favoritefolderview.h"
+#include "folderviewtooltip.h"
+using KMail::FolderViewToolTip;
+
+#include <maillistdrag.h>
+using namespace KPIM;
+
+#include <kapplication.h>
+#include <kglobalsettings.h>
+#include <kiconloader.h>
+#include <kmessagebox.h>
+#include <kconfig.h>
+#include <kpopupmenu.h>
+#include <kdebug.h>
+
+#include <qpainter.h>
+#include <qcursor.h>
+#include <qregexp.h>
+#include <qpopupmenu.h>
+
+#include <unistd.h>
+#include <assert.h>
+
+#include <X11/Xlib.h>
+#include <fixx11h.h>
+
+//=============================================================================
+
+KMFolderTreeItem::KMFolderTreeItem( KFolderTree *parent, const QString & name,
+ KFolderTreeItem::Protocol protocol )
+ : QObject( parent, name.latin1() ),
+ KFolderTreeItem( parent, name, protocol, Root ),
+ mFolder( 0 ), mNeedsRepaint( true )
+{
+ init();
+ setPixmap( 0, normalIcon( iconSize() ) );
+}
+
+//-----------------------------------------------------------------------------
+KMFolderTreeItem::KMFolderTreeItem( KFolderTree *parent, const QString & name,
+ KMFolder* folder )
+ : QObject( parent, name.latin1() ),
+ KFolderTreeItem( parent, name ),
+ mFolder( folder ), mNeedsRepaint( true )
+{
+ init();
+ setPixmap( 0, normalIcon( iconSize() ) );
+}
+
+//-----------------------------------------------------------------------------
+KMFolderTreeItem::KMFolderTreeItem( KFolderTreeItem *parent, const QString & name,
+ KMFolder* folder )
+ : QObject( 0, name.latin1() ),
+ KFolderTreeItem( parent, name ),
+ mFolder( folder ), mNeedsRepaint( true )
+{
+ init();
+ setPixmap( 0, normalIcon( iconSize() ) );
+}
+
+KMFolderTreeItem::~KMFolderTreeItem()
+{
+}
+
+static KFolderTreeItem::Protocol protocolFor( KMFolderType t ) {
+ switch ( t ) {
+ case KMFolderTypeImap:
+ return KFolderTreeItem::Imap;
+ case KMFolderTypeCachedImap:
+ return KFolderTreeItem::CachedImap;
+ case KMFolderTypeMbox:
+ case KMFolderTypeMaildir:
+ return KFolderTreeItem::Local;
+ case KMFolderTypeSearch:
+ return KFolderTreeItem::Search;
+ default:
+ return KFolderTreeItem::NONE;
+ }
+}
+
+QPixmap KMFolderTreeItem::normalIcon(int size) const
+{
+ QString icon;
+ if ( (!mFolder && type() == Root) || useTopLevelIcon() ) {
+ switch ( protocol() ) {
+ case KFolderTreeItem::Imap:
+ case KFolderTreeItem::CachedImap:
+ case KFolderTreeItem::News:
+ icon = "server"; break;
+ case KFolderTreeItem::Search:
+ icon = "viewmag";break;
+ default:
+ icon = "folder";break;
+ }
+ } else {
+ // special folders
+ switch ( type() ) {
+ case Inbox: icon = "folder_inbox"; break;
+ case Outbox: icon = "folder_outbox"; break;
+ case SentMail: icon = "folder_sent_mail"; break;
+ case Trash: icon = "trashcan_empty"; break;
+ case Drafts: icon = "edit"; break;
+ case Templates: icon = "filenew"; break;
+ default: icon = kmkernel->iCalIface().folderPixmap( type() ); break;
+ }
+ // non-root search folders
+ if ( protocol() == KMFolderTreeItem::Search ) {
+ icon = "mail_find";
+ }
+ if ( mFolder && mFolder->noContent() ) {
+ icon = "folder_grey";
+ }
+ }
+
+ if ( icon.isEmpty() )
+ icon = "folder";
+
+ if (mFolder && mFolder->useCustomIcons() ) {
+ icon = mFolder->normalIconPath();
+ }
+ KIconLoader * il = KGlobal::instance()->iconLoader();
+ QPixmap pm = il->loadIcon( icon, KIcon::Small, size,
+ KIcon::DefaultState, 0, true );
+ if ( mFolder && pm.isNull() ) {
+ pm = il->loadIcon( mFolder->normalIconPath(), KIcon::Small, size,
+ KIcon::DefaultState, 0, true );
+ }
+
+ return pm;
+}
+
+QPixmap KMFolderTreeItem::unreadIcon(int size) const
+{
+ QPixmap pm;
+
+ if ( !mFolder || useTopLevelIcon() || mFolder->isSystemFolder() ||
+ kmkernel->folderIsTrash( mFolder ) ||
+ kmkernel->folderIsTemplates( mFolder ) ||
+ kmkernel->folderIsDraftOrOutbox( mFolder ) )
+ pm = normalIcon( size );
+
+ KIconLoader * il = KGlobal::instance()->iconLoader();
+ if ( mFolder && mFolder->useCustomIcons() ) {
+ pm = il->loadIcon( mFolder->unreadIconPath(), KIcon::Small, size,
+ KIcon::DefaultState, 0, true );
+ if ( pm.isNull() )
+ pm = il->loadIcon( mFolder->normalIconPath(), KIcon::Small, size,
+ KIcon::DefaultState, 0, true );
+ }
+ if ( pm.isNull() ) {
+ if ( mFolder && mFolder->noContent() ) {
+ pm = il->loadIcon( "folder_grey_open", KIcon::Small, size,
+ KIcon::DefaultState, 0, true );
+ } else {
+ 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,
+ KIcon::DefaultState, 0, true );
+ }
+ }
+
+ return pm;
+}
+
+void KMFolderTreeItem::init()
+{
+ if ( !mFolder )
+ return;
+
+ setProtocol( protocolFor( mFolder->folderType() ) );
+
+ if ( useTopLevelIcon() )
+ setType(Root);
+ else {
+ if ( mFolder == kmkernel->inboxFolder() )
+ setType( Inbox );
+ else if ( kmkernel->folderIsDraftOrOutbox( mFolder ) ) {
+ if ( mFolder == kmkernel->outboxFolder() )
+ setType( Outbox );
+ else
+ setType( Drafts );
+ }
+ else if ( kmkernel->folderIsSentMailFolder( mFolder ) )
+ setType( SentMail );
+ else if ( kmkernel->folderIsTrash( mFolder ) )
+ setType( Trash );
+ else if ( kmkernel->folderIsTemplates( mFolder ) )
+ setType( Templates );
+ else if( kmkernel->iCalIface().isResourceFolder(mFolder) )
+ setType( kmkernel->iCalIface().folderType(mFolder) );
+ // System folders on dimap or imap which are not resource folders are
+ // inboxes. Urgs.
+ if ( mFolder->isSystemFolder() &&
+ !kmkernel->iCalIface().isResourceFolder( mFolder) &&
+ ( mFolder->folderType() == KMFolderTypeImap
+ || mFolder->folderType() == KMFolderTypeCachedImap ) )
+ setType( Inbox );
+ }
+ if ( !mFolder->isSystemFolder() )
+ setRenameEnabled( 0, false );
+
+ KMFolderTree* tree = dynamic_cast<KMFolderTree*>( listView() );
+ if ( tree )
+ tree->insertIntoFolderToItemMap( mFolder, this );
+}
+
+void KMFolderTreeItem::adjustUnreadCount( int newUnreadCount ) {
+ // adjust the icons if the folder is now newly unread or
+ // now newly not-unread
+ if ( newUnreadCount != 0 && unreadCount() == 0 )
+ setPixmap( 0, unreadIcon( iconSize() ) );
+ if ( unreadCount() != 0 && newUnreadCount == 0 )
+ setPixmap( 0, normalIcon( iconSize() ) );
+
+ setUnreadCount( newUnreadCount );
+}
+
+void KMFolderTreeItem::slotIconsChanged()
+{
+ kdDebug(5006) << k_funcinfo << endl;
+ // this is prone to change, so better check
+ if( kmkernel->iCalIface().isResourceFolder( mFolder ) )
+ setType( kmkernel->iCalIface().folderType(mFolder) );
+
+ if ( unreadCount() > 0 )
+ setPixmap( 0, unreadIcon( iconSize() ) );
+ else
+ setPixmap( 0, normalIcon( iconSize() ) );
+ emit iconChanged( this );
+ repaint();
+}
+
+void KMFolderTreeItem::slotNameChanged()
+{
+ setText( 0, mFolder->label() );
+ emit nameChanged( this );
+ repaint();
+}
+
+
+//-----------------------------------------------------------------------------
+bool KMFolderTreeItem::acceptDrag(QDropEvent* e) const
+{
+ // Do not allow drags from the favorite folder view, as they don't really
+ // make sense and do not work.
+ KMMainWidget *mainWidget = static_cast<KMFolderTree*>( listView() )->mainWidget();
+ assert( mainWidget );
+ if ( mainWidget->favoriteFolderView() &&
+ e->source() == mainWidget->favoriteFolderView()->viewport() )
+ return false;
+
+ if ( protocol() == KFolderTreeItem::Search )
+ return false; // nothing can be dragged into search folders
+
+ if ( e->provides( KPIM::MailListDrag::format() ) ) {
+ if ( !mFolder || mFolder->moveInProgress() || mFolder->isReadOnly() ||
+ (mFolder->noContent() && childCount() == 0) ||
+ (mFolder->noContent() && isOpen()) ) {
+ return false;
+ }
+ else {
+ return true;
+ }
+ } else if ( e->provides("application/x-qlistviewitem") ) {
+ // wtf: protocol() is NONE instead of Local for the local root folder
+ if ( !mFolder && protocol() == KFolderTreeItem::NONE && type() == KFolderTreeItem::Root )
+ return true; // local top-level folder
+ if ( !mFolder || mFolder->isReadOnly() || mFolder->noContent() )
+ return false;
+ return true;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTreeItem::slotShowExpiryProperties()
+{
+ if ( !mFolder )
+ return;
+
+ KMFolderTree* tree = static_cast<KMFolderTree*>( listView() );
+ KMail::ExpiryPropertiesDialog *dlg =
+ new KMail::ExpiryPropertiesDialog( tree, mFolder );
+ dlg->show();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFolderTreeItem::properties()
+{
+ if ( !mFolder )
+ return;
+
+ KMail::FolderTreeBase* tree = static_cast<KMail::FolderTreeBase*>( listView() );
+ tree->mainWidget()->modifyFolder( this );
+ //Nothing here the above may actually delete this KMFolderTreeItem
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTreeItem::assignShortcut()
+{
+ if ( !mFolder )
+ return;
+
+ KMail::FolderShortcutDialog *shorty =
+ new KMail::FolderShortcutDialog( mFolder,
+ kmkernel->getKMMainWidget(),
+ listView() );
+ shorty->exec();
+ return;
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTreeItem::updateCount()
+{
+ if ( !folder() ) {
+ setTotalCount( -1 );
+ return;
+ }
+ KMail::FolderTreeBase* tree = dynamic_cast<KMail::FolderTreeBase*>( listView() );
+ if ( !tree ) return;
+
+ tree->slotUpdateCounts( folder(), true /* force update */ );
+}
+
+
+//=============================================================================
+
+
+KMFolderTree::KMFolderTree( KMMainWidget *mainWidget, QWidget *parent,
+ const char *name )
+ : KMail::FolderTreeBase( mainWidget, parent, name )
+ , mUpdateTimer( 0, "mUpdateTimer" )
+ , autoopen_timer( 0, "autoopen_timer" )
+{
+ oldSelected = 0;
+ oldCurrent = 0;
+ mLastItem = 0;
+ mMainWidget = mainWidget;
+ mReloading = false;
+ mCutFolder = false;
+
+ mUpdateCountTimer= new QTimer( this, "mUpdateCountTimer" );
+
+ setDragEnabled( true );
+ addAcceptableDropMimetype( "application/x-qlistviewitem", false );
+
+ setSelectionModeExt( Extended );
+
+ int namecol = addColumn( i18n("Folder"), 250 );
+ header()->setStretchEnabled( true, namecol );
+
+ // connect
+ connectSignals();
+
+ // popup to switch columns
+ header()->setClickEnabled(true);
+ header()->installEventFilter(this);
+ mPopup = new KPopupMenu(this);
+ mPopup->insertTitle(i18n("View Columns"));
+ mPopup->setCheckable(true);
+ mUnreadPop = mPopup->insertItem(i18n("Unread Column"), this, SLOT(slotToggleUnreadColumn()));
+ mTotalPop = mPopup->insertItem(i18n("Total Column"), this, SLOT(slotToggleTotalColumn()));
+ mSizePop = mPopup->insertItem(i18n("Size Column"), this, SLOT(slotToggleSizeColumn()));
+
+ connect( this, SIGNAL( triggerRefresh() ),
+ this, SLOT( refresh() ) );
+
+ new FolderViewToolTip( this );
+}
+
+//-----------------------------------------------------------------------------
+// connects all needed signals to their slots
+void KMFolderTree::connectSignals()
+{
+ connect( mUpdateCountTimer, SIGNAL(timeout()),
+ this, SLOT(slotUpdateCountTimeout()) );
+
+ connect(&mUpdateTimer, SIGNAL(timeout()),
+ this, SLOT(delayedUpdate()));
+
+ connect(kmkernel->folderMgr(), SIGNAL(changed()),
+ this, SLOT(doFolderListChanged()));
+
+ connect(kmkernel->folderMgr(), SIGNAL(folderRemoved(KMFolder*)),
+ this, SLOT(slotFolderRemoved(KMFolder*)));
+
+ connect(kmkernel->folderMgr(), SIGNAL(folderMoveOrCopyOperationFinished()),
+ this, SLOT(slotFolderMoveOrCopyOperationFinished()));
+
+ connect(kmkernel->imapFolderMgr(), SIGNAL(changed()),
+ this, SLOT(doFolderListChanged()));
+
+ connect(kmkernel->imapFolderMgr(), SIGNAL(folderRemoved(KMFolder*)),
+ this, SLOT(slotFolderRemoved(KMFolder*)));
+
+ connect(kmkernel->dimapFolderMgr(), SIGNAL(changed()),
+ this, SLOT(doFolderListChanged()));
+
+ connect(kmkernel->dimapFolderMgr(), SIGNAL(folderRemoved(KMFolder*)),
+ this, SLOT(slotFolderRemoved(KMFolder*)));
+
+ connect(kmkernel->searchFolderMgr(), SIGNAL(changed()),
+ this, SLOT(doFolderListChanged()));
+
+ connect(kmkernel->acctMgr(), SIGNAL(accountRemoved(KMAccount*)),
+ this, SLOT(slotAccountRemoved(KMAccount*)));
+
+ connect(kmkernel->acctMgr(), SIGNAL(accountAdded(KMAccount*)),
+ this, SLOT(slotUnhideLocalInbox()));
+
+ connect(kmkernel->searchFolderMgr(), SIGNAL(folderRemoved(KMFolder*)),
+ this, SLOT(slotFolderRemoved(KMFolder*)));
+
+ connect( &autoopen_timer, SIGNAL( timeout() ),
+ this, SLOT( openFolder() ) );
+
+ connect( this, SIGNAL( contextMenuRequested( QListViewItem*, const QPoint &, int ) ),
+ this, SLOT( slotContextMenuRequested( QListViewItem*, const QPoint & ) ) );
+
+ connect( this, SIGNAL( expanded( QListViewItem* ) ),
+ this, SLOT( slotFolderExpanded( QListViewItem* ) ) );
+
+ connect( this, SIGNAL( collapsed( QListViewItem* ) ),
+ this, SLOT( slotFolderCollapsed( QListViewItem* ) ) );
+
+ connect( this, SIGNAL( itemRenamed( QListViewItem*, int, const QString &)),
+ this, SLOT( slotRenameFolder( QListViewItem*, int, const QString &)));
+
+ connect( this, SIGNAL(folderSelected(KMFolder*)), SLOT(updateCopyActions()) );
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::readConfig (void)
+{
+ KConfig* conf = KMKernel::config();
+
+ readColorConfig();
+
+ // Custom/Ssystem font support
+ {
+ KConfigGroupSaver saver(conf, "Fonts");
+ if (!conf->readBoolEntry("defaultFonts",true)) {
+ QFont folderFont( KGlobalSettings::generalFont() );
+ setFont(conf->readFontEntry("folder-font", &folderFont));
+ }
+ else
+ setFont(KGlobalSettings::generalFont());
+ }
+
+ // restore the layout
+ restoreLayout(conf, "Geometry");
+}
+
+//-----------------------------------------------------------------------------
+// Save the configuration file
+void KMFolderTree::writeConfig()
+{
+ // save the current state of the folders
+ for ( QListViewItemIterator it( this ) ; it.current() ; ++it ) {
+ KMFolderTreeItem* fti = static_cast<KMFolderTreeItem*>(it.current());
+ if (fti)
+ writeIsListViewItemOpen(fti);
+ }
+
+ // save the current layout
+ saveLayout(KMKernel::config(), "Geometry");
+}
+
+//-----------------------------------------------------------------------------
+// Updates the count of unread messages (count of unread messages
+// is now cached in KMails config file)
+void KMFolderTree::updateUnreadAll()
+{
+ bool upd = isUpdatesEnabled();
+ setUpdatesEnabled(false);
+
+ KMFolderDir* fdir;
+ KMFolderNode* folderNode;
+ KMFolder* folder;
+
+ fdir = &kmkernel->folderMgr()->dir();
+ for (folderNode = fdir->first();
+ folderNode != 0;
+ folderNode =fdir->next())
+ {
+ if (!folderNode->isDir()) {
+ folder = static_cast<KMFolder*>(folderNode);
+
+ folder->open("updateunread");
+ folder->countUnread();
+ folder->close("updateunread");
+ }
+ }
+
+ setUpdatesEnabled(upd);
+}
+
+//-----------------------------------------------------------------------------
+// Reload the tree of items in the list view
+void KMFolderTree::reload(bool openFolders)
+{
+ if ( mReloading ) {
+ // no parallel reloads are allowed
+ kdDebug(5006) << "KMFolderTree::reload - already reloading" << endl;
+ return;
+ }
+ mReloading = true;
+
+ int top = contentsY();
+ mLastItem = 0;
+ // invalidate selected drop item
+ oldSelected = 0;
+ // remember last
+ KMFolder* last = currentFolder();
+ KMFolder* selected = 0;
+ KMFolder* oldCurrentFolder =
+ ( oldCurrent ? static_cast<KMFolderTreeItem*>(oldCurrent)->folder(): 0 );
+ for ( QListViewItemIterator it( this ) ; it.current() ; ++it ) {
+ KMFolderTreeItem * fti = static_cast<KMFolderTreeItem*>(it.current());
+ writeIsListViewItemOpen( fti );
+ if ( fti->isSelected() )
+ selected = fti->folder();
+ }
+ mFolderToItem.clear();
+ clear();
+
+ // construct the root of the local folders
+ KMFolderTreeItem * root = new KMFolderTreeItem( this, i18n("Local Folders") );
+ root->setOpen( readIsListViewItemOpen(root) );
+
+ KMFolderDir * fdir = &kmkernel->folderMgr()->dir();
+ addDirectory(fdir, root);
+
+ fdir = &kmkernel->imapFolderMgr()->dir();
+ // each imap-account creates it's own root
+ addDirectory(fdir, 0);
+
+ fdir = &kmkernel->dimapFolderMgr()->dir();
+ // each dimap-account creates it's own root
+ addDirectory(fdir, 0);
+
+ // construct the root of the search folder hierarchy:
+ root = new KMFolderTreeItem( this, i18n("Searches"), KFolderTreeItem::Search );
+ root->setOpen( readIsListViewItemOpen( root ) );
+
+ fdir = &kmkernel->searchFolderMgr()->dir();
+ addDirectory(fdir, root);
+
+ if (openFolders)
+ {
+ // we open all folders to update the count
+ mUpdateIterator = QListViewItemIterator (this);
+ QTimer::singleShot( 0, this, SLOT(slotUpdateOneCount()) );
+ }
+
+ for ( QListViewItemIterator it( this ) ; it.current() ; ++it ) {
+ KMFolderTreeItem * fti = static_cast<KMFolderTreeItem*>(it.current());
+ if ( !fti || !fti->folder() )
+ continue;
+
+ disconnect(fti->folder(),SIGNAL(iconsChanged()),
+ fti,SLOT(slotIconsChanged()));
+ connect(fti->folder(),SIGNAL(iconsChanged()),
+ fti,SLOT(slotIconsChanged()));
+
+ disconnect(fti->folder(),SIGNAL(nameChanged()),
+ fti,SLOT(slotNameChanged()));
+ connect(fti->folder(),SIGNAL(nameChanged()),
+ fti,SLOT(slotNameChanged()));
+
+ // we want to be noticed of changes to update the unread/total columns
+ disconnect(fti->folder(), SIGNAL(msgAdded(KMFolder*,Q_UINT32)),
+ this,SLOT(slotUpdateCountsDelayed(KMFolder*)));
+ connect(fti->folder(), SIGNAL(msgAdded(KMFolder*,Q_UINT32)),
+ this,SLOT(slotUpdateCountsDelayed(KMFolder*)));
+ //}
+
+ disconnect(fti->folder(), SIGNAL(numUnreadMsgsChanged(KMFolder*)),
+ this,SLOT(slotUpdateCountsDelayed(KMFolder*)));
+ connect(fti->folder(), SIGNAL(numUnreadMsgsChanged(KMFolder*)),
+ this,SLOT(slotUpdateCountsDelayed(KMFolder*)));
+ disconnect(fti->folder(), SIGNAL(msgRemoved(KMFolder*)),
+ this,SLOT(slotUpdateCountsDelayed(KMFolder*)));
+ connect(fti->folder(), SIGNAL(msgRemoved(KMFolder*)),
+ this,SLOT(slotUpdateCountsDelayed(KMFolder*)));
+
+ disconnect(fti->folder(), SIGNAL(folderSizeChanged( KMFolder* )),
+ this,SLOT(slotUpdateCountsDelayed(KMFolder*)));
+ connect(fti->folder(), SIGNAL(folderSizeChanged( KMFolder* )),
+ this,SLOT(slotUpdateCountsDelayed(KMFolder*)));
+
+
+
+ disconnect(fti->folder(), SIGNAL(shortcutChanged(KMFolder*)),
+ mMainWidget, SLOT( slotShortcutChanged(KMFolder*)));
+ connect(fti->folder(), SIGNAL(shortcutChanged(KMFolder*)),
+ mMainWidget, SLOT( slotShortcutChanged(KMFolder*)));
+
+
+ if (!openFolders)
+ slotUpdateCounts(fti->folder());
+
+ // populate the size column
+ fti->setFolderSize( 0 );
+ fti->setFolderIsCloseToQuota( fti->folder()->storage()->isCloseToQuota() );
+
+ }
+ ensureVisible(0, top + visibleHeight(), 0, 0);
+ // if current and selected folder did not change set it again
+ for ( QListViewItemIterator it( this ) ; it.current() ; ++it )
+ {
+ if ( last &&
+ static_cast<KMFolderTreeItem*>( it.current() )->folder() == last )
+ {
+ mLastItem = static_cast<KMFolderTreeItem*>( it.current() );
+ setCurrentItem( it.current() );
+ }
+ if ( selected &&
+ static_cast<KMFolderTreeItem*>( it.current() )->folder() == selected )
+ {
+ setSelected( it.current(), true );
+ }
+ if ( oldCurrentFolder &&
+ static_cast<KMFolderTreeItem*>( it.current() )->folder() == oldCurrentFolder )
+ {
+ oldCurrent = it.current();
+ }
+ }
+ refresh();
+ mReloading = false;
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::slotUpdateOneCount()
+{
+ if ( !mUpdateIterator.current() ) return;
+ KMFolderTreeItem* fti = static_cast<KMFolderTreeItem*>(mUpdateIterator.current());
+ ++mUpdateIterator;
+ if ( !fti->folder() ) {
+ // next one please
+ QTimer::singleShot( 0, this, SLOT(slotUpdateOneCount()) );
+ return;
+ }
+
+ // open the folder and update the count
+ bool open = fti->folder()->isOpened();
+ if (!open) fti->folder()->open("updatecount");
+ slotUpdateCounts(fti->folder());
+ // restore previous state
+ if (!open) fti->folder()->close("updatecount");
+
+ QTimer::singleShot( 0, this, SLOT(slotUpdateOneCount()) );
+}
+
+//-----------------------------------------------------------------------------
+// Recursively add a directory of folders to the tree of folders
+void KMFolderTree::addDirectory( KMFolderDir *fdir, KMFolderTreeItem* parent )
+{
+ for ( KMFolderNode * node = fdir->first() ; node ; node = fdir->next() ) {
+ if ( node->isDir() )
+ continue;
+
+ KMFolder * folder = static_cast<KMFolder*>(node);
+ KMFolderTreeItem * fti = 0;
+ if (!parent)
+ {
+ // create new root-item, but only if this is not the root of a
+ // "groupware folders only" account
+ if ( kmkernel->iCalIface().hideResourceAccountRoot( folder ) )
+ continue;
+ // it needs a folder e.g. to save it's state (open/close)
+ fti = new KMFolderTreeItem( this, folder->label(), folder );
+ fti->setExpandable( true );
+
+ // add child-folders
+ if (folder && folder->child()) {
+ addDirectory( folder->child(), fti );
+ }
+ } else {
+ // hide local inbox if unused
+ if ( kmkernel->inboxFolder() == folder && hideLocalInbox() ) {
+ connect( kmkernel->inboxFolder(), SIGNAL(msgAdded(KMFolder*,Q_UINT32)), SLOT(slotUnhideLocalInbox()) );
+ continue;
+ }
+
+ // create new child
+ fti = new KMFolderTreeItem( parent, folder->label(), folder );
+ // set folders explicitely to exandable when they have children
+ // this way we can do a listing for IMAP folders when the user expands them
+ // even when the child folders are not created yet
+ if ( folder->storage()->hasChildren() == FolderStorage::HasChildren ) {
+ fti->setExpandable( true );
+ } else {
+ fti->setExpandable( false );
+ }
+
+ // add child-folders
+ if (folder && folder->child()) {
+ addDirectory( folder->child(), fti );
+ }
+
+ // Check if this is an IMAP resource folder or a no-content parent only
+ // containing groupware folders
+ if ( (kmkernel->iCalIface().hideResourceFolder( folder ) || folder->noContent())
+ && fti->childCount() == 0 ) {
+ // It is
+ removeFromFolderToItemMap( folder );
+ delete fti;
+ continue;
+ }
+
+ connect (fti, SIGNAL(iconChanged(KMFolderTreeItem*)),
+ this, SIGNAL(iconChanged(KMFolderTreeItem*)));
+ connect (fti, SIGNAL(nameChanged(KMFolderTreeItem*)),
+ this, SIGNAL(nameChanged(KMFolderTreeItem*)));
+ }
+ // restore last open-state
+ fti->setOpen( readIsListViewItemOpen(fti) );
+ } // for-end
+}
+
+//-----------------------------------------------------------------------------
+// Initiate a delayed refresh of the tree
+void KMFolderTree::refresh()
+{
+ mUpdateTimer.changeInterval(200);
+}
+
+//-----------------------------------------------------------------------------
+// Updates the pixmap and extendedLabel information for items
+void KMFolderTree::delayedUpdate()
+{
+ bool upd = isUpdatesEnabled();
+ if ( upd ) {
+ setUpdatesEnabled(false);
+
+ for ( QListViewItemIterator it( this ) ; it.current() ; ++it ) {
+ KMFolderTreeItem* fti = static_cast<KMFolderTreeItem*>(it.current());
+ if (!fti || !fti->folder())
+ continue;
+
+ if ( fti->needsRepaint() ) {
+ fti->repaint();
+ fti->setNeedsRepaint( false );
+ }
+ }
+ setUpdatesEnabled(upd);
+ }
+ mUpdateTimer.stop();
+}
+
+//-----------------------------------------------------------------------------
+// Folders have been added/deleted update the tree of folders
+void KMFolderTree::doFolderListChanged()
+{
+ reload();
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::slotAccountRemoved(KMAccount *)
+{
+ doFolderSelected( firstChild() );
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::slotFolderMoveOrCopyOperationFinished()
+{
+ setDragEnabled( true );
+}
+//-----------------------------------------------------------------------------
+void KMFolderTree::slotFolderRemoved(KMFolder *aFolder)
+{
+ QListViewItem *item = indexOfFolder(aFolder);
+ if (!item) return;
+ KMFolderTreeItem *fti = static_cast<KMFolderTreeItem*> ( item );
+ if ( oldCurrent == fti )
+ oldCurrent = 0;
+ if ( oldSelected == fti )
+ oldSelected = 0;
+ if (!fti || !fti->folder()) return;
+ if (fti == currentItem())
+ {
+ QListViewItem *qlvi = fti->itemAbove();
+ if (!qlvi) qlvi = fti->itemBelow();
+ doFolderSelected( qlvi );
+ }
+ removeFromFolderToItemMap( aFolder );
+
+ if ( dropItem == fti ) { // The removed item is the dropItem
+ dropItem = 0; // it becomes invalid
+ }
+
+ delete fti;
+ updateCopyActions();
+}
+
+//-----------------------------------------------------------------------------
+// Methods for navigating folders with the keyboard
+void KMFolderTree::prepareItem( KMFolderTreeItem* fti )
+{
+ for ( QListViewItem * parent = fti->parent() ; parent ; parent = parent->parent() )
+ parent->setOpen( true );
+ ensureItemVisible( fti );
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::nextUnreadFolder()
+{
+ nextUnreadFolder( false );
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::nextUnreadFolder(bool confirm)
+{
+ QListViewItemIterator it( currentItem() ? currentItem() : firstChild() );
+ if ( currentItem() )
+ ++it; // don't find current item
+ for ( ; it.current() ; ++it ) {
+ //check if folder is one to stop on
+ KMFolderTreeItem* fti = static_cast<KMFolderTreeItem*>(it.current());
+ if (checkUnreadFolder(fti,confirm)) return;
+ }
+ //Now if confirm is true we are doing "ReadOn"
+ //we have got to the bottom of the folder list
+ //so we have to start at the top
+ if (confirm) {
+ for ( it = firstChild() ; it.current() ; ++it ) {
+ //check if folder is one to stop on
+ KMFolderTreeItem* fti = static_cast<KMFolderTreeItem*>(it.current());
+ if (checkUnreadFolder(fti,confirm)) return;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+bool KMFolderTree::checkUnreadFolder (KMFolderTreeItem* fti, bool confirm)
+{
+ if ( fti && fti->folder() && !fti->folder()->ignoreNewMail() &&
+ ( fti->folder()->countUnread() > 0 ) ) {
+
+ // Don't change into the trash or outbox folders.
+ if (fti->type() == KFolderTreeItem::Trash ||
+ fti->type() == KFolderTreeItem::Outbox )
+ return false;
+
+ if (confirm) {
+ // Skip drafts, sent mail and templates as well, when reading mail with
+ // the space bar but not when changing into the next folder with unread
+ // mail via ctrl+ or ctrl- so we do this only if (confirm == true),
+ // which means we are doing readOn.
+ if ( fti->type() == KFolderTreeItem::Drafts ||
+ fti->type() == KFolderTreeItem::Templates ||
+ fti->type() == KFolderTreeItem::SentMail )
+ return false;
+
+ // warn user that going to next folder - but keep track of
+ // whether he wishes to be notified again in "AskNextFolder"
+ // parameter (kept in the config file for kmail)
+ if ( KMessageBox::questionYesNo( this,
+ i18n( "<qt>Go to the next unread message in folder <b>%1</b>?</qt>" )
+ .arg( fti->folder()->label() ),
+ i18n( "Go to Next Unread Message" ),
+ i18n("Go To"), i18n("Do Not Go To"), // defaults
+ "AskNextFolder",
+ false)
+ == KMessageBox::No ) return true;
+ }
+ prepareItem( fti );
+ blockSignals( true );
+ doFolderSelected( fti );
+ blockSignals( false );
+ emit folderSelectedUnread( fti->folder() );
+ return true;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::prevUnreadFolder()
+{
+ QListViewItemIterator it( currentItem() ? currentItem() : lastItem() );
+ if ( currentItem() )
+ --it; // don't find current item
+ for ( ; it.current() ; --it ) {
+ KMFolderTreeItem* fti = static_cast<KMFolderTreeItem*>(it.current());
+ if (checkUnreadFolder(fti,false)) return;
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::incCurrentFolder()
+{
+ QListViewItemIterator it( currentItem() );
+ ++it;
+ KMFolderTreeItem* fti = static_cast<KMFolderTreeItem*>(it.current());
+ if (fti) {
+ prepareItem( fti );
+ setFocus();
+ setCurrentItem( fti );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::decCurrentFolder()
+{
+ QListViewItemIterator it( currentItem() );
+ --it;
+ KMFolderTreeItem* fti = static_cast<KMFolderTreeItem*>(it.current());
+ if (fti) {
+ prepareItem( fti );
+ setFocus();
+ setCurrentItem( fti );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::selectCurrentFolder()
+{
+ KMFolderTreeItem* fti = static_cast<KMFolderTreeItem*>( currentItem() );
+ if (fti) {
+ prepareItem( fti );
+ doFolderSelected( fti );
+ }
+}
+
+//-----------------------------------------------------------------------------
+KMFolder *KMFolderTree::currentFolder() const
+{
+ KMFolderTreeItem* fti = static_cast<KMFolderTreeItem*>( currentItem() );
+ if (fti )
+ return fti->folder();
+ else
+ return 0;
+}
+
+QValueList<QGuardedPtr<KMFolder> > KMFolderTree::selectedFolders()
+{
+ QValueList<QGuardedPtr<KMFolder> > rv;
+ for ( QListViewItemIterator it( this ); it.current(); ++it ) {
+ if ( it.current()->isSelected() ) {
+ KMFolderTreeItem *fti = static_cast<KMFolderTreeItem*>( it.current() );
+ rv.append( fti->folder() );
+ }
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// When not dragging and dropping a change in the selected item
+// indicates the user has changed the active folder emit a signal
+// so that the header list and reader window can be udpated.
+void KMFolderTree::doFolderSelected( QListViewItem* qlvi, bool keepSelection )
+{
+ if (!qlvi) return;
+ if ( mLastItem && mLastItem == qlvi && (keepSelection || selectedFolders().count() == 1) )
+ return;
+
+ KMFolderTreeItem* fti = static_cast< KMFolderTreeItem* >(qlvi);
+ KMFolder* folder = 0;
+ if (fti) folder = fti->folder();
+
+
+ if (mLastItem && mLastItem != fti && mLastItem->folder()
+ && (mLastItem->folder()->folderType() == KMFolderTypeImap))
+ {
+ KMFolderImap *imapFolder = static_cast<KMFolderImap*>(mLastItem->folder()->storage());
+ imapFolder->setSelected(false);
+ }
+ mLastItem = fti;
+
+ if ( !keepSelection )
+ clearSelection();
+ setCurrentItem( qlvi );
+ if ( !keepSelection )
+ setSelected( qlvi, true );
+ ensureItemVisible( qlvi );
+ if (!folder) {
+ emit folderSelected(0); // Root has been selected
+ }
+ else {
+ emit folderSelected(folder);
+ slotUpdateCounts(folder);
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::resizeEvent(QResizeEvent* e)
+{
+ KConfig* conf = KMKernel::config();
+
+ KConfigGroupSaver saver(conf, "Geometry");
+ conf->writeEntry(name(), size().width());
+
+ KListView::resizeEvent(e);
+}
+
+//-----------------------------------------------------------------------------
+// show context menu
+void KMFolderTree::slotContextMenuRequested( QListViewItem *lvi,
+ const QPoint &p )
+{
+ if (!lvi)
+ return;
+ setCurrentItem( lvi );
+
+ if (!mMainWidget) return; // safe bet
+
+ KMFolderTreeItem* fti = static_cast<KMFolderTreeItem*>(lvi);
+ if ( !isSelected( fti ) )
+ doFolderSelected( fti );
+ else if ( fti != mLastItem )
+ doFolderSelected( fti, true );
+
+ if (!fti )
+ return;
+
+ KPopupMenu *folderMenu = new KPopupMenu;
+ bool multiFolder = selectedFolders().count() > 1;
+ if (fti->folder()) folderMenu->insertTitle(fti->folder()->label());
+
+ // outbox specific, but there it's the most used action
+ if ( (fti->folder() == kmkernel->outboxFolder()) && fti->folder()->count() )
+ mMainWidget->action("send_queued")->plug( folderMenu );
+ // Mark all as read is supposedly used often, therefor it is first
+ if ( fti->folder() && !fti->folder()->noContent() )
+ mMainWidget->action("mark_all_as_read")->plug( folderMenu );
+
+ /* Treat the special case of the root and account folders */
+ if ((!fti->folder() || (fti->folder()->noContent()
+ && !fti->parent())))
+ {
+ QString createChild = i18n("&New Subfolder...");
+ if (!fti->folder()) createChild = i18n("&New Folder...");
+
+ if (fti->folder() || (fti->text(0) != i18n("Searches")) && !multiFolder)
+ folderMenu->insertItem(SmallIconSet("folder_new"),
+ createChild, this,
+ SLOT(addChildFolder()));
+
+ if (!fti->folder()) {
+ mMainWidget->action("compact_all_folders")->plug(folderMenu);
+ mMainWidget->action("expire_all_folders")->plug(folderMenu);
+ } else if (fti->folder()->folderType() == KMFolderTypeImap) {
+ folderMenu->insertItem(SmallIconSet("mail_get"), i18n("Check &Mail"),
+ this,
+ SLOT(slotCheckMail()));
+ }
+ } else { // regular folders
+
+ folderMenu->insertSeparator();
+ if ( !fti->folder()->noChildren() && !multiFolder ) {
+ folderMenu->insertItem(SmallIconSet("folder_new"),
+ i18n("&New Subfolder..."), this,
+ SLOT(addChildFolder()));
+ }
+
+ // copy folder
+ QPopupMenu *copyMenu = new QPopupMenu( folderMenu );
+ folderToPopupMenu( CopyFolder, this, &mMenuToFolder, copyMenu );
+ folderMenu->insertItem( i18n("&Copy Folder To"), copyMenu );
+
+ if ( fti->folder()->isMoveable() )
+ {
+ QPopupMenu *moveMenu = new QPopupMenu( folderMenu );
+ folderToPopupMenu( MoveFolder, this, &mMenuToFolder, moveMenu );
+ folderMenu->insertItem( i18n("&Move Folder To"), moveMenu );
+ }
+
+ // Want to be able to display properties for ALL folders,
+ // so we can edit expiry properties.
+ // -- smp.
+ if (!fti->folder()->noContent())
+ {
+ if ( !multiFolder )
+ mMainWidget->action("search_messages")->plug(folderMenu);
+
+ mMainWidget->action("compact")->plug(folderMenu);
+
+ if ( GlobalSettings::self()->enableFavoriteFolderView() ) {
+ folderMenu->insertItem( SmallIconSet("bookmark_add"), i18n("Add to Favorite Folders"),
+ this, SLOT(slotAddToFavorites()) );
+ }
+
+ folderMenu->insertSeparator();
+ mMainWidget->action("empty")->plug(folderMenu);
+ if ( !fti->folder()->isSystemFolder() ) {
+ mMainWidget->action("delete_folder")->plug(folderMenu);
+ }
+ folderMenu->insertSeparator();
+ }
+ }
+
+ /* plug in IMAP and DIMAP specific things */
+ if (fti->folder() &&
+ (fti->folder()->folderType() == KMFolderTypeImap ||
+ fti->folder()->folderType() == KMFolderTypeCachedImap ))
+ {
+ folderMenu->insertItem(SmallIconSet("bookmark_folder"),
+ i18n("Subscription..."), mMainWidget,
+ SLOT(slotSubscriptionDialog()));
+ folderMenu->insertItem(SmallIcon("bookmark_folder"),
+ i18n("Local Subscription..."), mMainWidget,
+ SLOT(slotLocalSubscriptionDialog()));
+
+ if (!fti->folder()->noContent())
+ {
+ mMainWidget->action("refresh_folder")->plug(folderMenu);
+ if ( fti->folder()->folderType() == KMFolderTypeImap && !multiFolder ) {
+ folderMenu->insertItem(SmallIconSet("reload"), i18n("Refresh Folder List"), this,
+ SLOT(slotResetFolderList()));
+ }
+ }
+ if ( fti->folder()->folderType() == KMFolderTypeCachedImap && !multiFolder ) {
+ KMFolderCachedImap * folder = static_cast<KMFolderCachedImap*>( fti->folder()->storage() );
+ folderMenu->insertItem( SmallIconSet("wizard"),
+ i18n("&Troubleshoot IMAP Cache..."),
+ folder, SLOT(slotTroubleshoot()) );
+ }
+ folderMenu->insertSeparator();
+ }
+
+ if ( fti->folder() && fti->folder()->isMailingListEnabled() && !multiFolder ) {
+ mMainWidget->action("post_message")->plug(folderMenu);
+ }
+
+ if (fti->folder() && fti->parent() && !multiFolder)
+ {
+ folderMenu->insertItem(SmallIconSet("configure_shortcuts"),
+ i18n("&Assign Shortcut..."),
+ fti,
+ SLOT(assignShortcut()));
+
+ if ( !fti->folder()->noContent() ) {
+ folderMenu->insertItem( i18n("Expire..."), fti,
+ SLOT( slotShowExpiryProperties() ) );
+ }
+ mMainWidget->action("modify")->plug(folderMenu);
+ }
+
+
+ kmkernel->setContextMenuShown( true );
+ folderMenu->exec (p, 0);
+ kmkernel->setContextMenuShown( false );
+ triggerUpdate();
+ delete folderMenu;
+ folderMenu = 0;
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::contentsMousePressEvent(QMouseEvent * e)
+{
+ // KFolderTree messes around with the selection mode
+ KListView::contentsMousePressEvent( e );
+}
+
+// If middle button and folder holds mailing-list, create a message to that list
+void KMFolderTree::contentsMouseReleaseEvent(QMouseEvent* me)
+{
+ QListViewItem *lvi = currentItem(); // Needed for when branches are clicked on
+ ButtonState btn = me->button();
+ doFolderSelected(lvi, true);
+
+ // get underlying folder
+ KMFolderTreeItem* fti = dynamic_cast<KMFolderTreeItem*>(lvi);
+
+ if (!fti || !fti->folder()) {
+ KFolderTree::contentsMouseReleaseEvent(me);
+ return;
+ }
+
+ // react on middle-button only
+ if (btn != Qt::MidButton) {
+ KFolderTree::contentsMouseReleaseEvent(me);
+ return;
+ }
+
+ if ( fti->folder()->isMailingListEnabled() ) {
+ KMCommand *command = new KMMailingListPostCommand( this, fti->folder() );
+ command->start();
+ }
+
+ KFolderTree::contentsMouseReleaseEvent(me);
+}
+
+// little static helper
+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<const KMFolderImap*>( folder->storage() );
+ createRights = imapFolder->userRights() == 0 || // hack, we should get the acls
+ ( imapFolder->userRights() > 0 && ( imapFolder->userRights() & KMail::ACLJobs::Create ) );
+ } else if ( folder && folder->folderType() == KMFolderTypeCachedImap ) {
+ const KMFolderCachedImap *dimapFolder = static_cast<const KMFolderCachedImap*>( folder->storage() );
+ createRights = dimapFolder->userRights() == 0 ||
+ ( dimapFolder->userRights() > 0 && ( dimapFolder->userRights() & KMail::ACLJobs::Create ) );
+ }
+ return createRights;
+}
+
+//-----------------------------------------------------------------------------
+// Create a subfolder.
+// Requires creating the appropriate subdirectory and show a dialog
+void KMFolderTree::addChildFolder( KMFolder *folder, QWidget * parent )
+{
+ KMFolder *aFolder = folder;
+ if ( !aFolder ) {
+ KMFolderTreeItem *fti = static_cast<KMFolderTreeItem*>(currentItem());
+ if (!fti)
+ return;
+ aFolder = fti->folder();
+ }
+ if (aFolder) {
+ if (!aFolder->createChildFolder())
+ return;
+ if ( !folderHasCreateRights( aFolder ) ) {
+ // FIXME: change this message to "Cannot create folder under ..." or similar
+ const QString message = i18n( "<qt>Cannot create folder <b>%1</b> 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."
+ "</qt> " ).arg(aFolder->label());
+ KMessageBox::error( this, message );
+ return;
+ }
+ }
+
+ if ( parent )
+ ( new KMail::NewFolderDialog( parent, aFolder ) )->exec();
+ else
+ ( new KMail::NewFolderDialog( this, aFolder ) )->show();
+ return;
+/*
+ KMFolderDir *dir = &(kmkernel->folderMgr()->dir());
+ if (aFolder)
+ dir = aFolder->child();
+
+ KMFolderDialog *d =
+ new KMFolderDialog(0, dir, this, i18n("Create Subfolder") );
+
+ if (d->exec()) { // fti may be deleted here
+ QListViewItem *qlvi = indexOfFolder( aFolder );
+ if (qlvi) {
+ qlvi->setOpen(true);
+ blockSignals( true );
+ setCurrentItem( qlvi );
+ blockSignals( false );
+ }
+ }
+ delete d;
+ // update if added to root Folder
+ if (!aFolder || aFolder->noContent()) {
+ doFolderListChanged();
+ }
+ */
+}
+
+//-----------------------------------------------------------------------------
+// Returns whether a folder directory should be open as specified in the
+// config file.
+bool KMFolderTree::readIsListViewItemOpen(KMFolderTreeItem *fti)
+{
+ KConfig* config = KMKernel::config();
+ KMFolder *folder = fti->folder();
+ QString name;
+ if (folder)
+ {
+ name = "Folder-" + folder->idString();
+ } else if (fti->type() == KFolderTreeItem::Root)
+ {
+ if (fti->protocol() == KFolderTreeItem::NONE) // local root
+ name = "Folder_local_root";
+ else if (fti->protocol() == KFolderTreeItem::Search)
+ name = "Folder_search";
+ else
+ return false;
+ } else {
+ return false;
+ }
+ KConfigGroupSaver saver(config, name);
+
+ return config->readBoolEntry("isOpen", false);
+}
+
+//-----------------------------------------------------------------------------
+// Saves open/closed state of a folder directory into the config file
+void KMFolderTree::writeIsListViewItemOpen(KMFolderTreeItem *fti)
+{
+ KConfig* config = KMKernel::config();
+ KMFolder *folder = fti->folder();
+ QString name;
+ if (folder && !folder->idString().isEmpty())
+ {
+ name = "Folder-" + folder->idString();
+ } else if (fti->type() == KFolderTreeItem::Root)
+ {
+ if (fti->protocol() == KFolderTreeItem::NONE) // local root
+ name = "Folder_local_root";
+ else if (fti->protocol() == KFolderTreeItem::Search)
+ name = "Folder_search";
+ else
+ return;
+ } else {
+ return;
+ }
+ KConfigGroupSaver saver(config, name);
+ config->writeEntry("isOpen", fti->isOpen() );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::cleanupConfigFile()
+{
+ if ( childCount() == 0 )
+ return; // just in case reload wasn't called before
+ KConfig* config = KMKernel::config();
+ QStringList existingFolders;
+ QListViewItemIterator fldIt(this);
+ QMap<QString,bool> folderMap;
+ KMFolderTreeItem *fti;
+ for (QListViewItemIterator fldIt(this); fldIt.current(); fldIt++)
+ {
+ fti = static_cast<KMFolderTreeItem*>(fldIt.current());
+ if (fti && fti->folder())
+ folderMap.insert(fti->folder()->idString(), true);
+ }
+ QStringList groupList = config->groupList();
+ QString name;
+ for (QStringList::Iterator grpIt = groupList.begin();
+ grpIt != groupList.end(); grpIt++)
+ {
+ if ((*grpIt).left(7) != "Folder-") continue;
+ name = (*grpIt).mid(7);
+ if (folderMap.find(name) == folderMap.end())
+ {
+ KMFolder* folder = kmkernel->findFolderById( name );
+ if ( folder ) {
+ if ( kmkernel->iCalIface().hideResourceFolder( folder )
+ || kmkernel->iCalIface().hideResourceAccountRoot( folder ) )
+ continue; // hidden IMAP resource folder, don't delete info
+ if ( folder->noContent() )
+ continue; // we hide nocontent folders if they have no child folders
+ if ( folder == kmkernel->inboxFolder() )
+ continue; // local inbox can be hidden as well
+ }
+
+ //KMessageBox::error( 0, "cleanupConfigFile: Deleting group " + *grpIt );
+ config->deleteGroup(*grpIt, true);
+ kdDebug(5006) << "Deleting information about folder " << name << endl;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::openFolder()
+{
+ autoopen_timer.stop();
+ if ( dropItem && !dropItem->isOpen() ) {
+ dropItem->setOpen( true );
+ dropItem->repaint();
+ }
+}
+
+static const int autoopenTime = 750;
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::contentsDragEnterEvent( QDragEnterEvent *e )
+{
+ oldCurrent = 0;
+ oldSelected = 0;
+
+ oldCurrent = currentItem();
+ for ( QListViewItemIterator it( this ) ; it.current() ; ++it )
+ if ( it.current()->isSelected() )
+ oldSelected = it.current();
+
+ setFocus();
+
+ QListViewItem *i = itemAt( contentsToViewport(e->pos()) );
+ if ( i ) {
+ dropItem = i;
+ autoopen_timer.start( autoopenTime );
+ }
+ else
+ dropItem = 0;
+
+ e->accept( acceptDrag(e) );
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::contentsDragMoveEvent( QDragMoveEvent *e )
+{
+ QPoint vp = contentsToViewport(e->pos());
+ QListViewItem *i = itemAt( vp );
+ if ( i ) {
+ bool dragAccepted = acceptDrag( e );
+ if ( dragAccepted ) {
+ setCurrentItem( i );
+ }
+
+ if ( i != dropItem ) {
+ autoopen_timer.stop();
+ dropItem = i;
+ autoopen_timer.start( autoopenTime );
+ }
+
+ if ( dragAccepted ) {
+ e->accept( itemRect(i) );
+
+ switch ( e->action() ) {
+ case QDropEvent::Copy:
+ break;
+ case QDropEvent::Move:
+ e->acceptAction();
+ break;
+ case QDropEvent::Link:
+ e->acceptAction();
+ break;
+ default:
+ ;
+ }
+ } else {
+ e->accept( false );
+ }
+ } else {
+ e->accept( false );
+ autoopen_timer.stop();
+ dropItem = 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::contentsDragLeaveEvent( QDragLeaveEvent * )
+{
+ if (!oldCurrent) return;
+
+ autoopen_timer.stop();
+ dropItem = 0;
+
+ setCurrentItem( oldCurrent );
+ if ( oldSelected )
+ setSelected( oldSelected, true );
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::contentsDropEvent( QDropEvent *e )
+{
+ autoopen_timer.stop();
+
+ QListViewItem *item = itemAt( contentsToViewport(e->pos()) );
+ KMFolderTreeItem *fti = static_cast<KMFolderTreeItem*>(item);
+ // Check that each pointer is not null
+ for ( QValueList<QGuardedPtr<KMFolder> >::ConstIterator it = mCopySourceFolders.constBegin();
+ it != mCopySourceFolders.constEnd(); ++it ) {
+ if ( ! (*it) ) {
+ fti = 0;
+ break;
+ }
+ }
+ if (fti && mCopySourceFolders.count() == 1)
+ {
+ KMFolder *source = mCopySourceFolders.first();
+ // if we are dragging to ourselves or to our parent, set fti to 0 so nothing is done
+ if (source == fti->folder() || source->parent()->owner() == fti->folder()) fti = 0;
+ }
+ if (fti && acceptDrag(e) && ( fti != oldSelected || e->source() != mMainWidget->headers()->viewport() ) )
+ {
+ if ( e->provides("application/x-qlistviewitem") ) {
+ int action = dndMode( true /* always ask */ );
+ if ( (action == DRAG_COPY || action == DRAG_MOVE) && !mCopySourceFolders.isEmpty() ) {
+ for ( QValueList<QGuardedPtr<KMFolder> >::ConstIterator it = mCopySourceFolders.constBegin();
+ it != mCopySourceFolders.constEnd(); ++it ) {
+ if ( ! (*it)->isMoveable() )
+ action = DRAG_COPY;
+ }
+ moveOrCopyFolder( mCopySourceFolders, fti->folder(), (action == DRAG_MOVE) );
+ }
+ } else {
+ if ( e->source() == mMainWidget->headers()->viewport() ) {
+ int action;
+ if ( mMainWidget->headers()->folder() && mMainWidget->headers()->folder()->isReadOnly() )
+ action = DRAG_COPY;
+ else
+ action = dndMode();
+ // KMHeaders does copy/move itself
+ if ( action == DRAG_MOVE && fti->folder() )
+ emit folderDrop( fti->folder() );
+ else if ( action == DRAG_COPY && fti->folder() )
+ emit folderDropCopy( fti->folder() );
+ } else {
+ handleMailListDrop( e, fti->folder() );
+ }
+ }
+ e->accept( true );
+ } else
+ e->accept( false );
+
+ dropItem = 0;
+
+ setCurrentItem( oldCurrent );
+ if ( oldCurrent) mLastItem = static_cast<KMFolderTreeItem*>(oldCurrent);
+ if ( oldSelected )
+ {
+ clearSelection();
+ setSelected( oldSelected, true );
+ }
+
+ mCopySourceFolders.clear();
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::slotFolderExpanded( QListViewItem * item )
+{
+ KMFolderTreeItem *fti = static_cast<KMFolderTreeItem*>(item);
+ if ( !fti || !fti->folder() || !fti->folder()->storage() ) return;
+
+ fti->setFolderSize( fti->folder()->storage()->folderSize() );
+
+ if( fti->folder()->folderType() == KMFolderTypeImap )
+ {
+ KMFolderImap *folder = static_cast<KMFolderImap*>( fti->folder()->storage() );
+ // if we should list all folders we limit this to the root folder
+ if ( !folder->account() || ( !folder->account()->listOnlyOpenFolders() &&
+ fti->parent() ) )
+ return;
+ if ( folder->getSubfolderState() == KMFolderImap::imapNoInformation )
+ {
+ // check if all parents are expanded
+ QListViewItem *parent = item->parent();
+ while ( parent )
+ {
+ if ( !parent->isOpen() )
+ return;
+ parent = parent->parent();
+ }
+ // the tree will be reloaded after that
+ bool success = folder->listDirectory();
+ if (!success) fti->setOpen( false );
+ if ( fti->childCount() == 0 && fti->parent() )
+ fti->setExpandable( false );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::slotFolderCollapsed( QListViewItem * item )
+{
+ slotResetFolderList( item, false );
+ KMFolderTreeItem *fti = static_cast<KMFolderTreeItem*>(item);
+ if ( !fti || !fti->folder() || !fti->folder()->storage() ) return;
+
+ fti->setFolderSize( fti->folder()->storage()->folderSize() );
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::slotRenameFolder(QListViewItem *item, int col,
+ const QString &text)
+{
+
+ KMFolderTreeItem *fti = static_cast<KMFolderTreeItem*>(item);
+
+ if ((!fti) || (fti && fti->folder() && col != 0 && !currentFolder()->child()))
+ return;
+
+ QString fldName, oldFldName;
+
+ oldFldName = fti->name(0);
+
+ if (!text.isEmpty())
+ fldName = text;
+ else
+ fldName = oldFldName;
+
+ fldName.replace("/", "");
+ fldName.replace(QRegExp("^\\."), "");
+
+ if (fldName.isEmpty())
+ fldName = i18n("unnamed");
+
+ fti->setText(0, fldName);
+ fti->folder()->rename(fldName, &(kmkernel->folderMgr()->dir()));
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::slotUpdateCountsDelayed(KMFolder * folder)
+{
+// kdDebug(5006) << "KMFolderTree::slotUpdateCountsDelayed()" << endl;
+ if ( !mFolderToUpdateCount.contains( folder->idString() ) )
+ {
+// kdDebug( 5006 )<< "adding " << folder->idString() << " to updateCountList " << endl;
+ mFolderToUpdateCount.insert( folder->idString(),folder );
+ }
+ if ( !mUpdateCountTimer->isActive() )
+ mUpdateCountTimer->start( 500 );
+}
+
+
+void KMFolderTree::slotUpdateCountTimeout()
+{
+// kdDebug(5006) << "KMFolderTree::slotUpdateCountTimeout()" << endl;
+
+ QMap<QString,KMFolder*>::iterator it;
+ for ( it= mFolderToUpdateCount.begin();
+ it!=mFolderToUpdateCount.end();
+ ++it )
+ {
+ slotUpdateCounts( it.data() );
+ }
+ mFolderToUpdateCount.clear();
+ mUpdateCountTimer->stop();
+
+}
+
+void KMFolderTree::updatePopup() const
+{
+ mPopup->setItemChecked( mUnreadPop, isUnreadActive() );
+ mPopup->setItemChecked( mTotalPop, isTotalActive() );
+ mPopup->setItemChecked( mSizePop, isSizeActive() );
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::toggleColumn(int column, bool openFolders)
+{
+ if (column == unread)
+ {
+ // switch unread
+ if ( isUnreadActive() )
+ {
+ removeUnreadColumn();
+ reload();
+ } else {
+ addUnreadColumn( i18n("Unread"), 70 );
+ reload();
+ }
+ // toggle KPopupMenu
+ mPopup->setItemChecked( mUnreadPop, isUnreadActive() );
+
+ } else if (column == total) {
+ // switch total
+ if ( isTotalActive() )
+ {
+ removeTotalColumn();
+ reload();
+ } else {
+ addTotalColumn( i18n("Total"), 70 );
+ reload(openFolders);
+ }
+ mPopup->setItemChecked( mTotalPop, isTotalActive() );
+ } else if (column == foldersize) {
+ // switch total
+ if ( isSizeActive() )
+ {
+ removeSizeColumn();
+ reload();
+ } else {
+ addSizeColumn( i18n("Size"), 70 );
+ reload( openFolders );
+ }
+ // toggle KPopupMenu
+ mPopup->setItemChecked( mSizePop, isSizeActive() );
+
+ } else kdDebug(5006) << "unknown column:" << column << endl;
+
+ // toggles the switches of the mainwin
+ emit columnsChanged();
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::slotToggleUnreadColumn()
+{
+ toggleColumn(unread);
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::slotToggleTotalColumn()
+{
+ // activate the total-column and force the folders to be opened
+ toggleColumn(total, true);
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::slotToggleSizeColumn()
+{
+ // activate the size-column and force the folders to be opened
+ toggleColumn(foldersize, true);
+}
+
+
+//-----------------------------------------------------------------------------
+bool KMFolderTree::eventFilter( QObject *o, QEvent *e )
+{
+ if ( e->type() == QEvent::MouseButtonPress &&
+ static_cast<QMouseEvent*>(e)->button() == RightButton &&
+ o->isA("QHeader") )
+ {
+ mPopup->popup( static_cast<QMouseEvent*>(e)->globalPos() );
+ return true;
+ }
+ return KFolderTree::eventFilter(o, e);
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::slotCheckMail()
+{
+ if (!currentItem())
+ return;
+ KMFolderTreeItem* fti = static_cast<KMFolderTreeItem*>(currentItem());
+ KMFolder* folder = fti->folder();
+ if (folder && folder->storage() ) {
+ if ( KMAccount* acct = folder->storage()->account() ) {
+ kmkernel->acctMgr()->singleCheckMail(acct, true);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::slotNewMessageToMailingList()
+{
+ KMFolderTreeItem* fti = dynamic_cast<KMFolderTreeItem*>( currentItem() );
+ if ( !fti || !fti->folder() )
+ return;
+ KMCommand *command = new KMMailingListPostCommand( this, fti->folder() );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::createFolderList( QStringList *str,
+ QValueList<QGuardedPtr<KMFolder> > *folders,
+ bool localFolders,
+ bool imapFolders,
+ bool dimapFolders,
+ bool searchFolders,
+ bool includeNoContent,
+ bool includeNoChildren )
+{
+ for ( QListViewItemIterator it( this ) ; it.current() ; ++it )
+ {
+ KMFolderTreeItem * fti = static_cast<KMFolderTreeItem*>(it.current());
+ if (!fti || !fti->folder()) continue;
+ // type checks
+ KMFolder* folder = fti->folder();
+ if (!imapFolders && folder->folderType() == KMFolderTypeImap) continue;
+ if (!dimapFolders && folder->folderType() == KMFolderTypeCachedImap) continue;
+ if (!localFolders && (folder->folderType() == KMFolderTypeMbox ||
+ folder->folderType() == KMFolderTypeMaildir)) continue;
+ if (!searchFolders && folder->folderType() == KMFolderTypeSearch) continue;
+ if (!includeNoContent && folder->noContent()) continue;
+ if (!includeNoChildren && folder->noChildren()) continue;
+ QString prefix;
+ prefix.fill( ' ', 2 * fti->depth() );
+ str->append(prefix + fti->text(0));
+ folders->append(fti->folder());
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::slotResetFolderList( QListViewItem* item, bool startList )
+{
+ if ( !item )
+ item = currentItem();
+
+ KMFolderTreeItem* fti = dynamic_cast<KMFolderTreeItem*>( item );
+ if ( fti && fti->folder() &&
+ fti->folder()->folderType() == KMFolderTypeImap )
+ {
+ KMFolderImap *folder = static_cast<KMFolderImap*>( fti->folder()->storage() );
+ folder->setSubfolderState( KMFolderImap::imapNoInformation );
+ if ( startList )
+ folder->listDirectory();
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::showFolder( KMFolder* folder )
+{
+ if ( !folder ) return;
+ QListViewItem* item = indexOfFolder( folder );
+ if ( item )
+ {
+ doFolderSelected( item );
+ ensureItemVisible( item );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::folderToPopupMenu( MenuAction action, QObject *receiver,
+ KMMenuToFolder *aMenuToFolder, QPopupMenu *menu, QListViewItem *item )
+{
+ while ( menu->count() )
+ {
+ QPopupMenu *popup = menu->findItem( menu->idAt( 0 ) )->popup();
+ if ( popup )
+ delete popup;
+ else
+ menu->removeItemAt( 0 );
+ }
+ // connect the signals
+ if ( action == MoveMessage || action == MoveFolder )
+ {
+ disconnect( menu, SIGNAL(activated(int)), receiver,
+ SLOT(moveSelectedToFolder(int)) );
+ connect( menu, SIGNAL(activated(int)), receiver,
+ SLOT(moveSelectedToFolder(int)) );
+ } else {
+ disconnect( menu, SIGNAL(activated(int)), receiver,
+ SLOT(copySelectedToFolder(int)) );
+ connect( menu, SIGNAL(activated(int)), receiver,
+ SLOT(copySelectedToFolder(int)) );
+ }
+ if ( !item ) {
+ item = firstChild();
+
+ // avoid a popup menu with the single entry 'Local Folders' if there
+ // are no IMAP accounts
+ if ( childCount() == 2 && action != MoveFolder ) { // only 'Local Folders' and 'Searches'
+ KMFolderTreeItem *fti = static_cast<KMFolderTreeItem*>( item );
+ if ( fti->protocol() == KFolderTreeItem::Search ) {
+ // skip 'Searches'
+ item = item->nextSibling();
+ fti = static_cast<KMFolderTreeItem*>( item );
+ }
+ folderToPopupMenu( action, receiver, aMenuToFolder, menu, fti->firstChild() );
+ return;
+ }
+ }
+
+ while ( item )
+ {
+ KMFolderTreeItem* fti = static_cast<KMFolderTreeItem*>( item );
+ if ( fti->protocol() == KFolderTreeItem::Search )
+ {
+ // skip search folders
+ item = item->nextSibling();
+ continue;
+ }
+ QString label = fti->text( 0 );
+ label.replace( "&","&&" );
+ if ( fti->firstChild() )
+ {
+ // new level
+ QPopupMenu* popup = new QPopupMenu( menu, "subMenu" );
+ folderToPopupMenu( action, receiver, aMenuToFolder, popup, fti->firstChild() );
+ bool subMenu = false;
+ if ( ( action == MoveMessage || action == CopyMessage ) &&
+ fti->folder() && !fti->folder()->noContent() )
+ subMenu = true;
+ if ( ( action == MoveFolder || action == CopyFolder )
+ && ( !fti->folder() || ( fti->folder() && !fti->folder()->noChildren() ) ) )
+ subMenu = true;
+
+ QString sourceFolderName;
+ KMFolderTreeItem* srcItem = dynamic_cast<KMFolderTreeItem*>( currentItem() );
+ if ( srcItem )
+ sourceFolderName = srcItem->text( 0 );
+
+ if ( (action == MoveFolder || action == CopyFolder)
+ && fti->folder() && fti->folder()->child()
+ && fti->folder()->child()->hasNamedFolder( sourceFolderName ) ) {
+ subMenu = false;
+ }
+
+ if ( subMenu )
+ {
+ int menuId;
+ if ( action == MoveMessage || action == MoveFolder )
+ menuId = popup->insertItem( i18n("Move to This Folder"), -1, 0 );
+ else
+ menuId = popup->insertItem( i18n("Copy to This Folder"), -1, 0 );
+ popup->insertSeparator( 1 );
+ aMenuToFolder->insert( menuId, fti->folder() );
+ }
+ menu->insertItem( label, popup );
+ } else
+ {
+ // insert an item
+ int menuId = menu->insertItem( label );
+ if ( fti->folder() )
+ aMenuToFolder->insert( menuId, fti->folder() );
+ bool enabled = (fti->folder() ? true : false);
+ if ( fti->folder() &&
+ ( fti->folder()->isReadOnly() || fti->folder()->noContent() ) )
+ enabled = false;
+ menu->setItemEnabled( menuId, enabled );
+ }
+
+ item = item->nextSibling();
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::moveSelectedToFolder( int menuId )
+{
+ moveOrCopyFolder( selectedFolders(), mMenuToFolder[ menuId ], true /*move*/ );
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::copySelectedToFolder( int menuId )
+{
+ moveOrCopyFolder( selectedFolders(), mMenuToFolder[ menuId ], false /*copy, don't move*/ );
+}
+
+//-----------------------------------------------------------------------------
+void KMFolderTree::moveOrCopyFolder( QValueList<QGuardedPtr<KMFolder> > sources, KMFolder* destination, bool move )
+{
+ kdDebug(5006) << k_funcinfo << "source: " << sources << " destination: " << destination << " move: " << move << endl;
+
+ // Disable drag during copy operation since it prevents from many crashes
+ setDragEnabled( false );
+
+ KMFolderDir* parent = &(kmkernel->folderMgr()->dir());
+ if ( destination )
+ parent = destination->createChildFolder();
+
+ QStringList sourceFolderNames;
+
+ // check if move/copy is possible at all
+ for ( QValueList<QGuardedPtr<KMFolder> >::ConstIterator it = sources.constBegin(); it != sources.constEnd(); ++it ) {
+ KMFolder* source = *it;
+
+ // check if folder with same name already exits
+ QString sourceFolderName;
+ if ( source )
+ sourceFolderName = source->label();
+
+ if ( parent->hasNamedFolder( sourceFolderName ) || sourceFolderNames.contains( sourceFolderName ) ) {
+ KMessageBox::error( this, i18n("<qt>Cannot move or copy folder <b>%1</b> here because a folder with the same name already exists.</qt>")
+ .arg( sourceFolderName ) );
+ return;
+ }
+ sourceFolderNames.append( sourceFolderName );
+
+ // don't move/copy a folder that's still not completely moved/copied
+ KMFolder *f = source;
+ while ( f ) {
+ if ( f->moveInProgress() ) {
+ KMessageBox::error( this, i18n("<qt>Cannot move or copy folder <b>%1</b> because it is not completely copied itself.</qt>")
+ .arg( sourceFolderName ) );
+ return;
+ }
+ if ( f->parent() )
+ f = f->parent()->owner();
+ }
+
+ QString message =
+ i18n( "<qt>Cannot move or copy folder <b>%1</b> into a subfolder below itself.</qt>" ).
+ arg( sourceFolderName );
+ KMFolderDir* folderDir = parent;
+ // check that the folder can be moved
+ if ( source && source->child() )
+ {
+ while ( folderDir && ( folderDir != &kmkernel->folderMgr()->dir() ) &&
+ ( folderDir != source->parent() ) )
+ {
+ if ( folderDir->findRef( source ) != -1 )
+ {
+ KMessageBox::error( this, message );
+ return;
+ }
+ folderDir = folderDir->parent();
+ }
+ }
+
+ if( source && source->child() && parent &&
+ ( parent->path().find( source->child()->path() + "/" ) == 0 ) ) {
+ KMessageBox::error( this, message );
+ return;
+ }
+
+ if( source && source->child()
+ && ( parent == source->child() ) ) {
+ KMessageBox::error( this, message );
+ return;
+ }
+ }
+
+ // check if the source folders are independent of each other
+ for ( QValueList<QGuardedPtr<KMFolder> >::ConstIterator it = sources.constBegin(); move && it != sources.constEnd(); ++it ) {
+ KMFolderDir *parentDir = (*it)->child();
+ if ( !parentDir )
+ continue;
+ for ( QValueList<QGuardedPtr<KMFolder> >::ConstIterator it2 = sources.constBegin(); it2 != sources.constEnd(); ++it2 ) {
+ if ( *it == *it2 )
+ continue;
+ KMFolderDir *childDir = (*it2)->parent();
+ do {
+ if ( parentDir == childDir || parentDir->findRef( childDir->owner() ) != -1 ) {
+ KMessageBox::error( this, i18n("Moving the selected folders is not possible") );
+ return;
+ }
+ childDir = childDir->parent();
+ }
+ while ( childDir && childDir != &kmkernel->folderMgr()->dir() );
+ }
+ }
+
+ // de-select moved source folders (can cause crash due to unGetMsg() in KMHeaders)
+ if ( move ) {
+ doFolderSelected( indexOfFolder( destination ), false );
+ oldCurrent = currentItem();
+ }
+
+ // do the actual move/copy
+ for ( QValueList<QGuardedPtr<KMFolder> >::ConstIterator it = sources.constBegin(); it != sources.constEnd(); ++it ) {
+ KMFolder* source = *it;
+ if ( move ) {
+ kdDebug(5006) << "move folder " << (source ? source->label(): "Unknown") << " to "
+ << ( destination ? destination->label() : "Local Folders" ) << endl;
+ kmkernel->folderMgr()->moveFolder( source, parent );
+ } else {
+ kmkernel->folderMgr()->copyFolder( source, parent );
+ }
+ }
+}
+
+QDragObject * KMFolderTree::dragObject()
+{
+ KMFolderTreeItem *item = static_cast<KMFolderTreeItem*>
+ (itemAt(viewport()->mapFromGlobal(QCursor::pos())));
+ if ( !item || !item->parent() || !item->folder() ) // top-level items or something invalid
+ return 0;
+ mCopySourceFolders = selectedFolders();
+
+ QDragObject *drag = KFolderTree::dragObject();
+ if ( drag )
+ drag->setPixmap( SmallIcon("folder") );
+ return drag;
+}
+
+void KMFolderTree::copyFolder()
+{
+ KMFolderTreeItem *item = static_cast<KMFolderTreeItem*>( currentItem() );
+ if ( item ) {
+ mCopySourceFolders = selectedFolders();
+ mCutFolder = false;
+ }
+ updateCopyActions();
+}
+
+void KMFolderTree::cutFolder()
+{
+ KMFolderTreeItem *item = static_cast<KMFolderTreeItem*>( currentItem() );
+ if ( item ) {
+ mCopySourceFolders = selectedFolders();
+ mCutFolder = true;
+ }
+ updateCopyActions();
+}
+
+void KMFolderTree::pasteFolder()
+{
+ KMFolderTreeItem *item = static_cast<KMFolderTreeItem*>( currentItem() );
+ if ( !mCopySourceFolders.isEmpty() && item && !mCopySourceFolders.contains( item->folder() ) ) {
+ moveOrCopyFolder( mCopySourceFolders, item->folder(), mCutFolder );
+ if ( mCutFolder )
+ mCopySourceFolders.clear();
+ }
+ updateCopyActions();
+}
+
+void KMFolderTree::updateCopyActions()
+{
+ KAction *copy = mMainWidget->action("copy_folder");
+ KAction *cut = mMainWidget->action("cut_folder");
+ KAction *paste = mMainWidget->action("paste_folder");
+ KMFolderTreeItem *item = static_cast<KMFolderTreeItem*>( currentItem() );
+
+ if ( !item || !item->folder() ) {
+ copy->setEnabled( false );
+ cut->setEnabled( false );
+ } else {
+ copy->setEnabled( true );
+ cut->setEnabled( item->folder()->isMoveable() );
+ }
+
+ if ( mCopySourceFolders.isEmpty() )
+ paste->setEnabled( false );
+ else
+ paste->setEnabled( true );
+}
+
+void KMFolderTree::slotAddToFavorites()
+{
+ KMail::FavoriteFolderView *favView = mMainWidget->favoriteFolderView();
+ assert( favView );
+ for ( QListViewItemIterator it( this ); it.current(); ++it ) {
+ if ( it.current()->isSelected() )
+ favView->addFolder( static_cast<KMFolderTreeItem*>( it.current() ) );
+ }
+}
+
+void KMFolderTree::slotUnhideLocalInbox()
+{
+ disconnect( kmkernel->inboxFolder(), SIGNAL(msgAdded(KMFolder*,Q_UINT32)),
+ this, SLOT(slotUnhideLocalInbox()) );
+ reload();
+}
+
+#include "kmfoldertree.moc"
diff --git a/kmail/kmfoldertree.h b/kmail/kmfoldertree.h
new file mode 100644
index 00000000..e24f939b
--- /dev/null
+++ b/kmail/kmfoldertree.h
@@ -0,0 +1,350 @@
+/* -*- mode: C++ -*-
+ This file is part of the KDE libraries
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef __KMFOLDERTREE
+#define __KMFOLDERTREE
+
+#include "foldertreebase.h"
+
+#include <klocale.h>
+#include <kdepimmacros.h>
+
+#include <qguardedptr.h>
+#include <qwidget.h>
+#include <qtimer.h>
+#include <qheader.h>
+
+class QDropEvent;
+class QPixmap;
+class QPainter;
+class QPopupMenu;
+class KPopupMenu;
+class KMFolder;
+class KMFolderDir;
+class KMFolderImap;
+class KMFolderTree;
+class KMAccount;
+// duplication from kmcommands.h, to avoid the include
+typedef QMap<int,KMFolder*> KMMenuToFolder;
+template <typename T> class QGuardedPtr;
+
+class KDE_EXPORT KMFolderTreeItem : public QObject, public KFolderTreeItem
+{
+ Q_OBJECT
+public:
+ /** Construct a root item _without_ folder */
+ KMFolderTreeItem( KFolderTree *parent, const QString & name,
+ KFolderTreeItem::Protocol protocol=KFolderTreeItem::NONE );
+
+ /** Construct a root item _with_ folder */
+ KMFolderTreeItem( KFolderTree *parent, const QString & name,
+ KMFolder* folder );
+
+ /** Construct a child item */
+ KMFolderTreeItem( KFolderTreeItem* parent, const QString & name,
+ KMFolder* folder );
+ virtual ~KMFolderTreeItem();
+
+ QPixmap normalIcon( int size ) const;
+ QPixmap unreadIcon( int size ) const;
+
+ void setNeedsRepaint( bool value ) { mNeedsRepaint = value; }
+ bool needsRepaint() const { return mNeedsRepaint; }
+
+ /** associated folder */
+ KMFolder* folder() const { return mFolder; }
+ QListViewItem* parent() const { return KFolderTreeItem::parent(); }
+
+ /** Adjust the unread count from the folder and update the
+ * pixmaps accordingly. */
+ void adjustUnreadCount( int newUnreadCount );
+
+ /** dnd */
+ virtual bool acceptDrag(QDropEvent* e) const;
+
+signals:
+ /** Our icon changed */
+ void iconChanged( KMFolderTreeItem * );
+ /** Our name changed */
+ void nameChanged( KMFolderTreeItem * );
+
+public slots:
+ void properties();
+ void assignShortcut();
+ void slotShowExpiryProperties();
+ void slotIconsChanged();
+ void slotNameChanged();
+ void updateCount();
+
+protected:
+ void init();
+ KMFolder* mFolder;
+ /** Returns true when top-level/account icons should be used */
+ virtual bool useTopLevelIcon() const { return depth() == 0; }
+ /** Returns the icon size. */
+ virtual int iconSize() const { return 16; }
+
+private:
+ bool mNeedsRepaint;
+};
+
+//==========================================================================
+
+class KMFolderTree : public KMail::FolderTreeBase
+{
+ Q_OBJECT
+
+public:
+ KMFolderTree( KMMainWidget *mainWidget, QWidget *parent=0,
+ const char *name=0 );
+
+ /** 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 );
+
+ /** create a folderlist */
+ void createFolderList( QStringList *str,
+ QValueList<QGuardedPtr<KMFolder> > *folders,
+ bool localFolders=true,
+ bool imapFolders=true,
+ bool dimapFolders=true,
+ bool searchFolders=false,
+ bool includeNoContent=true,
+ bool includeNoChildren=true );
+
+ /** Read config options. */
+ virtual void readConfig(void);
+
+ /** Remove information about not existing folders from the config file */
+ void cleanupConfigFile();
+
+ /** Select the next folder with unread messages */
+ void nextUnreadFolder(bool confirm);
+
+ /** Check folder for unread messages (which isn't trash)*/
+ bool checkUnreadFolder(KMFolderTreeItem* ftl, bool confirm);
+
+ KMFolder *currentFolder() const;
+
+ QValueList<QGuardedPtr<KMFolder> > selectedFolders();
+
+ enum ColumnMode {unread=15, total=16, foldersize=17};
+
+ /** toggles the unread and total columns on/off */
+ void toggleColumn(int column, bool openFolders = false);
+
+ /** Set the checked/unchecked state of the unread and total column
+ * in the popup correctly */
+ virtual void updatePopup() const;
+
+ /** Select the folder and make sure it's visible */
+ void showFolder( KMFolder* );
+
+ /** Valid actions for the folderToPopup method */
+ enum MenuAction {
+ CopyMessage,
+ MoveMessage,
+ CopyFolder,
+ MoveFolder
+ };
+
+ /** Generate a popup menu that contains all folders that can have content */
+ void folderToPopupMenu( MenuAction action, QObject *receiver, KMMenuToFolder *,
+ QPopupMenu *menu, QListViewItem *start = 0 );
+
+signals:
+ /** The selected folder has changed */
+ void folderSelected(KMFolder*);
+
+ /** The selected folder has changed to go to an unread message */
+ void folderSelectedUnread( KMFolder * );
+
+ /** unread/total/size column has changed */
+ void columnsChanged();
+
+ /** an icon of one of our folders changed */
+ void iconChanged( KMFolderTreeItem * );
+
+ /** the name of one of our folders changed */
+ void nameChanged( KMFolderTreeItem * );
+
+public slots:
+ /** Select the next folder with unread messages */
+ void nextUnreadFolder();
+
+ /** Select the previous folder with unread messages */
+ void prevUnreadFolder();
+
+ /** Increment current folder */
+ void incCurrentFolder();
+
+ /** Decrement current folder */
+ void decCurrentFolder();
+
+ /** Select the current folder */
+ void selectCurrentFolder();
+
+ /** Executes delayed update of folder tree */
+ void delayedUpdate();
+
+ /** Make sure the given account is not selected because it is gone */
+ void slotAccountRemoved(KMAccount*);
+
+ /** Select the item and switch to the folder */
+ void doFolderSelected(QListViewItem *qlvi, bool keepSelection = false);
+
+ /**
+ * Reset current folder and all childs
+ * If no item is given we take the current one
+ * If startListing is true a folder listing is started
+ */
+ void slotResetFolderList( QListViewItem* item = 0, bool startList = true );
+
+ /** Create a child folder */
+ void addChildFolder( KMFolder *folder = 0, QWidget * parent = 0 );
+
+ /** Copies the currently selected folder. */
+ void copyFolder();
+
+ /** Cuts the currently selected folder. */
+ void cutFolder();
+
+ /** Pastes a previously copied/cutted folder below the currently selected folder. */
+ void pasteFolder();
+
+protected slots:
+ // void slotRMB(int, int);
+ /** called by the folder-manager when the list of folders changed */
+ void doFolderListChanged();
+
+ /** called, when a folder has been deleted */
+ void slotFolderRemoved(KMFolder *);
+
+ /** called, when a folder has been moved or copied, successfully or not */
+ void slotFolderMoveOrCopyOperationFinished();
+
+ /** Updates the folder tree (delayed), causing a "blink" */
+ void refresh();
+
+ /** Open a folder */
+ void openFolder();
+
+ /** Expand an IMAP folder */
+ void slotFolderExpanded( QListViewItem * item );
+
+ /** Tell the folder to refresh the contents on the next expansion */
+ void slotFolderCollapsed( QListViewItem * item );
+
+ /** Check if the new name is valid and confirm the new name */
+ void slotRenameFolder( QListViewItem * item, int col, const QString& text);
+
+ /** Update the total and unread columns but delayed */
+ void slotUpdateCountsDelayed(KMFolder * folder);
+ void slotUpdateCountTimeout();
+ void slotUpdateOneCount();
+
+ /** slots for the unread/total/size-popup */
+ void slotToggleUnreadColumn();
+ void slotToggleTotalColumn();
+ void slotToggleSizeColumn();
+
+ void slotContextMenuRequested( QListViewItem *, const QPoint & );
+
+ /** Fires a new-mail-check of the account that is accociated with currentItem */
+ void slotCheckMail();
+
+ void slotNewMessageToMailingList();
+
+ /** For RMB move folder */
+ virtual void moveSelectedToFolder( int menuId );
+ /** For RMB copy folder */
+ virtual void copySelectedToFolder( int menuId );
+
+ /** Updates copy/cut/paste actions */
+ void updateCopyActions();
+
+protected:
+ virtual void contentsMousePressEvent( QMouseEvent *e );
+ virtual void contentsMouseReleaseEvent(QMouseEvent* me);
+
+ /** Updates the number of unread messages for all folders */
+ virtual void updateUnreadAll( );
+
+ virtual void resizeEvent(QResizeEvent*);
+
+ /** Read/Save open/close state indicator for an item in folderTree list view */
+ bool readIsListViewItemOpen(KMFolderTreeItem *fti);
+ void writeIsListViewItemOpen(KMFolderTreeItem *fti);
+
+ QTimer mUpdateTimer;
+
+ /** Drag and drop methods */
+ void contentsDragEnterEvent( QDragEnterEvent *e );
+ void contentsDragMoveEvent( QDragMoveEvent *e );
+ void contentsDragLeaveEvent( QDragLeaveEvent *e );
+ void contentsDropEvent( QDropEvent *e );
+ virtual QDragObject* dragObject();
+
+ /** Drag and drop variables */
+ QListViewItem *oldCurrent, *oldSelected;
+ QListViewItem *dropItem;
+ KMFolderTreeItem *mLastItem;
+ QTimer autoopen_timer;
+
+ // filter some rmb-events
+ bool eventFilter(QObject*, QEvent*);
+
+ /** open ancestors and ensure item is visible */
+ void prepareItem( KMFolderTreeItem* );
+
+ /** connect all signals */
+ void connectSignals();
+
+ /** Move or copy the folder @p source to @p destination. */
+ void moveOrCopyFolder( QValueList<QGuardedPtr<KMFolder> > sources, KMFolder* destination, bool move=false );
+
+private slots:
+ void slotAddToFavorites();
+ void slotUnhideLocalInbox();
+
+private:
+ /** total column */
+ QListViewItemIterator mUpdateIterator;
+
+ /** popup for unread/total */
+ KPopupMenu* mPopup;
+ int mUnreadPop;
+ int mTotalPop;
+ int mSizePop;
+
+ bool mReloading;
+ QValueList<QGuardedPtr<KMFolder> > mCopySourceFolders;
+ bool mCutFolder;
+
+ QTimer *mUpdateCountTimer;
+ QMap<QString,KMFolder*> mFolderToUpdateCount;
+
+ /** Map menu id into a folder */
+ KMMenuToFolder mMenuToFolder;
+};
+
+#endif
diff --git a/kmail/kmfoldertype.h b/kmail/kmfoldertype.h
new file mode 100644
index 00000000..1414fa91
--- /dev/null
+++ b/kmail/kmfoldertype.h
@@ -0,0 +1,37 @@
+#ifndef KMFOLDERTYPE_H
+#define KMFOLDERTYPE_H
+
+typedef enum
+{
+ KMFolderTypeMbox = 0,
+ KMFolderTypeMaildir,
+ KMFolderTypeCachedImap,
+ KMFolderTypeImap,
+ KMFolderTypeSearch,
+ KMFolderTypeUnknown
+} KMFolderType;
+
+typedef enum
+{
+ KMStandardDir = 0,
+ KMImapDir,
+ KMDImapDir,
+ KMSearchDir
+} KMFolderDirType;
+
+namespace KMail {
+
+typedef enum
+{
+ ContentsTypeMail = 0,
+ ContentsTypeCalendar,
+ ContentsTypeContact,
+ ContentsTypeNote,
+ ContentsTypeTask,
+ ContentsTypeJournal,
+ ContentsTypeLast = ContentsTypeJournal
+} FolderContentsType;
+
+}
+
+#endif // KMFOLDERTYPE_H
diff --git a/kmail/kmglobal.h b/kmail/kmglobal.h
new file mode 100644
index 00000000..e44bd313
--- /dev/null
+++ b/kmail/kmglobal.h
@@ -0,0 +1,92 @@
+/*
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ *
+ * 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.
+ *
+ */
+// removed almost everything: Sven Radej <radej@kde.org>
+
+//this could all go int kmkernel.h
+#ifndef kmglobal_h
+#define kmglobal_h
+
+typedef enum
+{
+ FCNTL,
+ procmail_lockfile,
+ mutt_dotlock,
+ mutt_dotlock_privileged,
+ lock_none
+} LockType;
+
+/*
+ * Define the possible units to use for measuring message expiry.
+ * expireNever is used to switch off message expiry, and expireMaxUnits
+ * must always be the last in the list (for bounds checking).
+ */
+typedef enum {
+ expireNever,
+ expireDays,
+ expireWeeks,
+ expireMonths,
+ expireMaxUnits
+} ExpireUnits;
+
+#define HDR_FROM 0x01
+#define HDR_REPLY_TO 0x02
+#define HDR_TO 0x04
+#define HDR_CC 0x08
+#define HDR_BCC 0x10
+#define HDR_SUBJECT 0x20
+#define HDR_NEWSGROUPS 0x40
+#define HDR_FOLLOWUP_TO 0x80
+#define HDR_IDENTITY 0x100
+#define HDR_TRANSPORT 0x200
+#define HDR_FCC 0x400
+#define HDR_DICTIONARY 0x800
+#define HDR_ALL 0xfff
+
+#define HDR_STANDARD (HDR_SUBJECT|HDR_TO|HDR_CC)
+
+#include <algorithm>
+
+namespace KMail {
+// List of prime numbers shamelessly stolen from GCC STL
+ enum { num_primes = 29 };
+
+ static const unsigned long prime_list[ num_primes ] =
+ {
+ 31ul, 53ul, 97ul, 193ul, 389ul,
+ 769ul, 1543ul, 3079ul, 6151ul, 12289ul,
+ 24593ul, 49157ul, 98317ul, 196613ul, 393241ul,
+ 786433ul, 1572869ul, 3145739ul, 6291469ul, 12582917ul,
+ 25165843ul, 50331653ul, 100663319ul, 201326611ul, 402653189ul,
+ 805306457ul, 1610612741ul, 3221225473ul, 4294967291ul
+ };
+
+ inline unsigned long nextPrime( unsigned long n )
+ {
+ const unsigned long *first = prime_list;
+ const unsigned long *last = prime_list + num_primes;
+ const unsigned long *pos = std::lower_bound( first, last, n );
+ return pos == last ? *( last - 1 ) : *pos;
+ }
+
+}
+
+/** The "about KMail" text. */
+extern const char* aboutText;
+#endif
diff --git a/kmail/kmgroupware.cpp b/kmail/kmgroupware.cpp
new file mode 100644
index 00000000..ecc960ee
--- /dev/null
+++ b/kmail/kmgroupware.cpp
@@ -0,0 +1,88 @@
+/*
+ kmgroupware.cpp
+
+ This file is part of KMail.
+
+ Copyright (c) 2003 - 2004 Bo Thorsen <bo@sonofthor.dk>
+ Copyright (c) 2002 Karl-Heinz Zimmer <khz@klaralvdalens-datakonsult.se>
+ Copyright (c) 2003 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "kmgroupware.h"
+#include "kmmessage.h"
+#include "kmmsgpart.h"
+#include "libkcal/incidenceformatter.h"
+#include <kdebug.h>
+#include <mimelib/enum.h>
+#include <assert.h>
+
+
+bool vPartFoundAndDecoded( KMMessage* msg, QString& s )
+{
+ assert( msg );
+
+ if( ( DwMime::kTypeText == msg->type() && ( DwMime::kSubtypeVCal == msg->subtype() ||
+ DwMime::kSubtypeXVCard == msg->subtype() ) ) ||
+ ( DwMime::kTypeApplication == msg->type() &&
+ DwMime::kSubtypeOctetStream == msg->subtype() ) )
+ {
+ s = QString::fromUtf8( msg->bodyDecoded() );
+ return true;
+ } else if( DwMime::kTypeMultipart == msg->type() &&
+ (DwMime::kSubtypeMixed == msg->subtype() ) ||
+ (DwMime::kSubtypeAlternative == msg->subtype() ))
+ {
+ // kdDebug(5006) << "KMGroupware looking for TNEF data" << endl;
+ DwBodyPart* dwPart = msg->findDwBodyPart( DwMime::kTypeApplication,
+ DwMime::kSubtypeMsTNEF );
+ if( !dwPart )
+ dwPart = msg->findDwBodyPart( DwMime::kTypeApplication,
+ DwMime::kSubtypeOctetStream );
+ if( dwPart ){
+ // kdDebug(5006) << "KMGroupware analyzing TNEF data" << endl;
+ KMMessagePart msgPart;
+ KMMessage::bodyPart(dwPart, &msgPart);
+ s = KCal::IncidenceFormatter::msTNEFToVPart( msgPart.bodyDecodedBinary() );
+ return !s.isEmpty();
+ } else {
+ dwPart = msg->findDwBodyPart( DwMime::kTypeText, DwMime::kSubtypeVCal );
+ if (dwPart) {
+ KMMessagePart msgPart;
+ KMMessage::bodyPart(dwPart, &msgPart);
+ s = msgPart.body();
+ return true;
+ }
+ }
+ }else if( DwMime::kTypeMultipart == msg->type() &&
+ DwMime::kSubtypeMixed == msg->subtype() ) {
+ // TODO: Something?
+ }
+
+ return false;
+}
diff --git a/kmail/kmgroupware.h b/kmail/kmgroupware.h
new file mode 100644
index 00000000..7296d4b3
--- /dev/null
+++ b/kmail/kmgroupware.h
@@ -0,0 +1,46 @@
+/*
+ kmgroupware.h
+
+ This file is part of KMail.
+
+ Copyright (c) 2003 - 2004 Bo Thorsen <bo@sonofthor.dk>
+ Copyright (c) 2002 Karl-Heinz Zimmer <khz@klaralvdalens-datakonsult.se>
+ Copyright (c) 2003 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef KMGROUPWARE_H
+#define KMGROUPWARE_H
+
+#include <qstring.h>
+
+class KMMessage;
+
+bool vPartFoundAndDecoded( KMMessage* msg, QString& s );
+
+#endif /* KMGROUPWARE_H */
diff --git a/kmail/kmheaders.cpp b/kmail/kmheaders.cpp
new file mode 100644
index 00000000..e2dd439d
--- /dev/null
+++ b/kmail/kmheaders.cpp
@@ -0,0 +1,3569 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+// kmheaders.cpp
+
+#include <config.h>
+
+#include "kmheaders.h"
+#include "headeritem.h"
+using KMail::HeaderItem;
+
+#include "kcursorsaver.h"
+#include "kmcommands.h"
+#include "kmmainwidget.h"
+#include "kmfiltermgr.h"
+#include "undostack.h"
+#include "kmmsgdict.h"
+#include "kmdebug.h"
+#include "kmfoldertree.h"
+#include "folderjob.h"
+using KMail::FolderJob;
+#include "actionscheduler.h"
+using KMail::ActionScheduler;
+#include "messagecopyhelper.h"
+using KMail::MessageCopyHelper;
+#include "broadcaststatus.h"
+using KPIM::BroadcastStatus;
+#include "progressmanager.h"
+using KPIM::ProgressManager;
+using KPIM::ProgressItem;
+#include <maillistdrag.h>
+#include "globalsettings.h"
+using namespace KPIM;
+#include "messageactions.h"
+
+#include <kapplication.h>
+#include <kaccelmanager.h>
+#include <kglobalsettings.h>
+#include <kmessagebox.h>
+#include <kiconloader.h>
+#include <kpopupmenu.h>
+#include <kimageio.h>
+#include <kconfig.h>
+#include <klocale.h>
+#include <kdebug.h>
+
+#include <qbuffer.h>
+#include <qeventloop.h>
+#include <qfile.h>
+#include <qheader.h>
+#include <qptrstack.h>
+#include <qptrqueue.h>
+#include <qpainter.h>
+#include <qtextcodec.h>
+#include <qstyle.h>
+#include <qlistview.h>
+
+#include <mimelib/enum.h>
+#include <mimelib/field.h>
+#include <mimelib/mimepp.h>
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include "textsource.h"
+
+QPixmap* KMHeaders::pixNew = 0;
+QPixmap* KMHeaders::pixUns = 0;
+QPixmap* KMHeaders::pixDel = 0;
+QPixmap* KMHeaders::pixRead = 0;
+QPixmap* KMHeaders::pixRep = 0;
+QPixmap* KMHeaders::pixQueued = 0;
+QPixmap* KMHeaders::pixTodo = 0;
+QPixmap* KMHeaders::pixSent = 0;
+QPixmap* KMHeaders::pixFwd = 0;
+QPixmap* KMHeaders::pixFlag = 0;
+QPixmap* KMHeaders::pixWatched = 0;
+QPixmap* KMHeaders::pixIgnored = 0;
+QPixmap* KMHeaders::pixSpam = 0;
+QPixmap* KMHeaders::pixHam = 0;
+QPixmap* KMHeaders::pixFullySigned = 0;
+QPixmap* KMHeaders::pixPartiallySigned = 0;
+QPixmap* KMHeaders::pixUndefinedSigned = 0;
+QPixmap* KMHeaders::pixFullyEncrypted = 0;
+QPixmap* KMHeaders::pixPartiallyEncrypted = 0;
+QPixmap* KMHeaders::pixUndefinedEncrypted = 0;
+QPixmap* KMHeaders::pixEncryptionProblematic = 0;
+QPixmap* KMHeaders::pixSignatureProblematic = 0;
+QPixmap* KMHeaders::pixAttachment = 0;
+QPixmap* KMHeaders::pixReadFwd = 0;
+QPixmap* KMHeaders::pixReadReplied = 0;
+QPixmap* KMHeaders::pixReadFwdReplied = 0;
+
+
+//-----------------------------------------------------------------------------
+KMHeaders::KMHeaders(KMMainWidget *aOwner, QWidget *parent,
+ const char *name) :
+ KListView(parent, name)
+{
+ static bool pixmapsLoaded = false;
+ //qInitImageIO();
+ KImageIO::registerFormats();
+ mOwner = aOwner;
+ mFolder = 0;
+ noRepaint = false;
+ getMsgIndex = -1;
+ mTopItem = 0;
+ setSelectionMode( QListView::Extended );
+ setAllColumnsShowFocus( true );
+ mNested = false;
+ nestingPolicy = OpenUnread;
+ mNestedOverride = false;
+ mSubjThreading = true;
+ mMousePressed = false;
+ mSortInfo.dirty = true;
+ mSortInfo.fakeSort = 0;
+ mSortInfo.removed = 0;
+ mSortInfo.column = 0;
+ mSortCol = 2; // 2 == date
+ mSortDescending = false;
+ mSortInfo.ascending = false;
+ mReaderWindowActive = false;
+ mRoot = new SortCacheItem;
+ mRoot->setId(-666); //mark of the root!
+ setStyleDependantFrameWidth();
+ // popup-menu
+ header()->setClickEnabled(true);
+ header()->installEventFilter(this);
+ mPopup = new KPopupMenu(this);
+ mPopup->insertTitle(i18n("View Columns"));
+ mPopup->setCheckable(true);
+ mPopup->insertItem(i18n("Status"), KPaintInfo::COL_STATUS);
+ 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("Spam/Ham"), KPaintInfo::COL_SPAM_HAM);
+ mPopup->insertItem(i18n("Watched/Ignored"), KPaintInfo::COL_WATCHED_IGNORED);
+ mPopup->insertItem(i18n("Signature"), KPaintInfo::COL_SIGNED);
+ mPopup->insertItem(i18n("Encryption"), KPaintInfo::COL_CRYPTO);
+ mPopup->insertItem(i18n("Size"), KPaintInfo::COL_SIZE);
+ mPopup->insertItem(i18n("Receiver"), KPaintInfo::COL_RECEIVER);
+
+ connect(mPopup, SIGNAL(activated(int)), this, SLOT(slotToggleColumn(int)));
+
+ setShowSortIndicator(true);
+ setFocusPolicy( WheelFocus );
+
+ if (!pixmapsLoaded)
+ {
+ pixmapsLoaded = true;
+ pixNew = new QPixmap( UserIcon( "kmmsgnew" ) );
+ pixUns = new QPixmap( UserIcon( "kmmsgunseen" ) );
+ pixDel = new QPixmap( UserIcon( "kmmsgdel" ) );
+ pixRead = new QPixmap( UserIcon( "kmmsgread" ) );
+ pixRep = new QPixmap( UserIcon( "kmmsgreplied" ) );
+ pixQueued = new QPixmap( UserIcon( "kmmsgqueued" ) );
+ pixTodo = new QPixmap( UserIcon( "kmmsgtodo" ) );
+ pixSent = new QPixmap( UserIcon( "kmmsgsent" ) );
+ pixFwd = new QPixmap( UserIcon( "kmmsgforwarded" ) );
+ pixFlag = new QPixmap( UserIcon( "kmmsgflag" ) );
+ pixWatched = new QPixmap( UserIcon( "kmmsgwatched" ) );
+ pixIgnored = new QPixmap( UserIcon( "kmmsgignored" ) );
+ pixSpam = new QPixmap( UserIcon( "kmmsgspam" ) );
+ pixHam = new QPixmap( UserIcon( "kmmsgham" ) );
+ pixFullySigned = new QPixmap( UserIcon( "kmmsgfullysigned" ) );
+ pixPartiallySigned = new QPixmap( UserIcon( "kmmsgpartiallysigned" ) );
+ pixUndefinedSigned = new QPixmap( UserIcon( "kmmsgundefinedsigned" ) );
+ pixFullyEncrypted = new QPixmap( UserIcon( "kmmsgfullyencrypted" ) );
+ pixPartiallyEncrypted = new QPixmap( UserIcon( "kmmsgpartiallyencrypted" ) );
+ pixUndefinedEncrypted = new QPixmap( UserIcon( "kmmsgundefinedencrypted" ) );
+ pixEncryptionProblematic = new QPixmap( UserIcon( "kmmsgencryptionproblematic" ) );
+ pixSignatureProblematic = new QPixmap( UserIcon( "kmmsgsignatureproblematic" ) );
+ pixAttachment = new QPixmap( UserIcon( "kmmsgattachment" ) );
+ pixReadFwd = new QPixmap( UserIcon( "kmmsgread_fwd" ) );
+ pixReadReplied = new QPixmap( UserIcon( "kmmsgread_replied" ) );
+ pixReadFwdReplied = new QPixmap( UserIcon( "kmmsgread_fwd_replied" ) );
+ }
+
+ header()->setStretchEnabled( false );
+ header()->setResizeEnabled( false );
+
+ mPaintInfo.subCol = addColumn( i18n("Subject"), 310 );
+ mPaintInfo.senderCol = addColumn( i18n("Sender"), 170 );
+ mPaintInfo.dateCol = addColumn( i18n("Date"), 170 );
+ mPaintInfo.sizeCol = addColumn( i18n("Size"), 0 );
+ mPaintInfo.receiverCol = addColumn( i18n("Receiver"), 0 );
+
+ mPaintInfo.statusCol = addColumn( *pixNew , "", 0 );
+ mPaintInfo.importantCol = addColumn( *pixFlag , "", 0 );
+ mPaintInfo.todoCol = addColumn( *pixTodo , "", 0 );
+ mPaintInfo.attachmentCol = addColumn( *pixAttachment , "", 0 );
+ mPaintInfo.spamHamCol = addColumn( *pixSpam , "", 0 );
+ mPaintInfo.watchedIgnoredCol = addColumn( *pixWatched , "", 0 );
+ mPaintInfo.signedCol = addColumn( *pixFullySigned , "", 0 );
+ mPaintInfo.cryptoCol = addColumn( *pixFullyEncrypted, "", 0 );
+
+ setResizeMode( QListView::NoColumn );
+
+ // only the non-optional columns shall be resizeable
+ header()->setResizeEnabled( true, mPaintInfo.subCol );
+ header()->setResizeEnabled( true, mPaintInfo.senderCol );
+ header()->setResizeEnabled( true, mPaintInfo.dateCol );
+
+ connect( this, SIGNAL( contextMenuRequested( QListViewItem*, const QPoint &, int )),
+ this, SLOT( rightButtonPressed( QListViewItem*, const QPoint &, int )));
+ connect(this, SIGNAL(doubleClicked(QListViewItem*)),
+ this,SLOT(selectMessage(QListViewItem*)));
+ connect(this,SIGNAL(currentChanged(QListViewItem*)),
+ this,SLOT(highlightMessage(QListViewItem*)));
+ resetCurrentTime();
+
+ mSubjectLists.setAutoDelete( true );
+
+ mMoveMessages = false;
+ connect( this, SIGNAL(selectionChanged()), SLOT(updateActions()) );
+}
+
+
+//-----------------------------------------------------------------------------
+KMHeaders::~KMHeaders ()
+{
+ if (mFolder)
+ {
+ writeFolderConfig();
+ writeSortOrder();
+ mFolder->close("kmheaders");
+ }
+ writeConfig();
+ delete mRoot;
+}
+
+//-----------------------------------------------------------------------------
+bool KMHeaders::eventFilter ( QObject *o, QEvent *e )
+{
+ if ( e->type() == QEvent::MouseButtonPress &&
+ static_cast<QMouseEvent*>(e)->button() == RightButton &&
+ o->isA("QHeader") )
+ {
+ // if we currently only show one of either sender/receiver column
+ // modify the popup text in the way, that it displays the text of the other of the two
+ if ( mPaintInfo.showReceiver )
+ mPopup->changeItem(KPaintInfo::COL_RECEIVER, i18n("Receiver"));
+ else
+ if ( mFolder && (mFolder->whoField().lower() == "to") )
+ mPopup->changeItem(KPaintInfo::COL_RECEIVER, i18n("Sender"));
+ else
+ mPopup->changeItem(KPaintInfo::COL_RECEIVER, i18n("Receiver"));
+
+ mPopup->popup( static_cast<QMouseEvent*>(e)->globalPos() );
+ return true;
+ }
+ return KListView::eventFilter(o, e);
+}
+
+//-----------------------------------------------------------------------------
+
+void KMHeaders::slotToggleColumn(int id, int mode)
+{
+ bool *show = 0;
+ int *col = 0;
+ int width = 0;
+ int moveToCol = -1;
+
+ switch ( static_cast<KPaintInfo::ColumnIds>(id) )
+ {
+ case KPaintInfo::COL_SIZE:
+ {
+ show = &mPaintInfo.showSize;
+ col = &mPaintInfo.sizeCol;
+ width = 80;
+ break;
+ }
+ case KPaintInfo::COL_ATTACHMENT:
+ {
+ show = &mPaintInfo.showAttachment;
+ col = &mPaintInfo.attachmentCol;
+ width = pixAttachment->width() + 8;
+ if ( *col == header()->mapToIndex( *col ) )
+ moveToCol = 0;
+ break;
+ }
+ case KPaintInfo::COL_IMPORTANT:
+ {
+ show = &mPaintInfo.showImportant;
+ col = &mPaintInfo.importantCol;
+ width = pixFlag->width() + 8;
+ if ( *col == header()->mapToIndex( *col ) )
+ moveToCol = 0;
+ break;
+ }
+ case KPaintInfo::COL_TODO:
+ {
+ show = &mPaintInfo.showTodo;
+ col = &mPaintInfo.todoCol;
+ width = pixTodo->width() + 8;
+ if ( *col == header()->mapToIndex( *col ) )
+ moveToCol = 0;
+ break;
+ }
+ case KPaintInfo::COL_SPAM_HAM:
+ {
+ show = &mPaintInfo.showSpamHam;
+ col = &mPaintInfo.spamHamCol;
+ width = pixSpam->width() + 8;
+ if ( *col == header()->mapToIndex( *col ) )
+ moveToCol = 0;
+ break;
+ }
+ case KPaintInfo::COL_WATCHED_IGNORED:
+ {
+ show = &mPaintInfo.showWatchedIgnored;
+ col = &mPaintInfo.watchedIgnoredCol;
+ width = pixWatched->width() + 8;
+ if ( *col == header()->mapToIndex( *col ) )
+ moveToCol = 0;
+ break;
+ }
+ case KPaintInfo::COL_STATUS:
+ {
+ show = &mPaintInfo.showStatus;
+ col = &mPaintInfo.statusCol;
+ width = pixNew->width() + 8;
+ if ( *col == header()->mapToIndex( *col ) )
+ moveToCol = 0;
+ break;
+ }
+ case KPaintInfo::COL_SIGNED:
+ {
+ show = &mPaintInfo.showSigned;
+ col = &mPaintInfo.signedCol;
+ width = pixFullySigned->width() + 8;
+ if ( *col == header()->mapToIndex( *col ) )
+ moveToCol = 0;
+ break;
+ }
+ case KPaintInfo::COL_CRYPTO:
+ {
+ show = &mPaintInfo.showCrypto;
+ col = &mPaintInfo.cryptoCol;
+ width = pixFullyEncrypted->width() + 8;
+ if ( *col == header()->mapToIndex( *col ) )
+ moveToCol = 0;
+ break;
+ }
+ case KPaintInfo::COL_RECEIVER:
+ {
+ show = &mPaintInfo.showReceiver;
+ col = &mPaintInfo.receiverCol;
+ width = 170;
+ break;
+ }
+ case KPaintInfo::COL_SCORE: ; // only used by KNode
+ // don't use default, so that the compiler tells us you forgot to code here for a new column
+ }
+
+ assert(show);
+
+ if (mode == -1)
+ *show = !*show;
+ else
+ *show = mode;
+
+ mPopup->setItemChecked(id, *show);
+
+ if (*show) {
+ header()->setResizeEnabled(true, *col);
+ setColumnWidth(*col, width);
+ if ( moveToCol >= 0 )
+ header()->moveSection( *col, moveToCol );
+ }
+ else {
+ header()->setResizeEnabled(false, *col);
+ header()->setStretchEnabled(false, *col);
+ hideColumn(*col);
+ }
+
+ // if we change the visibility of the receiver column,
+ // the sender column has to show either the sender or the receiver
+ if ( static_cast<KPaintInfo::ColumnIds>(id) == KPaintInfo::COL_RECEIVER ) {
+ QString colText = i18n( "Sender" );
+ if ( mFolder && (mFolder->whoField().lower() == "to") && !mPaintInfo.showReceiver)
+ colText = i18n( "Receiver" );
+ setColumnText( mPaintInfo.senderCol, colText );
+ }
+
+ if (mode == -1)
+ writeConfig();
+}
+
+//-----------------------------------------------------------------------------
+// Support for backing pixmap
+void KMHeaders::paintEmptyArea( QPainter * p, const QRect & rect )
+{
+ if (mPaintInfo.pixmapOn)
+ p->drawTiledPixmap( rect.left(), rect.top(), rect.width(), rect.height(),
+ mPaintInfo.pixmap,
+ rect.left() + contentsX(),
+ rect.top() + contentsY() );
+ else
+ p->fillRect( rect, colorGroup().base() );
+}
+
+bool KMHeaders::event(QEvent *e)
+{
+ bool result = KListView::event(e);
+ if (e->type() == QEvent::ApplicationPaletteChange)
+ {
+ readColorConfig();
+ }
+ return result;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMHeaders::readColorConfig (void)
+{
+ KConfig* config = KMKernel::config();
+ // Custom/System colors
+ KConfigGroupSaver saver(config, "Reader");
+ QColor c1=QColor(kapp->palette().active().text());
+ QColor c2=QColor("red");
+ QColor c3=QColor("blue");
+ QColor c4=QColor(kapp->palette().active().base());
+ QColor c5=QColor(0,0x7F,0);
+ QColor c6=QColor(0,0x98,0);
+ QColor c7=KGlobalSettings::alternateBackgroundColor();
+
+ if (!config->readBoolEntry("defaultColors",true)) {
+ mPaintInfo.colFore = config->readColorEntry("ForegroundColor",&c1);
+ mPaintInfo.colBack = config->readColorEntry("BackgroundColor",&c4);
+ QPalette newPal = kapp->palette();
+ newPal.setColor( QColorGroup::Base, mPaintInfo.colBack );
+ newPal.setColor( QColorGroup::Text, mPaintInfo.colFore );
+ setPalette( newPal );
+ mPaintInfo.colNew = config->readColorEntry("NewMessage",&c2);
+ mPaintInfo.colUnread = config->readColorEntry("UnreadMessage",&c3);
+ mPaintInfo.colFlag = config->readColorEntry("FlagMessage",&c5);
+ mPaintInfo.colTodo = config->readColorEntry("TodoMessage",&c6);
+ c7 = config->readColorEntry("AltBackgroundColor",&c7);
+ }
+ else {
+ mPaintInfo.colFore = c1;
+ mPaintInfo.colBack = c4;
+ QPalette newPal = kapp->palette();
+ newPal.setColor( QColorGroup::Base, c4 );
+ newPal.setColor( QColorGroup::Text, c1 );
+ setPalette( newPal );
+ mPaintInfo.colNew = c2;
+ mPaintInfo.colUnread = c3;
+ mPaintInfo.colFlag = c5;
+ mPaintInfo.colTodo = c6;
+ }
+ setAlternateBackground(c7);
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::readConfig (void)
+{
+ KConfig* config = KMKernel::config();
+
+ // Backing pixmap support
+ { // area for config group "Pixmaps"
+ KConfigGroupSaver saver(config, "Pixmaps");
+ QString pixmapFile = config->readEntry("Headers");
+ mPaintInfo.pixmapOn = false;
+ if (!pixmapFile.isEmpty()) {
+ mPaintInfo.pixmapOn = true;
+ mPaintInfo.pixmap = QPixmap( pixmapFile );
+ }
+ }
+
+ { // area for config group "General"
+ KConfigGroupSaver saver(config, "General");
+ bool show = config->readBoolEntry("showMessageSize");
+ slotToggleColumn(KPaintInfo::COL_SIZE, show);
+
+ show = config->readBoolEntry("showAttachmentColumn");
+ slotToggleColumn(KPaintInfo::COL_ATTACHMENT, show);
+
+ show = config->readBoolEntry("showImportantColumn");
+ slotToggleColumn(KPaintInfo::COL_IMPORTANT, show);
+
+ show = config->readBoolEntry("showTodoColumn");
+ slotToggleColumn(KPaintInfo::COL_TODO, show);
+
+ show = config->readBoolEntry("showSpamHamColumn");
+ slotToggleColumn(KPaintInfo::COL_SPAM_HAM, show);
+
+ show = config->readBoolEntry("showWatchedIgnoredColumn");
+ slotToggleColumn(KPaintInfo::COL_WATCHED_IGNORED, show);
+
+ show = config->readBoolEntry("showStatusColumn");
+ slotToggleColumn(KPaintInfo::COL_STATUS, show);
+
+ show = config->readBoolEntry("showSignedColumn");
+ slotToggleColumn(KPaintInfo::COL_SIGNED, show);
+
+ show = config->readBoolEntry("showCryptoColumn");
+ slotToggleColumn(KPaintInfo::COL_CRYPTO, show);
+
+ show = config->readBoolEntry("showReceiverColumn");
+ slotToggleColumn(KPaintInfo::COL_RECEIVER, show);
+
+ mPaintInfo.showCryptoIcons = config->readBoolEntry( "showCryptoIcons", false );
+ mPaintInfo.showAttachmentIcon = config->readBoolEntry( "showAttachmentIcon", true );
+
+ KMime::DateFormatter::FormatType t =
+ (KMime::DateFormatter::FormatType) config->readNumEntry("dateFormat", KMime::DateFormatter::Fancy ) ;
+ mDate.setCustomFormat( config->readEntry("customDateFormat") );
+ mDate.setFormat( t );
+ }
+
+ readColorConfig();
+
+ // Custom/System fonts
+ { // area for config group "General"
+ KConfigGroupSaver saver(config, "Fonts");
+ if (!(config->readBoolEntry("defaultFonts",true)))
+ {
+ QFont listFont( KGlobalSettings::generalFont() );
+ listFont = config->readFontEntry( "list-font", &listFont );
+ setFont( listFont );
+ mNewFont = config->readFontEntry( "list-new-font", &listFont );
+ mUnreadFont = config->readFontEntry( "list-unread-font", &listFont );
+ mImportantFont = config->readFontEntry( "list-important-font", &listFont );
+ mTodoFont = config->readFontEntry( "list-todo-font", &listFont );
+ mDateFont = KGlobalSettings::fixedFont();
+ mDateFont = config->readFontEntry( "list-date-font", &mDateFont );
+ } else {
+ mNewFont= mUnreadFont = mImportantFont = mDateFont = mTodoFont =
+ KGlobalSettings::generalFont();
+ setFont( mDateFont );
+ }
+ }
+
+ // Behavior
+ {
+ KConfigGroupSaver saver(config, "Geometry");
+ mReaderWindowActive = config->readEntry( "readerWindowMode", "below" ) != "hide";
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMHeaders::reset()
+{
+ int top = topItemIndex();
+ int id = currentItemIndex();
+ noRepaint = true;
+ clear();
+ QString colText = i18n( "Sender" );
+ if ( mFolder && (mFolder->whoField().lower() == "to") && !mPaintInfo.showReceiver)
+ colText = i18n( "Receiver" );
+ setColumnText( mPaintInfo.senderCol, colText );
+ noRepaint = false;
+ mItems.resize(0);
+ updateMessageList();
+ setCurrentMsg(id);
+ setTopItemByIndex(top);
+ ensureCurrentItemVisible();
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::refreshNestedState(void)
+{
+ bool oldState = isThreaded();
+ NestingPolicy oldNestPolicy = nestingPolicy;
+ KConfig* config = KMKernel::config();
+ KConfigGroupSaver saver(config, "Geometry");
+ mNested = config->readBoolEntry( "nestedMessages", false );
+
+ nestingPolicy = (NestingPolicy)config->readNumEntry( "nestingPolicy", OpenUnread );
+ if ((nestingPolicy != oldNestPolicy) ||
+ (oldState != isThreaded()))
+ {
+ setRootIsDecorated( nestingPolicy != AlwaysOpen && isThreaded() );
+ reset();
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::readFolderConfig (void)
+{
+ if (!mFolder) return;
+ KConfig* config = KMKernel::config();
+
+ KConfigGroupSaver saver(config, "Folder-" + mFolder->idString());
+ mNestedOverride = config->readBoolEntry( "threadMessagesOverride", false );
+ mSortCol = config->readNumEntry("SortColumn", mSortCol+1 /* inited to date column */);
+ mSortDescending = (mSortCol < 0);
+ mSortCol = abs(mSortCol) - 1;
+
+ mTopItem = config->readNumEntry("Top", 0);
+ mCurrentItem = config->readNumEntry("Current", 0);
+ mCurrentItemSerNum = config->readNumEntry("CurrentSerialNum", 0);
+
+ mPaintInfo.orderOfArrival = config->readBoolEntry( "OrderOfArrival", true );
+ mPaintInfo.status = config->readBoolEntry( "Status", false );
+
+ { //area for config group "Geometry"
+ KConfigGroupSaver saver(config, "Geometry");
+ mNested = config->readBoolEntry( "nestedMessages", false );
+ nestingPolicy = (NestingPolicy)config->readNumEntry( "nestingPolicy", OpenUnread );
+ }
+
+ setRootIsDecorated( nestingPolicy != AlwaysOpen && isThreaded() );
+ mSubjThreading = config->readBoolEntry( "threadMessagesBySubject", true );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMHeaders::writeFolderConfig (void)
+{
+ if (!mFolder) return;
+ KConfig* config = KMKernel::config();
+ int mSortColAdj = mSortCol + 1;
+
+ KConfigGroupSaver saver(config, "Folder-" + mFolder->idString());
+ config->writeEntry("SortColumn", (mSortDescending ? -mSortColAdj : mSortColAdj));
+ config->writeEntry("Top", topItemIndex());
+ config->writeEntry("Current", currentItemIndex());
+ HeaderItem* current = currentHeaderItem();
+ ulong sernum = 0;
+ if ( current && mFolder->getMsgBase( current->msgId() ) )
+ sernum = mFolder->getMsgBase( current->msgId() )->getMsgSerNum();
+ config->writeEntry("CurrentSerialNum", sernum);
+
+ config->writeEntry("OrderOfArrival", mPaintInfo.orderOfArrival);
+ config->writeEntry("Status", mPaintInfo.status);
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::writeConfig (void)
+{
+ KConfig* config = KMKernel::config();
+ saveLayout(config, "Header-Geometry");
+ KConfigGroupSaver saver(config, "General");
+ config->writeEntry("showMessageSize" , mPaintInfo.showSize);
+ config->writeEntry("showAttachmentColumn" , mPaintInfo.showAttachment);
+ config->writeEntry("showImportantColumn" , mPaintInfo.showImportant);
+ config->writeEntry("showTodoColumn" , mPaintInfo.showTodo);
+ config->writeEntry("showSpamHamColumn" , mPaintInfo.showSpamHam);
+ config->writeEntry("showWatchedIgnoredColumn", mPaintInfo.showWatchedIgnored);
+ config->writeEntry("showStatusColumn" , mPaintInfo.showStatus);
+ config->writeEntry("showSignedColumn" , mPaintInfo.showSigned);
+ config->writeEntry("showCryptoColumn" , mPaintInfo.showCrypto);
+ config->writeEntry("showReceiverColumn" , mPaintInfo.showReceiver);
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::setFolder( KMFolder *aFolder, bool forceJumpToUnread )
+{
+ CREATE_TIMER(set_folder);
+ START_TIMER(set_folder);
+
+ int id;
+ QString str;
+
+ mSortInfo.fakeSort = 0;
+ if ( mFolder && static_cast<KMFolder*>(mFolder) == aFolder ) {
+ int top = topItemIndex();
+ id = currentItemIndex();
+ writeFolderConfig();
+ readFolderConfig();
+ updateMessageList(); // do not change the selection
+ setCurrentMsg(id);
+ setTopItemByIndex(top);
+ } else {
+ if (mFolder) {
+ // WABA: Make sure that no KMReaderWin is still using a msg
+ // from this folder, since it's msg's are about to be deleted.
+ highlightMessage(0, false);
+
+ disconnect(mFolder, SIGNAL(numUnreadMsgsChanged(KMFolder*)),
+ this, SLOT(setFolderInfoStatus()));
+
+ mFolder->markNewAsUnread();
+ writeFolderConfig();
+ disconnect(mFolder, SIGNAL(msgHeaderChanged(KMFolder*,int)),
+ this, SLOT(msgHeaderChanged(KMFolder*,int)));
+ disconnect(mFolder, SIGNAL(msgAdded(int)),
+ this, SLOT(msgAdded(int)));
+ disconnect(mFolder, SIGNAL( msgRemoved( int, QString ) ),
+ this, SLOT( msgRemoved( int, QString ) ) );
+ disconnect(mFolder, SIGNAL(changed()),
+ this, SLOT(msgChanged()));
+ disconnect(mFolder, SIGNAL(cleared()),
+ this, SLOT(folderCleared()));
+ disconnect(mFolder, SIGNAL(expunged( KMFolder* )),
+ this, SLOT(folderCleared()));
+ disconnect(mFolder, SIGNAL(closed()),
+ this, SLOT(folderClosed()));
+ disconnect( mFolder, SIGNAL( statusMsg( const QString& ) ),
+ BroadcastStatus::instance(), SLOT( setStatusMsg( const QString& ) ) );
+ disconnect(mFolder, SIGNAL(viewConfigChanged()), this, SLOT(reset()));
+ writeSortOrder();
+ mFolder->close("kmheaders");
+ // System folders remain open but we also should write the index from
+ // time to time
+ if (mFolder->dirty()) mFolder->writeIndex();
+ }
+
+ mSortInfo.removed = 0;
+ mFolder = aFolder;
+ mSortInfo.dirty = true;
+
+ mOwner->useAction()->setEnabled( mFolder ?
+ ( kmkernel->folderIsTemplates( mFolder ) ) : false );
+ mOwner->messageActions()->replyListAction()->setEnabled( mFolder ?
+ mFolder->isMailingListEnabled() : false );
+ if ( mFolder ) {
+ connect(mFolder, SIGNAL(msgHeaderChanged(KMFolder*,int)),
+ this, SLOT(msgHeaderChanged(KMFolder*,int)));
+ connect(mFolder, SIGNAL(msgAdded(int)),
+ this, SLOT(msgAdded(int)));
+ connect(mFolder, SIGNAL(msgRemoved(int,QString)),
+ this, SLOT(msgRemoved(int,QString)));
+ connect(mFolder, SIGNAL(changed()),
+ this, SLOT(msgChanged()));
+ connect(mFolder, SIGNAL(cleared()),
+ this, SLOT(folderCleared()));
+ connect(mFolder, SIGNAL(expunged( KMFolder* )),
+ this, SLOT(folderCleared()));
+ connect(mFolder, SIGNAL(closed()),
+ this, SLOT(folderClosed()));
+ connect(mFolder, SIGNAL(statusMsg(const QString&)),
+ BroadcastStatus::instance(), SLOT( setStatusMsg( const QString& ) ) );
+ connect(mFolder, SIGNAL(numUnreadMsgsChanged(KMFolder*)),
+ this, SLOT(setFolderInfoStatus()));
+ connect(mFolder, SIGNAL(viewConfigChanged()), this, SLOT(reset()));
+
+ // Not very nice, but if we go from nested to non-nested
+ // in the folderConfig below then we need to do this otherwise
+ // updateMessageList would do something unspeakable
+ if (isThreaded()) {
+ noRepaint = true;
+ clear();
+ noRepaint = false;
+ mItems.resize( 0 );
+ }
+
+ readFolderConfig();
+
+ CREATE_TIMER(kmfolder_open);
+ START_TIMER(kmfolder_open);
+ mFolder->open("kmheaders");
+ END_TIMER(kmfolder_open);
+ SHOW_TIMER(kmfolder_open);
+
+ if (isThreaded()) {
+ noRepaint = true;
+ clear();
+ noRepaint = false;
+ mItems.resize( 0 );
+ }
+ }
+
+ CREATE_TIMER(updateMsg);
+ START_TIMER(updateMsg);
+ updateMessageList(true, forceJumpToUnread);
+ END_TIMER(updateMsg);
+ SHOW_TIMER(updateMsg);
+ makeHeaderVisible();
+ setFolderInfoStatus();
+
+ QString colText = i18n( "Sender" );
+ if (mFolder && (mFolder->whoField().lower() == "to") && !mPaintInfo.showReceiver)
+ colText = i18n("Receiver");
+ setColumnText( mPaintInfo.senderCol, colText);
+
+ colText = i18n( "Date" );
+ if (mPaintInfo.orderOfArrival)
+ colText = i18n( "Order of Arrival" );
+ setColumnText( mPaintInfo.dateCol, colText);
+
+ colText = i18n( "Subject" );
+ if (mPaintInfo.status)
+ colText = colText + i18n( " (Status)" );
+ setColumnText( mPaintInfo.subCol, colText);
+ }
+
+ updateActions();
+
+ END_TIMER(set_folder);
+ SHOW_TIMER(set_folder);
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::msgChanged()
+{
+ if (mFolder->count() == 0) { // Folder cleared
+ mItems.resize(0);
+ clear();
+ return;
+ }
+ int i = topItemIndex();
+ int cur = currentItemIndex();
+ if (!isUpdatesEnabled()) return;
+ QString msgIdMD5;
+ QListViewItem *item = currentItem();
+ HeaderItem *hi = dynamic_cast<HeaderItem*>(item);
+ if (item && hi) {
+ // get the msgIdMD5 to compare it later
+ KMMsgBase *mb = mFolder->getMsgBase(hi->msgId());
+ if (mb)
+ msgIdMD5 = mb->msgIdMD5();
+ }
+// if (!isUpdatesEnabled()) return;
+ // prevent IMAP messages from scrolling to top
+ disconnect(this,SIGNAL(currentChanged(QListViewItem*)),
+ this,SLOT(highlightMessage(QListViewItem*)));
+ // remember all selected messages
+ QValueList<int> 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();
+ QListViewItem *unreadItem = 0;
+ while(item && item != topOfList) {
+ KMMsgBase *msg = mFolder->getMsgBase( static_cast<HeaderItem*>(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 );
+ connect(this,SIGNAL(currentChanged(QListViewItem*)),
+ this,SLOT(highlightMessage(QListViewItem*)));
+
+ // if the current message has changed then emit
+ // the selected signal to force an update
+
+ // Normally the serial number of the message would be
+ // used to do this, but because we don't yet have
+ // guaranteed serial numbers for IMAP messages fall back
+ // to using the MD5 checksum of the msgId.
+ item = currentItem();
+ hi = dynamic_cast<HeaderItem*>(item);
+ if (item && hi) {
+ KMMsgBase *mb = mFolder->getMsgBase(hi->msgId());
+ if (mb) {
+ if (msgIdMD5.isEmpty() || (msgIdMD5 != mb->msgIdMD5()))
+ emit selected(mFolder->getMsg(hi->msgId()));
+ } else {
+ emit selected(0);
+ }
+ } else
+ emit selected(0);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMHeaders::msgAdded(int id)
+{
+ HeaderItem* hi = 0;
+ if (!isUpdatesEnabled()) return;
+
+ CREATE_TIMER(msgAdded);
+ START_TIMER(msgAdded);
+
+ assert( mFolder->getMsgBase( id ) ); // otherwise using count() is wrong
+
+ /* Create a new SortCacheItem to be used for threading. */
+ SortCacheItem *sci = new SortCacheItem;
+ sci->setId(id);
+ if (isThreaded()) {
+ // make sure the id and subject dicts grow, if necessary
+ if (mSortCacheItems.count() == (uint)mFolder->count()
+ || mSortCacheItems.count() == 0) {
+ kdDebug (5006) << "KMHeaders::msgAdded - Resizing id and subject trees of " << mFolder->label()
+ << ": before=" << mSortCacheItems.count() << " ,after=" << (mFolder->count()*2) << endl;
+ mSortCacheItems.resize(mFolder->count()*2);
+ mSubjectLists.resize(mFolder->count()*2);
+ }
+ QString msgId = mFolder->getMsgBase(id)->msgIdMD5();
+ if (msgId.isNull())
+ msgId = "";
+ QString replyToId = mFolder->getMsgBase(id)->replyToIdMD5();
+
+ SortCacheItem *parent = findParent( sci );
+ if (!parent && mSubjThreading) {
+ parent = findParentBySubject( sci );
+ if (parent && sci->isImperfectlyThreaded()) {
+ // The parent we found could be by subject, in which case it is
+ // possible, that it would be preferrable to thread it below us,
+ // not the other way around. Check that. This is not only
+ // cosmetic, as getting this wrong leads to circular threading.
+ if (msgId == mFolder->getMsgBase(parent->item()->msgId())->replyToIdMD5()
+ || msgId == mFolder->getMsgBase(parent->item()->msgId())->replyToAuxIdMD5())
+ parent = NULL;
+ }
+ }
+
+ if (parent && mFolder->getMsgBase(parent->id())->isWatched())
+ mFolder->getMsgBase(id)->setStatus( KMMsgStatusWatched );
+ else if (parent && mFolder->getMsgBase(parent->id())->isIgnored())
+ mFolder->getMsgBase(id)->setStatus( KMMsgStatusIgnored );
+ if (parent)
+ hi = new HeaderItem( parent->item(), id );
+ else
+ hi = new HeaderItem( this, id );
+
+ // o/` ... my buddy and me .. o/`
+ hi->setSortCacheItem(sci);
+ sci->setItem(hi);
+
+ // Update and resize the id trees.
+ mItems.resize( mFolder->count() );
+ mItems[id] = hi;
+
+ if ( !msgId.isEmpty() )
+ mSortCacheItems.replace(msgId, sci);
+ /* Add to the list of potential parents for subject threading. But only if
+ * we are top level. */
+ if (mSubjThreading && parent) {
+ QString subjMD5 = mFolder->getMsgBase(id)->strippedSubjectMD5();
+ if (subjMD5.isEmpty()) {
+ mFolder->getMsgBase(id)->initStrippedSubjectMD5();
+ subjMD5 = mFolder->getMsgBase(id)->strippedSubjectMD5();
+ }
+ if( !subjMD5.isEmpty()) {
+ if ( !mSubjectLists.find(subjMD5) )
+ mSubjectLists.insert(subjMD5, new QPtrList<SortCacheItem>());
+ // insertion sort by date. See buildThreadTrees for details.
+ int p=0;
+ for (QPtrListIterator<SortCacheItem> it(*mSubjectLists[subjMD5]);
+ it.current(); ++it) {
+ KMMsgBase *mb = mFolder->getMsgBase((*it)->id());
+ if ( mb->date() < mFolder->getMsgBase(id)->date())
+ break;
+ p++;
+ }
+ mSubjectLists[subjMD5]->insert( p, sci);
+ sci->setSubjectThreadingList( mSubjectLists[subjMD5] );
+ }
+ }
+ // The message we just added might be a better parent for one of the as of
+ // yet imperfectly threaded messages. Let's find out.
+
+ /* In case the current item is taken during reparenting, prevent qlistview
+ * from selecting some unrelated item as a result of take() emitting
+ * currentChanged. */
+ disconnect( this, SIGNAL(currentChanged(QListViewItem*)),
+ this, SLOT(highlightMessage(QListViewItem*)));
+
+ if ( !msgId.isEmpty() ) {
+ QPtrListIterator<HeaderItem> it(mImperfectlyThreadedList);
+ HeaderItem *cur;
+ while ( (cur = it.current()) ) {
+ ++it;
+ int tryMe = cur->msgId();
+ // Check, whether our message is the replyToId or replyToAuxId of
+ // this one. If so, thread it below our message, unless it is already
+ // correctly threaded by replyToId.
+ bool perfectParent = true;
+ KMMsgBase *otherMsg = mFolder->getMsgBase(tryMe);
+ if ( !otherMsg ) {
+ kdDebug(5006) << "otherMsg is NULL !!! tryMe: " << tryMe << endl;
+ continue;
+ }
+ QString otherId = otherMsg->replyToIdMD5();
+ if (msgId != otherId) {
+ if (msgId != otherMsg->replyToAuxIdMD5())
+ continue;
+ else {
+ if (!otherId.isEmpty() && mSortCacheItems.find(otherId))
+ continue;
+ else
+ // Thread below us by aux id, but keep on the list of
+ // imperfectly threaded messages.
+ perfectParent = false;
+ }
+ }
+ QListViewItem *newParent = mItems[id];
+ QListViewItem *msg = mItems[tryMe];
+
+ if (msg->parent())
+ msg->parent()->takeItem(msg);
+ else
+ takeItem(msg);
+ newParent->insertItem(msg);
+ HeaderItem *hi = static_cast<HeaderItem*>( newParent );
+ hi->sortCacheItem()->addSortedChild( cur->sortCacheItem() );
+
+ makeHeaderVisible();
+
+ if (perfectParent) {
+ mImperfectlyThreadedList.removeRef (mItems[tryMe]);
+ // The item was imperfectly thread before, now it's parent
+ // is there. Update the .sorted file accordingly.
+ QString sortFile = KMAIL_SORT_FILE(mFolder);
+ FILE *sortStream = fopen(QFile::encodeName(sortFile), "r+");
+ if (sortStream) {
+ mItems[tryMe]->sortCacheItem()->updateSortFile( sortStream, mFolder );
+ fclose (sortStream);
+ }
+ }
+ }
+ }
+ // Add ourselves only now, to avoid circularity above.
+ if (hi && hi->sortCacheItem()->isImperfectlyThreaded())
+ mImperfectlyThreadedList.append(hi);
+ } else {
+ // non-threaded case
+ hi = new HeaderItem( this, id );
+ mItems.resize( mFolder->count() );
+ mItems[id] = hi;
+ // o/` ... my buddy and me .. o/`
+ hi->setSortCacheItem(sci);
+ sci->setItem(hi);
+ }
+ if (mSortInfo.fakeSort) {
+ QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int)));
+ KListView::setSorting(mSortCol, !mSortDescending );
+ mSortInfo.fakeSort = 0;
+ }
+ appendItemToSortFile(hi); //inserted into sorted list
+
+ msgHeaderChanged(mFolder,id);
+
+ if ((childCount() == 1) && hi) {
+ setSelected( hi, true );
+ setCurrentItem( firstChild() );
+ setSelectionAnchor( currentItem() );
+ highlightMessage( currentItem() );
+ }
+
+ /* restore signal */
+ connect( this, SIGNAL(currentChanged(QListViewItem*)),
+ this, SLOT(highlightMessage(QListViewItem*)));
+
+ emit msgAddedToListView( hi );
+ END_TIMER(msgAdded);
+ SHOW_TIMER(msgAdded);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMHeaders::msgRemoved(int id, QString msgId )
+{
+ if (!isUpdatesEnabled()) return;
+
+ if ((id < 0) || (id >= (int)mItems.size()))
+ return;
+ /*
+ * qlistview has its own ideas about what to select as the next
+ * item once this one is removed. Sine we have already selected
+ * something in prepare/finalizeMove that's counter productive
+ */
+ disconnect( this, SIGNAL(currentChanged(QListViewItem*)),
+ this, SLOT(highlightMessage(QListViewItem*)));
+
+ HeaderItem *removedItem = mItems[id];
+ if (!removedItem) return;
+ HeaderItem *curItem = currentHeaderItem();
+
+ for (int i = id; i < (int)mItems.size() - 1; ++i) {
+ mItems[i] = mItems[i+1];
+ mItems[i]->setMsgId( i );
+ mItems[i]->sortCacheItem()->setId( i );
+ }
+
+ mItems.resize( mItems.size() - 1 );
+
+ if (isThreaded() && mFolder->count()) {
+ if ( !msgId.isEmpty() && mSortCacheItems[msgId] ) {
+ if (mSortCacheItems[msgId] == removedItem->sortCacheItem())
+ mSortCacheItems.remove(msgId);
+ }
+ // Remove the message from the list of potential parents for threading by
+ // subject.
+ if ( mSubjThreading && removedItem->sortCacheItem()->subjectThreadingList() )
+ removedItem->sortCacheItem()->subjectThreadingList()->removeRef( removedItem->sortCacheItem() );
+
+ // Reparent children of item.
+ QListViewItem *myParent = removedItem;
+ QListViewItem *myChild = myParent->firstChild();
+ QListViewItem *threadRoot = myParent;
+ while (threadRoot->parent())
+ threadRoot = threadRoot->parent();
+ QString key = static_cast<HeaderItem*>(threadRoot)->key(mSortCol, !mSortDescending);
+
+ QPtrList<QListViewItem> childList;
+ while (myChild) {
+ HeaderItem *item = static_cast<HeaderItem*>(myChild);
+ // Just keep the item at top level, if it will be deleted anyhow
+ if ( !item->aboutToBeDeleted() ) {
+ childList.append(myChild);
+ }
+ myChild = myChild->nextSibling();
+ if ( item->aboutToBeDeleted() ) {
+ myParent->takeItem( item );
+ insertItem( item );
+ mRoot->addSortedChild( item->sortCacheItem() );
+ }
+ item->setTempKey( key + item->key( mSortCol, !mSortDescending ));
+ if (mSortInfo.fakeSort) {
+ QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int)));
+ KListView::setSorting(mSortCol, !mSortDescending );
+ mSortInfo.fakeSort = 0;
+ }
+ }
+
+ for (QPtrListIterator<QListViewItem> it(childList); it.current() ; ++it ) {
+ QListViewItem *lvi = *it;
+ HeaderItem *item = static_cast<HeaderItem*>(lvi);
+ SortCacheItem *sci = item->sortCacheItem();
+ SortCacheItem *parent = findParent( sci );
+ if ( !parent && mSubjThreading )
+ parent = findParentBySubject( sci );
+
+ Q_ASSERT( !parent || parent->item() != removedItem );
+ myParent->takeItem(lvi);
+ if ( parent && parent->item() != item && parent->item() != removedItem ) {
+ parent->item()->insertItem(lvi);
+ parent->addSortedChild( sci );
+ } else {
+ insertItem(lvi);
+ mRoot->addSortedChild( sci );
+ }
+
+ if ((!parent || sci->isImperfectlyThreaded())
+ && !mImperfectlyThreadedList.containsRef(item))
+ mImperfectlyThreadedList.append(item);
+
+ if (parent && !sci->isImperfectlyThreaded()
+ && mImperfectlyThreadedList.containsRef(item))
+ mImperfectlyThreadedList.removeRef(item);
+ }
+ }
+ // Make sure our data structures are cleared.
+ if (!mFolder->count())
+ folderCleared();
+
+ mImperfectlyThreadedList.removeRef( removedItem );
+#ifdef DEBUG
+ // This should never happen, in this case the folders are inconsistent.
+ while ( mImperfectlyThreadedList.findRef( removedItem ) != -1 ) {
+ mImperfectlyThreadedList.remove();
+ kdDebug(5006) << "Remove doubled item from mImperfectlyThreadedList: " << removedItem << endl;
+ }
+#endif
+ delete removedItem;
+ // we might have rethreaded it, in which case its current state will be lost
+ if ( curItem ) {
+ if ( curItem != removedItem ) {
+ setCurrentItem( curItem );
+ setSelectionAnchor( currentItem() );
+ } else {
+ // We've removed the current item, which means it was removed from
+ // something other than a user move or copy, which would have selected
+ // the next logical mail. This can happen when the mail is deleted by
+ // a filter, or some other behind the scenes action. Select something
+ // sensible, then, and make sure the reader window is cleared.
+ emit maybeDeleting();
+ int contentX, contentY;
+ HeaderItem *nextItem = prepareMove( &contentX, &contentY );
+ finalizeMove( nextItem, contentX, contentY );
+ }
+ }
+ /* restore signal */
+ connect( this, SIGNAL(currentChanged(QListViewItem*)),
+ this, SLOT(highlightMessage(QListViewItem*)));
+}
+
+
+//-----------------------------------------------------------------------------
+void KMHeaders::msgHeaderChanged(KMFolder*, int msgId)
+{
+ if (msgId<0 || msgId >= (int)mItems.size() || !isUpdatesEnabled()) return;
+ HeaderItem *item = mItems[msgId];
+ if (item) {
+ item->irefresh();
+ item->repaint();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMHeaders::setMsgStatus (KMMsgStatus status, bool toggle)
+{
+ // kdDebug() << k_funcinfo << endl;
+ SerNumList serNums = selectedVisibleSernums();
+ if (serNums.empty())
+ return;
+
+ KMCommand *command = new KMSetStatusCommand( status, serNums, toggle );
+ command->start();
+}
+
+
+QPtrList<QListViewItem> KMHeaders::currentThread() const
+{
+ if (!mFolder) return QPtrList<QListViewItem>();
+
+ // starting with the current item...
+ QListViewItem *curItem = currentItem();
+ if (!curItem) return QPtrList<QListViewItem>();
+
+ // ...find the top-level item:
+ QListViewItem *topOfThread = curItem;
+ while ( topOfThread->parent() )
+ topOfThread = topOfThread->parent();
+
+ // collect the items in this thread:
+ QPtrList<QListViewItem> list;
+ QListViewItem *topOfNextThread = topOfThread->nextSibling();
+ for ( QListViewItemIterator it( topOfThread ) ;
+ it.current() && it.current() != topOfNextThread ; ++it )
+ list.append( it.current() );
+ return list;
+}
+
+void KMHeaders::setThreadStatus(KMMsgStatus status, bool toggle)
+{
+ QPtrList<QListViewItem> curThread;
+
+ if (mFolder) {
+ QPtrList<QListViewItem> topOfThreads;
+
+ // for each selected item...
+ for (QListViewItem *item = firstChild(); item; item = item->itemBelow())
+ if (item->isSelected() ) {
+ // ...find the top-level item:
+ QListViewItem *top = item;
+ while ( top->parent() )
+ top = top->parent();
+ if (!topOfThreads.contains(top)) {
+ topOfThreads.append(top);
+ }
+ }
+
+ // for each thread found...
+ for ( QPtrListIterator<QListViewItem> it( topOfThreads ) ;
+ it.current() ; ++ it ) {
+ QListViewItem *top = *it;
+
+ // collect the items in this thread:
+ QListViewItem *topOfNextThread = top->nextSibling();
+ for ( QListViewItemIterator it( top ) ;
+ it.current() && it.current() != topOfNextThread ; ++it )
+ curThread.append( it.current() );
+ }
+ }
+
+ QPtrListIterator<QListViewItem> it( curThread );
+ SerNumList serNums;
+
+ for ( it.toFirst() ; it.current() ; ++it ) {
+ int id = static_cast<HeaderItem*>(*it)->msgId();
+ KMMsgBase *msgBase = mFolder->getMsgBase( id );
+ serNums.append( msgBase->getMsgSerNum() );
+ }
+
+ if (serNums.empty())
+ return;
+
+ KMCommand *command = new KMSetStatusCommand( status, serNums, toggle );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+int KMHeaders::slotFilterMsg(KMMessage *msg)
+{
+ if ( !msg ) return 2; // messageRetrieve(0) is always possible
+ msg->setTransferInProgress(false);
+ int filterResult = kmkernel->filterMgr()->process(msg,KMFilterMgr::Explicit);
+ if (filterResult == 2) {
+ // something went horribly wrong (out of space?)
+ kmkernel->emergencyExit( i18n("Unable to process messages: " ) + QString::fromLocal8Bit(strerror(errno)));
+ return 2;
+ }
+ if (msg->parent()) { // unGet this msg
+ int idx = -1;
+ KMFolder * p = 0;
+ KMMsgDict::instance()->getLocation( msg, &p, &idx );
+ assert( p == msg->parent() ); assert( idx >= 0 );
+ p->unGetMsg( idx );
+ }
+
+ return filterResult;
+}
+
+
+void KMHeaders::slotExpandOrCollapseThread( bool expand )
+{
+ if ( !isThreaded() ) return;
+ // find top-level parent of currentItem().
+ QListViewItem *item = currentItem();
+ if ( !item ) return;
+ clearSelection();
+ item->setSelected( true );
+ while ( item->parent() )
+ item = item->parent();
+ HeaderItem * hdrItem = static_cast<HeaderItem*>(item);
+ hdrItem->setOpenRecursive( expand );
+ if ( !expand ) // collapse can hide the current item:
+ setCurrentMsg( hdrItem->msgId() );
+ ensureItemVisible( currentItem() );
+}
+
+void KMHeaders::slotExpandOrCollapseAllThreads( bool expand )
+{
+ if ( !isThreaded() ) return;
+
+ QListViewItem * item = currentItem();
+ if( item ) {
+ clearSelection();
+ item->setSelected( true );
+ }
+
+ for ( QListViewItem *item = firstChild() ;
+ item ; item = item->nextSibling() )
+ static_cast<HeaderItem*>(item)->setOpenRecursive( expand );
+ if ( !expand ) { // collapse can hide the current item:
+ QListViewItem * item = currentItem();
+ if( item ) {
+ while ( item->parent() )
+ item = item->parent();
+ setCurrentMsg( static_cast<HeaderItem*>(item)->msgId() );
+ }
+ }
+ ensureItemVisible( currentItem() );
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::setStyleDependantFrameWidth()
+{
+ // set the width of the frame to a reasonable value for the current GUI style
+ int frameWidth;
+ if( style().isA("KeramikStyle") )
+ frameWidth = style().pixelMetric( QStyle::PM_DefaultFrameWidth ) - 1;
+ else
+ frameWidth = style().pixelMetric( QStyle::PM_DefaultFrameWidth );
+ if ( frameWidth < 0 )
+ frameWidth = 0;
+ if ( frameWidth != lineWidth() )
+ setLineWidth( frameWidth );
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::styleChange( QStyle& oldStyle )
+{
+ setStyleDependantFrameWidth();
+ KListView::styleChange( oldStyle );
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::setFolderInfoStatus ()
+{
+ if ( !mFolder ) return;
+ QString str;
+ const int unread = mFolder->countUnread();
+ if ( static_cast<KMFolder*>(mFolder) == kmkernel->outboxFolder() )
+ str = unread ? i18n( "1 unsent", "%n unsent", unread ) : i18n( "0 unsent" );
+ else
+ str = unread ? i18n( "1 unread", "%n unread", unread ) : i18n( "0 unread" );
+ const int count = mFolder->count();
+ str = count ? i18n( "1 message, %1.", "%n messages, %1.", count ).arg( str )
+ : i18n( "0 messages" ); // no need for "0 unread" to be added here
+ if ( mFolder->isReadOnly() )
+ str = i18n("%1 = n messages, m unread.", "%1 Folder is read-only.").arg( str );
+ BroadcastStatus::instance()->setStatusMsg(str);
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::applyFiltersOnMsg()
+{
+ if (ActionScheduler::isEnabled() ||
+ kmkernel->filterMgr()->atLeastOneOnlineImapFolderTarget()) {
+ // uses action scheduler
+ KMFilterMgr::FilterSet set = KMFilterMgr::Explicit;
+ QValueList<KMFilter*> filters = kmkernel->filterMgr()->filters();
+ ActionScheduler *scheduler = new ActionScheduler( set, filters, this );
+ scheduler->setAutoDestruct( true );
+
+ int contentX, contentY;
+ HeaderItem *nextItem = prepareMove( &contentX, &contentY );
+ QPtrList<KMMsgBase> msgList = *selectedMsgs(true);
+ finalizeMove( nextItem, contentX, contentY );
+
+ for (KMMsgBase *msg = msgList.first(); msg; msg = msgList.next())
+ scheduler->execFilters( msg );
+ } else {
+ int contentX, contentY;
+ HeaderItem *nextItem = prepareMove( &contentX, &contentY );
+
+ //prevent issues with stale message pointers by using serial numbers instead
+ QValueList<unsigned long> serNums = KMMsgDict::serNumList( *selectedMsgs() );
+ if ( serNums.isEmpty() )
+ return;
+
+ finalizeMove( nextItem, contentX, contentY );
+ CREATE_TIMER(filter);
+ START_TIMER(filter);
+
+ KCursorSaver busy( KBusyPtr::busy() );
+ int msgCount = 0;
+ int msgCountToFilter = serNums.count();
+ ProgressItem* progressItem =
+ ProgressManager::createProgressItem( "filter"+ProgressManager::getUniqueID(),
+ i18n( "Filtering messages" ) );
+ progressItem->setTotalItems( msgCountToFilter );
+
+ for ( QValueList<unsigned long>::ConstIterator it = serNums.constBegin();
+ it != serNums.constEnd(); ++it ) {
+ msgCount++;
+ if ( msgCountToFilter - msgCount < 10 || !( msgCount % 20 ) || msgCount <= 10 ) {
+ progressItem->updateProgress();
+ QString statusMsg = i18n("Filtering message %1 of %2");
+ statusMsg = statusMsg.arg( msgCount ).arg( msgCountToFilter );
+ KPIM::BroadcastStatus::instance()->setStatusMsg( statusMsg );
+ KApplication::kApplication()->eventLoop()->processEvents( QEventLoop::ExcludeUserInput, 50 );
+ }
+
+ KMFolder *folder = 0;
+ int idx;
+ KMMsgDict::instance()->getLocation( *it, &folder, &idx );
+ KMMessage *msg = 0;
+ if (folder)
+ msg = folder->getMsg(idx);
+ if (msg) {
+ if (msg->transferInProgress())
+ continue;
+ msg->setTransferInProgress(true);
+ if (!msg->isComplete()) {
+ FolderJob *job = mFolder->createJob(msg);
+ connect(job, SIGNAL(messageRetrieved(KMMessage*)),
+ this, SLOT(slotFilterMsg(KMMessage*)));
+ job->start();
+ } else {
+ if (slotFilterMsg(msg) == 2)
+ break;
+ }
+ } else {
+ kdDebug (5006) << "####### KMHeaders::applyFiltersOnMsg -"
+ " A message went missing during filtering " << endl;
+ }
+ progressItem->incCompletedItems();
+ }
+ progressItem->setComplete();
+ progressItem = 0;
+ END_TIMER(filter);
+ SHOW_TIMER(filter);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMHeaders::setMsgRead (int msgId)
+{
+ KMMsgBase *msgBase = mFolder->getMsgBase( msgId );
+ if (!msgBase)
+ return;
+
+ SerNumList serNums;
+ if (msgBase->isNew() || msgBase->isUnread()) {
+ serNums.append( msgBase->getMsgSerNum() );
+ }
+
+ KMCommand *command = new KMSetStatusCommand( KMMsgStatusRead, serNums );
+ command->start();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMHeaders::deleteMsg ()
+{
+ //make sure we have an associated folder (root of folder tree does not).
+ if (!mFolder)
+ return;
+
+ int contentX, contentY;
+ HeaderItem *nextItem = prepareMove( &contentX, &contentY );
+ KMMessageList msgList = *selectedMsgs(true);
+ finalizeMove( nextItem, contentX, contentY );
+
+ KMCommand *command = new KMDeleteMsgCommand( mFolder, msgList );
+ connect( command, SIGNAL( completed( KMCommand * ) ),
+ this, SLOT( slotMoveCompleted( KMCommand * ) ) );
+ command->start();
+
+ BroadcastStatus::instance()->setStatusMsg("");
+ // triggerUpdate();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMHeaders::moveSelectedToFolder( int menuId )
+{
+ if (mMenuToFolder[menuId])
+ moveMsgToFolder( mMenuToFolder[menuId] );
+}
+
+//-----------------------------------------------------------------------------
+HeaderItem* KMHeaders::prepareMove( int *contentX, int *contentY )
+{
+ HeaderItem *ret = 0;
+ emit maybeDeleting();
+
+ disconnect( this, SIGNAL(currentChanged(QListViewItem*)),
+ this, SLOT(highlightMessage(QListViewItem*)));
+
+ QListViewItem *curItem;
+ HeaderItem *item;
+ curItem = currentItem();
+ while (curItem && curItem->isSelected() && curItem->itemBelow())
+ curItem = curItem->itemBelow();
+ while (curItem && curItem->isSelected() && curItem->itemAbove())
+ curItem = curItem->itemAbove();
+ item = static_cast<HeaderItem*>(curItem);
+
+ *contentX = contentsX();
+ *contentY = contentsY();
+
+ if (item && !item->isSelected())
+ ret = item;
+
+ return ret;
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::finalizeMove( HeaderItem *item, int contentX, int contentY )
+{
+ emit selected( 0 );
+ clearSelection();
+
+ if ( item ) {
+ setCurrentItem( item );
+ setSelected( item, true );
+ setSelectionAnchor( currentItem() );
+ mPrevCurrent = 0;
+ highlightMessage( item, false);
+ }
+
+ setContentsPos( contentX, contentY );
+ makeHeaderVisible();
+ connect( this, SIGNAL(currentChanged(QListViewItem*)),
+ this, SLOT(highlightMessage(QListViewItem*)));
+}
+
+
+//-----------------------------------------------------------------------------
+void KMHeaders::moveMsgToFolder ( KMFolder* destFolder, bool askForConfirmation )
+{
+ if ( destFolder == mFolder ) return; // Catch the noop case
+ if ( mFolder->isReadOnly() ) return;
+
+ KMMessageList msgList = *selectedMsgs();
+ if ( msgList.isEmpty() ) return;
+ if ( !destFolder && askForConfirmation && // messages shall be deleted
+ KMessageBox::warningContinueCancel(this,
+ i18n("<qt>Do you really want to delete the selected message?<br>"
+ "Once deleted, it cannot be restored.</qt>",
+ "<qt>Do you really want to delete the %n selected messages?<br>"
+ "Once deleted, they cannot be restored.</qt>", msgList.count() ),
+ msgList.count()>1 ? i18n("Delete Messages") : i18n("Delete Message"), KStdGuiItem::del(),
+ "NoConfirmDelete") == KMessageBox::Cancel )
+ return; // user canceled the action
+
+ // remember the message to select afterwards
+ int contentX, contentY;
+ HeaderItem *nextItem = prepareMove( &contentX, &contentY );
+ msgList = *selectedMsgs(true);
+ finalizeMove( nextItem, contentX, contentY );
+
+ KMCommand *command = new KMMoveCommand( destFolder, msgList );
+ connect( command, SIGNAL( completed( KMCommand * ) ),
+ this, SLOT( slotMoveCompleted( KMCommand * ) ) );
+ command->start();
+}
+
+void KMHeaders::slotMoveCompleted( KMCommand *command )
+{
+ kdDebug(5006) << k_funcinfo << command->result() << endl;
+ bool deleted = static_cast<KMMoveCommand *>( command )->destFolder() == 0;
+ if ( command->result() == KMCommand::OK ) {
+ // make sure the current item is shown
+ makeHeaderVisible();
+ BroadcastStatus::instance()->setStatusMsg(
+ deleted ? i18n("Messages deleted successfully.") : i18n("Messages moved successfully") );
+ } else {
+ /* The move failed or the user canceled it; reset the state of all
+ * messages involved and repaint.
+ *
+ * Note: This potentially resets too many items if there is more than one
+ * move going on. Oh well, I suppose no animals will be harmed.
+ * */
+ for (QListViewItemIterator it(this); it.current(); it++) {
+ HeaderItem *item = static_cast<HeaderItem*>(it.current());
+ if ( item->aboutToBeDeleted() ) {
+ item->setAboutToBeDeleted ( false );
+ item->setSelectable ( true );
+ KMMsgBase *msgBase = mFolder->getMsgBase(item->msgId());
+ if ( msgBase->isMessage() ) {
+ KMMessage *msg = static_cast<KMMessage *>(msgBase);
+ if ( msg ) msg->setTransferInProgress( false, true );
+ }
+ }
+ }
+ triggerUpdate();
+ if ( command->result() == KMCommand::Failed )
+ BroadcastStatus::instance()->setStatusMsg(
+ deleted ? i18n("Deleting messages failed.") : i18n("Moving messages failed.") );
+ else
+ BroadcastStatus::instance()->setStatusMsg(
+ deleted ? i18n("Deleting messages canceled.") : i18n("Moving messages canceled.") );
+ }
+ mOwner->updateMessageActions();
+}
+
+bool KMHeaders::canUndo() const
+{
+ return ( kmkernel->undoStack()->size() > 0 );
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::undo()
+{
+ kmkernel->undoStack()->undo();
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::copySelectedToFolder(int menuId )
+{
+ if (mMenuToFolder[menuId])
+ copyMsgToFolder( mMenuToFolder[menuId] );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMHeaders::copyMsgToFolder(KMFolder* destFolder, KMMessage* aMsg)
+{
+ if ( !destFolder )
+ return;
+
+ KMCommand * command = 0;
+ if (aMsg)
+ command = new KMCopyCommand( destFolder, aMsg );
+ else {
+ KMMessageList msgList = *selectedMsgs();
+ command = new KMCopyCommand( destFolder, msgList );
+ }
+
+ command->start();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMHeaders::setCurrentMsg(int cur)
+{
+ if (!mFolder) return;
+ if (cur >= mFolder->count()) cur = mFolder->count() - 1;
+ if ((cur >= 0) && (cur < (int)mItems.size())) {
+ clearSelection();
+ setCurrentItem( mItems[cur] );
+ setSelected( mItems[cur], true );
+ setSelectionAnchor( currentItem() );
+ }
+ makeHeaderVisible();
+ setFolderInfoStatus();
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::setSelected( QListViewItem *item, bool selected )
+{
+ if ( !item )
+ return;
+
+ if ( item->isVisible() )
+ KListView::setSelected( item, selected );
+
+ // If the item is the parent of a closed thread recursively select
+ // children .
+ if ( isThreaded() && !item->isOpen() && item->firstChild() ) {
+ QListViewItem *nextRoot = item->itemBelow();
+ QListViewItemIterator it( item->firstChild() );
+ for( ; (*it) != nextRoot; ++it ) {
+ if ( (*it)->isVisible() )
+ (*it)->setSelected( selected );
+ }
+ }
+}
+
+void KMHeaders::setSelectedByIndex( QValueList<int> items, bool selected )
+{
+ for ( QValueList<int>::Iterator it = items.begin(); it != items.end(); ++it )
+ {
+ if ( ((*it) >= 0) && ((*it) < (int)mItems.size()) )
+ {
+ setSelected( mItems[(*it)], selected );
+ }
+ }
+}
+
+void KMHeaders::clearSelectableAndAboutToBeDeleted( Q_UINT32 serNum )
+{
+ // fugly, but I see no way around it
+ for (QListViewItemIterator it(this); it.current(); it++) {
+ HeaderItem *item = static_cast<HeaderItem*>(it.current());
+ if ( item->aboutToBeDeleted() ) {
+ KMMsgBase *msgBase = mFolder->getMsgBase( item->msgId() );
+ if ( serNum == msgBase->getMsgSerNum() ) {
+ item->setAboutToBeDeleted ( false );
+ item->setSelectable ( true );
+ }
+ }
+ }
+ triggerUpdate();
+}
+
+//-----------------------------------------------------------------------------
+KMMessageList* KMHeaders::selectedMsgs(bool toBeDeleted)
+{
+ mSelMsgBaseList.clear();
+ for (QListViewItemIterator it(this); it.current(); it++) {
+ if ( it.current()->isSelected() && it.current()->isVisible() ) {
+ HeaderItem *item = static_cast<HeaderItem*>(it.current());
+ if ( !item->aboutToBeDeleted() ) { // we are already working on this one
+ if (toBeDeleted) {
+ // make sure the item is not uselessly rethreaded and not selectable
+ item->setAboutToBeDeleted ( true );
+ item->setSelectable ( false );
+ }
+ KMMsgBase *msgBase = mFolder->getMsgBase(item->msgId());
+ mSelMsgBaseList.append(msgBase);
+ }
+ }
+ }
+ return &mSelMsgBaseList;
+}
+
+//-----------------------------------------------------------------------------
+QValueList<int> KMHeaders::selectedItems()
+{
+ QValueList<int> items;
+ for ( QListViewItemIterator it(this); it.current(); it++ )
+ {
+ if ( it.current()->isSelected() && it.current()->isVisible() )
+ {
+ HeaderItem* item = static_cast<HeaderItem*>( it.current() );
+ items.append( item->msgId() );
+ }
+ }
+ return items;
+}
+
+//-----------------------------------------------------------------------------
+int KMHeaders::firstSelectedMsg() const
+{
+ int selectedMsg = -1;
+ QListViewItem *item;
+ for (item = firstChild(); item; item = item->itemBelow())
+ if (item->isSelected()) {
+ selectedMsg = (static_cast<HeaderItem*>(item))->msgId();
+ break;
+ }
+ return selectedMsg;
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::nextMessage()
+{
+ QListViewItem *lvi = currentItem();
+ if (lvi && lvi->itemBelow()) {
+ clearSelection();
+ setSelected( lvi, false );
+ selectNextMessage();
+ setSelectionAnchor( currentItem() );
+ ensureCurrentItemVisible();
+ }
+}
+
+void KMHeaders::selectNextMessage()
+{
+ KMMessage *cm = currentMsg();
+ if ( cm && cm->isBeingParsed() )
+ return;
+ QListViewItem *lvi = currentItem();
+ if( lvi ) {
+ QListViewItem *below = lvi->itemBelow();
+ QListViewItem *temp = lvi;
+ if (lvi && below ) {
+ while (temp) {
+ temp->firstChild();
+ temp = temp->parent();
+ }
+ lvi->repaint();
+ /* test to see if we need to unselect messages on back track */
+ (below->isSelected() ? setSelected(lvi, false) : setSelected(below, true));
+ setCurrentItem(below);
+ makeHeaderVisible();
+ setFolderInfoStatus();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::prevMessage()
+{
+ QListViewItem *lvi = currentItem();
+ if (lvi && lvi->itemAbove()) {
+ clearSelection();
+ setSelected( lvi, false );
+ selectPrevMessage();
+ setSelectionAnchor( currentItem() );
+ ensureCurrentItemVisible();
+ }
+}
+
+void KMHeaders::selectPrevMessage()
+{
+ KMMessage *cm = currentMsg();
+ if ( cm && cm->isBeingParsed() )
+ return;
+ QListViewItem *lvi = currentItem();
+ if( lvi ) {
+ QListViewItem *above = lvi->itemAbove();
+ QListViewItem *temp = lvi;
+
+ if (lvi && above) {
+ while (temp) {
+ temp->firstChild();
+ temp = temp->parent();
+ }
+ lvi->repaint();
+ /* test to see if we need to unselect messages on back track */
+ (above->isSelected() ? setSelected(lvi, false) : setSelected(above, true));
+ setCurrentItem(above);
+ makeHeaderVisible();
+ setFolderInfoStatus();
+ }
+ }
+}
+
+
+void KMHeaders::incCurrentMessage()
+{
+ KMMessage *cm = currentMsg();
+ if ( cm && cm->isBeingParsed() )
+ return;
+ QListViewItem *lvi = currentItem();
+ if ( lvi && lvi->itemBelow() ) {
+
+ disconnect(this,SIGNAL(currentChanged(QListViewItem*)),
+ this,SLOT(highlightMessage(QListViewItem*)));
+ setCurrentItem( lvi->itemBelow() );
+ ensureCurrentItemVisible();
+ setFocus();
+ connect(this,SIGNAL(currentChanged(QListViewItem*)),
+ this,SLOT(highlightMessage(QListViewItem*)));
+ }
+}
+
+void KMHeaders::decCurrentMessage()
+{
+ KMMessage *cm = currentMsg();
+ if ( cm && cm->isBeingParsed() )
+ return;
+ QListViewItem *lvi = currentItem();
+ if ( lvi && lvi->itemAbove() ) {
+ disconnect(this,SIGNAL(currentChanged(QListViewItem*)),
+ this,SLOT(highlightMessage(QListViewItem*)));
+ setCurrentItem( lvi->itemAbove() );
+ ensureCurrentItemVisible();
+ setFocus();
+ connect(this,SIGNAL(currentChanged(QListViewItem*)),
+ this,SLOT(highlightMessage(QListViewItem*)));
+ }
+}
+
+void KMHeaders::selectCurrentMessage()
+{
+ setCurrentMsg( currentItemIndex() );
+ highlightMessage( currentItem() );
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::findUnreadAux( HeaderItem*& item,
+ bool & foundUnreadMessage,
+ bool onlyNew,
+ bool aDirNext )
+{
+ KMMsgBase* msgBase = 0;
+ HeaderItem *lastUnread = 0;
+ /* itemAbove() is _slow_ */
+ if (aDirNext)
+ {
+ while (item) {
+ msgBase = mFolder->getMsgBase(item->msgId());
+ if (!msgBase) continue;
+ if (msgBase->isUnread() || msgBase->isNew())
+ foundUnreadMessage = true;
+
+ if (!onlyNew && (msgBase->isUnread() || msgBase->isNew())) break;
+ if (onlyNew && msgBase->isNew()) break;
+ item = static_cast<HeaderItem*>(item->itemBelow());
+ }
+ } else {
+ HeaderItem *newItem = static_cast<HeaderItem*>(firstChild());
+ while (newItem)
+ {
+ msgBase = mFolder->getMsgBase(newItem->msgId());
+ if (!msgBase) continue;
+ if (msgBase->isUnread() || msgBase->isNew())
+ foundUnreadMessage = true;
+ if (!onlyNew && (msgBase->isUnread() || msgBase->isNew())
+ || onlyNew && msgBase->isNew())
+ lastUnread = newItem;
+ if (newItem == item) break;
+ newItem = static_cast<HeaderItem*>(newItem->itemBelow());
+ }
+ item = lastUnread;
+ }
+}
+
+//-----------------------------------------------------------------------------
+int KMHeaders::findUnread(bool aDirNext, int aStartAt, bool onlyNew, bool acceptCurrent)
+{
+ HeaderItem *item, *pitem;
+ bool foundUnreadMessage = false;
+
+ if (!mFolder) return -1;
+ if (mFolder->count() <= 0) return -1;
+
+ if ((aStartAt >= 0) && (aStartAt < (int)mItems.size()))
+ item = mItems[aStartAt];
+ else {
+ item = currentHeaderItem();
+ if (!item) {
+ if (aDirNext)
+ item = static_cast<HeaderItem*>(firstChild());
+ else
+ item = static_cast<HeaderItem*>(lastChild());
+ }
+ if (!item)
+ return -1;
+
+ if ( !acceptCurrent )
+ if (aDirNext)
+ item = static_cast<HeaderItem*>(item->itemBelow());
+ else
+ item = static_cast<HeaderItem*>(item->itemAbove());
+ }
+
+ pitem = item;
+
+ findUnreadAux( item, foundUnreadMessage, onlyNew, aDirNext );
+
+ // We have found an unread item, but it is not necessary the
+ // first unread item.
+ //
+ // Find the ancestor of the unread item closest to the
+ // root and recursively sort all of that ancestors children.
+ if (item) {
+ QListViewItem *next = item;
+ while (next->parent())
+ next = next->parent();
+ next = static_cast<HeaderItem*>(next)->firstChildNonConst();
+ while (next && (next != item))
+ if (static_cast<HeaderItem*>(next)->firstChildNonConst())
+ next = next->firstChild();
+ else if (next->nextSibling())
+ next = next->nextSibling();
+ else {
+ while (next && (next != item)) {
+ next = next->parent();
+ if (next == item)
+ break;
+ if (next && next->nextSibling()) {
+ next = next->nextSibling();
+ break;
+ }
+ }
+ }
+ }
+
+ item = pitem;
+
+ findUnreadAux( item, foundUnreadMessage, onlyNew, aDirNext );
+ if (item)
+ return item->msgId();
+
+
+ // A kludge to try to keep the number of unread messages in sync
+ int unread = mFolder->countUnread();
+ if (((unread == 0) && foundUnreadMessage) ||
+ ((unread > 0) && !foundUnreadMessage)) {
+ mFolder->correctUnreadMsgsCount();
+ }
+ return -1;
+}
+
+//-----------------------------------------------------------------------------
+bool KMHeaders::nextUnreadMessage(bool acceptCurrent)
+{
+ if ( !mFolder || !mFolder->countUnread() ) return false;
+ int i = findUnread(true, -1, false, acceptCurrent);
+ if ( i < 0 && GlobalSettings::self()->loopOnGotoUnread() !=
+ GlobalSettings::EnumLoopOnGotoUnread::DontLoop )
+ {
+ HeaderItem * first = static_cast<HeaderItem*>(firstChild());
+ if ( first )
+ i = findUnread(true, first->msgId(), false, acceptCurrent); // from top
+ }
+ if ( i < 0 )
+ return false;
+ setCurrentMsg(i);
+ ensureCurrentItemVisible();
+ return true;
+}
+
+void KMHeaders::ensureCurrentItemVisible()
+{
+ int i = currentItemIndex();
+ if ((i >= 0) && (i < (int)mItems.size()))
+ center( contentsX(), itemPos(mItems[i]), 0, 9.0 );
+}
+
+//-----------------------------------------------------------------------------
+bool KMHeaders::prevUnreadMessage()
+{
+ if ( !mFolder || !mFolder->countUnread() ) return false;
+ int i = findUnread(false);
+ if ( i < 0 && GlobalSettings::self()->loopOnGotoUnread() !=
+ GlobalSettings::EnumLoopOnGotoUnread::DontLoop )
+ {
+ HeaderItem * last = static_cast<HeaderItem*>(lastItem());
+ if ( last )
+ i = findUnread(false, last->msgId() ); // from bottom
+ }
+ if ( i < 0 )
+ return false;
+ setCurrentMsg(i);
+ ensureCurrentItemVisible();
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMHeaders::slotNoDrag()
+{
+ // This causes Kolab issue 1569 (encrypted mails sometimes not dragable)
+ // This was introduced in r73594 to fix interference between dnd and
+ // pinentry, which is no longer reproducable now. However, since the
+ // original problem was probably a race and might reappear, let's keep
+ // this workaround in for now and just disable it.
+// mMousePressed = false;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMHeaders::makeHeaderVisible()
+{
+ if (currentItem())
+ ensureItemVisible( currentItem() );
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::highlightMessage(QListViewItem* lvi, bool markitread)
+{
+ // shouldnt happen but will crash if it does
+ if (lvi && !lvi->isSelectable()) return;
+
+ HeaderItem *item = static_cast<HeaderItem*>(lvi);
+ if (lvi != mPrevCurrent) {
+ if (mPrevCurrent && mFolder)
+ {
+ KMMessage *prevMsg = mFolder->getMsg(mPrevCurrent->msgId());
+ if (prevMsg && mReaderWindowActive)
+ {
+ mFolder->ignoreJobsForMessage(prevMsg);
+ if (!prevMsg->transferInProgress())
+ mFolder->unGetMsg(mPrevCurrent->msgId());
+ }
+ }
+ mPrevCurrent = item;
+ }
+
+ if (!item) {
+ emit selected( 0 ); return;
+ }
+
+ int idx = item->msgId();
+ KMMessage *msg = mFolder->getMsg(idx);
+ if (mReaderWindowActive && !msg) {
+ emit selected( 0 );
+ mPrevCurrent = 0;
+ return;
+ }
+
+ BroadcastStatus::instance()->setStatusMsg("");
+ if (markitread && idx >= 0) setMsgRead(idx);
+ mItems[idx]->irefresh();
+ mItems[idx]->repaint();
+ emit selected( msg );
+ setFolderInfoStatus();
+}
+
+void KMHeaders::highlightCurrentThread()
+{
+ QPtrList<QListViewItem> curThread = currentThread();
+ QPtrListIterator<QListViewItem> it( curThread );
+
+ for ( it.toFirst() ; it.current() ; ++it ) {
+ QListViewItem *lvi = *it;
+ lvi->setSelected( true );
+ lvi->repaint();
+ }
+}
+
+void KMHeaders::resetCurrentTime()
+{
+ mDate.reset();
+ // only reset exactly during minute switch
+ QTimer::singleShot( ( 60-QTime::currentTime().second() ) * 1000,
+ this, SLOT( resetCurrentTime() ) );
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::selectMessage(QListViewItem* lvi)
+{
+ HeaderItem *item = static_cast<HeaderItem*>(lvi);
+ if (!item)
+ return;
+
+ int idx = item->msgId();
+ KMMessage *msg = mFolder->getMsg(idx);
+ if (msg && !msg->transferInProgress())
+ {
+ emit activated(mFolder->getMsg(idx));
+ }
+
+// if (kmkernel->folderIsDraftOrOutbox(mFolder))
+// setOpen(lvi, !lvi->isOpen());
+}
+
+
+//-----------------------------------------------------------------------------
+void KMHeaders::updateMessageList( bool set_selection, bool forceJumpToUnread )
+{
+ mPrevCurrent = 0;
+ noRepaint = true;
+ clear();
+ mItems.resize(0); // will contain deleted pointers
+ noRepaint = false;
+ KListView::setSorting( mSortCol, !mSortDescending );
+ if (!mFolder) {
+ repaint();
+ return;
+ }
+ readSortOrder( set_selection, forceJumpToUnread );
+ emit messageListUpdated();
+}
+
+
+//-----------------------------------------------------------------------------
+// KMail Header list selection/navigation description
+//
+// If the selection state changes the reader window is updated to show the
+// current item.
+//
+// (The selection state of a message or messages can be changed by pressing
+// space, or normal/shift/cntrl clicking).
+//
+// The following keyboard events are supported when the messages headers list
+// has focus, Ctrl+Key_Down, Ctrl+Key_Up, Ctrl+Key_Home, Ctrl+Key_End,
+// Ctrl+Key_Next, Ctrl+Key_Prior, these events change the current item but do
+// not change the selection state.
+//
+// Exception: When shift selecting either with mouse or key press the reader
+// window is updated regardless of whether of not the selection has changed.
+void KMHeaders::keyPressEvent( QKeyEvent * e )
+{
+ bool cntrl = (e->state() & ControlButton );
+ bool shft = (e->state() & ShiftButton );
+ QListViewItem *cur = currentItem();
+
+ if (!e || !firstChild())
+ return;
+
+ // If no current item, make some first item current when a key is pressed
+ if (!cur) {
+ setCurrentItem( firstChild() );
+ setSelectionAnchor( currentItem() );
+ return;
+ }
+
+ // Handle space key press
+ if (cur->isSelectable() && e->ascii() == ' ' ) {
+ setSelected( cur, !cur->isSelected() );
+ highlightMessage( cur, false);
+ return;
+ }
+
+ if (cntrl) {
+ if (!shft)
+ disconnect(this,SIGNAL(currentChanged(QListViewItem*)),
+ this,SLOT(highlightMessage(QListViewItem*)));
+ switch (e->key()) {
+ case Key_Down:
+ case Key_Up:
+ case Key_Home:
+ case Key_End:
+ case Key_Next:
+ case Key_Prior:
+ case Key_Escape:
+ KListView::keyPressEvent( e );
+ }
+ if (!shft)
+ connect(this,SIGNAL(currentChanged(QListViewItem*)),
+ this,SLOT(highlightMessage(QListViewItem*)));
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Handle RMB press, show pop up menu
+void KMHeaders::rightButtonPressed( QListViewItem *lvi, const QPoint &, int )
+{
+ if (!lvi)
+ return;
+
+ if (!(lvi->isSelected())) {
+ clearSelection();
+ }
+ setSelected( lvi, true );
+ slotRMB();
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::contentsMousePressEvent(QMouseEvent* e)
+{
+ mPressPos = e->pos();
+ QListViewItem *lvi = itemAt( contentsToViewport( e->pos() ));
+ bool wasSelected = false;
+ bool rootDecoClicked = false;
+ if (lvi) {
+ wasSelected = lvi->isSelected();
+ rootDecoClicked =
+ ( mPressPos.x() <= header()->cellPos( header()->mapToActual( 0 ) ) +
+ treeStepSize() * ( lvi->depth() + ( rootIsDecorated() ? 1 : 0 ) ) + itemMargin() )
+ && ( mPressPos.x() >= header()->cellPos( header()->mapToActual( 0 ) ) );
+
+ if ( rootDecoClicked ) {
+ // Check if our item is the parent of a closed thread and if so, if the root
+ // decoration of the item was clicked (i.e. the +/- sign) which would expand
+ // the thread. In that case, deselect all children, so opening the thread
+ // doesn't cause a flicker.
+ if ( !lvi->isOpen() && lvi->firstChild() ) {
+ QListViewItem *nextRoot = lvi->itemBelow();
+ QListViewItemIterator it( lvi->firstChild() );
+ for( ; (*it) != nextRoot; ++it )
+ (*it)->setSelected( false );
+ }
+ }
+ }
+
+ // let klistview do it's thing, expanding/collapsing, selection/deselection
+ KListView::contentsMousePressEvent(e);
+ /* QListView's shift-select selects also invisible items. Until that is
+ fixed, we have to deselect hidden items here manually, so the quick
+ search doesn't mess things up. */
+ if ( e->state() & ShiftButton ) {
+ QListViewItemIterator it( this, QListViewItemIterator::Invisible );
+ while ( it.current() ) {
+ it.current()->setSelected( false );
+ ++it;
+ }
+ }
+
+ if ( rootDecoClicked ) {
+ // select the thread's children after closing if the parent is selected
+ if ( lvi && !lvi->isOpen() && lvi->isSelected() )
+ setSelected( lvi, true );
+ }
+
+ if ( lvi && !rootDecoClicked ) {
+ if ( lvi != currentItem() )
+ highlightMessage( lvi );
+ /* Explicitely set selection state. This is necessary because we want to
+ * also select all children of closed threads when the parent is selected. */
+
+ // unless ctrl mask, set selected if it isn't already
+ if ( !( e->state() & ControlButton ) && !wasSelected )
+ setSelected( lvi, true );
+ // if ctrl mask, toggle selection
+ if ( e->state() & ControlButton )
+ setSelected( lvi, !wasSelected );
+
+ if ((e->button() == LeftButton) )
+ mMousePressed = true;
+ }
+
+ // check if we are on a status column and toggle it
+ if ( lvi && e->button() == LeftButton && !( e->state() & (ShiftButton | ControlButton | AltButton | MetaButton) ) ) {
+ bool flagsToggleable = GlobalSettings::self()->allowLocalFlags() || !(mFolder ? mFolder->isReadOnly() : true);
+ int section = header()->sectionAt( e->pos().x() );
+ HeaderItem *item = static_cast<HeaderItem*>( lvi );
+ KMMsgBase *msg = mFolder->getMsgBase(item->msgId());
+ if ( section == mPaintInfo.flagCol && flagsToggleable ) {
+ setMsgStatus( KMMsgStatusFlag, true );
+ } else if ( section == mPaintInfo.importantCol && flagsToggleable ) {
+ setMsgStatus( KMMsgStatusFlag, true );
+ } else if ( section == mPaintInfo.todoCol && flagsToggleable ) {
+ setMsgStatus( KMMsgStatusTodo, true );
+ } else if ( section == mPaintInfo.watchedIgnoredCol && flagsToggleable ) {
+ if ( msg->isWatched() || msg->isIgnored() )
+ setMsgStatus( KMMsgStatusIgnored, true );
+ else
+ setMsgStatus( KMMsgStatusWatched, true );
+ } else if ( section == mPaintInfo.statusCol ) {
+ if ( msg->isUnread() || msg->isNew() )
+ setMsgStatus( KMMsgStatusRead );
+ else
+ setMsgStatus( KMMsgStatusUnread );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::contentsMouseReleaseEvent(QMouseEvent* e)
+{
+ if (e->button() != RightButton)
+ KListView::contentsMouseReleaseEvent(e);
+
+ mMousePressed = false;
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::contentsMouseMoveEvent( QMouseEvent* e )
+{
+ if (mMousePressed &&
+ (e->pos() - mPressPos).manhattanLength() > KGlobalSettings::dndEventDelay()) {
+ mMousePressed = false;
+ QListViewItem *item = itemAt( contentsToViewport(mPressPos) );
+ if ( item ) {
+ MailList mailList;
+ unsigned int count = 0;
+ for( QListViewItemIterator it(this); it.current(); it++ )
+ if( it.current()->isSelected() ) {
+ HeaderItem *item = static_cast<HeaderItem*>(it.current());
+ KMMsgBase *msg = mFolder->getMsgBase(item->msgId());
+ // FIXME: msg can be null here which crashes. I think it's a race
+ // because it's very hard to reproduce. (GS)
+ MailSummary mailSummary( msg->getMsgSerNum(), msg->msgIdMD5(),
+ msg->subject(), msg->fromStrip(),
+ msg->toStrip(), msg->date() );
+ mailList.append( mailSummary );
+ ++count;
+ }
+ MailListDrag *d = new MailListDrag( mailList, viewport(), new KMTextSource );
+
+ // Set pixmap
+ QPixmap pixmap;
+ if( count == 1 )
+ pixmap = QPixmap( DesktopIcon("message", KIcon::SizeSmall) );
+ else
+ pixmap = QPixmap( DesktopIcon("kmultiple", KIcon::SizeSmall) );
+
+ // Calculate hotspot (as in Konqueror)
+ if( !pixmap.isNull() ) {
+ QPoint hotspot( pixmap.width() / 2, pixmap.height() / 2 );
+ d->setPixmap( pixmap, hotspot );
+ }
+ if ( mFolder->isReadOnly() )
+ d->dragCopy();
+ else
+ d->drag();
+ }
+ }
+}
+
+void KMHeaders::highlightMessage(QListViewItem* i)
+{
+ highlightMessage( i, false );
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::slotRMB()
+{
+ if (!topLevelWidget()) return; // safe bet
+ mOwner->updateMessageActions();
+
+ // check if the user clicked into a status column and only show the respective menues
+ QListViewItem *item = itemAt( viewport()->mapFromGlobal( QCursor::pos() ) );
+ if ( item ) {
+ int section = header()->sectionAt( viewportToContents( viewport()->mapFromGlobal( QCursor::pos() ) ).x() );
+ if ( section == mPaintInfo.flagCol || section == mPaintInfo.importantCol
+ || section == mPaintInfo.todoCol || section == mPaintInfo.statusCol ) {
+ mOwner->statusMenu()->popup( QCursor::pos() );
+ return;
+ }
+ if ( section == mPaintInfo.watchedIgnoredCol ) {
+ mOwner->threadStatusMenu()->popup( QCursor::pos() );
+ return;
+ }
+ }
+
+ QPopupMenu *menu = new QPopupMenu(this);
+
+ mMenuToFolder.clear();
+
+ mOwner->updateMessageMenu();
+
+ bool out_folder = kmkernel->folderIsDraftOrOutbox( mFolder );
+ bool tem_folder = kmkernel->folderIsTemplates( mFolder );
+ if ( tem_folder ) {
+ mOwner->useAction()->plug( menu );
+ } else {
+ // show most used actions
+ if( !mFolder->isSent() ) {
+ mOwner->messageActions()->replyMenu()->plug( menu );
+ }
+ mOwner->forwardMenu()->plug( menu );
+ if( mOwner->sendAgainAction()->isEnabled() ) {
+ mOwner->sendAgainAction()->plug( menu );
+ } else {
+ mOwner->editAction()->plug( menu );
+ }
+ }
+ menu->insertSeparator();
+
+ QPopupMenu *msgCopyMenu = new QPopupMenu(menu);
+ mOwner->folderTree()->folderToPopupMenu( KMFolderTree::CopyMessage, this,
+ &mMenuToFolder, msgCopyMenu );
+ menu->insertItem(i18n("&Copy To"), msgCopyMenu);
+
+ if ( mFolder->isReadOnly() ) {
+ int id = menu->insertItem( i18n("&Move To") );
+ menu->setItemEnabled( id, false );
+ } else {
+ QPopupMenu *msgMoveMenu = new QPopupMenu(menu);
+ mOwner->folderTree()->folderToPopupMenu( KMFolderTree::MoveMessage, this,
+ &mMenuToFolder, msgMoveMenu );
+ menu->insertItem(i18n("&Move To"), msgMoveMenu);
+ }
+ menu->insertSeparator();
+ mOwner->statusMenu()->plug( menu ); // Mark Message menu
+ if ( mOwner->threadStatusMenu()->isEnabled() ) {
+ mOwner->threadStatusMenu()->plug( menu ); // Mark Thread menu
+ }
+
+ if ( !out_folder && !tem_folder ) {
+ menu->insertSeparator();
+ mOwner->filterMenu()->plug( menu ); // Create Filter menu
+ mOwner->action( "apply_filter_actions" )->plug( menu );
+ }
+
+ menu->insertSeparator();
+ mOwner->printAction()->plug(menu);
+ mOwner->saveAsAction()->plug(menu);
+ mOwner->saveAttachmentsAction()->plug(menu);
+ menu->insertSeparator();
+ if ( mFolder->isTrash() ) {
+ mOwner->deleteAction()->plug(menu);
+ if ( mOwner->trashThreadAction()->isEnabled() )
+ mOwner->deleteThreadAction()->plug(menu);
+ } else {
+ mOwner->trashAction()->plug(menu);
+ if ( mOwner->trashThreadAction()->isEnabled() )
+ mOwner->trashThreadAction()->plug(menu);
+ }
+ menu->insertSeparator();
+ mOwner->messageActions()->createTodoAction()->plug( menu );
+
+ KAcceleratorManager::manage(menu);
+ kmkernel->setContextMenuShown( true );
+ menu->exec(QCursor::pos(), 0);
+ kmkernel->setContextMenuShown( false );
+ delete menu;
+}
+
+//-----------------------------------------------------------------------------
+KMMessage* KMHeaders::currentMsg()
+{
+ HeaderItem *hi = currentHeaderItem();
+ if (!hi)
+ return 0;
+ else
+ return mFolder->getMsg(hi->msgId());
+}
+
+//-----------------------------------------------------------------------------
+HeaderItem* KMHeaders::currentHeaderItem()
+{
+ return static_cast<HeaderItem*>(currentItem());
+}
+
+//-----------------------------------------------------------------------------
+int KMHeaders::currentItemIndex()
+{
+ HeaderItem* item = currentHeaderItem();
+ if (item)
+ return item->msgId();
+ else
+ return -1;
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::setCurrentItemByIndex(int msgIdx)
+{
+ if (!mFolder->isOpened()) setFolder(mFolder);
+
+ if ((msgIdx >= 0) && (msgIdx < (int)mItems.size())) {
+ clearSelection();
+ bool unchanged = (currentItem() == mItems[msgIdx]);
+ setCurrentItem( mItems[msgIdx] );
+ setSelected( mItems[msgIdx], true );
+ setSelectionAnchor( currentItem() );
+ if (unchanged)
+ highlightMessage( mItems[msgIdx], false);
+ makeHeaderVisible();
+ }
+}
+
+//-----------------------------------------------------------------------------
+int KMHeaders::topItemIndex()
+{
+ HeaderItem *item = static_cast<HeaderItem*>( itemAt( QPoint( 1, 1 ) ) );
+ if ( item )
+ return item->msgId();
+ else
+ return -1;
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::setTopItemByIndex( int aMsgIdx)
+{
+ if ( aMsgIdx < 0 || static_cast<unsigned int>( aMsgIdx ) >= mItems.size() )
+ return;
+ const QListViewItem * const item = mItems[aMsgIdx];
+ if ( item )
+ setContentsPos( 0, itemPos( item ) );
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::setNestedOverride( bool override )
+{
+ mSortInfo.dirty = true;
+ mNestedOverride = override;
+ setRootIsDecorated( nestingPolicy != AlwaysOpen
+ && isThreaded() );
+ QString sortFile = mFolder->indexLocation() + ".sorted";
+ unlink(QFile::encodeName(sortFile));
+ reset();
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::setSubjectThreading( bool aSubjThreading )
+{
+ mSortInfo.dirty = true;
+ mSubjThreading = aSubjThreading;
+ QString sortFile = mFolder->indexLocation() + ".sorted";
+ unlink(QFile::encodeName(sortFile));
+ reset();
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::setOpen( QListViewItem *item, bool open )
+{
+ if ((nestingPolicy != AlwaysOpen)|| open)
+ ((HeaderItem*)item)->setOpenRecursive( open );
+}
+
+//-----------------------------------------------------------------------------
+const KMMsgBase* KMHeaders::getMsgBaseForItem( const QListViewItem *item ) const
+{
+ const HeaderItem *hi = static_cast<const HeaderItem *> ( item );
+ return mFolder->getMsgBase( hi->msgId() );
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::setSorting( int column, bool ascending )
+{
+ if (column != -1) {
+ // carsten: really needed?
+// if (column != mSortCol)
+// setColumnText( mSortCol, QIconSet( QPixmap()), columnText( mSortCol ));
+ if(mSortInfo.dirty || column != mSortInfo.column || ascending != mSortInfo.ascending) { //dirtied
+ QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int)));
+ mSortInfo.dirty = true;
+ }
+
+ assert(column >= 0);
+ mSortCol = column;
+ mSortDescending = !ascending;
+
+ if (!ascending && (column == mPaintInfo.dateCol))
+ mPaintInfo.orderOfArrival = !mPaintInfo.orderOfArrival;
+
+ if (!ascending && (column == mPaintInfo.subCol))
+ mPaintInfo.status = !mPaintInfo.status;
+
+ QString colText = i18n( "Date" );
+ if (mPaintInfo.orderOfArrival)
+ colText = i18n( "Order of Arrival" );
+ setColumnText( mPaintInfo.dateCol, colText);
+
+ colText = i18n( "Subject" );
+ if (mPaintInfo.status)
+ colText = colText + i18n( " (Status)" );
+ setColumnText( mPaintInfo.subCol, colText);
+ }
+ KListView::setSorting( column, ascending );
+ ensureCurrentItemVisible();
+ // Make sure the config and .sorted file are updated, otherwise stale info
+ // is read on new imap mail. ( folder->folderComplete() -> readSortOrder() ).
+ if ( mFolder ) {
+ writeFolderConfig();
+ writeSortOrder();
+ }
+}
+
+//Flatten the list and write it to disk
+static void internalWriteItem(FILE *sortStream, KMFolder *folder, int msgid,
+ int parent_id, QString key,
+ bool update_discover=true)
+{
+ unsigned long msgSerNum;
+ unsigned long parentSerNum;
+ msgSerNum = KMMsgDict::instance()->getMsgSerNum( folder, msgid );
+ if (parent_id >= 0)
+ parentSerNum = KMMsgDict::instance()->getMsgSerNum( folder, parent_id ) + KMAIL_RESERVED;
+ else
+ parentSerNum = (unsigned long)(parent_id + KMAIL_RESERVED);
+
+ fwrite(&msgSerNum, sizeof(msgSerNum), 1, sortStream);
+ fwrite(&parentSerNum, sizeof(parentSerNum), 1, sortStream);
+ Q_INT32 len = key.length() * sizeof(QChar);
+ fwrite(&len, sizeof(len), 1, sortStream);
+ if (len)
+ fwrite(key.unicode(), QMIN(len, KMAIL_MAX_KEY_LEN), 1, sortStream);
+
+ if (update_discover) {
+ //update the discovered change count
+ Q_INT32 discovered_count = 0;
+ fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 20, SEEK_SET);
+ fread(&discovered_count, sizeof(discovered_count), 1, sortStream);
+ discovered_count++;
+ fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 20, SEEK_SET);
+ fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream);
+ }
+}
+
+void KMHeaders::folderCleared()
+{
+ mSortCacheItems.clear(); //autoDelete is true
+ mSubjectLists.clear();
+ mImperfectlyThreadedList.clear();
+ mPrevCurrent = 0;
+ emit selected(0);
+}
+
+
+void KMHeaders::folderClosed()
+{
+ mFolder->open( "kmheaders" );
+ folderCleared();
+}
+
+bool KMHeaders::writeSortOrder()
+{
+ QString sortFile = KMAIL_SORT_FILE(mFolder);
+
+ if (!mSortInfo.dirty) {
+ struct stat stat_tmp;
+ if(stat(QFile::encodeName(sortFile), &stat_tmp) == -1) {
+ mSortInfo.dirty = true;
+ }
+ }
+ if (mSortInfo.dirty) {
+ if (!mFolder->count()) {
+ // Folder is empty now, remove the sort file.
+ unlink(QFile::encodeName(sortFile));
+ return true;
+ }
+ QString tempName = sortFile + ".temp";
+ unlink(QFile::encodeName(tempName));
+ FILE *sortStream = fopen(QFile::encodeName(tempName), "w");
+ if (!sortStream)
+ return false;
+
+ mSortInfo.ascending = !mSortDescending;
+ mSortInfo.dirty = false;
+ mSortInfo.column = mSortCol;
+ fprintf(sortStream, KMAIL_SORT_HEADER, KMAIL_SORT_VERSION);
+ //magic header information
+ Q_INT32 byteOrder = 0x12345678;
+ Q_INT32 column = mSortCol;
+ Q_INT32 ascending= !mSortDescending;
+ Q_INT32 threaded = isThreaded();
+ Q_INT32 appended=0;
+ Q_INT32 discovered_count = 0;
+ Q_INT32 sorted_count=0;
+ fwrite(&byteOrder, sizeof(byteOrder), 1, sortStream);
+ fwrite(&column, sizeof(column), 1, sortStream);
+ fwrite(&ascending, sizeof(ascending), 1, sortStream);
+ fwrite(&threaded, sizeof(threaded), 1, sortStream);
+ fwrite(&appended, sizeof(appended), 1, sortStream);
+ fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream);
+ fwrite(&sorted_count, sizeof(sorted_count), 1, sortStream);
+
+ QPtrStack<HeaderItem> items;
+ {
+ QPtrStack<QListViewItem> s;
+ for (QListViewItem * i = firstChild(); i; ) {
+ items.push((HeaderItem *)i);
+ if ( i->firstChild() ) {
+ s.push( i );
+ i = i->firstChild();
+ } else if( i->nextSibling()) {
+ i = i->nextSibling();
+ } else {
+ for(i=0; !i && s.count(); i = s.pop()->nextSibling())
+ ;
+ }
+ }
+ }
+
+ KMMsgBase *kmb;
+ while(HeaderItem *i = items.pop()) {
+ int parent_id = -1; //no parent, top level
+ if (threaded) {
+ kmb = mFolder->getMsgBase( i->msgId() );
+ assert(kmb); // I have seen 0L come out of this, called from
+ // KMHeaders::setFolder(0xgoodpointer, false);
+ // I see this crash too. after rebuilding a broken index on a dimap folder. always
+ QString replymd5 = kmb->replyToIdMD5();
+ QString replyToAuxId = kmb->replyToAuxIdMD5();
+ SortCacheItem *p = NULL;
+ if(!replymd5.isEmpty())
+ p = mSortCacheItems[replymd5];
+
+ if (p)
+ parent_id = p->id();
+ // We now have either found a parent, or set it to -1, which means that
+ // it will be reevaluated when a message is added, for example. If there
+ // is no replyToId and no replyToAuxId and the message is not prefixed,
+ // this message is top level, and will always be, so no need to
+ // reevaluate it.
+ if (replymd5.isEmpty()
+ && replyToAuxId.isEmpty()
+ && !kmb->subjectIsPrefixed() )
+ parent_id = -2;
+ // FIXME also mark messages with -1 as -2 a certain amount of time after
+ // their arrival, since it becomes very unlikely that a new parent for
+ // them will show up. (Ingo suggests a month.) -till
+ }
+ internalWriteItem(sortStream, mFolder, i->msgId(), parent_id,
+ i->key(mSortCol, !mSortDescending), false);
+ //double check for magic headers
+ sorted_count++;
+ }
+
+ //magic header twice, case they've changed
+ fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET, SEEK_SET);
+ fwrite(&byteOrder, sizeof(byteOrder), 1, sortStream);
+ fwrite(&column, sizeof(column), 1, sortStream);
+ fwrite(&ascending, sizeof(ascending), 1, sortStream);
+ fwrite(&threaded, sizeof(threaded), 1, sortStream);
+ fwrite(&appended, sizeof(appended), 1, sortStream);
+ fwrite(&discovered_count, sizeof(discovered_count), 1, sortStream);
+ fwrite(&sorted_count, sizeof(sorted_count), 1, sortStream);
+ if (sortStream && ferror(sortStream)) {
+ fclose(sortStream);
+ unlink(QFile::encodeName(sortFile));
+ kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl;
+ kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl;
+ kmkernel->emergencyExit( i18n("Failure modifying %1\n(No space left on device?)").arg( sortFile ));
+ }
+ fclose(sortStream);
+ ::rename(QFile::encodeName(tempName), QFile::encodeName(sortFile));
+ }
+
+ return true;
+}
+
+void KMHeaders::appendItemToSortFile(HeaderItem *khi)
+{
+ QString sortFile = KMAIL_SORT_FILE(mFolder);
+ if(FILE *sortStream = fopen(QFile::encodeName(sortFile), "r+")) {
+ int parent_id = -1; //no parent, top level
+
+ if (isThreaded()) {
+ SortCacheItem *sci = khi->sortCacheItem();
+ KMMsgBase *kmb = mFolder->getMsgBase( khi->msgId() );
+ if(sci->parent() && !sci->isImperfectlyThreaded())
+ parent_id = sci->parent()->id();
+ else if(kmb->replyToIdMD5().isEmpty()
+ && kmb->replyToAuxIdMD5().isEmpty()
+ && !kmb->subjectIsPrefixed())
+ parent_id = -2;
+ }
+
+ internalWriteItem(sortStream, mFolder, khi->msgId(), parent_id,
+ khi->key(mSortCol, !mSortDescending), false);
+
+ //update the appended flag FIXME obsolete?
+ Q_INT32 appended = 1;
+ fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET);
+ fwrite(&appended, sizeof(appended), 1, sortStream);
+ fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET);
+
+ if (sortStream && ferror(sortStream)) {
+ fclose(sortStream);
+ unlink(QFile::encodeName(sortFile));
+ kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl;
+ kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl;
+ kmkernel->emergencyExit( i18n("Failure modifying %1\n(No space left on device?)").arg( sortFile ));
+ }
+ fclose(sortStream);
+ } else {
+ mSortInfo.dirty = true;
+ }
+}
+
+void KMHeaders::dirtySortOrder(int column)
+{
+ mSortInfo.dirty = true;
+ QObject::disconnect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int)));
+ setSorting(column, mSortInfo.column == column ? !mSortInfo.ascending : true);
+}
+
+// -----------------
+void SortCacheItem::updateSortFile( FILE *sortStream, KMFolder *folder,
+ bool waiting_for_parent, bool update_discover)
+{
+ if(mSortOffset == -1) {
+ fseek(sortStream, 0, SEEK_END);
+ mSortOffset = ftell(sortStream);
+ } else {
+ fseek(sortStream, mSortOffset, SEEK_SET);
+ }
+
+ int parent_id = -1;
+ if(!waiting_for_parent) {
+ if(mParent && !isImperfectlyThreaded())
+ parent_id = mParent->id();
+ }
+ internalWriteItem(sortStream, folder, mId, parent_id, mKey, update_discover);
+}
+
+static bool compare_ascending = false;
+static bool compare_toplevel = true;
+static int compare_SortCacheItem(const void *s1, const void *s2)
+{
+ if ( !s1 || !s2 )
+ return 0;
+ SortCacheItem **b1 = (SortCacheItem **)s1;
+ SortCacheItem **b2 = (SortCacheItem **)s2;
+ int ret = (*b1)->key().compare((*b2)->key());
+ if(compare_ascending || !compare_toplevel)
+ ret = -ret;
+ return ret;
+}
+
+// Debugging helpers
+void KMHeaders::printSubjectThreadingTree()
+{
+ QDictIterator< QPtrList< SortCacheItem > > it ( mSubjectLists );
+ kdDebug(5006) << "SubjectThreading tree: " << endl;
+ for( ; it.current(); ++it ) {
+ QPtrList<SortCacheItem> list = *( it.current() );
+ QPtrListIterator<SortCacheItem> it2( list ) ;
+ kdDebug(5006) << "Subject MD5: " << it.currentKey() << " list: " << endl;
+ for( ; it2.current(); ++it2 ) {
+ SortCacheItem *sci = it2.current();
+ kdDebug(5006) << " item:" << sci << " sci id: " << sci->id() << endl;
+ }
+ }
+ kdDebug(5006) << endl;
+}
+
+void KMHeaders::printThreadingTree()
+{
+ kdDebug(5006) << "Threading tree: " << endl;
+ QDictIterator<SortCacheItem> it( mSortCacheItems );
+ kdDebug(5006) << endl;
+ for( ; it.current(); ++it ) {
+ SortCacheItem *sci = it.current();
+ kdDebug(5006) << "MsgId MD5: " << it.currentKey() << " message id: " << sci->id() << endl;
+ }
+ for (int i = 0; i < (int)mItems.size(); ++i) {
+ HeaderItem *item = mItems[i];
+ int parentCacheId = item->sortCacheItem()->parent()?item->sortCacheItem()->parent()->id():0;
+ kdDebug( 5006 ) << "SortCacheItem: " << item->sortCacheItem()->id() << " parent: " << parentCacheId << endl;
+ kdDebug( 5006 ) << "Item: " << item << " sortCache: " << item->sortCacheItem() << " parent: " << item->sortCacheItem()->parent() << endl;
+ }
+ kdDebug(5006) << endl;
+}
+
+// -------------------------------------
+
+void KMHeaders::buildThreadingTree( QMemArray<SortCacheItem *> sortCache )
+{
+ mSortCacheItems.clear();
+ mSortCacheItems.resize( mFolder->count() * 2 );
+
+ // build a dict of all message id's
+ for(int x = 0; x < mFolder->count(); x++) {
+ KMMsgBase *mi = mFolder->getMsgBase(x);
+ QString md5 = mi->msgIdMD5();
+ if(!md5.isEmpty())
+ mSortCacheItems.replace(md5, sortCache[x]);
+ }
+}
+
+
+void KMHeaders::buildSubjectThreadingTree( QMemArray<SortCacheItem *> sortCache )
+{
+ mSubjectLists.clear(); // autoDelete is true
+ mSubjectLists.resize( mFolder->count() * 2 );
+
+ for(int x = 0; x < mFolder->count(); x++) {
+ // Only a lot items that are now toplevel
+ if ( sortCache[x]->parent()
+ && sortCache[x]->parent()->id() != -666 ) continue;
+ KMMsgBase *mi = mFolder->getMsgBase(x);
+ QString subjMD5 = mi->strippedSubjectMD5();
+ if (subjMD5.isEmpty()) {
+ mFolder->getMsgBase(x)->initStrippedSubjectMD5();
+ subjMD5 = mFolder->getMsgBase(x)->strippedSubjectMD5();
+ }
+ if( subjMD5.isEmpty() ) continue;
+
+ /* For each subject, keep a list of items with that subject
+ * (stripped of prefixes) sorted by date. */
+ if (!mSubjectLists.find(subjMD5))
+ mSubjectLists.insert(subjMD5, new QPtrList<SortCacheItem>());
+ /* Insertion sort by date. These lists are expected to be very small.
+ * Also, since the messages are roughly ordered by date in the store,
+ * they should mostly be prepended at the very start, so insertion is
+ * cheap. */
+ int p=0;
+ for (QPtrListIterator<SortCacheItem> it(*mSubjectLists[subjMD5]);
+ it.current(); ++it) {
+ KMMsgBase *mb = mFolder->getMsgBase((*it)->id());
+ if ( mb->date() < mi->date())
+ break;
+ p++;
+ }
+ mSubjectLists[subjMD5]->insert( p, sortCache[x]);
+ sortCache[x]->setSubjectThreadingList( mSubjectLists[subjMD5] );
+ }
+}
+
+
+SortCacheItem* KMHeaders::findParent(SortCacheItem *item)
+{
+ SortCacheItem *parent = NULL;
+ if (!item) return parent;
+ KMMsgBase *msg = mFolder->getMsgBase(item->id());
+ QString replyToIdMD5 = msg->replyToIdMD5();
+ item->setImperfectlyThreaded(true);
+ /* First, try if the message our Reply-To header points to
+ * is available to thread below. */
+ if(!replyToIdMD5.isEmpty()) {
+ parent = mSortCacheItems[replyToIdMD5];
+ if (parent)
+ item->setImperfectlyThreaded(false);
+ }
+ if (!parent) {
+ // If we dont have a replyToId, or if we have one and the
+ // corresponding message is not in this folder, as happens
+ // if you keep your outgoing messages in an OUTBOX, for
+ // example, try the list of references, because the second
+ // to last will likely be in this folder. replyToAuxIdMD5
+ // contains the second to last one.
+ QString ref = msg->replyToAuxIdMD5();
+ if (!ref.isEmpty())
+ parent = mSortCacheItems[ref];
+ }
+ return parent;
+}
+
+SortCacheItem* KMHeaders::findParentBySubject(SortCacheItem *item)
+{
+ SortCacheItem *parent = NULL;
+ if (!item) return parent;
+
+ KMMsgBase *msg = mFolder->getMsgBase(item->id());
+
+ // Let's try by subject, but only if the subject is prefixed.
+ // This is necessary to make for example cvs commit mailing lists
+ // work as expected without having to turn threading off alltogether.
+ if (!msg->subjectIsPrefixed())
+ return parent;
+
+ QString replyToIdMD5 = msg->replyToIdMD5();
+ QString subjMD5 = msg->strippedSubjectMD5();
+ if (!subjMD5.isEmpty() && mSubjectLists[subjMD5]) {
+ /* Iterate over the list of potential parents with the same
+ * subject, and take the closest one by date. */
+ for (QPtrListIterator<SortCacheItem> it2(*mSubjectLists[subjMD5]);
+ it2.current(); ++it2) {
+ KMMsgBase *mb = mFolder->getMsgBase((*it2)->id());
+ if ( !mb ) return parent;
+ // make sure it's not ourselves
+ if ( item == (*it2) ) continue;
+ int delta = msg->date() - mb->date();
+ // delta == 0 is not allowed, to avoid circular threading
+ // with duplicates.
+ if (delta > 0 ) {
+ // Don't use parents more than 6 weeks older than us.
+ if (delta < 3628899)
+ parent = (*it2);
+ break;
+ }
+ }
+ }
+ return parent;
+}
+
+bool KMHeaders::readSortOrder( bool set_selection, bool forceJumpToUnread )
+{
+ if (!mFolder->isOpened()) mFolder->open("kmheaders");
+
+ //all cases
+ Q_INT32 column, ascending, threaded, discovered_count, sorted_count, appended;
+ Q_INT32 deleted_count = 0;
+ bool unread_exists = false;
+ bool jumpToUnread = (GlobalSettings::self()->actionEnterFolder() ==
+ GlobalSettings::EnumActionEnterFolder::SelectFirstUnreadNew) ||
+ forceJumpToUnread;
+ QMemArray<SortCacheItem *> sortCache(mFolder->count());
+ bool error = false;
+
+ //threaded cases
+ QPtrList<SortCacheItem> unparented;
+ mImperfectlyThreadedList.clear();
+
+ //cleanup
+ mItems.fill( 0, mFolder->count() );
+ sortCache.fill( 0 );
+
+ mRoot->clearChildren();
+
+ QString sortFile = KMAIL_SORT_FILE(mFolder);
+ FILE *sortStream = fopen(QFile::encodeName(sortFile), "r+");
+ mSortInfo.fakeSort = 0;
+
+ if(sortStream) {
+ mSortInfo.fakeSort = 1;
+ int version = 0;
+ if (fscanf(sortStream, KMAIL_SORT_HEADER, &version) != 1)
+ version = -1;
+ if(version == KMAIL_SORT_VERSION) {
+ Q_INT32 byteOrder = 0;
+ fread(&byteOrder, sizeof(byteOrder), 1, sortStream);
+ if (byteOrder == 0x12345678)
+ {
+ fread(&column, sizeof(column), 1, sortStream);
+ fread(&ascending, sizeof(ascending), 1, sortStream);
+ fread(&threaded, sizeof(threaded), 1, sortStream);
+ fread(&appended, sizeof(appended), 1, sortStream);
+ fread(&discovered_count, sizeof(discovered_count), 1, sortStream);
+ fread(&sorted_count, sizeof(sorted_count), 1, sortStream);
+
+ //Hackyness to work around qlistview problems
+ KListView::setSorting(-1);
+ header()->setSortIndicator(column, ascending);
+ QObject::connect(header(), SIGNAL(clicked(int)), this, SLOT(dirtySortOrder(int)));
+ //setup mSortInfo here now, as above may change it
+ mSortInfo.dirty = false;
+ mSortInfo.column = (short)column;
+ mSortInfo.ascending = (compare_ascending = ascending);
+
+ SortCacheItem *item;
+ unsigned long serNum, parentSerNum;
+ int id, len, parent, x;
+ QChar *tmp_qchar = 0;
+ int tmp_qchar_len = 0;
+ const int mFolderCount = mFolder->count();
+ QString key;
+
+ CREATE_TIMER(parse);
+ START_TIMER(parse);
+ for(x = 0; !feof(sortStream) && !error; x++) {
+ off_t offset = ftell(sortStream);
+ KMFolder *folder;
+ //parse
+ if(!fread(&serNum, sizeof(serNum), 1, sortStream) || //short read means to end
+ !fread(&parentSerNum, sizeof(parentSerNum), 1, sortStream) ||
+ !fread(&len, sizeof(len), 1, sortStream)) {
+ break;
+ }
+ if ((len < 0) || (len > KMAIL_MAX_KEY_LEN)) {
+ kdDebug(5006) << "Whoa.2! len " << len << " " << __FILE__ << ":" << __LINE__ << endl;
+ error = true;
+ continue;
+ }
+ if(len) {
+ if(len > tmp_qchar_len) {
+ tmp_qchar = (QChar *)realloc(tmp_qchar, len);
+ tmp_qchar_len = len;
+ }
+ if(!fread(tmp_qchar, len, 1, sortStream))
+ break;
+ key = QString(tmp_qchar, len / 2);
+ } else {
+ key = QString(""); //yuck
+ }
+
+ KMMsgDict::instance()->getLocation(serNum, &folder, &id);
+ if (folder != mFolder) {
+ ++deleted_count;
+ continue;
+ }
+ if (parentSerNum < KMAIL_RESERVED) {
+ parent = (int)parentSerNum - KMAIL_RESERVED;
+ } else {
+ KMMsgDict::instance()->getLocation(parentSerNum - KMAIL_RESERVED, &folder, &parent);
+ if (folder != mFolder)
+ parent = -1;
+ }
+ if ((id < 0) || (id >= mFolderCount) ||
+ (parent < -2) || (parent >= mFolderCount)) { // sanity checking
+ kdDebug(5006) << "Whoa.1! " << __FILE__ << ":" << __LINE__ << endl;
+ error = true;
+ continue;
+ }
+
+ if ((item=sortCache[id])) {
+ if (item->id() != -1) {
+ kdDebug(5006) << "Whoa.3! " << __FILE__ << ":" << __LINE__ << endl;
+ error = true;
+ continue;
+ }
+ item->setKey(key);
+ item->setId(id);
+ item->setOffset(offset);
+ } else {
+ item = sortCache[id] = new SortCacheItem(id, key, offset);
+ }
+ if (threaded && parent != -2) {
+ if(parent == -1) {
+ unparented.append(item);
+ mRoot->addUnsortedChild(item);
+ } else {
+ if( ! sortCache[parent] ) {
+ sortCache[parent] = new SortCacheItem;
+ }
+ sortCache[parent]->addUnsortedChild(item);
+ }
+ } else {
+ if(x < sorted_count )
+ mRoot->addSortedChild(item);
+ else {
+ mRoot->addUnsortedChild(item);
+ }
+ }
+ }
+ if (error || (x != sorted_count + discovered_count)) {// sanity check
+ kdDebug(5006) << endl << "Whoa: x " << x << ", sorted_count " << sorted_count << ", discovered_count " << discovered_count << ", count " << mFolder->count() << endl << endl;
+ fclose(sortStream);
+ sortStream = 0;
+ }
+
+ if(tmp_qchar)
+ free(tmp_qchar);
+ END_TIMER(parse);
+ SHOW_TIMER(parse);
+ }
+ else {
+ fclose(sortStream);
+ sortStream = 0;
+ }
+ } else {
+ fclose(sortStream);
+ sortStream = 0;
+ }
+ }
+
+ if (!sortStream) {
+ mSortInfo.dirty = true;
+ mSortInfo.column = column = mSortCol;
+ mSortInfo.ascending = ascending = !mSortDescending;
+ threaded = (isThreaded());
+ sorted_count = discovered_count = appended = 0;
+ KListView::setSorting( mSortCol, !mSortDescending );
+ }
+ //fill in empty holes
+ if((sorted_count + discovered_count - deleted_count) < mFolder->count()) {
+ CREATE_TIMER(holes);
+ START_TIMER(holes);
+ KMMsgBase *msg = 0;
+ for(int x = 0; x < mFolder->count(); x++) {
+ if((!sortCache[x] || (sortCache[x]->id() < 0)) && (msg=mFolder->getMsgBase(x))) {
+ int sortOrder = column;
+ if (mPaintInfo.orderOfArrival)
+ sortOrder |= (1 << 6);
+ if (mPaintInfo.status)
+ sortOrder |= (1 << 5);
+ sortCache[x] = new SortCacheItem(
+ x, HeaderItem::generate_key( this, msg, &mPaintInfo, sortOrder ));
+ if(threaded)
+ unparented.append(sortCache[x]);
+ else
+ mRoot->addUnsortedChild(sortCache[x]);
+ if(sortStream)
+ sortCache[x]->updateSortFile(sortStream, mFolder, true, true);
+ discovered_count++;
+ appended = 1;
+ }
+ }
+ END_TIMER(holes);
+ SHOW_TIMER(holes);
+ }
+
+ // Make sure we've placed everything in parent/child relationship. All
+ // messages with a parent id of -1 in the sort file are reevaluated here.
+ if (threaded) buildThreadingTree( sortCache );
+ QPtrList<SortCacheItem> toBeSubjThreaded;
+
+ if (threaded && !unparented.isEmpty()) {
+ CREATE_TIMER(reparent);
+ START_TIMER(reparent);
+
+ for(QPtrListIterator<SortCacheItem> it(unparented); it.current(); ++it) {
+ SortCacheItem *item = (*it);
+ SortCacheItem *parent = findParent( item );
+ // If we have a parent, make sure it's not ourselves
+ if ( parent && (parent != (*it)) ) {
+ parent->addUnsortedChild((*it));
+ if(sortStream)
+ (*it)->updateSortFile(sortStream, mFolder);
+ } else {
+ // if we will attempt subject threading, add to the list,
+ // otherwise to the root with them
+ if (mSubjThreading)
+ toBeSubjThreaded.append((*it));
+ else
+ mRoot->addUnsortedChild((*it));
+ }
+ }
+
+ if (mSubjThreading) {
+ buildSubjectThreadingTree( sortCache );
+ for(QPtrListIterator<SortCacheItem> it(toBeSubjThreaded); it.current(); ++it) {
+ SortCacheItem *item = (*it);
+ SortCacheItem *parent = findParentBySubject( item );
+
+ if ( parent ) {
+ parent->addUnsortedChild((*it));
+ if(sortStream)
+ (*it)->updateSortFile(sortStream, mFolder);
+ } else {
+ //oh well we tried, to the root with you!
+ mRoot->addUnsortedChild((*it));
+ }
+ }
+ }
+ END_TIMER(reparent);
+ SHOW_TIMER(reparent);
+ }
+ //create headeritems
+ CREATE_TIMER(header_creation);
+ START_TIMER(header_creation);
+ HeaderItem *khi;
+ SortCacheItem *i, *new_kci;
+ QPtrQueue<SortCacheItem> s;
+ s.enqueue(mRoot);
+ compare_toplevel = true;
+ do {
+ i = s.dequeue();
+ const QPtrList<SortCacheItem> *sorted = i->sortedChildren();
+ int unsorted_count, unsorted_off=0;
+ SortCacheItem **unsorted = i->unsortedChildren(unsorted_count);
+ if(unsorted)
+ qsort(unsorted, unsorted_count, sizeof(SortCacheItem *), //sort
+ compare_SortCacheItem);
+
+ /* The sorted list now contains all sorted children of this item, while
+ * the (aptly named) unsorted array contains all as of yet unsorted
+ * ones. It has just been qsorted, so it is in itself sorted. These two
+ * sorted lists are now merged into one. */
+ for(QPtrListIterator<SortCacheItem> it(*sorted);
+ (unsorted && unsorted_off < unsorted_count) || it.current(); ) {
+ /* As long as we have something in the sorted list and there is
+ nothing unsorted left, use the item from the sorted list. Also
+ if we are sorting descendingly and the sorted item is supposed
+ to be sorted before the unsorted one do so. In the ascending
+ case we invert the logic for non top level items. */
+ if( it.current() &&
+ ( !unsorted || unsorted_off >= unsorted_count
+ ||
+ ( ( !ascending || (ascending && !compare_toplevel) )
+ && (*it)->key() < unsorted[unsorted_off]->key() )
+ ||
+ ( ascending && (*it)->key() >= unsorted[unsorted_off]->key() )
+ )
+ )
+ {
+ new_kci = (*it);
+ ++it;
+ } else {
+ /* Otherwise use the next item of the unsorted list */
+ new_kci = unsorted[unsorted_off++];
+ }
+ if(new_kci->item() || new_kci->parent() != i) //could happen if you reparent
+ continue;
+
+ if(threaded && i->item()) {
+ // If the parent is watched or ignored, propagate that to it's
+ // children
+ if (mFolder->getMsgBase(i->id())->isWatched())
+ mFolder->getMsgBase(new_kci->id())->setStatus(KMMsgStatusWatched);
+ if (mFolder->getMsgBase(i->id())->isIgnored())
+ mFolder->getMsgBase(new_kci->id())->setStatus(KMMsgStatusIgnored);
+ khi = new HeaderItem(i->item(), new_kci->id(), new_kci->key());
+ } else {
+ khi = new HeaderItem(this, new_kci->id(), new_kci->key());
+ }
+ new_kci->setItem(mItems[new_kci->id()] = khi);
+ if(new_kci->hasChildren())
+ s.enqueue(new_kci);
+ // we always jump to new messages, but we only jump to
+ // unread messages if we are told to do so
+ if ( ( mFolder->getMsgBase(new_kci->id())->isNew() &&
+ GlobalSettings::self()->actionEnterFolder() ==
+ GlobalSettings::EnumActionEnterFolder::SelectFirstNew ) ||
+ ( ( mFolder->getMsgBase(new_kci->id())->isNew() ||
+ mFolder->getMsgBase(new_kci->id())->isUnread() ) &&
+ jumpToUnread ) )
+ {
+ unread_exists = true;
+ }
+ }
+ // If we are sorting by date and ascending the top level items are sorted
+ // ascending and the threads themselves are sorted descending. One wants
+ // to have new threads on top but the threads themselves top down.
+ if (mSortCol == paintInfo()->dateCol)
+ compare_toplevel = false;
+ } while(!s.isEmpty());
+
+ for(int x = 0; x < mFolder->count(); x++) { //cleanup
+ if (!sortCache[x]) { // not yet there?
+ continue;
+ }
+
+ if (!sortCache[x]->item()) { // we missed a message, how did that happen ?
+ kdDebug(5006) << "KMHeaders::readSortOrder - msg could not be threaded. "
+ << endl << "Please talk to your threading counselor asap. " << endl;
+ khi = new HeaderItem(this, sortCache[x]->id(), sortCache[x]->key());
+ sortCache[x]->setItem(mItems[sortCache[x]->id()] = khi);
+ }
+ // Add all imperfectly threaded items to a list, so they can be
+ // reevaluated when a new message arrives which might be a better parent.
+ // Important for messages arriving out of order.
+ if (threaded && sortCache[x]->isImperfectlyThreaded()) {
+ mImperfectlyThreadedList.append(sortCache[x]->item());
+ }
+ // Set the reverse mapping HeaderItem -> SortCacheItem. Needed for
+ // keeping the data structures up to date on removal, for example.
+ sortCache[x]->item()->setSortCacheItem(sortCache[x]);
+ }
+
+ if (getNestingPolicy()<2)
+ for (HeaderItem *khi=static_cast<HeaderItem*>(firstChild()); khi!=0;khi=static_cast<HeaderItem*>(khi->nextSibling()))
+ khi->setOpen(true);
+
+ END_TIMER(header_creation);
+ SHOW_TIMER(header_creation);
+
+ if(sortStream) { //update the .sorted file now
+ // heuristic for when it's time to rewrite the .sorted file
+ if( discovered_count * discovered_count > sorted_count - deleted_count ) {
+ mSortInfo.dirty = true;
+ } else {
+ //update the appended flag
+ appended = 0;
+ fseek(sortStream, KMAIL_MAGIC_HEADER_OFFSET + 16, SEEK_SET);
+ fwrite(&appended, sizeof(appended), 1, sortStream);
+ }
+ }
+
+ //show a message
+ CREATE_TIMER(selection);
+ START_TIMER(selection);
+ if(set_selection) {
+ int first_unread = -1;
+ if (unread_exists) {
+ HeaderItem *item = static_cast<HeaderItem*>(firstChild());
+ while (item) {
+ if ( ( mFolder->getMsgBase(item->msgId())->isNew() &&
+ GlobalSettings::self()->actionEnterFolder() ==
+ GlobalSettings::EnumActionEnterFolder::SelectFirstNew ) ||
+ ( ( mFolder->getMsgBase(item->msgId())->isNew() ||
+ mFolder->getMsgBase(item->msgId())->isUnread() ) &&
+ jumpToUnread ) )
+ {
+ first_unread = item->msgId();
+ break;
+ }
+ item = static_cast<HeaderItem*>(item->itemBelow());
+ }
+ }
+
+ if(first_unread == -1 ) {
+ setTopItemByIndex(mTopItem);
+ if ( mCurrentItem >= 0 )
+ setCurrentItemByIndex( mCurrentItem );
+ else if ( mCurrentItemSerNum > 0 )
+ setCurrentItemBySerialNum( mCurrentItemSerNum );
+ else
+ setCurrentItemByIndex( 0 );
+ } else {
+ setCurrentItemByIndex(first_unread);
+ makeHeaderVisible();
+ center( contentsX(), itemPos(mItems[first_unread]), 0, 9.0 );
+ }
+ } else {
+ // only reset the selection if we have no current item
+ if (mCurrentItem <= 0) {
+ setTopItemByIndex(mTopItem);
+ setCurrentItemByIndex(0);
+ }
+ }
+ END_TIMER(selection);
+ SHOW_TIMER(selection);
+ if (error || (sortStream && ferror(sortStream))) {
+ if ( sortStream )
+ fclose(sortStream);
+ unlink(QFile::encodeName(sortFile));
+ kdWarning(5006) << "Error: Failure modifying " << sortFile << " (No space left on device?)" << endl;
+ kdWarning(5006) << __FILE__ << ":" << __LINE__ << endl;
+
+ return true;
+ }
+ if(sortStream)
+ fclose(sortStream);
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+void KMHeaders::setCurrentItemBySerialNum( unsigned long serialNum )
+{
+ // Linear search == slow. Don't overuse this method.
+ // It's currently only used for finding the current item again
+ // after expiry deleted mails (so the index got invalidated).
+ for (int i = 0; i < (int)mItems.size() - 1; ++i) {
+ KMMsgBase *mMsgBase = mFolder->getMsgBase( i );
+ if ( mMsgBase->getMsgSerNum() == serialNum ) {
+ bool unchanged = (currentItem() == mItems[i]);
+ setCurrentItem( mItems[i] );
+ setSelected( mItems[i], true );
+ setSelectionAnchor( currentItem() );
+ if ( unchanged )
+ highlightMessage( currentItem(), false );
+ ensureCurrentItemVisible();
+ return;
+ }
+ }
+ // Not found. Maybe we should select the last item instead?
+ kdDebug(5006) << "KMHeaders::setCurrentItem item with serial number " << serialNum << " NOT FOUND" << endl;
+}
+
+void KMHeaders::copyMessages()
+{
+ mCopiedMessages.clear();
+ KMMessageList* list = selectedMsgs();
+ for ( uint i = 0; i < list->count(); ++ i )
+ mCopiedMessages << list->at( i )->getMsgSerNum();
+ mMoveMessages = false;
+ updateActions();
+ triggerUpdate();
+}
+
+void KMHeaders::cutMessages()
+{
+ mCopiedMessages.clear();
+ KMMessageList* list = selectedMsgs();
+ for ( uint i = 0; i < list->count(); ++ i )
+ mCopiedMessages << list->at( i )->getMsgSerNum();
+ mMoveMessages = true;
+ updateActions();
+ triggerUpdate();
+}
+
+void KMHeaders::pasteMessages()
+{
+ new MessageCopyHelper( mCopiedMessages, folder(), mMoveMessages, this );
+ if ( mMoveMessages ) {
+ mCopiedMessages.clear();
+ updateActions();
+ }
+}
+
+void KMHeaders::updateActions()
+{
+ KAction *copy = owner()->action( "copy_messages" );
+ KAction *cut = owner()->action( "cut_messages" );
+ KAction *paste = owner()->action( "paste_messages" );
+
+ if ( selectedItems().isEmpty() ) {
+ copy->setEnabled( false );
+ cut->setEnabled( false );
+ } else {
+ copy->setEnabled( true );
+ if ( folder() && folder()->isReadOnly() )
+ cut->setEnabled( false );
+ else
+ cut->setEnabled( true );
+ }
+
+ if ( mCopiedMessages.isEmpty() || !folder() || folder()->isReadOnly() )
+ paste->setEnabled( false );
+ else
+ paste->setEnabled( true );
+}
+
+void KMHeaders::setCopiedMessages(const QValueList< Q_UINT32 > & msgs, bool move)
+{
+ mCopiedMessages = msgs;
+ mMoveMessages = move;
+ updateActions();
+}
+
+bool KMHeaders::isMessageCut(Q_UINT32 serNum) const
+{
+ return mMoveMessages && mCopiedMessages.contains( serNum );
+}
+
+QValueList< Q_UINT32 > KMHeaders::selectedSernums()
+{
+ QValueList<Q_UINT32> list;
+ for ( QListViewItemIterator it(this); it.current(); it++ ) {
+ if ( it.current()->isSelected() && it.current()->isVisible() ) {
+ HeaderItem* item = static_cast<HeaderItem*>( it.current() );
+ KMMsgBase *msgBase = mFolder->getMsgBase( item->msgId() );
+ list.append( msgBase->getMsgSerNum() );
+ }
+ }
+ return list;
+}
+
+QValueList< Q_UINT32 > KMHeaders::selectedVisibleSernums()
+{
+ QValueList<Q_UINT32> list;
+ QListViewItemIterator it(this, QListViewItemIterator::Selected|QListViewItemIterator::Visible);
+ while( it.current() ) {
+ if ( it.current()->isSelected() && it.current()->isVisible() ) {
+ if ( it.current()->parent() && ( !it.current()->parent()->isOpen() ) ) {
+ // the item's parent is closed, don't traverse any more of this subtree
+ QListViewItem * lastAncestorWithSiblings = it.current()->parent();
+ // travel towards the root until we find an ancestor with siblings
+ while ( ( lastAncestorWithSiblings->depth() > 0 ) && !lastAncestorWithSiblings->nextSibling() )
+ lastAncestorWithSiblings = lastAncestorWithSiblings->parent();
+ // move the iterator to that ancestor's next sibling
+ it = QListViewItemIterator( lastAncestorWithSiblings->nextSibling() );
+ continue;
+ }
+ HeaderItem *item = static_cast<HeaderItem*>(it.current());
+ KMMsgBase *msgBase = mFolder->getMsgBase( item->msgId() );
+ list.append( msgBase->getMsgSerNum() );
+ }
+ ++it;
+ }
+
+ return list;
+}
+
+#include "kmheaders.moc"
diff --git a/kmail/kmheaders.h b/kmail/kmheaders.h
new file mode 100644
index 00000000..40487f8d
--- /dev/null
+++ b/kmail/kmheaders.h
@@ -0,0 +1,465 @@
+// -*- mode: C++ -*-
+
+#ifndef __KMHEADERS
+#define __KMHEADERS
+
+#include "kmime_util.h"
+#include "headeritem.h"
+using KMail::SortCacheItem;
+using KMail::HeaderItem;
+
+#include <klistview.h>
+#include <kfoldertree.h> // for KPaintInfo
+#include <kmmsgbase.h> // for KMMsgStatus
+
+#include <qwidget.h>
+#include <qstrlist.h>
+#include <qmemarray.h>
+#include <qmap.h>
+#include <qdragobject.h>
+#include <qdict.h>
+#include <qguardedptr.h>
+
+class KMFolder;
+class KMMessage;
+class KMMsgBase;
+class KMCommand;
+class KMMainWidget;
+class KPopupMenu;
+class QPalette;
+class QPixmap;
+class QIconSet;
+class QDateTime;
+
+typedef QPtrList<KMMsgBase> KMMessageList;
+typedef QValueList<Q_UINT32> SerNumList;
+typedef QMap<int,KMFolder*> KMMenuToFolder;
+enum NestingPolicy { AlwaysOpen = 0, DefaultOpen, DefaultClosed, OpenUnread };
+
+
+#define KMAIL_SORT_VERSION 1012
+#define KMAIL_SORT_FILE(x) x->indexLocation() + ".sorted"
+#define KMAIL_SORT_HEADER "## KMail Sort V%04d\n\t"
+#define KMAIL_MAGIC_HEADER_OFFSET 21 //strlen(KMAIL_SORT_HEADER)
+#define KMAIL_MAX_KEY_LEN 16384
+#define KMAIL_RESERVED 3
+
+/** The widget that shows the contents of folders */
+class KMHeaders : public KListView
+{
+ Q_OBJECT
+
+ friend class ::KMail::HeaderItem; // For easy access to the pixmaps
+
+public:
+ KMHeaders(KMMainWidget *owner, QWidget *parent=0, const char *name=0);
+ virtual ~KMHeaders();
+
+ /** A new folder has been selected update the list of headers shown
+ * To override the global settings for jumping to the first unread msg
+ * use @p forceJumpToUnread
+ */
+ virtual void setFolder(KMFolder *, bool forceJumpToUnread = false);
+
+ /** Return the folder whose message headers are being displayed */
+ KMFolder* folder(void) { return mFolder; }
+
+ /** read the config file and update nested state if necessary */
+ void refreshNestedState(void);
+
+ /** Set current message. If id<0 then the first message is shown,
+ if id>count() the last message is shown. */
+ virtual void setCurrentMsg(int msgId);
+
+ /** Get a list of all items in the current thread */
+ QPtrList<QListViewItem> currentThread() const;
+
+ /** Set all messages in the current thread to status @p status
+ or toggle it, if specified. */
+ virtual void setThreadStatus(KMMsgStatus status, bool toggle=false);
+
+ /* Set message status to read if it is new, or unread */
+ virtual void setMsgRead(int msgId);
+
+ /** The following methods processes all selected messages. */
+ virtual void setMsgStatus(KMMsgStatus status, bool toggle=false);
+ virtual void deleteMsg();
+ virtual void applyFiltersOnMsg();
+ virtual void undo();
+ virtual bool canUndo() const;
+ virtual HeaderItem * prepareMove( int *contentX, int *contentY );
+ virtual void finalizeMove( HeaderItem *item, int contentX, int contentY );
+
+ /** If destination is 0 then the messages are deleted, otherwise
+ they are moved to this folder. The second parameter is usefull when the
+ user has already confirmed the move/deletion. */
+ virtual void moveMsgToFolder( KMFolder* destination,
+ bool askForConfirmation=true );
+
+ /** Messages are duplicated and added to given folder.
+ If aMsg is set this one will be written to the destination folder. */
+ virtual void copyMsgToFolder(KMFolder* destination,
+ KMMessage* aMsg = 0);
+
+ /** Resets toBeDeleted and selectable status of all selected items. */
+ virtual void clearSelectableAndAboutToBeDeleted(Q_UINT32 serNum);
+ /** Returns list of selected messages. Mark the corresponding
+ header items to be deleted, if specified. */
+ virtual KMMessageList* selectedMsgs(bool toBeDeleted = false);
+
+ /** Returns the index values of currently selected items */
+ QValueList<int> selectedItems();
+
+ /** Returns the sernums of all selected items. */
+ QValueList<Q_UINT32> selectedSernums();
+
+ /** Returns the sernums of all visible (ie. items with expanded parent, not hidden by
+ eg. the quick search) selected items.
+ */
+ QValueList<Q_UINT32> selectedVisibleSernums();
+
+ /** Returns index of message returned by last getMsg() call */
+ int indexOfGetMsg (void) const { return getMsgIndex; }
+
+ /** Returns pointer to owning main window. */
+ KMMainWidget* owner(void) const { return mOwner; }
+
+ /** PaintInfo pointer */
+ const KPaintInfo *paintInfo(void) const { return &mPaintInfo; }
+
+ /** Read config options. */
+ virtual void readConfig(void);
+
+ /** Read color options and set palette. */
+ virtual void readColorConfig(void);
+
+ /** Return the current message */
+ virtual KMMessage* currentMsg();
+ /** Return the current list view item */
+ virtual HeaderItem* currentHeaderItem();
+ /** Return the index of the message corresponding to the current item */
+ virtual int currentItemIndex();
+ /** Set the current item to the one corresponding to the given msg id */
+ virtual void setCurrentItemByIndex( int msgIdx );
+ /** Set the current item to the one corresponding to the given serial number (slow!) */
+ void setCurrentItemBySerialNum( unsigned long serialNum );
+ /** Return the message id of the top most visible item */
+ virtual int topItemIndex();
+ /** Make the item corresponding to the message with the given id the
+ top most visible item. */
+ virtual void setTopItemByIndex( int aMsgIdx );
+ virtual void setNestedOverride( bool override );
+ virtual void setSubjectThreading( bool subjThreading );
+ /** Double force items to always be open */
+ virtual void setOpen ( QListViewItem *, bool );
+
+ NestingPolicy getNestingPolicy() const { return nestingPolicy; }
+ /** Returns true if the current header list is threaded. */
+ bool isThreaded() const {
+ return mNested != mNestedOverride; // xor
+ }
+
+ /** Find next/prev unread message. Starts at currentItem() if startAt
+ is unset. */
+ virtual int findUnread(bool findNext, int startAt=-1, bool onlyNew = false, bool acceptCurrent = false);
+
+ void highlightMessage(QListViewItem*, bool markitread);
+ void highlightCurrentThread();
+
+ /** return a string relativ to the current time */
+ static QString fancyDate( time_t otime );
+
+ bool noRepaint;
+
+ // filter events for popup
+ bool eventFilter ( QObject *o, QEvent *e );
+
+ /** gets the message represented by the item as a KMMsgBase. */
+ const KMMsgBase * getMsgBaseForItem( const QListViewItem *item ) const;
+
+ // accessors
+ QFont newFont() const { return mNewFont; }
+ QFont unreadFont() const { return mUnreadFont; }
+ QFont importantFont() const { return mImportantFont; }
+ QFont todoFont() const { return mTodoFont; }
+ QFont dateFont() const { return mDateFont; }
+
+ /**
+ Sets the list of copied/cutted messages.
+ @param msgs A list of serial numbers.
+ @param move if true, the messages were cutted
+ */
+ void setCopiedMessages( const QValueList<Q_UINT32> &msgs, bool move );
+
+ /**
+ Returns true if the message with the given serial number has been cut.
+ @param serNum A message serial number.
+ */
+ bool isMessageCut( Q_UINT32 serNum ) const;
+
+signals:
+ /** emitted when the list view item corresponding to this message
+ has been selected */
+ void selected(KMMessage *);
+ /** emitted when the list view item corresponding to this message
+ has been double clicked */
+ void activated(KMMessage *);
+ /** emitted when we might be about to delete messages */
+ void maybeDeleting();
+ /** emitted when the list of messages has been completely rebuilt */
+ void messageListUpdated();
+
+ /** emitted after a new item has been fully built and added to the
+ * list view. We can't use KListView::itemAdded, as that is emitted
+ * from the ctor of the item, at which point the building of the item
+ * is not yet far enough along to update the quick search, which is
+ * what is connected to this signal. */
+ void msgAddedToListView( QListViewItem* );
+
+public slots:
+ /** For when a list view item has been double clicked */
+ void selectMessage(QListViewItem*);
+ /** For when a list view item has been selected */
+ void highlightMessage(QListViewItem*);
+ /** For when righ mouse button is pressed */
+ void slotRMB();
+ /** Refresh list view item corresponding to the messae with the given id */
+ void msgHeaderChanged(KMFolder *folder, int msgId);
+ /** For when the list of messages in a folder has changed */
+ void msgChanged();
+ /** For when the folder has been cleared */
+ void folderCleared();
+ /** For when the folder has been cleared */
+ void folderClosed();
+ /** For when the message with the given message id has been added to a folder */
+ void msgAdded(int);
+ /** For when the message with the given id has been removed for a folder */
+ void msgRemoved( int, QString );
+ /** Make the next header visible scrolling if necessary */
+ void nextMessage();
+ /** Same as nextMessage() but don't clear the current selection */
+ void selectNextMessage();
+ /** Make the previous header visible scrolling if necessary */
+ void prevMessage();
+ /** Same as prevMessage() but don't clear the current selection */
+ void selectPrevMessage();
+ /** Make the nextUnread message header visible scrolling if necessary, returning
+ true if an unread message is found */
+ bool nextUnreadMessage(bool acceptCurrent = false);
+ /** Make the previous message header visible scrolling if necessary, returning
+ true if an unread message is found */
+ bool prevUnreadMessage();
+ /** Focus the next message, but don't select it. */
+ void incCurrentMessage();
+ /** Focus the previous message, but don't select it. */
+ void decCurrentMessage();
+ /** Select the message which currently has focus, if it's not already selected. */
+ void selectCurrentMessage();
+ /** Don't show a drag cursor */
+ void slotNoDrag();
+ /** timer function to set the current time regularly */
+ void resetCurrentTime();
+
+ /** Refresh the list of message headers shown */
+ void reset();
+
+ /** Expands (@p expand == true) or collapses (@p expand == false)
+ the current thread. */
+ void slotExpandOrCollapseThread( bool expand );
+ /** Expands (@p expand == true) or collapses (@p expand == false)
+ all threads */
+ void slotExpandOrCollapseAllThreads( bool expand );
+
+ virtual void ensureCurrentItemVisible();
+
+ /** Select an item and if it is the parent of a closed thread, also
+ recursively select its children. */
+ virtual void setSelected(QListViewItem *item, bool selected);
+
+ /** Select several items by message index
+ * and if they are the parent of a closed thread, also
+ * recursively select their children. */
+ void setSelectedByIndex(QValueList<int> items, bool selected);
+
+ /** switch a column with the given id (see KPaintInfo enum)
+ 1 for activate, 0 for deactivate, -1 for toggle*/
+ void slotToggleColumn(int id, int mode = -1);
+
+ /** Provide information about number of messages in a folder */
+ void setFolderInfoStatus();
+
+protected:
+ static QPixmap *pixNew, *pixUns, *pixDel, *pixRead, *pixRep, *pixSent,
+ *pixQueued, *pixFwd, *pixFlag, *pixWatched, *pixIgnored, *pixSpam, *pixHam,
+ *pixFullySigned, *pixPartiallySigned, *pixUndefinedSigned,
+ *pixFullyEncrypted, *pixPartiallyEncrypted, *pixUndefinedEncrypted,
+ *pixFiller, *pixEncryptionProblematic,
+ *pixSignatureProblematic, *pixAttachment,
+ *pixReadFwd, *pixReadReplied, *pixReadFwdReplied,*pixTodo;
+
+ /** Look for color changes */
+ virtual bool event(QEvent *e);
+
+ /** Overridden to support backing pixmap */
+ virtual void paintEmptyArea( QPainter * p, const QRect & rect );
+
+ /** Ensure the current item is visible */
+ void makeHeaderVisible();
+
+ /** Auxillary method to findUnread */
+ void findUnreadAux( HeaderItem*&, bool &, bool, bool );
+
+ /** Returns message index of first selected message of the messages
+ where the message with the given id is in. This for finding the correct
+ message that shall be the current message after move/delete of multiple
+ messages. */
+ virtual int firstSelectedMsg() const;
+
+ /** Read per-folder config options and apply them. */
+ virtual void readFolderConfig(void);
+
+ /** 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(QMouseEvent*);
+ virtual void contentsMouseReleaseEvent(QMouseEvent* e);
+ virtual void keyPressEvent( QKeyEvent * e );
+
+ /** Called when a header is clicked */
+ virtual void setSorting( int column, bool ascending = true);
+
+ /** To initiate a drag operation */
+ void contentsMouseMoveEvent( QMouseEvent *e );
+
+ /** reimplemented in order to update the frame width in case of a changed
+ GUI style */
+ void styleChange( QStyle& oldStyle );
+
+ /** Set the width of the frame to a reasonable value for the current GUI
+ style */
+ void setStyleDependantFrameWidth();
+
+protected slots:
+ /** Move messages corresponding to the selected items to the folder
+ corresponding to the given menuId */
+ virtual void moveSelectedToFolder( int menuId );
+ /** Same thing but copy */
+ virtual void copySelectedToFolder( int menuId );
+ /** Apply the filter Rules to a single message */
+ virtual int slotFilterMsg( KMMessage * );
+ /** dirties the sort order */
+ void dirtySortOrder(int);
+ /** show context menu */
+ void rightButtonPressed( QListViewItem *, const QPoint &, int );
+
+private slots:
+ void slotMoveCompleted( KMCommand * );
+
+ void copyMessages();
+ void cutMessages();
+ void pasteMessages();
+
+ void updateActions();
+
+private:
+ /** Is equivalent to clearing the list and inserting an item for
+ each message in the current folder */
+ virtual void updateMessageList( bool set_selection=false,
+ bool forceJumpToUnread = false );
+
+ /** Currently associated folder */
+ QGuardedPtr<KMFolder> mFolder;
+ /** The KMMainWin for status bar updates */
+ KMMainWidget* mOwner;
+ /** Top most visible item */
+ int mTopItem;
+ /** Index of the current item */
+ int mCurrentItem;
+ /** Serial number of the current item */
+ unsigned long mCurrentItemSerNum;
+ /** Map messages ids into HeaderItems */
+ QMemArray<HeaderItem*> mItems;
+
+ // ===== threading and sorting ==========
+ bool mNested, mNestedOverride, mSubjThreading;
+ NestingPolicy nestingPolicy;
+ int mSortCol;
+ bool mSortDescending;
+
+ struct {
+ uint ascending : 1;
+ uint dirty : 1;
+ short column;
+ uint fakeSort : 1;
+ uint removed : 1;
+ } mSortInfo;
+
+
+ /** */
+ QDict< SortCacheItem > mSortCacheItems;
+ /** */
+ QDict< QPtrList< SortCacheItem > > mSubjectLists;
+ /** */
+ QPtrList<HeaderItem> mImperfectlyThreadedList;
+
+ /** Debugging helpers for outputting the threading data structures. */
+ void printSubjectThreadingTree( );
+ void printThreadingTree( );
+ /** Initializes the mSortCacheItems tree with the contents of the folder */
+ void buildThreadingTree( QMemArray<SortCacheItem *> sortCache );
+ /** Initializes the mSubjectLists tree with the contents of the folder */
+ void buildSubjectThreadingTree( QMemArray<SortCacheItem *> sortCache );
+ /** Find a msg to thread item below */
+ SortCacheItem* findParent(SortCacheItem *item);
+ /** Find a msg to thread item below by subject */
+ SortCacheItem* findParentBySubject(SortCacheItem *item);
+ SortCacheItem* mRoot; // used to represent the list view itself while threading
+
+ /** */
+ void appendItemToSortFile(HeaderItem *);
+ /** */
+ bool writeSortOrder();
+ /** */
+ bool readSortOrder( bool set_selection = false,
+ bool forceJumpToUnread = false );
+
+ /** Updated as side effect of KMHeaders::getMsg */
+ int getMsgIndex;
+ /** ditto */
+ bool getMsgMulti;
+ /** ditto */
+ HeaderItem* getMsgItem;
+ /** @see KMHeaders::selectedMsgs isn't reentrant */
+ KMMessageList mSelMsgBaseList;
+ HeaderItem* mPrevCurrent;
+
+ /** Current colours and backing pixmap */
+ KPaintInfo mPaintInfo;
+
+ QFont mNewFont, mUnreadFont, mImportantFont, mDateFont,mTodoFont;
+
+ /** Icons shown in header */
+ static QIconSet *up, *down;
+ /** Map menu id into a folder */
+ KMMenuToFolder mMenuToFolder;
+
+ /** Drag and drop support */
+ bool mMousePressed;
+ /** ditto */
+ QPoint mPressPos;
+
+ KMime::DateFormatter mDate;
+ bool mReaderWindowActive;
+
+ /** popup to switch columns */
+ KPopupMenu* mPopup;
+
+ // copied messages
+ QValueList<Q_UINT32> mCopiedMessages;
+ bool mMoveMessages;
+}; // class
+#endif
diff --git a/kmail/kmkernel.cpp b/kmail/kmkernel.cpp
new file mode 100644
index 00000000..a686cffd
--- /dev/null
+++ b/kmail/kmkernel.cpp
@@ -0,0 +1,2401 @@
+/* -*- mode: C++; c-file-style: "gnu" -*- */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "config.h"
+#include "kmkernel.h"
+
+#include <weaver.h>
+#include <weaverlogger.h>
+
+#include "globalsettings.h"
+#include "broadcaststatus.h"
+using KPIM::BroadcastStatus;
+#include "kmstartup.h"
+#include "index.h"
+#include "kmmainwin.h"
+#include "composer.h"
+#include "kmmsgpart.h"
+#include "kmreadermainwin.h"
+#include "kmfoldermgr.h"
+#include "kmfoldercachedimap.h"
+#include "kmacctcachedimap.h"
+#include "kmfiltermgr.h"
+#include "kmfilteraction.h"
+#define REALLY_WANT_KMSENDER
+#include "kmsender.h"
+#undef REALLY_WANT_KMSENDER
+#include "undostack.h"
+#include "accountmanager.h"
+using KMail::AccountManager;
+#include <libkdepim/kfileio.h>
+#include "kmversion.h"
+#include "kmreaderwin.h"
+#include "kmmainwidget.h"
+#include "kmfoldertree.h"
+#include "recentaddresses.h"
+using KRecentAddress::RecentAddresses;
+#include "kmmsgdict.h"
+#include <libkpimidentities/identity.h>
+#include <libkpimidentities/identitymanager.h>
+#include "configuredialog.h"
+#include "kmcommands.h"
+#include "kmsystemtray.h"
+#include "transportmanager.h"
+
+#include <kwin.h>
+#include "kmailicalifaceimpl.h"
+#include "mailserviceimpl.h"
+using KMail::MailServiceImpl;
+#include "mailcomposerIface.h"
+#include "folderIface.h"
+using KMail::FolderIface;
+#include "jobscheduler.h"
+#include "templateparser.h"
+
+#include <kapplication.h>
+#include <kmessagebox.h>
+#include <knotifyclient.h>
+#include <kstaticdeleter.h>
+#include <kstandarddirs.h>
+#include <kconfig.h>
+#include <kprogress.h>
+#include <kpassivepopup.h>
+#include <dcopclient.h>
+#include <ksystemtray.h>
+#include <kpgp.h>
+#include <kdebug.h>
+#include <kio/netaccess.h>
+#include <kwallet.h>
+using KWallet::Wallet;
+#include "actionscheduler.h"
+
+#include <qutf7codec.h>
+#include <qvbox.h>
+#include <qdir.h>
+#include <qwidgetlist.h>
+#include <qobjectlist.h>
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include <X11/Xlib.h>
+#include <fixx11h.h>
+#include <kcmdlineargs.h>
+#include <kstartupinfo.h>
+
+KMKernel *KMKernel::mySelf = 0;
+
+/********************************************************************/
+/* Constructor and destructor */
+/********************************************************************/
+KMKernel::KMKernel (QObject *parent, const char *name) :
+ DCOPObject("KMailIface"), QObject(parent, name),
+ mIdentityManager(0), mConfigureDialog(0),
+ mContextMenuShown( false ), mWallet( 0 )
+{
+ kdDebug(5006) << "KMKernel::KMKernel" << endl;
+ mySelf = this;
+ the_startingUp = true;
+ closed_by_user = true;
+ the_firstInstance = true;
+ the_msgIndex = 0;
+
+ the_inboxFolder = 0;
+ the_outboxFolder = 0;
+ the_sentFolder = 0;
+ the_trashFolder = 0;
+ the_draftsFolder = 0;
+ the_templatesFolder = 0;
+
+ the_folderMgr = 0;
+ the_imapFolderMgr = 0;
+ the_dimapFolderMgr = 0;
+ the_searchFolderMgr = 0;
+ the_undoStack = 0;
+ the_acctMgr = 0;
+ the_filterMgr = 0;
+ the_popFilterMgr = 0;
+ the_filterActionDict = 0;
+ the_msgSender = 0;
+ mWin = 0;
+ mMailCheckAborted = false;
+
+ // make sure that we check for config updates before doing anything else
+ KMKernel::config();
+ // this shares the kmailrc parsing too (via KSharedConfig), and reads values from it
+ // so better do it here, than in some code where changing the group of config()
+ // would be unexpected
+ GlobalSettings::self();
+
+ // Set up DCOP interface
+ mICalIface = new KMailICalIfaceImpl();
+
+ mJobScheduler = new JobScheduler( this );
+
+ mXmlGuiInstance = 0;
+
+ new Kpgp::Module();
+
+ // register our own (libkdenetwork) utf-7 codec as long as Qt
+ // doesn't have it's own:
+ if ( !QTextCodec::codecForName("utf-7") ) {
+ kdDebug(5006) << "No Qt-native utf-7 codec found; registering QUtf7Codec from libkdenetwork" << endl;
+ (void) new QUtf7Codec();
+ }
+
+ // In the case of Japan. Japanese locale name is "eucjp" but
+ // The Japanese mail systems normally used "iso-2022-jp" of locale name.
+ // We want to change locale name from eucjp to iso-2022-jp at KMail only.
+ if ( QCString(QTextCodec::codecForLocale()->name()).lower() == "eucjp" )
+ {
+ netCodec = QTextCodec::codecForName("jis7");
+ // QTextCodec *cdc = QTextCodec::codecForName("jis7");
+ // QTextCodec::setCodecForLocale(cdc);
+ // KGlobal::locale()->setEncoding(cdc->mibEnum());
+ } else {
+ netCodec = QTextCodec::codecForLocale();
+ }
+ mMailService = new MailServiceImpl();
+
+ connectDCOPSignal( 0, 0, "kmailSelectFolder(QString)",
+ "selectFolder(QString)", false );
+}
+
+KMKernel::~KMKernel ()
+{
+ QMap<KIO::Job*, putData>::Iterator it = mPutJobs.begin();
+ while ( it != mPutJobs.end() )
+ {
+ KIO::Job *job = it.key();
+ mPutJobs.remove( it );
+ job->kill();
+ it = mPutJobs.begin();
+ }
+
+ delete mICalIface;
+ mICalIface = 0;
+ delete mMailService;
+ mMailService = 0;
+
+ GlobalSettings::self()->writeConfig();
+ delete mWallet;
+ mWallet = 0;
+ mySelf = 0;
+ kdDebug(5006) << "KMKernel::~KMKernel" << endl;
+}
+
+bool KMKernel::handleCommandLine( bool noArgsOpensReader )
+{
+ QString to, cc, bcc, subj, body;
+ QCStringList customHeaders;
+ KURL messageFile;
+ KURL::List attachURLs;
+ bool mailto = false;
+ bool checkMail = false;
+ bool viewOnly = false;
+ bool calledWithSession = false; // for ignoring '-session foo'
+
+ // process args:
+ KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
+ if (args->getOption("subject"))
+ {
+ subj = QString::fromLocal8Bit(args->getOption("subject"));
+ // if kmail is called with 'kmail -session abc' then this doesn't mean
+ // that the user wants to send a message with subject "ession" but
+ // (most likely) that the user clicked on KMail's system tray applet
+ // which results in KMKernel::raise() calling "kmail kmail newInstance"
+ // via dcop which apparently executes the application with the original
+ // command line arguments and those include "-session ..." if
+ // kmail/kontact was restored by session management
+ if ( subj == "ession" ) {
+ subj = QString::null;
+ calledWithSession = true;
+ }
+ else
+ mailto = true;
+ }
+
+ if (args->getOption("cc"))
+ {
+ mailto = true;
+ cc = QString::fromLocal8Bit(args->getOption("cc"));
+ }
+
+ if (args->getOption("bcc"))
+ {
+ mailto = true;
+ bcc = QString::fromLocal8Bit(args->getOption("bcc"));
+ }
+
+ if (args->getOption("msg"))
+ {
+ mailto = true;
+ messageFile.setPath( QString::fromLocal8Bit(args->getOption("msg")) );
+ }
+
+ if (args->getOption("body"))
+ {
+ mailto = true;
+ body = QString::fromLocal8Bit(args->getOption("body"));
+ }
+
+ QCStringList attachList = args->getOptionList("attach");
+ if (!attachList.isEmpty())
+ {
+ mailto = true;
+ for ( QCStringList::Iterator it = attachList.begin() ; it != attachList.end() ; ++it )
+ if ( !(*it).isEmpty() )
+ attachURLs += KURL( QString::fromLocal8Bit( *it ) );
+ }
+
+ customHeaders = args->getOptionList("header");
+
+ if (args->isSet("composer"))
+ mailto = true;
+
+ if (args->isSet("check"))
+ checkMail = true;
+
+ if ( args->getOption( "view" ) ) {
+ viewOnly = true;
+ const QString filename =
+ QString::fromLocal8Bit( args->getOption( "view" ) );
+ messageFile = KURL::fromPathOrURL( filename );
+ if ( !messageFile.isValid() ) {
+ messageFile = KURL();
+ messageFile.setPath( filename );
+ }
+ }
+
+ if ( !calledWithSession ) {
+ // only read additional command line arguments if kmail/kontact is
+ // not called with "-session foo"
+ for(int i= 0; i < args->count(); i++)
+ {
+ if (strncasecmp(args->arg(i),"mailto:",7)==0)
+ to += args->url(i).path() + ", ";
+ else {
+ QString tmpArg = QString::fromLocal8Bit( args->arg(i) );
+ KURL url( tmpArg );
+ if ( url.isValid() )
+ attachURLs += url;
+ else
+ to += tmpArg + ", ";
+ }
+ mailto = true;
+ }
+ if ( !to.isEmpty() ) {
+ // cut off the superfluous trailing ", "
+ to.truncate( to.length() - 2 );
+ }
+ }
+
+ if ( !calledWithSession )
+ args->clear();
+
+ if ( !noArgsOpensReader && !mailto && !checkMail && !viewOnly )
+ return false;
+
+ if ( viewOnly )
+ viewMessage( messageFile );
+ else
+ action( mailto, checkMail, to, cc, bcc, subj, body, messageFile,
+ attachURLs, customHeaders );
+ return true;
+}
+
+/********************************************************************/
+/* DCOP-callable, and command line actions */
+/********************************************************************/
+void KMKernel::checkMail () //might create a new reader but won't show!!
+{
+ kmkernel->acctMgr()->checkMail(false);
+}
+
+QStringList KMKernel::accounts()
+{
+ return kmkernel->acctMgr()->getAccounts();
+}
+
+void KMKernel::checkAccount (const QString &account) //might create a new reader but won't show!!
+{
+ kdDebug(5006) << "KMKernel::checkMail called" << endl;
+
+ KMAccount* acct = kmkernel->acctMgr()->findByName(account);
+ if (acct)
+ kmkernel->acctMgr()->singleCheckMail(acct, false);
+}
+
+void KMKernel::loadProfile( const QString& )
+{
+}
+
+void KMKernel::saveToProfile( const QString& ) const
+{
+}
+
+void KMKernel::openReader( bool onlyCheck )
+{
+ mWin = 0;
+ KMainWindow *ktmw = 0;
+ kdDebug(5006) << "KMKernel::openReader called" << endl;
+
+ if (KMainWindow::memberList)
+ for (ktmw = KMainWindow::memberList->first(); ktmw;
+ ktmw = KMainWindow::memberList->next())
+ if (ktmw->isA("KMMainWin"))
+ break;
+
+ bool activate;
+ if (ktmw) {
+ mWin = (KMMainWin *) ktmw;
+ activate = !onlyCheck; // existing window: only activate if not --check
+ if ( activate )
+ mWin->show();
+ } else {
+ mWin = new KMMainWin;
+ mWin->show();
+ activate = false; // new window: no explicit activation (#73591)
+ }
+
+ if ( activate ) {
+ // Activate window - doing this instead of KWin::activateWindow(mWin->winId());
+ // so that it also works when called from KMailApplication::newInstance()
+#if defined Q_WS_X11 && ! defined K_WS_QTONLY
+ KStartupInfo::setNewStartupId( mWin, kapp->startupId() );
+#endif
+ }
+}
+
+int KMKernel::openComposer (const QString &to, const QString &cc,
+ const QString &bcc, const QString &subject,
+ const QString &body, int hidden,
+ const KURL &messageFile,
+ const KURL::List &attachURLs,
+ const QCStringList &customHeaders)
+{
+ kdDebug(5006) << "KMKernel::openComposer called" << endl;
+ KMMessage *msg = new KMMessage;
+ msg->initHeader();
+ msg->setCharset("utf-8");
+ // tentatively decode to, cc and bcc because invokeMailer calls us with
+ // RFC 2047 encoded addresses in order to protect non-ASCII email addresses
+ if (!to.isEmpty())
+ msg->setTo( KMMsgBase::decodeRFC2047String( to.latin1() ) );
+ if (!cc.isEmpty())
+ msg->setCc( KMMsgBase::decodeRFC2047String( cc.latin1() ) );
+ if (!bcc.isEmpty())
+ msg->setBcc( KMMsgBase::decodeRFC2047String( bcc.latin1() ) );
+ if (!subject.isEmpty()) msg->setSubject(subject);
+ if (!messageFile.isEmpty() && messageFile.isLocalFile()) {
+ QCString str = KPIM::kFileToString( messageFile.path(), true, false );
+ if( !str.isEmpty() ) {
+ msg->setBody( QString::fromLocal8Bit( str ).utf8() );
+ } else {
+ TemplateParser parser( msg, TemplateParser::NewMessage,
+ "", false, false, false, false );
+ parser.process( NULL, NULL );
+ }
+ }
+ else if (!body.isEmpty())
+ {
+ msg->setBody(body.utf8());
+ }
+ else
+ {
+ TemplateParser parser( msg, TemplateParser::NewMessage,
+ "", false, false, false, false );
+ parser.process( NULL, NULL );
+ }
+
+ if (!customHeaders.isEmpty())
+ {
+ for ( QCStringList::ConstIterator it = customHeaders.begin() ; it != customHeaders.end() ; ++it )
+ if ( !(*it).isEmpty() )
+ {
+ const int pos = (*it).find( ':' );
+ if ( pos > 0 )
+ {
+ QCString header, value;
+ header = (*it).left( pos ).stripWhiteSpace();
+ value = (*it).mid( pos+1 ).stripWhiteSpace();
+ if ( !header.isEmpty() && !value.isEmpty() )
+ msg->setHeaderField( header, value );
+ }
+ }
+ }
+
+ KMail::Composer * cWin = KMail::makeComposer( msg );
+ cWin->setCharset("", true);
+ for ( KURL::List::ConstIterator it = attachURLs.begin() ; it != attachURLs.end() ; ++it )
+ cWin->addAttach((*it));
+ if (hidden == 0) {
+ cWin->show();
+ // Activate window - doing this instead of KWin::activateWindow(cWin->winId());
+ // so that it also works when called from KMailApplication::newInstance()
+#if defined Q_WS_X11 && ! defined K_WS_QTONLY
+ KStartupInfo::setNewStartupId( cWin, kapp->startupId() );
+#endif
+ }
+ return 1;
+}
+
+
+int KMKernel::openComposer (const QString &to, const QString &cc,
+ const QString &bcc, const QString &subject,
+ const QString &body, int hidden,
+ const QString &attachName,
+ const QCString &attachCte,
+ const QCString &attachData,
+ const QCString &attachType,
+ const QCString &attachSubType,
+ const QCString &attachParamAttr,
+ const QString &attachParamValue,
+ const QCString &attachContDisp )
+{
+ kdDebug(5006) << "KMKernel::openComposer called (deprecated version)" << endl;
+
+ return openComposer ( to, cc, bcc, subject, body, hidden,
+ attachName, attachCte, attachData,
+ attachType, attachSubType, attachParamAttr,
+ attachParamValue, attachContDisp, QCString() );
+}
+
+int KMKernel::openComposer (const QString &to, const QString &cc,
+ const QString &bcc, const QString &subject,
+ const QString &body, int hidden,
+ const QString &attachName,
+ const QCString &attachCte,
+ const QCString &attachData,
+ const QCString &attachType,
+ const QCString &attachSubType,
+ const QCString &attachParamAttr,
+ const QString &attachParamValue,
+ const QCString &attachContDisp,
+ const QCString &attachCharset )
+{
+ kdDebug(5006) << "KMKernel::openComposer()" << endl;
+
+ KMMessage *msg = new KMMessage;
+ KMMessagePart *msgPart = 0;
+ msg->initHeader();
+ msg->setCharset( "utf-8" );
+ if ( !cc.isEmpty() ) msg->setCc(cc);
+ if ( !bcc.isEmpty() ) msg->setBcc(bcc);
+ if ( !subject.isEmpty() ) msg->setSubject(subject);
+ if ( !to.isEmpty() ) msg->setTo(to);
+ if ( !body.isEmpty() ) {
+ msg->setBody(body.utf8());
+ } else {
+ TemplateParser parser( msg, TemplateParser::NewMessage,
+ "", false, false, false, false );
+ parser.process( NULL, NULL );
+ }
+
+ bool iCalAutoSend = false;
+ bool noWordWrap = false;
+ bool isICalInvitation = false;
+ KConfigGroup options( config(), "Groupware" );
+ if ( !attachData.isEmpty() ) {
+ isICalInvitation = attachName == "cal.ics" &&
+ attachType == "text" &&
+ attachSubType == "calendar" &&
+ attachParamAttr == "method";
+ // Remove BCC from identity on ical invitations (https://intevation.de/roundup/kolab/issue474)
+ if ( isICalInvitation && bcc.isEmpty() )
+ msg->setBcc( "" );
+ if ( isICalInvitation &&
+ GlobalSettings::self()->legacyBodyInvites() ) {
+ // KOrganizer invitation caught and to be sent as body instead
+ msg->setBody( attachData );
+ msg->setHeaderField( "Content-Type",
+ QString( "text/calendar; method=%1; "
+ "charset=\"utf-8\"" ).
+ arg( attachParamValue ) );
+
+ iCalAutoSend = true; // no point in editing raw ICAL
+ noWordWrap = true; // we shant word wrap inline invitations
+ } else {
+ // Just do what we're told to do
+ msgPart = new KMMessagePart;
+ msgPart->setName( attachName );
+ msgPart->setCteStr( attachCte );
+ msgPart->setBodyEncoded( attachData );
+ msgPart->setTypeStr( attachType );
+ msgPart->setSubtypeStr( attachSubType );
+ msgPart->setParameter( attachParamAttr, attachParamValue );
+ if( ! GlobalSettings::self()->exchangeCompatibleInvitations() ) {
+ msgPart->setContentDisposition( attachContDisp );
+ }
+ if( !attachCharset.isEmpty() ) {
+ // kdDebug(5006) << "KMKernel::openComposer set attachCharset to "
+ // << attachCharset << endl;
+ msgPart->setCharset( attachCharset );
+ }
+ // Don't show the composer window, if the automatic sending is checked
+ KConfigGroup options( config(), "Groupware" );
+ iCalAutoSend = options.readBoolEntry( "AutomaticSending", true );
+ }
+ }
+
+ KMail::Composer * cWin = KMail::makeComposer();
+ cWin->setMsg( msg, !isICalInvitation /* mayAutoSign */ );
+ cWin->setSigningAndEncryptionDisabled( isICalInvitation
+ && GlobalSettings::self()->legacyBodyInvites() );
+ cWin->setAutoDelete( true );
+ if( noWordWrap )
+ cWin->disableWordWrap();
+ else
+ cWin->setCharset( "", true );
+ if ( msgPart )
+ cWin->addAttach(msgPart);
+
+ if ( hidden == 0 && !iCalAutoSend ) {
+ cWin->show();
+ // Activate window - doing this instead of KWin::activateWindow(cWin->winId());
+ // so that it also works when called from KMailApplication::newInstance()
+#if defined Q_WS_X11 && ! defined K_WS_QTONLY
+ KStartupInfo::setNewStartupId( cWin, kapp->startupId() );
+#endif
+ } else {
+ cWin->setAutoDeleteWindow( true );
+ cWin->slotSendNow();
+ }
+
+ return 1;
+}
+
+void KMKernel::setDefaultTransport( const QString & transport )
+{
+ QStringList availTransports = KMail::TransportManager::transportNames();
+ QStringList::const_iterator it = availTransports.find( transport );
+ if ( it == availTransports.end() ) {
+ kdWarning() << "The transport you entered is not available" << endl;
+ return;
+ }
+ GlobalSettings::self()->setDefaultTransport( transport );
+}
+
+DCOPRef KMKernel::openComposer(const QString &to, const QString &cc,
+ const QString &bcc, const QString &subject,
+ const QString &body,bool hidden)
+{
+ KMMessage *msg = new KMMessage;
+ msg->initHeader();
+ msg->setCharset("utf-8");
+ if (!cc.isEmpty()) msg->setCc(cc);
+ if (!bcc.isEmpty()) msg->setBcc(bcc);
+ if (!subject.isEmpty()) msg->setSubject(subject);
+ if (!to.isEmpty()) msg->setTo(to);
+ if (!body.isEmpty()) {
+ msg->setBody(body.utf8());
+ } else {
+ TemplateParser parser( msg, TemplateParser::NewMessage,
+ "", false, false, false, false );
+ parser.process( NULL, NULL );
+ }
+
+ KMail::Composer * cWin = KMail::makeComposer( msg );
+ cWin->setCharset("", true);
+ if (!hidden) {
+ cWin->show();
+ // Activate window - doing this instead of KWin::activateWindow(cWin->winId());
+ // so that it also works when called from KMailApplication::newInstance()
+#if defined Q_WS_X11 && ! defined K_WS_QTONLY
+ KStartupInfo::setNewStartupId( cWin, kapp->startupId() );
+#endif
+ }
+
+ return DCOPRef( cWin->asMailComposerIFace() );
+}
+
+DCOPRef KMKernel::newMessage(const QString &to,
+ const QString &cc,
+ const QString &bcc,
+ bool hidden,
+ bool useFolderId,
+ const KURL & /*messageFile*/,
+ const KURL &attachURL)
+{
+ KMail::Composer * win = 0;
+ KMMessage *msg = new KMMessage;
+ KMFolder *folder = NULL;
+ uint id;
+
+ if ( useFolderId ) {
+ //create message with required folder identity
+ folder = currentFolder();
+ id = folder ? folder->identity() : 0;
+ msg->initHeader( id );
+ } else {
+ msg->initHeader();
+ }
+ msg->setCharset("utf-8");
+ //set basic headers
+ if (!to.isEmpty()) msg->setTo(to);
+ if (!cc.isEmpty()) msg->setCc(cc);
+ if (!bcc.isEmpty()) msg->setBcc(bcc);
+
+ if ( useFolderId ) {
+ TemplateParser parser( msg, TemplateParser::NewMessage,
+ "", false, false, false, false );
+ parser.process( NULL, folder );
+ win = makeComposer( msg, id );
+ } else {
+ TemplateParser parser( msg, TemplateParser::NewMessage,
+ "", false, false, false, false );
+ parser.process( NULL, folder );
+ win = makeComposer( msg );
+ }
+
+ //Add the attachment if we have one
+ if(!attachURL.isEmpty() && attachURL.isValid()) {
+ win->addAttach(attachURL);
+ }
+
+ //only show window when required
+ if(!hidden) {
+ win->show();
+ }
+ return DCOPRef( win->asMailComposerIFace() );
+}
+
+int KMKernel::viewMessage( const KURL & messageFile )
+{
+ KMOpenMsgCommand *openCommand = new KMOpenMsgCommand( 0, messageFile );
+
+ openCommand->start();
+
+ return 1;
+}
+
+int KMKernel::sendCertificate( const QString& to, const QByteArray& certData )
+{
+ KMMessage *msg = new KMMessage;
+ msg->initHeader();
+ msg->setCharset("utf-8");
+ msg->setSubject( i18n( "Certificate Signature Request" ) );
+ if (!to.isEmpty()) msg->setTo(to);
+ // ### Make this message customizable via KIOSK
+ msg->setBody( i18n( "Please create a certificate from attachment and return to sender." ).utf8() );
+
+ KMail::Composer * cWin = KMail::makeComposer( msg );
+ cWin->setCharset("", true);
+ cWin->slotSetAlwaysSend( true );
+ if (!certData.isEmpty()) {
+ KMMessagePart *msgPart = new KMMessagePart;
+ msgPart->setName("smime.p10");
+ msgPart->setCteStr("base64");
+ msgPart->setBodyEncodedBinary(certData);
+ msgPart->setTypeStr("application");
+ msgPart->setSubtypeStr("pkcs10");
+ msgPart->setContentDisposition("attachment; filename=smime.p10");
+ cWin->addAttach(msgPart);
+ }
+
+ cWin->show();
+ return 1;
+}
+
+KMMsgStatus KMKernel::strToStatus(const QString &flags)
+{
+ KMMsgStatus status = 0;
+ if (!flags.isEmpty()) {
+ for (uint n = 0; n < flags.length() ; n++) {
+ switch (flags[n]) {
+ case 'N':
+ status |= KMMsgStatusNew;
+ break;
+ case 'U':
+ status |= KMMsgStatusUnread;
+ break;
+ case 'O':
+ status |= KMMsgStatusOld;
+ break;
+ case 'R':
+ status |= KMMsgStatusRead;
+ break;
+ case 'D':
+ status |= KMMsgStatusDeleted;
+ break;
+ case 'A':
+ status |= KMMsgStatusReplied;
+ break;
+ case 'F':
+ status |= KMMsgStatusForwarded;
+ break;
+ case 'Q':
+ status |= KMMsgStatusQueued;
+ break;
+ case 'K':
+ status |= KMMsgStatusTodo;
+ break;
+ case 'S':
+ status |= KMMsgStatusSent;
+ break;
+ case 'G':
+ status |= KMMsgStatusFlag;
+ break;
+ case 'W':
+ status |= KMMsgStatusWatched;
+ break;
+ case 'I':
+ status |= KMMsgStatusIgnored;
+ break;
+ case 'P':
+ status |= KMMsgStatusSpam;
+ break;
+ case 'H':
+ status |= KMMsgStatusHam;
+ break;
+ case 'T':
+ status |= KMMsgStatusHasAttach;
+ break;
+ case 'C':
+ status |= KMMsgStatusHasNoAttach;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ return status;
+}
+
+int KMKernel::dcopAddMessage( const QString & foldername, const QString & msgUrlString,
+ const QString & MsgStatusFlags)
+{
+ return dcopAddMessage(foldername, KURL(msgUrlString), MsgStatusFlags);
+}
+
+int KMKernel::dcopAddMessage( const QString & foldername,const KURL & msgUrl,
+ const QString & MsgStatusFlags)
+{
+ kdDebug(5006) << "KMKernel::dcopAddMessage called" << endl;
+
+ if ( foldername.isEmpty() || foldername.startsWith("."))
+ return -1;
+
+ int retval;
+ bool readFolderMsgIds = false;
+ QString _foldername = foldername.stripWhiteSpace();
+ _foldername = _foldername.replace('\\',""); //try to prevent ESCAPE Sequences
+
+ if ( foldername != mAddMessageLastFolder ) {
+ mAddMessageMsgIds.clear();
+ readFolderMsgIds = true;
+ mAddMessageLastFolder = foldername;
+ }
+
+ if (!msgUrl.isEmpty() && msgUrl.isLocalFile()) {
+
+ // This is a proposed change by Daniel Andor.
+ // He proposed to change from the fopen(blah)
+ // to a KPIM::kFileToString(blah).
+ // Although it assigns a QString to a QString,
+ // because of the implicit sharing this poses
+ // no memory or performance penalty.
+
+ const QCString messageText =
+ KPIM::kFileToString( msgUrl.path(), true, false );
+ if ( messageText.isEmpty() )
+ return -2;
+
+ KMMessage *msg = new KMMessage();
+ msg->fromString( messageText );
+
+ if (readFolderMsgIds) {
+ if ( foldername.contains("/")) {
+ QString tmp_fname = "";
+ KMFolder *folder = NULL;
+ KMFolderDir *subfolder;
+ bool root = true;
+
+ QStringList subFList = QStringList::split("/",_foldername,false);
+
+ for ( QStringList::Iterator it = subFList.begin(); it != subFList.end(); ++it ) {
+ QString _newFolder = *it;
+ if(_newFolder.startsWith(".")) return -1;
+
+ if(root) {
+ folder = the_folderMgr->findOrCreate(*it, false);
+ if (folder) {
+ root = false;
+ tmp_fname = "/" + *it;
+ }
+ else return -1;
+ } else {
+ subfolder = folder->createChildFolder();
+ tmp_fname += "/" + *it;
+ if(!the_folderMgr->getFolderByURL( tmp_fname )) {
+ folder = the_folderMgr->createFolder(*it, false, folder->folderType(), subfolder);
+ }
+
+ if(!(folder = the_folderMgr->getFolderByURL( tmp_fname ))) return -1;
+ }
+ }
+
+ mAddMsgCurrentFolder = the_folderMgr->getFolderByURL( tmp_fname );
+ if(!folder) return -1;
+
+ } else {
+ mAddMsgCurrentFolder = the_folderMgr->findOrCreate(_foldername, false);
+ }
+ }
+
+ if ( mAddMsgCurrentFolder ) {
+ if (readFolderMsgIds) {
+
+ // OLD COMMENT:
+ // Try to determine if a message already exists in
+ // the folder. The message id that is searched for, is
+ // the subject line + the date. This should be quite
+ // unique. The change that a given date with a given
+ // subject is in the folder twice is very small.
+ // If the subject is empty, the fromStrip string
+ // is taken.
+
+ // NEW COMMENT from Danny Kukawka (danny.kukawka@web.de):
+ // subject line + the date is only unique if the following
+ // return a correct unique value:
+ // time_t DT = mb->date();
+ // QString dt = ctime(&DT);
+ // But if the datestring in the Header isn't RFC conform
+ // subject line + the date isn't unique.
+ //
+ // The only uique headerfield is the Message-ID. In some
+ // cases this could be empty. I then I use the
+ // subject line + dateStr .
+
+ int i;
+
+ mAddMsgCurrentFolder->open("dcopadd");
+ for( i=0; i<mAddMsgCurrentFolder->count(); i++) {
+ KMMsgBase *mb = mAddMsgCurrentFolder->getMsgBase(i);
+ QString id = mb->msgIdMD5();
+ if ( id.isEmpty() ) {
+ id = mb->subject();
+ if ( id.isEmpty() )
+ id = mb->fromStrip();
+ if ( id.isEmpty() )
+ id = mb->toStrip();
+
+ id += mb->dateStr();
+ }
+
+ //fprintf(stderr,"%s\n",(const char *) id);
+ if ( !id.isEmpty() ) {
+ mAddMessageMsgIds.append(id);
+ }
+ }
+ mAddMsgCurrentFolder->close("dcopadd");
+ }
+
+ QString msgId = msg->msgIdMD5();
+ if ( msgId.isEmpty()) {
+ msgId = msg->subject();
+ if ( msgId.isEmpty() )
+ msgId = msg->fromStrip();
+ if ( msgId.isEmpty() )
+ msgId = msg->toStrip();
+
+ msgId += msg->dateStr();
+ }
+
+ int k = mAddMessageMsgIds.findIndex( msgId );
+ //fprintf(stderr,"find %s = %d\n",(const char *) msgId,k);
+
+ if ( k == -1 ) {
+ if ( !msgId.isEmpty() ) {
+ mAddMessageMsgIds.append( msgId );
+ }
+
+ if ( !MsgStatusFlags.isEmpty() ) {
+ KMMsgStatus status = strToStatus(MsgStatusFlags);
+ if (status) msg->setStatus(status);
+ }
+
+ int index;
+ if ( mAddMsgCurrentFolder->addMsg( msg, &index ) == 0 ) {
+ mAddMsgCurrentFolder->unGetMsg( index );
+ retval = 1;
+ } else {
+ retval =- 2;
+ delete msg;
+ msg = 0;
+ }
+ } else {
+ //qDebug( "duplicate: " + msgId + "; subj: " + msg->subject() + ", from: " + msgId = msg->fromStrip());
+ retval = -4;
+ }
+ } else {
+ retval = -1;
+ }
+ } else {
+ retval = -2;
+ }
+ return retval;
+}
+
+void KMKernel::dcopResetAddMessage()
+{
+ mAddMessageMsgIds.clear();
+ mAddMessageLastFolder = QString();
+}
+
+int KMKernel::dcopAddMessage_fastImport( const QString & foldername,
+ const QString & msgUrlString,
+ const QString & MsgStatusFlags)
+{
+ return dcopAddMessage_fastImport(foldername, KURL(msgUrlString), MsgStatusFlags);
+}
+
+int KMKernel::dcopAddMessage_fastImport( const QString & foldername,
+ const KURL & msgUrl,
+ const QString & MsgStatusFlags)
+{
+ // Use this function to import messages without
+ // search for already existing emails.
+ kdDebug(5006) << "KMKernel::dcopAddMessage_fastImport called" << endl;
+
+ if ( foldername.isEmpty() || foldername.startsWith("."))
+ return -1;
+
+ int retval;
+ bool createNewFolder = false;
+
+ QString _foldername = foldername.stripWhiteSpace();
+ _foldername = _foldername.replace('\\',""); //try to prevent ESCAPE Sequences
+
+ if ( foldername != mAddMessageLastFolder ) {
+ createNewFolder = true;
+ mAddMessageLastFolder = foldername;
+ }
+
+
+ if ( !msgUrl.isEmpty() && msgUrl.isLocalFile() ) {
+ const QCString messageText =
+ KPIM::kFileToString( msgUrl.path(), true, false );
+ if ( messageText.isEmpty() )
+ return -2;
+
+ KMMessage *msg = new KMMessage();
+ msg->fromString( messageText );
+
+ if (createNewFolder) {
+ if ( foldername.contains("/")) {
+ QString tmp_fname = "";
+ KMFolder *folder = NULL;
+ KMFolderDir *subfolder;
+ bool root = true;
+
+ QStringList subFList = QStringList::split("/",_foldername,false);
+
+ for ( QStringList::Iterator it = subFList.begin(); it != subFList.end(); ++it ) {
+ QString _newFolder = *it;
+ if(_newFolder.startsWith(".")) return -1;
+
+ if(root) {
+ folder = the_folderMgr->findOrCreate(*it, false);
+ if (folder) {
+ root = false;
+ tmp_fname = "/" + *it;
+ }
+ else return -1;
+ } else {
+ subfolder = folder->createChildFolder();
+ tmp_fname += "/" + *it;
+ if(!the_folderMgr->getFolderByURL( tmp_fname )) {
+ folder = the_folderMgr->createFolder(*it, false, folder->folderType(), subfolder);
+ }
+ if(!(folder = the_folderMgr->getFolderByURL( tmp_fname ))) return -1;
+ }
+ }
+
+ mAddMsgCurrentFolder = the_folderMgr->getFolderByURL( tmp_fname );
+ if(!folder) return -1;
+
+ } else {
+ mAddMsgCurrentFolder = the_folderMgr->findOrCreate(_foldername, false);
+ }
+ }
+
+ if ( mAddMsgCurrentFolder ) {
+ int index;
+
+ if( !MsgStatusFlags.isEmpty() ) {
+ KMMsgStatus status = strToStatus(MsgStatusFlags);
+ if (status) msg->setStatus(status);
+ }
+
+ if ( mAddMsgCurrentFolder->addMsg( msg, &index ) == 0 ) {
+ mAddMsgCurrentFolder->unGetMsg( index );
+ retval = 1;
+ } else {
+ retval =- 2;
+ delete msg;
+ msg = 0;
+ }
+ } else {
+ retval = -1;
+ }
+ } else {
+ retval = -2;
+ }
+
+ return retval;
+}
+
+QStringList KMKernel::folderList() const
+{
+ QStringList folders;
+ const QString localPrefix = "/Local";
+ folders << localPrefix;
+ the_folderMgr->getFolderURLS( folders, localPrefix );
+ the_imapFolderMgr->getFolderURLS( folders );
+ the_dimapFolderMgr->getFolderURLS( folders );
+ return folders;
+}
+
+DCOPRef KMKernel::getFolder( const QString& vpath )
+{
+ const QString localPrefix = "/Local";
+ if ( the_folderMgr->getFolderByURL( vpath ) )
+ return DCOPRef( new FolderIface( vpath ) );
+ else if ( vpath.startsWith( localPrefix ) &&
+ the_folderMgr->getFolderByURL( vpath.mid( localPrefix.length() ) ) )
+ return DCOPRef( new FolderIface( vpath.mid( localPrefix.length() ) ) );
+ else if ( the_imapFolderMgr->getFolderByURL( vpath ) )
+ return DCOPRef( new FolderIface( vpath ) );
+ else if ( the_dimapFolderMgr->getFolderByURL( vpath ) )
+ return DCOPRef( new FolderIface( vpath ) );
+ return DCOPRef();
+}
+
+void KMKernel::raise()
+{
+ DCOPRef kmail( "kmail", "kmail" );
+ kmail.call( "newInstance" );
+}
+
+bool KMKernel::showMail( Q_UINT32 serialNumber, QString /* messageId */ )
+{
+ KMMainWidget *mainWidget = 0;
+ if (KMainWindow::memberList) {
+ KMainWindow *win = 0;
+ QObjectList *l;
+
+ // First look for a KMainWindow.
+ for (win = KMainWindow::memberList->first(); win;
+ win = KMainWindow::memberList->next()) {
+ // Then look for a KMMainWidget.
+ l = win->queryList("KMMainWidget");
+ if (l && l->first()) {
+ mainWidget = dynamic_cast<KMMainWidget *>(l->first());
+ if (win->isActiveWindow())
+ break;
+ }
+ }
+ }
+
+ if (mainWidget) {
+ int idx = -1;
+ KMFolder *folder = 0;
+ KMMsgDict::instance()->getLocation(serialNumber, &folder, &idx);
+ if (!folder || (idx == -1))
+ return false;
+ KMFolderOpener openFolder(folder, "showmail");
+ KMMsgBase *msgBase = folder->getMsgBase(idx);
+ if (!msgBase)
+ return false;
+ bool unGet = !msgBase->isMessage();
+ KMMessage *msg = folder->getMsg(idx);
+
+ KMReaderMainWin *win = new KMReaderMainWin( false, false );
+ KMMessage *newMessage = new KMMessage( *msg );
+ newMessage->setParent( msg->parent() );
+ newMessage->setMsgSerNum( msg->getMsgSerNum() );
+ newMessage->setReadyToShow( true );
+ win->showMsg( GlobalSettings::self()->overrideCharacterEncoding(), newMessage );
+ win->show();
+
+ if (unGet)
+ folder->unGetMsg(idx);
+ return true;
+ }
+
+ return false;
+}
+
+QString KMKernel::getFrom( Q_UINT32 serialNumber )
+{
+ int idx = -1;
+ KMFolder *folder = 0;
+ KMMsgDict::instance()->getLocation(serialNumber, &folder, &idx);
+ if (!folder || (idx == -1))
+ return QString::null;
+ KMFolderOpener openFolder(folder, "getFrom");
+ KMMsgBase *msgBase = folder->getMsgBase(idx);
+ if (!msgBase)
+ return QString::null;
+ bool unGet = !msgBase->isMessage();
+ KMMessage *msg = folder->getMsg(idx);
+ QString result = msg->from();
+ if (unGet)
+ folder->unGetMsg(idx);
+ return result;
+}
+
+QString KMKernel::debugScheduler()
+{
+ QString res = KMail::ActionScheduler::debug();
+ return res;
+}
+
+QString KMKernel::debugSernum( Q_UINT32 serialNumber )
+{
+ QString res;
+ if (serialNumber != 0) {
+ int idx = -1;
+ KMFolder *folder = 0;
+ KMMsgBase *msg = 0;
+ KMMsgDict::instance()->getLocation( serialNumber, &folder, &idx );
+ // It's possible that the message has been deleted or moved into a
+ // different folder
+ if (folder && (idx != -1)) {
+ // everything is ok
+ KMFolderOpener openFolder(folder, "debugser");
+ msg = folder->getMsgBase( idx );
+ if (msg) {
+ res.append( QString( " subject %s,\n sender %s,\n date %s.\n" )
+ .arg( msg->subject() )
+ .arg( msg->fromStrip() )
+ .arg( msg->dateStr() ) );
+ } else {
+ res.append( QString( "Invalid serial number." ) );
+ }
+ } else {
+ res.append( QString( "Invalid serial number." ) );
+ }
+ }
+ return res;
+}
+
+
+void KMKernel::pauseBackgroundJobs()
+{
+ mBackgroundTasksTimer->stop();
+ mJobScheduler->pause();
+}
+
+void KMKernel::resumeBackgroundJobs()
+{
+ mJobScheduler->resume();
+ mBackgroundTasksTimer->start( 4 * 60 * 60 * 1000, true );
+}
+
+void KMKernel::stopNetworkJobs()
+{
+ if ( GlobalSettings::self()->networkState() == GlobalSettings::EnumNetworkState::Offline )
+ return;
+
+ GlobalSettings::setNetworkState( GlobalSettings::EnumNetworkState::Offline );
+ BroadcastStatus::instance()->setStatusMsg( i18n("KMail is set to be offline; all network jobs are suspended"));
+ emit onlineStatusChanged( (GlobalSettings::EnumNetworkState::type)GlobalSettings::networkState() );
+}
+
+void KMKernel::resumeNetworkJobs()
+{
+ if ( GlobalSettings::self()->networkState() == GlobalSettings::EnumNetworkState::Online )
+ return;
+
+ GlobalSettings::setNetworkState( GlobalSettings::EnumNetworkState::Online );
+ BroadcastStatus::instance()->setStatusMsg( i18n("KMail is set to be online; all network jobs resumed"));
+ emit onlineStatusChanged( (GlobalSettings::EnumNetworkState::type)GlobalSettings::networkState() );
+
+ if ( kmkernel->msgSender()->sendImmediate() ) {
+ kmkernel->msgSender()->sendQueued();
+ }
+}
+
+bool KMKernel::isOffline()
+{
+ if ( GlobalSettings::self()->networkState() == GlobalSettings::EnumNetworkState::Offline )
+ return true;
+ else
+ return false;
+}
+
+bool KMKernel::askToGoOnline()
+{
+ if ( kmkernel->isOffline() ) {
+ int rc =
+ KMessageBox::questionYesNo( KMKernel::self()->mainWin(),
+ i18n("KMail is currently in offline mode. "
+ "How do you want to proceed?"),
+ i18n("Online/Offline"),
+ i18n("Work Online"),
+ i18n("Work Offline"));
+
+ if( rc == KMessageBox::No ) {
+ return false;
+ } else {
+ kmkernel->resumeNetworkJobs();
+ }
+ }
+ return true;
+}
+
+/********************************************************************/
+/* Kernel methods */
+/********************************************************************/
+
+void KMKernel::quit()
+{
+ // Called when all windows are closed. Will take care of compacting,
+ // sending... should handle session management too!!
+}
+ /* TODO later:
+ Asuming that:
+ - msgsender is nonblocking
+ (our own, QSocketNotifier based. Pops up errors and sends signal
+ senderFinished when done)
+
+ o If we are getting mail, stop it (but dont lose something!)
+ [Done already, see mailCheckAborted]
+ o If we are sending mail, go on UNLESS this was called by SM,
+ in which case stop ASAP that too (can we warn? should we continue
+ on next start?)
+ o If we are compacting, or expunging, go on UNLESS this was SM call.
+ In that case stop compacting ASAP and continue on next start, before
+ touching any folders. [Not needed anymore with CompactionJob]
+
+ KMKernel::quit ()
+ {
+ SM call?
+ if compacting, stop;
+ if sending, stop;
+ if receiving, stop;
+ Windows will take care of themselves (composer should dump
+ its messages, if any but not in deadMail)
+ declare us ready for the End of the Session
+
+ No, normal quit call
+ All windows are off. Anything to do, should compact or sender sends?
+ Yes, maybe put an icon in panel as a sign of life
+ if sender sending, connect us to his finished slot, declare us ready
+ for quit and wait for senderFinished
+ if not, Folder manager, go compact sent-mail and outbox
+} (= call slotFinished())
+
+void KMKernel::slotSenderFinished()
+{
+ good, Folder manager go compact sent-mail and outbox
+ clean up stage1 (release folders and config, unregister from dcop)
+ -- another kmail may start now ---
+ kapp->quit();
+}
+*/
+
+
+/********************************************************************/
+/* Init, Exit, and handler methods */
+/********************************************************************/
+void KMKernel::testDir(const char *_name)
+{
+ QString foldersPath = QDir::homeDirPath() + QString( _name );
+ QFileInfo info( foldersPath );
+ if ( !info.exists() ) {
+ if ( ::mkdir( QFile::encodeName( foldersPath ) , S_IRWXU ) == -1 ) {
+ KMessageBox::sorry(0, i18n("KMail could not create folder '%1';\n"
+ "please make sure that you can view and "
+ "modify the content of the folder '%2'.")
+ .arg( foldersPath ).arg( QDir::homeDirPath() ) );
+ ::exit(-1);
+ }
+ }
+ if ( !info.isDir() || !info.isReadable() || !info.isWritable() ) {
+ KMessageBox::sorry(0, i18n("The permissions of the folder '%1' are "
+ "incorrect;\n"
+ "please make sure that you can view and modify "
+ "the content of this folder.")
+ .arg( foldersPath ) );
+ ::exit(-1);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Open a composer for each message found in the dead.letter folder
+void KMKernel::recoverDeadLetters()
+{
+ const QString pathName = localDataPath();
+ QDir 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" );
+ 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();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMKernel::initFolders(KConfig* cfg)
+{
+ QString name;
+
+ name = cfg->readEntry("inboxFolder");
+
+ // Currently the folder manager cannot manage folders which are not
+ // in the base folder directory.
+ //if (name.isEmpty()) name = getenv("MAIL");
+
+ if (name.isEmpty()) name = I18N_NOOP("inbox");
+
+ the_inboxFolder = (KMFolder*)the_folderMgr->findOrCreate(name);
+
+ if (the_inboxFolder->canAccess() != 0) {
+ emergencyExit( i18n("You do not have read/write permission to your inbox folder.") );
+ }
+
+ the_inboxFolder->setSystemFolder(true);
+ if ( the_inboxFolder->userWhoField().isEmpty() )
+ the_inboxFolder->setUserWhoField( QString::null );
+ // inboxFolder->open();
+
+ the_outboxFolder = the_folderMgr->findOrCreate(cfg->readEntry("outboxFolder", I18N_NOOP("outbox")));
+ if (the_outboxFolder->canAccess() != 0) {
+ emergencyExit( i18n("You do not have read/write permission to your outbox folder.") );
+ }
+ the_outboxFolder->setNoChildren(true);
+
+ the_outboxFolder->setSystemFolder(true);
+ if ( the_outboxFolder->userWhoField().isEmpty() )
+ the_outboxFolder->setUserWhoField( QString::null );
+ /* Nuke the oubox's index file, to make sure that no ghost messages are in
+ * it from a previous crash. Ghost messages happen in the outbox because it
+ * the only folder where messages enter and leave within 5 seconds, which is
+ * the leniency period for index invalidation. Since the number of mails in
+ * this folder is expected to be very small, we can live with regenerating
+ * the index on each start to be on the save side. */
+ //if ( the_outboxFolder->folderType() == KMFolderTypeMaildir )
+ // unlink( QFile::encodeName( the_outboxFolder->indexLocation() ) );
+ the_outboxFolder->open("kmkernel");
+
+ the_sentFolder = the_folderMgr->findOrCreate(cfg->readEntry("sentFolder", I18N_NOOP("sent-mail")));
+ if (the_sentFolder->canAccess() != 0) {
+ emergencyExit( i18n("You do not have read/write permission to your sent-mail folder.") );
+ }
+ the_sentFolder->setSystemFolder(true);
+ if ( the_sentFolder->userWhoField().isEmpty() )
+ the_sentFolder->setUserWhoField( QString::null );
+ // the_sentFolder->open();
+
+ the_trashFolder = the_folderMgr->findOrCreate(cfg->readEntry("trashFolder", I18N_NOOP("trash")));
+ if (the_trashFolder->canAccess() != 0) {
+ emergencyExit( i18n("You do not have read/write permission to your trash folder.") );
+ }
+ the_trashFolder->setSystemFolder( true );
+ if ( the_trashFolder->userWhoField().isEmpty() )
+ the_trashFolder->setUserWhoField( QString::null );
+ // the_trashFolder->open();
+
+ the_draftsFolder = the_folderMgr->findOrCreate(cfg->readEntry("draftsFolder", I18N_NOOP("drafts")));
+ if (the_draftsFolder->canAccess() != 0) {
+ emergencyExit( i18n("You do not have read/write permission to your drafts folder.") );
+ }
+ the_draftsFolder->setSystemFolder( true );
+ if ( the_draftsFolder->userWhoField().isEmpty() )
+ the_draftsFolder->setUserWhoField( QString::null );
+ the_draftsFolder->open("kmkernel");
+
+ the_templatesFolder =
+ the_folderMgr->findOrCreate( cfg->readEntry( "templatesFolder",
+ I18N_NOOP("templates") ) );
+ if ( the_templatesFolder->canAccess() != 0 ) {
+ emergencyExit( i18n("You do not have read/write permission to your templates folder.") );
+ }
+ the_templatesFolder->setSystemFolder( true );
+ if ( the_templatesFolder->userWhoField().isEmpty() )
+ the_templatesFolder->setUserWhoField( QString::null );
+ the_templatesFolder->open("kmkernel");
+}
+
+
+void KMKernel::init()
+{
+ the_shuttingDown = false;
+ the_server_is_ready = false;
+
+ KConfig* cfg = KMKernel::config();
+
+ QDir dir;
+
+ KConfigGroupSaver saver(cfg, "General");
+ the_firstStart = cfg->readBoolEntry("first-start", true);
+ cfg->writeEntry("first-start", false);
+ the_previousVersion = cfg->readEntry("previous-version");
+ cfg->writeEntry("previous-version", KMAIL_VERSION);
+ QString foldersPath = cfg->readPathEntry( "folders" );
+ kdDebug(5006) << k_funcinfo << "foldersPath (from config): '" << foldersPath << "'" << endl;
+
+ if ( foldersPath.isEmpty() ) {
+ foldersPath = localDataPath() + "mail";
+ if ( transferMail( foldersPath ) ) {
+ cfg->writePathEntry( "folders", foldersPath );
+ }
+ kdDebug(5006) << k_funcinfo << "foldersPath (after transferMail): '" << foldersPath << "'" << endl;
+ }
+
+ // moved up here because KMMessage::stripOffPrefixes is used below
+ KMMessage::readConfig();
+
+ the_undoStack = new UndoStack(20);
+ the_folderMgr = new KMFolderMgr(foldersPath);
+ the_imapFolderMgr = new KMFolderMgr( KMFolderImap::cacheLocation(), KMImapDir);
+ the_dimapFolderMgr = new KMFolderMgr( KMFolderCachedImap::cacheLocation(), KMDImapDir);
+
+ the_searchFolderMgr = new KMFolderMgr(locateLocal("data","kmail/search"), KMSearchDir);
+ KMFolder *lsf = the_searchFolderMgr->find( i18n("Last Search") );
+ if (lsf)
+ the_searchFolderMgr->remove( lsf );
+
+ the_acctMgr = new AccountManager();
+ the_filterMgr = new KMFilterMgr();
+ the_popFilterMgr = new KMFilterMgr(true);
+ the_filterActionDict = new KMFilterActionDict;
+
+ initFolders(cfg);
+ the_acctMgr->readConfig();
+ the_filterMgr->readConfig();
+ the_popFilterMgr->readConfig();
+ cleanupImapFolders();
+
+ the_msgSender = new KMSender;
+ the_server_is_ready = true;
+ imProxy()->initialize();
+ { // area for config group "Composer"
+ KConfigGroupSaver saver(cfg, "Composer");
+ if (cfg->readListEntry("pref-charsets").isEmpty())
+ {
+ cfg->writeEntry("pref-charsets", "us-ascii,iso-8859-1,locale,utf-8");
+ }
+ }
+ readConfig();
+ mICalIface->readConfig();
+ // filterMgr->dump();
+#ifdef HAVE_INDEXLIB
+ the_msgIndex = new KMMsgIndex(this); //create the indexer
+#else
+ the_msgIndex = 0;
+#endif
+
+//#if 0
+ the_weaver = new KPIM::ThreadWeaver::Weaver( this );
+ the_weaverLogger = new KPIM::ThreadWeaver::WeaverThreadLogger(this);
+ the_weaverLogger->attach (the_weaver);
+//#endif
+
+ connect( the_folderMgr, SIGNAL( folderRemoved(KMFolder*) ),
+ this, SIGNAL( folderRemoved(KMFolder*) ) );
+ connect( the_dimapFolderMgr, SIGNAL( folderRemoved(KMFolder*) ),
+ this, SIGNAL( folderRemoved(KMFolder*) ) );
+ connect( the_imapFolderMgr, SIGNAL( folderRemoved(KMFolder*) ),
+ this, SIGNAL( folderRemoved(KMFolder*) ) );
+ connect( the_searchFolderMgr, SIGNAL( folderRemoved(KMFolder*) ),
+ this, SIGNAL( folderRemoved(KMFolder*) ) );
+
+ mBackgroundTasksTimer = new QTimer( this, "mBackgroundTasksTimer" );
+ connect( mBackgroundTasksTimer, SIGNAL( timeout() ), this, SLOT( slotRunBackgroundTasks() ) );
+#ifdef DEBUG_SCHEDULER // for debugging, see jobscheduler.h
+ mBackgroundTasksTimer->start( 10000, true ); // 10s, singleshot
+#else
+ mBackgroundTasksTimer->start( 5 * 60000, true ); // 5 minutes, singleshot
+#endif
+}
+
+void KMKernel::readConfig()
+{
+ //Needed here, since this function is also called when the configuration
+ //changes, and the static variables should be updated then - IOF
+ KMMessage::readConfig();
+}
+
+void KMKernel::cleanupImapFolders()
+{
+ KMAccount *acct = 0;
+ KMFolderNode *node = the_imapFolderMgr->dir().first();
+ while (node)
+ {
+ if (node->isDir() || ((acct = the_acctMgr->find(node->id()))
+ && ( acct->type() == "imap" )) )
+ {
+ node = the_imapFolderMgr->dir().next();
+ } else {
+ KMFolder* folder = static_cast<KMFolder*>(node);
+ // delete only local
+ static_cast<KMFolderImap*>( folder->storage() )->setAlreadyRemoved( true );
+ the_imapFolderMgr->remove(folder);
+ node = the_imapFolderMgr->dir().first();
+ }
+ }
+
+ node = the_dimapFolderMgr->dir().first();
+ while (node)
+ {
+ if (node->isDir() || ((acct = the_acctMgr->find(node->id()))
+ && ( acct->type() == "cachedimap" )) )
+ {
+ node = the_dimapFolderMgr->dir().next();
+ } else {
+ the_dimapFolderMgr->remove(static_cast<KMFolder*>(node));
+ node = the_dimapFolderMgr->dir().first();
+ }
+ }
+
+ the_imapFolderMgr->quiet(true);
+ for (acct = the_acctMgr->first(); acct; acct = the_acctMgr->next())
+ {
+ KMFolderImap *fld;
+ KMAcctImap *imapAcct;
+
+ if (acct->type() != "imap") continue;
+ fld = static_cast<KMFolderImap*>(the_imapFolderMgr
+ ->findOrCreate(QString::number(acct->id()), false, acct->id())->storage());
+ fld->setNoContent(true);
+ fld->folder()->setLabel(acct->name());
+ imapAcct = static_cast<KMAcctImap*>(acct);
+ fld->setAccount(imapAcct);
+ imapAcct->setImapFolder(fld);
+ fld->close( "kernel", true );
+ }
+ the_imapFolderMgr->quiet(false);
+
+ the_dimapFolderMgr->quiet( true );
+ for (acct = the_acctMgr->first(); acct; acct = the_acctMgr->next())
+ {
+ KMFolderCachedImap *cfld = 0;
+ KMAcctCachedImap *cachedImapAcct;
+
+ if (acct->type() != "cachedimap" ) continue;
+
+ KMFolder* fld = the_dimapFolderMgr->find(QString::number(acct->id()));
+ if( fld )
+ cfld = static_cast<KMFolderCachedImap*>( fld->storage() );
+ if (cfld == 0) {
+ // Folder doesn't exist yet
+ cfld = static_cast<KMFolderCachedImap*>(the_dimapFolderMgr->createFolder(QString::number(acct->id()),
+ false, KMFolderTypeCachedImap)->storage());
+ if (!cfld) {
+ KMessageBox::error(0,(i18n("Cannot create file `%1' in %2.\nKMail cannot start without it.").arg(acct->name()).arg(the_dimapFolderMgr->basePath())));
+ exit(-1);
+ }
+ cfld->folder()->setId( acct->id() );
+ }
+
+ cfld->setNoContent(true);
+ cfld->folder()->setLabel(acct->name());
+ cachedImapAcct = static_cast<KMAcctCachedImap*>(acct);
+ cfld->setAccount(cachedImapAcct);
+ cachedImapAcct->setImapFolder(cfld);
+ cfld->close("kmkernel");
+ }
+ the_dimapFolderMgr->quiet( false );
+}
+
+bool KMKernel::doSessionManagement()
+{
+
+ // Do session management
+ if (kapp->isRestored()){
+ int n = 1;
+ while (KMMainWin::canBeRestored(n)){
+ //only restore main windows! (Matthias);
+ if (KMMainWin::classNameOfToplevel(n) == "KMMainWin")
+ (new KMMainWin)->restore(n);
+ n++;
+ }
+ return true; // we were restored by SM
+ }
+ return false; // no, we were not restored
+}
+
+void KMKernel::closeAllKMailWindows()
+{
+ if (!KMainWindow::memberList) return;
+ QPtrListIterator<KMainWindow> it(*KMainWindow::memberList);
+ KMainWindow *window = 0;
+ while ((window = it.current()) != 0) {
+ ++it;
+ if (window->isA("KMMainWindow") ||
+ window->inherits("KMail::SecondaryWindow"))
+ window->close( true ); // close and delete the window
+ }
+}
+
+void KMKernel::cleanup(void)
+{
+ dumpDeadLetters();
+ the_shuttingDown = true;
+ closeAllKMailWindows();
+
+ delete the_acctMgr;
+ the_acctMgr = 0;
+ delete the_filterMgr;
+ the_filterMgr = 0;
+ delete the_msgSender;
+ the_msgSender = 0;
+ delete the_filterActionDict;
+ the_filterActionDict = 0;
+ delete the_undoStack;
+ the_undoStack = 0;
+ delete the_popFilterMgr;
+ the_popFilterMgr = 0;
+
+#if 0
+ delete the_weaver;
+ the_weaver = 0;
+#endif
+
+ KConfig* config = KMKernel::config();
+ KConfigGroupSaver saver(config, "General");
+
+ if (the_trashFolder) {
+
+ the_trashFolder->close("kmkernel", true);
+
+ if (config->readBoolEntry("empty-trash-on-exit", true))
+ {
+ if ( the_trashFolder->count( true ) > 0 )
+ the_trashFolder->expunge();
+ }
+ }
+
+ mICalIface->cleanup();
+
+ QValueList<QGuardedPtr<KMFolder> > folders;
+ QStringList strList;
+ KMFolder *folder;
+ the_folderMgr->createFolderList(&strList, &folders);
+ for (int i = 0; folders.at(i) != folders.end(); i++)
+ {
+ folder = *folders.at(i);
+ if (!folder || folder->isDir()) continue;
+ folder->close("kmkernel", true);
+ }
+ strList.clear();
+ folders.clear();
+ the_searchFolderMgr->createFolderList(&strList, &folders);
+ for (int i = 0; folders.at(i) != folders.end(); i++)
+ {
+ folder = *folders.at(i);
+ if (!folder || folder->isDir()) continue;
+ folder->close("kmkernel", true);
+ }
+
+ delete the_msgIndex;
+ the_msgIndex = 0;
+ delete the_folderMgr;
+ the_folderMgr = 0;
+ delete the_imapFolderMgr;
+ the_imapFolderMgr = 0;
+ delete the_dimapFolderMgr;
+ the_dimapFolderMgr = 0;
+ delete the_searchFolderMgr;
+ the_searchFolderMgr = 0;
+ delete mConfigureDialog;
+ mConfigureDialog = 0;
+ // do not delete, because mWin may point to an existing window
+ // delete mWin;
+ mWin = 0;
+
+ if ( RecentAddresses::exists() )
+ RecentAddresses::self( config )->save( config );
+ config->sync();
+}
+
+bool KMKernel::transferMail( QString & destinationDir )
+{
+ QString dir;
+
+ // check whether the user has a ~/KMail folder
+ QFileInfo fi( QDir::home(), "KMail" );
+ if ( fi.exists() && fi.isDir() ) {
+ dir = QDir::homeDirPath() + "/KMail";
+ // the following two lines can be removed once moving mail is reactivated
+ destinationDir = dir;
+ return true;
+ }
+
+ if ( dir.isEmpty() ) {
+ // check whether the user has a ~/Mail folder
+ fi.setFile( QDir::home(), "Mail" );
+ if ( fi.exists() && fi.isDir() &&
+ QFile::exists( QDir::homeDirPath() + "/Mail/.inbox.index" ) ) {
+ // there's a ~/Mail folder which seems to be used by KMail (because of the
+ // index file)
+ dir = QDir::homeDirPath() + "/Mail";
+ // the following two lines can be removed once moving mail is reactivated
+ destinationDir = dir;
+ return true;
+ }
+ }
+
+ if ( dir.isEmpty() ) {
+ return true; // there's no old mail folder
+ }
+
+#if 0
+ // disabled for now since moving fails in certain cases (e.g. if symbolic links are involved)
+ const QString kmailName = kapp->aboutData()->programName();
+ QString msg;
+ if ( KIO::NetAccess::exists( destinationDir, true, 0 ) ) {
+ // if destinationDir exists, we need to warn about possible
+ // overwriting of files. otherwise, we don't have to
+ msg = i18n( "%1-%3 is the application name, %4-%7 are folder path",
+ "<qt>The <i>%4</i> folder exists. "
+ "%1 now uses the <i>%5</i> folder for "
+ "its messages.<p>"
+ "%2 can move the contents of <i>%6<i> into this folder for "
+ "you, though this may replace any existing files with "
+ "the same name in <i>%7</i>.<p>"
+ "<strong>Would you like %3 to move the mail "
+ "files now?</strong></qt>" )
+ .arg( kmailName, kmailName, kmailName )
+ .arg( dir, destinationDir, dir, destinationDir );
+ } else {
+ msg = i18n( "%1-%3 is the application name, %4-%6 are folder path",
+ "<qt>The <i>%4</i> folder exists. "
+ "%1 now uses the <i>%5</i> folder for "
+ "its messages. %2 can move the contents of <i>%6</i> into "
+ "this folder for you.<p>"
+ "<strong>Would you like %3 to move the mail "
+ "files now?</strong></qt>" )
+ .arg( kmailName, kmailName, kmailName )
+ .arg( dir, destinationDir, dir );
+ }
+ QString title = i18n( "Migrate Mail Files?" );
+ QString buttonText = i18n( "Move" );
+
+ if ( KMessageBox::questionYesNo( 0, msg, title, buttonText, i18n("Do Not Move") ) ==
+ KMessageBox::No ) {
+ destinationDir = dir;
+ return true;
+ }
+
+ if ( !KIO::NetAccess::move( dir, destinationDir ) ) {
+ kdDebug(5006) << k_funcinfo << "Moving " << dir << " to " << destinationDir << " failed: " << KIO::NetAccess::lastErrorString() << endl;
+ kdDebug(5006) << k_funcinfo << "Deleting " << destinationDir << endl;
+ KIO::NetAccess::del( destinationDir, 0 );
+ destinationDir = dir;
+ return false;
+ }
+#endif
+
+ return true;
+}
+
+
+void KMKernel::ungrabPtrKb(void)
+{
+ if(!KMainWindow::memberList) return;
+ QWidget* widg = KMainWindow::memberList->first();
+ Display* dpy;
+
+ if (!widg) return;
+ dpy = widg->x11Display();
+ XUngrabKeyboard(dpy, CurrentTime);
+ XUngrabPointer(dpy, CurrentTime);
+}
+
+
+// Message handler
+void KMKernel::kmailMsgHandler(QtMsgType aType, const char* aMsg)
+{
+ static int recurse=-1;
+
+ recurse++;
+
+ switch (aType)
+ {
+ case QtDebugMsg:
+ case QtWarningMsg:
+ kdDebug(5006) << aMsg << endl;
+ break;
+
+ case QtFatalMsg: // Hm, what about using kdFatal() here?
+ ungrabPtrKb();
+ kdDebug(5006) << kapp->caption() << " fatal error "
+ << aMsg << endl;
+ KMessageBox::error(0, aMsg);
+ abort();
+ }
+
+ recurse--;
+}
+
+
+void KMKernel::dumpDeadLetters()
+{
+ if ( shuttingDown() )
+ return; //All documents should be saved before shutting down is set!
+
+ // make all composer windows autosave their contents
+ if ( !KMainWindow::memberList )
+ return;
+
+ for ( QPtrListIterator<KMainWindow> it(*KMainWindow::memberList) ; it.current() != 0; ++it )
+ if ( KMail::Composer * win = ::qt_cast<KMail::Composer*>( it.current() ) )
+ win->autoSaveMessage();
+}
+
+
+
+void KMKernel::action(bool mailto, bool check, const QString &to,
+ const QString &cc, const QString &bcc,
+ const QString &subj, const QString &body,
+ const KURL &messageFile,
+ const KURL::List &attachURLs,
+ const QCStringList &customHeaders)
+{
+ if ( mailto )
+ openComposer( to, cc, bcc, subj, body, 0, messageFile, attachURLs, customHeaders );
+ else
+ openReader( check );
+
+ if ( check )
+ checkMail();
+ //Anything else?
+}
+
+void KMKernel::byteArrayToRemoteFile(const QByteArray &aData, const KURL &aURL,
+ bool overwrite)
+{
+ // ## when KDE 3.3 is out: use KIO::storedPut to remove slotDataReq altogether
+ KIO::Job *job = KIO::put(aURL, -1, overwrite, false);
+ putData pd; pd.url = aURL; pd.data = aData; pd.offset = 0;
+ mPutJobs.insert(job, pd);
+ connect(job, SIGNAL(dataReq(KIO::Job*,QByteArray&)),
+ SLOT(slotDataReq(KIO::Job*,QByteArray&)));
+ connect(job, SIGNAL(result(KIO::Job*)),
+ SLOT(slotResult(KIO::Job*)));
+}
+
+void KMKernel::slotDataReq(KIO::Job *job, QByteArray &data)
+{
+ // send the data in 64 KB chunks
+ const int MAX_CHUNK_SIZE = 64*1024;
+ QMap<KIO::Job*, putData>::Iterator it = mPutJobs.find(job);
+ assert(it != mPutJobs.end());
+ int remainingBytes = (*it).data.size() - (*it).offset;
+ if( remainingBytes > MAX_CHUNK_SIZE )
+ {
+ // send MAX_CHUNK_SIZE bytes to the receiver (deep copy)
+ data.duplicate( (*it).data.data() + (*it).offset, MAX_CHUNK_SIZE );
+ (*it).offset += MAX_CHUNK_SIZE;
+ //kdDebug( 5006 ) << "Sending " << MAX_CHUNK_SIZE << " bytes ("
+ // << remainingBytes - MAX_CHUNK_SIZE << " bytes remain)\n";
+ }
+ else
+ {
+ // send the remaining bytes to the receiver (deep copy)
+ data.duplicate( (*it).data.data() + (*it).offset, remainingBytes );
+ (*it).data = QByteArray();
+ (*it).offset = 0;
+ //kdDebug( 5006 ) << "Sending " << remainingBytes << " bytes\n";
+ }
+}
+
+void KMKernel::slotResult(KIO::Job *job)
+{
+ QMap<KIO::Job*, putData>::Iterator it = mPutJobs.find(job);
+ assert(it != mPutJobs.end());
+ if (job->error())
+ {
+ if (job->error() == KIO::ERR_FILE_ALREADY_EXIST)
+ {
+ if (KMessageBox::warningContinueCancel(0,
+ i18n("File %1 exists.\nDo you want to replace it?")
+ .arg((*it).url.prettyURL()), i18n("Save to File"), i18n("&Replace"))
+ == KMessageBox::Continue)
+ byteArrayToRemoteFile((*it).data, (*it).url, true);
+ }
+ else job->showErrorDialog();
+ }
+ mPutJobs.remove(it);
+}
+
+void KMKernel::slotRequestConfigSync() {
+ // ### FIXME: delay as promised in the kdoc of this function ;-)
+ KMKernel::config()->sync();
+}
+
+void KMKernel::slotShowConfigurationDialog()
+{
+ if( !mConfigureDialog ) {
+ mConfigureDialog = new ConfigureDialog( 0, "configure", false );
+ connect( mConfigureDialog, SIGNAL( configCommitted() ),
+ this, SLOT( slotConfigChanged() ) );
+ }
+
+ if( KMKernel::getKMMainWidget() == 0 )
+ {
+ // ensure that there is a main widget available
+ // as parts of the configure dialog (identity) rely on this
+ // and this slot can be called when there is only a KMComposeWin showing
+ KMMainWin * win = new KMMainWin;
+ win->show();
+ }
+
+ if( mConfigureDialog->isHidden() )
+ mConfigureDialog->show();
+ else
+ mConfigureDialog->raise();
+}
+
+void KMKernel::slotConfigChanged()
+{
+ readConfig();
+ emit configChanged();
+}
+
+//-------------------------------------------------------------------------------
+//static
+QString KMKernel::localDataPath()
+{
+ return locateLocal( "data", "kmail/" );
+}
+
+//-------------------------------------------------------------------------------
+
+bool KMKernel::haveSystemTrayApplet()
+{
+ return !systemTrayApplets.isEmpty();
+}
+
+bool KMKernel::registerSystemTrayApplet( const KSystemTray* applet )
+{
+ if ( systemTrayApplets.findIndex( applet ) == -1 ) {
+ systemTrayApplets.append( applet );
+ return true;
+ }
+ else
+ return false;
+}
+
+bool KMKernel::unregisterSystemTrayApplet( const KSystemTray* applet )
+{
+ QValueList<const KSystemTray*>::iterator it =
+ systemTrayApplets.find( applet );
+ if ( it != systemTrayApplets.end() ) {
+ systemTrayApplets.remove( it );
+ return true;
+ }
+ else
+ return false;
+}
+
+void KMKernel::emergencyExit( const QString& reason )
+{
+ QString mesg;
+ if ( reason.length() == 0 ) {
+ mesg = i18n("KMail encountered a fatal error and will terminate now");
+ } else {
+ mesg = i18n("KMail encountered a fatal error and will "
+ "terminate now.\nThe error was:\n%1").arg( reason );
+ }
+
+ kdWarning() << mesg << endl;
+ KNotifyClient::userEvent( 0, "<qt>"+mesg+"</qt>", KNotifyClient::Messagebox, KNotifyClient::Error );
+
+ ::exit(1);
+}
+
+/**
+ * Returns true if the folder is either the outbox or one of the drafts-folders
+ */
+bool KMKernel::folderIsDraftOrOutbox(const KMFolder * folder)
+{
+ assert( folder );
+ if ( folder == the_outboxFolder )
+ return true;
+ return folderIsDrafts( folder );
+}
+
+bool KMKernel::folderIsDrafts(const KMFolder * folder)
+{
+ assert( folder );
+ if ( folder == the_draftsFolder )
+ return true;
+
+ QString idString = folder->idString();
+ if ( idString.isEmpty() )
+ return false;
+
+ // search the identities if the folder matches the drafts-folder
+ const KPIM::IdentityManager *im = identityManager();
+ for ( KPIM::IdentityManager::ConstIterator it=im->begin(); it != im->end(); ++it )
+ if ( (*it).drafts() == idString )
+ return true;
+ return false;
+}
+
+bool KMKernel::folderIsTemplates( const KMFolder *folder )
+{
+ assert( folder );
+ if ( folder == the_templatesFolder )
+ return true;
+
+ QString idString = folder->idString();
+ if ( idString.isEmpty() )
+ return false;
+
+ // search the identities if the folder matches the templates-folder
+ const KPIM::IdentityManager *im = identityManager();
+ for ( KPIM::IdentityManager::ConstIterator it=im->begin(); it != im->end(); ++it )
+ if ( (*it).templates() == idString )
+ return true;
+ return false;
+}
+
+bool KMKernel::folderIsTrash(KMFolder * folder)
+{
+ assert(folder);
+ if (folder == the_trashFolder) return true;
+ QStringList actList = acctMgr()->getAccounts();
+ QStringList::Iterator it( actList.begin() );
+ for( ; it != actList.end() ; ++it ) {
+ KMAccount* act = acctMgr()->findByName( *it );
+ if ( act && ( act->trash() == folder->idString() ) )
+ return true;
+ }
+ return false;
+}
+
+bool KMKernel::folderIsSentMailFolder( const KMFolder * folder )
+{
+ assert( folder );
+ if ( folder == the_sentFolder )
+ return true;
+
+ QString idString = folder->idString();
+ if ( idString.isEmpty() ) return false;
+
+ // search the identities if the folder matches the sent-folder
+ const KPIM::IdentityManager * im = identityManager();
+ for( KPIM::IdentityManager::ConstIterator it = im->begin(); it != im->end(); ++it )
+ if ( (*it).fcc() == idString ) return true;
+ return false;
+}
+
+KPIM::IdentityManager * KMKernel::identityManager() {
+ if ( !mIdentityManager ) {
+ kdDebug(5006) << "instantating KPIM::IdentityManager" << endl;
+ mIdentityManager = new KPIM::IdentityManager( false, this, "mIdentityManager" );
+ }
+ return mIdentityManager;
+}
+
+KMMsgIndex *KMKernel::msgIndex()
+{
+ return the_msgIndex;
+}
+
+KMainWindow* KMKernel::mainWin()
+{
+ if (KMainWindow::memberList) {
+ KMainWindow *kmWin = 0;
+
+ // First look for a KMMainWin.
+ for (kmWin = KMainWindow::memberList->first(); kmWin;
+ kmWin = KMainWindow::memberList->next())
+ if (kmWin->isA("KMMainWin"))
+ return kmWin;
+
+ // There is no KMMainWin. Use any other KMainWindow instead (e.g. in
+ // case we are running inside Kontact) because we anyway only need
+ // it for modal message boxes and for KNotify events.
+ kmWin = KMainWindow::memberList->first();
+ if ( kmWin )
+ return kmWin;
+ }
+
+ // There's not a single KMainWindow. Create a KMMainWin.
+ // This could happen if we want to pop up an error message
+ // while we are still doing the startup wizard and no other
+ // KMainWindow is running.
+ mWin = new KMMainWin;
+ return mWin;
+}
+
+
+/**
+ * Empties all trash folders
+ */
+void KMKernel::slotEmptyTrash()
+{
+ QString title = i18n("Empty Trash");
+ QString text = i18n("Are you sure you want to empty the trash folders of all accounts?");
+ if (KMessageBox::warningContinueCancel(0, text, title,
+ KStdGuiItem::cont(), "confirm_empty_trash")
+ != KMessageBox::Continue)
+ {
+ return;
+ }
+
+ for (KMAccount* acct = acctMgr()->first(); acct; acct = acctMgr()->next())
+ {
+ KMFolder* trash = findFolderById(acct->trash());
+ if (trash)
+ {
+ trash->expunge();
+ }
+ }
+}
+
+KConfig* KMKernel::config()
+{
+ assert(mySelf);
+ if (!mySelf->mConfig)
+ {
+ mySelf->mConfig = KSharedConfig::openConfig( "kmailrc" );
+ // Check that all updates have been run on the config file:
+ KMail::checkConfigUpdates();
+ }
+ return mySelf->mConfig;
+}
+
+KMailICalIfaceImpl& KMKernel::iCalIface()
+{
+ assert( mICalIface );
+ return *mICalIface;
+}
+
+void KMKernel::selectFolder( QString folderPath )
+{
+ kdDebug(5006)<<"Selecting a folder "<<folderPath<<endl;
+ const QString localPrefix = "/Local";
+ KMFolder *folder = kmkernel->folderMgr()->getFolderByURL( folderPath );
+ if ( !folder && folderPath.startsWith( localPrefix ) )
+ folder = the_folderMgr->getFolderByURL( folderPath.mid( localPrefix.length() ) );
+ if ( !folder )
+ folder = kmkernel->imapFolderMgr()->getFolderByURL( folderPath );
+ if ( !folder )
+ folder = kmkernel->dimapFolderMgr()->getFolderByURL( folderPath );
+ Q_ASSERT( folder );
+
+ KMMainWidget *widget = getKMMainWidget();
+ Q_ASSERT( widget );
+ if ( !widget )
+ return;
+
+ KMFolderTree *tree = widget->folderTree();
+ tree->doFolderSelected( tree->indexOfFolder( folder ) );
+ tree->ensureItemVisible( tree->indexOfFolder( folder ) );
+}
+
+KMMainWidget *KMKernel::getKMMainWidget()
+{
+ //This could definitely use a speadup
+ QWidgetList *l = kapp->topLevelWidgets();
+ QWidgetListIt it( *l );
+ QWidget *wid;
+
+ while ( ( wid = it.current() ) != 0 ) {
+ ++it;
+ QObjectList *l2 = wid->topLevelWidget()->queryList( "KMMainWidget" );
+ if (l2 && l2->first()) {
+ KMMainWidget* kmmw = dynamic_cast<KMMainWidget *>( l2->first() );
+ Q_ASSERT( kmmw );
+ delete l2;
+ delete l;
+ return kmmw;
+ }
+ delete l2;
+ }
+ delete l;
+ return 0;
+}
+
+void KMKernel::slotRunBackgroundTasks() // called regularly by timer
+{
+ // Hidden KConfig keys. Not meant to be used, but a nice fallback in case
+ // a stable kmail release goes out with a nasty bug in CompactionJob...
+ KConfigGroup generalGroup( config(), "General" );
+
+ if ( generalGroup.readBoolEntry( "auto-expiring", true ) ) {
+ the_folderMgr->expireAllFolders( false /*scheduled, not immediate*/ );
+ the_imapFolderMgr->expireAllFolders( false /*scheduled, not immediate*/ );
+ the_dimapFolderMgr->expireAllFolders( false /*scheduled, not immediate*/ );
+ // the_searchFolderMgr: no expiry there
+ }
+
+ if ( generalGroup.readBoolEntry( "auto-compaction", true ) ) {
+ the_folderMgr->compactAllFolders( false /*scheduled, not immediate*/ );
+ // the_imapFolderMgr: no compaction
+ the_dimapFolderMgr->compactAllFolders( false /*scheduled, not immediate*/ );
+ // the_searchFolderMgr: no compaction
+ }
+
+#ifdef DEBUG_SCHEDULER // for debugging, see jobscheduler.h
+ mBackgroundTasksTimer->start( 60 * 1000, true ); // check again in 1 minute
+#else
+ mBackgroundTasksTimer->start( 4 * 60 * 60 * 1000, true ); // check again in 4 hours
+#endif
+
+}
+
+void KMKernel::expireAllFoldersNow() // called by the GUI
+{
+ the_folderMgr->expireAllFolders( true /*immediate*/ );
+ the_imapFolderMgr->expireAllFolders( true /*immediate*/ );
+ the_dimapFolderMgr->expireAllFolders( true /*immediate*/ );
+}
+
+void KMKernel::compactAllFolders() // called by the GUI
+{
+ the_folderMgr->compactAllFolders( true /*immediate*/ );
+ //the_imapFolderMgr->compactAllFolders( true /*immediate*/ );
+ the_dimapFolderMgr->compactAllFolders( true /*immediate*/ );
+}
+
+KMFolder* KMKernel::findFolderById( const QString& idString )
+{
+ KMFolder * folder = the_folderMgr->findIdString( idString );
+ if ( !folder )
+ folder = the_imapFolderMgr->findIdString( idString );
+ if ( !folder )
+ folder = the_dimapFolderMgr->findIdString( idString );
+ if ( !folder )
+ folder = the_searchFolderMgr->findIdString( idString );
+ return folder;
+}
+
+::KIMProxy* KMKernel::imProxy()
+{
+ return KIMProxy::instance( kapp->dcopClient() );
+}
+
+void KMKernel::enableMailCheck()
+{
+ mMailCheckAborted = false;
+}
+
+bool KMKernel::mailCheckAborted() const
+{
+ return mMailCheckAborted;
+}
+
+void KMKernel::abortMailCheck()
+{
+ mMailCheckAborted = true;
+}
+
+bool KMKernel::canQueryClose()
+{
+ if ( KMMainWidget::mainWidgetList() &&
+ KMMainWidget::mainWidgetList()->count() > 1 )
+ return true;
+ KMMainWidget *widget = getKMMainWidget();
+ if ( !widget )
+ return true;
+ KMSystemTray* systray = widget->systray();
+ if ( !systray || GlobalSettings::closeDespiteSystemTray() )
+ return true;
+ if ( systray->mode() == GlobalSettings::EnumSystemTrayPolicy::ShowAlways ) {
+ systray->hideKMail();
+ return false;
+ } else if ( systray->mode() == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ) {
+ systray->show();
+ systray->hideKMail();
+ return false;
+ }
+ return true;
+}
+
+void KMKernel::messageCountChanged()
+{
+ mTimeOfLastMessageCountChange = ::time( 0 );
+}
+
+int KMKernel::timeOfLastMessageCountChange() const
+{
+ return mTimeOfLastMessageCountChange;
+}
+
+Wallet *KMKernel::wallet() {
+ static bool walletOpenFailed = false;
+ if ( mWallet && mWallet->isOpen() )
+ return mWallet;
+
+ if ( !Wallet::isEnabled() || walletOpenFailed )
+ return 0;
+
+ // find an appropriate parent window for the wallet dialog
+ WId window = 0;
+ if ( qApp->activeWindow() )
+ window = qApp->activeWindow()->winId();
+ else if ( getKMMainWidget() )
+ window = getKMMainWidget()->topLevelWidget()->winId();
+
+ delete mWallet;
+ mWallet = Wallet::openWallet( Wallet::NetworkWallet(), window );
+
+ if ( !mWallet ) {
+ walletOpenFailed = true;
+ return 0;
+ }
+
+ if ( !mWallet->hasFolder( "kmail" ) )
+ mWallet->createFolder( "kmail" );
+ mWallet->setFolder( "kmail" );
+ return mWallet;
+}
+
+QValueList< QGuardedPtr<KMFolder> > KMKernel::allFolders()
+{
+ QStringList names;
+ QValueList<QGuardedPtr<KMFolder> > folders;
+ folderMgr()->createFolderList(&names, &folders);
+ imapFolderMgr()->createFolderList(&names, &folders);
+ dimapFolderMgr()->createFolderList(&names, &folders);
+ searchFolderMgr()->createFolderList(&names, &folders);
+
+ return folders;
+}
+
+KMFolder *KMKernel::currentFolder() {
+ KMMainWidget *widget = getKMMainWidget();
+ KMFolder *folder = 0;
+ if ( widget && widget->folderTree() ) {
+ folder = widget->folderTree()->currentFolder();
+ }
+ return folder;
+}
+
+// can't be inline, since KMSender isn't known to implement
+// KMail::MessageSender outside this .cpp file
+KMail::MessageSender * KMKernel::msgSender() { return the_msgSender; }
+
+#include "kmkernel.moc"
diff --git a/kmail/kmkernel.h b/kmail/kmkernel.h
new file mode 100644
index 00000000..cd30a6e2
--- /dev/null
+++ b/kmail/kmkernel.h
@@ -0,0 +1,504 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+
+#ifndef _KMCONTROL
+#define _KMCONTROL
+
+#include <qobject.h>
+#include <qstring.h>
+#include <qguardedptr.h>
+#include <weaver.h>
+#include <weaverlogger.h>
+
+#include <kconfig.h>
+#include <kdeversion.h>
+#include <kimproxy.h>
+#include <kdepimmacros.h>
+
+#include "kmailIface.h"
+#include "kmmsgbase.h"
+#include "globalsettings.h"
+
+#define kmkernel KMKernel::self()
+#define kmconfig KMKernel::config()
+
+namespace KIO {
+ class Job;
+}
+namespace KWallet {
+ class Wallet;
+}
+
+/** The KMail namespace contains classes used for KMail.
+* This is to keep them out of the way from all the other
+* un-namespaced classes in libs and the rest of PIM.
+*/
+namespace KMail {
+ class MailServiceImpl;
+ class UndoStack;
+ class JobScheduler;
+ class MessageSender;
+ class AccountManager;
+}
+namespace KPIM { class ProgressDialog; }
+using KMail::MailServiceImpl;
+using KMail::AccountManager;
+using KMail::UndoStack;
+using KMail::JobScheduler;
+using KPIM::ProgressDialog;
+class KMMsgIndex;
+class QLabel;
+class KMFolder;
+class KMFolderMgr;
+class KMFilterMgr;
+class KMFilterActionDict;
+class KMSender;
+namespace KPIM {
+ class Identity;
+ class IdentityManager;
+}
+class KMKernel;
+class KProcess;
+class KProgressDialog;
+class KInstance;
+class QTimer;
+class KProgress;
+class KPassivePopup;
+class KMMainWin;
+class KMainWindow;
+class KMailICalIfaceImpl;
+class KMReaderWin;
+class KSystemTray;
+class KMMainWidget;
+class ConfigureDialog;
+
+/**
+ * @short Central point of coordination in KMail
+ *
+ * The KMKernel class represents the core of KMail, where the different parts
+ * come together and are coordinated. It is currently also the class which exports
+ * KMail's main DCOP interfaces. The kernel is responsible for creating various
+ * (singleton) objects such as the UndoStack, the folder managers and filter
+ * manager, etc.
+ */
+class KDE_EXPORT KMKernel : public QObject, virtual public KMailIface
+{
+ Q_OBJECT
+
+public:
+ KMKernel (QObject *parent=0, const char *name=0);
+ ~KMKernel ();
+
+ /** dcop callable stuff */
+
+ void checkMail ();
+ QStringList accounts();
+ void checkAccount (const QString &account);
+ /** returns id of composer if more are opened */
+ int openComposer (const QString &to, const QString &cc, const QString &bcc,
+ const QString &subject, const QString &body, int hidden,
+ const KURL &messageFile, const KURL::List &attachURLs,
+ const QCStringList &customHeaders);
+ /** For backward compatibility */
+ int openComposer (const QString &to, const QString &cc, const QString &bcc,
+ const QString &subject, const QString &body, int hidden,
+ const KURL &messageFile, const KURL::List &attachURLs)
+ {
+ QCStringList noCustomHeaders;
+ return openComposer(to, cc, bcc, subject, body, hidden, messageFile, attachURLs, noCustomHeaders);
+ }
+ /** For backward compatibility */
+ int openComposer (const QString &to, const QString &cc, const QString &bcc,
+ const QString &subject, const QString &body, int hidden,
+ const KURL &messageFile, const KURL& attachURL)
+ {
+ return openComposer(to, cc, bcc, subject, body, hidden, messageFile, KURL::List(attachURL));
+ }
+ /** For backward compatibility */
+ int openComposer (const QString &to, const QString &cc, const QString &bcc,
+ const QString &subject, const QString &body, int hidden,
+ const KURL &messageFile)
+ {
+ return openComposer(to, cc, bcc, subject, body, hidden, messageFile, KURL::List());
+ }
+ /** For backward compatibility
+ * @deprecated
+ */
+ int openComposer (const QString &to, const QString &cc,
+ const QString &bcc, const QString &subject,
+ const QString &body, int hidden,
+ const QString &attachName,
+ const QCString &attachCte,
+ const QCString &attachData,
+ const QCString &attachType,
+ const QCString &attachSubType,
+ const QCString &attachParamAttr,
+ const QString &attachParamValue,
+ const QCString &attachContDisp);
+
+ int openComposer (const QString &to, const QString &cc,
+ const QString &bcc, const QString &subject,
+ const QString &body, int hidden,
+ const QString &attachName,
+ const QCString &attachCte,
+ const QCString &attachData,
+ const QCString &attachType,
+ const QCString &attachSubType,
+ const QCString &attachParamAttr,
+ const QString &attachParamValue,
+ const QCString &attachContDisp,
+ const QCString &attachCharset);
+
+ DCOPRef openComposer(const QString &to, const QString &cc,
+ const QString &bcc, const QString &subject,
+ const QString &body,bool hidden);
+
+ /** DCOP call used to set the default transport. */
+
+ void setDefaultTransport( const QString & transport );
+
+ /** DCOP call used by the Kontact plugin to create a new message. */
+ DCOPRef newMessage(const QString &to,
+ const QString &cc,
+ const QString &bcc,
+ bool hidden,
+ bool useFolderId,
+ const KURL &messageFile,
+ const KURL &attachURL);
+
+ int sendCertificate( const QString& to, const QByteArray& certData );
+
+ void openReader() { openReader( false ); }
+
+ int dcopAddMessage(const QString & foldername, const QString & messagefile,
+ const QString & MsgStatusFlags = QString());
+ int dcopAddMessage(const QString & foldername, const KURL & messagefile,
+ const QString & MsgStatusFlags = QString());
+ void dcopResetAddMessage();
+ /** add messages without rejecting duplicates */
+ int dcopAddMessage_fastImport(const QString & foldername, const QString & messagefile,
+ const QString & MsgStatusFlags = QString());
+ int dcopAddMessage_fastImport(const QString & foldername, const KURL & messagefile,
+ const QString & MsgStatusFlags = QString());
+
+ QStringList folderList() const;
+ DCOPRef getFolder( const QString& vpath );
+ void selectFolder( QString folder );
+ int timeOfLastMessageCountChange() const;
+ virtual bool showMail( Q_UINT32 serialNumber, QString messageId );
+ virtual QString getFrom( Q_UINT32 serialNumber );
+ virtual QString debugScheduler();
+ virtual QString debugSernum( Q_UINT32 serialNumber );
+ int viewMessage( const KURL & messageFile );
+
+ /**
+ * Pauses all background jobs and does not
+ * allow new background jobs to be started.
+ */
+ virtual void pauseBackgroundJobs();
+
+ /**
+ * Resumes all background jobs and allows
+ * new jobs to be started.
+ */
+ virtual void resumeBackgroundJobs();
+
+ /**
+ * Stops all network related jobs and enter offline mode
+ * New network jobs cannot be started.
+ */
+ void stopNetworkJobs();
+
+ /**
+ * Resumes all network related jobs and enter online mode
+ * New network jobs can be started.
+ */
+ void resumeNetworkJobs();
+
+ /** A static helper function that asks the user
+ * if they want to go online.
+ * @return true if the user wants to go online
+ * @return false if the user wants to stay offline
+ */
+ static bool askToGoOnline();
+
+ /** Checks if the current network state is online or offline
+ * @return true if the network state is offline
+ * @return false if the network state is online
+ */
+ static bool isOffline();
+
+ /** normal control stuff */
+
+ static KMKernel *self() { return mySelf; }
+ static KConfig *config();
+
+ void init();
+ void readConfig();
+ void cleanupImapFolders();
+ void testDir(const char *_name);
+ void recoverDeadLetters();
+ void initFolders(KConfig* cfg);
+ void closeAllKMailWindows();
+ void cleanup(void);
+ void quit();
+ /**
+ * Returns true if the transfer was successful, otherwise false. In any case
+ * destinationDir contains the path to the current mail storage when the
+ * method returns.
+ */
+ bool transferMail( QString & destinationDir );
+ void ungrabPtrKb(void);
+ void kmailMsgHandler(QtMsgType aType, const char* aMsg);
+ bool doSessionManagement();
+ bool firstInstance() { return the_firstInstance; }
+ void setFirstInstance(bool value) { the_firstInstance = value; }
+ void action (bool mailto, bool check, const QString &to, const QString &cc,
+ const QString &bcc, const QString &subj, const QString &body,
+ const KURL &messageFile, const KURL::List &attach,
+ const QCStringList &customHeaders);
+ void byteArrayToRemoteFile(const QByteArray&, const KURL&,
+ bool overwrite = FALSE);
+ bool folderIsDraftOrOutbox( const KMFolder * );
+ bool folderIsDrafts( const KMFolder * );
+ bool folderIsTemplates( const KMFolder * );
+ bool folderIsTrash( KMFolder * );
+ /**
+ * Returns true if the folder is one of the sent-mail folders.
+ */
+ bool folderIsSentMailFolder( const KMFolder * );
+ /**
+ * Find a folder by ID string in all folder managers
+ */
+ KMFolder* findFolderById( const QString& idString );
+
+ KInstance *xmlGuiInstance() { return mXmlGuiInstance; }
+ void setXmlGuiInstance( KInstance *instance ) { mXmlGuiInstance = instance; }
+
+ KMFolder *inboxFolder() { return the_inboxFolder; }
+ KMFolder *outboxFolder() { return the_outboxFolder; }
+ KMFolder *sentFolder() { return the_sentFolder; }
+ KMFolder *trashFolder() { return the_trashFolder; }
+ KMFolder *draftsFolder() { return the_draftsFolder; }
+ KMFolder *templatesFolder() { return the_templatesFolder; }
+
+ KMFolderMgr *folderMgr() { return the_folderMgr; }
+ KMFolderMgr *imapFolderMgr() { return the_imapFolderMgr; }
+ KMFolderMgr *dimapFolderMgr() { return the_dimapFolderMgr; }
+ KMFolderMgr *searchFolderMgr() { return the_searchFolderMgr; }
+ UndoStack *undoStack() { return the_undoStack; }
+ AccountManager *acctMgr() { return the_acctMgr; }
+ KMFilterMgr *filterMgr() { return the_filterMgr; }
+ KMFilterMgr *popFilterMgr() { return the_popFilterMgr; }
+ KMFilterActionDict *filterActionDict() { return the_filterActionDict; }
+ KMail::MessageSender *msgSender();
+ KMMsgIndex *msgIndex();
+
+ KPIM::ThreadWeaver::Weaver *weaver() { return the_weaver; }
+ /** return the pointer to the identity manager */
+ KPIM::IdentityManager *identityManager();
+
+ JobScheduler* jobScheduler() { return mJobScheduler; }
+
+ /** Compact all folders, used for the gui action (and from DCOP) */
+ void compactAllFolders();
+ /** Expire all folders, used for the gui action */
+ void expireAllFoldersNow();
+
+ KMailICalIfaceImpl& iCalIface();
+
+ bool firstStart() { return the_firstStart; }
+ QString previousVersion() { return the_previousVersion; }
+ bool startingUp() { return the_startingUp; }
+ void setStartingUp (bool flag) { the_startingUp = flag; }
+ bool shuttingDown() { return the_shuttingDown; }
+ void setShuttingDown(bool flag) { the_shuttingDown = flag; }
+ void serverReady (bool flag) { the_server_is_ready = flag; }
+
+ /** Returns the full path of the user's local data directory for KMail.
+ The path ends with '/'.
+ */
+ static QString localDataPath();
+
+ /** Returns true if we have a system tray applet. This is needed in order
+ * to know whether the application should be allowed to exit in case the
+ * last visible composer or separate message window is closed.
+ */
+ bool haveSystemTrayApplet();
+
+ bool registerSystemTrayApplet( const KSystemTray* );
+ bool unregisterSystemTrayApplet( const KSystemTray* );
+
+ /// Reimplemented from KMailIface
+ bool handleCommandLine( bool noArgsOpensReader );
+ void emergencyExit( const QString& reason );
+
+ /** Returns a message serial number that hasn't been used yet. */
+ unsigned long getNextMsgSerNum();
+ QTextCodec *networkCodec() { return netCodec; }
+
+ /** returns a reference to the first Mainwin or a temporary Mainwin */
+ KMainWindow* mainWin();
+
+ // ### The mContextMenuShown flag is necessary to work around bug# 56693
+ // ### (kmail freeze with the complete desktop while pinentry-qt appears)
+ // ### FIXME: Once the encryption support is asynchron this can be removed
+ // ### again.
+ void setContextMenuShown( bool flag ) { mContextMenuShown = flag; }
+ bool contextMenuShown() const { return mContextMenuShown; }
+
+ /**
+ * Get a reference to KMail's KIMProxy instance
+ * @return a pointer to a valid KIMProxy
+ */
+ ::KIMProxy* imProxy();
+
+ /**
+ * Returns true IFF the user has requested that the current mail checks
+ * should be aborted. Needs to be periodically polled.
+ */
+ bool mailCheckAborted() const;
+ /** Set the state of the abort requested variable to false,
+ * i.e. enable mail checking again
+ */
+ void enableMailCheck();
+ /**
+ * Set the state of the abort requested variable to true,
+ * (to let the current jobs run, but stop when possible).
+ * This is used to cancel mail checks when closing the last mainwindow
+ */
+ void abortMailCheck();
+
+ bool canQueryClose();
+
+ /**
+ * Called by the folder tree if the count of unread/total messages changed.
+ */
+ void messageCountChanged();
+
+ /** Open KDE wallet and set it to kmail folder */
+ KWallet::Wallet *wallet();
+
+ /** Get first mainwidget */
+ KMMainWidget *getKMMainWidget();
+
+ /** @return a list of all folders from all folder managers. */
+ QValueList< QGuardedPtr<KMFolder> > allFolders();
+
+ void raise();
+
+ void loadProfile( const QString& path );
+
+ void saveToProfile( const QString& path ) const;
+public slots:
+
+ /// Save contents of all open composer widnows to ~/dead.letter
+ void dumpDeadLetters();
+
+ /** Call this slot instead of directly KConfig::sync() to
+ minimize the overall config writes. Calling this slot will
+ schedule a sync of the application config file using a timer, so
+ that many consecutive calls can be condensed into a single
+ sync, which is more efficient. */
+ void slotRequestConfigSync();
+
+ /** empty all the trash bins */
+ void slotEmptyTrash();
+
+ void slotShowConfigurationDialog();
+ void slotRunBackgroundTasks();
+
+ void slotConfigChanged();
+
+protected slots:
+ void slotDataReq(KIO::Job*,QByteArray&);
+ void slotResult(KIO::Job*);
+
+signals:
+ void configChanged();
+ void folderRemoved( KMFolder* aFolder );
+ void onlineStatusChanged( GlobalSettings::EnumNetworkState::type );
+
+private:
+ void openReader( bool onlyCheck );
+ KMMsgStatus strToStatus(const QString &flags);
+ KMFolder *currentFolder();
+
+ KMFolder *the_inboxFolder;
+ KMFolder *the_outboxFolder;
+ KMFolder *the_sentFolder;
+ KMFolder *the_trashFolder;
+ KMFolder *the_draftsFolder;
+ KMFolder *the_templatesFolder;
+
+ KMFolderMgr *the_folderMgr;
+ KMFolderMgr *the_imapFolderMgr;
+ KMFolderMgr *the_dimapFolderMgr;
+ KMFolderMgr *the_searchFolderMgr;
+ UndoStack *the_undoStack;
+ AccountManager *the_acctMgr;
+ KMFilterMgr *the_filterMgr;
+ KMFilterMgr *the_popFilterMgr;
+ KMFilterActionDict *the_filterActionDict;
+ mutable KPIM::IdentityManager *mIdentityManager;
+ KMSender *the_msgSender;
+ KMMsgIndex *the_msgIndex;
+ struct putData
+ {
+ KURL url;
+ QByteArray data;
+ int offset;
+ };
+ QMap<KIO::Job *, putData> mPutJobs;
+ /** previous KMail version. If different from current,
+ the user has just updated. read from config */
+ QString the_previousVersion;
+ /** is this the first start? read from config */
+ bool the_firstStart;
+ /** are we starting up? set in main.cpp directly before kapp->exec() */
+ bool the_startingUp;
+ /** are we going down? set from here */
+ bool the_shuttingDown;
+ /** are we in the middle of network operations (needed?) */
+ bool the_server_is_ready;
+ /** true unles kmail is closed by session management */
+ bool closed_by_user;
+ bool the_firstInstance;
+ bool mMailCheckAborted;
+ static KMKernel *mySelf;
+ KSharedConfig::Ptr mConfig;
+ QTextCodec *netCodec;
+ KInstance* mXmlGuiInstance;
+ ConfigureDialog *mConfigureDialog;
+
+ QTimer *mBackgroundTasksTimer;
+ KMailICalIfaceImpl* mICalIface;
+ JobScheduler* mJobScheduler;
+ // temporary mainwin
+ KMMainWin *mWin;
+ MailServiceImpl *mMailService;
+
+ // the time of the last change of the unread or total count of a folder;
+ // this can be queried via DCOP in order to determine whether the counts
+ // need to be updated (e.g. in the Summary in Kontact)
+ int mTimeOfLastMessageCountChange;
+
+ // true if the context menu of KMFolderTree or KMHeaders is shown
+ // this is necessary to know in order to prevent a dead lock between the
+ // context menus and the pinentry program
+ bool mContextMenuShown;
+
+ QValueList<const KSystemTray*> systemTrayApplets;
+
+ /* Weaver */
+ KPIM::ThreadWeaver::Weaver *the_weaver;
+ KPIM::ThreadWeaver::WeaverThreadLogger *the_weaverLogger;
+
+ KWallet::Wallet *mWallet;
+
+ // variables used by dcopAddMessage()
+ QStringList mAddMessageMsgIds;
+ QString mAddMessageLastFolder;
+ KMFolder *mAddMsgCurrentFolder;
+};
+
+#endif
diff --git a/kmail/kmlineeditspell.cpp b/kmail/kmlineeditspell.cpp
new file mode 100644
index 00000000..ca315dea
--- /dev/null
+++ b/kmail/kmlineeditspell.cpp
@@ -0,0 +1,216 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+// kmcomposewin.cpp
+// Author: Markus Wuebben <markus.wuebben@kde.org>
+// This code is published under the GPL.
+
+#include "kmlineeditspell.h"
+
+#include "recentaddresses.h"
+#include "kmkernel.h"
+#include "globalsettings.h"
+
+#include <libkdepim/kvcarddrag.h>
+#include <libemailfunctions/email.h>
+
+#include <kabc/vcardconverter.h>
+#include <kio/netaccess.h>
+
+#include <kpopupmenu.h>
+#include <kurl.h>
+#include <kurldrag.h>
+#include <kmessagebox.h>
+#include <kcompletionbox.h>
+#include <klocale.h>
+
+#include <qevent.h>
+#include <qfile.h>
+#include <qcstring.h>
+#include <qcursor.h>
+
+
+KMLineEdit::KMLineEdit(bool useCompletion,
+ QWidget *parent, const char *name)
+ : KPIM::AddresseeLineEdit(parent,useCompletion,name)
+{
+ allowSemiColonAsSeparator( GlobalSettings::allowSemicolonAsAddressSeparator() );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMLineEdit::keyPressEvent(QKeyEvent *e)
+{
+ if ((e->key() == Key_Enter || e->key() == Key_Return) &&
+ !completionBox()->isVisible())
+ {
+ emit focusDown();
+ AddresseeLineEdit::keyPressEvent(e);
+ return;
+ }
+ if (e->key() == Key_Up)
+ {
+ emit focusUp();
+ return;
+ }
+ if (e->key() == Key_Down)
+ {
+ emit focusDown();
+ return;
+ }
+ AddresseeLineEdit::keyPressEvent(e);
+}
+
+
+void KMLineEdit::insertEmails( const QStringList & emails )
+{
+ if ( emails.empty() )
+ return;
+
+ QString contents = text();
+ if ( !contents.isEmpty() )
+ contents += ',';
+ // only one address, don't need kpopup to choose
+ if ( emails.size() == 1 ) {
+ setText( contents + emails.front() );
+ return;
+ }
+ //multiple emails, let the user choose one
+ KPopupMenu menu( this, "Addresschooser" );
+ for ( QStringList::const_iterator it = emails.begin(), end = emails.end() ; it != end; ++it )
+ menu.insertItem( *it );
+ const int result = menu.exec( QCursor::pos() );
+ if ( result < 0 )
+ return;
+ setText( contents + menu.text( result ) );
+}
+
+void KMLineEdit::dropEvent(QDropEvent *event)
+{
+ QString vcards;
+ KVCardDrag::decode( event, vcards );
+ if ( !vcards.isEmpty() ) {
+ KABC::VCardConverter converter;
+ KABC::Addressee::List list = converter.parseVCards( vcards );
+ KABC::Addressee::List::Iterator ait;
+ for ( ait = list.begin(); ait != list.end(); ++ait ){
+ insertEmails( (*ait).emails() );
+ }
+ } else {
+ KURL::List urls;
+ if ( KURLDrag::decode( event, urls) ) {
+ //kdDebug(5006) << "urlList" << endl;
+ KURL::List::Iterator it = urls.begin();
+ KABC::VCardConverter converter;
+ KABC::Addressee::List list;
+ QString fileName;
+ QString caption( i18n( "vCard Import Failed" ) );
+ for ( it = urls.begin(); it != urls.end(); ++it ) {
+ if ( KIO::NetAccess::download( *it, fileName, parentWidget() ) ) {
+ QFile file( fileName );
+ file.open( IO_ReadOnly );
+ QByteArray rawData = file.readAll();
+ file.close();
+ QString data = QString::fromUtf8( rawData.data(), rawData.size() + 1 );
+ list += converter.parseVCards( data );
+ KIO::NetAccess::removeTempFile( fileName );
+ } else {
+ QString text = i18n( "<qt>Unable to access <b>%1</b>.</qt>" );
+ KMessageBox::error( parentWidget(), text.arg( (*it).url() ), caption );
+ }
+ KABC::Addressee::List::Iterator ait;
+ for ( ait = list.begin(); ait != list.end(); ++ait )
+ insertEmails((*ait).emails());
+ }
+ } else {
+ KPIM::AddresseeLineEdit::dropEvent( event );
+ }
+ }
+}
+
+QPopupMenu *KMLineEdit::createPopupMenu()
+{
+ QPopupMenu *menu = KPIM::AddresseeLineEdit::createPopupMenu();
+ if ( !menu )
+ return 0;
+
+ menu->insertSeparator();
+ menu->insertItem( i18n( "Edit Recent Addresses..." ),
+ this, SLOT( editRecentAddresses() ) );
+
+ return menu;
+}
+
+void KMLineEdit::editRecentAddresses()
+{
+ KRecentAddress::RecentAddressDialog dlg( this );
+ dlg.setAddresses( KRecentAddress::RecentAddresses::self( KMKernel::config() )->addresses() );
+ if ( !dlg.exec() )
+ return;
+ KRecentAddress::RecentAddresses::self( KMKernel::config() )->clear();
+ const QStringList addrList = dlg.addresses();
+ for ( QStringList::const_iterator it = addrList.begin(), end = addrList.end() ; it != end ; ++it )
+ KRecentAddress::RecentAddresses::self( KMKernel::config() )->add( *it );
+ loadContacts();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMLineEdit::loadContacts()
+{
+ // was: KABC::AddressLineEdit::loadAddresses()
+ AddresseeLineEdit::loadContacts();
+
+ if ( GlobalSettings::self()->showRecentAddressesInComposer() ){
+ if ( KMKernel::self() ) {
+ QStringList recent =
+ KRecentAddress::RecentAddresses::self( KMKernel::config() )->addresses();
+ QStringList::Iterator it = recent.begin();
+ QString name, email;
+ int idx = addCompletionSource( i18n( "Recent Addresses" ) );
+ for ( ; it != recent.end(); ++it ) {
+ KABC::Addressee addr;
+ KPIM::getNameAndMail(*it, name, email);
+ addr.setNameFromString( KPIM::quoteNameIfNecessary( name ));
+ addr.insertEmail( email, true );
+ addContact( addr, 120, idx ); // more weight than kabc entries and more than ldap results
+ }
+ }
+ }
+}
+
+
+KMLineEditSpell::KMLineEditSpell(bool useCompletion,
+ QWidget *parent, const char *name)
+ : KMLineEdit(useCompletion,parent,name)
+{
+}
+
+
+void KMLineEditSpell::highLightWord( unsigned int length, unsigned int pos )
+{
+ setSelection ( pos, length );
+}
+
+void KMLineEditSpell::spellCheckDone( const QString &s )
+{
+ if( s != text() )
+ setText( s );
+}
+
+void KMLineEditSpell::spellCheckerMisspelling( const QString &_text, const QStringList&, unsigned int pos)
+{
+ highLightWord( _text.length(),pos );
+}
+
+void KMLineEditSpell::spellCheckerCorrected( const QString &old, const QString &corr, unsigned int pos)
+{
+ if( old!= corr )
+ {
+ setSelection ( pos, old.length() );
+ insert( corr );
+ setSelection ( pos, corr.length() );
+ emit subjectTextSpellChecked();
+ }
+}
+
+
+#include "kmlineeditspell.moc"
diff --git a/kmail/kmlineeditspell.h b/kmail/kmlineeditspell.h
new file mode 100644
index 00000000..85b4c1c9
--- /dev/null
+++ b/kmail/kmlineeditspell.h
@@ -0,0 +1,55 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ * KMComposeWin Header File
+ * Author: Markus Wuebben <markus.wuebben@kde.org>
+ */
+#ifndef __KMAIL_KMLINEEDITSPELL_H__
+#define __KMAIL_KMLINEEDITSPELL_H__
+
+#include <libkdepim/addresseelineedit.h>
+
+class QPopupMenu;
+
+class KMLineEdit : public KPIM::AddresseeLineEdit
+{
+ Q_OBJECT
+public:
+ KMLineEdit(bool useCompletion, QWidget *parent = 0,
+ const char *name = 0);
+
+signals:
+ void focusUp();
+ void focusDown();
+
+protected:
+ // Inherited. Always called by the parent when this widget is created.
+ virtual void loadContacts();
+
+ virtual void keyPressEvent(QKeyEvent*);
+
+ virtual QPopupMenu *createPopupMenu();
+
+private slots:
+ void editRecentAddresses();
+
+private:
+ void dropEvent( QDropEvent *event );
+ void insertEmails( const QStringList & emails );
+};
+
+
+class KMLineEditSpell : public KMLineEdit
+{
+ Q_OBJECT
+public:
+ KMLineEditSpell(bool useCompletion, QWidget *parent = 0,
+ const char *name = 0);
+ void highLightWord( unsigned int length, unsigned int pos );
+ void spellCheckDone( const QString &s );
+ void spellCheckerMisspelling( const QString &text, const QStringList &, unsigned int pos);
+ void spellCheckerCorrected( const QString &old, const QString &corr, unsigned int pos);
+
+ signals:
+ void subjectTextSpellChecked();
+};
+
+#endif // __KMAIL_KMLINEEDITSPELL_H__
diff --git a/kmail/kmmainwidget.cpp b/kmail/kmmainwidget.cpp
new file mode 100644
index 00000000..2876e3b0
--- /dev/null
+++ b/kmail/kmmainwidget.cpp
@@ -0,0 +1,3979 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+// kmmainwidget.cpp
+//#define MALLOC_DEBUG 1
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <kwin.h>
+
+#ifdef MALLOC_DEBUG
+#include <malloc.h>
+#endif
+
+#undef Unsorted // X headers...
+#include <qaccel.h>
+#include <qlayout.h>
+#include <qhbox.h>
+#include <qvbox.h>
+#include <qpopupmenu.h>
+#include <qpushbutton.h>
+#include <qptrlist.h>
+
+#include <kopenwith.h>
+
+#include <kmessagebox.h>
+#include <klistviewsearchline.h>
+#include <kiconloader.h>
+
+#include <kpopupmenu.h>
+#include <kaccelmanager.h>
+#include <kglobalsettings.h>
+#include <kstdaccel.h>
+#include <kkeydialog.h>
+#include <kcharsets.h>
+#include <knotifyclient.h>
+#include <kdebug.h>
+#include <kapplication.h>
+#include <kfiledialog.h>
+#include <ktip.h>
+#include <knotifydialog.h>
+#include <kstandarddirs.h>
+#include <dcopclient.h>
+#include <kaddrbook.h>
+#include <kaccel.h>
+#include <kstringhandler.h>
+
+#include <qvaluevector.h>
+
+#include "globalsettings.h"
+#include "kcursorsaver.h"
+#include "broadcaststatus.h"
+using KPIM::BroadcastStatus;
+#include "kmfoldermgr.h"
+#include "kmfolderdia.h"
+#include "accountmanager.h"
+using KMail::AccountManager;
+#include "kmfilter.h"
+#include "kmfoldertree.h"
+#include "kmreadermainwin.h"
+#include "kmfoldercachedimap.h"
+#include "kmfolderimap.h"
+#include "kmacctcachedimap.h"
+#include "composer.h"
+#include "kmfolderseldlg.h"
+#include "kmfiltermgr.h"
+#include "messagesender.h"
+#include "kmaddrbook.h"
+#include "kmversion.h"
+#include "searchwindow.h"
+using KMail::SearchWindow;
+#include "kmacctfolder.h"
+#include "undostack.h"
+#include "kmcommands.h"
+#include "kmmainwin.h"
+#include "kmsystemtray.h"
+#include "imapaccountbase.h"
+#include "transportmanager.h"
+using KMail::ImapAccountBase;
+#include "vacation.h"
+using KMail::Vacation;
+#include "favoritefolderview.h"
+
+#include <qsignalmapper.h>
+
+#include "subscriptiondialog.h"
+using KMail::SubscriptionDialog;
+#include "localsubscriptiondialog.h"
+using KMail::LocalSubscriptionDialog;
+#include "attachmentstrategy.h"
+using KMail::AttachmentStrategy;
+#include "headerstrategy.h"
+using KMail::HeaderStrategy;
+#include "headerstyle.h"
+using KMail::HeaderStyle;
+#include "folderjob.h"
+using KMail::FolderJob;
+#include "mailinglist-magic.h"
+#include "antispamwizard.h"
+using KMail::AntiSpamWizard;
+#include "filterlogdlg.h"
+using KMail::FilterLogDialog;
+#include <headerlistquicksearch.h>
+#include "klistviewindexedsearchline.h"
+using KMail::HeaderListQuickSearch;
+#include "kmheaders.h"
+#include "mailinglistpropertiesdialog.h"
+#include "templateparser.h"
+
+#if !defined(NDEBUG)
+ #include "sievedebugdialog.h"
+ using KMail::SieveDebugDialog;
+#endif
+
+#include <libkpimidentities/identity.h>
+#include <libkpimidentities/identitymanager.h>
+
+#include <assert.h>
+#include <kstatusbar.h>
+#include <kstaticdeleter.h>
+#include <kaction.h>
+
+#include <kmime_mdn.h>
+#include <kmime_header_parsing.h>
+using namespace KMime;
+using KMime::Types::AddrSpecList;
+
+#include "progressmanager.h"
+using KPIM::ProgressManager;
+
+#include "managesievescriptsdialog.h"
+#include <qstylesheet.h>
+
+#include "customtemplates.h"
+#include "customtemplates_kfg.h"
+
+#include "kmmainwidget.moc"
+
+QValueList<KMMainWidget*>* KMMainWidget::s_mainWidgetList = 0;
+static KStaticDeleter<QValueList<KMMainWidget*> > mwlsd;
+
+//-----------------------------------------------------------------------------
+KMMainWidget::KMMainWidget(QWidget *parent, const char *name,
+ KXMLGUIClient *aGUIClient,
+ KActionCollection *actionCollection, KConfig* config ) :
+ QWidget(parent, name),
+ mFavoritesCheckMailAction( 0 ),
+ mFavoriteFolderView( 0 ),
+ mFolderView( 0 ),
+ mFolderViewParent( 0 ),
+ mFolderViewSplitter( 0 ),
+ mQuickSearchLine( 0 ),
+ mShowBusySplashTimer( 0 ),
+ mShowingOfflineScreen( false ),
+ mMsgActions( 0 ),
+ mVacationIndicatorActive( false )
+{
+ // must be the first line of the constructor:
+ mStartupDone = false;
+ mSearchWin = 0;
+ mIntegrated = true;
+ mFolder = 0;
+ mTemplateFolder = 0;
+ mFolderThreadPref = false;
+ mFolderThreadSubjPref = true;
+ mReaderWindowActive = true;
+ mReaderWindowBelow = true;
+ mFolderHtmlPref = false;
+ mFolderHtmlLoadExtPref = false;
+ mSystemTray = 0;
+ mDestructed = false;
+ mActionCollection = actionCollection;
+ mTopLayout = new QVBoxLayout(this);
+ mFilterMenuActions.setAutoDelete(true);
+ mFilterTBarActions.setAutoDelete(false);
+ mFilterCommands.setAutoDelete(true);
+ mFolderShortcutCommands.setAutoDelete(true);
+ mJob = 0;
+ mConfig = config;
+ mGUIClient = aGUIClient;
+
+ mCustomReplyActionMenu = 0;
+ mCustomReplyAllActionMenu = 0;
+ mCustomForwardActionMenu = 0;
+ mCustomReplyMapper = 0;
+ mCustomReplyAllMapper = 0;
+ mCustomForwardMapper = 0;
+
+ // FIXME This should become a line separator as soon as the API
+ // is extended in kdelibs.
+ mToolbarActionSeparator = new KActionSeparator( actionCollection );
+
+ if( !s_mainWidgetList )
+ mwlsd.setObject( s_mainWidgetList, new QValueList<KMMainWidget*>() );
+ s_mainWidgetList->append( this );
+
+ mPanner1Sep << 1 << 1;
+ mPanner2Sep << 1 << 1;
+
+ setMinimumSize(400, 300);
+
+ readPreConfig();
+ createWidgets();
+
+ setupActions();
+
+ readConfig();
+
+ activatePanners();
+
+ QTimer::singleShot( 0, this, SLOT( slotShowStartupFolder() ));
+
+ connect( kmkernel->acctMgr(), SIGNAL( checkedMail( bool, bool, const QMap<QString, int> & ) ),
+ this, SLOT( slotMailChecked( bool, bool, const QMap<QString, int> & ) ) );
+
+ connect( kmkernel->acctMgr(), SIGNAL( accountAdded( KMAccount* ) ),
+ this, SLOT( initializeIMAPActions() ) );
+ connect( kmkernel->acctMgr(), SIGNAL( accountRemoved( KMAccount* ) ),
+ this, SLOT( initializeIMAPActions() ) );
+
+ connect(kmkernel, SIGNAL( configChanged() ),
+ this, SLOT( slotConfigChanged() ));
+
+ // display the full path to the folder in the caption
+ connect(mFolderTree, SIGNAL(currentChanged(QListViewItem*)),
+ this, SLOT(slotChangeCaption(QListViewItem*)));
+ connect(mFolderTree, SIGNAL(selectionChanged()),
+ SLOT(updateFolderMenu()) );
+
+ connect(kmkernel->folderMgr(), SIGNAL(folderRemoved(KMFolder*)),
+ this, SLOT(slotFolderRemoved(KMFolder*)));
+
+ connect(kmkernel->imapFolderMgr(), SIGNAL(folderRemoved(KMFolder*)),
+ this, SLOT(slotFolderRemoved(KMFolder*)));
+
+ connect(kmkernel->dimapFolderMgr(), SIGNAL(folderRemoved(KMFolder*)),
+ this, SLOT(slotFolderRemoved(KMFolder*)));
+
+ connect(kmkernel->searchFolderMgr(), SIGNAL(folderRemoved(KMFolder*)),
+ this, SLOT(slotFolderRemoved(KMFolder*)));
+
+ connect( kmkernel, SIGNAL( onlineStatusChanged( GlobalSettings::EnumNetworkState::type ) ),
+ this, SLOT( slotUpdateOnlineStatus( GlobalSettings::EnumNetworkState::type ) ) );
+
+ toggleSystemTray();
+
+ // must be the last line of the constructor:
+ mStartupDone = true;
+
+
+ KMainWindow *mainWin = dynamic_cast<KMainWindow*>(topLevelWidget());
+ KStatusBar *sb = mainWin ? mainWin->statusBar() : 0;
+ mVacationScriptIndicator = new KStatusBarLabel( QString(), 0, sb );
+ mVacationScriptIndicator->hide();
+ connect( mVacationScriptIndicator, SIGNAL(itemReleased(int)), SLOT(slotEditVacation()) );
+ if ( GlobalSettings::checkOutOfOfficeOnStartup() )
+ QTimer::singleShot( 0, this, SLOT(slotCheckVacation()) );
+}
+
+
+//-----------------------------------------------------------------------------
+//The kernel may have already been deleted when this method is called,
+//perform all cleanup that requires the kernel in destruct()
+KMMainWidget::~KMMainWidget()
+{
+ s_mainWidgetList->remove( this );
+ destruct();
+}
+
+
+//-----------------------------------------------------------------------------
+//This method performs all cleanup that requires the kernel to exist.
+void KMMainWidget::destruct()
+{
+ if (mDestructed)
+ return;
+ if (mSearchWin)
+ mSearchWin->close();
+ writeConfig();
+ writeFolderConfig();
+ delete mHeaders;
+ delete mFolderTree;
+ delete mSystemTray;
+ delete mMsgView;
+ mDestructed = true;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::readPreConfig(void)
+{
+ const KConfigGroup geometry( KMKernel::config(), "Geometry" );
+ const KConfigGroup reader( KMKernel::config(), "Reader" );
+
+ mLongFolderList = geometry.readEntry( "FolderList", "long" ) != "short";
+ mReaderWindowActive = geometry.readEntry( "readerWindowMode", "below" ) != "hide";
+ mReaderWindowBelow = geometry.readEntry( "readerWindowMode", "below" ) == "below";
+ mThreadPref = geometry.readBoolEntry( "nestedMessages", false );
+
+ mHtmlPref = reader.readBoolEntry( "htmlMail", false );
+ mHtmlLoadExtPref = reader.readBoolEntry( "htmlLoadExternal", false );
+ mEnableFavoriteFolderView = GlobalSettings::self()->enableFavoriteFolderView();
+ mEnableFolderQuickSearch = GlobalSettings::self()->enableFolderQuickSearch();
+ mEnableQuickSearch = GlobalSettings::self()->quickSearchActive();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::readFolderConfig(void)
+{
+ if (!mFolder)
+ return;
+
+ KConfig *config = KMKernel::config();
+ KConfigGroupSaver saver(config, "Folder-" + mFolder->idString());
+ mFolderThreadPref = config->readBoolEntry( "threadMessagesOverride", false );
+ mFolderThreadSubjPref = config->readBoolEntry( "threadMessagesBySubject", true );
+ mFolderHtmlPref = config->readBoolEntry( "htmlMailOverride", false );
+ mFolderHtmlLoadExtPref = config->readBoolEntry( "htmlLoadExternalOverride", false );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::writeFolderConfig(void)
+{
+ if (!mFolder)
+ return;
+
+ KConfig *config = KMKernel::config();
+ KConfigGroupSaver saver(config, "Folder-" + mFolder->idString());
+ config->writeEntry( "threadMessagesOverride", mFolderThreadPref );
+ config->writeEntry( "threadMessagesBySubject", mFolderThreadSubjPref );
+ config->writeEntry( "htmlMailOverride", mFolderHtmlPref );
+ config->writeEntry( "htmlLoadExternalOverride", mFolderHtmlLoadExtPref );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::readConfig(void)
+{
+ KConfig *config = KMKernel::config();
+
+ bool oldLongFolderList = mLongFolderList;
+ bool oldReaderWindowActive = mReaderWindowActive;
+ bool oldReaderWindowBelow = mReaderWindowBelow;
+ bool oldFavoriteFolderView = mEnableFavoriteFolderView;
+ bool oldFolderQuickSearch = mEnableFolderQuickSearch;
+ bool oldQuickSearch = mEnableQuickSearch;
+
+ QString str;
+ QSize siz;
+
+ if (mStartupDone)
+ {
+ writeConfig();
+
+ readPreConfig();
+ mHeaders->refreshNestedState();
+
+ bool layoutChanged = ( oldLongFolderList != mLongFolderList )
+ || ( oldReaderWindowActive != mReaderWindowActive )
+ || ( oldReaderWindowBelow != mReaderWindowBelow )
+ || ( oldFavoriteFolderView != mEnableFavoriteFolderView )
+ || ( oldFolderQuickSearch != mEnableFolderQuickSearch )
+ || ( oldQuickSearch != mEnableQuickSearch );
+
+
+ if( layoutChanged ) {
+ hide();
+ // delete all panners
+ delete mPanner1; // will always delete the others
+ createWidgets();
+ }
+
+ }
+
+ { // area for config group "Geometry"
+ KConfigGroupSaver saver(config, "Geometry");
+ // size of the mainwin
+ QSize defaultSize(750,560);
+ siz = config->readSizeEntry("MainWin", &defaultSize);
+ if (!siz.isEmpty())
+ resize(siz);
+ // default width of the foldertree
+ static const int folderpanewidth = 250;
+
+ const int folderW = config->readNumEntry( "FolderPaneWidth", folderpanewidth );
+ const int headerW = config->readNumEntry( "HeaderPaneWidth", width()-folderpanewidth );
+ const int headerH = config->readNumEntry( "HeaderPaneHeight", 180 );
+ const int readerH = config->readNumEntry( "ReaderPaneHeight", 280 );
+
+ mPanner1Sep.clear();
+ mPanner2Sep.clear();
+ QValueList<int> & widths = mLongFolderList ? mPanner1Sep : mPanner2Sep ;
+ QValueList<int> & heights = mLongFolderList ? mPanner2Sep : mPanner1Sep ;
+
+ widths << folderW << headerW;
+ heights << headerH << readerH;
+
+ bool layoutChanged = ( oldLongFolderList != mLongFolderList )
+ || ( oldReaderWindowActive != mReaderWindowActive )
+ || ( oldReaderWindowBelow != mReaderWindowBelow );
+
+ if (!mStartupDone || layoutChanged )
+ {
+ /** unread / total columns
+ * as we have some dependencies in this widget
+ * it's better to manage these here */
+ // The columns are shown by default.
+
+ const int unreadColumn = config->readNumEntry("UnreadColumn", 1);
+ const int totalColumn = config->readNumEntry("TotalColumn", 2);
+ const int sizeColumn = config->readNumEntry("SizeColumn", 3);
+
+ /* we need to _activate_ them in the correct order
+ * this is ugly because we can't use header()->moveSection
+ * but otherwise the restoreLayout from KMFolderTree
+ * doesn't know that to do */
+ if (unreadColumn == 1)
+ mFolderTree->addUnreadColumn( i18n("Unread"), 70 );
+ else if (totalColumn == 1)
+ mFolderTree->addTotalColumn( i18n("Total"), 70 );
+ else if (sizeColumn == 1)
+ mFolderTree->addSizeColumn( i18n("Size"), 70 );
+
+ if (unreadColumn == 2)
+ mFolderTree->addUnreadColumn( i18n("Unread"), 70 );
+ else if (totalColumn == 2)
+ mFolderTree->addTotalColumn( i18n("Total"), 70 );
+ else if (sizeColumn == 2)
+ mFolderTree->addSizeColumn( i18n("Size"), 70 );
+
+ if (unreadColumn == 3)
+ mFolderTree->addUnreadColumn( i18n("Unread"), 70 );
+ else if (totalColumn == 3)
+ mFolderTree->addTotalColumn( i18n("Total"), 70 );
+ else if (sizeColumn == 3)
+ mFolderTree->addSizeColumn( i18n("Size"), 70 );
+
+ mUnreadColumnToggle->setChecked( mFolderTree->isUnreadActive() );
+ mUnreadTextToggle->setChecked( !mFolderTree->isUnreadActive() );
+ mTotalColumnToggle->setChecked( mFolderTree->isTotalActive() );
+ mSizeColumnToggle->setChecked( mFolderTree->isSizeActive() );
+
+ mFolderTree->updatePopup();
+ }
+ }
+
+ if (mMsgView)
+ mMsgView->readConfig();
+
+ mHeaders->readConfig();
+ mHeaders->restoreLayout(KMKernel::config(), "Header-Geometry");
+
+ if ( mFolderViewSplitter && !GlobalSettings::self()->folderViewSplitterPosition().isEmpty() ) {
+ mFolderViewSplitter->setSizes( GlobalSettings::self()->folderViewSplitterPosition() );
+ } else {
+ QValueList<int> defaults;
+ defaults << (int)(height() * 0.2) << (int)(height() * 0.8);
+ mFolderViewSplitter->setSizes( defaults );
+ }
+
+ mFolderTree->readConfig();
+ if ( mFavoriteFolderView )
+ mFavoriteFolderView->readConfig();
+ mFavoritesCheckMailAction->setEnabled( GlobalSettings::self()->enableFavoriteFolderView() );
+
+ { // area for config group "General"
+ KConfigGroupSaver saver(config, "General");
+ mBeepOnNew = config->readBoolEntry("beep-on-mail", false);
+ mConfirmEmpty = config->readBoolEntry("confirm-before-empty", true);
+ // startup-Folder, defaults to system-inbox
+ mStartupFolder = config->readEntry("startupFolder", kmkernel->inboxFolder()->idString());
+ if (!mStartupDone)
+ {
+ // check mail on startup
+ bool check = config->readBoolEntry("checkmail-startup", false);
+ if (check)
+ // do it after building the kmmainwin, so that the progressdialog is available
+ QTimer::singleShot( 0, this, SLOT( slotCheckMail() ) );
+ }
+ }
+
+ // reload foldertree
+ mFolderTree->reload();
+
+ // Re-activate panners
+ if (mStartupDone)
+ {
+ // Update systray
+ toggleSystemTray();
+
+ bool layoutChanged = ( oldLongFolderList != mLongFolderList )
+ || ( oldReaderWindowActive != mReaderWindowActive )
+ || ( oldReaderWindowBelow != mReaderWindowBelow )
+ || ( oldFavoriteFolderView != mEnableFavoriteFolderView )
+ || ( oldFolderQuickSearch != mEnableFolderQuickSearch )
+ || ( oldQuickSearch != mEnableQuickSearch );
+ if ( layoutChanged ) {
+ activatePanners();
+ }
+
+ mFolderTree->showFolder( mFolder );
+
+ // sanders - New code
+ mHeaders->setFolder(mFolder);
+ if (mMsgView) {
+ int aIdx = mHeaders->currentItemIndex();
+ if (aIdx != -1)
+ mMsgView->setMsg( mFolder->getMsg(aIdx), true );
+ else
+ mMsgView->clear( true );
+ }
+ updateMessageActions();
+ show();
+ // sanders - Maybe this fixes a bug?
+
+ }
+ updateMessageMenu();
+ updateFileMenu();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::writeConfig(void)
+{
+ QString s;
+ KConfig *config = KMKernel::config();
+ KConfigGroup geometry( config, "Geometry" );
+
+ if (mMsgView)
+ mMsgView->writeConfig();
+
+ if ( mFolderViewSplitter )
+ GlobalSettings::setFolderViewSplitterPosition( mFolderViewSplitter->sizes() );
+ mFolderTree->writeConfig();
+ if ( mFavoriteFolderView )
+ mFavoriteFolderView->writeConfig();
+
+ geometry.writeEntry( "MainWin", this->geometry().size() );
+
+ const QValueList<int> widths = ( mLongFolderList ? mPanner1 : mPanner2 )->sizes();
+ const QValueList<int> heights = ( mLongFolderList ? mPanner2 : mPanner1 )->sizes();
+
+ geometry.writeEntry( "FolderPaneWidth", widths[0] );
+ geometry.writeEntry( "HeaderPaneWidth", widths[1] );
+
+ // Only save when the widget is shown (to avoid saving a wrong value)
+ if ( mSearchAndHeaders && mSearchAndHeaders->isShown() ) {
+ geometry.writeEntry( "HeaderPaneHeight", heights[0] );
+ geometry.writeEntry( "ReaderPaneHeight", heights[1] );
+ }
+
+ // save the state of the unread/total-columns
+ geometry.writeEntry( "UnreadColumn", mFolderTree->unreadIndex() );
+ geometry.writeEntry( "TotalColumn", mFolderTree->totalIndex() );
+ geometry.writeEntry( "SizeColumn", mFolderTree->sizeIndex() );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::createWidgets(void)
+{
+ // Create the splitters according to the layout settings
+ QWidget *headerParent = 0,
+ *mimeParent = 0, *messageParent = 0;
+
+ const bool opaqueResize = KGlobalSettings::opaqueResize();
+ if ( mLongFolderList ) {
+ // superior splitter: folder tree vs. rest
+ // inferior splitter: headers vs. message vs. mime tree
+ mPanner1 = new QSplitter( Qt::Horizontal, this, "panner 1" );
+ mPanner1->setOpaqueResize( opaqueResize );
+ Qt::Orientation orientation = mReaderWindowBelow ? Qt::Vertical : Qt::Horizontal;
+ mPanner2 = new QSplitter( orientation, mPanner1, "panner 2" );
+ mPanner2->setOpaqueResize( opaqueResize );
+ mPanner2->setChildrenCollapsible( false );
+ mFolderViewParent = mPanner1;
+ headerParent = mimeParent = messageParent = mPanner2;
+ } else /* !mLongFolderList */ {
+ // superior splitter: ( folder tree + headers ) vs. message vs. mime
+ // inferior splitter: folder tree vs. headers
+ mPanner1 = new QSplitter( Qt::Vertical, this, "panner 1" );
+ mPanner1->setOpaqueResize( opaqueResize );
+ mPanner1->setChildrenCollapsible( false );
+ mPanner2 = new QSplitter( Qt::Horizontal, mPanner1, "panner 2" );
+ mPanner2->setOpaqueResize( opaqueResize );
+ headerParent = mFolderViewParent = mPanner2;
+ mimeParent = messageParent = mPanner1;
+ }
+
+#ifndef NDEBUG
+ if( mPanner1 ) mPanner1->dumpObjectTree();
+ if( mPanner2 ) mPanner2->dumpObjectTree();
+#endif
+
+ mTopLayout->add( mPanner1 );
+
+ // BUG -sanders these accelerators stop working after switching
+ // between long/short folder layout
+ // Probably need to disconnect them first.
+
+ // create list of messages
+#ifndef NDEBUG
+ headerParent->dumpObjectTree();
+#endif
+ mSearchAndHeaders = new QVBox( headerParent );
+ mSearchToolBar = new KToolBar( mSearchAndHeaders, "search toolbar");
+ mSearchToolBar->setMovingEnabled(false);
+ mSearchToolBar->boxLayout()->setSpacing( KDialog::spacingHint() );
+ QLabel *label = new QLabel( i18n("S&earch:"), mSearchToolBar, "kde toolbar widget" );
+
+
+ mHeaders = new KMHeaders(this, mSearchAndHeaders, "headers");
+#ifdef HAVE_INDEXLIB
+ mQuickSearchLine = new KListViewIndexedSearchLine( mSearchToolBar, mHeaders,
+ actionCollection(), "headers quick search line" );
+#else
+ mQuickSearchLine = new HeaderListQuickSearch( mSearchToolBar, mHeaders,
+ actionCollection(), "headers quick search line" );
+#endif
+ label->setBuddy( mQuickSearchLine );
+ connect( mQuickSearchLine, SIGNAL( requestFullSearch() ),
+ this, SLOT( slotRequestFullSearchFromQuickSearch() ) );
+ mSearchToolBar->setStretchableWidget( mQuickSearchLine );
+ connect( mHeaders, SIGNAL( messageListUpdated() ),
+ mQuickSearchLine, SLOT( updateSearch() ) );
+ if ( !GlobalSettings::self()->quickSearchActive() ) mSearchToolBar->hide();
+
+ if (mReaderWindowActive) {
+ connect(mHeaders, SIGNAL(selected(KMMessage*)),
+ this, SLOT(slotMsgSelected(KMMessage*)));
+ }
+ connect(mHeaders, SIGNAL(activated(KMMessage*)),
+ this, SLOT(slotMsgActivated(KMMessage*)));
+ connect( mHeaders, SIGNAL( selectionChanged() ),
+ SLOT( startUpdateMessageActionsTimer() ) );
+ QAccel *accel = actionCollection()->kaccel();
+ accel->connectItem(accel->insertItem(SHIFT+Key_Left),
+ mHeaders, SLOT(selectPrevMessage()));
+ accel->connectItem(accel->insertItem(SHIFT+Key_Right),
+ mHeaders, SLOT(selectNextMessage()));
+
+ if (mReaderWindowActive) {
+ mMsgView = new KMReaderWin(messageParent, this, actionCollection(), 0 );
+ if ( mMsgActions ) {
+ mMsgActions->setMessageView( mMsgView );
+ }
+
+ connect(mMsgView, SIGNAL(replaceMsgByUnencryptedVersion()),
+ this, SLOT(slotReplaceMsgByUnencryptedVersion()));
+ connect(mMsgView, SIGNAL(popupMenu(KMMessage&,const KURL&,const QPoint&)),
+ this, SLOT(slotMsgPopup(KMMessage&,const KURL&,const QPoint&)));
+ connect(mMsgView, SIGNAL(urlClicked(const KURL&,int)),
+ mMsgView, SLOT(slotUrlClicked()));
+ connect(mHeaders, SIGNAL(maybeDeleting()),
+ mMsgView, SLOT(clearCache()));
+ connect(mMsgView, SIGNAL(noDrag()),
+ mHeaders, SLOT(slotNoDrag()));
+ accel->connectItem(accel->insertItem(Key_Up),
+ mMsgView, SLOT(slotScrollUp()));
+ accel->connectItem(accel->insertItem(Key_Down),
+ mMsgView, SLOT(slotScrollDown()));
+ accel->connectItem(accel->insertItem(Key_Prior),
+ mMsgView, SLOT(slotScrollPrior()));
+ accel->connectItem(accel->insertItem(Key_Next),
+ mMsgView, SLOT(slotScrollNext()));
+ } else {
+ mMsgView = NULL;
+ }
+
+ KAction *action;
+
+ action = new KAction( i18n("Move Message to Folder"), Key_M, this,
+ SLOT(slotMoveMsg()), actionCollection(),
+ "move_message_to_folder" );
+ action->plugAccel( actionCollection()->kaccel() );
+
+ action = new KAction( i18n("Copy Message to Folder"), Key_C, this,
+ SLOT(slotCopyMsg()), actionCollection(),
+ "copy_message_to_folder" );
+ action->plugAccel( actionCollection()->kaccel() );
+
+ action = new KAction( i18n("Jump to Folder"), Key_J, this,
+ SLOT(slotJumpToFolder()), actionCollection(),
+ "jump_to_folder" );
+ action->plugAccel( actionCollection()->kaccel() );
+
+ // create list of folders
+ mFolderViewSplitter = new QSplitter( Qt::Vertical, mFolderViewParent );
+ mFolderViewSplitter->setOpaqueResize( KGlobalSettings::opaqueResize() );
+ mFavoriteFolderView = new KMail::FavoriteFolderView( this, mFolderViewSplitter );
+ if ( mFavoritesCheckMailAction )
+ connect( mFavoritesCheckMailAction, SIGNAL(activated()), mFavoriteFolderView, SLOT(checkMail()) );
+ QWidget *folderTreeParent = mFolderViewParent;
+ if ( GlobalSettings::enableFavoriteFolderView() ) {
+ folderTreeParent = mFolderViewSplitter;
+ mFolderView = mFolderViewSplitter;
+ }
+
+ // the "folder tree" consists of a quicksearch input field and the tree itself
+ mSearchAndTree = new QVBox(folderTreeParent);
+ mFolderQuickSearch = new QHBox(mSearchAndTree);
+ QPushButton *clear = new QPushButton(QApplication::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, SIGNAL(clicked()), search, SLOT(clear()));
+
+ if ( !GlobalSettings::enableFolderQuickSearch() ) {
+ mFolderQuickSearch->hide();
+ }
+
+ if ( !GlobalSettings::enableFavoriteFolderView() ) {
+ mFolderView = mSearchAndTree;
+ }
+ connect( mFolderTree, SIGNAL(folderSelected(KMFolder*)),
+ mFavoriteFolderView, SLOT(folderTreeSelectionChanged(KMFolder*)) );
+
+ connect(mFolderTree, SIGNAL(folderSelected(KMFolder*)),
+ this, SLOT(folderSelected(KMFolder*)));
+ connect( mFolderTree, SIGNAL( folderSelected( KMFolder* ) ),
+ mQuickSearchLine, SLOT( reset() ) );
+ connect(mFolderTree, SIGNAL(folderSelectedUnread(KMFolder*)),
+ this, SLOT(folderSelectedUnread(KMFolder*)));
+ connect(mFolderTree, SIGNAL(folderDrop(KMFolder*)),
+ this, SLOT(slotMoveMsgToFolder(KMFolder*)));
+ connect(mFolderTree, SIGNAL(folderDropCopy(KMFolder*)),
+ this, SLOT(slotCopyMsgToFolder(KMFolder*)));
+ connect(mFolderTree, SIGNAL(columnsChanged()),
+ this, SLOT(slotFolderTreeColumnsChanged()));
+
+ if ( mFavoriteFolderView ) {
+ connect( mFavoriteFolderView, SIGNAL(folderDrop(KMFolder*)), SLOT(slotMoveMsgToFolder(KMFolder*)) );
+ connect( mFavoriteFolderView, SIGNAL(folderDropCopy(KMFolder*)), SLOT(slotCopyMsgToFolder(KMFolder*)) );
+ }
+
+ //Commands not worthy of menu items, but that deserve configurable keybindings
+ mRemoveDuplicatesAction = new KAction(
+ i18n("Remove Duplicate Messages"), CTRL+Key_Asterisk, this,
+ SLOT(removeDuplicates()), actionCollection(), "remove_duplicate_messages");
+ action->plugAccel( actionCollection()->kaccel() );
+
+ action = new KAction(
+ i18n("Abort Current Operation"), Key_Escape, ProgressManager::instance(),
+ SLOT(slotAbortAll()), actionCollection(), "cancel" );
+ action->plugAccel( actionCollection()->kaccel() );
+
+ action = new KAction(
+ i18n("Focus on Next Folder"), CTRL+Key_Right, mFolderTree,
+ SLOT(incCurrentFolder()), actionCollection(), "inc_current_folder");
+ action->plugAccel( actionCollection()->kaccel() );
+
+ action = new KAction(
+ i18n("Focus on Previous Folder"), CTRL+Key_Left, mFolderTree,
+ SLOT(decCurrentFolder()), actionCollection(), "dec_current_folder");
+ action->plugAccel( actionCollection()->kaccel() );
+
+ action = new KAction(
+ i18n("Select Folder with Focus"), CTRL+Key_Space, mFolderTree,
+ SLOT(selectCurrentFolder()), actionCollection(), "select_current_folder");
+ action->plugAccel( actionCollection()->kaccel() );
+
+ action = new KAction(
+ i18n("Focus on Next Message"), ALT+Key_Right, mHeaders,
+ SLOT(incCurrentMessage()), actionCollection(), "inc_current_message");
+ action->plugAccel( actionCollection()->kaccel() );
+
+ action = new KAction(
+ i18n("Focus on Previous Message"), ALT+Key_Left, mHeaders,
+ SLOT(decCurrentMessage()), actionCollection(), "dec_current_message");
+ action->plugAccel( actionCollection()->kaccel() );
+
+ action = new KAction(
+ i18n("Select Message with Focus"), ALT+Key_Space, mHeaders,
+ SLOT( selectCurrentMessage() ), actionCollection(), "select_current_message");
+ action->plugAccel( actionCollection()->kaccel() );
+
+ connect( kmkernel->outboxFolder(), SIGNAL( msgRemoved(int, QString) ),
+ SLOT( startUpdateMessageActionsTimer() ) );
+ connect( kmkernel->outboxFolder(), SIGNAL( msgAdded(int) ),
+ SLOT( startUpdateMessageActionsTimer() ) );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::activatePanners(void)
+{
+ if (mMsgView) {
+ QObject::disconnect( mMsgView->copyAction(),
+ SIGNAL( activated() ),
+ mMsgView, SLOT( slotCopySelectedText() ));
+ }
+
+ setupFolderView();
+ if ( mLongFolderList ) {
+ mSearchAndHeaders->reparent( mPanner2, 0, QPoint( 0, 0 ) );
+ if (mMsgView) {
+ mMsgView->reparent( mPanner2, 0, QPoint( 0, 0 ) );
+ mPanner2->moveToLast( mMsgView );
+ }
+ mFolderViewParent = mPanner1;
+ mFolderView->reparent( mFolderViewParent, 0, QPoint( 0, 0 ) );
+ mPanner1->moveToLast( mPanner2 );
+ mPanner1->setSizes( mPanner1Sep );
+ mPanner1->setResizeMode( mFolderView, QSplitter::KeepSize );
+ mPanner2->setSizes( mPanner2Sep );
+ mPanner2->setResizeMode( mSearchAndHeaders, QSplitter::KeepSize );
+ } else /* !mLongFolderList */ {
+ mFolderViewParent = mPanner2;
+ mFolderView->reparent( mFolderViewParent, 0, QPoint( 0, 0 ) );
+ mSearchAndHeaders->reparent( mPanner2, 0, QPoint( 0, 0 ) );
+ mPanner2->moveToLast( mSearchAndHeaders );
+ mPanner1->moveToFirst( mPanner2 );
+ if (mMsgView) {
+ mMsgView->reparent( mPanner1, 0, QPoint( 0, 0 ) );
+ mPanner1->moveToLast( mMsgView );
+ }
+ mPanner1->setSizes( mPanner1Sep );
+ mPanner2->setSizes( mPanner2Sep );
+ mPanner1->setResizeMode( mPanner2, QSplitter::KeepSize );
+ mPanner2->setResizeMode( mFolderView, QSplitter::KeepSize );
+ }
+
+ if (mMsgView) {
+ QObject::connect( mMsgView->copyAction(),
+ SIGNAL( activated() ),
+ mMsgView, SLOT( slotCopySelectedText() ));
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::hide()
+{
+ QWidget::hide();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::show()
+{
+ QWidget::show();
+}
+
+//-------------------------------------------------------------------------
+void KMMainWidget::slotSearch()
+{
+ if(!mSearchWin)
+ {
+ mSearchWin = new SearchWindow(this, "Search", mFolder, false);
+ connect(mSearchWin, SIGNAL(destroyed()),
+ this, SLOT(slotSearchClosed()));
+ }
+ else
+ {
+ mSearchWin->activateFolder(mFolder);
+ }
+
+ mSearchWin->show();
+ KWin::activateWindow( mSearchWin->winId() );
+}
+
+
+//-------------------------------------------------------------------------
+void KMMainWidget::slotSearchClosed()
+{
+ mSearchWin = 0;
+}
+
+
+//-------------------------------------------------------------------------
+void KMMainWidget::slotFind()
+{
+ if( mMsgView )
+ mMsgView->slotFind();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotHelp()
+{
+ kapp->invokeHelp();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotFilter()
+{
+ kmkernel->filterMgr()->openDialog( this );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotPopFilter()
+{
+ kmkernel->popFilterMgr()->openDialog( this );
+}
+
+void KMMainWidget::slotManageSieveScripts()
+{
+ if ( !kmkernel->askToGoOnline() ) {
+ return;
+ }
+ KMail::ManageSieveScriptsDialog * dlg = new KMail::ManageSieveScriptsDialog( this );
+ dlg->show();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotAddrBook()
+{
+ KAddrBookExternal::openAddressBook(this);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotImport()
+{
+ KRun::runCommand("kmailcvt");
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotCheckMail()
+{
+ if ( !kmkernel->askToGoOnline() ) {
+ return;
+ }
+ kmkernel->acctMgr()->checkMail(true);
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotCheckOneAccount(int item)
+{
+ if ( !kmkernel->askToGoOnline() ) {
+ return;
+ }
+ kmkernel->acctMgr()->intCheckMail(item);
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotMailChecked( bool newMail, bool sendOnCheck,
+ const QMap<QString, int> & newInFolder )
+{
+ const bool sendOnAll =
+ GlobalSettings::self()->sendOnCheck() == GlobalSettings::EnumSendOnCheck::SendOnAllChecks;
+ const bool sendOnManual =
+ GlobalSettings::self()->sendOnCheck() == GlobalSettings::EnumSendOnCheck::SendOnManualChecks;
+ if( !kmkernel->isOffline() && ( sendOnAll || (sendOnManual && sendOnCheck ) ) )
+ slotSendQueued();
+
+ if ( !newMail || newInFolder.isEmpty() )
+ return;
+
+ kapp->dcopClient()->emitDCOPSignal( "unreadCountChanged()", QByteArray() );
+
+ // build summary for new mail message
+ bool showNotification = false;
+ QString summary;
+ QStringList keys( newInFolder.keys() );
+ keys.sort();
+ for ( QStringList::const_iterator it = keys.begin();
+ it != keys.end();
+ ++it ) {
+ kdDebug(5006) << newInFolder.find( *it ).data() << " new message(s) in "
+ << *it << endl;
+
+ KMFolder *folder = kmkernel->findFolderById( *it );
+
+ if ( folder && !folder->ignoreNewMail() ) {
+ showNotification = true;
+ if ( GlobalSettings::self()->verboseNewMailNotification() ) {
+ summary += "<br>" + i18n( "1 new message in %1",
+ "%n new messages in %1",
+ newInFolder.find( *it ).data() )
+ .arg( folder->prettyURL() );
+ }
+ }
+ }
+
+ // update folder menus in case some mail got filtered to trash/current folder
+ // and we can enable "empty trash/move all to trash" action etc.
+ updateFolderMenu();
+
+ if ( !showNotification )
+ return;
+
+ if ( GlobalSettings::self()->verboseNewMailNotification() ) {
+ summary = i18n( "%1 is a list of the number of new messages per folder",
+ "<b>New mail arrived</b><br>%1" )
+ .arg( summary );
+ }
+ else {
+ summary = i18n( "New mail arrived" );
+ }
+
+ if(kmkernel->xmlGuiInstance()) {
+ KNotifyClient::Instance instance(kmkernel->xmlGuiInstance());
+ KNotifyClient::event( topLevelWidget()->winId(), "new-mail-arrived",
+ summary );
+ }
+ else
+ KNotifyClient::event( topLevelWidget()->winId(), "new-mail-arrived",
+ summary );
+
+ if (mBeepOnNew) {
+ KNotifyClient::beep();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotCompose()
+{
+ KMail::Composer * win;
+ KMMessage* msg = new KMMessage;
+
+ if ( mFolder ) {
+ msg->initHeader( mFolder->identity() );
+ TemplateParser parser( msg, TemplateParser::NewMessage,
+ "", false, false, false, false );
+ parser.process( NULL, mFolder );
+ win = KMail::makeComposer( msg, mFolder->identity() );
+ } else {
+ msg->initHeader();
+ TemplateParser parser( msg, TemplateParser::NewMessage,
+ "", false, false, false, false );
+ parser.process( NULL, NULL );
+ win = KMail::makeComposer( msg );
+ }
+
+ win->show();
+
+}
+
+//-----------------------------------------------------------------------------
+// TODO: do we want the list sorted alphabetically?
+void KMMainWidget::slotShowNewFromTemplate()
+{
+ if ( mFolder ) {
+ const KPIM::Identity & ident =
+ kmkernel->identityManager()->identityForUoidOrDefault( mFolder->identity() );
+ mTemplateFolder = kmkernel->folderMgr()->findIdString( ident.templates() );
+ }
+ else mTemplateFolder = kmkernel->templatesFolder();
+ if ( !mTemplateFolder )
+ return;
+
+ mTemplateMenu->popupMenu()->clear();
+ for ( int idx = 0; idx<mTemplateFolder->count(); ++idx ) {
+ KMMsgBase *mb = mTemplateFolder->getMsgBase( idx );
+
+ QString subj = mb->subject();
+ if ( subj.isEmpty() ) subj = i18n("No Subject");
+ mTemplateMenu->popupMenu()->insertItem(
+ KStringHandler::rsqueeze( subj.replace( "&", "&&" ) ), idx );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotNewFromTemplate( int id )
+{
+ if ( !mTemplateFolder )
+ return;
+ newFromTemplate(mTemplateFolder->getMsg( id ) );
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::newFromTemplate( KMMessage *msg )
+{
+ if ( !msg )
+ return;
+ KMCommand *command = new KMUseTemplateCommand( this, msg );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotPostToML()
+{
+ if ( mFolder && mFolder->isMailingListEnabled() ) {
+ KMCommand *command = new KMMailingListPostCommand( this, mFolder );
+ command->start();
+ }
+ else
+ slotCompose();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotFolderMailingListProperties()
+{
+ if (!mFolderTree) return;
+ KMFolderTreeItem *item = static_cast<KMFolderTreeItem*>( mFolderTree->currentItem() );
+ if ( !item ) return;
+ KMFolder* folder = item->folder();
+ if ( folder ) {
+ ( new KMail::MailingListFolderPropertiesDialog( this, folder ) )->show();
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotFolderShortcutCommand()
+{
+ if (!mFolderTree) return;
+ KMFolderTreeItem *item = static_cast<KMFolderTreeItem*>( mFolderTree->currentItem() );
+ if ( item )
+ item->assignShortcut();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotModifyFolder()
+{
+ if (!mFolderTree) return;
+ KMFolderTreeItem *item = static_cast<KMFolderTreeItem*>( mFolderTree->currentItem() );
+ if ( item )
+ modifyFolder( item );
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::modifyFolder( KMFolderTreeItem* folderItem )
+{
+ KMFolder* folder = folderItem->folder();
+ KMFolderTree* folderTree = static_cast<KMFolderTree *>( folderItem->listView() );
+ KMFolderDialog props( folder, folder->parent(), folderTree,
+ i18n("Properties of Folder %1").arg( folder->label() ) );
+ props.exec();
+ updateFolderMenu();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotExpireFolder()
+{
+ QString str;
+ bool canBeExpired = true;
+
+ if (!mFolder) return;
+
+ if (!mFolder->isAutoExpire()) {
+ canBeExpired = false;
+ } else if (mFolder->getUnreadExpireUnits()==expireNever &&
+ mFolder->getReadExpireUnits()==expireNever) {
+ canBeExpired = false;
+ }
+
+ if (!canBeExpired) {
+ str = i18n("This folder does not have any expiry options set");
+ KMessageBox::information(this, str);
+ return;
+ }
+ KConfig *config = KMKernel::config();
+ KConfigGroupSaver saver(config, "General");
+
+ if (config->readBoolEntry("warn-before-expire", true)) {
+ str = i18n("<qt>Are you sure you want to expire the folder <b>%1</b>?</qt>").arg(QStyleSheet::escape( mFolder->label() ));
+ if (KMessageBox::warningContinueCancel(this, str, i18n("Expire Folder"),
+ i18n("&Expire"))
+ != KMessageBox::Continue) return;
+ }
+
+ mFolder->expireOldMessages( true /*immediate*/);
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotEmptyFolder()
+{
+ QString str;
+
+ if (!mFolder) return;
+ bool isTrash = kmkernel->folderIsTrash(mFolder);
+
+ if (mConfirmEmpty)
+ {
+ QString title = (isTrash) ? i18n("Empty Trash") : i18n("Move to Trash");
+ QString text = (isTrash) ?
+ i18n("Are you sure you want to empty the trash folder?") :
+ i18n("<qt>Are you sure you want to move all messages from "
+ "folder <b>%1</b> to the trash?</qt>").arg( QStyleSheet::escape( mFolder->label() ) );
+
+ if (KMessageBox::warningContinueCancel(this, text, title, KGuiItem( title, "edittrash"))
+ != KMessageBox::Continue) return;
+ }
+ KCursorSaver busy(KBusyPtr::busy());
+ slotMarkAll();
+ if (isTrash) {
+ /* Don't ask for confirmation again when deleting, the user has already
+ confirmed. */
+ slotDeleteMsg( false );
+ }
+ else
+ slotTrashMsg();
+
+ if (mMsgView) mMsgView->clearCache();
+
+ if ( !isTrash )
+ BroadcastStatus::instance()->setStatusMsg(i18n("Moved all messages to the trash"));
+
+ updateMessageActions();
+
+ // Disable empty trash/move all to trash action - we've just deleted/moved all folder
+ // contents.
+ mEmptyFolderAction->setEnabled( false );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotRemoveFolder()
+{
+ QString str;
+ QDir dir;
+
+ if ( !mFolder ) return;
+ if ( mFolder->isSystemFolder() ) return;
+ if ( mFolder->isReadOnly() ) return;
+
+ QString title;
+ if ( mFolder->folderType() == KMFolderTypeSearch ) {
+ title = i18n("Delete Search");
+ str = i18n("<qt>Are you sure you want to delete the search <b>%1</b>?<br>"
+ "Any messages it shows will still be available in their original folder.</qt>")
+ .arg( QStyleSheet::escape( mFolder->label() ) );
+ } else {
+ title = i18n("Delete Folder");
+ if ( mFolder->count() == 0 ) {
+ if ( !mFolder->child() || mFolder->child()->isEmpty() ) {
+ str = i18n("<qt>Are you sure you want to delete the empty folder "
+ "<b>%1</b>?</qt>")
+ .arg( QStyleSheet::escape( mFolder->label() ) );
+ }
+ else {
+ str = i18n("<qt>Are you sure you want to delete the empty folder "
+ "<b>%1</b> and all its subfolders? Those subfolders might "
+ "not be empty and their contents will be discarded as well. "
+ "<p><b>Beware</b> that discarded messages are not saved "
+ "into your Trash folder and are permanently deleted.</qt>")
+ .arg( QStyleSheet::escape( mFolder->label() ) );
+ }
+ } else {
+ if ( !mFolder->child() || mFolder->child()->isEmpty() ) {
+ str = i18n("<qt>Are you sure you want to delete the folder "
+ "<b>%1</b>, discarding its contents? "
+ "<p><b>Beware</b> that discarded messages are not saved "
+ "into your Trash folder and are permanently deleted.</qt>")
+ .arg( QStyleSheet::escape( mFolder->label() ) );
+ }
+ else {
+ str = i18n("<qt>Are you sure you want to delete the folder <b>%1</b> "
+ "and all its subfolders, discarding their contents? "
+ "<p><b>Beware</b> that discarded messages are not saved "
+ "into your Trash folder and are permanently deleted.</qt>")
+ .arg( QStyleSheet::escape( mFolder->label() ) );
+ }
+ }
+ }
+
+ if (KMessageBox::warningContinueCancel(this, str, title,
+ 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("<qt>The folder you deleted was associated with the account "
+ "<b>%1</b> which delivered mail into it. The folder the account "
+ "delivers new mail into was reset to the main Inbox folder.</qt>").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<KMFolderCachedImap*>( 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);
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotMarkAllAsRead()
+{
+ if (!mFolder)
+ return;
+ mFolder->markUnreadAsRead();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotCompactFolder()
+{
+ if (mFolder) {
+ int idx = mHeaders->currentItemIndex();
+ KCursorSaver busy(KBusyPtr::busy());
+ mFolder->compact( KMFolder::CompactNow );
+ // setCurrentItemByIndex will override the statusbar message, so save/restore it
+ QString statusMsg = BroadcastStatus::instance()->statusMsg();
+ mHeaders->setCurrentItemByIndex(idx);
+ BroadcastStatus::instance()->setStatusMsg( statusMsg );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotRefreshFolder()
+{
+ if (mFolder)
+ {
+ if ( mFolder->folderType() == KMFolderTypeImap || mFolder->folderType() == KMFolderTypeCachedImap ) {
+ if ( !kmkernel->askToGoOnline() ) {
+ return;
+ }
+ }
+
+ if (mFolder->folderType() == KMFolderTypeImap)
+ {
+ KMFolderImap *imap = static_cast<KMFolderImap*>(mFolder->storage());
+ imap->getAndCheckFolder();
+ } else if ( mFolder->folderType() == KMFolderTypeCachedImap ) {
+ KMFolderCachedImap* f = static_cast<KMFolderCachedImap*>( mFolder->storage() );
+ f->account()->processNewMailSingleFolder( mFolder );
+ }
+ }
+}
+
+void KMMainWidget::slotTroubleshootFolder()
+{
+ if (mFolder)
+ {
+ if ( mFolder->folderType() == KMFolderTypeCachedImap ) {
+ KMFolderCachedImap* f = static_cast<KMFolderCachedImap*>( mFolder->storage() );
+ f->slotTroubleshoot();
+ }
+ }
+}
+
+void KMMainWidget::slotInvalidateIMAPFolders() {
+ if ( KMessageBox::warningContinueCancel( this,
+ i18n("Are you sure you want to refresh the IMAP cache?\n"
+ "This will remove all changes that you have done "
+ "locally to your IMAP folders."),
+ i18n("Refresh IMAP Cache"), i18n("&Refresh") ) == KMessageBox::Continue )
+ kmkernel->acctMgr()->invalidateIMAPFolders();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotExpireAll() {
+ KConfig *config = KMKernel::config();
+ int ret = 0;
+
+ KConfigGroupSaver saver(config, "General");
+
+ if (config->readBoolEntry("warn-before-expire", true)) {
+ ret = KMessageBox::warningContinueCancel(KMainWindow::memberList->first(),
+ i18n("Are you sure you want to expire all old messages?"),
+ i18n("Expire Old Messages?"), i18n("Expire"));
+ if (ret != KMessageBox::Continue) {
+ return;
+ }
+ }
+
+ kmkernel->expireAllFoldersNow();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotCompactAll()
+{
+ KCursorSaver busy(KBusyPtr::busy());
+ kmkernel->compactAllFolders();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotOverrideHtml()
+{
+ if( mHtmlPref == mFolderHtmlPref ) {
+ int result = KMessageBox::warningContinueCancel( this,
+ // the warning text is taken from configuredialog.cpp:
+ i18n( "Use of HTML in mail will make you more vulnerable to "
+ "\"spam\" and may increase the likelihood that your system will be "
+ "compromised by other present and anticipated security exploits." ),
+ i18n( "Security Warning" ),
+ i18n( "Use HTML" ),
+ "OverrideHtmlWarning", false);
+ if( result == KMessageBox::Cancel ) {
+ mPreferHtmlAction->setChecked( false );
+ return;
+ }
+ }
+ mFolderHtmlPref = !mFolderHtmlPref;
+ if (mMsgView) {
+ mMsgView->setHtmlOverride(mFolderHtmlPref);
+ mMsgView->update( true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotOverrideHtmlLoadExt()
+{
+ if( mHtmlLoadExtPref == mFolderHtmlLoadExtPref ) {
+ int result = KMessageBox::warningContinueCancel( this,
+ // the warning text is taken from configuredialog.cpp:
+ i18n( "Loading external references in html mail will make you more vulnerable to "
+ "\"spam\" and may increase the likelihood that your system will be "
+ "compromised by other present and anticipated security exploits." ),
+ i18n( "Security Warning" ),
+ i18n( "Load External References" ),
+ "OverrideHtmlLoadExtWarning", false);
+ if( result == KMessageBox::Cancel ) {
+ mPreferHtmlLoadExtAction->setChecked( false );
+ return;
+ }
+ }
+ mFolderHtmlLoadExtPref = !mFolderHtmlLoadExtPref;
+ if (mMsgView) {
+ mMsgView->setHtmlLoadExtOverride(mFolderHtmlLoadExtPref);
+ mMsgView->update( true );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotOverrideThread()
+{
+ mFolderThreadPref = !mFolderThreadPref;
+ mHeaders->setNestedOverride(mFolderThreadPref);
+ mThreadBySubjectAction->setEnabled(mThreadMessagesAction->isChecked());
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotToggleSubjectThreading()
+{
+ mFolderThreadSubjPref = !mFolderThreadSubjPref;
+ mHeaders->setSubjectThreading(mFolderThreadSubjPref);
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotMessageQueuedOrDrafted()
+{
+ if (!kmkernel->folderIsDraftOrOutbox(mFolder))
+ return;
+ if (mMsgView)
+ mMsgView->update(true);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotForwardInlineMsg()
+{
+ KMMessageList* selected = mHeaders->selectedMsgs();
+ KMCommand *command = 0L;
+ if(selected && !selected->isEmpty()) {
+ command = new KMForwardInlineCommand( this, *selected,
+ mFolder->identity() );
+ } else {
+ command = new KMForwardInlineCommand( this, mHeaders->currentMsg(),
+ mFolder->identity() );
+ }
+
+ command->start();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotForwardAttachedMsg()
+{
+ KMMessageList* selected = mHeaders->selectedMsgs();
+ KMCommand *command = 0L;
+ if(selected && !selected->isEmpty()) {
+ command = new KMForwardAttachedCommand( this, *selected, mFolder->identity() );
+ } else {
+ command = new KMForwardAttachedCommand( this, mHeaders->currentMsg(), mFolder->identity() );
+ }
+
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotForwardDigestMsg()
+{
+ KMMessageList* selected = mHeaders->selectedMsgs();
+ KMCommand *command = 0L;
+ if(selected && !selected->isEmpty()) {
+ command = new KMForwardDigestCommand( this, *selected, mFolder->identity() );
+ } else {
+ command = new KMForwardDigestCommand( this, mHeaders->currentMsg(), mFolder->identity() );
+ }
+
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotUseTemplate()
+{
+ newFromTemplate( mHeaders->currentMsg() );
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotResendMsg()
+{
+ KMCommand *command = new KMResendMessageCommand( this, mHeaders->currentMsg() );
+ command->start();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotTrashMsg()
+{
+ mHeaders->deleteMsg();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotDeleteMsg( bool confirmDelete )
+{
+ mHeaders->moveMsgToFolder( 0, confirmDelete );
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotTrashThread()
+{
+ mHeaders->highlightCurrentThread();
+ mHeaders->deleteMsg();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotDeleteThread( bool confirmDelete )
+{
+ mHeaders->highlightCurrentThread();
+ mHeaders->moveMsgToFolder( 0, confirmDelete );
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotRedirectMsg()
+{
+ KMCommand *command = new KMRedirectCommand( this, mHeaders->currentMsg() );
+ command->start();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotCustomReplyToMsg( int tid )
+{
+ QString text = mMsgView? mMsgView->copyText() : "";
+ QString tmpl = mCustomTemplates[ tid ];
+ kdDebug() << "Reply with template: " << tmpl << " (" << tid << ")" << endl;
+ KMCommand *command = new KMCustomReplyToCommand( this,
+ mHeaders->currentMsg(),
+ text,
+ tmpl );
+ command->start();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotCustomReplyAllToMsg( int tid )
+{
+ QString text = mMsgView? mMsgView->copyText() : "";
+ QString tmpl = mCustomTemplates[ tid ];
+ kdDebug() << "Reply to All with template: " << tmpl << " (" << tid << ")" << endl;
+ KMCommand *command = new KMCustomReplyAllToCommand( this,
+ mHeaders->currentMsg(),
+ text,
+ tmpl );
+ command->start();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotCustomForwardMsg( int tid )
+{
+ QString tmpl = mCustomTemplates[ tid ];
+ kdDebug() << "Forward with template: " << tmpl << " (" << tid << ")" << endl;
+ KMMessageList* selected = mHeaders->selectedMsgs();
+ KMCommand *command = 0L;
+ if(selected && !selected->isEmpty()) {
+ command = new KMCustomForwardCommand( this, *selected,
+ mFolder->identity(), tmpl );
+ } else {
+ command = new KMCustomForwardCommand( this, mHeaders->currentMsg(),
+ mFolder->identity(), tmpl );
+ }
+ command->start();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotNoQuoteReplyToMsg()
+{
+ KMCommand *command = new KMNoQuoteReplyToCommand( this, mHeaders->currentMsg() );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotSubjectFilter()
+{
+ KMMessage *msg = mHeaders->currentMsg();
+ if (!msg)
+ return;
+
+ KMCommand *command = new KMFilterCommand( "Subject", msg->subject() );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotMailingListFilter()
+{
+ KMMessage *msg = mHeaders->currentMsg();
+ if (!msg)
+ return;
+
+ KMCommand *command = new KMMailingListFilterCommand( this, msg );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotFromFilter()
+{
+ KMMessage *msg = mHeaders->currentMsg();
+ if (!msg)
+ return;
+
+ AddrSpecList al = msg->extractAddrSpecs( "From" );
+ KMCommand *command;
+ if ( al.empty() )
+ command = new KMFilterCommand( "From", msg->from() );
+ else
+ command = new KMFilterCommand( "From", al.front().asString() );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotToFilter()
+{
+ KMMessage *msg = mHeaders->currentMsg();
+ if (!msg)
+ return;
+
+ KMCommand *command = new KMFilterCommand( "To", msg->to() );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::updateListFilterAction()
+{
+ //Proxy the mListFilterAction to update the action text
+ QCString name;
+ QString value;
+ QString lname = MailingList::name( mHeaders->currentMsg(), name, value );
+ mListFilterAction->setText( i18n("Filter on Mailing-List...") );
+ if ( lname.isNull() )
+ mListFilterAction->setEnabled( false );
+ else {
+ mListFilterAction->setEnabled( true );
+ mListFilterAction->setText( i18n( "Filter on Mailing-List %1..." ).arg( lname ) );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotUndo()
+{
+ mHeaders->undo();
+ updateMessageActions();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotToggleUnread()
+{
+ mFolderTree->toggleColumn(KMFolderTree::unread);
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotToggleTotalColumn()
+{
+ mFolderTree->toggleColumn(KMFolderTree::total, true);
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotToggleSizeColumn()
+{
+ mFolderTree->toggleColumn(KMFolderTree::foldersize);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotJumpToFolder()
+{
+ KMail::KMFolderSelDlg dlg( this, i18n("Jump to Folder"), true );
+ KMFolder* dest;
+
+ if (!dlg.exec()) return;
+ if (!(dest = dlg.folder())) return;
+
+ slotSelectFolder( dest );
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotMoveMsg()
+{
+ KMail::KMFolderSelDlg dlg( this, i18n("Move Message to Folder"), true );
+ KMFolder* dest;
+
+ if (!dlg.exec()) return;
+ if (!(dest = dlg.folder())) return;
+
+ mHeaders->moveMsgToFolder(dest);
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotMoveMsgToFolder( KMFolder *dest)
+{
+ mHeaders->moveMsgToFolder(dest);
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotCopyMsgToFolder( KMFolder *dest)
+{
+ mHeaders->copyMsgToFolder(dest);
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotApplyFilters()
+{
+ mHeaders->applyFiltersOnMsg();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotCheckVacation()
+{
+ updateVactionScriptStatus( false );
+ if ( !kmkernel->askToGoOnline() )
+ return;
+
+ Vacation *vac = new Vacation( this, true /* check only */ );
+ connect( vac, SIGNAL(scriptActive(bool)), SLOT(updateVactionScriptStatus(bool)) );
+}
+
+void KMMainWidget::slotEditVacation()
+{
+ if ( !kmkernel->askToGoOnline() ) {
+ return;
+ }
+
+ if ( mVacation )
+ return;
+
+ mVacation = new Vacation( this );
+ connect( mVacation, SIGNAL(scriptActive(bool)), SLOT(updateVactionScriptStatus(bool)) );
+ if ( mVacation->isUsable() ) {
+ connect( mVacation, SIGNAL(result(bool)), mVacation, SLOT(deleteLater()) );
+ } else {
+ QString msg = i18n("KMail's Out of Office Reply functionality relies on "
+ "server-side filtering. You have not yet configured an "
+ "IMAP server for this.\n"
+ "You can do this on the \"Filtering\" tab of the IMAP "
+ "account configuration.");
+ KMessageBox::sorry( this, msg, i18n("No Server-Side Filtering Configured") );
+
+ delete mVacation; // QGuardedPtr sets itself to 0!
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotDebugSieve()
+{
+#if !defined(NDEBUG)
+ if ( mSieveDebugDialog )
+ return;
+
+ mSieveDebugDialog = new SieveDebugDialog( this );
+ mSieveDebugDialog->exec();
+ delete mSieveDebugDialog;
+#endif
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotStartCertManager()
+{
+ KProcess certManagerProc; // save to create on the heap, since
+ // there is no parent
+ certManagerProc << "kleopatra";
+
+ if( !certManagerProc.start( KProcess::DontCare ) )
+ KMessageBox::error( this, i18n( "Could not start certificate manager; "
+ "please check your installation." ),
+ i18n( "KMail Error" ) );
+ else
+ kdDebug(5006) << "\nslotStartCertManager(): certificate manager started.\n" << endl;
+ // process continues to run even after the KProcess object goes
+ // out of scope here, since it is started in DontCare run mode.
+
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotStartWatchGnuPG()
+{
+ KProcess certManagerProc;
+ certManagerProc << "kwatchgnupg";
+
+ if( !certManagerProc.start( KProcess::DontCare ) )
+ KMessageBox::error( this, i18n( "Could not start GnuPG LogViewer (kwatchgnupg); "
+ "please check your installation." ),
+ i18n( "KMail Error" ) );
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotCopyMsg()
+{
+ KMail::KMFolderSelDlg dlg( this, i18n("Copy Message to Folder"), true );
+ KMFolder* dest;
+
+ if (!dlg.exec()) return;
+ if (!(dest = dlg.folder())) return;
+
+ mHeaders->copyMsgToFolder(dest);
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotPrintMsg()
+{
+ 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(),
+ htmlOverride, htmlLoadExtOverride,
+ useFixedFont, overrideEncoding() );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotConfigChanged()
+{
+ readConfig();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotSaveMsg()
+{
+ KMMessage *msg = mHeaders->currentMsg();
+ if (!msg)
+ return;
+ KMSaveMsgCommand *saveCommand = new KMSaveMsgCommand( this,
+ *mHeaders->selectedMsgs() );
+
+ if (saveCommand->url().isEmpty())
+ delete saveCommand;
+ else
+ saveCommand->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotOpenMsg()
+{
+ KMOpenMsgCommand *openCommand = new KMOpenMsgCommand( this, 0, overrideEncoding() );
+
+ openCommand->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotSaveAttachments()
+{
+ KMMessage *msg = mHeaders->currentMsg();
+ if (!msg)
+ return;
+ KMSaveAttachmentsCommand *saveCommand = new KMSaveAttachmentsCommand( this,
+ *mHeaders->selectedMsgs() );
+ saveCommand->start();
+}
+
+void KMMainWidget::slotOnlineStatus()
+{
+ // KMKernel will emit a signal when we toggle the network state that is caught by
+ // KMMainWidget::slotUpdateOnlineStatus to update our GUI
+ if ( GlobalSettings::self()->networkState() == GlobalSettings::EnumNetworkState::Online ) {
+ // if online; then toggle and set it offline.
+ kmkernel->stopNetworkJobs();
+ } else {
+ kmkernel->resumeNetworkJobs();
+ }
+}
+
+void KMMainWidget::slotUpdateOnlineStatus( GlobalSettings::EnumNetworkState::type )
+{
+ if ( GlobalSettings::self()->networkState() == GlobalSettings::EnumNetworkState::Online )
+ actionCollection()->action( "online_status" )->setText( i18n("Work Offline") );
+ else
+ actionCollection()->action( "online_status" )->setText( i18n("Work Online") );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotSendQueued()
+{
+ if ( !kmkernel->askToGoOnline() ) {
+ return;
+ }
+
+ kmkernel->msgSender()->sendQueued();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotSendQueuedVia( int item )
+{
+ if ( !kmkernel->askToGoOnline() ) {
+ return;
+ }
+
+ QStringList availTransports= KMail::TransportManager::transportNames();
+ QString customTransport = availTransports[ item ];
+
+ kmkernel->msgSender()->sendQueued( customTransport );
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotViewChange()
+{
+ if(mBodyPartsMenu->isItemChecked(mBodyPartsMenu->idAt(0)))
+ {
+ mBodyPartsMenu->setItemChecked(mBodyPartsMenu->idAt(0),false);
+ mBodyPartsMenu->setItemChecked(mBodyPartsMenu->idAt(1),true);
+ }
+ else if(mBodyPartsMenu->isItemChecked(mBodyPartsMenu->idAt(1)))
+ {
+ mBodyPartsMenu->setItemChecked(mBodyPartsMenu->idAt(1),false);
+ mBodyPartsMenu->setItemChecked(mBodyPartsMenu->idAt(0),true);
+ }
+
+ //mMsgView->setInline(!mMsgView->isInline());
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::folderSelectedUnread( KMFolder* aFolder )
+{
+ folderSelected( aFolder, true );
+ slotChangeCaption( mFolderTree->currentItem() );
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::folderSelected()
+{
+ folderSelected( mFolder );
+ updateFolderMenu();
+ // opened() before the getAndCheckFolder() in folderSelected
+ if ( mFolder && mFolder->folderType() == KMFolderTypeImap )
+ mFolder->close("mainwidget");
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::folderSelected( KMFolder* aFolder, bool forceJumpToUnread )
+{
+ KCursorSaver busy(KBusyPtr::busy());
+
+ if (mMsgView)
+ mMsgView->clear(true);
+
+ if ( mFolder && mFolder->folderType() == KMFolderTypeImap && !mFolder->noContent() )
+ {
+ KMFolderImap *imap = static_cast<KMFolderImap*>(mFolder->storage());
+ if ( mFolder->needsCompacting() && imap->autoExpunge() )
+ imap->expungeFolder(imap, true);
+ }
+
+ // Re-enable the msg list and quicksearch if we're showing a splash
+ // screen. This is true either if there's no active folder, or if we
+ // have a timer that is no longer active (i.e. it has already fired)
+ // To make the if() a bit more complicated, we suppress the hiding
+ // when the new folder is also an IMAP folder, because that's an
+ // async operation and we don't want flicker if it results in just
+ // a new splash.
+ bool newFolder = ( (KMFolder*)mFolder != aFolder );
+ bool isNewImapFolder = aFolder && aFolder->folderType() == KMFolderTypeImap && newFolder;
+ if( !mFolder
+ || ( !isNewImapFolder && mShowBusySplashTimer )
+ || ( newFolder && mShowingOfflineScreen && !( isNewImapFolder && kmkernel->isOffline() ) ) ) {
+ if ( mMsgView ) {
+ mMsgView->enableMsgDisplay();
+ mMsgView->clear( true );
+ }
+ if( mSearchAndHeaders && mHeaders )
+ mSearchAndHeaders->show();
+ mShowingOfflineScreen = false;
+ }
+
+ // Delete any pending timer, if needed it will be recreated below
+ delete mShowBusySplashTimer;
+ mShowBusySplashTimer = 0;
+
+ if ( newFolder )
+ writeFolderConfig();
+ if ( mFolder ) {
+ disconnect( mFolder, SIGNAL( changed() ),
+ this, SLOT( updateMarkAsReadAction() ) );
+ disconnect( mFolder, SIGNAL( msgHeaderChanged( KMFolder*, int ) ),
+ this, SLOT( updateMarkAsReadAction() ) );
+ disconnect( mFolder, SIGNAL( msgAdded( int ) ),
+ this, SLOT( updateMarkAsReadAction() ) );
+ disconnect( mFolder, SIGNAL( msgRemoved( KMFolder * ) ),
+ this, SLOT( updateMarkAsReadAction() ) );
+ }
+
+ mFolder = aFolder;
+
+ if ( aFolder && aFolder->folderType() == KMFolderTypeImap )
+ {
+ if ( kmkernel->isOffline() ) {
+ showOfflinePage();
+ return;
+ }
+ KMFolderImap *imap = static_cast<KMFolderImap*>(aFolder->storage());
+ if ( newFolder && !mFolder->noContent() )
+ {
+ imap->open("mainwidget"); // will be closed in the folderSelected slot
+ // first get new headers before we select the folder
+ imap->setSelected( true );
+ connect( imap, SIGNAL( folderComplete( KMFolderImap*, bool ) ),
+ this, SLOT( folderSelected() ) );
+ imap->getAndCheckFolder();
+ mHeaders->setFolder( 0 );
+ updateFolderMenu();
+ mForceJumpToUnread = forceJumpToUnread;
+
+ // Set a timer to show a splash screen if fetching folder contents
+ // takes more than the amount of seconds configured in the kmailrc (default 1000 msec)
+ mShowBusySplashTimer = new QTimer( this );
+ connect( mShowBusySplashTimer, SIGNAL( timeout() ), this, SLOT( slotShowBusySplash() ) );
+ mShowBusySplashTimer->start( GlobalSettings::self()->folderLoadingTimeout(), true );
+ return;
+ } else {
+ // the folder is complete now - so go ahead
+ disconnect( imap, SIGNAL( folderComplete( KMFolderImap*, bool ) ),
+ this, SLOT( folderSelected() ) );
+ forceJumpToUnread = mForceJumpToUnread;
+ }
+ }
+
+ if ( mFolder ) { // == 0 -> pointing to toplevel ("Welcome to KMail") folder
+ connect( mFolder, SIGNAL( changed() ),
+ this, SLOT( updateMarkAsReadAction() ) );
+ connect( mFolder, SIGNAL( msgHeaderChanged( KMFolder*, int ) ),
+ this, SLOT( updateMarkAsReadAction() ) );
+ connect( mFolder, SIGNAL( msgAdded( int ) ),
+ this, SLOT( updateMarkAsReadAction() ) );
+ connect( mFolder, SIGNAL( msgRemoved(KMFolder *) ),
+ this, SLOT( updateMarkAsReadAction() ) );
+ }
+ readFolderConfig();
+ if (mMsgView)
+ {
+ mMsgView->setHtmlOverride(mFolderHtmlPref);
+ mMsgView->setHtmlLoadExtOverride(mFolderHtmlLoadExtPref);
+ }
+ mHeaders->setFolder( mFolder, forceJumpToUnread );
+ updateMessageActions();
+ updateFolderMenu();
+ if (!aFolder)
+ slotIntro();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotShowBusySplash()
+{
+ if ( mReaderWindowActive )
+ {
+ mMsgView->displayBusyPage();
+ // hide widgets that are in the way:
+ if ( mSearchAndHeaders && mHeaders && mLongFolderList )
+ mSearchAndHeaders->hide();
+ }
+}
+
+void KMMainWidget::showOfflinePage()
+{
+ if ( !mReaderWindowActive ) return;
+ mShowingOfflineScreen = true;
+
+ mMsgView->displayOfflinePage();
+ // hide widgets that are in the way:
+ if ( mSearchAndHeaders && mHeaders && mLongFolderList )
+ mSearchAndHeaders->hide();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotMsgSelected(KMMessage *msg)
+{
+ if ( msg && msg->parent() && !msg->isComplete() )
+ {
+ if ( msg->transferInProgress() )
+ return;
+ mMsgView->clear();
+ mMsgView->setWaitingForSerNum( msg->getMsgSerNum() );
+
+ if ( mJob ) {
+ disconnect( mJob, 0, mMsgView, 0 );
+ delete mJob;
+ }
+ mJob = msg->parent()->createJob( msg, FolderJob::tGetMessage, 0,
+ "STRUCTURE", mMsgView->attachmentStrategy() );
+ connect(mJob, SIGNAL(messageRetrieved(KMMessage*)),
+ mMsgView, SLOT(slotMessageArrived(KMMessage*)));
+ mJob->start();
+ } else {
+ mMsgView->setMsg(msg);
+ }
+ // reset HTML override to the folder setting
+ mMsgView->setHtmlOverride(mFolderHtmlPref);
+ mMsgView->setHtmlLoadExtOverride(mFolderHtmlLoadExtPref);
+ mMsgView->setDecryptMessageOverwrite( false );
+ mMsgView->setShowSignatureDetails( false );
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotMsgChanged()
+{
+ mHeaders->msgChanged();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotSelectFolder(KMFolder* folder)
+{
+ QListViewItem* item = mFolderTree->indexOfFolder(folder);
+ if ( item ) {
+ mFolderTree->ensureItemVisible( item );
+ mFolderTree->doFolderSelected( item );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotSelectMessage(KMMessage* msg)
+{
+ int idx = mFolder->find(msg);
+ if (idx != -1) {
+ mHeaders->setCurrentMsg(idx);
+ if (mMsgView)
+ mMsgView->setMsg(msg);
+ else
+ slotMsgActivated(msg);
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotReplaceMsgByUnencryptedVersion()
+{
+ kdDebug(5006) << "KMMainWidget::slotReplaceMsgByUnencryptedVersion()" << endl;
+ KMMessage* oldMsg = mHeaders->currentMsg();
+ if( oldMsg ) {
+ kdDebug(5006) << "KMMainWidget - old message found" << endl;
+ if( oldMsg->hasUnencryptedMsg() ) {
+ kdDebug(5006) << "KMMainWidget - extra unencrypted message found" << endl;
+ KMMessage* newMsg = oldMsg->unencryptedMsg();
+ // adjust the message id
+ {
+ QString msgId( oldMsg->msgId() );
+ QString prefix("DecryptedMsg.");
+ int oldIdx = msgId.find(prefix, 0, false);
+ if( -1 == oldIdx ) {
+ int leftAngle = msgId.findRev( '<' );
+ msgId = msgId.insert( (-1 == leftAngle) ? 0 : ++leftAngle, prefix );
+ }
+ else {
+ // toggle between "DecryptedMsg." and "DeCryptedMsg."
+ // to avoid same message id
+ QCharRef c = msgId[ oldIdx+2 ];
+ if( 'C' == c )
+ c = 'c';
+ else
+ c = 'C';
+ }
+ newMsg->setMsgId( msgId );
+ mMsgView->setIdOfLastViewedMessage( msgId );
+ }
+ // insert the unencrypted message
+ kdDebug(5006) << "KMMainWidget - adding unencrypted message to folder" << endl;
+ mFolder->addMsg( newMsg );
+ /* Figure out its index in the folder for selecting. This must be count()-1,
+ * since we append. Be safe and do find, though, just in case. */
+ int newMsgIdx = mFolder->find( newMsg );
+ Q_ASSERT( newMsgIdx != -1 );
+ /* we need this unget, to have the message displayed correctly initially */
+ mFolder->unGetMsg( newMsgIdx );
+ int idx = mFolder->find( oldMsg );
+ Q_ASSERT( idx != -1 );
+ /* only select here, so the old one is not un-Gotten before, which would
+ * render the pointer we hold invalid so that find would fail */
+ mHeaders->setCurrentItemByIndex( newMsgIdx );
+ // remove the old one
+ if ( idx != -1 ) {
+ kdDebug(5006) << "KMMainWidget - deleting encrypted message" << endl;
+ mFolder->take( idx );
+ }
+
+ kdDebug(5006) << "KMMainWidget - updating message actions" << endl;
+ updateMessageActions();
+
+ kdDebug(5006) << "KMMainWidget - done." << endl;
+ } else
+ kdDebug(5006) << "KMMainWidget - NO EXTRA UNENCRYPTED MESSAGE FOUND" << endl;
+ } else
+ kdDebug(5006) << "KMMainWidget - PANIC: NO OLD MESSAGE FOUND" << endl;
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotSetThreadStatusNew()
+{
+ mHeaders->setThreadStatus(KMMsgStatusNew);
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotSetThreadStatusUnread()
+{
+ mHeaders->setThreadStatus(KMMsgStatusUnread);
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotSetThreadStatusFlag()
+{
+ mHeaders->setThreadStatus(KMMsgStatusFlag, true);
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotSetThreadStatusRead()
+{
+ mHeaders->setThreadStatus(KMMsgStatusRead);
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotSetThreadStatusTodo()
+{
+ mHeaders->setThreadStatus(KMMsgStatusTodo, true);
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotSetThreadStatusWatched()
+{
+ mHeaders->setThreadStatus(KMMsgStatusWatched, true);
+ if (mWatchThreadAction->isChecked()) {
+ mIgnoreThreadAction->setChecked(false);
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotSetThreadStatusIgnored()
+{
+ mHeaders->setThreadStatus(KMMsgStatusIgnored, true);
+ if (mIgnoreThreadAction->isChecked()) {
+ mWatchThreadAction->setChecked(false);
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotNextMessage() { mHeaders->nextMessage(); }
+void KMMainWidget::slotNextUnreadMessage()
+{
+ if ( !mHeaders->nextUnreadMessage() )
+ if ( GlobalSettings::self()->loopOnGotoUnread() == GlobalSettings::EnumLoopOnGotoUnread::LoopInAllFolders )
+ mFolderTree->nextUnreadFolder(true);
+}
+void KMMainWidget::slotNextImportantMessage() {
+ //mHeaders->nextImportantMessage();
+}
+void KMMainWidget::slotPrevMessage() { mHeaders->prevMessage(); }
+void KMMainWidget::slotPrevUnreadMessage()
+{
+ if ( !mHeaders->prevUnreadMessage() )
+ if ( GlobalSettings::self()->loopOnGotoUnread() == GlobalSettings::EnumLoopOnGotoUnread::LoopInAllFolders )
+ mFolderTree->prevUnreadFolder();
+}
+void KMMainWidget::slotPrevImportantMessage() {
+ //mHeaders->prevImportantMessage();
+}
+
+void KMMainWidget::slotDisplayCurrentMessage()
+{
+ if ( mHeaders->currentMsg() )
+ slotMsgActivated( mHeaders->currentMsg() );
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotMsgActivated(KMMessage *msg)
+{
+ if ( !msg ) return;
+ if ( msg->parent() && !msg->isComplete() ) {
+ FolderJob *job = msg->parent()->createJob( msg );
+ connect( job, SIGNAL( messageRetrieved( KMMessage* ) ),
+ SLOT( slotMsgActivated( KMMessage* ) ) );
+ job->start();
+ return;
+ }
+
+ if (kmkernel->folderIsDraftOrOutbox( mFolder ) ) {
+ mMsgActions->editCurrentMessage();
+ return;
+ }
+ if ( kmkernel->folderIsTemplates( mFolder ) ) {
+ slotUseTemplate();
+ return;
+ }
+
+ assert( msg != 0 );
+ KMReaderMainWin *win = new KMReaderMainWin( mFolderHtmlPref, mFolderHtmlLoadExtPref );
+ KConfigGroup reader( KMKernel::config(), "Reader" );
+ bool useFixedFont = mMsgView ? mMsgView->isFixedFont()
+ : reader.readBoolEntry( "useFixedFont", false );
+ win->setUseFixedFont( useFixedFont );
+ KMMessage *newMessage = new KMMessage(*msg);
+ newMessage->setParent( msg->parent() );
+ newMessage->setMsgSerNum( msg->getMsgSerNum() );
+ newMessage->setReadyToShow( true );
+ win->showMsg( overrideEncoding(), newMessage );
+ win->show();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotMarkAll()
+{
+ mHeaders->selectAll( true );
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotMsgPopup(KMMessage&, const KURL &aUrl, const QPoint& aPoint)
+{
+ KPopupMenu * menu = new KPopupMenu;
+ updateMessageMenu();
+ mUrlCurrent = aUrl;
+
+ bool urlMenuAdded = false;
+
+ if (!aUrl.isEmpty())
+ {
+ if (aUrl.protocol() == "mailto")
+ {
+ // popup on a mailto URL
+ mMsgView->mailToComposeAction()->plug( menu );
+ mMsgView->mailToReplyAction()->plug( menu );
+ mMsgView->mailToForwardAction()->plug( menu );
+
+ menu->insertSeparator();
+ mMsgView->addAddrBookAction()->plug( menu );
+ mMsgView->openAddrBookAction()->plug( menu );
+ mMsgView->copyURLAction()->plug( menu );
+ mMsgView->startImChatAction()->plug( menu );
+ // only enable if our KIMProxy is functional
+ mMsgView->startImChatAction()->setEnabled( kmkernel->imProxy()->initialize() );
+
+ } else {
+ // popup on a not-mailto URL
+ mMsgView->urlOpenAction()->plug( menu );
+ mMsgView->addBookmarksAction()->plug( menu );
+ mMsgView->urlSaveAsAction()->plug( menu );
+ mMsgView->copyURLAction()->plug( menu );
+ }
+ if ( aUrl.protocol() == "im" )
+ {
+ // popup on an IM address
+ // no need to check the KIMProxy is initialized, as these protocols will
+ // only be present if it is.
+ mMsgView->startImChatAction()->plug( menu );
+ }
+
+ urlMenuAdded=true;
+ kdDebug( 0 ) << k_funcinfo << " URL is: " << aUrl << endl;
+ }
+
+
+ if(mMsgView && !mMsgView->copyText().isEmpty()) {
+ if ( urlMenuAdded )
+ menu->insertSeparator();
+ mMsgActions->replyMenu()->plug(menu);
+ menu->insertSeparator();
+
+ mMsgView->copyAction()->plug( menu );
+ mMsgView->selectAllAction()->plug( menu );
+ } else if ( !urlMenuAdded )
+ {
+ // popup somewhere else (i.e., not a URL) on the message
+
+ if (!mHeaders->currentMsg()) // no messages
+ {
+ delete menu;
+ return;
+ }
+
+
+ if ( mFolder->isTemplates() ) {
+ mUseAction->plug( menu );
+ } else {
+
+ if ( !mFolder->isSent() )
+ mMsgActions->replyMenu()->plug( menu );
+ mForwardActionMenu->plug( menu );
+ }
+ editAction()->plug(menu);
+ menu->insertSeparator();
+
+ mCopyActionMenu->plug( menu );
+ mMoveActionMenu->plug( menu );
+
+ menu->insertSeparator();
+
+ mMsgActions->messageStatusMenu()->plug( menu );
+ menu->insertSeparator();
+
+ viewSourceAction()->plug(menu);
+ if(mMsgView) {
+ mMsgView->toggleFixFontAction()->plug(menu);
+ }
+ menu->insertSeparator();
+ mPrintAction->plug( menu );
+ mSaveAsAction->plug( menu );
+ mSaveAttachmentsAction->plug( menu );
+
+ menu->insertSeparator();
+ if( mFolder->isTrash() )
+ mDeleteAction->plug( menu );
+ else
+ mTrashAction->plug( menu );
+
+ menu->insertSeparator();
+ mMsgActions->createTodoAction()->plug( menu );
+ }
+ KAcceleratorManager::manage(menu);
+ menu->exec(aPoint, 0);
+ delete menu;
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::getAccountMenu()
+{
+ QStringList actList;
+
+ mActMenu->clear();
+ actList = kmkernel->acctMgr()->getAccounts();
+ QStringList::Iterator it;
+ int id = 0;
+ for(it = actList.begin(); it != actList.end() ; ++it, id++)
+ mActMenu->insertItem((*it).replace("&", "&&"), id);
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::getTransportMenu()
+{
+ QStringList availTransports;
+
+ mSendMenu->clear();
+ availTransports = KMail::TransportManager::transportNames();
+ QStringList::Iterator it;
+ int id = 0;
+ for(it = availTransports.begin(); it != availTransports.end() ; ++it, id++)
+ mSendMenu->insertItem((*it).replace("&", "&&"), id);
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::updateCustomTemplateMenus()
+{
+ if ( !mCustomTemplateActions.isEmpty() ) {
+ QPtrList<KAction>::iterator ait = mCustomTemplateActions.begin();
+ for ( ; ait != mCustomTemplateActions.end() ; ++ait ) {
+ (*ait)->unplugAll();
+ delete (*ait);
+ }
+ mCustomTemplateActions.clear();
+ }
+
+ delete mCustomReplyActionMenu;
+ delete mCustomReplyAllActionMenu;
+ delete mCustomForwardActionMenu;
+
+ delete mCustomReplyMapper;
+ delete mCustomReplyAllMapper;
+ delete mCustomForwardMapper;
+
+ mCustomForwardActionMenu =
+ new KActionMenu( i18n("Forward With Custom Template"),
+ "mail_custom_forward",
+ actionCollection(), "custom_forward" );
+ QSignalMapper *mCustomForwardMapper = new QSignalMapper( this );
+ connect( mCustomForwardMapper, SIGNAL( mapped( int ) ),
+ this, SLOT( slotCustomForwardMsg( int ) ) );
+ mForwardActionMenu->insert( mCustomForwardActionMenu );
+
+ mCustomReplyActionMenu =
+ new KActionMenu( i18n("Reply With Custom Template"), "mail_custom_reply",
+ actionCollection(), "custom_reply" );
+ QSignalMapper *mCustomReplyMapper = new QSignalMapper( this );
+ connect( mCustomReplyMapper, SIGNAL( mapped( int ) ),
+ this, SLOT( slotCustomReplyToMsg( int ) ) );
+ mMsgActions->replyMenu()->insert( mCustomReplyActionMenu );
+
+ mCustomReplyAllActionMenu =
+ new KActionMenu( i18n("Reply to All With Custom Template"),
+ "mail_custom_reply_all",
+ actionCollection(), "custom_reply_all" );
+ QSignalMapper *mCustomReplyAllMapper = new QSignalMapper( this );
+ connect( mCustomReplyAllMapper, SIGNAL( mapped( int ) ),
+ this, SLOT( slotCustomReplyAllToMsg( int ) ) );
+ mMsgActions->replyMenu()->insert( mCustomReplyAllActionMenu );
+
+ mCustomTemplates.clear();
+
+ QStringList list = GlobalSettingsBase::self()->customTemplates();
+ QStringList::iterator it = list.begin();
+ int idx = 0;
+ int replyc = 0;
+ int replyallc = 0;
+ int forwardc = 0;
+ for ( ; it != list.end(); ++it ) {
+ CTemplates t( *it );
+ mCustomTemplates.append( *it );
+
+ KAction *action;
+ switch ( t.type() ) {
+ case CustomTemplates::TReply:
+ action = new KAction( (*it).replace( "&", "&&" ),
+ KShortcut( t.shortcut() ),
+ mCustomReplyMapper,
+ SLOT( map() ),
+ actionCollection(),
+ (*it).utf8() );
+ mCustomReplyMapper->setMapping( action, idx );
+ mCustomReplyActionMenu->insert( action, idx );
+ mCustomTemplateActions.append( action );
+ ++replyc;
+ break;
+ case CustomTemplates::TReplyAll:
+ action = new KAction( (*it).replace( "&", "&&" ),
+ KShortcut( t.shortcut() ),
+ mCustomReplyAllMapper,
+ SLOT( map() ),
+ actionCollection(),
+ (*it).utf8() );
+ mCustomReplyAllMapper->setMapping( action, idx );
+ mCustomReplyAllActionMenu->insert( action, idx );
+ mCustomTemplateActions.append( action );
+ ++replyallc;
+ break;
+ case CustomTemplates::TForward:
+ action = new KAction( (*it).replace( "&", "&&" ),
+ KShortcut( t.shortcut() ),
+ mCustomForwardMapper,
+ SLOT( map() ),
+ actionCollection(),
+ (*it).utf8() );
+ mCustomForwardMapper->setMapping( action, idx );
+ mCustomForwardActionMenu->insert( action, idx );
+ mCustomTemplateActions.append( action );
+ ++forwardc;
+ break;
+ case CustomTemplates::TUniversal:
+ action = new KAction( (*it).replace( "&", "&&" ),
+ KShortcut::null(),
+ mCustomReplyMapper,
+ SLOT( map() ),
+ actionCollection(),
+ (*it).utf8() );
+ mCustomReplyMapper->setMapping( action, idx );
+ mCustomReplyActionMenu->insert( action, idx );
+ mCustomTemplateActions.append( action );
+ ++replyc;
+ action = new KAction( (*it).replace( "&", "&&" ),
+ KShortcut::null(),
+ mCustomReplyAllMapper,
+ SLOT( map() ),
+ actionCollection(),
+ (*it).utf8() );
+ mCustomReplyAllMapper->setMapping( action, idx );
+ mCustomReplyAllActionMenu->insert( action, idx );
+ mCustomTemplateActions.append( action );
+ ++replyallc;
+ action = new KAction( (*it).replace( "&", "&&" ),
+ KShortcut::null(),
+ mCustomForwardMapper,
+ SLOT( map() ),
+ actionCollection(),
+ (*it).utf8() );
+ mCustomForwardMapper->setMapping( action, idx );
+ mCustomForwardActionMenu->insert( action, idx );
+ mCustomTemplateActions.append( action );
+ ++forwardc;
+ break;
+ }
+
+ ++idx;
+ }
+ if ( !replyc ) {
+ mCustomReplyActionMenu->popupMenu()->insertItem( i18n( "(no custom templates)" ), 0 );
+ mCustomReplyActionMenu->popupMenu()->setItemEnabled( 0, false );
+ mCustomReplyActionMenu->setEnabled(false);
+ }
+ if ( !replyallc ) {
+ mCustomReplyAllActionMenu->popupMenu()->insertItem( i18n( "(no custom templates)" ), 0 );
+ mCustomReplyAllActionMenu->popupMenu()->setItemEnabled( 0, false );
+ mCustomReplyAllActionMenu->setEnabled(false);
+ }
+ if ( !forwardc ) {
+ mCustomForwardActionMenu->popupMenu()->insertItem( i18n( "(no custom templates)" ), 0 );
+ mCustomForwardActionMenu->popupMenu()->setItemEnabled( 0, false );
+ mCustomForwardActionMenu->setEnabled(false);
+ }
+
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::setupActions()
+{
+ mMsgActions = new KMail::MessageActions( actionCollection(), this );
+ mMsgActions->setMessageView( mMsgView );
+
+ //----- File Menu
+ mSaveAsAction = new KAction( i18n("Save &As..."), "filesave",
+ KStdAccel::shortcut(KStdAccel::Save),
+ this, SLOT(slotSaveMsg()), actionCollection(), "file_save_as" );
+
+ mOpenAction = KStdAction::open( this, SLOT( slotOpenMsg() ),
+ actionCollection() );
+
+ (void) new KAction( i18n("&Compact All Folders"), 0,
+ this, SLOT(slotCompactAll()),
+ actionCollection(), "compact_all_folders" );
+
+ (void) new KAction( i18n("&Expire All Folders"), 0,
+ this, SLOT(slotExpireAll()),
+ actionCollection(), "expire_all_folders" );
+
+ (void) new KAction( i18n("&Refresh Local IMAP Cache"), "refresh",
+ this, SLOT(slotInvalidateIMAPFolders()),
+ actionCollection(), "file_invalidate_imap_cache" );
+
+ (void) new KAction( i18n("Empty All &Trash Folders"), 0,
+ KMKernel::self(), SLOT(slotEmptyTrash()),
+ actionCollection(), "empty_trash" );
+
+ (void) new KAction( i18n("Check &Mail"), "mail_get", CTRL+Key_L,
+ this, SLOT(slotCheckMail()),
+ actionCollection(), "check_mail" );
+
+ mFavoritesCheckMailAction = new KAction( i18n("Check Mail in Favorite Folders"),
+ "mail_get", CTRL+SHIFT+Key_L, 0, 0,
+ actionCollection(), "favorite_check_mail" );
+ if ( mFavoriteFolderView )
+ connect( mFavoritesCheckMailAction, SIGNAL(activated()), mFavoriteFolderView, SLOT(checkMail()) );
+
+ KActionMenu *actActionMenu = new
+ KActionMenu( i18n("Check Mail &In"), "mail_get", actionCollection(),
+ "check_mail_in" );
+ actActionMenu->setDelayed(true); //needed for checking "all accounts"
+
+ connect(actActionMenu,SIGNAL(activated()),this,SLOT(slotCheckMail()));
+
+ mActMenu = actActionMenu->popupMenu();
+ connect(mActMenu,SIGNAL(activated(int)),this,SLOT(slotCheckOneAccount(int)));
+ connect(mActMenu,SIGNAL(aboutToShow()),this,SLOT(getAccountMenu()));
+
+ (void) new KAction( i18n("&Send Queued Messages"), "mail_send", 0, this,
+ SLOT(slotSendQueued()), actionCollection(), "send_queued");
+
+ (void) new KAction( i18n("Online Status (unknown)"), "online_status", 0, this,
+ SLOT(slotOnlineStatus()), actionCollection(), "online_status");
+
+ KActionMenu *sendActionMenu = new
+ KActionMenu( i18n("Send Queued Messages Via"), "mail_send_via", actionCollection(),
+ "send_queued_via" );
+ sendActionMenu->setDelayed(true);
+
+ mSendMenu = sendActionMenu->popupMenu();
+ connect(mSendMenu,SIGNAL(activated(int)), this, SLOT(slotSendQueuedVia(int)));
+ connect(mSendMenu,SIGNAL(aboutToShow()),this,SLOT(getTransportMenu()));
+
+ KAction *act;
+ //----- Tools menu
+ if (parent()->inherits("KMMainWin")) {
+ act = new KAction( i18n("&Address Book..."), "contents", 0, this,
+ SLOT(slotAddrBook()), actionCollection(), "addressbook" );
+ if (KStandardDirs::findExe("kaddressbook").isEmpty()) act->setEnabled(false);
+ }
+
+ act = new KAction( i18n("Certificate Manager..."), "pgp-keys", 0, this,
+ SLOT(slotStartCertManager()), actionCollection(), "tools_start_certman");
+ // disable action if no certman binary is around
+ if (KStandardDirs::findExe("kleopatra").isEmpty()) act->setEnabled(false);
+
+ act = new KAction( i18n("GnuPG Log Viewer..."), "pgp-keys", 0, this,
+ SLOT(slotStartWatchGnuPG()), actionCollection(), "tools_start_kwatchgnupg");
+ // disable action if no kwatchgnupg binary is around
+ if (KStandardDirs::findExe("kwatchgnupg").isEmpty()) act->setEnabled(false);
+
+ act = new KAction( i18n("&Import Messages..."), "fileopen", 0, this,
+ SLOT(slotImport()), actionCollection(), "import" );
+ if (KStandardDirs::findExe("kmailcvt").isEmpty()) act->setEnabled(false);
+
+#if !defined(NDEBUG)
+ (void) new KAction( i18n("&Debug Sieve..."),
+ "idea", 0, this, SLOT(slotDebugSieve()),
+ actionCollection(), "tools_debug_sieve" );
+#endif
+
+ if ( GlobalSettings::allowOutOfOfficeSettings() ) {
+ (void) new KAction( i18n("Edit \"Out of Office\" Replies..."),
+ "configure", 0, this, SLOT(slotEditVacation()),
+ actionCollection(), "tools_edit_vacation" );
+
+ }
+
+ (void) new KAction( i18n("Filter &Log Viewer..."), 0, this,
+ SLOT(slotFilterLogViewer()), actionCollection(), "filter_log_viewer" );
+
+ (void) new KAction( i18n("&Anti-Spam Wizard..."), 0, this,
+ SLOT(slotAntiSpamWizard()), actionCollection(), "antiSpamWizard" );
+ (void) new KAction( i18n("&Anti-Virus Wizard..."), 0, this,
+ SLOT(slotAntiVirusWizard()), actionCollection(), "antiVirusWizard" );
+
+ //----- Edit Menu
+ mTrashAction = new KAction( KGuiItem( i18n("&Move to Trash"), "edittrash",
+ i18n("Move message to trashcan") ),
+ Key_Delete, this, SLOT(slotTrashMsg()),
+ actionCollection(), "move_to_trash" );
+
+ /* The delete action is nowhere in the gui, by default, so we need to make
+ * sure it is plugged into the KAccel now, since that won't happen on
+ * XMLGui construction or manual ->plug(). This is only a problem when run
+ * as a part, though. */
+ mDeleteAction = new KAction( i18n("&Delete"), "editdelete", SHIFT+Key_Delete, this,
+ SLOT(slotDeleteMsg()), actionCollection(), "delete" );
+ mDeleteAction->plugAccel( actionCollection()->kaccel() );
+
+ mTrashThreadAction = new KAction( KGuiItem( i18n("M&ove Thread to Trash"), "edittrash",
+ i18n("Move thread to trashcan") ),
+ CTRL+Key_Delete, this, SLOT(slotTrashThread()),
+ actionCollection(), "move_thread_to_trash" );
+
+ mDeleteThreadAction = new KAction( i18n("Delete T&hread"), "editdelete", CTRL+SHIFT+Key_Delete, this,
+ SLOT(slotDeleteThread()), actionCollection(), "delete_thread" );
+
+
+ (void) new KAction( i18n("&Find Messages..."), "mail_find", Key_S, this,
+ SLOT(slotRequestFullSearchFromQuickSearch()), actionCollection(), "search_messages" );
+
+ mFindInMessageAction = new KAction( i18n("&Find in Message..."), "find", KStdAccel::shortcut(KStdAccel::Find), this,
+ SLOT(slotFind()), actionCollection(), "find_in_messages" );
+
+ (void) new KAction( i18n("Select &All Messages"), KStdAccel::selectAll(), this,
+ SLOT(slotMarkAll()), actionCollection(), "mark_all_messages" );
+
+ //----- Folder Menu
+ mNewFolderAction = new KAction( i18n("&New Folder..."), "folder_new", 0, mFolderTree,
+ SLOT(addChildFolder()), actionCollection(), "new_folder" );
+
+ mModifyFolderAction = new KAction( i18n("&Properties"), "configure", 0, this,
+ SLOT(slotModifyFolder()), actionCollection(), "modify" );
+
+ mFolderMailingListPropertiesAction = new KAction( i18n("&Mailing List Management..."),
+ /*"folder_mailinglist_properties",*/ 0, this, SLOT( slotFolderMailingListProperties() ),
+ actionCollection(), "folder_mailinglist_properties" );
+
+ mFolderShortCutCommandAction = new KAction( i18n("&Assign Shortcut..."), "configure_shortcuts",
+ 0, this, SLOT( slotFolderShortcutCommand() ), actionCollection(),
+ "folder_shortcut_command" );
+
+
+ mMarkAllAsReadAction = new KAction( i18n("Mark All Messages as &Read"), "goto", 0, this,
+ SLOT(slotMarkAllAsRead()), actionCollection(), "mark_all_as_read" );
+
+ mExpireFolderAction = new KAction(i18n("&Expiration Settings"), 0, this, SLOT(slotExpireFolder()),
+ actionCollection(), "expire");
+
+ mCompactFolderAction = new KAction( i18n("&Compact Folder"), 0, this,
+ SLOT(slotCompactFolder()), actionCollection(), "compact" );
+
+ mRefreshFolderAction = new KAction( i18n("Check Mail &in This Folder"), "reload",
+ KStdAccel::shortcut( KStdAccel::Reload ), this,
+ SLOT(slotRefreshFolder()),
+ actionCollection(), "refresh_folder" );
+ mTroubleshootFolderAction = 0; // set in initializeIMAPActions
+
+ mEmptyFolderAction = new KAction( "foo" /*set in updateFolderMenu*/, "edittrash", 0, this,
+ SLOT(slotEmptyFolder()), actionCollection(), "empty" );
+
+ mRemoveFolderAction = new KAction( "foo" /*set in updateFolderMenu*/, "editdelete", 0, this,
+ SLOT(slotRemoveFolder()), actionCollection(), "delete_folder" );
+
+ mPreferHtmlAction = new KToggleAction( i18n("Prefer &HTML to Plain Text"), 0, this,
+ SLOT(slotOverrideHtml()), actionCollection(), "prefer_html" );
+
+ mPreferHtmlLoadExtAction = new KToggleAction( i18n("Load E&xternal References"), 0, this,
+ SLOT(slotOverrideHtmlLoadExt()), actionCollection(), "prefer_html_external_refs" );
+
+ mThreadMessagesAction = new KToggleAction( i18n("&Thread Messages"), 0, this,
+ SLOT(slotOverrideThread()), actionCollection(), "thread_messages" );
+
+ mThreadBySubjectAction = new KToggleAction( i18n("Thread Messages also by &Subject"), 0, this,
+ SLOT(slotToggleSubjectThreading()), actionCollection(), "thread_messages_by_subject" );
+
+ new KAction( i18n("Copy Folder"), "editcopy", SHIFT+CTRL+Key_C, folderTree(),
+ SLOT(copyFolder()), actionCollection(), "copy_folder" );
+ new KAction( i18n("Cut Folder"), "editcut", SHIFT+CTRL+Key_X, folderTree(),
+ SLOT(cutFolder()), actionCollection(), "cut_folder" );
+ new KAction( i18n("Paste Folder"), "editpaste", SHIFT+CTRL+Key_V, folderTree(),
+ SLOT(pasteFolder()), actionCollection(), "paste_folder" );
+
+ new KAction( i18n("Copy Messages"), "editcopy", ALT+CTRL+Key_C, headers(),
+ SLOT(copyMessages()), actionCollection(), "copy_messages" );
+ new KAction( i18n("Cut Messages"), "editcut", ALT+CTRL+Key_X, headers(),
+ SLOT(cutMessages()), actionCollection(), "cut_messages" );
+ new KAction( i18n("Paste Messages"), "editpaste", ALT+CTRL+Key_V, headers(),
+ SLOT(pasteMessages()), actionCollection(), "paste_messages" );
+
+ //----- Message Menu
+ (void) new KAction( i18n("&New Message..."), "mail_new", KStdAccel::shortcut(KStdAccel::New), this,
+ SLOT(slotCompose()), actionCollection(), "new_message" );
+ mTemplateMenu =
+ new KActionMenu( i18n("New Message From &Template"), "filenew",
+ actionCollection(), "new_from_template" );
+ mTemplateMenu->setDelayed( true );
+ connect( mTemplateMenu->popupMenu(), SIGNAL( aboutToShow() ), this,
+ SLOT( slotShowNewFromTemplate() ) );
+ connect( mTemplateMenu->popupMenu(), SIGNAL( activated(int) ), this,
+ SLOT( slotNewFromTemplate(int) ) );
+
+ KAction* newToML = new KAction( i18n("New Message t&o Mailing-List..."), "mail_post_to",
+ CTRL+SHIFT+Key_N, this,
+ SLOT(slotPostToML()), actionCollection(), "post_message" );
+ newToML->plugAccel( actionCollection()->kaccel() );
+
+ mForwardActionMenu = new KActionMenu( i18n("Message->","&Forward"),
+ "mail_forward", actionCollection(),
+ "message_forward" );
+
+ mForwardInlineAction = new KAction( i18n("&Inline..."),
+ "mail_forward", 0, this,
+ SLOT(slotForwardInlineMsg()),
+ actionCollection(),
+ "message_forward_inline" );
+
+ mForwardAttachedAction = new KAction( i18n("Message->Forward->","As &Attachment..."),
+ "mail_forward", 0, this,
+ SLOT(slotForwardAttachedMsg()),
+ actionCollection(),
+ "message_forward_as_attachment" );
+
+ mForwardDigestAction = new KAction( i18n("Message->Forward->","As Di&gest..."),
+ "mail_forward", 0, this,
+ SLOT(slotForwardDigestMsg()),
+ actionCollection(),
+ "message_forward_as_digest" );
+
+ mRedirectAction = new KAction( i18n("Message->Forward->","&Redirect..."),
+ "mail_forward", Key_E, this,
+ SLOT(slotRedirectMsg()),
+ 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, SIGNAL(activated()), this,
+ SLOT(slotForwardInlineMsg()) );
+
+ } else {
+ mForwardActionMenu->insert( mForwardAttachedAction );
+ mForwardActionMenu->insert( mForwardInlineAction );
+ mForwardInlineAction->setShortcut( SHIFT+Key_F );
+ mForwardAttachedAction->setShortcut( Key_F );
+ connect( mForwardActionMenu, SIGNAL(activated()), this,
+ SLOT(slotForwardAttachedMsg()) );
+ }
+
+ mForwardActionMenu->insert( mForwardDigestAction );
+ mForwardActionMenu->insert( mRedirectAction );
+
+ mSendAgainAction = new KAction( i18n("Send A&gain..."), 0, this,
+ SLOT(slotResendMsg()), actionCollection(), "send_again" );
+
+
+ //----- Create filter actions
+ mFilterMenu = new KActionMenu( i18n("&Create Filter"), "filter", actionCollection(), "create_filter" );
+ connect( mFilterMenu, SIGNAL(activated()), this,
+ SLOT(slotFilter()) );
+ mSubjectFilterAction = new KAction( i18n("Filter on &Subject..."), 0, this,
+ SLOT(slotSubjectFilter()),
+ actionCollection(), "subject_filter");
+ mFilterMenu->insert( mSubjectFilterAction );
+
+ mFromFilterAction = new KAction( i18n("Filter on &From..."), 0, this,
+ SLOT(slotFromFilter()),
+ actionCollection(), "from_filter");
+ mFilterMenu->insert( mFromFilterAction );
+
+ mToFilterAction = new KAction( i18n("Filter on &To..."), 0, this,
+ SLOT(slotToFilter()),
+ actionCollection(), "to_filter");
+ mFilterMenu->insert( mToFilterAction );
+
+ mListFilterAction = new KAction( i18n("Filter on Mailing-&List..."), 0, this,
+ SLOT(slotMailingListFilter()), actionCollection(),
+ "mlist_filter");
+ mFilterMenu->insert( mListFilterAction );
+
+ mPrintAction = KStdAction::print (this, SLOT(slotPrintMsg()), actionCollection());
+
+ mUseAction = new KAction( i18n("New Message From &Template"), "filenew",
+ Key_N, this, SLOT( slotUseTemplate() ),
+ actionCollection(), "use_template" );
+ mUseAction->plugAccel( actionCollection()->kaccel() );
+
+ //----- "Mark Thread" submenu
+ mThreadStatusMenu = new KActionMenu ( i18n( "Mark &Thread" ),
+ actionCollection(), "thread_status" );
+
+ mMarkThreadAsReadAction = new KAction(KGuiItem(i18n("Mark Thread as &Read"), "kmmsgread",
+ i18n("Mark all messages in the selected thread as read")),
+ 0, this, SLOT(slotSetThreadStatusRead()),
+ actionCollection(), "thread_read");
+ mThreadStatusMenu->insert( mMarkThreadAsReadAction );
+
+ mMarkThreadAsNewAction = new KAction(KGuiItem(i18n("Mark Thread as &New"), "kmmsgnew",
+ i18n("Mark all messages in the selected thread as new")),
+ 0, this, SLOT(slotSetThreadStatusNew()),
+ actionCollection(), "thread_new");
+ mThreadStatusMenu->insert( mMarkThreadAsNewAction );
+
+ mMarkThreadAsUnreadAction = new KAction(KGuiItem(i18n("Mark Thread as &Unread"), "kmmsgunseen",
+ i18n("Mark all messages in the selected thread as unread")),
+ 0, this, SLOT(slotSetThreadStatusUnread()),
+ actionCollection(), "thread_unread");
+ mThreadStatusMenu->insert( mMarkThreadAsUnreadAction );
+
+ mThreadStatusMenu->insert( new KActionSeparator( this ) );
+
+ //----- "Mark Thread" toggle actions
+ mToggleThreadFlagAction = new KToggleAction(i18n("Mark Thread as &Important"), "mail_flag",
+ 0, this, SLOT(slotSetThreadStatusFlag()),
+ actionCollection(), "thread_flag");
+ mToggleThreadFlagAction->setCheckedState( i18n("Remove &Important Thread Mark") );
+ mThreadStatusMenu->insert( mToggleThreadFlagAction );
+
+ mToggleThreadTodoAction = new KToggleAction(i18n("Mark Thread as &Action Item"), "mail_todo",
+ 0, this, SLOT(slotSetThreadStatusTodo()),
+ actionCollection(), "thread_todo");
+ mToggleThreadTodoAction->setCheckedState( i18n("Remove &Action Item Thread Mark") );
+ mThreadStatusMenu->insert( mToggleThreadTodoAction );
+
+ //------- "Watch and ignore thread" actions
+ mWatchThreadAction = new KToggleAction(i18n("&Watch Thread"), "kmmsgwatched",
+ 0, this, SLOT(slotSetThreadStatusWatched()),
+ actionCollection(), "thread_watched");
+
+ mIgnoreThreadAction = new KToggleAction(i18n("&Ignore Thread"), "mail_ignore",
+ 0, this, SLOT(slotSetThreadStatusIgnored()),
+ actionCollection(), "thread_ignored");
+
+ mThreadStatusMenu->insert( new KActionSeparator( this ) );
+ mThreadStatusMenu->insert( mWatchThreadAction );
+ mThreadStatusMenu->insert( mIgnoreThreadAction );
+
+ mSaveAttachmentsAction = new KAction( i18n("Save A&ttachments..."), "attach",
+ 0, this, SLOT(slotSaveAttachments()),
+ actionCollection(), "file_save_attachments" );
+
+ mMoveActionMenu = new KActionMenu( i18n("&Move To" ),
+ actionCollection(), "move_to" );
+
+ mCopyActionMenu = new KActionMenu( i18n("&Copy To" ),
+ actionCollection(), "copy_to" );
+
+ mApplyAllFiltersAction = new KAction( i18n("Appl&y All Filters"), "filter",
+ CTRL+Key_J, this,
+ SLOT(slotApplyFilters()),
+ actionCollection(), "apply_filters" );
+
+ mApplyFilterActionsMenu = new KActionMenu( i18n("A&pply Filter" ),
+ actionCollection(),
+ "apply_filter_actions" );
+
+ //----- View Menu
+ // Unread Submenu
+ KActionMenu * unreadMenu =
+ new KActionMenu( i18n("View->", "&Unread Count"),
+ actionCollection(), "view_unread" );
+ unreadMenu->setToolTip( i18n("Choose how to display the count of unread messages") );
+
+ mUnreadColumnToggle = new KRadioAction( i18n("View->Unread Count", "View in &Separate Column"), 0, this,
+ SLOT(slotToggleUnread()),
+ actionCollection(), "view_unread_column" );
+ mUnreadColumnToggle->setExclusiveGroup( "view_unread_group" );
+ unreadMenu->insert( mUnreadColumnToggle );
+
+ mUnreadTextToggle = new KRadioAction( i18n("View->Unread Count", "View After &Folder Name"), 0, this,
+ SLOT(slotToggleUnread()),
+ actionCollection(), "view_unread_text" );
+ mUnreadTextToggle->setExclusiveGroup( "view_unread_group" );
+ unreadMenu->insert( mUnreadTextToggle );
+
+ // toggle for total column
+ mTotalColumnToggle = new KToggleAction( i18n("View->", "&Total Column"), 0, this,
+ SLOT(slotToggleTotalColumn()),
+ actionCollection(), "view_columns_total" );
+ mTotalColumnToggle->setToolTip( i18n("Toggle display of column showing the "
+ "total number of messages in folders.") );
+ mSizeColumnToggle = new KToggleAction( i18n("View->", "&Size Column"), 0, this,
+ SLOT(slotToggleSizeColumn()),
+ actionCollection(), "view_columns_size" );
+ mSizeColumnToggle->setToolTip( i18n("Toggle display of column showing the "
+ "total size of messages in folders.") );
+
+ (void)new KAction( KGuiItem( i18n("View->","&Expand Thread"), QString::null,
+ i18n("Expand the current thread") ),
+ Key_Period, this,
+ SLOT(slotExpandThread()),
+ actionCollection(), "expand_thread" );
+
+ (void)new KAction( KGuiItem( i18n("View->","&Collapse Thread"), QString::null,
+ i18n("Collapse the current thread") ),
+ Key_Comma, this,
+ SLOT(slotCollapseThread()),
+ actionCollection(), "collapse_thread" );
+
+ (void)new KAction( KGuiItem( i18n("View->","Ex&pand All Threads"), QString::null,
+ i18n("Expand all threads in the current folder") ),
+ CTRL+Key_Period, this,
+ SLOT(slotExpandAllThreads()),
+ actionCollection(), "expand_all_threads" );
+
+ (void)new KAction( KGuiItem( i18n("View->","C&ollapse All Threads"), QString::null,
+ i18n("Collapse all threads in the current folder") ),
+ CTRL+Key_Comma, this,
+ SLOT(slotCollapseAllThreads()),
+ actionCollection(), "collapse_all_threads" );
+
+ mViewSourceAction = new KAction( i18n("&View Source"), Key_V, this,
+ SLOT(slotShowMsgSrc()), actionCollection(),
+ "view_source" );
+
+ KAction* dukeOfMonmoth = new KAction( i18n("&Display Message"), Key_Return, this,
+ SLOT( slotDisplayCurrentMessage() ), actionCollection(),
+ "display_message" );
+ dukeOfMonmoth->plugAccel( actionCollection()->kaccel() );
+
+ //----- Go Menu
+ new KAction( KGuiItem( i18n("&Next Message"), QString::null,
+ i18n("Go to the next message") ),
+ "N;Right", this, SLOT(slotNextMessage()),
+ actionCollection(), "go_next_message" );
+
+ new KAction( KGuiItem( i18n("Next &Unread Message"),
+ QApplication::reverseLayout() ? "previous" : "next",
+ i18n("Go to the next unread message") ),
+ Key_Plus, this, SLOT(slotNextUnreadMessage()),
+ actionCollection(), "go_next_unread_message" );
+
+ /* ### needs better support from folders:
+ new KAction( KGuiItem( i18n("Next &Important Message"), QString::null,
+ i18n("Go to the next important message") ),
+ 0, this, SLOT(slotNextImportantMessage()),
+ actionCollection(), "go_next_important_message" );
+ */
+
+ new KAction( KGuiItem( i18n("&Previous Message"), QString::null,
+ i18n("Go to the previous message") ),
+ "P;Left", this, SLOT(slotPrevMessage()),
+ actionCollection(), "go_prev_message" );
+
+ new KAction( KGuiItem( i18n("Previous Unread &Message"),
+ QApplication::reverseLayout() ? "next" : "previous",
+ i18n("Go to the previous unread message") ),
+ Key_Minus, this, SLOT(slotPrevUnreadMessage()),
+ actionCollection(), "go_prev_unread_message" );
+
+ /* needs better support from folders:
+ new KAction( KGuiItem( i18n("Previous I&mportant Message"), QString::null,
+ i18n("Go to the previous important message") ),
+ 0, this, SLOT(slotPrevImportantMessage()),
+ actionCollection(), "go_prev_important_message" );
+ */
+
+ KAction *action =
+ new KAction( KGuiItem( i18n("Next Unread &Folder"), QString::null,
+ i18n("Go to the next folder with unread messages") ),
+ ALT+Key_Plus, this, SLOT(slotNextUnreadFolder()),
+ actionCollection(), "go_next_unread_folder" );
+ KShortcut shortcut = action->shortcut();
+ shortcut.append( KKey( CTRL+Key_Plus ) );
+ action->setShortcut( shortcut );
+
+ action =
+ new KAction( KGuiItem( i18n("Previous Unread F&older"), QString::null,
+ i18n("Go to the previous folder with unread messages") ),
+ ALT+Key_Minus, this, SLOT(slotPrevUnreadFolder()),
+ actionCollection(), "go_prev_unread_folder" );
+ shortcut = action->shortcut();
+ shortcut.append( KKey( CTRL+Key_Minus ) );
+ action->setShortcut( shortcut );
+
+ new KAction( KGuiItem( i18n("Go->","Next Unread &Text"), QString::null,
+ i18n("Go to the next unread text"),
+ i18n("Scroll down current message. "
+ "If at end of current message, "
+ "go to next unread message.") ),
+ Key_Space, this, SLOT(slotReadOn()),
+ actionCollection(), "go_next_unread_text" );
+
+ //----- Settings Menu
+ (void) new KAction( i18n("Configure &Filters..."), 0, this,
+ SLOT(slotFilter()), actionCollection(), "filter" );
+ (void) new KAction( i18n("Configure &POP Filters..."), 0, this,
+ SLOT(slotPopFilter()), actionCollection(), "popFilter" );
+ (void) new KAction( i18n("Manage &Sieve Scripts..."), 0, this,
+ SLOT(slotManageSieveScripts()), actionCollection(), "sieveFilters" );
+
+ (void) new KAction( KGuiItem( i18n("KMail &Introduction"), 0,
+ i18n("Display KMail's Welcome Page") ),
+ 0, this, SLOT(slotIntro()),
+ actionCollection(), "help_kmail_welcomepage" );
+
+ // ----- Standard Actions
+// KStdAction::configureNotifications(this, SLOT(slotEditNotifications()), actionCollection());
+ (void) new KAction( i18n("Configure &Notifications..."),
+ "knotify", 0, this,
+ SLOT(slotEditNotifications()), actionCollection(),
+ "kmail_configure_notifications" );
+// KStdAction::preferences(this, SLOT(slotSettings()), actionCollection());
+ (void) new KAction( i18n("&Configure KMail..."),
+ "configure", 0, kmkernel,
+ SLOT(slotShowConfigurationDialog()), actionCollection(),
+ "kmail_configure_kmail" );
+
+ KStdAction::undo(this, SLOT(slotUndo()), actionCollection(), "kmail_undo");
+
+ KStdAction::tipOfDay( this, SLOT( slotShowTip() ), actionCollection() );
+
+ menutimer = new QTimer( this, "menutimer" );
+ connect( menutimer, SIGNAL( timeout() ), SLOT( updateMessageActions() ) );
+ connect( kmkernel->undoStack(),
+ SIGNAL( undoStackChanged() ), this, SLOT( slotUpdateUndo() ));
+
+ initializeIMAPActions( false ); // don't set state, config not read yet
+ updateMessageActions();
+ updateCustomTemplateMenus();
+ updateFolderMenu();
+}
+
+void KMMainWidget::setupForwardingActionsList()
+{
+ QPtrList<KAction> mForwardActionList;
+ if ( GlobalSettings::self()->forwardingInlineByDefault() ) {
+ mGUIClient->unplugActionList( "forward_action_list" );
+ mForwardActionList.append( mForwardInlineAction );
+ mForwardActionList.append( mForwardAttachedAction );
+ mForwardActionList.append( mForwardDigestAction );
+ mForwardActionList.append( mRedirectAction );
+ mGUIClient->plugActionList( "forward_action_list", mForwardActionList );
+ } else {
+ mGUIClient->unplugActionList( "forward_action_list" );
+ mForwardActionList.append( mForwardAttachedAction );
+ mForwardActionList.append( mForwardInlineAction );
+ mForwardActionList.append( mForwardDigestAction );
+ mForwardActionList.append( mRedirectAction );
+ mGUIClient->plugActionList( "forward_action_list", mForwardActionList );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotEditNotifications()
+{
+ if(kmkernel->xmlGuiInstance())
+ KNotifyDialog::configure(this, 0, kmkernel->xmlGuiInstance()->aboutData());
+ else
+ KNotifyDialog::configure(this);
+}
+
+void KMMainWidget::slotEditKeys()
+{
+ KKeyDialog::configure( actionCollection(),
+ true /*allow one-letter shortcuts*/
+ );
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotReadOn()
+{
+ if ( !mMsgView )
+ return;
+
+ if ( !mMsgView->atBottom() ) {
+ mMsgView->slotJumpDown();
+ return;
+ }
+ slotNextUnreadMessage();
+}
+
+void KMMainWidget::slotNextUnreadFolder() {
+ if ( !mFolderTree ) return;
+ mFolderTree->nextUnreadFolder();
+}
+
+void KMMainWidget::slotPrevUnreadFolder() {
+ if ( !mFolderTree ) return;
+ mFolderTree->prevUnreadFolder();
+}
+
+void KMMainWidget::slotExpandThread()
+{
+ mHeaders->slotExpandOrCollapseThread( true ); // expand
+}
+
+void KMMainWidget::slotCollapseThread()
+{
+ mHeaders->slotExpandOrCollapseThread( false ); // collapse
+}
+
+void KMMainWidget::slotExpandAllThreads()
+{
+ mHeaders->slotExpandOrCollapseAllThreads( true ); // expand
+}
+
+void KMMainWidget::slotCollapseAllThreads()
+{
+ mHeaders->slotExpandOrCollapseAllThreads( false ); // collapse
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotShowMsgSrc()
+{
+ if ( mMsgView )
+ mMsgView->setUpdateAttachment( false );
+ KMMessage *msg = mHeaders->currentMsg();
+ if ( !msg )
+ return;
+ KMCommand *command = new KMShowMsgSrcCommand( this, msg,
+ mMsgView
+ ? mMsgView->isFixedFont()
+ : false );
+ command->start();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::moveSelectedToFolder( int menuId )
+{
+ if (mMenuToFolder[menuId])
+ mHeaders->moveMsgToFolder( mMenuToFolder[menuId] );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::copySelectedToFolder(int menuId )
+{
+ if (mMenuToFolder[menuId])
+ mHeaders->copyMsgToFolder( mMenuToFolder[menuId] );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::updateMessageMenu()
+{
+ mMenuToFolder.clear();
+ folderTree()->folderToPopupMenu( KMFolderTree::MoveMessage, this,
+ &mMenuToFolder, mMoveActionMenu->popupMenu() );
+ folderTree()->folderToPopupMenu( KMFolderTree::CopyMessage, this,
+ &mMenuToFolder, mCopyActionMenu->popupMenu() );
+ updateMessageActions();
+}
+
+void KMMainWidget::startUpdateMessageActionsTimer()
+{
+ menutimer->stop();
+ menutimer->start( 20, true );
+}
+
+void KMMainWidget::updateMessageActions()
+{
+ int count = 0;
+ QPtrList<QListViewItem> selectedItems;
+
+ if ( mFolder ) {
+ for (QListViewItem *item = mHeaders->firstChild(); item; item = item->itemBelow())
+ if (item->isSelected() )
+ selectedItems.append(item);
+ if ( selectedItems.isEmpty() && mFolder->count() ) // there will always be one in mMsgView
+ count = 1;
+ else
+ count = selectedItems.count();
+ mMsgActions->setCurrentMessage( mHeaders->currentMsg() );
+ mMsgActions->setSelectedSernums( mHeaders->selectedSernums() );
+ mMsgActions->setSelectedVisibleSernums( mHeaders->selectedVisibleSernums() );
+ } else {
+ mMsgActions->setCurrentMessage( 0 );
+ }
+
+ updateListFilterAction();
+
+ bool allSelectedInCommonThread = false;
+ if ( mHeaders->isThreaded() && count > 1 ) {
+ allSelectedInCommonThread = true;
+ for ( QPtrListIterator<QListViewItem> it( selectedItems ) ;
+ it.current() ; ++ it ) {
+ QListViewItem * item = *it;
+ if ( item->parent()==0 && item->childCount()==0 ) {
+ allSelectedInCommonThread = false;
+ break;
+ }
+ }
+ }
+ else if ( mHeaders->isThreaded() && count == 1 ) {
+ allSelectedInCommonThread = true;
+ }
+
+ QListViewItem *curItemParent = mHeaders->currentItem();
+ bool parent_thread = 0;
+ if ( curItemParent && curItemParent->firstChild() != 0 ) parent_thread = 1;
+
+ bool mass_actions = count >= 1;
+ bool thread_actions = mass_actions && allSelectedInCommonThread &&
+ mHeaders->isThreaded();
+ bool flags_available = GlobalSettings::self()->allowLocalFlags() || !(mFolder ? mFolder->isReadOnly() : true);
+ mThreadStatusMenu->setEnabled( thread_actions );
+ // these need to be handled individually, the user might have them
+ // in the toolbar
+ mWatchThreadAction->setEnabled( thread_actions && flags_available );
+ mIgnoreThreadAction->setEnabled( thread_actions && flags_available );
+ mMarkThreadAsNewAction->setEnabled( thread_actions );
+ mMarkThreadAsReadAction->setEnabled( thread_actions );
+ 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() );
+
+ if (mFolder && mHeaders && mHeaders->currentMsg()) {
+ if (thread_actions) {
+ mToggleThreadTodoAction->setChecked(mHeaders->currentMsg()->isTodo());
+ mToggleThreadFlagAction->setChecked(mHeaders->currentMsg()->isImportant());
+ mWatchThreadAction->setChecked( mHeaders->currentMsg()->isWatched());
+ mIgnoreThreadAction->setChecked( mHeaders->currentMsg()->isIgnored());
+ }
+ }
+
+ mMoveActionMenu->setEnabled( mass_actions && !mFolder->isReadOnly() );
+ 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 );
+
+ forwardMenu()->setEnabled( mass_actions );
+
+ bool single_actions = count == 1;
+ mUseAction->setEnabled( single_actions &&
+ kmkernel->folderIsTemplates( mFolder ) );
+ filterMenu()->setEnabled( single_actions );
+ redirectAction()->setEnabled( single_actions );
+ printAction()->setEnabled( single_actions );
+ viewSourceAction()->setEnabled( single_actions );
+
+ mSendAgainAction->setEnabled( single_actions
+ && ( 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
+ || (GlobalSettings::self()->loopOnGotoUnread() == GlobalSettings::EnumLoopOnGotoUnread::LoopInAllFolders);
+ actionCollection()->action( "go_next_message" )->setEnabled( mails );
+ actionCollection()->action( "go_next_unread_message" )->setEnabled( enable_goto_unread );
+ actionCollection()->action( "go_prev_message" )->setEnabled( mails );
+ actionCollection()->action( "go_prev_unread_message" )->setEnabled( enable_goto_unread );
+ actionCollection()->action( "send_queued" )->setEnabled( kmkernel->outboxFolder()->count() > 0 );
+ actionCollection()->action( "send_queued_via" )->setEnabled( kmkernel->outboxFolder()->count() > 0 );
+ slotUpdateOnlineStatus( static_cast<GlobalSettingsBase::EnumNetworkState::type>( GlobalSettings::self()->networkState() ) );
+ if (action( "edit_undo" ))
+ action( "edit_undo" )->setEnabled( mHeaders->canUndo() );
+
+ if ( count == 1 ) {
+ KMMessage *msg;
+ int aIdx;
+ if((aIdx = mHeaders->currentItemIndex()) <= -1)
+ return;
+ if(!(msg = mFolder->getMsg(aIdx)))
+ return;
+
+ if ((KMFolder*)mFolder == kmkernel->outboxFolder())
+ editAction()->setEnabled( !msg->transferInProgress() );
+ }
+
+ mApplyAllFiltersAction->setEnabled(count);
+ mApplyFilterActionsMenu->setEnabled(count);
+}
+
+// This needs to be updated more often, so it is in its method.
+void KMMainWidget::updateMarkAsReadAction()
+{
+ mMarkAllAsReadAction->setEnabled( mFolder && (mFolder->countUnread() > 0) );
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::updateFolderMenu()
+{
+ bool folderWithContent = mFolder && !mFolder->noContent();
+ bool multiFolder = folderTree()->selectedFolders().count() > 1;
+ mModifyFolderAction->setEnabled( folderWithContent && !multiFolder );
+ mFolderMailingListPropertiesAction->setEnabled( folderWithContent && !multiFolder );
+ mCompactFolderAction->setEnabled( folderWithContent && !multiFolder );
+
+ // This is the refresh-folder action in the menu. See kmfoldertree for the one in the RMB...
+ bool imap = mFolder && mFolder->folderType() == KMFolderTypeImap;
+ bool cachedImap = mFolder && mFolder->folderType() == KMFolderTypeCachedImap;
+ // For dimap, check that the imap path is known before allowing "check mail in this folder".
+ bool knownImapPath = cachedImap && !static_cast<KMFolderCachedImap*>( mFolder->storage() )->imapPath().isEmpty();
+ mRefreshFolderAction->setEnabled( folderWithContent && ( imap
+ || ( 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 );
+ updateMarkAsReadAction();
+ // the visual ones only make sense if we are showing a message list
+ mPreferHtmlAction->setEnabled( mHeaders->folder() ? true : false );
+ mPreferHtmlLoadExtAction->setEnabled( mHeaders->folder() && (mHtmlPref ? !mFolderHtmlPref : mFolderHtmlPref) ? true : false );
+ mThreadMessagesAction->setEnabled( mHeaders->folder() ? true : false );
+
+ mPreferHtmlAction->setChecked( mHtmlPref ? !mFolderHtmlPref : mFolderHtmlPref );
+ mPreferHtmlLoadExtAction->setChecked( mHtmlLoadExtPref ? !mFolderHtmlLoadExtPref : mFolderHtmlLoadExtPref );
+ mThreadMessagesAction->setChecked(
+ mThreadPref ? !mFolderThreadPref : mFolderThreadPref );
+ mThreadBySubjectAction->setEnabled(
+ mHeaders->folder() ? ( mThreadMessagesAction->isChecked()) : false );
+ mThreadBySubjectAction->setChecked( mFolderThreadSubjPref );
+
+ mNewFolderAction->setEnabled( !multiFolder );
+ mRemoveDuplicatesAction->setEnabled( !multiFolder );
+ mFolderShortCutCommandAction->setEnabled( !multiFolder );
+}
+
+
+#ifdef MALLOC_DEBUG
+static QString fmt(long n) {
+ char buf[32];
+
+ if(n > 1024*1024*1024)
+ sprintf(buf, "%0.2f GB", ((double)n)/1024.0/1024.0/1024.0);
+ else if(n > 1024*1024)
+ sprintf(buf, "%0.2f MB", ((double)n)/1024.0/1024.0);
+ else if(n > 1024)
+ sprintf(buf, "%0.2f KB", ((double)n)/1024.0);
+ else
+ sprintf(buf, "%ld Byte", n);
+ return QString(buf);
+}
+#endif
+
+void KMMainWidget::slotMemInfo() {
+#ifdef MALLOC_DEBUG
+ struct mallinfo mi;
+
+ mi = mallinfo();
+ QString s = QString("\nMALLOC - Info\n\n"
+ "Number of mmapped regions : %1\n"
+ "Memory allocated in use : %2\n"
+ "Memory allocated, not used: %3\n"
+ "Memory total allocated : %4\n"
+ "Max. freeable memory : %5\n")
+ .arg(mi.hblks).arg(fmt(mi.uordblks)).arg(fmt(mi.fordblks))
+ .arg(fmt(mi.arena)).arg(fmt(mi.keepcost));
+ KMessageBox::information(0, s, "Malloc information", s);
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotIntro()
+{
+ if ( !mMsgView ) return;
+
+ mMsgView->clear( true );
+ // hide widgets that are in the way:
+ if ( mSearchAndHeaders && mHeaders && mLongFolderList )
+ mSearchAndHeaders->hide();
+
+
+ mMsgView->displayAboutPage();
+
+ mFolder = 0;
+}
+
+void KMMainWidget::slotShowStartupFolder()
+{
+ if ( mFolderTree ) {
+ mFolderTree->reload();
+ mFolderTree->readConfig();
+ // get rid of old-folders
+ mFolderTree->cleanupConfigFile();
+ }
+
+ connect( kmkernel->filterMgr(), SIGNAL( filterListUpdated() ),
+ this, SLOT( initializeFilterActions() ));
+
+ // plug shortcut filter actions now
+ initializeFilterActions();
+
+ // plug folder shortcut actions
+ initializeFolderShortcutActions();
+
+ QString newFeaturesMD5 = KMReaderWin::newFeaturesMD5();
+ if ( kmkernel->firstStart() ||
+ GlobalSettings::self()->previousNewFeaturesMD5() != newFeaturesMD5 ) {
+ GlobalSettings::self()->setPreviousNewFeaturesMD5( newFeaturesMD5 );
+ slotIntro();
+ return;
+ }
+
+ KMFolder* startup = 0;
+ if ( !mStartupFolder.isEmpty() ) {
+ // find the startup-folder
+ startup = kmkernel->findFolderById( mStartupFolder );
+ }
+ if ( !startup )
+ startup = kmkernel->inboxFolder();
+
+ if ( mFolderTree ) {
+ mFolderTree->showFolder( startup );
+ }
+}
+
+void KMMainWidget::slotShowTip()
+{
+ KTipDialog::showTip( this, QString::null, true );
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotChangeCaption(QListViewItem * i)
+{
+ if ( !i ) return;
+ // set the caption to the current full path
+ QStringList names;
+ for ( QListViewItem * item = i ; item ; item = item->parent() )
+ names.prepend( item->text(0) );
+ emit captionChangeRequest( names.join( "/" ) );
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::removeDuplicates()
+{
+ if (!mFolder)
+ return;
+ KMFolder *oFolder = mFolder;
+ mHeaders->setFolder(0);
+ QMap< QString, QValueList<int> > idMD5s;
+ QValueList<int> redundantIds;
+ QValueList<int>::Iterator kt;
+ mFolder->open("removedups");
+ for (int i = mFolder->count() - 1; i >= 0; --i) {
+ QString id = (*mFolder)[i]->msgIdMD5();
+ if ( !id.isEmpty() ) {
+ QString subjMD5 = (*mFolder)[i]->strippedSubjectMD5();
+ int other = -1;
+ if ( idMD5s.contains(id) )
+ other = idMD5s[id].first();
+ else
+ idMD5s[id].append( i );
+ if ( other != -1 ) {
+ QString otherSubjMD5 = (*mFolder)[other]->strippedSubjectMD5();
+ if (otherSubjMD5 == subjMD5)
+ idMD5s[id].append( i );
+ }
+ }
+ }
+ QMap< QString, QValueList<int> >::Iterator it;
+ for ( it = idMD5s.begin(); it != idMD5s.end() ; ++it ) {
+ QValueList<int>::Iterator jt;
+ bool finished = false;
+ for ( jt = (*it).begin(); jt != (*it).end() && !finished; ++jt )
+ if (!((*mFolder)[*jt]->isUnread())) {
+ (*it).remove( jt );
+ (*it).prepend( *jt );
+ finished = true;
+ }
+ for ( jt = (*it).begin(), ++jt; jt != (*it).end(); ++jt )
+ redundantIds.append( *jt );
+ }
+ qHeapSort( redundantIds );
+ kt = redundantIds.end();
+ int numDuplicates = 0;
+ if (kt != redundantIds.begin()) do {
+ mFolder->removeMsg( *(--kt) );
+ ++numDuplicates;
+ }
+ while (kt != redundantIds.begin());
+
+ mFolder->close("removedups");
+ mHeaders->setFolder(oFolder);
+ QString msg;
+ if ( numDuplicates )
+ msg = i18n("Removed %n duplicate message.",
+ "Removed %n duplicate messages.", numDuplicates );
+ else
+ msg = i18n("No duplicate messages found.");
+ BroadcastStatus::instance()->setStatusMsg( msg );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotUpdateUndo()
+{
+ if (actionCollection()->action( "edit_undo" ))
+ actionCollection()->action( "edit_undo" )->setEnabled( mHeaders->canUndo() );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::clearFilterActions()
+{
+ if ( !mFilterTBarActions.isEmpty() ) {
+ if ( mGUIClient->factory() )
+ mGUIClient->unplugActionList( "toolbar_filter_actions" );
+ mFilterTBarActions.clear();
+ }
+ mApplyFilterActionsMenu->popupMenu()->clear();
+ if ( !mFilterMenuActions.isEmpty() ) {
+ if ( mGUIClient->factory() )
+ mGUIClient->unplugActionList( "menu_filter_actions" );
+ mFilterMenuActions.clear();
+ }
+ mFilterCommands.clear();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::initializeFolderShortcutActions()
+{
+
+ // If we are loaded as a part, this will be set to fals, since the part
+ // does xml loading. Temporarily set to true, in that case, so the
+ // accels are added to the collection as expected.
+ bool old = actionCollection()->isAutoConnectShortcuts();
+
+ actionCollection()->setAutoConnectShortcuts( true );
+ QValueList< QGuardedPtr< KMFolder > > folders = kmkernel->allFolders();
+ QValueList< QGuardedPtr< KMFolder > >::Iterator it = folders.begin();
+ while ( it != folders.end() ) {
+ KMFolder *folder = (*it);
+ ++it;
+ slotShortcutChanged( folder ); // load the initial accel
+ }
+ actionCollection()->setAutoConnectShortcuts( old );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::initializeFilterActions()
+{
+ QString filterName, normalizedName;
+ KMMetaFilterActionCommand *filterCommand;
+ KAction *filterAction = 0;
+
+ clearFilterActions();
+ mApplyAllFiltersAction->plug(mApplyFilterActionsMenu->popupMenu());
+ bool addedSeparator = false;
+ QValueListConstIterator<KMFilter*> it = kmkernel->filterMgr()->filters().constBegin();
+ for ( ;it != kmkernel->filterMgr()->filters().constEnd(); ++it ) {
+ if (!(*it)->isEmpty() && (*it)->configureShortcut()) {
+ filterName = QString("Filter %1").arg((*it)->name());
+ normalizedName = filterName.replace(" ", "_");
+ if (action(normalizedName.utf8()))
+ continue;
+ filterCommand = new KMMetaFilterActionCommand(*it, mHeaders, this);
+ mFilterCommands.append(filterCommand);
+ QString as = i18n("Filter %1").arg((*it)->name());
+ QString icon = (*it)->icon();
+ if ( icon.isEmpty() )
+ icon = "gear";
+ filterAction = new KAction(as, icon, (*it)->shortcut(), filterCommand,
+ SLOT(start()), actionCollection(),
+ normalizedName.local8Bit());
+ if(!addedSeparator) {
+ mApplyFilterActionsMenu->popupMenu()->insertSeparator();
+ addedSeparator = !addedSeparator;
+ mFilterMenuActions.append( new KActionSeparator());
+ }
+ filterAction->plug( mApplyFilterActionsMenu->popupMenu() );
+ mFilterMenuActions.append(filterAction);
+ if ( (*it)->configureToolbar() )
+ mFilterTBarActions.append(filterAction);
+ }
+ }
+ if ( !mFilterMenuActions.isEmpty() && mGUIClient->factory() )
+ mGUIClient->plugActionList( "menu_filter_actions", mFilterMenuActions );
+ if ( !mFilterTBarActions.isEmpty() && mGUIClient->factory() ) {
+ mFilterTBarActions.prepend( mToolbarActionSeparator );
+ mGUIClient->plugActionList( "toolbar_filter_actions", mFilterTBarActions );
+ }
+}
+
+void KMMainWidget::slotFolderRemoved( KMFolder *folder )
+{
+ mFolderShortcutCommands.remove( folder->idString() );
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::initializeIMAPActions( bool setState /* false the first time, true later on */ )
+{
+ bool hasImapAccount = false;
+ for( KMAccount *a = kmkernel->acctMgr()->first(); a;
+ a = kmkernel->acctMgr()->next() ) {
+ if ( a->type() == "cachedimap" ) {
+ hasImapAccount = true;
+ break;
+ }
+ }
+ if ( hasImapAccount == ( mTroubleshootFolderAction != 0 ) )
+ return; // nothing to do
+
+ KXMLGUIFactory* factory = mGUIClient->factory();
+ if ( factory )
+ factory->removeClient( mGUIClient );
+
+ if ( !mTroubleshootFolderAction ) {
+ mTroubleshootFolderAction = new KAction( i18n("&Troubleshoot IMAP Cache..."), "wizard", 0,
+ this, SLOT(slotTroubleshootFolder()), actionCollection(), "troubleshoot_folder" );
+ if ( setState )
+ updateFolderMenu(); // set initial state of the action
+ } else {
+ delete mTroubleshootFolderAction ;
+ mTroubleshootFolderAction = 0;
+ }
+
+ if ( factory )
+ factory->addClient( mGUIClient );
+}
+
+bool KMMainWidget::shortcutIsValid( const KShortcut &sc ) const
+{
+ KActionPtrList actions = actionCollection()->actions();
+ KActionPtrList::Iterator it( actions.begin() );
+ for ( ; it != actions.end(); it++ ) {
+ if ( (*it)->shortcut() == sc ) return false;
+ }
+ return true;
+}
+
+void KMMainWidget::slotShortcutChanged( KMFolder *folder )
+{
+ // remove the old one, autodelete
+ mFolderShortcutCommands.remove( folder->idString() );
+ if ( folder->shortcut().isNull() )
+ return;
+
+ FolderShortcutCommand *c = new FolderShortcutCommand( this, folder );
+ mFolderShortcutCommands.insert( folder->idString(), c );
+
+ QString actionlabel = QString( "FolderShortcut %1").arg( folder->prettyURL() );
+ QString actionname = QString( "FolderShortcut %1").arg( folder->idString() );
+ QString normalizedName = actionname.replace(" ", "_");
+ KAction* action =
+ new KAction(actionlabel, folder->shortcut(), c, SLOT(start()),
+ actionCollection(), normalizedName.local8Bit());
+ action->setIcon( folder->unreadIconPath() );
+ c->setAction( action ); // will be deleted along with the command
+}
+
+//-----------------------------------------------------------------------------
+QString KMMainWidget::findCurrentImapPath()
+{
+ QString startPath;
+ if (!mFolder) return startPath;
+ if (mFolder->folderType() == KMFolderTypeImap)
+ {
+ startPath = static_cast<KMFolderImap*>(mFolder->storage())->imapPath();
+ } else if (mFolder->folderType() == KMFolderTypeCachedImap)
+ {
+ startPath = static_cast<KMFolderCachedImap*>(mFolder->storage())->imapPath();
+ }
+ return startPath;
+}
+
+//-----------------------------------------------------------------------------
+ImapAccountBase* KMMainWidget::findCurrentImapAccountBase()
+{
+ ImapAccountBase* account = 0;
+ if (!mFolder) return account;
+ if (mFolder->folderType() == KMFolderTypeImap)
+ {
+ account = static_cast<KMFolderImap*>(mFolder->storage())->account();
+ } else if (mFolder->folderType() == KMFolderTypeCachedImap)
+ {
+ account = static_cast<KMFolderCachedImap*>(mFolder->storage())->account();
+ }
+ return account;
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotSubscriptionDialog()
+{
+ if ( !kmkernel->askToGoOnline() )
+ return;
+ ImapAccountBase* account = findCurrentImapAccountBase();
+ if ( !account ) return;
+ const QString startPath = findCurrentImapPath();
+
+ // KSubscription sets "DestruciveClose"
+ SubscriptionDialog * dialog =
+ new SubscriptionDialog(this, i18n("Subscription"), account, startPath);
+ if ( dialog->exec() ) {
+ // start a new listing
+ if (mFolder->folderType() == KMFolderTypeImap)
+ static_cast<KMFolderImap*>(mFolder->storage())->account()->listDirectory();
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotLocalSubscriptionDialog()
+{
+ ImapAccountBase* account = findCurrentImapAccountBase();
+ if ( !account ) return;
+
+ const QString startPath = findCurrentImapPath();
+ // KSubscription sets "DestruciveClose"
+ LocalSubscriptionDialog *dialog =
+ new LocalSubscriptionDialog(this, i18n("Local Subscription"), account, startPath);
+ if ( dialog->exec() ) {
+ // start a new listing
+ if (mFolder->folderType() == KMFolderTypeImap)
+ static_cast<KMFolderImap*>(mFolder->storage())->account()->listDirectory();
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotFolderTreeColumnsChanged()
+{
+ mTotalColumnToggle->setChecked( mFolderTree->isTotalActive() );
+ mUnreadColumnToggle->setChecked( mFolderTree->isUnreadActive() );
+ mSizeColumnToggle->setChecked( mFolderTree->isSizeActive() );
+}
+
+void KMMainWidget::toggleSystemTray()
+{
+ if ( !mSystemTray && GlobalSettings::self()->systemTrayEnabled() ) {
+ mSystemTray = new KMSystemTray();
+ }
+ else if ( mSystemTray && !GlobalSettings::self()->systemTrayEnabled() ) {
+ // Get rid of system tray on user's request
+ kdDebug(5006) << "deleting systray" << endl;
+ delete mSystemTray;
+ mSystemTray = 0;
+ }
+
+ // Set mode of systemtray. If mode has changed, tray will handle this.
+ if ( mSystemTray )
+ mSystemTray->setMode( GlobalSettings::self()->systemTrayPolicy() );
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotAntiSpamWizard()
+{
+ AntiSpamWizard wiz( AntiSpamWizard::AntiSpam, this, folderTree() );
+ wiz.exec();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotAntiVirusWizard()
+{
+ AntiSpamWizard wiz( AntiSpamWizard::AntiVirus, this, folderTree() );
+ wiz.exec();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::slotFilterLogViewer()
+{
+ FilterLogDialog * dlg = new FilterLogDialog( 0 );
+ dlg->show();
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::updateFileMenu()
+{
+ QStringList actList = kmkernel->acctMgr()->getAccounts();
+
+ actionCollection()->action("check_mail")->setEnabled( actList.size() > 0 );
+ actionCollection()->action("check_mail_in")->setEnabled( actList.size() > 0 );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMainWidget::setAccelsEnabled( bool enabled )
+{
+ actionCollection()->kaccel()->setEnabled( enabled );
+}
+
+
+//-----------------------------------------------------------------------------
+KMSystemTray *KMMainWidget::systray() const
+{
+ return mSystemTray;
+}
+
+//-----------------------------------------------------------------------------
+QString KMMainWidget::overrideEncoding() const
+{
+ if ( mMsgView )
+ return mMsgView->overrideEncoding();
+ else
+ return GlobalSettings::self()->overrideCharacterEncoding();
+}
+
+void KMMainWidget::slotCreateTodo()
+{
+ KMMessage *msg = mHeaders->currentMsg();
+ if ( !msg )
+ return;
+ KMCommand *command = new CreateTodoCommand( this, msg );
+ command->start();
+}
+
+void KMMainWidget::setupFolderView()
+{
+ if ( GlobalSettings::self()->enableFavoriteFolderView() ) {
+ mFolderView = mFolderViewSplitter;
+ mSearchAndTree->reparent( mFolderViewSplitter, 0, QPoint( 0, 0 ) );
+ mFolderViewSplitter->show();
+ mFavoriteFolderView->show();
+ } else {
+ mFolderView = mSearchAndTree;
+ mFolderViewSplitter->hide();
+ mFavoriteFolderView->hide();
+ }
+ mFolderView->reparent( mFolderViewParent, 0, QPoint( 0, 0 ) );
+ mFolderViewParent->moveToFirst( mFolderView );
+ mSearchAndTree->show();
+}
+
+
+void KMMainWidget::slotRequestFullSearchFromQuickSearch()
+{
+ slotSearch();
+#ifdef HAVE_INDEXLIB
+ return;
+#endif
+ assert( mSearchWin );
+ KMSearchPattern pattern;
+ pattern.append( KMSearchRule::createInstance( "<message>", KMSearchRule::FuncContains, mQuickSearchLine->currentSearchTerm() ) );
+ int status = mQuickSearchLine->currentStatus();
+ if ( status != 0 ) {
+ pattern.append( new KMSearchRuleStatus( status ) );
+ }
+ mSearchWin->setSearchPattern( pattern );
+}
+
+void KMMainWidget::updateVactionScriptStatus(bool active)
+{
+ mVacationIndicatorActive = active;
+ if ( active ) {
+ mVacationScriptIndicator->setText( i18n("Out of office reply active") );
+ mVacationScriptIndicator->setPaletteBackgroundColor( Qt::yellow );
+ mVacationScriptIndicator->setCursor( QCursor( Qt::PointingHandCursor ) );
+ mVacationScriptIndicator->show();
+ } else {
+ mVacationScriptIndicator->hide();
+ }
+}
+
diff --git a/kmail/kmmainwidget.h b/kmail/kmmainwidget.h
new file mode 100644
index 00000000..c56cf813
--- /dev/null
+++ b/kmail/kmmainwidget.h
@@ -0,0 +1,564 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2002 Don Sanders <sanders@kde.org>
+
+ Based on the work of Stefan Taferner <taferner@kde.org>
+
+ 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
+*/
+
+#ifndef __KMMAINWIDGET
+#define __KMMAINWIDGET
+
+#include <kurl.h>
+#include <kxmlguiclient.h>
+#include <qguardedptr.h>
+#include <qlistview.h>
+#include <qvbox.h>
+#include <qvaluevector.h>
+
+#include "kmreaderwin.h" //for inline actions
+#include "kmkernel.h" // for access to config
+#include "messageactions.h"
+#include <kaction.h>
+
+class QVBoxLayout;
+class QSplitter;
+class QSignalMapper;
+
+class KActionMenu;
+class KActionCollection;
+class KConfig;
+class KRadioAction;
+class KToggleAction;
+class KMenuBar;
+class KStatusBarLabel;
+
+class KMFolder;
+class KMFolderDir;
+class KMFolderTree;
+class KMFolderTreeItem;
+class KMCommand;
+class KMMetaFilterActionCommand;
+class FolderShortcutCommand;
+class KMMessage;
+class KMFolder;
+class KMAccount;
+class KMSystemTray;
+class KMHeaders;
+
+template <typename T> class QValueList;
+template <typename T, typename S> class QMap;
+template <typename T> class QGuardedPtr;
+
+namespace KIO {
+ class Job;
+}
+
+namespace KMail {
+ class Vacation;
+ class SieveDebugDialog;
+ class FolderJob;
+ class HeaderListQuickSearch;
+ class SearchWindow;
+ class ImapAccountBase;
+ class FavoriteFolderView;
+}
+
+typedef QMap<int,KMFolder*> KMMenuToFolder;
+
+
+class KDE_EXPORT KMMainWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ KMMainWidget(QWidget *parent, const char *name,
+ KXMLGUIClient *aGUIClient,
+ KActionCollection *actionCollection,
+ KConfig*config = KMKernel::config() );
+ virtual ~KMMainWidget();
+ void destruct();
+
+ /** Read configuration options before widgets are created. */
+ virtual void readPreConfig(void);
+
+ /** Read configuration for current folder. */
+ virtual void readFolderConfig(void);
+
+ /** Write configuration for current folder. */
+ virtual void writeFolderConfig(void);
+
+ /** Read configuration options after widgets are created. */
+ virtual void readConfig(void);
+
+ /** Write configuration options. */
+ virtual void writeConfig(void);
+
+ /** Easy access to main components of the window. */
+ KMReaderWin* messageView(void) const { return mMsgView; }
+ KMFolderTree* folderTree(void) const { return mFolderTree; }
+ KMail::FavoriteFolderView *favoriteFolderView() const { return mFavoriteFolderView; }
+
+ static void cleanup();
+
+ KAction *action( const char *name ) { return mActionCollection->action( name ); }
+ KActionMenu *customReplyAction() const { return mCustomReplyActionMenu; }
+ KActionMenu *customReplyAllAction() const { return mCustomReplyAllActionMenu; }
+ KActionMenu *forwardMenu() const { return mForwardActionMenu; }
+ KAction *forwardInlineAction() const { return mForwardInlineAction; }
+ KAction *forwardAttachedAction() const { return mForwardAttachedAction; }
+ KAction *forwardDigestAction() const { return mForwardDigestAction; }
+ KAction *redirectAction() const { return mRedirectAction; }
+ KActionMenu *customForwardAction() const { return mCustomForwardActionMenu; }
+ KActionMenu *filterMenu() const { return mFilterMenu; }
+ KAction *printAction() const { return mPrintAction; }
+ KAction *trashAction() const { return mTrashAction; }
+ KAction *deleteAction() const { return mDeleteAction; }
+ KAction *trashThreadAction() const { return mTrashThreadAction; }
+ KAction *deleteThreadAction() const { return mDeleteThreadAction; }
+ KAction *saveAsAction() const { return mSaveAsAction; }
+ KAction *editAction() const { return mMsgActions->editAction(); }
+ KAction *useAction() const { return mUseAction; }
+ KAction *sendAgainAction() const { return mSendAgainAction; }
+ KAction *applyAllFiltersAction() const { return mApplyAllFiltersAction; }
+ KAction *findInMessageAction() const { return mFindInMessageAction; }
+ KAction *saveAttachmentsAction() const { return mSaveAttachmentsAction; }
+ KAction *openAction() const { return mOpenAction; }
+ KAction *viewSourceAction() const { return mViewSourceAction; }
+ KMail::MessageActions *messageActions() const { return mMsgActions; }
+
+ KActionMenu *statusMenu() const{ return mMsgActions->messageStatusMenu(); }
+ KActionMenu *threadStatusMenu() const { return mThreadStatusMenu; }
+ KActionMenu *moveActionMenu() const{ return mMoveActionMenu; }
+ KActionMenu *mopyActionMenu() const { return mCopyActionMenu; }
+ KActionMenu *applyFilterActionsMenu() const { return mApplyFilterActionsMenu; }
+
+ KToggleAction *watchThreadAction() const { return mWatchThreadAction; }
+ KToggleAction *ignoreThreadAction() const { return mIgnoreThreadAction; }
+
+ KMHeaders *headers() const { return mHeaders; }
+ void toggleSystemTray();
+
+ void updateListFilterAction();
+
+ /** Returns a list of all KMMainWidgets. Warning, the list itself can be 0.
+ * @return the list of all main widgets, or 0 if it is not yet initialized */
+ static const QValueList<KMMainWidget*>* mainWidgetList() { return s_mainWidgetList; }
+
+ KMSystemTray *systray() const;
+
+ /** Checks a shortcut against the actioncollection and returns whether it
+ * is already used and therefor not valid or not. */
+ bool shortcutIsValid( const KShortcut& ) const;
+
+
+ void modifyFolder( KMFolderTreeItem* folderItem );
+
+ /**
+ * Enable or disable the global accelerators. This is useful for keyboard
+ * navigation inside child widgets like combo boxes.
+ */
+ void setAccelsEnabled( bool enabled = true );
+
+ /**
+ * Sets up action list for forward menu.
+ */
+ void setupForwardingActionsList();
+
+ KStatusBarLabel* vacationScriptIndicator() const { return mVacationScriptIndicator; }
+ void updateVactionScriptStatus() { updateVactionScriptStatus( mVacationIndicatorActive ); }
+
+public slots:
+ void slotMoveMsgToFolder( KMFolder *dest);
+ void slotTrashMsg(); // move to trash
+
+ virtual void show();
+ virtual void hide();
+ /** sven: moved here as public */
+ void slotCheckMail();
+
+ /**
+ * Select the given folder
+ * If the folder is 0 the intro is shown
+ */
+ void folderSelected( KMFolder*, bool forceJumpToUnread = false );
+
+ /** Reselect current folder */
+ void folderSelected();
+
+ /** Select the folder and jump to the next unread msg */
+ void folderSelectedUnread( KMFolder* );
+
+ void slotMsgSelected(KMMessage*);
+ void slotMsgChanged();
+
+ /** Change the current folder, select a message in the current folder */
+ void slotSelectFolder(KMFolder*);
+ void slotSelectMessage(KMMessage*);
+
+ void slotReplaceMsgByUnencryptedVersion();
+
+ /** Update message menu */
+ void updateMessageMenu();
+ /** Start a timer to update message actions */
+ void startUpdateMessageActionsTimer();
+ /** Update message actions */
+ void updateMessageActions();
+
+ /** Launch subscription-dialog (server side) */
+ void slotSubscriptionDialog();
+
+ /** Launch dialog for local (client side) subscription configuration */
+ void slotLocalSubscriptionDialog();
+
+ /** The columns of the foldertree changed */
+ void slotFolderTreeColumnsChanged();
+
+ /** Clear and create actions for marked filters */
+ void clearFilterActions();
+ void initializeFilterActions();
+
+ /** Create IMAP-account-related actions if applicable */
+ void initializeIMAPActions() { initializeIMAPActions( true ); }
+
+ /** Create actions for the folder shortcuts. */
+ void initializeFolderShortcutActions();
+
+ /** Add, remove or adjust the folder's shortcut. */
+ void slotShortcutChanged( KMFolder *folder );
+
+ void updateCustomTemplateMenus();
+ void slotEditVacation();
+
+signals:
+ void messagesTransfered( bool );
+ void captionChangeRequest( const QString & caption );
+
+protected:
+ void setupActions();
+ void createWidgets();
+ void activatePanners();
+ void showMsg( KMReaderWin *win, KMMessage *msg );
+ void updateFileMenu();
+ void newFromTemplate( KMMessage *msg );
+
+ KActionCollection * actionCollection() const { return mActionCollection; }
+
+ /** @return the correct config dialog depending on whether the parent of the mainWidget
+ * is a KPart or a KMMainWindow. When dealing with geometries, use this pointer
+ */
+ KConfig * config();
+
+protected slots:
+ void slotCheckOneAccount(int);
+ void slotMailChecked( bool newMail, bool sendOnCheck,
+ const QMap<QString, int> & newInFolder );
+ void getAccountMenu();
+ void getTransportMenu();
+ void slotHelp();
+ void slotFilter();
+ void slotPopFilter();
+ void slotManageSieveScripts();
+ void slotAddrBook();
+ void slotImport();
+ void slotViewChange();
+ void slotCompose();
+ void slotPostToML();
+ void slotModifyFolder();
+ void slotFolderMailingListProperties();
+ void slotFolderShortcutCommand();
+ void slotExpireFolder();
+ void slotExpireAll();
+ void slotInvalidateIMAPFolders();
+ void slotMarkAllAsRead();
+ void slotRemoveFolder();
+ void slotEmptyFolder();
+ void slotCompactFolder();
+ void slotRefreshFolder();
+ void slotTroubleshootFolder();
+ void slotCompactAll();
+ void slotOverrideHtml();
+ void slotOverrideHtmlLoadExt();
+ void slotOverrideThread();
+ void slotToggleSubjectThreading();
+ void slotMessageQueuedOrDrafted();
+ void slotUseTemplate();
+ //void slotTrashMsg(); // move to trash
+ void slotDeleteMsg( bool confirmDelete = true ); // completely delete message
+ void slotTrashThread();
+ void slotDeleteThread( bool confirmDelete = true ); // completely delete thread
+ void slotUndo();
+ void slotReadOn();
+ void slotSaveMsg();
+ void slotOpenMsg();
+ void slotSaveAttachments();
+ void slotJumpToFolder();
+ void slotMoveMsg();
+ //void slotMoveMsgToFolder( KMFolder *dest);
+ void slotCopyMsgToFolder( KMFolder *dest);
+ void slotCopyMsg();
+ void slotResendMsg();
+ void slotCheckVacation();
+ void slotDebugSieve();
+ void slotStartCertManager();
+ void slotStartWatchGnuPG();
+ void slotApplyFilters();
+ void slotExpandThread();
+ void slotExpandAllThreads();
+ void slotCollapseThread();
+ void slotCollapseAllThreads();
+ void slotShowMsgSrc();
+ void slotSetThreadStatusNew();
+ void slotSetThreadStatusUnread();
+ void slotSetThreadStatusRead();
+ void slotSetThreadStatusTodo();
+ void slotSetThreadStatusFlag();
+ void slotSetThreadStatusWatched();
+ void slotSetThreadStatusIgnored();
+ void slotToggleUnread();
+ void slotToggleTotalColumn();
+ void slotToggleSizeColumn();
+ void slotSendQueued();
+ void slotSendQueuedVia( int item );
+ void slotOnlineStatus();
+ void slotUpdateOnlineStatus( GlobalSettings::EnumNetworkState::type );
+ void slotMsgPopup(KMMessage &msg, const KURL &aUrl, const QPoint&);
+ void slotMarkAll();
+ void slotMemInfo();
+ void slotSearch();
+ void slotSearchClosed();
+ void slotFind();
+ void slotIntro();
+ void slotShowStartupFolder();
+ /** Show tip-of-the-day, forced */
+ void slotShowTip();
+ void slotAntiSpamWizard();
+ void slotAntiVirusWizard();
+ void slotFilterLogViewer();
+
+ /** Message navigation */
+ void slotNextMessage();
+ void slotNextUnreadMessage();
+ void slotNextImportantMessage();
+ void slotNextUnreadFolder();
+ void slotPrevMessage();
+ void slotPrevUnreadMessage();
+ void slotPrevImportantMessage();
+ void slotPrevUnreadFolder();
+
+ /** etc. */
+ void slotDisplayCurrentMessage();
+ void slotMsgActivated( KMMessage* );
+
+ void slotShowNewFromTemplate();
+ void slotNewFromTemplate( int );
+
+ /** Update the undo action */
+ void slotUpdateUndo();
+
+ /** Move selected messages to folder with corresponding to given menuid */
+ virtual void moveSelectedToFolder( int menuId );
+ /** Copy selected messages to folder with corresponding to given menuid */
+ virtual void copySelectedToFolder( int menuId );
+ /** Update html and threaded messages preferences in Folder menu. */
+ void updateFolderMenu();
+ /** Enable or disable the "mark all as read" action. Needs to happen more
+ * often the the other updates and is therefor in its own method. */
+ void updateMarkAsReadAction();
+
+ /** XML-GUI stuff */
+ void slotEditNotifications();
+ void slotEditKeys();
+
+ /** changes the caption and displays the foldername */
+ void slotChangeCaption(QListViewItem*);
+ void removeDuplicates();
+
+ void slotCustomReplyToMsg( int tid );
+ void slotCustomReplyAllToMsg( int tid );
+ void slotForwardInlineMsg();
+ void slotForwardAttachedMsg();
+ void slotForwardDigestMsg();
+ void slotRedirectMsg();
+ void slotCustomForwardMsg( int tid );
+ void slotNoQuoteReplyToMsg();
+ void slotSubjectFilter();
+ void slotMailingListFilter();
+ void slotFromFilter();
+ void slotToFilter();
+ void slotPrintMsg();
+ void slotCreateTodo();
+
+ void slotConfigChanged();
+ /** Remove the shortcut actions associated with a folder. */
+ void slotFolderRemoved( KMFolder *folder );
+
+ /** Show a splash screen for the longer-lasting operation */
+ void slotShowBusySplash();
+ /** Show a message screen explaining that we are currently offline, when
+ * an online folder is selected. */
+ void showOfflinePage();
+
+private:
+ /** Get override character encoding. */
+ QString overrideEncoding() const;
+
+ void initializeIMAPActions( bool setState );
+
+ /** Helper which finds the associated account if there is a current
+ * folder and it is an imap or disconnected imap one.
+ */
+ KMail::ImapAccountBase* findCurrentImapAccountBase();
+
+ /** Helper which finds the associated IMAP path if there is a current
+ * folder and it is an imap or disconnected imap one.
+ */
+ QString findCurrentImapPath();
+
+ void setupFolderView();
+
+private slots:
+ void slotRequestFullSearchFromQuickSearch();
+ void updateVactionScriptStatus( bool active );
+
+private:
+ // Message actions
+ KAction *mTrashAction, *mDeleteAction, *mTrashThreadAction,
+ *mDeleteThreadAction, *mSaveAsAction, *mUseAction,
+ *mSendAgainAction, *mApplyAllFiltersAction, *mFindInMessageAction,
+ *mSaveAttachmentsAction, *mOpenAction, *mViewSourceAction,
+ *mFavoritesCheckMailAction;
+ // Composition actions
+ KAction *mPrintAction,
+ *mForwardInlineAction, *mForwardAttachedAction, *mForwardDigestAction,
+ *mRedirectAction;
+ KActionMenu *mForwardActionMenu;
+ // Filter actions
+ KActionMenu *mFilterMenu;
+ KAction *mSubjectFilterAction, *mFromFilterAction, *mToFilterAction,
+ *mListFilterAction;
+ KActionMenu *mTemplateMenu;
+
+ // Custom template actions menu
+ KActionMenu *mCustomReplyActionMenu,
+ *mCustomReplyAllActionMenu,
+ *mCustomForwardActionMenu;
+ // Signal mappers for custom template actions
+ QSignalMapper *mCustomReplyMapper,
+ *mCustomReplyAllMapper,
+ *mCustomForwardMapper;
+
+ KActionMenu *mThreadStatusMenu,
+ *mMoveActionMenu, *mCopyActionMenu, *mApplyFilterActionsMenu;
+ KAction *mMarkThreadAsNewAction;
+ KAction *mMarkThreadAsReadAction;
+ KAction *mMarkThreadAsUnreadAction;
+ KToggleAction *mToggleThreadTodoAction;
+ KToggleAction *mToggleThreadFlagAction;
+
+ KToggleAction *mWatchThreadAction, *mIgnoreThreadAction;
+
+ /** we need to access those KToggleActions from the foldertree-popup */
+ KRadioAction* mUnreadColumnToggle;
+ KRadioAction* mUnreadTextToggle;
+ KToggleAction* mTotalColumnToggle;
+ KToggleAction* mSizeColumnToggle;
+
+ QVBox *mSearchAndTree;
+ QHBox *mFolderQuickSearch;
+ KMFolderTree *mFolderTree;
+ KMail::FavoriteFolderView *mFavoriteFolderView;
+ QWidget *mFolderView;
+ QSplitter *mFolderViewParent;
+ KMReaderWin *mMsgView;
+ QSplitter *mPanner1, *mPanner2;
+ QSplitter *mFolderViewSplitter;
+ KMHeaders *mHeaders;
+ QVBox *mSearchAndHeaders;
+ KToolBar *mSearchToolBar;
+ KMail::HeaderListQuickSearch *mQuickSearchLine;
+ QGuardedPtr<KMFolder> mFolder;
+ KMFolder *mTemplateFolder;
+ QPopupMenu *mViewMenu, *mBodyPartsMenu;
+ KAction *mlistFilterAction;
+ bool mIntegrated;
+ bool mBeepOnNew;
+ bool mConfirmEmpty;
+ QString mStartupFolder;
+ int mMessageStatusId;
+ QValueList<int> mPanner1Sep, mPanner2Sep;
+ KURL mUrlCurrent;
+ QPopupMenu *mActMenu;
+ QPopupMenu *mSendMenu;
+ QPopupMenu *mFileMenu;
+
+ bool mLongFolderList;
+
+ bool mStartupDone;
+ KMMenuToFolder mMenuToFolder;
+ int copyId, moveId, htmlId, threadId;
+ bool mHtmlPref, mHtmlLoadExtPref, mThreadPref,
+ mFolderHtmlPref, mFolderHtmlLoadExtPref, mFolderThreadPref,
+ mFolderThreadSubjPref, mReaderWindowActive, mReaderWindowBelow;
+ bool mEnableFavoriteFolderView;
+ bool mEnableFolderQuickSearch;
+ bool mEnableQuickSearch;
+
+// QPopupMenu *mMessageMenu;
+ KMail::SearchWindow *mSearchWin;
+
+ KAction *mNewFolderAction, *mModifyFolderAction, *mRemoveFolderAction, *mExpireFolderAction,
+ *mCompactFolderAction, *mRefreshFolderAction, *mEmptyFolderAction,
+ *mMarkAllAsReadAction, *mFolderMailingListPropertiesAction,
+ *mFolderShortCutCommandAction, *mTroubleshootFolderAction,
+ *mRemoveDuplicatesAction;
+ KToggleAction *mPreferHtmlAction, *mPreferHtmlLoadExtAction, *mThreadMessagesAction;
+ KToggleAction *mThreadBySubjectAction;
+ KToggleAction *mFolderAction, *mHeaderAction, *mMimeAction;
+
+ QTimer *menutimer;
+ QTimer *mShowBusySplashTimer;
+
+ QGuardedPtr<KMail::Vacation> mVacation;
+#if !defined(NDEBUG)
+ QGuardedPtr<KMail::SieveDebugDialog> mSieveDebugDialog;
+#endif
+ KActionCollection *mActionCollection;
+ KActionSeparator *mToolbarActionSeparator;
+ QVBoxLayout *mTopLayout;
+ bool mDestructed, mForceJumpToUnread, mShowingOfflineScreen;
+ QPtrList<KAction> mFilterMenuActions;
+ QPtrList<KAction> mFilterTBarActions;
+ QPtrList<KMMetaFilterActionCommand> mFilterCommands;
+ QDict<FolderShortcutCommand> mFolderShortcutCommands;
+ QGuardedPtr <KMail::FolderJob> mJob;
+
+ QValueVector<QString> mCustomTemplates;
+ QPtrList<KAction> mCustomTemplateActions;
+
+ KMSystemTray *mSystemTray;
+ KConfig *mConfig;
+ KXMLGUIClient *mGUIClient;
+
+ KMail::MessageActions *mMsgActions;
+
+ static QValueList<KMMainWidget*>* s_mainWidgetList;
+
+ KStatusBarLabel *mVacationScriptIndicator;
+ bool mVacationIndicatorActive;
+};
+
+#endif
+
diff --git a/kmail/kmmainwin.cpp b/kmail/kmmainwin.cpp
new file mode 100644
index 00000000..ce0c6460
--- /dev/null
+++ b/kmail/kmmainwin.cpp
@@ -0,0 +1,222 @@
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "kmmainwin.h"
+#include "kmmainwidget.h"
+#include "kstatusbar.h"
+#include "messagesender.h"
+#include "progressdialog.h"
+#include "statusbarprogresswidget.h"
+#include "accountwizard.h"
+#include "broadcaststatus.h"
+#include "accountmanager.h"
+#include "kmtransport.h"
+
+#include <kapplication.h>
+#include <klocale.h>
+#include <kedittoolbar.h>
+#include <kconfig.h>
+#include <kmessagebox.h>
+#include <kstringhandler.h>
+#include <kdebug.h>
+#include <ktip.h>
+
+#include "kmmainwin.moc"
+
+KMMainWin::KMMainWin(QWidget *)
+ : KMainWindow( 0, "kmail-mainwindow#" ),
+ mReallyClose( false )
+{
+ // Set this to be the group leader for all subdialogs - this means
+ // modal subdialogs will only affect this dialog, not the other windows
+ setWFlags( getWFlags() | WGroupLeader );
+
+ kapp->ref();
+
+ (void) new KAction( i18n("New &Window"), "window_new", 0,
+ this, SLOT(slotNewMailReader()),
+ actionCollection(), "new_mail_client" );
+
+ mKMMainWidget = new KMMainWidget( this, "KMMainWidget", this, actionCollection() );
+ mKMMainWidget->resize( 725, 700 );
+ setCentralWidget(mKMMainWidget);
+ setupStatusBar();
+ if (kmkernel->xmlGuiInstance())
+ setInstance( kmkernel->xmlGuiInstance() );
+
+ if ( kmkernel->firstInstance() )
+ QTimer::singleShot( 200, this, SLOT(slotShowTipOnStart()) );
+
+ setStandardToolBarMenuEnabled(true);
+
+ KStdAction::configureToolbars(this, SLOT(slotEditToolbars()),
+ actionCollection());
+
+ KStdAction::keyBindings(mKMMainWidget, SLOT(slotEditKeys()),
+ actionCollection());
+
+ KStdAction::quit( this, SLOT(slotQuit()), actionCollection());
+ createGUI( "kmmainwin.rc", false );
+ // Don't use conserveMemory() because this renders dynamic plugging
+ // of actions unusable!
+
+ mKMMainWidget->setupForwardingActionsList();
+
+ applyMainWindowSettings(KMKernel::config(), "Main Window");
+
+ connect( KPIM::BroadcastStatus::instance(), SIGNAL( statusMsg( const QString& ) ),
+ this, SLOT( displayStatusMsg(const QString&) ) );
+
+ connect(kmkernel, SIGNAL(configChanged()),
+ this, SLOT(slotConfigChanged()));
+
+ connect(mKMMainWidget, SIGNAL(captionChangeRequest(const QString&)),
+ SLOT(setCaption(const QString&)) );
+
+ // Enable mail checks again (see destructor)
+ kmkernel->enableMailCheck();
+
+ if ( kmkernel->firstStart() )
+ AccountWizard::start( kmkernel, this );
+}
+
+KMMainWin::~KMMainWin()
+{
+ saveMainWindowSettings(KMKernel::config(), "Main Window");
+ KMKernel::config()->sync();
+ kapp->deref();
+
+ if ( !kmkernel->haveSystemTrayApplet() ) {
+ // Check if this was the last KMMainWin
+ int not_withdrawn = 0;
+ QPtrListIterator<KMainWindow> it(*KMainWindow::memberList);
+ for (it.toFirst(); it.current(); ++it){
+ if ( !it.current()->isHidden() &&
+ it.current()->isTopLevel() &&
+ it.current() != this &&
+ ::qt_cast<KMMainWin *>( it.current() )
+ )
+ not_withdrawn++;
+ }
+
+ if ( not_withdrawn == 0 ) {
+ kdDebug(5006) << "Closing last KMMainWin: stopping mail check" << endl;
+ // Running KIO jobs prevent kapp from exiting, so we need to kill them
+ // if they are only about checking mail (not important stuff like moving messages)
+ kmkernel->abortMailCheck();
+ kmkernel->acctMgr()->cancelMailCheck();
+ }
+ }
+}
+
+void KMMainWin::displayStatusMsg(const QString& aText)
+{
+ if ( !statusBar() || !mLittleProgress) return;
+ int statusWidth = statusBar()->width() - mLittleProgress->width()
+ - fontMetrics().maxWidth();
+ QString text = KStringHandler::rPixelSqueeze( " " + aText, fontMetrics(),
+ statusWidth );
+
+ // ### FIXME: We should disable richtext/HTML (to avoid possible denial of service attacks),
+ // but this code would double the size of the satus bar if the user hovers
+ // over an <foo@bar.com>-style email address :-(
+// text.replace("&", "&amp;");
+// text.replace("<", "&lt;");
+// text.replace(">", "&gt;");
+
+ statusBar()->changeItem(text, mMessageStatusId);
+}
+
+//-----------------------------------------------------------------------------
+void KMMainWin::slotNewMailReader()
+{
+ KMMainWin *d;
+
+ d = new KMMainWin();
+ d->show();
+ d->resize(d->size());
+}
+
+
+void KMMainWin::slotEditToolbars()
+{
+ saveMainWindowSettings(KMKernel::config(), "Main Window");
+ KEditToolbar dlg(actionCollection(), "kmmainwin.rc");
+
+ connect( &dlg, SIGNAL(newToolbarConfig()),
+ SLOT(slotUpdateToolbars()) );
+
+ dlg.exec();
+}
+
+void KMMainWin::slotUpdateToolbars()
+{
+ // remove dynamically created actions before editing
+ mKMMainWidget->clearFilterActions();
+
+ createGUI("kmmainwin.rc", false);
+ applyMainWindowSettings(KMKernel::config(), "Main Window");
+
+ // plug dynamically created actions again
+ mKMMainWidget->initializeFilterActions();
+}
+
+void KMMainWin::setupStatusBar()
+{
+ mMessageStatusId = 1;
+
+ /* Create a progress dialog and hide it. */
+ mProgressDialog = new KPIM::ProgressDialog( statusBar(), this );
+ mProgressDialog->hide();
+
+ mLittleProgress = new StatusbarProgressWidget( mProgressDialog, statusBar() );
+ mLittleProgress->show();
+
+ statusBar()->addWidget( mLittleProgress, 0 , true );
+ statusBar()->insertItem(i18n(" Initializing..."), 1, 4 );
+ statusBar()->setItemAlignment( 1, AlignLeft | AlignVCenter );
+ statusBar()->addWidget( mKMMainWidget->vacationScriptIndicator(), 1 );
+ mLittleProgress->show();
+}
+
+/** Read configuration options after widgets are created. */
+void KMMainWin::readConfig(void)
+{
+}
+
+/** Write configuration options. */
+void KMMainWin::writeConfig(void)
+{
+ mKMMainWidget->writeConfig();
+}
+
+void KMMainWin::slotQuit()
+{
+ mReallyClose = true;
+ close();
+}
+
+void KMMainWin::slotConfigChanged()
+{
+ readConfig();
+}
+
+//-----------------------------------------------------------------------------
+bool KMMainWin::queryClose()
+{
+ if ( kapp->sessionSaving() )
+ writeConfig();
+
+ if ( kmkernel->shuttingDown() || kapp->sessionSaving() || mReallyClose )
+ return true;
+ return kmkernel->canQueryClose();
+}
+
+void KMMainWin::slotShowTipOnStart()
+{
+ KTipDialog::showTip( this );
+}
+
+
diff --git a/kmail/kmmainwin.h b/kmail/kmmainwin.h
new file mode 100644
index 00000000..eb538267
--- /dev/null
+++ b/kmail/kmmainwin.h
@@ -0,0 +1,80 @@
+/*
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ *
+ * 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 __KMMAINWIN
+#define __KMMAINWIN
+
+#include <kmainwindow.h>
+#include "kdeversion.h"
+#include "qstring.h"
+
+class KMMainWidget;
+namespace KPIM {
+ class StatusbarProgressWidget;
+ class ProgressDialog;
+}
+using KPIM::StatusbarProgressWidget;
+using KPIM::ProgressDialog;
+
+class KMMainWin : public KMainWindow
+{
+ Q_OBJECT
+
+public:
+ // the main window needs to have a name since else restoring the window
+ // settings by kwin doesn't work
+ KMMainWin(QWidget *parent = 0);
+ virtual ~KMMainWin();
+ KMMainWidget *mainKMWidget() const { return mKMMainWidget; };
+ StatusbarProgressWidget* progressWidget() const { return mLittleProgress; }
+ ProgressDialog* progressDialog() const { return mProgressDialog; }
+
+
+ /** Read configuration options after widgets are created. */
+ virtual void readConfig(void);
+
+ /** Write configuration options. */
+ virtual void writeConfig(void);
+
+public slots:
+ void displayStatusMsg(const QString&);
+ void slotEditToolbars();
+ void slotUpdateToolbars();
+ void setupStatusBar();
+
+protected:
+ virtual bool queryClose ();
+
+protected slots:
+ void slotQuit();
+ void slotConfigChanged();
+ void slotShowTipOnStart();
+
+private slots:
+ void slotNewMailReader();
+
+private:
+ KMMainWidget *mKMMainWidget;
+ StatusbarProgressWidget *mLittleProgress;
+ ProgressDialog *mProgressDialog;
+ int mMessageStatusId;
+ bool mReallyClose;
+};
+
+#endif
diff --git a/kmail/kmmainwin.rc b/kmail/kmmainwin.rc
new file mode 100644
index 00000000..07b9f6c5
--- /dev/null
+++ b/kmail/kmmainwin.rc
@@ -0,0 +1,205 @@
+<!-- This file should be synchronized with kmail_part.rc to provide
+ the same menu entries at the same place in KMail and Kontact -->
+
+<!DOCTYPE kpartgui>
+<kpartgui version="100" name="kmmainwin" >
+ <MenuBar>
+ <Menu noMerge="1" name="file" >
+ <text>&amp;File</text>
+ <Menu name="file_new" >
+ <text>New</text>
+ <Action name="new_message" />
+ <Action name="new_from_template" />
+ <Separator/>
+ <!-- Action name="new_todo" />
+ etc -->
+ <Separator/>
+ <Action name="new_folder" />
+ <Separator/>
+ <Action name="new_mail_client" />
+ </Menu>
+ <Separator/>
+ <Action name="file_open" />
+ <Action name="file_save_as" />
+ <Action name="import"/>
+ <Action name="file_print" />
+ <Separator/>
+ <Action name="expire_all_folders" />
+ <Action name="file_invalidate_imap_cache"/>
+ <Action name="empty_trash"/>
+ <Separator/>
+ <Action name="online_status" />
+ <Action name="check_mail" />
+ <Action name="favorite_check_mail"/>
+ <Action name="check_mail_in" />
+ <Action name="send_queued" />
+ <Action name="send_queued_via" />
+ <Separator/>
+ <Action name="file_quit" />
+ </Menu>
+ <Menu noMerge="1" name="edit" >
+ <text>&amp;Edit</text>
+ <Action name="kmail_undo" />
+ <Action name="kmail_redo" />
+ <Separator/>
+ <Action name="kmail_copy" />
+ <Separator/>
+ <Action name="copy_folder"/>
+ <Action name="cut_folder"/>
+ <Action name="paste_folder"/>
+ <Separator/>
+ <Action name="copy_messages"/>
+ <Action name="cut_messages"/>
+ <Action name="paste_messages"/>
+ <Separator/>
+ <Action name="move_to_trash" />
+ <Action name="move_thread_to_trash" />
+ <Separator/>
+ <Action name="search_messages" />
+ <Action name="find_in_messages" />
+ <Separator/>
+ <Action name="mark_all_messages" />
+ <Action name="mark_all_text" />
+ </Menu>
+ <Menu noMerge="1" name="view">
+ <text>&amp;View</text>
+ <Action name="view_headers"/>
+ <Action name="view_attachments"/>
+ <Separator/>
+ <Action name="view_unread"/>
+ <Action name="view_columns_total"/>
+ <Separator/>
+ <Action name="expand_thread"/>
+ <Action name="collapse_thread"/>
+ <Action name="expand_all_threads"/>
+ <Action name="collapse_all_threads"/>
+ <Separator/>
+ <Action name="view_source"/>
+ <Separator/>
+ <Action name="toggle_fixedfont"/>
+ <Action name="encoding" />
+ </Menu>
+ <Menu noMerge="1" name="go">
+ <text>&amp;Go</text>
+ <Action name="go_next_message"/>
+ <Action name="go_next_unread_message"/>
+ <Action name="go_prev_message"/>
+ <Action name="go_prev_unread_message"/>
+ <Separator/>
+ <Action name="go_next_unread_folder"/>
+ <Action name="go_prev_unread_folder"/>
+ <Separator/>
+ <Action name="go_next_unread_text"/>
+ </Menu>
+ <Menu noMerge="1" name="folder" >
+ <text>F&amp;older</text>
+ <Action name="new_folder" />
+ <Separator/>
+ <Action name="mark_all_as_read" />
+ <Separator/>
+ <Action name="remove_duplicate_messages" />
+ <Separator/>
+ <Action name="refresh_folder" />
+ <Action name="troubleshoot_folder" />
+ <Separator/>
+ <Action name="empty" />
+ <Action name="delete_folder" />
+ <Separator/>
+ <Action name="prefer_html" />
+ <Action name="prefer_html_external_refs" />
+ <Action name="thread_messages" />
+ <Action name="thread_messages_by_subject" />
+ <Separator/>
+ <Action name="folder_mailinglist_properties" />
+ <Action name="folder_shortcut_command" />
+ <Action name="modify" />
+ </Menu>
+ <Menu noMerge="1" name="message" >
+ <text>&amp;Message</text>
+ <Action name="new_message" />
+ <Separator/>
+ <Action name="reply" />
+ <Action name="reply_all" />
+ <Menu noMerge="1" name="reply_special" >
+ <text>Reply Special</text>
+ <Action name="reply_author" />
+ <Action name="reply_list" />
+ <Action name="noquotereply" />
+ </Menu>
+ <Menu name="menubar_message_forward">
+ <text>&amp;Forward</text>
+ <ActionList name="forward_action_list"/>
+ </Menu>
+ <Action name="send_again" />
+ <Action name="edit"/>
+ <Separator/>
+ <Action name="copy_to" />
+ <Action name="move_to" />
+ <Separator/>
+ <Action name="set_status" />
+ <Action name="thread_status" />
+ <Separator/>
+ <Action name="create_filter"/>
+ <Menu name="apply_filter_actions" >
+ <text>A&amp;pply Filter</text>
+ <Action name="apply_filters" />
+ <ActionList name="menu_filter_actions" />
+ </Menu>
+ <Separator/>
+ <Action name="create_todo"/>
+ </Menu>
+ <Menu noMerge="1" name="tools">
+ <text>&amp;Tools</text>
+ <Action name="search_messages" />
+ <Separator/>
+ <Action name="addressbook"/>
+ <Action name="tools_start_certman"/>
+ <Action name="tools_start_kwatchgnupg"/>
+ <Separator/>
+ <Action name="tools_edit_vacation"/>
+ <Separator/>
+ <Action name="tools_debug_sieve"/>
+ <Action name="filter_log_viewer"/>
+ <Separator/>
+ <Action name="antiSpamWizard"/>
+ <Action name="antiVirusWizard"/>
+ </Menu>
+ <Menu noMerge="1" name="settings">
+ <text>&amp;Settings</text>
+ <Merge name="StandardToolBarMenuHandler"/>
+ <Action name="show_quick_search"/>
+ <Action name="filter" append="save_merge"/>
+ <Action name="popFilter" append="save_merge"/>
+ <Action name="sieveFilters" append="save_merge"/>
+ <Separator/>
+ <Action name="options_configure_keybinding" group="settings_configure"/>
+ <Action name="kmail_configure_notifications" group="settings_configure"/>
+ <Action name="options_configure_toolbars" group="settings_configure" />
+ <Action name="kmail_configure_kmail" group="settings_configure"/>
+ </Menu>
+ <Menu name="help">
+ <text>&amp;Help</text>
+ <Action name="help_kmail_welcomepage"/>
+ </Menu>
+ </MenuBar>
+ <ToolBar noMerge="1" name="mainToolBar" fullWidth="true" >
+ <text>Main Toolbar</text>
+ <Action name="new_message" />
+ <Action name="file_save_as" />
+ <Action name="file_print" />
+ <Separator/>
+ <Action name="check_mail_in" />
+ <Separator/>
+ <Action name="message_reply_menu" />
+ <Action name="message_forward"/>
+ <Separator/>
+ <Action name="go_prev_unread_message"/>
+ <Action name="go_next_unread_message"/>
+ <Separator/>
+ <Action name="move_to_trash" />
+ <Separator/>
+ <Action name="search_messages" />
+ <ActionList name="toolbar_filter_actions" />
+ <Action name="create_todo"/>
+ </ToolBar>
+</kpartgui>
diff --git a/kmail/kmmessage.cpp b/kmail/kmmessage.cpp
new file mode 100644
index 00000000..9c006b68
--- /dev/null
+++ b/kmail/kmmessage.cpp
@@ -0,0 +1,4373 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+// kmmessage.cpp
+
+// if you do not want GUI elements in here then set ALLOW_GUI to 0.
+#include <config.h>
+// needed temporarily until KMime is replacing the partNode helper class:
+#include "partNode.h"
+
+
+#define ALLOW_GUI 1
+#include "kmkernel.h"
+#include "kmmessage.h"
+#include "mailinglist-magic.h"
+#include "messageproperty.h"
+using KMail::MessageProperty;
+#include "objecttreeparser.h"
+using KMail::ObjectTreeParser;
+#include "kmfolderindex.h"
+#include "undostack.h"
+#include "kmversion.h"
+#include "headerstrategy.h"
+#include "globalsettings.h"
+using KMail::HeaderStrategy;
+#include "kmaddrbook.h"
+#include "kcursorsaver.h"
+#include "templateparser.h"
+
+#include <libkpimidentities/identity.h>
+#include <libkpimidentities/identitymanager.h>
+#include <libemailfunctions/email.h>
+
+#include <kasciistringtools.h>
+
+#include <kpgpblock.h>
+#include <kaddrbook.h>
+
+#include <kapplication.h>
+#include <kglobalsettings.h>
+#include <kdebug.h>
+#include <kconfig.h>
+#include <khtml_part.h>
+#include <kuser.h>
+#include <kidna.h>
+#include <kasciistricmp.h>
+
+#include <qcursor.h>
+#include <qtextcodec.h>
+#include <qmessagebox.h>
+#include <kmime_util.h>
+#include <kmime_charfreq.h>
+
+#include <kmime_header_parsing.h>
+using KMime::HeaderParsing::parseAddressList;
+using namespace KMime::Types;
+
+#include <mimelib/body.h>
+#include <mimelib/field.h>
+#include <mimelib/mimepp.h>
+#include <mimelib/string.h>
+#include <assert.h>
+#include <sys/time.h>
+#include <time.h>
+#include <klocale.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "util.h"
+
+#if ALLOW_GUI
+#include <kmessagebox.h>
+#endif
+
+using namespace KMime;
+
+static DwString emptyString("");
+
+// Values that are set from the config file with KMMessage::readConfig()
+static QString sReplyLanguage, sReplyStr, sReplyAllStr, sIndentPrefixStr;
+static bool sSmartQuote,
+ sWordWrap;
+static int sWrapCol;
+static QStringList sPrefCharsets;
+
+QString KMMessage::sForwardStr;
+const HeaderStrategy * KMMessage::sHeaderStrategy = HeaderStrategy::rich();
+//helper
+static void applyHeadersToMessagePart( DwHeaders& headers, KMMessagePart* aPart );
+
+QValueList<KMMessage*> KMMessage::sPendingDeletes;
+
+//-----------------------------------------------------------------------------
+KMMessage::KMMessage(DwMessage* aMsg)
+ : KMMsgBase()
+{
+ init( aMsg );
+ // aMsg might need assembly
+ mNeedsAssembly = true;
+}
+
+//-----------------------------------------------------------------------------
+KMMessage::KMMessage(KMFolder* parent): KMMsgBase(parent)
+{
+ init();
+}
+
+
+//-----------------------------------------------------------------------------
+KMMessage::KMMessage(KMMsgInfo& msgInfo): KMMsgBase()
+{
+ init();
+ // now overwrite a few from the msgInfo
+ mMsgSize = msgInfo.msgSize();
+ mFolderOffset = msgInfo.folderOffset();
+ mStatus = msgInfo.status();
+ mEncryptionState = msgInfo.encryptionState();
+ mSignatureState = msgInfo.signatureState();
+ mMDNSentState = msgInfo.mdnSentState();
+ mDate = msgInfo.date();
+ mFileName = msgInfo.fileName();
+ KMMsgBase::assign(&msgInfo);
+}
+
+
+//-----------------------------------------------------------------------------
+KMMessage::KMMessage(const KMMessage& other) :
+ KMMsgBase( other ),
+ ISubject(),
+ mMsg(0)
+{
+ init(); // to be safe
+ assign( other );
+}
+
+void KMMessage::init( DwMessage* aMsg )
+{
+ mNeedsAssembly = false;
+ if ( aMsg ) {
+ mMsg = aMsg;
+ } else {
+ mMsg = new DwMessage;
+ }
+ mOverrideCodec = 0;
+ mDecodeHTML = false;
+ mComplete = true;
+ mReadyToShow = true;
+ mMsgSize = 0;
+ mMsgLength = 0;
+ mFolderOffset = 0;
+ mStatus = KMMsgStatusNew;
+ mEncryptionState = KMMsgEncryptionStateUnknown;
+ mSignatureState = KMMsgSignatureStateUnknown;
+ mMDNSentState = KMMsgMDNStateUnknown;
+ mDate = 0;
+ mUnencryptedMsg = 0;
+ mLastUpdated = 0;
+ mCursorPos = 0;
+ mMsgInfo = 0;
+ mIsParsed = false;
+}
+
+void KMMessage::assign( const KMMessage& other )
+{
+ MessageProperty::forget( this );
+ delete mMsg;
+ delete mUnencryptedMsg;
+
+ mNeedsAssembly = true;//other.mNeedsAssembly;
+ if( other.mMsg )
+ mMsg = new DwMessage( *(other.mMsg) );
+ else
+ mMsg = 0;
+ mOverrideCodec = other.mOverrideCodec;
+ mDecodeHTML = other.mDecodeHTML;
+ mMsgSize = other.mMsgSize;
+ mMsgLength = other.mMsgLength;
+ mFolderOffset = other.mFolderOffset;
+ mStatus = other.mStatus;
+ mEncryptionState = other.mEncryptionState;
+ mSignatureState = other.mSignatureState;
+ mMDNSentState = other.mMDNSentState;
+ mIsParsed = other.mIsParsed;
+ mDate = other.mDate;
+ if( other.hasUnencryptedMsg() )
+ mUnencryptedMsg = new KMMessage( *other.unencryptedMsg() );
+ else
+ mUnencryptedMsg = 0;
+ setDrafts( other.drafts() );
+ setTemplates( other.templates() );
+ //mFileName = ""; // we might not want to copy the other messages filename (?)
+ //KMMsgBase::assign( &other );
+}
+
+//-----------------------------------------------------------------------------
+KMMessage::~KMMessage()
+{
+ delete mMsgInfo;
+ delete mMsg;
+ kmkernel->undoStack()->msgDestroyed( this );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setReferences(const QCString& aStr)
+{
+ if (!aStr) return;
+ mMsg->Headers().References().FromString(aStr);
+ mNeedsAssembly = true;
+}
+
+
+//-----------------------------------------------------------------------------
+QCString KMMessage::id() const
+{
+ DwHeaders& header = mMsg->Headers();
+ if (header.HasMessageId())
+ return KMail::Util::CString( header.MessageId().AsString() );
+ else
+ return "";
+}
+
+
+//-----------------------------------------------------------------------------
+//WARNING: This method updates the memory resident cache of serial numbers
+//WARNING: held in MessageProperty, but it does not update the persistent
+//WARNING: store of serial numbers on the file system that is managed by
+//WARNING: KMMsgDict
+void KMMessage::setMsgSerNum(unsigned long newMsgSerNum)
+{
+ MessageProperty::setSerialCache( this, newMsgSerNum );
+}
+
+
+//-----------------------------------------------------------------------------
+bool KMMessage::isMessage() const
+{
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+bool KMMessage::transferInProgress() const
+{
+ return MessageProperty::transferInProgress( getMsgSerNum() );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setTransferInProgress(bool value, bool force)
+{
+ MessageProperty::setTransferInProgress( getMsgSerNum(), value, force );
+ if ( !transferInProgress() && sPendingDeletes.contains( this ) ) {
+ sPendingDeletes.remove( this );
+ if ( parent() ) {
+ int idx = parent()->find( this );
+ if ( idx > 0 ) {
+ parent()->removeMsg( idx );
+ }
+ }
+ }
+}
+
+
+
+bool KMMessage::isUrgent() const {
+ return headerField( "Priority" ).contains( "urgent", false )
+ || headerField( "X-Priority" ).startsWith( "2" );
+}
+
+//-----------------------------------------------------------------------------
+void KMMessage::setUnencryptedMsg( KMMessage* unencrypted )
+{
+ delete mUnencryptedMsg;
+ mUnencryptedMsg = unencrypted;
+}
+
+//-----------------------------------------------------------------------------
+//FIXME: move to libemailfunctions
+KPIM::EmailParseResult KMMessage::isValidEmailAddressList( const QString& aStr,
+ QString& brokenAddress )
+{
+ if ( aStr.isEmpty() ) {
+ return KPIM::AddressEmpty;
+ }
+
+ QStringList list = KPIM::splitEmailAddrList( aStr );
+ for( QStringList::const_iterator it = list.begin(); it != list.end(); ++it ) {
+ KPIM::EmailParseResult errorCode = KPIM::isValidEmailAddress( *it );
+ if ( errorCode != KPIM::AddressOk ) {
+ brokenAddress = ( *it );
+ return errorCode;
+ }
+ }
+ return KPIM::AddressOk;
+}
+
+//-----------------------------------------------------------------------------
+const DwString& KMMessage::asDwString() const
+{
+ if (mNeedsAssembly)
+ {
+ mNeedsAssembly = false;
+ mMsg->Assemble();
+ }
+ return mMsg->AsString();
+}
+
+//-----------------------------------------------------------------------------
+const DwMessage* KMMessage::asDwMessage()
+{
+ if (mNeedsAssembly)
+ {
+ mNeedsAssembly = false;
+ mMsg->Assemble();
+ }
+ return mMsg;
+}
+
+//-----------------------------------------------------------------------------
+QCString KMMessage::asString() const {
+ return KMail::Util::CString( asDwString() );
+}
+
+
+QByteArray KMMessage::asSendableString() const
+{
+ KMMessage msg( new DwMessage( *this->mMsg ) );
+ msg.removePrivateHeaderFields();
+ msg.removeHeaderField("Bcc");
+ return KMail::Util::ByteArray( msg.asDwString() ); // and another copy again!
+}
+
+QCString KMMessage::headerAsSendableString() const
+{
+ KMMessage msg( new DwMessage( *this->mMsg ) );
+ msg.removePrivateHeaderFields();
+ msg.removeHeaderField("Bcc");
+ return msg.headerAsString().latin1();
+}
+
+void KMMessage::removePrivateHeaderFields() {
+ removeHeaderField("Status");
+ removeHeaderField("X-Status");
+ removeHeaderField("X-KMail-EncryptionState");
+ removeHeaderField("X-KMail-SignatureState");
+ removeHeaderField("X-KMail-MDN-Sent");
+ removeHeaderField("X-KMail-Transport");
+ removeHeaderField("X-KMail-Identity");
+ removeHeaderField("X-KMail-Fcc");
+ removeHeaderField("X-KMail-Redirect-From");
+ removeHeaderField("X-KMail-Link-Message");
+ removeHeaderField("X-KMail-Link-Type");
+ removeHeaderField( "X-KMail-Markup" );
+}
+
+//-----------------------------------------------------------------------------
+void KMMessage::setStatusFields()
+{
+ char str[2] = { 0, 0 };
+
+ setHeaderField("Status", status() & KMMsgStatusNew ? "R" : "RO");
+ setHeaderField("X-Status", statusToStr(status()));
+
+ str[0] = (char)encryptionState();
+ setHeaderField("X-KMail-EncryptionState", str);
+
+ str[0] = (char)signatureState();
+ //kdDebug(5006) << "Setting SignatureState header field to " << str[0] << endl;
+ setHeaderField("X-KMail-SignatureState", str);
+
+ str[0] = static_cast<char>( mdnSentState() );
+ setHeaderField("X-KMail-MDN-Sent", str);
+
+ // We better do the assembling ourselves now to prevent the
+ // mimelib from changing the message *body*. (khz, 10.8.2002)
+ mNeedsAssembly = false;
+ mMsg->Headers().Assemble();
+ mMsg->Assemble( mMsg->Headers(),
+ mMsg->Body() );
+}
+
+
+//----------------------------------------------------------------------------
+QString KMMessage::headerAsString() const
+{
+ DwHeaders& header = mMsg->Headers();
+ header.Assemble();
+ if ( header.AsString().empty() )
+ return QString::null;
+ return QString::fromLatin1( header.AsString().c_str() );
+}
+
+
+//-----------------------------------------------------------------------------
+DwMediaType& KMMessage::dwContentType()
+{
+ return mMsg->Headers().ContentType();
+}
+
+void KMMessage::fromByteArray( const QByteArray & ba, bool setStatus ) {
+ return fromDwString( DwString( ba.data(), ba.size() ), setStatus );
+}
+
+void KMMessage::fromString( const QCString & str, bool aSetStatus ) {
+ return fromDwString( KMail::Util::dwString( str ), aSetStatus );
+}
+
+void KMMessage::fromDwString(const DwString& str, bool aSetStatus)
+{
+ delete mMsg;
+ mMsg = new DwMessage;
+ mMsg->FromString( str );
+ mMsg->Parse();
+
+ if (aSetStatus) {
+ setStatus(headerField("Status").latin1(), headerField("X-Status").latin1());
+ setEncryptionStateChar( headerField("X-KMail-EncryptionState").at(0) );
+ setSignatureStateChar( headerField("X-KMail-SignatureState").at(0) );
+ setMDNSentState( static_cast<KMMsgMDNSentState>( headerField("X-KMail-MDN-Sent").at(0).latin1() ) );
+ }
+ if (attachmentState() == KMMsgAttachmentUnknown && readyToShow())
+ updateAttachmentState();
+
+ mNeedsAssembly = false;
+ mDate = date();
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMessage::formatString(const QString& aStr) const
+{
+ QString result, str;
+ QChar ch;
+ uint j;
+
+ if (aStr.isEmpty())
+ return aStr;
+
+ unsigned int strLength(aStr.length());
+ for (uint i=0; i<strLength;) {
+ ch = aStr[i++];
+ if (ch == '%') {
+ ch = aStr[i++];
+ switch ((char)ch) {
+ case 'D':
+ /* I'm not too sure about this change. Is it not possible
+ to have a long form of the date used? I don't
+ like this change to a short XX/XX/YY date format.
+ At least not for the default. -sanders */
+ result += KMime::DateFormatter::formatDate( KMime::DateFormatter::Localized,
+ date(), sReplyLanguage, false );
+ break;
+ case 'e':
+ result += from();
+ break;
+ case 'F':
+ result += fromStrip();
+ break;
+ case 'f':
+ {
+ str = fromStrip();
+
+ for (j=0; str[j]>' '; j++)
+ ;
+ unsigned int strLength(str.length());
+ for (; j < strLength && str[j] <= ' '; j++)
+ ;
+ result += str[0];
+ if (str[j]>' ')
+ result += str[j];
+ else
+ if (str[1]>' ')
+ result += str[1];
+ }
+ break;
+ case 'T':
+ result += toStrip();
+ break;
+ case 't':
+ result += to();
+ break;
+ case 'C':
+ result += ccStrip();
+ break;
+ case 'c':
+ result += cc();
+ break;
+ case 'S':
+ result += subject();
+ break;
+ case '_':
+ result += ' ';
+ break;
+ case 'L':
+ result += "\n";
+ break;
+ case '%':
+ result += '%';
+ break;
+ default:
+ result += '%';
+ result += ch;
+ break;
+ }
+ } else
+ result += ch;
+ }
+ return result;
+}
+
+static void removeTrailingSpace( QString &line )
+{
+ int i = line.length()-1;
+ while( (i >= 0) && ((line[i] == ' ') || (line[i] == '\t')))
+ i--;
+ line.truncate( i+1);
+}
+
+static QString splitLine( QString &line)
+{
+ removeTrailingSpace( line );
+ int i = 0;
+ int j = -1;
+ int l = line.length();
+
+ // TODO: Replace tabs with spaces first.
+
+ while(i < l)
+ {
+ QChar c = line[i];
+ if ((c == '>') || (c == ':') || (c == '|'))
+ j = i+1;
+ else if ((c != ' ') && (c != '\t'))
+ break;
+ i++;
+ }
+
+ if ( j <= 0 )
+ {
+ return "";
+ }
+ if ( i == l )
+ {
+ QString result = line.left(j);
+ line = QString::null;
+ return result;
+ }
+
+ QString result = line.left(j);
+ line = line.mid(j);
+ return result;
+}
+
+static QString flowText(QString &text, const QString& indent, int maxLength)
+{
+ maxLength--;
+ if (text.isEmpty())
+ {
+ return indent+"<NULL>\n";
+ }
+ QString result;
+ while (1)
+ {
+ int i;
+ if ((int) text.length() > maxLength)
+ {
+ i = maxLength;
+ while( (i >= 0) && (text[i] != ' '))
+ i--;
+ if (i <= 0)
+ {
+ // Couldn't break before maxLength.
+ i = maxLength;
+// while( (i < (int) text.length()) && (text[i] != ' '))
+// i++;
+ }
+ }
+ else
+ {
+ i = text.length();
+ }
+
+ QString line = text.left(i);
+ if (i < (int) text.length())
+ text = text.mid(i);
+ else
+ text = QString::null;
+
+ result += indent + line + '\n';
+
+ if (text.isEmpty())
+ return result;
+ }
+}
+
+static bool flushPart(QString &msg, QStringList &part,
+ const QString &indent, int maxLength)
+{
+ maxLength -= indent.length();
+ if (maxLength < 20) maxLength = 20;
+
+ // Remove empty lines at end of quote
+ while ((part.begin() != part.end()) && part.last().isEmpty())
+ {
+ part.remove(part.fromLast());
+ }
+
+ QString text;
+ for(QStringList::Iterator it2 = part.begin();
+ it2 != part.end();
+ it2++)
+ {
+ QString line = (*it2);
+
+ if (line.isEmpty())
+ {
+ if (!text.isEmpty())
+ msg += flowText(text, indent, maxLength);
+ msg += indent + '\n';
+ }
+ else
+ {
+ if (text.isEmpty())
+ text = line;
+ else
+ text += ' '+line.stripWhiteSpace();
+
+ if (((int) text.length() < maxLength) || ((int) line.length() < (maxLength-10)))
+ msg += flowText(text, indent, maxLength);
+ }
+ }
+ if (!text.isEmpty())
+ msg += flowText(text, indent, maxLength);
+
+ bool appendEmptyLine = true;
+ if (!part.count())
+ appendEmptyLine = false;
+
+ part.clear();
+ return appendEmptyLine;
+}
+
+static QString stripSignature( const QString & msg, bool clearSigned ) {
+ if ( clearSigned )
+ return msg.left( msg.findRev( QRegExp( "\n--\\s?\n" ) ) );
+ else
+ return msg.left( msg.findRev( "\n-- \n" ) );
+}
+
+QString KMMessage::smartQuote( const QString & msg, int maxLineLength )
+{
+ QStringList part;
+ QString oldIndent;
+ bool firstPart = true;
+
+
+ const QStringList lines = QStringList::split('\n', msg, true);
+
+ QString result;
+ for(QStringList::const_iterator it = lines.begin();
+ it != lines.end();
+ ++it)
+ {
+ QString line = *it;
+
+ const QString indent = splitLine( line );
+
+ if ( line.isEmpty())
+ {
+ if (!firstPart)
+ part.append(QString::null);
+ continue;
+ };
+
+ if (firstPart)
+ {
+ oldIndent = indent;
+ firstPart = false;
+ }
+
+ if (oldIndent != indent)
+ {
+ QString fromLine;
+ // Search if the last non-blank line could be "From" line
+ if (part.count() && (oldIndent.length() < indent.length()))
+ {
+ QStringList::Iterator it2 = part.fromLast();
+ while( (it2 != part.end()) && (*it2).isEmpty())
+ --it2;
+
+ if ((it2 != part.end()) && ((*it2).endsWith(":")))
+ {
+ fromLine = oldIndent + (*it2) + '\n';
+ part.remove(it2);
+ }
+ }
+ if (flushPart( result, part, oldIndent, maxLineLength))
+ {
+ if (oldIndent.length() > indent.length())
+ result += indent + '\n';
+ else
+ result += oldIndent + '\n';
+ }
+ if (!fromLine.isEmpty())
+ {
+ result += fromLine;
+ }
+ oldIndent = indent;
+ }
+ part.append(line);
+ }
+ flushPart( result, part, oldIndent, maxLineLength);
+ return result;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::parseTextStringFromDwPart( partNode * root,
+ QCString& parsedString,
+ const QTextCodec*& codec,
+ bool& isHTML ) const
+{
+ 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,
+ false );
+ kdDebug(5006) << "\n\n======= KMMessage::parseTextStringFromDwPart() - "
+ << ( curNode ? "text part found!\n" : "sorry, no text node!\n" ) << endl;
+ if( curNode ) {
+ isHTML = DwMime::kSubtypeHtml == curNode->subType();
+ // now parse the TEXT message part we want to quote
+ ObjectTreeParser otp( 0, 0, true, false, true );
+ otp.parseObjectTree( curNode );
+ parsedString = otp.rawReplyString();
+ codec = curNode->msgPart().codec();
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+QString KMMessage::asPlainText( bool aStripSignature, bool allowDecryption ) const {
+ QCString parsedString;
+ bool isHTML = false;
+ const QTextCodec * codec = 0;
+
+ partNode * root = partNode::fromMessage( this );
+ if ( !root ) return QString::null;
+ parseTextStringFromDwPart( root, parsedString, codec, isHTML );
+ delete root;
+
+ if ( mOverrideCodec || !codec )
+ codec = this->codec();
+
+ if ( parsedString.isEmpty() )
+ return QString::null;
+
+ bool clearSigned = false;
+ QString result;
+
+ // decrypt
+ if ( allowDecryption ) {
+ QPtrList<Kpgp::Block> pgpBlocks;
+ QStrList nonPgpBlocks;
+ if ( Kpgp::Module::prepareMessageForDecryption( parsedString,
+ 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;
+ }
+
+ result = codec->toUnicode( nonPgpBlocks.first() )
+ + codec->toUnicode( block->text() )
+ + codec->toUnicode( nonPgpBlocks.last() );
+ }
+ }
+ }
+ }
+
+ if ( result.isEmpty() ) {
+ result = codec->toUnicode( parsedString );
+ if ( result.isEmpty() )
+ return result;
+ }
+
+ // html -> plaintext conversion, if necessary:
+ if ( isHTML && mDecodeHTML ) {
+ KHTMLPart htmlPart;
+ htmlPart.setOnlyLocalReferences( true );
+ htmlPart.setMetaRefreshEnabled( false );
+ htmlPart.setPluginsEnabled( false );
+ htmlPart.setJScriptEnabled( false );
+ htmlPart.setJavaEnabled( false );
+ htmlPart.begin();
+ htmlPart.write( result );
+ htmlPart.end();
+ htmlPart.selectAll();
+ result = htmlPart.selectedText();
+ }
+
+ // strip the signature (footer):
+ if ( aStripSignature )
+ return stripSignature( result, clearSigned );
+ else
+ return result;
+}
+
+QString KMMessage::asQuotedString( const QString& aHeaderStr,
+ const QString& aIndentStr,
+ const QString& selection /* = QString::null */,
+ bool aStripSignature /* = true */,
+ bool allowDecryption /* = true */) const
+{
+ QString content = selection.isEmpty() ?
+ asPlainText( aStripSignature, allowDecryption ) : selection ;
+
+ // Remove blank lines at the beginning:
+ const int firstNonWS = content.find( QRegExp( "\\S" ) );
+ const int lineStart = content.findRev( '\n', firstNonWS );
+ if ( lineStart >= 0 )
+ content.remove( 0, static_cast<unsigned int>( lineStart ) );
+
+ const QString indentStr = formatString( aIndentStr );
+
+ content.replace( '\n', '\n' + indentStr );
+ content.prepend( indentStr );
+ content += '\n';
+
+ const QString headerStr = formatString( aHeaderStr );
+ if ( sSmartQuote && sWordWrap )
+ return headerStr + smartQuote( content, sWrapCol );
+ return headerStr + content;
+}
+
+//-----------------------------------------------------------------------------
+KMMessage* KMMessage::createReply( KMail::ReplyStrategy replyStrategy,
+ QString selection /* = QString::null */,
+ bool noQuote /* = false */,
+ bool allowDecryption /* = true */,
+ bool selectionIsBody /* = false */,
+ const QString &tmpl /* = QString::null */ )
+{
+ KMMessage* msg = new KMMessage;
+ QString str, replyStr, mailingListStr, replyToStr, toStr;
+ QStringList mailingListAddresses;
+ QCString refStr, headerName;
+ bool replyAll = true;
+
+ msg->initFromMessage(this);
+
+ MailingList::name(this, headerName, mailingListStr);
+ replyToStr = replyTo();
+
+ msg->setCharset("utf-8");
+
+ // determine the mailing list posting address
+ if ( parent() && parent()->isMailingListEnabled() &&
+ !parent()->mailingListPostAddress().isEmpty() ) {
+ mailingListAddresses << parent()->mailingListPostAddress();
+ }
+ if ( headerField("List-Post").find( "mailto:", 0, false ) != -1 ) {
+ QString listPost = headerField("List-Post");
+ QRegExp rx( "<mailto:([^@>]+)@([^>]+)>", false );
+ if ( rx.search( listPost, 0 ) != -1 ) // matched
+ mailingListAddresses << rx.cap(1) + '@' + rx.cap(2);
+ }
+
+ // use the "On ... Joe User wrote:" header by default
+ replyStr = sReplyAllStr;
+
+ switch( replyStrategy ) {
+ case KMail::ReplySmart : {
+ if ( !headerField( "Mail-Followup-To" ).isEmpty() ) {
+ toStr = headerField( "Mail-Followup-To" );
+ }
+ else if ( !replyToStr.isEmpty() ) {
+ // assume a Reply-To header mangling mailing list
+ toStr = replyToStr;
+ }
+ else if ( !mailingListAddresses.isEmpty() ) {
+ toStr = mailingListAddresses[0];
+ }
+ 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:"
+ replyAll = false;
+ }
+ // strip all my addresses from the list of recipients
+ QStringList recipients = KPIM::splitEmailAddrList( toStr );
+ toStr = stripMyAddressesFromAddressList( recipients ).join(", ");
+ // ... unless the list contains only my addresses (reply to self)
+ if ( toStr.isEmpty() && !recipients.isEmpty() )
+ toStr = recipients[0];
+
+ break;
+ }
+ case KMail::ReplyList : {
+ if ( !headerField( "Mail-Followup-To" ).isEmpty() ) {
+ toStr = headerField( "Mail-Followup-To" );
+ }
+ else if ( !mailingListAddresses.isEmpty() ) {
+ toStr = mailingListAddresses[0];
+ }
+ else if ( !replyToStr.isEmpty() ) {
+ // assume a Reply-To header mangling mailing list
+ toStr = replyToStr;
+ }
+ // strip all my addresses from the list of recipients
+ QStringList recipients = KPIM::splitEmailAddrList( toStr );
+ toStr = stripMyAddressesFromAddressList( recipients ).join(", ");
+
+ break;
+ }
+ case KMail::ReplyAll : {
+ QStringList recipients;
+ QStringList ccRecipients;
+
+ // add addresses from the Reply-To header to the list of recipients
+ if( !replyToStr.isEmpty() ) {
+ recipients += KPIM::splitEmailAddrList( replyToStr );
+ // strip all possible mailing list addresses from the list of Reply-To
+ // addresses
+ for ( QStringList::const_iterator it = mailingListAddresses.begin();
+ it != mailingListAddresses.end();
+ ++it ) {
+ recipients = stripAddressFromAddressList( *it, recipients );
+ }
+ }
+
+ if ( !mailingListAddresses.isEmpty() ) {
+ // this is a mailing list message
+ if ( recipients.isEmpty() && !from().isEmpty() ) {
+ // The sender didn't set a Reply-to address, so we add the From
+ // address to the list of CC recipients.
+ ccRecipients += from();
+ kdDebug(5006) << "Added " << from() << " to the list of CC recipients"
+ << endl;
+ }
+ // if it is a mailing list, add the posting address
+ recipients.prepend( mailingListAddresses[0] );
+ }
+ else {
+ // this is a normal message
+ if ( recipients.isEmpty() && !from().isEmpty() ) {
+ // in case of replying to a normal message only then add the From
+ // address to the list of recipients if there was no Reply-to address
+ recipients += from();
+ kdDebug(5006) << "Added " << from() << " to the list of recipients"
+ << endl;
+ }
+ }
+
+ // strip all my addresses from the list of recipients
+ toStr = stripMyAddressesFromAddressList( recipients ).join(", ");
+
+ // merge To header and CC header into a list of CC recipients
+ if( !cc().isEmpty() || !to().isEmpty() ) {
+ QStringList list;
+ if (!to().isEmpty())
+ list += KPIM::splitEmailAddrList(to());
+ if (!cc().isEmpty())
+ list += KPIM::splitEmailAddrList(cc());
+ for( QStringList::Iterator it = list.begin(); it != list.end(); ++it ) {
+ if( !addressIsInAddressList( *it, recipients )
+ && !addressIsInAddressList( *it, ccRecipients ) ) {
+ ccRecipients += *it;
+ kdDebug(5006) << "Added " << *it << " to the list of CC recipients"
+ << endl;
+ }
+ }
+ }
+
+ if ( !ccRecipients.isEmpty() ) {
+ // strip all my addresses from the list of CC recipients
+ ccRecipients = stripMyAddressesFromAddressList( ccRecipients );
+
+ // in case of a reply to self toStr might be empty. if that's the case
+ // then propagate a cc recipient to To: (if there is any).
+ if ( toStr.isEmpty() && !ccRecipients.isEmpty() ) {
+ toStr = ccRecipients[0];
+ ccRecipients.pop_front();
+ }
+
+ msg->setCc( ccRecipients.join(", ") );
+ }
+
+ if ( toStr.isEmpty() && !recipients.isEmpty() ) {
+ // reply to self without other recipients
+ toStr = recipients[0];
+ }
+ break;
+ }
+ case KMail::ReplyAuthor : {
+ if ( !replyToStr.isEmpty() ) {
+ QStringList recipients = KPIM::splitEmailAddrList( replyToStr );
+ // strip the mailing list post address from the list of Reply-To
+ // addresses since we want to reply in private
+ for ( QStringList::const_iterator it = mailingListAddresses.begin();
+ it != mailingListAddresses.end();
+ ++it ) {
+ recipients = stripAddressFromAddressList( *it, recipients );
+ }
+ if ( !recipients.isEmpty() ) {
+ toStr = recipients.join(", ");
+ }
+ else {
+ // there was only the mailing list post address in the Reply-To header,
+ // so use the From address instead
+ toStr = from();
+ }
+ }
+ else if ( !from().isEmpty() ) {
+ toStr = from();
+ }
+ replyStr = sReplyStr; // reply to author, so use "On ... you wrote:"
+ replyAll = false;
+ break;
+ }
+ case KMail::ReplyNone : {
+ // the addressees will be set by the caller
+ }
+ }
+
+ msg->setTo(toStr);
+
+ refStr = getRefStr();
+ if (!refStr.isEmpty())
+ msg->setReferences(refStr);
+ //In-Reply-To = original msg-id
+ msg->setReplyToId(msgId());
+
+// if (!noQuote) {
+// if( selectionIsBody ){
+// QCString cStr = selection.latin1();
+// msg->setBody( cStr );
+// }else{
+// msg->setBody(asQuotedString(replyStr + "\n", sIndentPrefixStr, selection,
+// sSmartQuote, allowDecryption).utf8());
+// }
+// }
+
+ 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 );
+ }
+
+ // setStatus(KMMsgStatusReplied);
+ msg->link(this, KMMsgStatusReplied);
+
+ if ( parent() && parent()->putRepliesInSameFolder() )
+ msg->setFcc( parent()->idString() );
+
+ // replies to an encrypted message should be encrypted as well
+ if ( encryptionState() == KMMsgPartiallyEncrypted ||
+ encryptionState() == KMMsgFullyEncrypted ) {
+ msg->setEncryptionState( KMMsgFullyEncrypted );
+ }
+
+ return msg;
+}
+
+
+//-----------------------------------------------------------------------------
+QCString KMMessage::getRefStr() const
+{
+ QCString firstRef, lastRef, refStr, retRefStr;
+ int i, j;
+
+ refStr = headerField("References").stripWhiteSpace().latin1();
+
+ if (refStr.isEmpty())
+ return headerField("Message-Id").latin1();
+
+ i = refStr.find('<');
+ j = refStr.find('>');
+ firstRef = refStr.mid(i, j-i+1);
+ if (!firstRef.isEmpty())
+ retRefStr = firstRef + ' ';
+
+ i = refStr.findRev('<');
+ j = refStr.findRev('>');
+
+ lastRef = refStr.mid(i, j-i+1);
+ if (!lastRef.isEmpty() && lastRef != firstRef)
+ retRefStr += lastRef + ' ';
+
+ retRefStr += headerField("Message-Id").latin1();
+ return retRefStr;
+}
+
+
+KMMessage* KMMessage::createRedirect( const QString &toStr )
+{
+ // copy the message 1:1
+ KMMessage* msg = new KMMessage( new DwMessage( *this->mMsg ) );
+ KMMessagePart msgPart;
+
+ uint id = 0;
+ QString strId = msg->headerField( "X-KMail-Identity" ).stripWhiteSpace();
+ if ( !strId.isEmpty())
+ id = strId.toUInt();
+ const KPIM::Identity & ident =
+ kmkernel->identityManager()->identityForUoidOrDefault( id );
+
+ // X-KMail-Redirect-From: content
+ QString strByWayOf = QString("%1 (by way of %2 <%3>)")
+ .arg( from() )
+ .arg( ident.fullName() )
+ .arg( ident.emailAddr() );
+
+ // Resent-From: content
+ QString strFrom = QString("%1 <%2>")
+ .arg( ident.fullName() )
+ .arg( ident.emailAddr() );
+
+ // format the current date to be used in Resent-Date:
+ QString origDate = msg->headerField( "Date" );
+ msg->setDateToday();
+ QString newDate = msg->headerField( "Date" );
+ // make sure the Date: header is valid
+ if ( origDate.isEmpty() )
+ msg->removeHeaderField( "Date" );
+ else
+ msg->setHeaderField( "Date", origDate );
+
+ // prepend Resent-*: headers (c.f. RFC2822 3.6.6)
+ msg->setHeaderField( "Resent-Message-ID", generateMessageId( msg->sender() ),
+ Structured, true);
+ msg->setHeaderField( "Resent-Date", newDate, Structured, true );
+ msg->setHeaderField( "Resent-To", toStr, Address, true );
+ msg->setHeaderField( "Resent-From", strFrom, Address, true );
+
+ msg->setHeaderField( "X-KMail-Redirect-From", strByWayOf );
+ msg->setHeaderField( "X-KMail-Recipients", toStr, Address );
+
+ msg->link(this, KMMsgStatusForwarded);
+
+ return msg;
+}
+
+
+//-----------------------------------------------------------------------------
+QCString KMMessage::createForwardBody()
+{
+ QString s;
+ QCString str;
+
+ if (sHeaderStrategy == HeaderStrategy::all()) {
+ s = "\n\n---------- " + sForwardStr + " ----------\n\n";
+ s += headerAsString();
+ str = asQuotedString(s, "", QString::null, false, false).utf8();
+ str += "\n-------------------------------------------------------\n";
+ } else {
+ s = "\n\n---------- " + sForwardStr + " ----------\n\n";
+ s += "Subject: " + subject() + "\n";
+ s += "Date: "
+ + KMime::DateFormatter::formatDate( KMime::DateFormatter::Localized,
+ date(), sReplyLanguage, false )
+ + "\n";
+ s += "From: " + from() + "\n";
+ s += "To: " + to() + "\n";
+ if (!cc().isEmpty()) s += "Cc: " + cc() + "\n";
+ s += "\n";
+ str = asQuotedString(s, "", QString::null, false, false).utf8();
+ str += "\n-------------------------------------------------------\n";
+ }
+
+ return str;
+}
+
+void KMMessage::sanitizeHeaders( const QStringList& whiteList )
+{
+ // Strip out all headers apart from the content description and other
+ // whitelisted ones, because we don't want to inherit them.
+ DwHeaders& header = mMsg->Headers();
+ DwField* field = header.FirstField();
+ DwField* nextField;
+ while (field)
+ {
+ nextField = field->Next();
+ if ( field->FieldNameStr().find( "ontent" ) == DwString::npos
+ && !whiteList.contains( QString::fromLatin1( field->FieldNameStr().c_str() ) ) )
+ header.RemoveField(field);
+ field = nextField;
+ }
+ mMsg->Assemble();
+}
+
+//-----------------------------------------------------------------------------
+KMMessage* KMMessage::createForward( const QString &tmpl /* = QString::null */ )
+{
+ KMMessage* msg = new KMMessage();
+ QString 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
+ // preserved
+ if ( type() == DwMime::kTypeMultipart ||
+ ( type() == DwMime::kTypeText && subtype() == DwMime::kSubtypePlain ) ) {
+ // ## slow, we could probably use: delete msg->mMsg; msg->mMsg = new DwMessage( this->mMsg );
+ 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();
+
+ msg->sanitizeHeaders();
+
+ // strip blacklisted parts
+ QStringList blacklist = GlobalSettings::self()->mimetypesToStripWhenInlineForwarding();
+ for ( QStringList::Iterator it = blacklist.begin(); it != blacklist.end(); ++it ) {
+ QString entry = (*it);
+ int sep = entry.find( '/' );
+ QCString type = entry.left( sep ).latin1();
+ QCString subtype = entry.mid( sep+1 ).latin1();
+ kdDebug( 5006 ) << "Looking for blacklisted type: " << type << "/" << subtype << endl;
+ while ( DwBodyPart * part = msg->findDwBodyPart( type, subtype ) ) {
+ msg->mMsg->Body().RemoveBodyPart( part );
+ }
+ }
+ msg->mMsg->Assemble();
+
+ msg->initFromMessage( this );
+ //restore type
+ msg->setType( type );
+ msg->setSubtype( subtype );
+ } 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 );
+ msg->setType( DwMime::kTypeText );
+ msg->setSubtype( DwMime::kSubtypeHtml );
+ msg->mNeedsAssembly = true;
+ msg->cleanupHeader();
+ } 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 );
+ msg->removeHeaderField("Content-Type");
+ msg->removeHeaderField("Content-Transfer-Encoding");
+ // Modify the ContentType directly (replaces setAutomaticFields(true))
+ DwHeaders & header = msg->mMsg->Headers();
+ header.MimeVersion().FromString("1.0");
+ DwMediaType & contentType = msg->dwContentType();
+ contentType.SetType( DwMime::kTypeMultipart );
+ contentType.SetSubtype( DwMime::kSubtypeMixed );
+ contentType.CreateBoundary(0);
+ contentType.Assemble();
+
+ // empty text part
+ KMMessagePart msgPart;
+ bodyPart( 0, &msgPart );
+ msg->addBodyPart(&msgPart);
+ // the old contents of the mail
+ KMMessagePart secondPart;
+ secondPart.setType( type() );
+ secondPart.setSubtype( subtype() );
+ secondPart.setBody( mMsg->Body().AsString() );
+ // use the headers of the original mail
+ applyHeadersToMessagePart( mMsg->Headers(), &secondPart );
+ msg->addBodyPart(&secondPart);
+ msg->mNeedsAssembly = true;
+ msg->cleanupHeader();
+ }
+ // QString st = QString::fromUtf8(createForwardBody());
+
+ msg->setSubject( forwardSubject() );
+
+ TemplateParser parser( msg, TemplateParser::Forward,
+ asPlainText( false, false ),
+ false, false, false, false);
+ if ( !tmpl.isEmpty() ) {
+ parser.process( tmpl, this );
+ } else {
+ parser.process( this );
+ }
+
+ // QCString encoding = autoDetectCharset(charset(), sPrefCharsets, msg->body());
+ // if (encoding.isEmpty()) encoding = "utf-8";
+ // msg->setCharset(encoding);
+
+ // force utf-8
+ // msg->setCharset( "utf-8" );
+
+ msg->link(this, KMMsgStatusForwarded);
+ return msg;
+}
+
+static const struct {
+ const char * dontAskAgainID;
+ bool canDeny;
+ const char * text;
+} mdnMessageBoxes[] = {
+ { "mdnNormalAsk", true,
+ I18N_NOOP("This message contains a request to return a notification "
+ "about your reception of the message.\n"
+ "You can either ignore the request or let KMail send a "
+ "\"denied\" or normal response.") },
+ { "mdnUnknownOption", false,
+ I18N_NOOP("This message contains a request to send a notification "
+ "about your reception of the message.\n"
+ "It contains a processing instruction that is marked as "
+ "\"required\", but which is unknown to KMail.\n"
+ "You can either ignore the request or let KMail send a "
+ "\"failed\" response.") },
+ { "mdnMultipleAddressesInReceiptTo", true,
+ I18N_NOOP("This message contains a request to send a notification "
+ "about your reception of the message,\n"
+ "but it is requested to send the notification to more "
+ "than one address.\n"
+ "You can either ignore the request or let KMail send a "
+ "\"denied\" or normal response.") },
+ { "mdnReturnPathEmpty", true,
+ I18N_NOOP("This message contains a request to send a notification "
+ "about your reception of the message,\n"
+ "but there is no return-path set.\n"
+ "You can either ignore the request or let KMail send a "
+ "\"denied\" or normal response.") },
+ { "mdnReturnPathNotInReceiptTo", true,
+ I18N_NOOP("This message contains a request to send a notification "
+ "about your reception of the message,\n"
+ "but the return-path address differs from the address "
+ "the notification was requested to be sent to.\n"
+ "You can either ignore the request or let KMail send a "
+ "\"denied\" or normal response.") },
+};
+
+static const int numMdnMessageBoxes
+ = sizeof mdnMessageBoxes / sizeof *mdnMessageBoxes;
+
+
+static int requestAdviceOnMDN( const char * what ) {
+ for ( int i = 0 ; i < numMdnMessageBoxes ; ++i )
+ if ( !qstrcmp( what, mdnMessageBoxes[i].dontAskAgainID ) )
+ if ( mdnMessageBoxes[i].canDeny ) {
+ const KCursorSaver saver( QCursor::ArrowCursor );
+ int answer = QMessageBox::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( QCursor::ArrowCursor );
+ int answer = QMessageBox::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;
+ return 0;
+}
+
+KMMessage* KMMessage::createMDN( MDN::ActionMode a,
+ MDN::DispositionType d,
+ bool allowGUI,
+ QValueList<MDN::DispositionModifier> m )
+{
+ // RFC 2298: At most one MDN may be issued on behalf of each
+ // particular recipient by their user agent. That is, once an MDN
+ // has been issued on behalf of a recipient, no further MDNs may be
+ // issued on behalf of that recipient, even if another disposition
+ // is performed on the message.
+//#define MDN_DEBUG 1
+#ifndef MDN_DEBUG
+ if ( mdnSentState() != KMMsgMDNStateUnknown &&
+ mdnSentState() != KMMsgMDNNone )
+ return 0;
+#else
+ char st[2]; st[0] = (char)mdnSentState(); st[1] = 0;
+ kdDebug(5006) << "mdnSentState() == '" << st << "'" << endl;
+#endif
+
+ // RFC 2298: An MDN MUST NOT be generated in response to an MDN.
+ if ( findDwBodyPart( DwMime::kTypeMessage,
+ DwMime::kSubtypeDispositionNotification ) ) {
+ setMDNSentState( KMMsgMDNIgnore );
+ return 0;
+ }
+
+ // extract where to send to:
+ QString receiptTo = headerField("Disposition-Notification-To");
+ if ( receiptTo.stripWhiteSpace().isEmpty() ) return 0;
+ receiptTo.remove( '\n' );
+
+
+ MDN::SendingMode s = MDN::SentAutomatically; // set to manual if asked user
+ QString special; // fill in case of error, warning or failure
+ KConfigGroup mdnConfig( KMKernel::config(), "MDN" );
+
+ // default:
+ int mode = mdnConfig.readNumEntry( "default-policy", 0 );
+ if ( !mode || mode < 0 || mode > 3 ) {
+ // early out for ignore:
+ setMDNSentState( KMMsgMDNIgnore );
+ return 0;
+ }
+
+ // RFC 2298: An importance of "required" indicates that
+ // interpretation of the parameter is necessary for proper
+ // generation of an MDN in response to this request. If a UA does
+ // not understand the meaning of the parameter, it MUST NOT generate
+ // an MDN with any disposition type other than "failed" in response
+ // to the request.
+ QString notificationOptions = headerField("Disposition-Notification-Options");
+ if ( notificationOptions.contains( "required", false ) ) {
+ // ### hacky; should parse...
+ // There is a required option that we don't understand. We need to
+ // ask the user what we should do:
+ if ( !allowGUI ) return 0; // don't setMDNSentState here!
+ mode = requestAdviceOnMDN( "mdnUnknownOption" );
+ s = MDN::SentManually;
+
+ special = i18n("Header \"Disposition-Notification-Options\" contained "
+ "required, but unknown parameter");
+ d = MDN::Failed;
+ m.clear(); // clear modifiers
+ }
+
+ // RFC 2298: [ Confirmation from the user SHOULD be obtained (or no
+ // MDN sent) ] if there is more than one distinct address in the
+ // Disposition-Notification-To header.
+ kdDebug(5006) << "KPIM::splitEmailAddrList(receiptTo): "
+ << KPIM::splitEmailAddrList(receiptTo).join("\n") << endl;
+ if ( KPIM::splitEmailAddrList(receiptTo).count() > 1 ) {
+ if ( !allowGUI ) return 0; // don't setMDNSentState here!
+ mode = requestAdviceOnMDN( "mdnMultipleAddressesInReceiptTo" );
+ s = MDN::SentManually;
+ }
+
+ // RFC 2298: MDNs SHOULD NOT be sent automatically if the address in
+ // the Disposition-Notification-To header differs from the address
+ // in the Return-Path header. [...] Confirmation from the user
+ // SHOULD be obtained (or no MDN sent) if there is no Return-Path
+ // header in the message [...]
+ AddrSpecList returnPathList = extractAddrSpecs("Return-Path");
+ QString returnPath = returnPathList.isEmpty() ? QString::null
+ : returnPathList.front().localPart + '@' + returnPathList.front().domain ;
+ kdDebug(5006) << "clean return path: " << returnPath << endl;
+ if ( returnPath.isEmpty() || !receiptTo.contains( returnPath, false ) ) {
+ if ( !allowGUI ) return 0; // don't setMDNSentState here!
+ mode = requestAdviceOnMDN( returnPath.isEmpty() ?
+ "mdnReturnPathEmpty" :
+ "mdnReturnPathNotInReceiptTo" );
+ s = MDN::SentManually;
+ }
+
+ if ( a != KMime::MDN::AutomaticAction ) {
+ //TODO: only ingore user settings for AutomaticAction if requested
+ if ( mode == 1 ) { // ask
+ if ( !allowGUI ) return 0; // don't setMDNSentState here!
+ mode = requestAdviceOnMDN( "mdnNormalAsk" );
+ s = MDN::SentManually; // asked user
+ }
+
+ switch ( mode ) {
+ case 0: // ignore:
+ setMDNSentState( KMMsgMDNIgnore );
+ return 0;
+ default:
+ case 1:
+ kdFatal(5006) << "KMMessage::createMDN(): The \"ask\" mode should "
+ << "never appear here!" << endl;
+ break;
+ case 2: // deny
+ d = MDN::Denied;
+ m.clear();
+ break;
+ case 3:
+ break;
+ }
+ }
+
+
+ // extract where to send from:
+ QString finalRecipient = kmkernel->identityManager()
+ ->identityForUoidOrDefault( identityUoid() ).fullEmailAddr();
+
+ //
+ // Generate message:
+ //
+
+ KMMessage * receipt = new KMMessage();
+ receipt->initFromMessage( this );
+ receipt->removeHeaderField("Content-Type");
+ receipt->removeHeaderField("Content-Transfer-Encoding");
+ // Modify the ContentType directly (replaces setAutomaticFields(true))
+ DwHeaders & header = receipt->mMsg->Headers();
+ header.MimeVersion().FromString("1.0");
+ DwMediaType & contentType = receipt->dwContentType();
+ contentType.SetType( DwMime::kTypeMultipart );
+ contentType.SetSubtype( DwMime::kSubtypeReport );
+ contentType.CreateBoundary(0);
+ receipt->mNeedsAssembly = true;
+ receipt->setContentTypeParam( "report-type", "disposition-notification" );
+
+ QString description = replaceHeadersInString( MDN::descriptionFor( d, m ) );
+
+ // text/plain part:
+ KMMessagePart firstMsgPart;
+ firstMsgPart.setTypeStr( "text" );
+ firstMsgPart.setSubtypeStr( "plain" );
+ firstMsgPart.setBodyFromUnicode( description );
+ receipt->addBodyPart( &firstMsgPart );
+
+ // message/disposition-notification part:
+ KMMessagePart secondMsgPart;
+ secondMsgPart.setType( DwMime::kTypeMessage );
+ secondMsgPart.setSubtype( DwMime::kSubtypeDispositionNotification );
+ //secondMsgPart.setCharset( "us-ascii" );
+ //secondMsgPart.setCteStr( "7bit" );
+ secondMsgPart.setBodyEncoded( MDN::dispositionNotificationBodyContent(
+ finalRecipient,
+ rawHeaderField("Original-Recipient"),
+ id(), /* Message-ID */
+ d, a, s, m, special ) );
+ receipt->addBodyPart( &secondMsgPart );
+
+ // message/rfc822 or text/rfc822-headers body part:
+ int num = mdnConfig.readNumEntry( "quote-message", 0 );
+ if ( num < 0 || num > 2 ) num = 0;
+ MDN::ReturnContent returnContent = static_cast<MDN::ReturnContent>( num );
+
+ KMMessagePart thirdMsgPart;
+ switch ( returnContent ) {
+ case MDN::All:
+ thirdMsgPart.setTypeStr( "message" );
+ thirdMsgPart.setSubtypeStr( "rfc822" );
+ thirdMsgPart.setBody( asSendableString() );
+ receipt->addBodyPart( &thirdMsgPart );
+ break;
+ case MDN::HeadersOnly:
+ thirdMsgPart.setTypeStr( "text" );
+ thirdMsgPart.setSubtypeStr( "rfc822-headers" );
+ thirdMsgPart.setBody( headerAsSendableString() );
+ receipt->addBodyPart( &thirdMsgPart );
+ break;
+ case MDN::Nothing:
+ default:
+ break;
+ };
+
+ receipt->setTo( receiptTo );
+ receipt->setSubject( "Message Disposition Notification" );
+ receipt->setReplyToId( msgId() );
+ receipt->setReferences( getRefStr() );
+
+ receipt->cleanupHeader();
+
+ kdDebug(5006) << "final message:\n" + receipt->asString() << endl;
+
+ //
+ // Set "MDN sent" status:
+ //
+ KMMsgMDNSentState state = KMMsgMDNStateUnknown;
+ switch ( d ) {
+ case MDN::Displayed: state = KMMsgMDNDisplayed; break;
+ case MDN::Deleted: state = KMMsgMDNDeleted; break;
+ case MDN::Dispatched: state = KMMsgMDNDispatched; break;
+ case MDN::Processed: state = KMMsgMDNProcessed; break;
+ case MDN::Denied: state = KMMsgMDNDenied; break;
+ case MDN::Failed: state = KMMsgMDNFailed; break;
+ };
+ setMDNSentState( state );
+
+ return receipt;
+}
+
+QString KMMessage::replaceHeadersInString( const QString & s ) const {
+ QString result = s;
+ QRegExp rx( "\\$\\{([a-z0-9-]+)\\}", false );
+ Q_ASSERT( rx.isValid() );
+
+ QRegExp rxDate( "\\$\\{date\\}" );
+ Q_ASSERT( rxDate.isValid() );
+
+ QString sDate = KMime::DateFormatter::formatDate(
+ KMime::DateFormatter::Localized, date() );
+
+ int idx = 0;
+ if( ( idx = rxDate.search( result, idx ) ) != -1 ) {
+ result.replace( idx, rxDate.matchedLength(), sDate );
+ }
+
+ idx = 0;
+ while ( ( idx = rx.search( result, idx ) ) != -1 ) {
+ QString replacement = headerField( rx.cap(1).latin1() );
+ result.replace( idx, rx.matchedLength(), replacement );
+ idx += replacement.length();
+ }
+ return result;
+}
+
+KMMessage* KMMessage::createDeliveryReceipt() const
+{
+ QString str, receiptTo;
+ KMMessage *receipt;
+
+ receiptTo = headerField("Disposition-Notification-To");
+ if ( receiptTo.stripWhiteSpace().isEmpty() ) return 0;
+ receiptTo.remove( '\n' );
+
+ receipt = new KMMessage;
+ receipt->initFromMessage(this);
+ receipt->setTo(receiptTo);
+ receipt->setSubject(i18n("Receipt: ") + subject());
+
+ str = "Your message was successfully delivered.";
+ str += "\n\n---------- Message header follows ----------\n";
+ str += headerAsString();
+ str += "--------------------------------------------\n";
+ // Conversion to latin1 is correct here as Mail headers should contain
+ // ascii only
+ receipt->setBody(str.latin1());
+ receipt->setAutomaticFields();
+
+ return receipt;
+}
+
+
+void KMMessage::applyIdentity( uint id )
+{
+ const KPIM::Identity & ident =
+ kmkernel->identityManager()->identityForUoidOrDefault( id );
+
+ if(ident.fullEmailAddr().isEmpty())
+ setFrom("");
+ else
+ setFrom(ident.fullEmailAddr());
+
+ if(ident.replyToAddr().isEmpty())
+ setReplyTo("");
+ else
+ setReplyTo(ident.replyToAddr());
+
+ if(ident.bcc().isEmpty())
+ setBcc("");
+ else
+ setBcc(ident.bcc());
+
+ if (ident.organization().isEmpty())
+ removeHeaderField("Organization");
+ else
+ setHeaderField("Organization", ident.organization());
+
+ if (ident.isDefault())
+ removeHeaderField("X-KMail-Identity");
+ else
+ setHeaderField("X-KMail-Identity", QString::number( ident.uoid() ));
+
+ if ( ident.transport().isEmpty() )
+ removeHeaderField( "X-KMail-Transport" );
+ else
+ setHeaderField( "X-KMail-Transport", ident.transport() );
+
+ if ( ident.fcc().isEmpty() )
+ setFcc( QString::null );
+ else
+ setFcc( ident.fcc() );
+
+ if ( ident.drafts().isEmpty() )
+ setDrafts( QString::null );
+ else
+ setDrafts( ident.drafts() );
+
+ if ( ident.templates().isEmpty() )
+ setTemplates( QString::null );
+ else
+ setTemplates( ident.templates() );
+
+}
+
+//-----------------------------------------------------------------------------
+void KMMessage::initHeader( uint id )
+{
+ applyIdentity( id );
+ setTo("");
+ setSubject("");
+ setDateToday();
+
+ setHeaderField("User-Agent", "KMail/" KMAIL_VERSION );
+ // This will allow to change Content-Type:
+ setHeaderField("Content-Type","text/plain");
+}
+
+uint KMMessage::identityUoid() const {
+ QString idString = headerField("X-KMail-Identity").stripWhiteSpace();
+ bool ok = false;
+ int id = idString.toUInt( &ok );
+
+ if ( !ok || id == 0 )
+ id = kmkernel->identityManager()->identityForAddress( to() + ", " + cc() ).uoid();
+ if ( id == 0 && parent() )
+ id = parent()->identity();
+
+ return id;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::initFromMessage(const KMMessage *msg, bool idHeaders)
+{
+ uint id = msg->identityUoid();
+
+ if ( idHeaders ) initHeader(id);
+ else setHeaderField("X-KMail-Identity", QString::number(id));
+ if (!msg->headerField("X-KMail-Transport").isEmpty())
+ setHeaderField("X-KMail-Transport", msg->headerField("X-KMail-Transport"));
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::cleanupHeader()
+{
+ DwHeaders& header = mMsg->Headers();
+ DwField* field = header.FirstField();
+ DwField* nextField;
+
+ if (mNeedsAssembly) mMsg->Assemble();
+ mNeedsAssembly = false;
+
+ while (field)
+ {
+ nextField = field->Next();
+ if (field->FieldBody()->AsString().empty())
+ {
+ header.RemoveField(field);
+ mNeedsAssembly = true;
+ }
+ field = nextField;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setAutomaticFields(bool aIsMulti)
+{
+ DwHeaders& header = mMsg->Headers();
+ header.MimeVersion().FromString("1.0");
+
+ if (aIsMulti || numBodyParts() > 1)
+ {
+ // Set the type to 'Multipart' and the subtype to 'Mixed'
+ DwMediaType& contentType = dwContentType();
+ contentType.SetType( DwMime::kTypeMultipart);
+ contentType.SetSubtype(DwMime::kSubtypeMixed );
+
+ // Create a random printable string and set it as the boundary parameter
+ contentType.CreateBoundary(0);
+ }
+ mNeedsAssembly = true;
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMessage::dateStr() const
+{
+ KConfigGroup general( KMKernel::config(), "General" );
+ DwHeaders& header = mMsg->Headers();
+ time_t unixTime;
+
+ if (!header.HasDate()) return "";
+ unixTime = header.Date().AsUnixTime();
+
+ //kdDebug(5006)<<"#### Date = "<<header.Date().AsString().c_str()<<endl;
+
+ return KMime::DateFormatter::formatDate(
+ static_cast<KMime::DateFormatter::FormatType>(general.readNumEntry( "dateFormat", KMime::DateFormatter::Fancy )),
+ unixTime, general.readEntry( "customDateFormat" ));
+}
+
+
+//-----------------------------------------------------------------------------
+QCString KMMessage::dateShortStr() const
+{
+ DwHeaders& header = mMsg->Headers();
+ time_t unixTime;
+
+ if (!header.HasDate()) return "";
+ unixTime = header.Date().AsUnixTime();
+
+ QCString result = ctime(&unixTime);
+ int len = result.length();
+ if (result[len-1]=='\n')
+ result.truncate(len-1);
+
+ return result;
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMessage::dateIsoStr() const
+{
+ DwHeaders& header = mMsg->Headers();
+ time_t unixTime;
+
+ if (!header.HasDate()) return "";
+ unixTime = header.Date().AsUnixTime();
+
+ char cstr[64];
+ strftime(cstr, 63, "%Y-%m-%d %H:%M:%S", localtime(&unixTime));
+ return QString(cstr);
+}
+
+
+//-----------------------------------------------------------------------------
+time_t KMMessage::date() const
+{
+ time_t res = ( time_t )-1;
+ DwHeaders& header = mMsg->Headers();
+ if (header.HasDate())
+ res = header.Date().AsUnixTime();
+ return res;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setDateToday()
+{
+ struct timeval tval;
+ gettimeofday(&tval, 0);
+ setDate((time_t)tval.tv_sec);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setDate(time_t aDate)
+{
+ mDate = aDate;
+ mMsg->Headers().Date().FromCalendarTime(aDate);
+ mMsg->Headers().Date().Assemble();
+ mNeedsAssembly = true;
+ mDirty = true;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setDate(const QCString& aStr)
+{
+ DwHeaders& header = mMsg->Headers();
+
+ header.Date().FromString(aStr);
+ header.Date().Parse();
+ mNeedsAssembly = true;
+ mDirty = true;
+
+ if (header.HasDate())
+ mDate = header.Date().AsUnixTime();
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMessage::to() const
+{
+ // handle To same as Cc below, bug 80747
+ QValueList<QCString> rawHeaders = rawHeaderFields( "To" );
+ QStringList headers;
+ for ( QValueList<QCString>::Iterator it = rawHeaders.begin(); it != rawHeaders.end(); ++it ) {
+ headers << *it;
+ }
+ return KPIM::normalizeAddressesAndDecodeIDNs( headers.join( ", " ) );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setTo(const QString& aStr)
+{
+ setHeaderField( "To", aStr, Address );
+}
+
+//-----------------------------------------------------------------------------
+QString KMMessage::toStrip() const
+{
+ return stripEmailAddr( to() );
+}
+
+//-----------------------------------------------------------------------------
+QString KMMessage::replyTo() const
+{
+ return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField("Reply-To") );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setReplyTo(const QString& aStr)
+{
+ setHeaderField( "Reply-To", aStr, Address );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setReplyTo(KMMessage* aMsg)
+{
+ setHeaderField( "Reply-To", aMsg->from(), Address );
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMessage::cc() const
+{
+ // get the combined contents of all Cc headers (as workaround for invalid
+ // messages with multiple Cc headers)
+ QValueList<QCString> rawHeaders = rawHeaderFields( "Cc" );
+ QStringList headers;
+ for ( QValueList<QCString>::Iterator it = rawHeaders.begin(); it != rawHeaders.end(); ++it ) {
+ headers << *it;
+ }
+ return KPIM::normalizeAddressesAndDecodeIDNs( headers.join( ", " ) );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setCc(const QString& aStr)
+{
+ setHeaderField( "Cc", aStr, Address );
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMessage::ccStrip() const
+{
+ return stripEmailAddr( cc() );
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMessage::bcc() const
+{
+ return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField("Bcc") );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setBcc(const QString& aStr)
+{
+ setHeaderField( "Bcc", aStr, Address );
+}
+
+//-----------------------------------------------------------------------------
+QString KMMessage::fcc() const
+{
+ return headerField( "X-KMail-Fcc" );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setFcc( const QString &aStr )
+{
+ setHeaderField( "X-KMail-Fcc", aStr );
+}
+
+//-----------------------------------------------------------------------------
+void KMMessage::setDrafts( const QString &aStr )
+{
+ mDrafts = aStr;
+}
+
+//-----------------------------------------------------------------------------
+void KMMessage::setTemplates( const QString &aStr )
+{
+ mTemplates = aStr;
+}
+
+//-----------------------------------------------------------------------------
+QString KMMessage::who() const
+{
+ if (mParent)
+ return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField(mParent->whoField().utf8()) );
+ return from();
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMessage::from() const
+{
+ return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField("From") );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setFrom(const QString& bStr)
+{
+ QString aStr = bStr;
+ if (aStr.isNull())
+ aStr = "";
+ setHeaderField( "From", aStr, Address );
+ mDirty = true;
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMessage::fromStrip() const
+{
+ return stripEmailAddr( from() );
+}
+
+//-----------------------------------------------------------------------------
+QString KMMessage::sender() const {
+ AddrSpecList asl = extractAddrSpecs( "Sender" );
+ if ( asl.empty() )
+ asl = extractAddrSpecs( "From" );
+ if ( asl.empty() )
+ return QString::null;
+ return asl.front().asString();
+}
+
+//-----------------------------------------------------------------------------
+QString KMMessage::subject() const
+{
+ return headerField("Subject");
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setSubject(const QString& aStr)
+{
+ setHeaderField("Subject",aStr);
+ mDirty = true;
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMessage::xmark() const
+{
+ return headerField("X-KMail-Mark");
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setXMark(const QString& aStr)
+{
+ setHeaderField("X-KMail-Mark", aStr);
+ mDirty = true;
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMessage::replyToId() const
+{
+ int leftAngle, rightAngle;
+ QString replyTo, references;
+
+ replyTo = headerField("In-Reply-To");
+ // search the end of the (first) message id in the In-Reply-To header
+ rightAngle = replyTo.find( '>' );
+ if (rightAngle != -1)
+ replyTo.truncate( rightAngle + 1 );
+ // now search the start of the message id
+ leftAngle = replyTo.findRev( '<' );
+ if (leftAngle != -1)
+ replyTo = replyTo.mid( leftAngle );
+
+ // if we have found a good message id we can return immediately
+ // We ignore mangled In-Reply-To headers which are created by a
+ // misconfigured Mutt. They look like this <"from foo"@bar.baz>, i.e.
+ // they contain double quotes and spaces. We only check for '"'.
+ if (!replyTo.isEmpty() && (replyTo[0] == '<') &&
+ ( -1 == replyTo.find( '"' ) ) )
+ return replyTo;
+
+ references = headerField("References");
+ leftAngle = references.findRev( '<' );
+ if (leftAngle != -1)
+ references = references.mid( leftAngle );
+ rightAngle = references.find( '>' );
+ if (rightAngle != -1)
+ references.truncate( rightAngle + 1 );
+
+ // if we found a good message id in the References header return it
+ if (!references.isEmpty() && references[0] == '<')
+ return references;
+ // else return the broken message id we found in the In-Reply-To header
+ else
+ return replyTo;
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMessage::replyToIdMD5() const {
+ return base64EncodedMD5( replyToId() );
+}
+
+//-----------------------------------------------------------------------------
+QString KMMessage::references() const
+{
+ int leftAngle, rightAngle;
+ QString references = headerField( "References" );
+
+ // keep the last two entries for threading
+ leftAngle = references.findRev( '<' );
+ leftAngle = references.findRev( '<', leftAngle - 1 );
+ if( leftAngle != -1 )
+ references = references.mid( leftAngle );
+ rightAngle = references.findRev( '>' );
+ if( rightAngle != -1 )
+ references.truncate( rightAngle + 1 );
+
+ if( !references.isEmpty() && references[0] == '<' )
+ return references;
+ else
+ return QString::null;
+}
+
+//-----------------------------------------------------------------------------
+QString KMMessage::replyToAuxIdMD5() const
+{
+ QString result = references();
+ // references contains two items, use the first one
+ // (the second to last reference)
+ const int rightAngle = result.find( '>' );
+ if( rightAngle != -1 )
+ result.truncate( rightAngle + 1 );
+
+ return base64EncodedMD5( result );
+}
+
+//-----------------------------------------------------------------------------
+QString KMMessage::strippedSubjectMD5() const {
+ return base64EncodedMD5( stripOffPrefixes( subject() ), true /*utf8*/ );
+}
+
+//-----------------------------------------------------------------------------
+QString KMMessage::subjectMD5() const {
+ return base64EncodedMD5( subject(), true /*utf8*/ );
+}
+
+//-----------------------------------------------------------------------------
+bool KMMessage::subjectIsPrefixed() const {
+ return subjectMD5() != strippedSubjectMD5();
+}
+
+//-----------------------------------------------------------------------------
+void KMMessage::setReplyToId(const QString& aStr)
+{
+ setHeaderField("In-Reply-To", aStr);
+ mDirty = true;
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMessage::msgId() const
+{
+ QString msgId = headerField("Message-Id");
+
+ // search the end of the message id
+ const int rightAngle = msgId.find( '>' );
+ if (rightAngle != -1)
+ msgId.truncate( rightAngle + 1 );
+ // now search the start of the message id
+ const int leftAngle = msgId.findRev( '<' );
+ if (leftAngle != -1)
+ msgId = msgId.mid( leftAngle );
+ return msgId;
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMessage::msgIdMD5() const {
+ return base64EncodedMD5( msgId() );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setMsgId(const QString& aStr)
+{
+ setHeaderField("Message-Id", aStr);
+ mDirty = true;
+}
+
+//-----------------------------------------------------------------------------
+size_t KMMessage::msgSizeServer() const {
+ return headerField( "X-Length" ).toULong();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setMsgSizeServer(size_t size)
+{
+ setHeaderField("X-Length", QCString().setNum(size));
+ mDirty = true;
+}
+
+//-----------------------------------------------------------------------------
+ulong KMMessage::UID() const {
+ return headerField( "X-UID" ).toULong();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setUID(ulong uid)
+{
+ setHeaderField("X-UID", QCString().setNum(uid));
+ mDirty = true;
+}
+
+//-----------------------------------------------------------------------------
+AddressList KMMessage::splitAddrField( const QCString & str )
+{
+ AddressList result;
+ const char * scursor = str.begin();
+ if ( !scursor )
+ return AddressList();
+ const char * const send = str.begin() + str.length();
+ if ( !parseAddressList( scursor, send, result ) )
+ kdDebug(5006) << "Error in address splitting: parseAddressList returned false!"
+ << endl;
+ return result;
+}
+
+AddressList KMMessage::headerAddrField( const QCString & aName ) const {
+ return KMMessage::splitAddrField( rawHeaderField( aName ) );
+}
+
+AddrSpecList KMMessage::extractAddrSpecs( const QCString & header ) const {
+ AddressList al = headerAddrField( header );
+ AddrSpecList result;
+ for ( AddressList::const_iterator ait = al.begin() ; ait != al.end() ; ++ait )
+ for ( MailboxList::const_iterator mit = (*ait).mailboxList.begin() ; mit != (*ait).mailboxList.end() ; ++mit )
+ result.push_back( (*mit).addrSpec );
+ return result;
+}
+
+QCString KMMessage::rawHeaderField( const QCString & name ) const {
+ if ( name.isEmpty() ) return QCString();
+
+ DwHeaders & header = mMsg->Headers();
+ DwField * field = header.FindField( name );
+
+ if ( !field ) return QCString();
+
+ return header.FieldBody( name.data() ).AsString().c_str();
+}
+
+QValueList<QCString> KMMessage::rawHeaderFields( const QCString& field ) const
+{
+ if ( field.isEmpty() || !mMsg->Headers().FindField( field ) )
+ return QValueList<QCString>();
+
+ std::vector<DwFieldBody*> v = mMsg->Headers().AllFieldBodies( field.data() );
+ QValueList<QCString> headerFields;
+ for ( uint i = 0; i < v.size(); ++i ) {
+ headerFields.append( v[i]->AsString().c_str() );
+ }
+
+ return headerFields;
+}
+
+QString KMMessage::headerField(const QCString& aName) const
+{
+ if ( aName.isEmpty() )
+ return QString::null;
+
+ if ( !mMsg->Headers().FindField( aName ) )
+ return QString::null;
+
+ return decodeRFC2047String( mMsg->Headers().FieldBody( aName.data() ).AsString().c_str(),
+ charset() );
+
+}
+
+QStringList KMMessage::headerFields( const QCString& field ) const
+{
+ if ( field.isEmpty() || !mMsg->Headers().FindField( field ) )
+ return QStringList();
+
+ std::vector<DwFieldBody*> v = mMsg->Headers().AllFieldBodies( field.data() );
+ QStringList headerFields;
+ for ( uint i = 0; i < v.size(); ++i ) {
+ headerFields.append( decodeRFC2047String( v[i]->AsString().c_str(), charset() ) );
+ }
+
+ return headerFields;
+}
+
+//-----------------------------------------------------------------------------
+void KMMessage::removeHeaderField(const QCString& aName)
+{
+ DwHeaders & header = mMsg->Headers();
+ DwField * field = header.FindField(aName);
+ if (!field) return;
+
+ header.RemoveField(field);
+ mNeedsAssembly = true;
+}
+
+//-----------------------------------------------------------------------------
+void KMMessage::removeHeaderFields(const QCString& aName)
+{
+ DwHeaders & header = mMsg->Headers();
+ while ( DwField * field = header.FindField(aName) ) {
+ header.RemoveField(field);
+ mNeedsAssembly = true;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setHeaderField( const QCString& aName, const QString& bValue,
+ HeaderFieldType type, bool prepend )
+{
+#if 0
+ if ( type != Unstructured )
+ kdDebug(5006) << "KMMessage::setHeaderField( \"" << aName << "\", \""
+ << bValue << "\", " << type << " )" << endl;
+#endif
+ if (aName.isEmpty()) return;
+
+ DwHeaders& header = mMsg->Headers();
+
+ DwString str;
+ DwField* field;
+ QCString aValue;
+ if (!bValue.isEmpty())
+ {
+ QString value = bValue;
+ if ( type == Address )
+ value = KPIM::normalizeAddressesAndEncodeIDNs( value );
+#if 0
+ if ( type != Unstructured )
+ kdDebug(5006) << "value: \"" << value << "\"" << endl;
+#endif
+ QCString encoding = autoDetectCharset( charset(), sPrefCharsets, value );
+ if (encoding.isEmpty())
+ encoding = "utf-8";
+ aValue = encodeRFC2047String( value, encoding );
+#if 0
+ if ( type != Unstructured )
+ kdDebug(5006) << "aValue: \"" << aValue << "\"" << endl;
+#endif
+ }
+ str = aName;
+ if (str[str.length()-1] != ':') str += ": ";
+ else str += ' ';
+ if ( !aValue.isEmpty() )
+ str += aValue;
+ if (str[str.length()-1] != '\n') str += '\n';
+
+ field = new DwField(str, mMsg);
+ field->Parse();
+
+ if ( prepend )
+ header.AddFieldAt( 1, field );
+ else
+ header.AddOrReplaceField( field );
+ mNeedsAssembly = true;
+}
+
+
+//-----------------------------------------------------------------------------
+QCString KMMessage::typeStr() const
+{
+ DwHeaders& header = mMsg->Headers();
+ if (header.HasContentType()) return header.ContentType().TypeStr().c_str();
+ else return "";
+}
+
+
+//-----------------------------------------------------------------------------
+int KMMessage::type() const
+{
+ DwHeaders& header = mMsg->Headers();
+ if (header.HasContentType()) return header.ContentType().Type();
+ else return DwMime::kTypeNull;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setTypeStr(const QCString& aStr)
+{
+ dwContentType().SetTypeStr(DwString(aStr));
+ dwContentType().Parse();
+ mNeedsAssembly = true;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setType(int aType)
+{
+ dwContentType().SetType(aType);
+ dwContentType().Assemble();
+ mNeedsAssembly = true;
+}
+
+
+
+//-----------------------------------------------------------------------------
+QCString KMMessage::subtypeStr() const
+{
+ DwHeaders& header = mMsg->Headers();
+ if (header.HasContentType()) return header.ContentType().SubtypeStr().c_str();
+ else return "";
+}
+
+
+//-----------------------------------------------------------------------------
+int KMMessage::subtype() const
+{
+ DwHeaders& header = mMsg->Headers();
+ if (header.HasContentType()) return header.ContentType().Subtype();
+ else return DwMime::kSubtypeNull;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setSubtypeStr(const QCString& aStr)
+{
+ dwContentType().SetSubtypeStr(DwString(aStr));
+ dwContentType().Parse();
+ mNeedsAssembly = true;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setSubtype(int aSubtype)
+{
+ dwContentType().SetSubtype(aSubtype);
+ dwContentType().Assemble();
+ mNeedsAssembly = true;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setDwMediaTypeParam( DwMediaType &mType,
+ const QCString& attr,
+ const QCString& val )
+{
+ mType.Parse();
+ DwParameter *param = mType.FirstParameter();
+ while(param) {
+ if (!kasciistricmp(param->Attribute().c_str(), attr))
+ break;
+ else
+ param = param->Next();
+ }
+ if (!param){
+ param = new DwParameter;
+ param->SetAttribute(DwString( attr ));
+ mType.AddParameter( param );
+ }
+ else
+ mType.SetModified();
+ param->SetValue(DwString( val ));
+ mType.Assemble();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setContentTypeParam(const QCString& attr, const QCString& val)
+{
+ if (mNeedsAssembly) mMsg->Assemble();
+ mNeedsAssembly = false;
+ setDwMediaTypeParam( dwContentType(), attr, val );
+ mNeedsAssembly = true;
+}
+
+
+//-----------------------------------------------------------------------------
+QCString KMMessage::contentTransferEncodingStr() const
+{
+ DwHeaders& header = mMsg->Headers();
+ if (header.HasContentTransferEncoding())
+ return header.ContentTransferEncoding().AsString().c_str();
+ else return "";
+}
+
+
+//-----------------------------------------------------------------------------
+int KMMessage::contentTransferEncoding() const
+{
+ DwHeaders& header = mMsg->Headers();
+ if (header.HasContentTransferEncoding())
+ return header.ContentTransferEncoding().AsEnum();
+ else return DwMime::kCteNull;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setContentTransferEncodingStr(const QCString& aStr)
+{
+ mMsg->Headers().ContentTransferEncoding().FromString(aStr);
+ mMsg->Headers().ContentTransferEncoding().Parse();
+ mNeedsAssembly = true;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setContentTransferEncoding(int aCte)
+{
+ mMsg->Headers().ContentTransferEncoding().FromEnum(aCte);
+ mNeedsAssembly = true;
+}
+
+
+//-----------------------------------------------------------------------------
+DwHeaders& KMMessage::headers() const
+{
+ return mMsg->Headers();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setNeedsAssembly()
+{
+ mNeedsAssembly = true;
+}
+
+
+//-----------------------------------------------------------------------------
+QCString KMMessage::body() const
+{
+ const DwString& body = mMsg->Body().AsString();
+ QCString str = KMail::Util::CString( body );
+ // Calls length() -> slow
+ //kdWarning( str.length() != body.length(), 5006 )
+ // << "KMMessage::body(): body is binary but used as text!" << endl;
+ return str;
+}
+
+
+//-----------------------------------------------------------------------------
+QByteArray KMMessage::bodyDecodedBinary() const
+{
+ DwString dwstr;
+ const DwString& dwsrc = mMsg->Body().AsString();
+
+ switch (cte())
+ {
+ case DwMime::kCteBase64:
+ DwDecodeBase64(dwsrc, dwstr);
+ break;
+ case DwMime::kCteQuotedPrintable:
+ DwDecodeQuotedPrintable(dwsrc, dwstr);
+ break;
+ default:
+ dwstr = dwsrc;
+ break;
+ }
+
+ int len = dwstr.size();
+ QByteArray ba(len);
+ memcpy(ba.data(),dwstr.data(),len);
+ return ba;
+}
+
+
+//-----------------------------------------------------------------------------
+QCString KMMessage::bodyDecoded() const
+{
+ DwString dwstr;
+ DwString dwsrc = mMsg->Body().AsString();
+
+ switch (cte())
+ {
+ case DwMime::kCteBase64:
+ DwDecodeBase64(dwsrc, dwstr);
+ break;
+ case DwMime::kCteQuotedPrintable:
+ DwDecodeQuotedPrintable(dwsrc, dwstr);
+ break;
+ default:
+ dwstr = dwsrc;
+ break;
+ }
+
+ return KMail::Util::CString( dwstr );
+
+ // Calling QCString::length() is slow
+ //QCString result = KMail::Util::CString( dwstr );
+ //kdWarning(result.length() != len, 5006)
+ // << "KMMessage::bodyDecoded(): body is binary but used as text!" << endl;
+ //return result;
+}
+
+
+//-----------------------------------------------------------------------------
+QValueList<int> KMMessage::determineAllowedCtes( const CharFreq& cf,
+ bool allow8Bit,
+ bool willBeSigned )
+{
+ QValueList<int> allowedCtes;
+
+ switch ( cf.type() ) {
+ case CharFreq::SevenBitText:
+ allowedCtes << DwMime::kCte7bit;
+ case CharFreq::EightBitText:
+ if ( allow8Bit )
+ allowedCtes << DwMime::kCte8bit;
+ case CharFreq::SevenBitData:
+ if ( cf.printableRatio() > 5.0/6.0 ) {
+ // let n the length of data and p the number of printable chars.
+ // Then base64 \approx 4n/3; qp \approx p + 3(n-p)
+ // => qp < base64 iff p > 5n/6.
+ allowedCtes << DwMime::kCteQp;
+ allowedCtes << DwMime::kCteBase64;
+ } else {
+ allowedCtes << DwMime::kCteBase64;
+ allowedCtes << DwMime::kCteQp;
+ }
+ break;
+ case CharFreq::EightBitData:
+ allowedCtes << DwMime::kCteBase64;
+ break;
+ case CharFreq::None:
+ default:
+ // just nothing (avoid compiler warning)
+ ;
+ }
+
+ // In the following cases only QP and Base64 are allowed:
+ // - the buffer will be OpenPGP/MIME signed and it contains trailing
+ // whitespace (cf. RFC 3156)
+ // - a line starts with "From "
+ if ( ( willBeSigned && cf.hasTrailingWhitespace() ) ||
+ cf.hasLeadingFrom() ) {
+ allowedCtes.remove( DwMime::kCte8bit );
+ allowedCtes.remove( DwMime::kCte7bit );
+ }
+
+ return allowedCtes;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setBodyAndGuessCte( const QByteArray& aBuf,
+ QValueList<int> & allowedCte,
+ bool allow8Bit,
+ bool willBeSigned )
+{
+ 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 );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setBodyAndGuessCte( const QCString& aBuf,
+ QValueList<int> & allowedCte,
+ bool allow8Bit,
+ bool willBeSigned )
+{
+ 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 );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setBodyEncoded(const QCString& aStr)
+{
+ DwString dwSrc(aStr.data(), aStr.size()-1 /* not the trailing NUL */);
+ DwString dwResult;
+
+ switch (cte())
+ {
+ case DwMime::kCteBase64:
+ DwEncodeBase64(dwSrc, dwResult);
+ break;
+ case DwMime::kCteQuotedPrintable:
+ DwEncodeQuotedPrintable(dwSrc, dwResult);
+ break;
+ default:
+ dwResult = dwSrc;
+ break;
+ }
+
+ mMsg->Body().FromString(dwResult);
+ mNeedsAssembly = true;
+}
+
+//-----------------------------------------------------------------------------
+void KMMessage::setBodyEncodedBinary(const QByteArray& aStr)
+{
+ DwString dwSrc(aStr.data(), aStr.size());
+ DwString dwResult;
+
+ switch (cte())
+ {
+ case DwMime::kCteBase64:
+ DwEncodeBase64(dwSrc, dwResult);
+ break;
+ case DwMime::kCteQuotedPrintable:
+ DwEncodeQuotedPrintable(dwSrc, dwResult);
+ break;
+ default:
+ dwResult = dwSrc;
+ break;
+ }
+
+ mMsg->Body().FromString(dwResult);
+ mNeedsAssembly = true;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setBody(const QCString& aStr)
+{
+ mMsg->Body().FromString(KMail::Util::dwString(aStr));
+ mNeedsAssembly = true;
+}
+void KMMessage::setBody(const DwString& aStr)
+{
+ mMsg->Body().FromString(aStr);
+ mNeedsAssembly = true;
+}
+void KMMessage::setBody(const char* aStr)
+{
+ mMsg->Body().FromString(aStr);
+ mNeedsAssembly = true;
+}
+
+void KMMessage::setMultiPartBody( const QCString & aStr ) {
+ setBody( aStr );
+ mMsg->Body().Parse();
+ mNeedsAssembly = true;
+}
+
+
+// Patched by Daniel Moisset <dmoisset@grulic.org.ar>
+// modified numbodyparts, bodypart to take nested body parts as
+// a linear sequence.
+// third revision, Sep 26 2000
+
+// this is support structure for traversing tree without recursion
+
+//-----------------------------------------------------------------------------
+int KMMessage::numBodyParts() const
+{
+ int count = 0;
+ DwBodyPart* part = getFirstDwBodyPart();
+ QPtrList< DwBodyPart > parts;
+
+ while (part)
+ {
+ //dive into multipart messages
+ while ( part
+ && part->hasHeaders()
+ && part->Headers().HasContentType()
+ && part->Body().FirstBodyPart()
+ && (DwMime::kTypeMultipart == part->Headers().ContentType().Type()) )
+ {
+ parts.append( part );
+ part = part->Body().FirstBodyPart();
+ }
+ // this is where currPart->msgPart contains a leaf message part
+ count++;
+ // go up in the tree until reaching a node with next
+ // (or the last top-level node)
+ while (part && !(part->Next()) && !(parts.isEmpty()))
+ {
+ part = parts.getLast();
+ parts.removeLast();
+ }
+
+ if (part && part->Body().Message() &&
+ part->Body().Message()->Body().FirstBodyPart())
+ {
+ part = part->Body().Message()->Body().FirstBodyPart();
+ } else if (part) {
+ part = part->Next();
+ }
+ }
+
+ return count;
+}
+
+
+//-----------------------------------------------------------------------------
+DwBodyPart * KMMessage::getFirstDwBodyPart() const
+{
+ return mMsg->Body().FirstBodyPart();
+}
+
+
+//-----------------------------------------------------------------------------
+int KMMessage::partNumber( DwBodyPart * aDwBodyPart ) const
+{
+ DwBodyPart *curpart;
+ QPtrList< DwBodyPart > parts;
+ int curIdx = 0;
+ int idx = 0;
+ // Get the DwBodyPart for this index
+
+ curpart = getFirstDwBodyPart();
+
+ while (curpart && !idx) {
+ //dive into multipart messages
+ while( curpart
+ && curpart->hasHeaders()
+ && curpart->Headers().HasContentType()
+ && curpart->Body().FirstBodyPart()
+ && (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) )
+ {
+ parts.append( curpart );
+ curpart = curpart->Body().FirstBodyPart();
+ }
+ // this is where currPart->msgPart contains a leaf message part
+ if (curpart == aDwBodyPart)
+ idx = curIdx;
+ curIdx++;
+ // go up in the tree until reaching a node with next
+ // (or the last top-level node)
+ while (curpart && !(curpart->Next()) && !(parts.isEmpty()))
+ {
+ curpart = parts.getLast();
+ parts.removeLast();
+ } ;
+ if (curpart)
+ curpart = curpart->Next();
+ }
+ return idx;
+}
+
+
+//-----------------------------------------------------------------------------
+DwBodyPart * KMMessage::dwBodyPart( int aIdx ) const
+{
+ DwBodyPart *part, *curpart;
+ QPtrList< DwBodyPart > parts;
+ int curIdx = 0;
+ // Get the DwBodyPart for this index
+
+ curpart = getFirstDwBodyPart();
+ part = 0;
+
+ while (curpart && !part) {
+ //dive into multipart messages
+ while( curpart
+ && curpart->hasHeaders()
+ && curpart->Headers().HasContentType()
+ && curpart->Body().FirstBodyPart()
+ && (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) )
+ {
+ parts.append( curpart );
+ curpart = curpart->Body().FirstBodyPart();
+ }
+ // this is where currPart->msgPart contains a leaf message part
+ if (curIdx==aIdx)
+ part = curpart;
+ curIdx++;
+ // go up in the tree until reaching a node with next
+ // (or the last top-level node)
+ while (curpart && !(curpart->Next()) && !(parts.isEmpty()))
+ {
+ curpart = parts.getLast();
+ parts.removeLast();
+ }
+ if (curpart)
+ curpart = curpart->Next();
+ }
+ return part;
+}
+
+
+//-----------------------------------------------------------------------------
+DwBodyPart * KMMessage::findDwBodyPart( int type, int subtype ) const
+{
+ DwBodyPart *part, *curpart;
+ QPtrList< DwBodyPart > parts;
+ // Get the DwBodyPart for this index
+
+ curpart = getFirstDwBodyPart();
+ part = 0;
+
+ while (curpart && !part) {
+ //dive into multipart messages
+ while(curpart
+ && curpart->hasHeaders()
+ && curpart->Headers().HasContentType()
+ && curpart->Body().FirstBodyPart()
+ && (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) ) {
+ parts.append( curpart );
+ curpart = curpart->Body().FirstBodyPart();
+ }
+ // this is where curPart->msgPart contains a leaf message part
+
+ // pending(khz): Find out WHY this look does not travel down *into* an
+ // embedded "Message/RfF822" message containing a "Multipart/Mixed"
+ if ( curpart && curpart->hasHeaders() && curpart->Headers().HasContentType() ) {
+ kdDebug(5006) << curpart->Headers().ContentType().TypeStr().c_str()
+ << " " << curpart->Headers().ContentType().SubtypeStr().c_str() << endl;
+ }
+
+ if (curpart &&
+ curpart->hasHeaders() &&
+ curpart->Headers().HasContentType() &&
+ curpart->Headers().ContentType().Type() == type &&
+ curpart->Headers().ContentType().Subtype() == subtype) {
+ part = curpart;
+ } else {
+ // go up in the tree until reaching a node with next
+ // (or the last top-level node)
+ while (curpart && !(curpart->Next()) && !(parts.isEmpty())) {
+ curpart = parts.getLast();
+ parts.removeLast();
+ } ;
+ if (curpart)
+ curpart = curpart->Next();
+ }
+ }
+ return part;
+}
+
+//-----------------------------------------------------------------------------
+DwBodyPart * KMMessage::findDwBodyPart( const QCString& type, const QCString& subtype ) const
+{
+ DwBodyPart *part, *curpart;
+ QPtrList< DwBodyPart > parts;
+ // Get the DwBodyPart for this index
+
+ curpart = getFirstDwBodyPart();
+ part = 0;
+
+ while (curpart && !part) {
+ //dive into multipart messages
+ while(curpart
+ && curpart->hasHeaders()
+ && curpart->Headers().HasContentType()
+ && curpart->Body().FirstBodyPart()
+ && (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) ) {
+ parts.append( curpart );
+ curpart = curpart->Body().FirstBodyPart();
+ }
+ // this is where curPart->msgPart contains a leaf message part
+
+ // pending(khz): Find out WHY this look does not travel down *into* an
+ // embedded "Message/RfF822" message containing a "Multipart/Mixed"
+ if (curpart && curpart->hasHeaders() && curpart->Headers().HasContentType() ) {
+ kdDebug(5006) << curpart->Headers().ContentType().TypeStr().c_str()
+ << " " << curpart->Headers().ContentType().SubtypeStr().c_str() << endl;
+ }
+
+ if (curpart &&
+ curpart->hasHeaders() &&
+ curpart->Headers().HasContentType() &&
+ curpart->Headers().ContentType().TypeStr().c_str() == type &&
+ curpart->Headers().ContentType().SubtypeStr().c_str() == subtype) {
+ part = curpart;
+ } else {
+ // go up in the tree until reaching a node with next
+ // (or the last top-level node)
+ while (curpart && !(curpart->Next()) && !(parts.isEmpty())) {
+ curpart = parts.getLast();
+ parts.removeLast();
+ } ;
+ if (curpart)
+ curpart = curpart->Next();
+ }
+ }
+ return part;
+}
+
+
+void applyHeadersToMessagePart( DwHeaders& headers, KMMessagePart* aPart )
+{
+ // TODO: Instead of manually implementing RFC2231 header encoding (i.e.
+ // possibly multiple values given as paramname*0=..; parmaname*1=..;...
+ // or par as paramname*0*=..; parmaname*1*=..;..., which should be
+ // concatenated), use a generic method to decode the header, using RFC
+ // 2047 or 2231, or whatever future RFC might be appropriate!
+ // Right now, some fields are decoded, while others are not. E.g.
+ // Content-Disposition is not decoded here, rather only on demand in
+ // KMMsgPart::fileName; Name however is decoded here and stored as a
+ // decoded String in KMMsgPart...
+ // Content-type
+ QCString additionalCTypeParams;
+ if (headers.HasContentType())
+ {
+ DwMediaType& ct = headers.ContentType();
+ aPart->setOriginalContentTypeStr( ct.AsString().c_str() );
+ aPart->setTypeStr(ct.TypeStr().c_str());
+ aPart->setSubtypeStr(ct.SubtypeStr().c_str());
+ DwParameter *param = ct.FirstParameter();
+ while(param)
+ {
+ if (!qstricmp(param->Attribute().c_str(), "charset"))
+ aPart->setCharset(QCString(param->Value().c_str()).lower());
+ else if (!qstrnicmp(param->Attribute().c_str(), "name*", 5))
+ aPart->setName(KMMsgBase::decodeRFC2231String(KMMsgBase::extractRFC2231HeaderField( param->Value().c_str(), "name" )));
+ else {
+ additionalCTypeParams += ';';
+ additionalCTypeParams += param->AsString().c_str();
+ }
+ param=param->Next();
+ }
+ }
+ else
+ {
+ aPart->setTypeStr("text"); // Set to defaults
+ aPart->setSubtypeStr("plain");
+ }
+ aPart->setAdditionalCTypeParamStr( additionalCTypeParams );
+ // Modification by Markus
+ if (aPart->name().isEmpty())
+ {
+ if (headers.HasContentType() && !headers.ContentType().Name().empty()) {
+ aPart->setName(KMMsgBase::decodeRFC2047String(headers.
+ ContentType().Name().c_str()) );
+ } else if (headers.HasSubject() && !headers.Subject().AsString().empty()) {
+ aPart->setName( KMMsgBase::decodeRFC2047String(headers.
+ Subject().AsString().c_str()) );
+ }
+ }
+
+ // Content-transfer-encoding
+ if (headers.HasContentTransferEncoding())
+ aPart->setCteStr(headers.ContentTransferEncoding().AsString().c_str());
+ else
+ aPart->setCteStr("7bit");
+
+ // Content-description
+ if (headers.HasContentDescription())
+ aPart->setContentDescription(headers.ContentDescription().AsString().c_str());
+ else
+ aPart->setContentDescription("");
+
+ // Content-disposition
+ if (headers.HasContentDisposition())
+ aPart->setContentDisposition(headers.ContentDisposition().AsString().c_str());
+ else
+ aPart->setContentDisposition("");
+}
+
+//-----------------------------------------------------------------------------
+void KMMessage::bodyPart(DwBodyPart* aDwBodyPart, KMMessagePart* aPart,
+ bool withBody)
+{
+ if ( !aPart )
+ return;
+
+ aPart->clear();
+
+ if( aDwBodyPart && aDwBodyPart->hasHeaders() ) {
+ // This must not be an empty string, because we'll get a
+ // spurious empty Subject: line in some of the parts.
+ //aPart->setName(" ");
+ // partSpecifier
+ QString partId( aDwBodyPart->partId() );
+ aPart->setPartSpecifier( partId );
+
+ DwHeaders& headers = aDwBodyPart->Headers();
+ applyHeadersToMessagePart( headers, aPart );
+
+ // Body
+ if (withBody)
+ aPart->setBody( aDwBodyPart->Body().AsString() );
+ else
+ aPart->setBody( QCString("") );
+
+ // Content-id
+ if ( headers.HasContentId() ) {
+ const QCString contentId = headers.ContentId().AsString().c_str();
+ // ignore leading '<' and trailing '>'
+ aPart->setContentId( contentId.mid( 1, contentId.length() - 2 ) );
+ }
+ }
+ // If no valid body part was given,
+ // set all MultipartBodyPart attributes to empty values.
+ else
+ {
+ aPart->setTypeStr("");
+ aPart->setSubtypeStr("");
+ aPart->setCteStr("");
+ // This must not be an empty string, because we'll get a
+ // spurious empty Subject: line in some of the parts.
+ //aPart->setName(" ");
+ aPart->setContentDescription("");
+ aPart->setContentDisposition("");
+ aPart->setBody(QCString(""));
+ aPart->setContentId("");
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::bodyPart(int aIdx, KMMessagePart* aPart) const
+{
+ if ( !aPart )
+ return;
+
+ // If the DwBodyPart was found get the header fields and body
+ if ( DwBodyPart *part = dwBodyPart( aIdx ) ) {
+ KMMessage::bodyPart(part, aPart);
+ if( aPart->name().isEmpty() )
+ aPart->setName( i18n("Attachment: %1").arg( aIdx ) );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::deleteBodyParts()
+{
+ mMsg->Body().DeleteBodyParts();
+}
+
+//-----------------------------------------------------------------------------
+DwBodyPart* KMMessage::createDWBodyPart(const KMMessagePart* aPart)
+{
+ DwBodyPart* part = DwBodyPart::NewBodyPart(emptyString, 0);
+
+ if ( !aPart )
+ return part;
+
+ QCString charset = aPart->charset();
+ QCString type = aPart->typeStr();
+ QCString subtype = aPart->subtypeStr();
+ QCString cte = aPart->cteStr();
+ QCString contDesc = aPart->contentDescriptionEncoded();
+ QCString contDisp = aPart->contentDisposition();
+ QCString encoding = autoDetectCharset(charset, sPrefCharsets, aPart->name());
+ if (encoding.isEmpty()) encoding = "utf-8";
+ QCString name = KMMsgBase::encodeRFC2231String(aPart->name(), encoding);
+ bool RFC2231encoded = aPart->name() != QString(name);
+ QCString paramAttr = aPart->parameterAttribute();
+
+ DwHeaders& headers = part->Headers();
+
+ DwMediaType& ct = headers.ContentType();
+ if (!type.isEmpty() && !subtype.isEmpty())
+ {
+ ct.SetTypeStr(type.data());
+ ct.SetSubtypeStr(subtype.data());
+ if (!charset.isEmpty()){
+ DwParameter *param;
+ param=new DwParameter;
+ param->SetAttribute("charset");
+ param->SetValue(charset.data());
+ ct.AddParameter(param);
+ }
+ }
+
+ QCString additionalParam = aPart->additionalCTypeParamStr();
+ if( !additionalParam.isEmpty() )
+ {
+ QCString parAV;
+ DwString parA, parV;
+ int iL, i1, i2, iM;
+ iL = additionalParam.length();
+ i1 = 0;
+ i2 = additionalParam.find(';', i1, false);
+ while ( i1 < iL )
+ {
+ if( -1 == i2 )
+ i2 = iL;
+ if( i1+1 < i2 ) {
+ parAV = additionalParam.mid( i1, (i2-i1) );
+ iM = parAV.find('=');
+ if( -1 < iM )
+ {
+ parA = parAV.left( iM );
+ parV = parAV.right( parAV.length() - iM - 1 );
+ if( ('"' == parV.at(0)) && ('"' == parV.at(parV.length()-1)) )
+ {
+ parV.erase( 0, 1);
+ parV.erase( parV.length()-1 );
+ }
+ }
+ else
+ {
+ parA = parAV;
+ parV = "";
+ }
+ DwParameter *param;
+ param = new DwParameter;
+ param->SetAttribute( parA );
+ param->SetValue( parV );
+ ct.AddParameter( param );
+ }
+ i1 = i2+1;
+ i2 = additionalParam.find(';', i1, false);
+ }
+ }
+
+ if ( !name.isEmpty() ) {
+ if (RFC2231encoded)
+ {
+ DwParameter *nameParam;
+ nameParam = new DwParameter;
+ nameParam->SetAttribute("name*");
+ nameParam->SetValue(name.data(),true);
+ ct.AddParameter(nameParam);
+ } else {
+ ct.SetName(name.data());
+ }
+ }
+
+ if (!paramAttr.isEmpty())
+ {
+ QCString encoding = autoDetectCharset(charset, sPrefCharsets,
+ aPart->parameterValue());
+ if (encoding.isEmpty()) encoding = "utf-8";
+ QCString paramValue;
+ paramValue = KMMsgBase::encodeRFC2231String(aPart->parameterValue(),
+ encoding);
+ DwParameter *param = new DwParameter;
+ if (aPart->parameterValue() != QString(paramValue))
+ {
+ param->SetAttribute((paramAttr + '*').data());
+ param->SetValue(paramValue.data(),true);
+ } else {
+ param->SetAttribute(paramAttr.data());
+ param->SetValue(paramValue.data());
+ }
+ ct.AddParameter(param);
+ }
+
+ if (!cte.isEmpty())
+ headers.Cte().FromString(cte);
+
+ if (!contDesc.isEmpty())
+ headers.ContentDescription().FromString(contDesc);
+
+ if (!contDisp.isEmpty())
+ headers.ContentDisposition().FromString(contDisp);
+
+ const DwString bodyStr = aPart->dwBody();
+ if (!bodyStr.empty())
+ part->Body().FromString(bodyStr);
+ else
+ part->Body().FromString("");
+
+ if (!aPart->partSpecifier().isNull())
+ part->SetPartId( aPart->partSpecifier().latin1() );
+
+ if (aPart->decodedSize() > 0)
+ part->SetBodySize( aPart->decodedSize() );
+
+ return part;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::addDwBodyPart(DwBodyPart * aDwPart)
+{
+ mMsg->Body().AddBodyPart( aDwPart );
+ mNeedsAssembly = true;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::addBodyPart(const KMMessagePart* aPart)
+{
+ DwBodyPart* part = createDWBodyPart( aPart );
+ addDwBodyPart( part );
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMessage::generateMessageId( const QString& addr )
+{
+ QDateTime datetime = QDateTime::currentDateTime();
+ QString msgIdStr;
+
+ msgIdStr = '<' + datetime.toString( "yyyyMMddhhmm.sszzz" );
+
+ QString msgIdSuffix;
+ KConfigGroup general( KMKernel::config(), "General" );
+
+ if( general.readBoolEntry( "useCustomMessageIdSuffix", false ) )
+ msgIdSuffix = general.readEntry( "myMessageIdSuffix" );
+
+ if( !msgIdSuffix.isEmpty() )
+ msgIdStr += '@' + msgIdSuffix;
+ else
+ msgIdStr += '.' + KPIM::encodeIDN( addr );
+
+ msgIdStr += '>';
+
+ return msgIdStr;
+}
+
+
+//-----------------------------------------------------------------------------
+QCString KMMessage::html2source( const QCString & src )
+{
+ QCString result( 1 + 6*(src.size()-1) ); // maximal possible length
+
+ QCString::ConstIterator s = src.begin();
+ QCString::Iterator d = result.begin();
+ while ( *s ) {
+ switch ( *s ) {
+ case '<': {
+ *d++ = '&';
+ *d++ = 'l';
+ *d++ = 't';
+ *d++ = ';';
+ ++s;
+ }
+ break;
+ case '\r': {
+ ++s;
+ }
+ break;
+ case '\n': {
+ *d++ = '<';
+ *d++ = 'b';
+ *d++ = 'r';
+ *d++ = '>';
+ ++s;
+ }
+ break;
+ case '>': {
+ *d++ = '&';
+ *d++ = 'g';
+ *d++ = 't';
+ *d++ = ';';
+ ++s;
+ }
+ break;
+ case '&': {
+ *d++ = '&';
+ *d++ = 'a';
+ *d++ = 'm';
+ *d++ = 'p';
+ *d++ = ';';
+ ++s;
+ }
+ break;
+ case '"': {
+ *d++ = '&';
+ *d++ = 'q';
+ *d++ = 'u';
+ *d++ = 'o';
+ *d++ = 't';
+ *d++ = ';';
+ ++s;
+ }
+ break;
+ case '\'': {
+ *d++ = '&';
+ *d++ = 'a';
+ *d++ = 'p';
+ *d++ = 's';
+ *d++ = ';';
+ ++s;
+ }
+ break;
+ default:
+ *d++ = *s++;
+ }
+ }
+ result.truncate( d - result.begin() ); // adds trailing NUL
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+QString KMMessage::encodeMailtoUrl( const QString& str )
+{
+ QString result;
+ result = QString::fromLatin1( KMMsgBase::encodeRFC2047String( str,
+ "utf-8" ) );
+ result = KURL::encode_string( result );
+ return result;
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMessage::decodeMailtoUrl( const QString& url )
+{
+ QString result;
+ result = KURL::decode_string( url );
+ result = KMMsgBase::decodeRFC2047String( result.latin1() );
+ return result;
+}
+
+
+//-----------------------------------------------------------------------------
+QCString KMMessage::stripEmailAddr( const QCString& aStr )
+{
+ //kdDebug(5006) << "KMMessage::stripEmailAddr( " << aStr << " )" << endl;
+
+ if ( aStr.isEmpty() )
+ return QCString();
+
+ QCString result;
+
+ // The following is a primitive parser for a mailbox-list (cf. RFC 2822).
+ // The purpose is to extract a displayable string from the mailboxes.
+ // Comments in the addr-spec are not handled. No error checking is done.
+
+ QCString name;
+ QCString comment;
+ QCString angleAddress;
+ enum { TopLevel, InComment, InAngleAddress } context = TopLevel;
+ bool inQuotedString = false;
+ int commentLevel = 0;
+
+ for ( char* p = aStr.data(); *p; ++p ) {
+ switch ( context ) {
+ case TopLevel : {
+ switch ( *p ) {
+ case '"' : inQuotedString = !inQuotedString;
+ break;
+ case '(' : if ( !inQuotedString ) {
+ context = InComment;
+ commentLevel = 1;
+ }
+ else
+ name += *p;
+ break;
+ case '<' : if ( !inQuotedString ) {
+ context = InAngleAddress;
+ }
+ else
+ name += *p;
+ break;
+ case '\\' : // quoted character
+ ++p; // skip the '\'
+ if ( *p )
+ name += *p;
+ break;
+ case ',' : if ( !inQuotedString ) {
+ // next email address
+ if ( !result.isEmpty() )
+ result += ", ";
+ name = name.stripWhiteSpace();
+ comment = comment.stripWhiteSpace();
+ angleAddress = angleAddress.stripWhiteSpace();
+ /*
+ kdDebug(5006) << "Name : \"" << name
+ << "\"" << endl;
+ kdDebug(5006) << "Comment : \"" << comment
+ << "\"" << endl;
+ kdDebug(5006) << "Address : \"" << angleAddress
+ << "\"" << endl;
+ */
+ if ( angleAddress.isEmpty() && !comment.isEmpty() ) {
+ // handle Outlook-style addresses like
+ // john.doe@invalid (John Doe)
+ result += comment;
+ }
+ else if ( !name.isEmpty() ) {
+ result += name;
+ }
+ else if ( !comment.isEmpty() ) {
+ result += comment;
+ }
+ else if ( !angleAddress.isEmpty() ) {
+ result += angleAddress;
+ }
+ name = QCString();
+ comment = QCString();
+ angleAddress = QCString();
+ }
+ else
+ name += *p;
+ break;
+ default : name += *p;
+ }
+ break;
+ }
+ case InComment : {
+ switch ( *p ) {
+ case '(' : ++commentLevel;
+ comment += *p;
+ break;
+ case ')' : --commentLevel;
+ if ( commentLevel == 0 ) {
+ context = TopLevel;
+ comment += ' '; // separate the text of several comments
+ }
+ else
+ comment += *p;
+ break;
+ case '\\' : // quoted character
+ ++p; // skip the '\'
+ if ( *p )
+ comment += *p;
+ break;
+ default : comment += *p;
+ }
+ break;
+ }
+ case InAngleAddress : {
+ switch ( *p ) {
+ case '"' : inQuotedString = !inQuotedString;
+ angleAddress += *p;
+ break;
+ case '>' : if ( !inQuotedString ) {
+ context = TopLevel;
+ }
+ else
+ angleAddress += *p;
+ break;
+ case '\\' : // quoted character
+ ++p; // skip the '\'
+ if ( *p )
+ angleAddress += *p;
+ break;
+ default : angleAddress += *p;
+ }
+ break;
+ }
+ } // switch ( context )
+ }
+ if ( !result.isEmpty() )
+ result += ", ";
+ name = name.stripWhiteSpace();
+ comment = comment.stripWhiteSpace();
+ angleAddress = angleAddress.stripWhiteSpace();
+ /*
+ kdDebug(5006) << "Name : \"" << name << "\"" << endl;
+ kdDebug(5006) << "Comment : \"" << comment << "\"" << endl;
+ kdDebug(5006) << "Address : \"" << angleAddress << "\"" << endl;
+ */
+ if ( angleAddress.isEmpty() && !comment.isEmpty() ) {
+ // handle Outlook-style addresses like
+ // john.doe@invalid (John Doe)
+ result += comment;
+ }
+ else if ( !name.isEmpty() ) {
+ result += name;
+ }
+ else if ( !comment.isEmpty() ) {
+ result += comment;
+ }
+ else if ( !angleAddress.isEmpty() ) {
+ result += angleAddress;
+ }
+
+ //kdDebug(5006) << "KMMessage::stripEmailAddr(...) returns \"" << result
+ // << "\"" << endl;
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+QString KMMessage::stripEmailAddr( const QString& aStr )
+{
+ //kdDebug(5006) << "KMMessage::stripEmailAddr( " << aStr << " )" << endl;
+
+ if ( aStr.isEmpty() )
+ return QString::null;
+
+ QString result;
+
+ // The following is a primitive parser for a mailbox-list (cf. RFC 2822).
+ // The purpose is to extract a displayable string from the mailboxes.
+ // Comments in the addr-spec are not handled. No error checking is done.
+
+ QString name;
+ QString comment;
+ QString angleAddress;
+ enum { TopLevel, InComment, InAngleAddress } context = TopLevel;
+ bool inQuotedString = false;
+ int commentLevel = 0;
+
+ QChar ch;
+ unsigned int strLength(aStr.length());
+ for ( uint index = 0; index < strLength; ++index ) {
+ ch = aStr[index];
+ switch ( context ) {
+ case TopLevel : {
+ switch ( ch.latin1() ) {
+ case '"' : inQuotedString = !inQuotedString;
+ break;
+ case '(' : if ( !inQuotedString ) {
+ context = InComment;
+ commentLevel = 1;
+ }
+ else
+ name += ch;
+ break;
+ case '<' : if ( !inQuotedString ) {
+ context = InAngleAddress;
+ }
+ else
+ name += ch;
+ break;
+ case '\\' : // quoted character
+ ++index; // skip the '\'
+ if ( index < aStr.length() )
+ name += aStr[index];
+ break;
+ case ',' : if ( !inQuotedString ) {
+ // next email address
+ if ( !result.isEmpty() )
+ result += ", ";
+ name = name.stripWhiteSpace();
+ comment = comment.stripWhiteSpace();
+ angleAddress = angleAddress.stripWhiteSpace();
+ /*
+ kdDebug(5006) << "Name : \"" << name
+ << "\"" << endl;
+ kdDebug(5006) << "Comment : \"" << comment
+ << "\"" << endl;
+ kdDebug(5006) << "Address : \"" << angleAddress
+ << "\"" << endl;
+ */
+ if ( angleAddress.isEmpty() && !comment.isEmpty() ) {
+ // handle Outlook-style addresses like
+ // john.doe@invalid (John Doe)
+ result += comment;
+ }
+ else if ( !name.isEmpty() ) {
+ result += name;
+ }
+ else if ( !comment.isEmpty() ) {
+ result += comment;
+ }
+ else if ( !angleAddress.isEmpty() ) {
+ result += angleAddress;
+ }
+ name = QString::null;
+ comment = QString::null;
+ angleAddress = QString::null;
+ }
+ else
+ name += ch;
+ break;
+ default : name += ch;
+ }
+ break;
+ }
+ case InComment : {
+ switch ( ch.latin1() ) {
+ case '(' : ++commentLevel;
+ comment += ch;
+ break;
+ case ')' : --commentLevel;
+ if ( commentLevel == 0 ) {
+ context = TopLevel;
+ comment += ' '; // separate the text of several comments
+ }
+ else
+ comment += ch;
+ break;
+ case '\\' : // quoted character
+ ++index; // skip the '\'
+ if ( index < aStr.length() )
+ comment += aStr[index];
+ break;
+ default : comment += ch;
+ }
+ break;
+ }
+ case InAngleAddress : {
+ switch ( ch.latin1() ) {
+ case '"' : inQuotedString = !inQuotedString;
+ angleAddress += ch;
+ break;
+ case '>' : if ( !inQuotedString ) {
+ context = TopLevel;
+ }
+ else
+ angleAddress += ch;
+ break;
+ case '\\' : // quoted character
+ ++index; // skip the '\'
+ if ( index < aStr.length() )
+ angleAddress += aStr[index];
+ break;
+ default : angleAddress += ch;
+ }
+ break;
+ }
+ } // switch ( context )
+ }
+ if ( !result.isEmpty() )
+ result += ", ";
+ name = name.stripWhiteSpace();
+ comment = comment.stripWhiteSpace();
+ angleAddress = angleAddress.stripWhiteSpace();
+ /*
+ kdDebug(5006) << "Name : \"" << name << "\"" << endl;
+ kdDebug(5006) << "Comment : \"" << comment << "\"" << endl;
+ kdDebug(5006) << "Address : \"" << angleAddress << "\"" << endl;
+ */
+ if ( angleAddress.isEmpty() && !comment.isEmpty() ) {
+ // handle Outlook-style addresses like
+ // john.doe@invalid (John Doe)
+ result += comment;
+ }
+ else if ( !name.isEmpty() ) {
+ result += name;
+ }
+ else if ( !comment.isEmpty() ) {
+ result += comment;
+ }
+ else if ( !angleAddress.isEmpty() ) {
+ result += angleAddress;
+ }
+
+ //kdDebug(5006) << "KMMessage::stripEmailAddr(...) returns \"" << result
+ // << "\"" << endl;
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+QString KMMessage::quoteHtmlChars( const QString& str, bool removeLineBreaks )
+{
+ QString result;
+
+ unsigned int strLength(str.length());
+ result.reserve( 6*strLength ); // maximal possible length
+ for( unsigned int i = 0; i < strLength; ++i )
+ switch ( str[i].latin1() ) {
+ case '<':
+ result += "&lt;";
+ break;
+ case '>':
+ result += "&gt;";
+ break;
+ case '&':
+ result += "&amp;";
+ break;
+ case '"':
+ result += "&quot;";
+ break;
+ case '\n':
+ if ( !removeLineBreaks )
+ result += "<br>";
+ break;
+ case '\r':
+ // ignore CR
+ break;
+ default:
+ result += str[i];
+ }
+
+ result.squeeze();
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+QString KMMessage::emailAddrAsAnchor(const QString& aEmail, bool stripped, const QString& cssStyle, bool aLink)
+{
+ if( aEmail.isEmpty() )
+ return aEmail;
+
+ QStringList addressList = KPIM::splitEmailAddrList( aEmail );
+
+ QString result;
+
+ for( QStringList::ConstIterator it = addressList.begin();
+ ( it != addressList.end() );
+ ++it ) {
+ if( !(*it).isEmpty() ) {
+ QString address = *it;
+ if(aLink) {
+ result += "<a href=\"mailto:"
+ + KMMessage::encodeMailtoUrl( address )
+ + "\" "+cssStyle+">";
+ }
+ if( stripped )
+ address = KMMessage::stripEmailAddr( address );
+ result += KMMessage::quoteHtmlChars( address, true );
+ if(aLink)
+ result += "</a>, ";
+ }
+ }
+ // cut of the trailing ", "
+ if(aLink)
+ result.truncate( result.length() - 2 );
+
+ //kdDebug(5006) << "KMMessage::emailAddrAsAnchor('" << aEmail
+ // << "') returns:\n-->" << result << "<--" << endl;
+ return result;
+}
+
+
+//-----------------------------------------------------------------------------
+//static
+QStringList KMMessage::stripAddressFromAddressList( const QString& address,
+ const QStringList& list )
+{
+ QStringList addresses( list );
+ QString addrSpec( KPIM::getEmailAddress( address ) );
+ for ( QStringList::Iterator it = addresses.begin();
+ it != addresses.end(); ) {
+ if ( kasciistricmp( addrSpec.utf8().data(),
+ KPIM::getEmailAddress( *it ).utf8().data() ) == 0 ) {
+ kdDebug(5006) << "Removing " << *it << " from the address list"
+ << endl;
+ it = addresses.remove( it );
+ }
+ else
+ ++it;
+ }
+ return addresses;
+}
+
+
+//-----------------------------------------------------------------------------
+//static
+QStringList KMMessage::stripMyAddressesFromAddressList( const QStringList& list )
+{
+ QStringList addresses = list;
+ for( QStringList::Iterator it = addresses.begin();
+ it != addresses.end(); ) {
+ kdDebug(5006) << "Check whether " << *it << " is one of my addresses"
+ << endl;
+ if( kmkernel->identityManager()->thatIsMe( KPIM::getEmailAddress( *it ) ) ) {
+ kdDebug(5006) << "Removing " << *it << " from the address list"
+ << endl;
+ it = addresses.remove( it );
+ }
+ else
+ ++it;
+ }
+ return addresses;
+}
+
+
+//-----------------------------------------------------------------------------
+//static
+bool KMMessage::addressIsInAddressList( const QString& address,
+ const QStringList& addresses )
+{
+ QString addrSpec = KPIM::getEmailAddress( address );
+ for( QStringList::ConstIterator it = addresses.begin();
+ it != addresses.end(); ++it ) {
+ if ( kasciistricmp( addrSpec.utf8().data(),
+ KPIM::getEmailAddress( *it ).utf8().data() ) == 0 )
+ return true;
+ }
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+//static
+QString KMMessage::expandAliases( const QString& recipients )
+{
+ if ( recipients.isEmpty() )
+ return QString();
+
+ QStringList recipientList = KPIM::splitEmailAddrList( recipients );
+
+ QString expandedRecipients;
+ for ( QStringList::Iterator it = recipientList.begin();
+ it != recipientList.end(); ++it ) {
+ if ( !expandedRecipients.isEmpty() )
+ expandedRecipients += ", ";
+ QString receiver = (*it).stripWhiteSpace();
+
+ // try to expand distribution list
+ QString expandedList = KAddrBookExternal::expandDistributionList( receiver );
+ if ( !expandedList.isEmpty() ) {
+ expandedRecipients += expandedList;
+ continue;
+ }
+
+ // try to expand nick name
+ QString expandedNickName = KabcBridge::expandNickName( receiver );
+ if ( !expandedNickName.isEmpty() ) {
+ expandedRecipients += expandedNickName;
+ continue;
+ }
+
+ // check whether the address is missing the domain part
+ // FIXME: looking for '@' might be wrong
+ if ( receiver.find('@') == -1 ) {
+ KConfigGroup general( KMKernel::config(), "General" );
+ QString defaultdomain = general.readEntry( "Default domain" );
+ if( !defaultdomain.isEmpty() ) {
+ expandedRecipients += receiver + "@" + defaultdomain;
+ }
+ else {
+ expandedRecipients += guessEmailAddressFromLoginName( receiver );
+ }
+ }
+ else
+ expandedRecipients += receiver;
+ }
+
+ return expandedRecipients;
+}
+
+
+//-----------------------------------------------------------------------------
+//static
+QString KMMessage::guessEmailAddressFromLoginName( const QString& loginName )
+{
+ if ( loginName.isEmpty() )
+ return QString();
+
+ char hostnameC[256];
+ // null terminate this C string
+ hostnameC[255] = '\0';
+ // set the string to 0 length if gethostname fails
+ if ( gethostname( hostnameC, 255 ) )
+ hostnameC[0] = '\0';
+ QString address = loginName;
+ address += '@';
+ address += QString::fromLocal8Bit( hostnameC );
+
+ // try to determine the real name
+ const KUser user( loginName );
+ if ( user.isValid() ) {
+ QString fullName = user.fullName();
+ if ( fullName.find( QRegExp( "[^ 0-9A-Za-z\\x0080-\\xFFFF]" ) ) != -1 )
+ address = '"' + fullName.replace( '\\', "\\" ).replace( '"', "\\" )
+ + "\" <" + address + '>';
+ else
+ address = fullName + " <" + address + '>';
+ }
+
+ return address;
+}
+
+//-----------------------------------------------------------------------------
+void KMMessage::readConfig()
+{
+ KMMsgBase::readConfig();
+
+ KConfig *config=KMKernel::config();
+ KConfigGroupSaver saver(config, "General");
+
+ config->setGroup("General");
+
+ int languageNr = config->readNumEntry("reply-current-language",0);
+
+ { // area for config group "KMMessage #n"
+ KConfigGroupSaver saver(config, QString("KMMessage #%1").arg(languageNr));
+ sReplyLanguage = config->readEntry("language",KGlobal::locale()->language());
+ sReplyStr = config->readEntry("phrase-reply",
+ i18n("On %D, you wrote:"));
+ sReplyAllStr = config->readEntry("phrase-reply-all",
+ i18n("On %D, %F wrote:"));
+ sForwardStr = config->readEntry("phrase-forward",
+ i18n("Forwarded Message"));
+ sIndentPrefixStr = config->readEntry("indent-prefix",">%_");
+ }
+
+ { // area for config group "Composer"
+ KConfigGroupSaver saver(config, "Composer");
+ sSmartQuote = GlobalSettings::self()->smartQuote();
+ sWordWrap = GlobalSettings::self()->wordWrap();
+ sWrapCol = GlobalSettings::self()->lineWrapWidth();
+ if ((sWrapCol == 0) || (sWrapCol > 78))
+ sWrapCol = 78;
+ if (sWrapCol < 30)
+ sWrapCol = 30;
+
+ sPrefCharsets = config->readListEntry("pref-charsets");
+ }
+
+ { // area for config group "Reader"
+ KConfigGroupSaver saver(config, "Reader");
+ sHeaderStrategy = HeaderStrategy::create( config->readEntry( "header-set-displayed", "rich" ) );
+ }
+}
+
+QCString KMMessage::defaultCharset()
+{
+ QCString retval;
+
+ if (!sPrefCharsets.isEmpty())
+ retval = sPrefCharsets[0].latin1();
+
+ if (retval.isEmpty() || (retval == "locale")) {
+ retval = QCString(kmkernel->networkCodec()->mimeName());
+ KPIM::kAsciiToLower( retval.data() );
+ }
+
+ if (retval == "jisx0208.1983-0") retval = "iso-2022-jp";
+ else if (retval == "ksc5601.1987-0") retval = "euc-kr";
+ return retval;
+}
+
+const QStringList &KMMessage::preferredCharsets()
+{
+ return sPrefCharsets;
+}
+
+//-----------------------------------------------------------------------------
+QCString KMMessage::charset() const
+{
+ if ( mMsg->Headers().HasContentType() ) {
+ DwMediaType &mType=mMsg->Headers().ContentType();
+ mType.Parse();
+ DwParameter *param=mType.FirstParameter();
+ while(param){
+ if (!kasciistricmp(param->Attribute().c_str(), "charset"))
+ return param->Value().c_str();
+ else param=param->Next();
+ }
+ }
+ return ""; // us-ascii, but we don't have to specify it
+}
+
+//-----------------------------------------------------------------------------
+void KMMessage::setCharset(const QCString& bStr)
+{
+ kdWarning( type() != DwMime::kTypeText )
+ << "KMMessage::setCharset(): trying to set a charset for a non-textual mimetype." << endl
+ << "Fix this caller:" << endl
+ << "====================================================================" << endl
+ << kdBacktrace( 5 ) << endl
+ << "====================================================================" << endl;
+ QCString aStr = bStr;
+ KPIM::kAsciiToLower( aStr.data() );
+ DwMediaType &mType = dwContentType();
+ mType.Parse();
+ 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);
+ }
+ else
+ mType.SetModified();
+ param->SetValue(DwString(aStr));
+ mType.Assemble();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessage::setStatus(const KMMsgStatus aStatus, int idx)
+{
+ if (mStatus == aStatus)
+ return;
+ KMMsgBase::setStatus(aStatus, idx);
+}
+
+void KMMessage::setEncryptionState(const KMMsgEncryptionState s, int idx)
+{
+ if( mEncryptionState == s )
+ return;
+ mEncryptionState = s;
+ mDirty = true;
+ KMMsgBase::setEncryptionState(s, idx);
+}
+
+void KMMessage::setSignatureState(KMMsgSignatureState s, int idx)
+{
+ if( mSignatureState == s )
+ return;
+ mSignatureState = s;
+ mDirty = true;
+ KMMsgBase::setSignatureState(s, idx);
+}
+
+void KMMessage::setMDNSentState( KMMsgMDNSentState status, int idx ) {
+ if ( mMDNSentState == status )
+ return;
+ if ( status == 0 )
+ status = KMMsgMDNStateUnknown;
+ mMDNSentState = status;
+ mDirty = true;
+ KMMsgBase::setMDNSentState( status, idx );
+}
+
+//-----------------------------------------------------------------------------
+void KMMessage::link( const KMMessage *aMsg, KMMsgStatus aStatus )
+{
+ Q_ASSERT( aStatus == KMMsgStatusReplied
+ || aStatus == KMMsgStatusForwarded
+ || aStatus == KMMsgStatusDeleted );
+
+ QString message = headerField( "X-KMail-Link-Message" );
+ if ( !message.isEmpty() )
+ message += ',';
+ QString type = headerField( "X-KMail-Link-Type" );
+ if ( !type.isEmpty() )
+ type += ',';
+
+ message += QString::number( aMsg->getMsgSerNum() );
+ if ( aStatus == KMMsgStatusReplied )
+ type += "reply";
+ else if ( aStatus == KMMsgStatusForwarded )
+ type += "forward";
+ else if ( aStatus == KMMsgStatusDeleted )
+ type += "deleted";
+
+ setHeaderField( "X-KMail-Link-Message", message );
+ setHeaderField( "X-KMail-Link-Type", type );
+}
+
+//-----------------------------------------------------------------------------
+void KMMessage::getLink(int n, ulong *retMsgSerNum, KMMsgStatus *retStatus) const
+{
+ *retMsgSerNum = 0;
+ *retStatus = KMMsgStatusUnknown;
+
+ QString message = headerField("X-KMail-Link-Message");
+ QString type = headerField("X-KMail-Link-Type");
+ message = message.section(',', n, n);
+ type = type.section(',', n, n);
+
+ if ( !message.isEmpty() && !type.isEmpty() ) {
+ *retMsgSerNum = message.toULong();
+ if ( type == "reply" )
+ *retStatus = KMMsgStatusReplied;
+ else if ( type == "forward" )
+ *retStatus = KMMsgStatusForwarded;
+ else if ( type == "deleted" )
+ *retStatus = KMMsgStatusDeleted;
+ }
+}
+
+//-----------------------------------------------------------------------------
+DwBodyPart* KMMessage::findDwBodyPart( DwBodyPart* part, const QString & partSpecifier )
+{
+ if ( !part ) return 0;
+ DwBodyPart* current;
+
+ if ( part->partId() == partSpecifier )
+ return part;
+
+ // multipart
+ if ( part->hasHeaders() &&
+ part->Headers().HasContentType() &&
+ part->Body().FirstBodyPart() &&
+ (DwMime::kTypeMultipart == part->Headers().ContentType().Type() ) &&
+ (current = findDwBodyPart( part->Body().FirstBodyPart(), partSpecifier )) )
+ {
+ return current;
+ }
+
+ // encapsulated message
+ if ( part->Body().Message() &&
+ part->Body().Message()->Body().FirstBodyPart() &&
+ (current = findDwBodyPart( part->Body().Message()->Body().FirstBodyPart(),
+ partSpecifier )) )
+ {
+ return current;
+ }
+
+ // next part
+ return findDwBodyPart( part->Next(), partSpecifier );
+}
+
+//-----------------------------------------------------------------------------
+void KMMessage::updateBodyPart(const QString partSpecifier, const QByteArray & data)
+{
+ if ( !data.data() || !data.size() )
+ return;
+
+ DwString content( data.data(), data.size() );
+ if ( numBodyParts() > 0 &&
+ partSpecifier != "0" &&
+ partSpecifier != "TEXT" )
+ {
+ QString specifier = partSpecifier;
+ if ( partSpecifier.endsWith(".HEADER") ||
+ partSpecifier.endsWith(".MIME") ) {
+ // get the parent bodypart
+ specifier = partSpecifier.section( '.', 0, -2 );
+ }
+
+ // search for the bodypart
+ mLastUpdated = findDwBodyPart( getFirstDwBodyPart(), specifier );
+ kdDebug(5006) << "KMMessage::updateBodyPart " << specifier << endl;
+ if (!mLastUpdated)
+ {
+ kdWarning(5006) << "KMMessage::updateBodyPart - can not find part "
+ << specifier << endl;
+ return;
+ }
+ if ( partSpecifier.endsWith(".MIME") )
+ {
+ // update headers
+ // get rid of EOL
+ content.resize( QMAX( content.length(), 2 ) - 2 );
+ // we have to delete the fields first as they might have been created by
+ // an earlier call to DwHeaders::FieldBody
+ mLastUpdated->Headers().DeleteAllFields();
+ mLastUpdated->Headers().FromString( content );
+ mLastUpdated->Headers().Parse();
+ } else if ( partSpecifier.endsWith(".HEADER") )
+ {
+ // update header of embedded message
+ mLastUpdated->Body().Message()->Headers().FromString( content );
+ mLastUpdated->Body().Message()->Headers().Parse();
+ } else {
+ // update body
+ mLastUpdated->Body().FromString( content );
+ QString parentSpec = partSpecifier.section( '.', 0, -2 );
+ if ( !parentSpec.isEmpty() )
+ {
+ DwBodyPart* parent = findDwBodyPart( getFirstDwBodyPart(), parentSpec );
+ if ( parent && parent->hasHeaders() && parent->Headers().HasContentType() )
+ {
+ const DwMediaType& contentType = parent->Headers().ContentType();
+ if ( contentType.Type() == DwMime::kTypeMessage &&
+ contentType.Subtype() == DwMime::kSubtypeRfc822 )
+ {
+ // an embedded message that is not multipart
+ // update this directly
+ parent->Body().Message()->Body().FromString( content );
+ }
+ }
+ }
+ }
+
+ } else
+ {
+ // update text-only messages
+ if ( partSpecifier == "TEXT" )
+ deleteBodyParts(); // delete empty parts first
+ mMsg->Body().FromString( content );
+ mMsg->Body().Parse();
+ }
+ mNeedsAssembly = true;
+ if (! partSpecifier.endsWith(".HEADER") )
+ {
+ // notify observers
+ notify();
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMMessage::updateAttachmentState( DwBodyPart* part )
+{
+ if ( !part )
+ part = getFirstDwBodyPart();
+
+ if ( !part )
+ {
+ // kdDebug(5006) << "updateAttachmentState - no part!" << endl;
+ setStatus( KMMsgStatusHasNoAttach );
+ return;
+ }
+
+ bool filenameEmpty = true;
+ if ( part->hasHeaders() ) {
+ if ( part->Headers().HasContentDisposition() ) {
+ DwDispositionType cd = part->Headers().ContentDisposition();
+ filenameEmpty = cd.Filename().empty();
+ if ( filenameEmpty ) {
+ // let's try if it is rfc 2231 encoded which mimelib can't handle
+ filenameEmpty = KMMsgBase::decodeRFC2231String( KMMsgBase::extractRFC2231HeaderField( cd.AsString().c_str(), "filename" ) ).isEmpty();
+ }
+ }
+ }
+
+ if ( part->hasHeaders() &&
+ ( ( part->Headers().HasContentDisposition() &&
+ !part->Headers().ContentDisposition().Filename().empty() ) ||
+ ( part->Headers().HasContentType() &&
+ !filenameEmpty ) ) )
+ {
+ // now blacklist certain ContentTypes
+ if ( !part->Headers().HasContentType() ||
+ ( part->Headers().HasContentType() &&
+ part->Headers().ContentType().Subtype() != DwMime::kSubtypePgpSignature &&
+ part->Headers().ContentType().Subtype() != DwMime::kSubtypePkcs7Signature ) )
+ {
+ setStatus( KMMsgStatusHasAttach );
+ }
+ return;
+ }
+
+ // multipart
+ if ( part->hasHeaders() &&
+ part->Headers().HasContentType() &&
+ part->Body().FirstBodyPart() &&
+ (DwMime::kTypeMultipart == part->Headers().ContentType().Type() ) )
+ {
+ updateAttachmentState( part->Body().FirstBodyPart() );
+ }
+
+ // encapsulated message
+ if ( part->Body().Message() &&
+ part->Body().Message()->Body().FirstBodyPart() )
+ {
+ updateAttachmentState( part->Body().Message()->Body().FirstBodyPart() );
+ }
+
+ // next part
+ if ( part->Next() )
+ updateAttachmentState( part->Next() );
+ else if ( attachmentState() == KMMsgAttachmentUnknown )
+ setStatus( KMMsgStatusHasNoAttach );
+}
+
+void KMMessage::setBodyFromUnicode( const QString & str ) {
+ QCString encoding = KMMsgBase::autoDetectCharset( charset(), KMMessage::preferredCharsets(), str );
+ if ( encoding.isEmpty() )
+ encoding = "utf-8";
+ const QTextCodec * codec = KMMsgBase::codecForName( encoding );
+ assert( codec );
+ QValueList<int> dummy;
+ setCharset( encoding );
+ setBodyAndGuessCte( codec->fromUnicode( str ), dummy, false /* no 8bit */ );
+}
+
+const QTextCodec * KMMessage::codec() const {
+ const QTextCodec * c = mOverrideCodec;
+ if ( !c )
+ // no override-codec set for this message, try the CT charset parameter:
+ c = KMMsgBase::codecForName( charset() );
+ if ( !c ) {
+ // Ok, no override and nothing in the message, let's use the fallback
+ // the user configured
+ c = KMMsgBase::codecForName( GlobalSettings::self()->fallbackCharacterEncoding().latin1() );
+ }
+ if ( !c )
+ // no charset means us-ascii (RFC 2045), so using local encoding should
+ // be okay
+ c = kmkernel->networkCodec();
+ assert( c );
+ return c;
+}
+
+QString KMMessage::bodyToUnicode(const QTextCodec* codec) const {
+ if ( !codec )
+ // No codec was given, so try the charset in the mail
+ codec = this->codec();
+ assert( codec );
+
+ return codec->toUnicode( bodyDecoded() );
+}
+
+//-----------------------------------------------------------------------------
+QCString KMMessage::mboxMessageSeparator()
+{
+ QCString str( KPIM::getFirstEmailAddress( rawHeaderField("From") ) );
+ if ( str.isEmpty() )
+ str = "unknown@unknown.invalid";
+ QCString dateStr( dateShortStr() );
+ if ( dateStr.isEmpty() ) {
+ time_t t = ::time( 0 );
+ dateStr = ctime( &t );
+ const int len = dateStr.length();
+ if ( dateStr[len-1] == '\n' )
+ dateStr.truncate( len - 1 );
+ }
+ return "From " + str + " " + dateStr + "\n";
+}
+
+void KMMessage::deleteWhenUnused()
+{
+ sPendingDeletes << this;
+}
diff --git a/kmail/kmmessage.h b/kmail/kmmessage.h
new file mode 100644
index 00000000..a19c9d51
--- /dev/null
+++ b/kmail/kmmessage.h
@@ -0,0 +1,916 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+/* kmmessage.h: Mime Message Class
+ *
+ */
+#ifndef kmmessage_h
+#define kmmessage_h
+
+/** @file This file defines Mime Message classes. */
+
+// for large file support
+#include <config.h>
+#include <sys/types.h>
+
+#include <mimelib/string.h>
+#include "kmmsgbase.h"
+#include "isubject.h"
+
+#include <kmime_mdn.h>
+
+#include<libemailfunctions/email.h>
+
+template <typename T>
+class QValueList;
+
+class QStringList;
+class QString;
+class QTextCodec;
+class QStrList;
+
+class KMFolder;
+class KMFolderIndex;
+class DwMessage;
+class KMMessagePart;
+class KMMsgInfo;
+class KMHeaders;
+class KMForwardDigestCommand;
+
+namespace KMime {
+ class CharFreq;
+ namespace Types {
+ class AddrSpec;
+ class Address;
+ typedef QValueList<Address> AddressList;
+ typedef QValueList<AddrSpec> AddrSpecList;
+ }
+}
+
+namespace KMail {
+ class HeaderStrategy;
+}
+
+class DwBodyPart;
+class DwMediaType;
+class DwHeaders;
+
+class partNode;
+
+namespace KMail {
+ enum ReplyStrategy { ReplySmart = 0,
+ ReplyAuthor,
+ ReplyList,
+ ReplyAll,
+ ReplyNone };
+}
+
+/** This is a Mime Message. */
+class KMMessage: public KMMsgBase, public KMail::ISubject
+{
+ friend class ::KMForwardDigestCommand; // needed for MIME Digest forward
+
+public:
+ // promote some of KMMsgBase's methods to public:
+ using KMMsgBase::parent;
+ using KMMsgBase::setParent;
+ using KMMsgBase::enableUndo; // KMFolder
+ using KMMsgBase::setEnableUndo; // dto.
+ using KMMsgBase::isRead; // dto.
+ using KMMsgBase::isUnread; // dto.
+ using KMMsgBase::isNew; // dto.
+ using KMMsgBase::isOld;
+ using KMMsgBase::isWatched;
+ using KMMsgBase::isIgnored;
+ using KMMsgBase::setEncryptionStateChar; // KMAcct*
+ using KMMsgBase::setSignatureStateChar; // dto.
+
+ /** Straight forward initialization. */
+ KMMessage(KMFolder* parent=0);
+
+ /** Constructor from a DwMessage. KMMessage takes possession of the
+ DwMessage, so don't dare to delete it.
+ */
+ KMMessage(DwMessage*);
+
+ /** Copy constructor. Does *not* automatically load the message. */
+ KMMessage(KMMsgInfo& msgInfo);
+
+ /** Copy constructor. */
+ KMMessage( const KMMessage& other );
+
+#if 0 // currently unused
+ /** Assignment operator. */
+ const KMMessage& operator=( const KMMessage& other ) {
+ if( &other == this )
+ return *this;
+ assign( other );
+ return *this;
+ }
+#endif
+
+ /** Destructor. */
+ virtual ~KMMessage();
+
+ /** Get KMMsgBase for this object */
+ KMMsgBase & toMsgBase() { return *this; }
+ const KMMsgBase & toMsgBase() const { return *this; }
+
+ /** Returns TRUE if object is a real message (not KMMsgInfo or KMMsgBase) */
+ bool isMessage() const;
+
+ /** @return whether the priority: or x-priority headers indicate
+ that this message should be considered urgent
+ **/
+ bool isUrgent() const;
+
+ /** Specifies an unencrypted copy of this message to be stored
+ in a separate member variable to allow saving messages in
+ unencrypted form that were sent in encrypted form.
+ NOTE: Ownership of @p unencrypted transfers to this KMMessage,
+ and it will be deleted in the d'tor.
+ */
+ void setUnencryptedMsg( KMMessage* unencrypted );
+
+ /** Returns TRUE if the message contains an unencrypted copy of itself. */
+ bool hasUnencryptedMsg() const { return 0 != mUnencryptedMsg; }
+
+ /** Returns an unencrypted copy of this message or 0 if none exists. */
+ KMMessage* unencryptedMsg() const { return mUnencryptedMsg; }
+
+ /** Returns an unencrypted copy of this message or 0 if none exists.
+ \note This function removes the internal unencrypted message pointer
+ from the message: the process calling takeUnencryptedMsg() must
+ delete the returned pointer when no longer needed.
+ */
+ KMMessage* takeUnencryptedMsg()
+ {
+ KMMessage* ret = mUnencryptedMsg;
+ mUnencryptedMsg = 0;
+ return ret;
+ }
+
+ /** Mark the message as deleted */
+ void del() { setStatus(KMMsgStatusDeleted); }
+
+ /** Undelete the message. Same as touch */
+ void undel() { setStatus(KMMsgStatusOld); }
+
+ /** Touch the message - mark it as read */
+ void touch() { setStatus(KMMsgStatusOld); }
+
+ /** Create a new message that is a reply to this message, filling all
+ 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,
+ QString selection=QString::null, bool noQuote=false,
+ bool allowDecryption=true, bool selectionIsBody=false,
+ const QString &tmpl = QString::null );
+
+ /** Create a new message that is a redirect to this message, filling all
+ required header fields with the proper values. The returned message
+ is not stored in any folder. Marks this message as replied.
+ Redirects differ from forwards so they are forwarded to some other
+ user, mail is not changed and the reply-to field is set to
+ the email address of the original sender
+ */
+ KMMessage* createRedirect( const QString &toStr );
+
+ /** Create the forwarded body for the message. */
+ QCString createForwardBody();
+
+ /** Create a new message that is a forward of this message, filling all
+ required header fields with the proper values. The returned message
+ is not stored in any folder. Marks this message as forwarded. */
+ KMMessage* createForward( const QString &tmpl = QString::null );
+
+ /** Create a new message that is a delivery receipt of this message,
+ filling required header fileds with the proper values. The
+ returned message is not stored in any folder. */
+ KMMessage* createDeliveryReceipt() const;
+
+ /** Create a new message that is a MDN for this message, filling all
+ required fields with proper values. The returned message is not
+ stored in any folder.
+
+ @param a Use AutomaticAction for filtering and ManualAction for
+ user-induced events.
+ @param d See docs for KMime::MDN::DispositionType
+ @param m See docs for KMime::MDN::DispositionModifier
+ @param allowGUI Set to true if this method is allowed to ask the
+ user questions
+
+ @return The notification message or 0, if none should be sent.
+ **/
+ KMMessage* createMDN( KMime::MDN::ActionMode a,
+ KMime::MDN::DispositionType d,
+ bool allowGUI=false,
+ QValueList<KMime::MDN::DispositionModifier> m=QValueList<KMime::MDN::DispositionModifier>() );
+
+ /** Remove all headers but the content description ones, and those in the white list. */
+ void sanitizeHeaders( const QStringList& whiteList = QStringList() );
+
+ /** Parse the string and create this message from it. */
+ void fromDwString(const DwString& str, bool setStatus=false);
+ void fromString(const QCString& str, bool setStatus=false);
+ void fromByteArray(const QByteArray & ba, bool setStatus=false);
+
+ /** Return the entire message contents in the DwString. This function
+ is *fast* even for large message since it does *not* involve a
+ string copy.
+ */
+ const DwString& asDwString() const;
+ const DwMessage *asDwMessage();
+
+ /** Return the entire message contents as a string. This function is
+ slow for large message since it involves a string copy. If you
+ need the string representation only for a short time
+ (i.e. without the chance of calling any function in the
+ underlying mimelib, then you should use the asDwString function.
+ @see asDwString
+ */
+ QCString asString() const;
+
+ /**
+ * Return the message contents with the headers that should not be
+ * sent stripped off.
+ */
+ QByteArray asSendableString() const;
+
+ /**
+ * Return the message header with the headers that should not be
+ * sent stripped off.
+ */
+ QCString headerAsSendableString() const;
+
+ /**
+ * Remove all private header fields: *Status: and X-KMail-*
+ **/
+ void removePrivateHeaderFields();
+
+ /** Return reference to Content-Type header for direct manipulation. */
+ DwMediaType& dwContentType();
+
+ /** Return header as string. */
+ QString headerAsString() const;
+
+ /** Returns a decoded body part string to be further processed
+ by function asQuotedString().
+ THIS FUNCTION WILL BE REPLACED ONCE KMime IS FULLY INTEGRATED
+ (khz, June 05 2002)*/
+ void parseTextStringFromDwPart( partNode * root,
+ QCString& parsedString,
+ const QTextCodec*& codec,
+ bool& isHTML ) const;
+
+ /** Initialize header fields. Should be called on new messages
+ if they are not set manually. E.g. before composing. Calling
+ of setAutomaticFields(), see below, is still required. */
+ void initHeader(uint identity=0);
+
+ /** Initialize headers fields according to the identity and the transport
+ header of the given original message */
+ void initFromMessage(const KMMessage *msg, bool idHeaders = true);
+
+ /** @return the UOID of the identity for this message.
+ Searches the "x-kmail-identity" header and if that fails,
+ searches with KPIM::IdentityManager::identityForAddress()
+ and if that fails queries the KMMsgBase::parent() folder for a default.
+ **/
+ uint identityUoid() const;
+
+ /** Set the from, to, cc, bcc, encrytion etc headers as specified in the
+ * given identity. */
+ void applyIdentity( uint id );
+
+ /** Removes empty fields from the header, e.g. an empty Cc: or Bcc:
+ field. */
+ void cleanupHeader();
+
+ /** Set fields that are either automatically set (Message-id)
+ or that do not change from one message to another (MIME-Version).
+ Call this method before sending *after* all changes to the message
+ are done because this method does things different if there are
+ attachments / multiple body parts. */
+ void setAutomaticFields(bool isMultipart=false);
+
+ /** Get or set the 'Date' header field */
+ QString dateStr() const;
+ /** Returns the message date in asctime format or an empty string if the
+ message lacks a Date header. */
+ QCString dateShortStr() const;
+ QString dateIsoStr() const;
+ time_t date() const;
+ void setDate(const QCString& str);
+ void setDate(time_t aUnixTime);
+
+ /** Set the 'Date' header field to the current date. */
+ void setDateToday();
+
+ /** Get or set the 'To' header field */
+ QString to() const;
+ void setTo(const QString& aStr);
+ QString toStrip() const;
+
+ /** Get or set the 'ReplyTo' header field */
+ QString replyTo() const;
+ void setReplyTo( const QString &aStr );
+ void setReplyTo(KMMessage*);
+
+ /** Get or set the 'Cc' header field */
+ QString cc() const;
+ void setCc( const QString &aStr );
+ QString ccStrip() const;
+
+ /** Get or set the 'Bcc' header field */
+ QString bcc() const;
+ void setBcc( const QString &aStr );
+
+ /** Get or set the 'Fcc' header field */
+ QString fcc() const;
+ void setFcc( const QString &aStr );
+
+ /** Get or set the 'Drafts' folder */
+ QString drafts() const { return mDrafts; }
+ void setDrafts( const QString &aStr );
+
+ /** Get or set the 'Templates' folder */
+ QString templates() const { return mTemplates; }
+ void setTemplates( const QString &aStr );
+
+ /** Get or set the 'From' header field */
+ QString from() const;
+ void setFrom(const QString& aStr);
+ QString fromStrip() const;
+
+ /** @return The addr-spec of either the Sender: (if one is given) or
+ * the first addr-spec in From: */
+ QString sender() const;
+
+ /** Get or set the 'Who' header field. The actual field that is
+ returned depends on the contents of the owning folders whoField().
+ Usually this is 'From', but it can also contain 'To'. */
+ QString who() const;
+
+ /** Get or set the 'Subject' header field */
+ QString subject() const;
+ void setSubject(const QString& aStr);
+
+ /** Calculate strippedSubject */
+ void initStrippedSubjectMD5() {};
+
+ /** Get or set the 'X-Mark' header field */
+ QString xmark() const;
+ void setXMark(const QString& aStr);
+
+ /** Get or set the 'In-Reply-To' header field */
+ QString replyToId() const;
+ void setReplyToId(const QString& aStr);
+ QString replyToIdMD5() const;
+
+ /** Get the second to last id from the References header
+ field. If outgoing messages are not kept in the same
+ folder as incoming ones, this will be a good place to
+ thread the message beneath.
+ bob <- second to last reference points to this
+ |_kmailuser <- not in our folder, but Outbox
+ |_bob <- In-Reply-To points to our mail above
+
+ Thread like this:
+ bob
+ |_bob
+
+ using replyToAuxIdMD5
+ */
+ QString replyToAuxIdMD5() const;
+
+ /**
+ Get a hash of the subject with all prefixes such as Re: removed.
+ Used for threading.
+ */
+ QString strippedSubjectMD5() const;
+
+ /**
+ Validate a list of email addresses, and also allow
+ aliases and distribution lists to be expanded
+ before validation.
+ @return Enum to describe the error.
+ @return brokenAddress the address that was faulty.
+ FIXME: this should be in libemailfucntions but that
+ requires moving expandAliases and all that
+ it brings
+ */
+ static KPIM::EmailParseResult isValidEmailAddressList( const QString& aStr,
+ QString& brokenAddress );
+
+ /**
+ Get a hash of the subject.
+ Used for threading.
+ */
+ QString subjectMD5() const;
+
+ /** Is the subject prefixed by Re: or similar? */
+ bool subjectIsPrefixed() const;
+
+ /** Get or set the 'Message-Id' header field */
+ QString msgId() const;
+ void setMsgId(const QString& aStr);
+ QString msgIdMD5() const;
+
+ /** Get or set the references for this message */
+ QString references() const;
+ void setReferences(const QCString& aStr);
+
+ /** Returns the message ID, useful for followups */
+ QCString id() const;
+
+ /** Sets the message serial number. If defaulted to zero, the
+ serial number will be assigned using the dictionary. Note that
+ unless it is explicitely set the serial number will remain 0
+ as long as the mail is not in a folder. */
+ void setMsgSerNum(unsigned long newMsgSerNum = 0);
+
+ /** Returns the value of a header field with the given name. If multiple
+ header fields with the given name might exist then you should use
+ headerFields() instead.
+ */
+ QString headerField(const QCString& name) const;
+
+ enum HeaderFieldType { Unstructured, Structured, Address };
+
+ /** Set the header field with the given name to the given value.
+ If prepend is set to true, the header is inserted at the beginning
+ and does not overwrite an existing header field with the same name.
+ */
+ void setHeaderField( const QCString& name, const QString& value,
+ HeaderFieldType type = Unstructured,
+ bool prepend = false );
+
+ /** Returns a list of the values of all header fields with the given name. */
+ QStringList headerFields( const QCString& name ) const;
+
+ /** Returns the raw value of a header field with the given name. If multiple
+ header fields with the given name might exist then you should use
+ rawHeaderFields() instead.
+ */
+ QCString rawHeaderField( const QCString & name ) const;
+
+ /** Returns a list of the raw values of all header fields with the given
+ name.
+ */
+ QValueList<QCString> rawHeaderFields( const QCString & field ) const;
+
+ /** Splits the given address list into separate addresses. */
+ static KMime::Types::AddressList splitAddrField( const QCString & str );
+
+ /** Returns header address list as string list.
+ Valid for the following fields: To, Bcc, Cc, ReplyTo, ResentBcc,
+ ResentCc, ResentReplyTo, ResentTo */
+ KMime::Types::AddressList headerAddrField(const QCString& name) const;
+ KMime::Types::AddrSpecList extractAddrSpecs( const QCString & headerNames ) const;
+
+ /** Remove header field with given name */
+ void removeHeaderField(const QCString& name);
+
+ /** Remove all header fields with given name */
+ void removeHeaderFields(const QCString& name);
+
+ /** Get or set the 'Content-Type' header field
+ The member functions that involve enumerated types (ints)
+ will work only for well-known types or subtypes. */
+ QCString typeStr() const;
+ int type() const;
+ void setTypeStr(const QCString& aStr);
+ void setType(int aType);
+ /** Subtype */
+ QCString subtypeStr() const;
+ int subtype() const;
+ void setSubtypeStr(const QCString& aStr);
+ void setSubtype(int aSubtype);
+ /** add or change a parameter of a DwMediaType field */
+ static void setDwMediaTypeParam( DwMediaType &mType,
+ const QCString& attr,
+ const QCString& val );
+ /** add or change a parameter of the Content-Type field */
+ void setContentTypeParam(const QCString& attr, const QCString& val);
+
+ /** get the DwHeaders
+ (make sure to call setNeedsAssembly() function after directly
+ modyfying internal data like the headers) */
+ DwHeaders& headers() const;
+
+ /** tell the message that internal data were changed
+ (must be called after directly modifying message structures
+ e.g. when like changing header information by accessing
+ 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. */
+ QCString contentTransferEncodingStr() const;
+ int contentTransferEncoding() const;
+ void setContentTransferEncodingStr(const QCString& aStr);
+ void setContentTransferEncoding(int aCte);
+
+ /** Cte is short for ContentTransferEncoding.
+ These functions are an alternative to the ones with longer names. */
+ QCString cteStr() const { return contentTransferEncodingStr(); }
+ int cte() const { return contentTransferEncoding(); }
+ void setCteStr(const QCString& aStr) { setContentTransferEncodingStr(aStr); }
+ void setCte(int aCte) { setContentTransferEncoding(aCte); }
+
+ /** Sets this body part's content to @p str. @p str is subject to
+ automatic charset and CTE detection.
+ **/
+ void setBodyFromUnicode( const QString & str );
+
+ /** Returns the body part decoded to unicode.
+ **/
+ QString bodyToUnicode(const QTextCodec* codec=0) const;
+
+ /** Get the message body. Does not decode the body. */
+ QCString body() const;
+
+ /** Set the message body. Does not encode the body. */
+ void setBody(const QCString& aStr);
+ void setBody(const DwString& aStr);
+ void setBody(const char* aStr); // avoid ambiguous calls
+
+ /** Hack to enable structured body parts to be set as flat text... */
+ void setMultiPartBody( const QCString & 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 QCString& aStr);
+ void setBodyEncodedBinary(const QByteArray& aStr);
+
+ /** Returns a list of content-transfer-encodings that can be used with
+ the given result of the character frequency analysis of a message or
+ message part under the given restrictions. */
+ static QValueList<int> determineAllowedCtes( const KMime::CharFreq& cf,
+ bool allow8Bit,
+ bool willBeSigned );
+
+ /** 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
+ */
+ void setBodyAndGuessCte( const QByteArray& aBuf,
+ QValueList<int>& allowedCte,
+ bool allow8Bit = false,
+ bool willBeSigned = false );
+ void setBodyAndGuessCte( const QCString& aBuf,
+ QValueList<int>& allowedCte,
+ bool allow8Bit = false,
+ bool willBeSigned = false );
+
+ /** Returns a decoded version of the body from the current content transfer
+ encoding. The first method returns a null terminated string, the second
+ method is meant for binary data, not null is appended */
+ QCString bodyDecoded() const;
+ QByteArray bodyDecodedBinary() const;
+
+ /** Number of body parts the message has. This is one for plain messages
+ without any attachment. */
+ int numBodyParts() const;
+
+ /** Return the first DwBodyPart matching a given Content-Type
+ or zero, if no found. */
+ DwBodyPart * findDwBodyPart( int type, int subtype ) const;
+
+ /** Return the first DwBodyPart matching a given Content-Type
+ or zero, if no found. */
+ DwBodyPart * findDwBodyPart( const QCString& type, const QCString& subtype ) const;
+
+ /** Return the first DwBodyPart matching a given partSpecifier
+ or zero, if no found. */
+ DwBodyPart* findDwBodyPart( DwBodyPart* part, const QString & partSpecifier );
+
+ /** Get the DwBodyPart at position in aIdx. Indexing starts at 0.
+ If there is no body part at that index, return value will be zero. */
+ DwBodyPart * dwBodyPart( int aIdx ) const;
+
+ /** Get the number of the given DwBodyPart.
+ If no body part is given, return value will be -1. */
+ int partNumber( DwBodyPart * aDwBodyPart ) const;
+
+ /** Get the 1st DwBodyPart.
+ If there is no body part, return value will be zero. */
+ DwBodyPart * getFirstDwBodyPart() const;
+ DwMessage * getTopLevelPart() const { return mMsg; }
+
+ /** Fill the KMMessagePart structure for a given DwBodyPart.
+ If withBody is false the body of the KMMessagePart will be left
+ empty and only the headers of the part will be filled in*/
+ static void bodyPart(DwBodyPart* aDwBodyPart, KMMessagePart* aPart,
+ bool withBody = true );
+
+ /** Get the body part at position in aIdx. Indexing starts at 0.
+ If there is no body part at that index, aPart will have its
+ attributes set to empty values. */
+ void bodyPart(int aIdx, KMMessagePart* aPart) const;
+
+ /** Compose a DwBodyPart (needed for adding a part to the message). */
+ DwBodyPart* createDWBodyPart(const KMMessagePart* aPart);
+
+ /** Append a DwBodyPart to the message. */
+ void addDwBodyPart(DwBodyPart * aDwPart);
+
+ /** Append a body part to the message. */
+ void addBodyPart(const KMMessagePart* aPart);
+
+ /** Delete all body parts. */
+ void deleteBodyParts();
+
+ /** Set "Status" and "X-Status" fields of the message from the
+ * internal message status. */
+ void setStatusFields();
+
+ /** Generates the Message-Id. It uses either the Message-Id suffix
+ * defined by the user or the given email address as suffix. The address
+ * must be given as addr-spec as defined in RFC 2822.
+ */
+ static QString generateMessageId( const QString& addr );
+
+ /** Convert '<' into "&lt;" resp. '>' into "&gt;" in order to
+ * prevent their interpretation by KHTML.
+ * Does *not* use the Qt replace function but runs a very fast C code
+ * the same way as lf2crlf() does.
+ */
+ static QCString html2source( const QCString & src );
+
+ /** Encodes an email address as mailto URL
+ */
+ static QString encodeMailtoUrl( const QString& str );
+
+ /** Decodes a mailto URL
+ */
+ static QString decodeMailtoUrl( const QString& url );
+
+ /** This function generates a displayable string from a list of email
+ addresses.
+ Input : mailbox-list
+ Output: comma separated list of display name resp. comment resp.
+ address
+ */
+ static QCString stripEmailAddr(const QCString& emailAddr);
+
+ /** Does the same as the above function. Shouldn't be used.
+ */
+ static QString stripEmailAddr(const QString& emailAddr);
+
+ /** Quotes the following characters which have a special meaning in HTML:
+ * '<' '>' '&' '"'. Additionally '\\n' is converted to "<br />" if
+ * @p removeLineBreaks is false. If @p removeLineBreaks is true, then
+ * '\\n' is removed. Last but not least '\\r' is removed.
+ */
+ static QString quoteHtmlChars( const QString& str,
+ bool removeLineBreaks = false );
+
+ /** Converts the email address(es) to (a) nice HTML mailto: anchor(s).
+ * If stripped is TRUE then the visible part of the anchor contains
+ * only the name part and not the given emailAddr.
+ */
+ static QString emailAddrAsAnchor(const QString& emailAddr,
+ bool stripped=true, const QString& cssStyle = QString::null, bool link = true);
+
+ /** Strips an address from an address list. This is for example used
+ when replying to all.
+ */
+ static QStringList stripAddressFromAddressList( const QString& address,
+ const QStringList& addresses );
+
+ /** Strips all the user's addresses from an address list. This is used
+ when replying.
+ */
+ static QStringList stripMyAddressesFromAddressList( const QStringList& list );
+
+ /** Returns true if the given address is contained in the given address list.
+ */
+ static bool addressIsInAddressList( const QString& address,
+ const QStringList& addresses );
+
+ /** Expands aliases (distribution lists and nick names) and appends a
+ domain part to all email addresses which are missing the domain part.
+ */
+ static QString expandAliases( const QString& recipients );
+
+ /** Uses the hostname as domain part and tries to determine the real name
+ from the entries in the password file.
+ */
+ static QString guessEmailAddressFromLoginName( const QString& userName );
+
+ /**
+ * Given argument msg add quoting characters and relayout for max width maxLength
+ * @param msg the string which it to be quoted
+ * @param maxLineLength reformat text to be this amount of columns at maximum, adding
+ * linefeeds at word boundaries to make it fit.
+ */
+ static QString smartQuote( const QString &msg, int maxLineLength );
+
+ /** Get the default message charset.*/
+ static QCString defaultCharset();
+
+ /** Get a list of preferred message charsets.*/
+ static const QStringList &preferredCharsets();
+
+ /** Replaces every occurrence of "${foo}" in @p s with headerField("foo") */
+ QString replaceHeadersInString( const QString & s ) const;
+
+ /** Get the message charset.*/
+ QCString charset() const;
+
+ /** Set the message charset. */
+ void setCharset(const QCString& aStr);
+
+ /** Get a QTextCodec suitable for this message part */
+ const QTextCodec * codec() const;
+
+ /** Set the charset the user selected for the message to display */
+ void setOverrideCodec( const QTextCodec* codec ) { mOverrideCodec = codec; }
+
+ /** Allow decoding of HTML for quoting */
+ void setDecodeHTML(bool aDecodeHTML)
+ { mDecodeHTML = aDecodeHTML; }
+
+ /** Reads config settings from group "KMMessage" and sets all internal
+ * variables (e.g. indent-prefix, etc.) */
+ static void readConfig();
+
+ /** Creates reference string for reply to messages.
+ * reference = original first reference + original last reference + original msg-id
+ */
+ QCString getRefStr() const;
+
+ /** Get/set offset in mail folder. */
+ off_t folderOffset() const { return mFolderOffset; }
+ void setFolderOffset(off_t offs) { if(mFolderOffset != offs) { mFolderOffset=offs; setDirty(true); } }
+
+ /** Get/set filename in mail folder. */
+ QString fileName() const { return mFileName; }
+ void setFileName(const QString& file) { if(mFileName != file) { mFileName=file; setDirty(true); } }
+
+ /** Get/set size of message in the folder including the whole header in
+ bytes. Can be 0, if the message is not in a folder.
+ The setting of mMsgSize = mMsgLength = sz is needed for popFilter*/
+ size_t msgSize() const { return mMsgSize; }
+ void setMsgSize(size_t sz) { if(mMsgSize != sz) { mMsgSize = sz; setDirty(true); } }
+
+ /** Unlike the above function this works also, if the message is not in a
+ folder */
+ size_t msgLength() const
+ { return (mMsgLength) ? mMsgLength : mMsgSize; }
+ void setMsgLength(size_t sz) { mMsgLength = sz; }
+
+ /** Get/set size on server */
+ size_t msgSizeServer() const;
+ void setMsgSizeServer(size_t sz);
+
+ /** Get/set UID */
+ ulong UID() const;
+ void setUID(ulong uid);
+
+ /** Status of the message. */
+ KMMsgStatus status() const { return mStatus; }
+ /** Set status and mark dirty. */
+ void setStatus(const KMMsgStatus status, int idx = -1);
+ void setStatus(const char* s1, const char* s2=0) { KMMsgBase::setStatus(s1, s2); }
+
+ /** Set encryption status of the message. */
+ void setEncryptionState(const KMMsgEncryptionState, int idx = -1);
+
+ /** Set signature status of the message. */
+ void setSignatureState(const KMMsgSignatureState, int idx = -1);
+
+ void setMDNSentState( KMMsgMDNSentState status, int idx=-1 );
+
+ /** Encryption status of the message. */
+ KMMsgEncryptionState encryptionState() const { return mEncryptionState; }
+
+ /** Signature status of the message. */
+ KMMsgSignatureState signatureState() const { return mSignatureState; }
+
+ KMMsgMDNSentState mdnSentState() const { return mMDNSentState; }
+
+ /** Links this message to @p aMsg, setting link type to @p aStatus. */
+ void link(const KMMessage *aMsg, KMMsgStatus aStatus);
+ /** Returns the information for the Nth link into @p retMsg
+ * and @p retStatus. */
+ void getLink(int n, ulong *retMsgSerNum, KMMsgStatus *retStatus) const;
+
+ /** Convert wildcards into normal string */
+ QString formatString(const QString&) const;
+
+ /** Sets the body of the specified part */
+ void updateBodyPart(const QString partSpecifier, const QByteArray & data);
+
+ /** Returns the last DwBodyPart that was updated */
+ DwBodyPart* lastUpdatedPart() { return mLastUpdated; }
+
+ /** Return true if the complete message is available without referring to the backing store.*/
+ bool isComplete() const { return mComplete; }
+ /** Set if the message is a complete message */
+ void setComplete( bool v ) { mComplete = v; }
+
+ /** Return if the message is ready to be shown */
+ bool readyToShow() const { return mReadyToShow; }
+ /** Set if the message is ready to be shown */
+ void setReadyToShow( bool v ) { mReadyToShow = v; }
+
+ void updateAttachmentState(DwBodyPart * part = 0);
+
+ /** Return, if the message should not be deleted */
+ bool transferInProgress() const;
+ /** Set that the message shall not be deleted because it is still required */
+ void setTransferInProgress(bool value, bool force = false);
+
+ /** Returns an mbox message separator line for this message, i.e. a
+ string of the form
+ "From local@domain.invalid Sat Jun 12 14:00:00 2004\n".
+ */
+ QCString mboxMessageSeparator();
+
+ /** Returns message body with quoting header and indented by the
+ given indentation string. This is suitable for including the message
+ in another message of for replies, forwards. The header string is
+ a template where the following fields are replaced with the
+ corresponding values:
+ <pre>
+ %D: date of this message
+ %S: subject of this message
+ %F: sender (from) of this message
+ %%: a single percent sign
+ </pre>
+ No attachments are handled if includeAttach is false.
+ The signature is stripped if aStripSignature is true and
+ smart quoting is turned on. Signed or encrypted texts
+ get converted to plain text when allowDecryption is true. */
+ QString asQuotedString( const QString & headerStr,
+ const QString & indentStr,
+ const QString & selection=QString::null,
+ bool aStripSignature=true,
+ bool allowDecryption=true) const;
+
+ /** Return the textual content of the message as plain text,
+ converting HTML to plain text if necessary. */
+ QString asPlainText( bool stripSignature, bool allowDecryption ) const;
+
+ /** Get stored cursor position */
+ int getCursorPos() { return mCursorPos; };
+ /** Set cursor position as offset from message start */
+ void setCursorPos(int pos) { mCursorPos = pos; };
+
+ /** Get the KMMsgInfo object that was set with setMsgInfo(). */
+ KMMsgInfo* msgInfo() { return mMsgInfo; }
+ /** Set the KMMsgInfo object corresponding to this message. */
+ void setMsgInfo( KMMsgInfo* msgInfo ) { mMsgInfo = msgInfo; }
+
+ /* This is set in kmreaderwin if a message is being parsed to avoid
+ other parts of kmail (e.g. kmheaders) destroying the message.
+ Parsing can take longer and can be async (in case of gpg mails) */
+ bool isBeingParsed() const { return mIsParsed; }
+ void setIsBeingParsed( bool t ) { mIsParsed = t; }
+
+ /** Delete this message as soon as it no longer in use. */
+ void deleteWhenUnused();
+
+private:
+
+ /** Initialization shared by the ctors. */
+ void init( DwMessage* aMsg = 0 );
+ /** Assign the values of @param other to this message. Used in the copy c'tor. */
+ void assign( const KMMessage& other );
+
+ QString mDrafts;
+ QString mTemplates;
+ mutable DwMessage* mMsg;
+ mutable bool mNeedsAssembly :1;
+ bool mDecodeHTML :1;
+ bool mReadyToShow :1;
+ bool mComplete :1;
+ bool mIsParsed : 1;
+ static const KMail::HeaderStrategy * sHeaderStrategy;
+ static QString sForwardStr;
+ const QTextCodec * mOverrideCodec;
+
+ QString mFileName;
+ off_t mFolderOffset;
+ size_t mMsgSize, mMsgLength;
+ time_t mDate;
+ KMMsgEncryptionState mEncryptionState;
+ KMMsgSignatureState mSignatureState;
+ KMMsgMDNSentState mMDNSentState;
+ KMMessage* mUnencryptedMsg;
+ DwBodyPart* mLastUpdated;
+ int mCursorPos;
+ KMMsgInfo* mMsgInfo; // used to remember the KMMsgInfo object this KMMessage replaced in the KMMsgList
+ static QValueList<KMMessage*> sPendingDeletes;
+};
+
+
+#endif /*kmmessage_h*/
diff --git a/kmail/kmmimeparttree.cpp b/kmail/kmmimeparttree.cpp
new file mode 100644
index 00000000..606b6891
--- /dev/null
+++ b/kmail/kmmimeparttree.cpp
@@ -0,0 +1,395 @@
+/* -*- c++ -*-
+ kmmimeparttree.h A MIME part tree viwer.
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2002-2004 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+
+#include <config.h>
+
+#include "kmmimeparttree.h"
+
+#include "kmreaderwin.h"
+#include "partNode.h"
+#include "kmmsgpart.h"
+#include "kmkernel.h"
+#include "kmcommands.h"
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kfiledialog.h>
+#include <kmessagebox.h>
+#include <kiconloader.h>
+
+#include <qclipboard.h>
+#include <qheader.h>
+#include <qpopupmenu.h>
+#include <qstyle.h>
+#include <kurldrag.h>
+#include <kurl.h>
+
+
+KMMimePartTree::KMMimePartTree( KMReaderWin* readerWin,
+ QWidget* parent,
+ const char* name )
+ : KListView( parent, name ),
+ mReaderWin( readerWin ), mSizeColumn(0)
+{
+ setStyleDependantFrameWidth();
+ addColumn( i18n("Description") );
+ addColumn( i18n("Type") );
+ addColumn( i18n("Encoding") );
+ mSizeColumn = addColumn( i18n("Size") );
+ setColumnAlignment( 3, Qt::AlignRight );
+
+ restoreLayoutIfPresent();
+ connect( this, SIGNAL( clicked( QListViewItem* ) ),
+ this, SLOT( itemClicked( QListViewItem* ) ) );
+ connect( this, SIGNAL( contextMenuRequested( QListViewItem*,
+ const QPoint&, int ) ),
+ this, SLOT( itemRightClicked( QListViewItem*, const QPoint& ) ) );
+ setSelectionMode( QListView::Extended );
+ setRootIsDecorated( false );
+ setAllColumnsShowFocus( true );
+ setShowToolTips( true );
+ setSorting(-1);
+ setDragEnabled( true );
+}
+
+
+static const char configGroup[] = "MimePartTree";
+
+KMMimePartTree::~KMMimePartTree() {
+ saveLayout( KMKernel::config(), configGroup );
+}
+
+
+void KMMimePartTree::restoreLayoutIfPresent() {
+ // first column: soaks up the rest of the space:
+ setColumnWidthMode( 0, Manual );
+ header()->setStretchEnabled( true, 0 );
+ // rest of the columns:
+ if ( KMKernel::config()->hasGroup( configGroup ) ) {
+ // there is a saved layout. use it...
+ restoreLayout( KMKernel::config(), configGroup );
+ // and disable Maximum mode:
+ for ( int i = 1 ; i < 4 ; ++i )
+ setColumnWidthMode( i, Manual );
+ } else {
+ // columns grow with their contents:
+ for ( int i = 1 ; i < 4 ; ++i )
+ setColumnWidthMode( i, Maximum );
+ }
+}
+
+
+void KMMimePartTree::itemClicked( QListViewItem* item )
+{
+ if ( const KMMimePartTreeItem * i = dynamic_cast<KMMimePartTreeItem*>( item ) ) {
+ if( mReaderWin->mRootNode == i->node() )
+ mReaderWin->update( true ); // Force update
+ else
+ mReaderWin->setMsgPart( i->node() );
+ } else
+ kdWarning(5006) << "Item was not a KMMimePartTreeItem!" << endl;
+}
+
+
+void KMMimePartTree::itemRightClicked( QListViewItem* item,
+ const QPoint& point )
+{
+ // TODO: remove this member var?
+ mCurrentContextMenuItem = dynamic_cast<KMMimePartTreeItem*>( item );
+ if ( 0 == mCurrentContextMenuItem ) {
+ kdDebug(5006) << "Item was not a KMMimePartTreeItem!" << endl;
+ }
+ else {
+ kdDebug(5006) << "\n**\n** KMMimePartTree::itemRightClicked() **\n**" << endl;
+
+ QPopupMenu* popup = new QPopupMenu;
+ if ( mCurrentContextMenuItem->node()->nodeId() > 2 &&
+ mCurrentContextMenuItem->node()->typeString() != "Multipart" ) {
+ popup->insertItem( SmallIcon("fileopen"), i18n("to open", "Open"), this, SLOT(slotOpen()) );
+ popup->insertItem( i18n("Open With..."), this, SLOT(slotOpenWith()) );
+ popup->insertItem( i18n("to view something", "View"), this, SLOT(slotView()) );
+ }
+ popup->insertItem( SmallIcon("filesaveas"),i18n( "Save &As..." ), this, SLOT( slotSaveAs() ) );
+ /*
+ * FIXME mkae optional?
+ popup->insertItem( i18n( "Save as &Encoded..." ), this,
+ SLOT( slotSaveAsEncoded() ) );
+ */
+ popup->insertItem( i18n( "Save All Attachments..." ), this,
+ SLOT( slotSaveAll() ) );
+ // edit + delete only for attachments
+ if ( mCurrentContextMenuItem->node()->nodeId() > 2 &&
+ mCurrentContextMenuItem->node()->typeString() != "Multipart" ) {
+ popup->insertItem( SmallIcon("editcopy"), i18n("Copy"), this, SLOT(slotCopy()) );
+ if ( GlobalSettings::self()->allowAttachmentDeletion() )
+ popup->insertItem( SmallIcon("editdelete"), i18n( "Delete Attachment" ),
+ this, SLOT( slotDelete() ) );
+ if ( GlobalSettings::self()->allowAttachmentEditing() )
+ popup->insertItem( SmallIcon( "edit" ), i18n( "Edit Attachment" ),
+ this, SLOT( slotEdit() ) );
+ }
+ if ( mCurrentContextMenuItem->node()->nodeId() > 0 )
+ popup->insertItem( i18n("Properties"), this, SLOT(slotProperties()) );
+ popup->exec( point );
+ delete popup;
+ mCurrentContextMenuItem = 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMMimePartTree::slotSaveAs()
+{
+ saveSelectedBodyParts( false );
+}
+
+//-----------------------------------------------------------------------------
+void KMMimePartTree::slotSaveAsEncoded()
+{
+ saveSelectedBodyParts( true );
+}
+
+//-----------------------------------------------------------------------------
+void KMMimePartTree::saveSelectedBodyParts( bool encoded )
+{
+ QPtrList<QListViewItem> selected = selectedItems();
+
+ Q_ASSERT( !selected.isEmpty() );
+ if ( selected.isEmpty() )
+ return;
+
+ QPtrListIterator<QListViewItem> it( selected );
+ QPtrList<partNode> parts;
+ while ( it.current() ) {
+ parts.append( static_cast<KMMimePartTreeItem *>(it.current())->node() );
+ ++it;
+ }
+ mReaderWin->setUpdateAttachment();
+ KMSaveAttachmentsCommand *command =
+ new KMSaveAttachmentsCommand( this, parts, mReaderWin->message(), encoded );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMMimePartTree::slotSaveAll()
+{
+ if( childCount() == 0)
+ return;
+
+ mReaderWin->setUpdateAttachment();
+ KMCommand *command =
+ new KMSaveAttachmentsCommand( this, mReaderWin->message() );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMMimePartTree::setStyleDependantFrameWidth()
+{
+ // set the width of the frame to a reasonable value for the current GUI style
+ int frameWidth;
+ if( style().isA("KeramikStyle") )
+ frameWidth = style().pixelMetric( QStyle::PM_DefaultFrameWidth ) - 1;
+ else
+ frameWidth = style().pixelMetric( QStyle::PM_DefaultFrameWidth );
+ if ( frameWidth < 0 )
+ frameWidth = 0;
+ if ( frameWidth != lineWidth() )
+ setLineWidth( frameWidth );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMimePartTree::styleChange( QStyle& oldStyle )
+{
+ setStyleDependantFrameWidth();
+ KListView::styleChange( oldStyle );
+}
+
+//-----------------------------------------------------------------------------
+void KMMimePartTree::correctSize( QListViewItem * item )
+{
+ if (!item) return;
+
+ KIO::filesize_t totalSize = 0;
+ QListViewItem * myChild = item->firstChild();
+ while ( myChild )
+ {
+ totalSize += static_cast<KMMimePartTreeItem*>(myChild)->origSize();
+ myChild = myChild->nextSibling();
+ }
+ if ( totalSize > static_cast<KMMimePartTreeItem*>(item)->origSize() )
+ item->setText( mSizeColumn, KIO::convertSize(totalSize) );
+ if ( item->parent() )
+ correctSize( item->parent() );
+}
+
+void KMMimePartTree::slotDelete()
+{
+ QPtrList<QListViewItem> selected = selectedItems();
+ if ( selected.count() != 1 )
+ return;
+ mReaderWin->slotDeleteAttachment( static_cast<KMMimePartTreeItem*>( selected.first() )->node() );
+}
+
+void KMMimePartTree::slotEdit()
+{
+ QPtrList<QListViewItem> selected = selectedItems();
+ if ( selected.count() != 1 )
+ return;
+ mReaderWin->slotEditAttachment( static_cast<KMMimePartTreeItem*>( selected.first() )->node() );
+}
+
+void KMMimePartTree::slotOpen()
+{
+ startHandleAttachmentCommand( KMHandleAttachmentCommand::Open );
+}
+
+void KMMimePartTree::slotOpenWith()
+{
+ startHandleAttachmentCommand( KMHandleAttachmentCommand::OpenWith );
+}
+
+void KMMimePartTree::slotView()
+{
+ startHandleAttachmentCommand( KMHandleAttachmentCommand::View );
+}
+
+void KMMimePartTree::slotProperties()
+{
+ startHandleAttachmentCommand( KMHandleAttachmentCommand::Properties );
+}
+
+void KMMimePartTree::startHandleAttachmentCommand(int type)
+{
+ QPtrList<QListViewItem> selected = selectedItems();
+ if ( selected.count() != 1 )
+ return;
+ partNode* node = static_cast<KMMimePartTreeItem*>( selected.first() )->node();
+ QString name = mReaderWin->tempFileUrlFromPartNode( node ).path();
+ KMHandleAttachmentCommand* command = new KMHandleAttachmentCommand(
+ node, mReaderWin->message(), node->nodeId(), name,
+ KMHandleAttachmentCommand::AttachmentAction( type ), 0, this );
+ connect( command, SIGNAL( showAttachment( int, const QString& ) ),
+ mReaderWin, SLOT( slotAtmView( int, const QString& ) ) );
+ command->start();
+}
+
+void KMMimePartTree::slotCopy()
+{
+ KURL::List urls;
+ KMMimePartTreeItem *item = static_cast<KMMimePartTreeItem*>( currentItem() );
+ if ( !item ) return;
+ KURL url = mReaderWin->tempFileUrlFromPartNode( item->node() );
+ if ( !url.isValid() ) return;
+ urls.append( url );
+ KURLDrag* drag = new KURLDrag( urls, this );
+ QApplication::clipboard()->setData( drag, QClipboard::Clipboard );
+}
+
+//=============================================================================
+KMMimePartTreeItem::KMMimePartTreeItem( KMMimePartTree * parent,
+ partNode* node,
+ const QString & description,
+ const QString & mimetype,
+ const QString & encoding,
+ KIO::filesize_t size )
+ : QListViewItem( parent, description,
+ QString::null, // set by setIconAndTextForType()
+ encoding,
+ KIO::convertSize( size ) ),
+ mPartNode( node ), mOrigSize(size)
+{
+ if( node )
+ node->setMimePartTreeItem( this );
+ setIconAndTextForType( mimetype );
+ if ( parent )
+ parent->correctSize(this);
+}
+
+KMMimePartTreeItem::KMMimePartTreeItem( KMMimePartTreeItem * parent,
+ partNode* node,
+ const QString & description,
+ const QString & mimetype,
+ const QString & encoding,
+ KIO::filesize_t size,
+ bool revertOrder )
+ : QListViewItem( parent, description,
+ QString::null, // set by setIconAndTextForType()
+ encoding,
+ KIO::convertSize( size ) ),
+ mPartNode( node ), mOrigSize(size)
+{
+ if( revertOrder && nextSibling() ){
+ QListViewItem* sib = nextSibling();
+ while( sib->nextSibling() )
+ sib = sib->nextSibling();
+ moveItem( sib );
+ }
+ if( node )
+ node->setMimePartTreeItem( this );
+ setIconAndTextForType( mimetype );
+ if ( listView() )
+ static_cast<KMMimePartTree*>(listView())->correctSize(this);
+}
+
+void KMMimePartTreeItem::setIconAndTextForType( const QString & mime )
+{
+ QString mimetype = mime.lower();
+ if ( mimetype.startsWith( "multipart/" ) ) {
+ setText( 1, mimetype );
+ setPixmap( 0, SmallIcon("folder") );
+ } else if ( mimetype == "application/octet-stream" ) {
+ setText( 1, i18n("Unspecified Binary Data") ); // don't show "Unknown"...
+ setPixmap( 0, SmallIcon("unknown") );
+ } else {
+ KMimeType::Ptr mtp = KMimeType::mimeType( mimetype );
+ setText( 1, (mtp && !mtp->comment().isEmpty()) ? mtp->comment() : mimetype );
+ setPixmap( 0, mtp ? mtp->pixmap( KIcon::Small) : SmallIcon("unknown") );
+ }
+}
+
+
+void KMMimePartTree::startDrag()
+{
+ KURL::List urls;
+ KMMimePartTreeItem *item = static_cast<KMMimePartTreeItem*>( currentItem() );
+ if ( !item ) return;
+ partNode *node = item->node();
+ if ( !node ) return;
+ KURL url = mReaderWin->tempFileUrlFromPartNode( node );
+ if (!url.isValid() ) return;
+ urls.append( url );
+ KURLDrag* drag = new KURLDrag( urls, this );
+ drag->drag();
+}
+
+#include "kmmimeparttree.moc"
+
diff --git a/kmail/kmmimeparttree.h b/kmail/kmmimeparttree.h
new file mode 100644
index 00000000..fe33c1f6
--- /dev/null
+++ b/kmail/kmmimeparttree.h
@@ -0,0 +1,125 @@
+/* -*- c++ -*-
+ kmmimeparttree.cpp A MIME part tree viwer.
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2002-2004 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+
+
+// -*- c++ -*-
+#ifndef KMMIMEPARTTREE_H
+#define KMMIMEPARTTREE_H
+
+#include <klistview.h>
+#include <kio/global.h>
+
+#include <qstring.h>
+
+class partNode;
+class KMReaderWin;
+class KMMimePartTreeItem;
+
+class KMMimePartTree : public KListView
+{
+ Q_OBJECT
+ friend class ::KMReaderWin;
+
+public:
+ KMMimePartTree( KMReaderWin* readerWin,
+ QWidget* parent,
+ const char* name = 0 );
+ virtual ~KMMimePartTree();
+
+ void correctSize( QListViewItem * item );
+
+protected slots:
+ void itemClicked( QListViewItem* );
+ void itemRightClicked( QListViewItem*, const QPoint& );
+ void slotSaveAs();
+ void slotSaveAsEncoded();
+ void slotSaveAll();
+ void slotDelete();
+ void slotEdit();
+ void slotOpen();
+ void slotOpenWith();
+ void slotView();
+ void slotProperties();
+ void slotCopy();
+
+protected:
+ /** reimplemented in order to update the frame width in case of a changed
+ GUI style */
+ void styleChange( QStyle& oldStyle );
+
+ /** Set the width of the frame to a reasonable value for the current GUI
+ style */
+ void setStyleDependantFrameWidth();
+
+ void saveSelectedBodyParts( bool encoded );
+ void restoreLayoutIfPresent();
+
+ /* reimpl */
+ void startDrag();
+
+ void startHandleAttachmentCommand( int type );
+
+protected:
+ KMReaderWin* mReaderWin;
+ KMMimePartTreeItem* mCurrentContextMenuItem;
+ int mSizeColumn;
+};
+
+class KMMimePartTreeItem :public QListViewItem
+{
+public:
+ KMMimePartTreeItem( KMMimePartTree * parent,
+ partNode* node,
+ const QString & labelDescr,
+ const QString & labelCntType = QString::null,
+ const QString & labelEncoding = QString::null,
+ KIO::filesize_t size=0 );
+ KMMimePartTreeItem( KMMimePartTreeItem * parent,
+ partNode* node,
+ const QString & labelDescr,
+ const QString & labelCntType = QString::null,
+ const QString & labelEncoding = QString::null,
+ KIO::filesize_t size=0,
+ bool revertOrder = false );
+ partNode* node() const { return mPartNode; }
+
+ KIO::filesize_t origSize() const { return mOrigSize; }
+ void setOrigSize( KIO::filesize_t size ) { mOrigSize = size; }
+
+private:
+ void setIconAndTextForType( const QString & mimetype );
+
+ partNode* mPartNode;
+ KIO::filesize_t mOrigSize;
+};
+
+#endif // KMMIMEPARTTREE_H
diff --git a/kmail/kmmsgbase.cpp b/kmail/kmmsgbase.cpp
new file mode 100644
index 00000000..00c7e576
--- /dev/null
+++ b/kmail/kmmsgbase.cpp
@@ -0,0 +1,1483 @@
+// kmmsgbase.cpp
+
+#include <config.h>
+
+#include "globalsettings.h"
+#include "kmmsgbase.h"
+
+#include "kmfolderindex.h"
+#include "kmfolder.h"
+#include "kmheaders.h"
+#include "kmmsgdict.h"
+#include "messageproperty.h"
+using KMail::MessageProperty;
+
+#include <kdebug.h>
+#include <kglobal.h>
+#include <kcharsets.h>
+#include <kasciistringtools.h>
+#include <kmdcodec.h>
+#include <krfcdate.h>
+
+#include <mimelib/mimepp.h>
+#include <kmime_codecs.h>
+
+#include <qtextcodec.h>
+#include <qdeepcopy.h>
+#include <qregexp.h>
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#ifdef HAVE_BYTESWAP_H
+#include <byteswap.h>
+#endif
+
+// We define functions as kmail_swap_NN so that we don't get compile errors
+// on platforms where bswap_NN happens to be a function instead of a define.
+
+/* Swap bytes in 16 bit value. */
+#ifdef bswap_16
+#define kmail_swap_16(x) bswap_16(x)
+#else
+#define kmail_swap_16(x) \
+ ((((x) >> 8) & 0xff) | (((x) & 0xff) << 8))
+#endif
+
+/* Swap bytes in 32 bit value. */
+#ifdef bswap_32
+#define kmail_swap_32(x) bswap_32(x)
+#else
+#define kmail_swap_32(x) \
+ ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \
+ (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))
+#endif
+
+/* Swap bytes in 64 bit value. */
+#ifdef bswap_64
+#define kmail_swap_64(x) bswap_64(x)
+#else
+#define kmail_swap_64(x) \
+ ((((x) & 0xff00000000000000ull) >> 56) \
+ | (((x) & 0x00ff000000000000ull) >> 40) \
+ | (((x) & 0x0000ff0000000000ull) >> 24) \
+ | (((x) & 0x000000ff00000000ull) >> 8) \
+ | (((x) & 0x00000000ff000000ull) << 8) \
+ | (((x) & 0x0000000000ff0000ull) << 24) \
+ | (((x) & 0x000000000000ff00ull) << 40) \
+ | (((x) & 0x00000000000000ffull) << 56))
+#endif
+
+//-----------------------------------------------------------------------------
+KMMsgBase::KMMsgBase(KMFolder* aParentFolder)
+ : mParent( aParentFolder ), mIndexOffset( 0 ),
+ mIndexLength( 0 ), mDirty( false ), mEnableUndo( false ), mStatus( KMMsgStatusUnknown )
+{
+}
+
+
+//-----------------------------------------------------------------------------
+KMMsgBase::~KMMsgBase()
+{
+ MessageProperty::forget( this );
+}
+
+KMFolderIndex* KMMsgBase::storage() const
+{
+ // TODO: How did this ever work? What about KMFolderSearch that does
+ // not inherit KMFolderIndex?
+ if( mParent )
+ return static_cast<KMFolderIndex*>( mParent->storage() );
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+void KMMsgBase::assign(const KMMsgBase* other)
+{
+ mParent = other->mParent;
+ mDirty = other->mDirty;
+ mIndexOffset = other->mIndexOffset;
+ mIndexLength = other->mIndexLength;
+}
+
+//-----------------------------------------------------------------------------
+KMMsgBase& KMMsgBase::operator=(const KMMsgBase& other)
+{
+ assign(&other);
+ return *this;
+}
+
+
+//----------------------------------------------------------------------------
+KMMsgBase::KMMsgBase( const KMMsgBase& other )
+{
+ assign( &other );
+}
+
+//-----------------------------------------------------------------------------
+bool KMMsgBase::isMessage(void) const
+{
+ return false;
+}
+//-----------------------------------------------------------------------------
+void KMMsgBase::toggleStatus(const KMMsgStatus aStatus, int idx)
+{
+ mDirty = true;
+ KMMsgStatus oldStatus = status();
+ if ( status() & aStatus ) {
+ mStatus &= ~aStatus;
+ } else {
+ mStatus |= aStatus;
+ // Ignored and Watched are toggleable, yet mutually exclusive.
+ // That is an arbitrary restriction on my part. HAR HAR HAR :) -till
+ if (aStatus == KMMsgStatusWatched)
+ mStatus &= ~KMMsgStatusIgnored;
+ if (aStatus == KMMsgStatusIgnored)
+ mStatus &= ~KMMsgStatusWatched;
+ if (aStatus == KMMsgStatusSpam)
+ mStatus &= ~KMMsgStatusHam;
+ if (aStatus == KMMsgStatusHam)
+ mStatus &= ~KMMsgStatusSpam;
+ }
+ if (storage()) {
+ if (idx < 0)
+ idx = storage()->find( this );
+ storage()->msgStatusChanged( oldStatus, status(), idx );
+ storage()->headerOfMsgChanged(this, idx);
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+void KMMsgBase::setStatus(const KMMsgStatus aStatus, int idx)
+{
+ mDirty = true;
+ KMMsgStatus oldStatus = status();
+ switch (aStatus) {
+ case KMMsgStatusRead:
+ // Unset unread and new, set read
+ mStatus &= ~KMMsgStatusUnread;
+ mStatus &= ~KMMsgStatusNew;
+ mStatus |= KMMsgStatusRead;
+ break;
+
+ case KMMsgStatusUnread:
+ // unread overrides read
+ mStatus &= ~KMMsgStatusOld;
+ mStatus &= ~KMMsgStatusRead;
+ mStatus &= ~KMMsgStatusNew;
+ mStatus |= KMMsgStatusUnread;
+ break;
+
+ case KMMsgStatusOld:
+ // old can't be new or unread
+ mStatus &= ~KMMsgStatusNew;
+ mStatus &= ~KMMsgStatusUnread;
+ mStatus |= KMMsgStatusOld;
+ break;
+
+ case KMMsgStatusNew:
+ // new overrides old and read
+ mStatus &= ~KMMsgStatusOld;
+ mStatus &= ~KMMsgStatusRead;
+ mStatus &= ~KMMsgStatusUnread;
+ mStatus |= KMMsgStatusNew;
+ break;
+
+ case KMMsgStatusDeleted:
+ mStatus |= KMMsgStatusDeleted;
+ break;
+
+ case KMMsgStatusReplied:
+ mStatus |= KMMsgStatusReplied;
+ break;
+
+ case KMMsgStatusForwarded:
+ mStatus |= KMMsgStatusForwarded;
+ break;
+
+ case KMMsgStatusQueued:
+ mStatus |= KMMsgStatusQueued;
+ break;
+
+ case KMMsgStatusTodo:
+ mStatus |= KMMsgStatusTodo;
+ break;
+
+ case KMMsgStatusSent:
+ mStatus &= ~KMMsgStatusQueued;
+ mStatus &= ~KMMsgStatusUnread;
+ mStatus &= ~KMMsgStatusNew;
+ mStatus |= KMMsgStatusSent;
+ break;
+
+ case KMMsgStatusFlag:
+ mStatus |= KMMsgStatusFlag;
+ break;
+
+ // Watched and ignored are mutually exclusive
+ case KMMsgStatusWatched:
+ mStatus &= ~KMMsgStatusIgnored;
+ mStatus |= KMMsgStatusWatched;
+ break;
+
+ case KMMsgStatusIgnored:
+ mStatus &= ~KMMsgStatusWatched;
+ mStatus |= KMMsgStatusIgnored;
+ break;
+ // as are ham and spam
+ case KMMsgStatusSpam:
+ mStatus &= ~KMMsgStatusHam;
+ mStatus |= KMMsgStatusSpam;
+ break;
+ case KMMsgStatusHam:
+ mStatus &= ~KMMsgStatusSpam;
+ mStatus |= KMMsgStatusHam;
+ break;
+ case KMMsgStatusHasAttach:
+ mStatus &= ~KMMsgStatusHasNoAttach;
+ mStatus |= KMMsgStatusHasAttach;
+ break;
+ case KMMsgStatusHasNoAttach:
+ mStatus &= ~KMMsgStatusHasAttach;
+ mStatus |= KMMsgStatusHasNoAttach;
+ break;
+ default:
+ mStatus = aStatus;
+ break;
+ }
+
+ if ( oldStatus != mStatus && storage() ) {
+ if (idx < 0)
+ idx = storage()->find( this );
+ storage()->msgStatusChanged( oldStatus, status(), idx );
+ storage()->headerOfMsgChanged( this, idx );
+ }
+}
+
+
+
+//-----------------------------------------------------------------------------
+void KMMsgBase::setStatus(const char* aStatusStr, const char* aXStatusStr)
+{
+ // first try to find status from "X-Status" field if given
+ if (aXStatusStr) {
+ if (strchr(aXStatusStr, 'N')) setStatus(KMMsgStatusNew);
+ if (strchr(aXStatusStr, 'U')) setStatus(KMMsgStatusUnread);
+ if (strchr(aXStatusStr, 'O')) setStatus(KMMsgStatusOld);
+ if (strchr(aXStatusStr, 'R')) setStatus(KMMsgStatusRead);
+ if (strchr(aXStatusStr, 'D')) setStatus(KMMsgStatusDeleted);
+ if (strchr(aXStatusStr, 'A')) setStatus(KMMsgStatusReplied);
+ if (strchr(aXStatusStr, 'F')) setStatus(KMMsgStatusForwarded);
+ if (strchr(aXStatusStr, 'Q')) setStatus(KMMsgStatusQueued);
+ if (strchr(aXStatusStr, 'K')) setStatus(KMMsgStatusTodo);
+ if (strchr(aXStatusStr, 'S')) setStatus(KMMsgStatusSent);
+ if (strchr(aXStatusStr, 'G')) setStatus(KMMsgStatusFlag);
+ if (strchr(aXStatusStr, 'P')) setStatus(KMMsgStatusSpam);
+ if (strchr(aXStatusStr, 'H')) setStatus(KMMsgStatusHam);
+ if (strchr(aXStatusStr, 'T')) setStatus(KMMsgStatusHasAttach);
+ if (strchr(aXStatusStr, 'C')) setStatus(KMMsgStatusHasNoAttach);
+ }
+
+ // Merge the contents of the "Status" field
+ if (aStatusStr) {
+ if ((aStatusStr[0]== 'R' && aStatusStr[1]== 'O') ||
+ (aStatusStr[0]== 'O' && aStatusStr[1]== 'R')) {
+ setStatus( KMMsgStatusOld );
+ setStatus( KMMsgStatusRead );
+ }
+ else if (aStatusStr[0] == 'R')
+ setStatus(KMMsgStatusRead);
+ else if (aStatusStr[0] == 'D')
+ setStatus(KMMsgStatusDeleted);
+ else
+ setStatus(KMMsgStatusNew);
+ }
+}
+
+
+void KMMsgBase::setEncryptionState( const KMMsgEncryptionState /*status*/, int idx )
+{
+ //kdDebug(5006) << "***setEncryptionState1( " << status << " )" << endl;
+ mDirty = true;
+ if (storage())
+ storage()->headerOfMsgChanged(this, idx);
+}
+
+void KMMsgBase::setEncryptionStateChar( QChar status, int idx )
+{
+ //kdDebug(5006) << "***setEncryptionState2( " << (status.isNull() ? '?' : status.latin1()) << " )" << endl;
+
+ if( status.latin1() == (char)KMMsgEncryptionStateUnknown )
+ setEncryptionState( KMMsgEncryptionStateUnknown, idx );
+ else if( status.latin1() == (char)KMMsgNotEncrypted )
+ setEncryptionState( KMMsgNotEncrypted, idx );
+ else if( status.latin1() == (char)KMMsgPartiallyEncrypted )
+ setEncryptionState( KMMsgPartiallyEncrypted, idx );
+ else if( status.latin1() == (char)KMMsgFullyEncrypted )
+ setEncryptionState( KMMsgFullyEncrypted, idx );
+ else
+ setEncryptionState( KMMsgEncryptionStateUnknown, idx );
+}
+
+
+void KMMsgBase::setSignatureState( const KMMsgSignatureState /*status*/, int idx )
+{
+ //kdDebug(5006) << "***setSignatureState1( " << status << " )" << endl;
+ mDirty = true;
+ if (storage())
+ storage()->headerOfMsgChanged(this, idx);
+}
+
+void KMMsgBase::setMDNSentState( KMMsgMDNSentState, int idx ) {
+ mDirty = true;
+ if ( storage() )
+ storage()->headerOfMsgChanged(this, idx);
+}
+
+void KMMsgBase::setSignatureStateChar( QChar status, int idx )
+{
+ //kdDebug(5006) << "***setSignatureState2( " << (status.isNull() ? '?' : status.latin1()) << " )" << endl;
+
+ if( status.latin1() == (char)KMMsgSignatureStateUnknown )
+ setSignatureState( KMMsgSignatureStateUnknown, idx );
+ else if( status.latin1() == (char)KMMsgNotSigned )
+ setSignatureState( KMMsgNotSigned, idx );
+ else if( status.latin1() == (char)KMMsgPartiallySigned )
+ setSignatureState( KMMsgPartiallySigned,idx );
+ else if( status.latin1() == (char)KMMsgFullySigned )
+ setSignatureState( KMMsgFullySigned, idx );
+ else
+ setSignatureState( KMMsgSignatureStateUnknown, idx );
+}
+
+//-----------------------------------------------------------------------------
+bool KMMsgBase::isUnread(void) const
+{
+ KMMsgStatus st = status();
+ return (st & KMMsgStatusUnread && !(st & KMMsgStatusIgnored));
+}
+
+//-----------------------------------------------------------------------------
+bool KMMsgBase::isNew(void) const
+{
+ KMMsgStatus st = status();
+ return (st & KMMsgStatusNew && !(st & KMMsgStatusIgnored));
+}
+
+//-----------------------------------------------------------------------------
+bool KMMsgBase::isOfUnknownStatus(void) const
+{
+ KMMsgStatus st = status();
+ return (st == KMMsgStatusUnknown);
+}
+
+//-----------------------------------------------------------------------------
+bool KMMsgBase::isOld(void) const
+{
+ KMMsgStatus st = status();
+ return (st & KMMsgStatusOld);
+}
+
+//-----------------------------------------------------------------------------
+bool KMMsgBase::isRead(void) const
+{
+ KMMsgStatus st = status();
+ return (st & KMMsgStatusRead || st & KMMsgStatusIgnored);
+}
+
+//-----------------------------------------------------------------------------
+bool KMMsgBase::isDeleted(void) const
+{
+ KMMsgStatus st = status();
+ return (st & KMMsgStatusDeleted);
+}
+
+//-----------------------------------------------------------------------------
+bool KMMsgBase::isReplied(void) const
+{
+ KMMsgStatus st = status();
+ return (st & KMMsgStatusReplied);
+}
+
+//-----------------------------------------------------------------------------
+bool KMMsgBase::isForwarded(void) const
+{
+ KMMsgStatus st = status();
+ return (st & KMMsgStatusForwarded);
+}
+
+//-----------------------------------------------------------------------------
+bool KMMsgBase::isQueued(void) const
+{
+ KMMsgStatus st = status();
+ return (st & KMMsgStatusQueued);
+}
+
+//-----------------------------------------------------------------------------
+bool KMMsgBase::isTodo(void) const
+{
+ KMMsgStatus st = status();
+ return (st & KMMsgStatusTodo);
+}
+
+//-----------------------------------------------------------------------------
+bool KMMsgBase::isSent(void) const
+{
+ KMMsgStatus st = status();
+ return (st & KMMsgStatusSent);
+}
+
+//-----------------------------------------------------------------------------
+bool KMMsgBase::isImportant(void) const
+{
+ KMMsgStatus st = status();
+ return (st & KMMsgStatusFlag);
+}
+
+//-----------------------------------------------------------------------------
+bool KMMsgBase::isWatched(void) const
+{
+ KMMsgStatus st = status();
+ return (st & KMMsgStatusWatched);
+}
+
+//-----------------------------------------------------------------------------
+bool KMMsgBase::isIgnored(void) const
+{
+ KMMsgStatus st = status();
+ return (st & KMMsgStatusIgnored);
+}
+
+//-----------------------------------------------------------------------------
+bool KMMsgBase::isSpam(void) const
+{
+ KMMsgStatus st = status();
+ return (st & KMMsgStatusSpam);
+}
+
+//-----------------------------------------------------------------------------
+bool KMMsgBase::isHam(void) const
+{
+ KMMsgStatus st = status();
+ return (st & KMMsgStatusHam);
+}
+
+//-----------------------------------------------------------------------------
+QCString KMMsgBase::statusToStr(const KMMsgStatus status)
+{
+ QCString sstr;
+ if (status & KMMsgStatusNew) sstr += 'N';
+ if (status & KMMsgStatusUnread) sstr += 'U';
+ if (status & KMMsgStatusOld) sstr += 'O';
+ if (status & KMMsgStatusRead) sstr += 'R';
+ if (status & KMMsgStatusDeleted) sstr += 'D';
+ if (status & KMMsgStatusReplied) sstr += 'A';
+ if (status & KMMsgStatusForwarded) sstr += 'F';
+ if (status & KMMsgStatusQueued) sstr += 'Q';
+ if (status & KMMsgStatusTodo) sstr += 'K';
+ if (status & KMMsgStatusSent) sstr += 'S';
+ if (status & KMMsgStatusFlag) sstr += 'G';
+ if (status & KMMsgStatusWatched) sstr += 'W';
+ if (status & KMMsgStatusIgnored) sstr += 'I';
+ if (status & KMMsgStatusSpam) sstr += 'P';
+ if (status & KMMsgStatusHam) sstr += 'H';
+ if (status & KMMsgStatusHasAttach) sstr += 'T';
+ if (status & KMMsgStatusHasNoAttach) sstr += 'C';
+
+ return sstr;
+}
+
+//-----------------------------------------------------------------------------
+QString KMMsgBase::statusToSortRank()
+{
+ QString sstr = "bcbbbbbbbb";
+
+ // put watched ones first, then normal ones, ignored ones last
+ if (status() & KMMsgStatusWatched) sstr[0] = 'a';
+ if (status() & KMMsgStatusIgnored) sstr[0] = 'c';
+
+ // Second level. One of new, old, read, unread
+ if (status() & KMMsgStatusNew) sstr[1] = 'a';
+ if (status() & KMMsgStatusUnread) sstr[1] = 'b';
+ //if (status() & KMMsgStatusOld) sstr[1] = 'c';
+ //if (status() & KMMsgStatusRead) sstr[1] = 'c';
+
+ // Third level. In somewhat arbitrary order.
+ if (status() & KMMsgStatusDeleted) sstr[2] = 'a';
+ if (status() & KMMsgStatusFlag) sstr[3] = 'a';
+ if (status() & KMMsgStatusReplied) sstr[4] = 'a';
+ if (status() & KMMsgStatusForwarded) sstr[5] = 'a';
+ if (status() & KMMsgStatusQueued) sstr[6] = 'a';
+ if (status() & KMMsgStatusSent) sstr[7] = 'a';
+ if (status() & KMMsgStatusHam) sstr[8] = 'a';
+ if (status() & KMMsgStatusSpam) sstr[8] = 'c';
+ if (status() & KMMsgStatusTodo) sstr[9] = 'a';
+
+ return sstr;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMsgBase::setDate(const QCString& aDateStr)
+{
+ setDate( KRFCDate::parseDate( aDateStr ) );
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMsgBase::dateStr(void) const
+{
+ time_t d = date();
+ return KMime::DateFormatter::formatDate(KMime::DateFormatter::Fancy, d);
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMsgBase::skipKeyword(const QString& aStr, QChar sepChar,
+ bool* hasKeyword)
+{
+ unsigned int i = 0, maxChars = 3;
+ QString str = aStr;
+
+ while (str[0] == ' ') str.remove(0,1);
+ if (hasKeyword) *hasKeyword=false;
+
+ unsigned int strLength(str.length());
+ for (i=0; i < strLength && i < maxChars; i++)
+ {
+ if (str[i] < 'A' || str[i] == sepChar) break;
+ }
+
+ if (str[i] == sepChar) // skip following spaces too
+ {
+ do {
+ i++;
+ } while (str[i] == ' ');
+ if (hasKeyword) *hasKeyword=true;
+ return str.mid(i);
+ }
+ return str;
+}
+
+
+//-----------------------------------------------------------------------------
+const QTextCodec* KMMsgBase::codecForName(const QCString& _str)
+{
+ if (_str.isEmpty()) return 0;
+ QCString codec = _str;
+ KPIM::kAsciiToLower(codec.data());
+ return KGlobal::charsets()->codecForName(codec);
+}
+
+
+//-----------------------------------------------------------------------------
+QCString KMMsgBase::toUsAscii(const QString& _str, bool *ok)
+{
+ bool all_ok =true;
+ QString result = _str;
+ int len = result.length();
+ for (int i = 0; i < len; i++)
+ if (result.at(i).unicode() >= 128) {
+ result.at(i) = '?';
+ all_ok = false;
+ }
+ if (ok)
+ *ok = all_ok;
+ return result.latin1();
+}
+
+
+//-----------------------------------------------------------------------------
+QStringList KMMsgBase::supportedEncodings(bool usAscii)
+{
+ QStringList encodingNames = KGlobal::charsets()->availableEncodingNames();
+ QStringList encodings;
+ QMap<QString,bool> mimeNames;
+ for (QStringList::Iterator it = encodingNames.begin();
+ it != encodingNames.end(); it++)
+ {
+ QTextCodec *codec = KGlobal::charsets()->codecForName(*it);
+ QString mimeName = (codec) ? QString(codec->mimeName()).lower() : (*it);
+ if (mimeNames.find(mimeName) == mimeNames.end())
+ {
+ encodings.append(KGlobal::charsets()->languageForEncoding(*it)
+ + " ( " + mimeName + " )");
+ mimeNames.insert(mimeName, true);
+ }
+ }
+ encodings.sort();
+ if (usAscii) encodings.prepend(KGlobal::charsets()
+ ->languageForEncoding("us-ascii") + " ( us-ascii )");
+ return encodings;
+}
+
+namespace {
+ // don't rely on isblank(), which is a GNU extension in
+ // <cctype>. But if someone wants to write a configure test for
+ // isblank(), we can then rename this function to isblank and #ifdef
+ // it's definition...
+ inline bool isBlank( char ch ) { return ch == ' ' || ch == '\t' ; }
+
+ QCString unfold( const QCString & header ) {
+ if ( header.isEmpty() )
+ return QCString();
+
+ QCString result( header.size() ); // size() >= length()+1 and size() is O(1)
+ char * d = result.data();
+
+ for ( const char * s = header.data() ; *s ; )
+ if ( *s == '\r' ) { // ignore
+ ++s;
+ continue;
+ } else if ( *s == '\n' ) { // unfold
+ while ( isBlank( *++s ) )
+ ;
+ *d++ = ' ';
+ } else
+ *d++ = *s++;
+
+ *d++ = '\0';
+
+ result.truncate( d - result.data() );
+ return result;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMsgBase::decodeRFC2047String(const QCString& aStr, QCString prefCharset)
+{
+ if ( aStr.isEmpty() )
+ return QString::null;
+
+ const QCString str = unfold( aStr );
+
+ if ( str.isEmpty() )
+ return QString::null;
+
+ if ( str.find( "=?" ) < 0 ) {
+ if ( !prefCharset.isEmpty() ) {
+ if ( prefCharset == "us-ascii" ) {
+ // isn`t this foolproof?
+ return KMMsgBase::codecForName( "utf-8" )->toUnicode( str );
+ } else {
+ return KMMsgBase::codecForName( prefCharset )->toUnicode( str );
+ }
+ } else {
+ return KMMsgBase::codecForName( GlobalSettings::self()->
+ fallbackCharacterEncoding().latin1() )->toUnicode( str );
+ }
+ }
+
+ QString result;
+ QCString LWSP_buffer;
+ bool lastWasEncodedWord = false;
+
+ for ( const char * pos = str.data() ; *pos ; ++pos ) {
+ // collect LWSP after encoded-words,
+ // because we might need to throw it out
+ // (when the next word is an encoded-word)
+ if ( lastWasEncodedWord && isBlank( pos[0] ) ) {
+ LWSP_buffer += pos[0];
+ continue;
+ }
+ // verbatimly copy normal text
+ if (pos[0]!='=' || pos[1]!='?') {
+ result += LWSP_buffer + pos[0];
+ LWSP_buffer = 0;
+ lastWasEncodedWord = false;
+ continue;
+ }
+ // found possible encoded-word
+ const char * const beg = pos;
+ {
+ // parse charset name
+ QCString charset;
+ int i = 2;
+ pos += 2;
+ for ( ; *pos != '?' && ( *pos==' ' || ispunct(*pos) || isalnum(*pos) );
+ ++i, ++pos ) {
+ charset += *pos;
+ }
+ if ( *pos!='?' || i<4 )
+ goto invalid_encoded_word;
+
+ // get encoding and check delimiting question marks
+ const char encoding[2] = { pos[1], '\0' };
+ if (pos[2]!='?' || (encoding[0]!='Q' && encoding[0]!='q' &&
+ encoding[0]!='B' && encoding[0]!='b'))
+ goto invalid_encoded_word;
+ pos+=3; i+=3; // skip ?x?
+ const char * enc_start = pos;
+ // search for end of encoded part
+ while ( *pos && !(*pos=='?' && *(pos+1)=='=') ) {
+ i++;
+ pos++;
+ }
+ if ( !*pos )
+ goto invalid_encoded_word;
+
+ // valid encoding: decode and throw away separating LWSP
+ const KMime::Codec * c = KMime::Codec::codecForName( encoding );
+ kdFatal( !c, 5006 ) << "No \"" << encoding << "\" codec!?" << endl;
+
+ QByteArray in; in.setRawData( enc_start, pos - enc_start );
+ const QByteArray enc = c->decode( in );
+ in.resetRawData( enc_start, pos - enc_start );
+
+ const QTextCodec * codec = codecForName(charset);
+ if (!codec) codec = kmkernel->networkCodec();
+ result += codec->toUnicode(enc);
+ lastWasEncodedWord = true;
+
+ ++pos; // eat '?' (for loop eats '=')
+ LWSP_buffer = 0;
+ }
+ continue;
+ invalid_encoded_word:
+ // invalid encoding, keep separating LWSP.
+ pos = beg;
+ if ( !LWSP_buffer.isNull() )
+ result += LWSP_buffer;
+ result += "=?";
+ lastWasEncodedWord = false;
+ ++pos; // eat '?' (for loop eats '=')
+ LWSP_buffer = 0;
+ }
+ return result;
+}
+
+
+//-----------------------------------------------------------------------------
+static const QCString especials = "()<>@,;:\"/[]?.= \033";
+
+QCString KMMsgBase::encodeRFC2047Quoted( const QCString & s, bool base64 ) {
+ const char * codecName = base64 ? "b" : "q" ;
+ const KMime::Codec * codec = KMime::Codec::codecForName( codecName );
+ kdFatal( !codec, 5006 ) << "No \"" << codecName << "\" found!?" << endl;
+ QByteArray in; in.setRawData( s.data(), s.length() );
+ const QByteArray result = codec->encode( in );
+ in.resetRawData( s.data(), s.length() );
+ return QCString( result.data(), result.size() + 1 );
+}
+
+QCString KMMsgBase::encodeRFC2047String(const QString& _str,
+ const QCString& charset)
+{
+ static const QString dontQuote = "\"()<>,@";
+
+ if (_str.isEmpty()) return QCString();
+ if (charset == "us-ascii") return toUsAscii(_str);
+
+ QCString cset;
+ if (charset.isEmpty())
+ {
+ cset = kmkernel->networkCodec()->mimeName();
+ KPIM::kAsciiToLower(cset.data());
+ }
+ else cset = charset;
+
+ const QTextCodec *codec = codecForName(cset);
+ if (!codec) codec = kmkernel->networkCodec();
+
+ unsigned int nonAscii = 0;
+ unsigned int strLength(_str.length());
+ for (unsigned int i = 0; i < strLength; i++)
+ if (_str.at(i).unicode() >= 128) nonAscii++;
+ bool useBase64 = (nonAscii * 6 > strLength);
+
+ unsigned int start, stop, p, pos = 0, encLength;
+ QCString result;
+ bool breakLine = false;
+ const unsigned int maxLen = 75 - 7 - cset.length();
+
+ while (pos < strLength)
+ {
+ start = pos; p = pos;
+ while (p < strLength)
+ {
+ if (!breakLine && (_str.at(p) == ' ' || dontQuote.find(_str.at(p)) != -1))
+ start = p + 1;
+ if (_str.at(p).unicode() >= 128 || _str.at(p).unicode() < 32)
+ break;
+ p++;
+ }
+ if (breakLine || p < strLength)
+ {
+ while (dontQuote.find(_str.at(start)) != -1) start++;
+ stop = start;
+ while (stop < strLength && dontQuote.find(_str.at(stop)) == -1)
+ stop++;
+ result += _str.mid(pos, start - pos).latin1();
+ encLength = encodeRFC2047Quoted(codec->fromUnicode(_str.
+ mid(start, stop - start)), useBase64).length();
+ breakLine = (encLength > maxLen);
+ if (breakLine)
+ {
+ int dif = (stop - start) / 2;
+ int step = dif;
+ while (abs(step) > 1)
+ {
+ encLength = encodeRFC2047Quoted(codec->fromUnicode(_str.
+ mid(start, dif)), useBase64).length();
+ step = (encLength > maxLen) ? (-abs(step) / 2) : (abs(step) / 2);
+ dif += step;
+ }
+ stop = start + dif;
+ }
+ p = stop;
+ while (p > start && _str.at(p) != ' ') p--;
+ if (p > start) stop = p;
+ if (result.right(3) == "?= ") start--;
+ if (result.right(5) == "?=\n ") {
+ start--; result.truncate(result.length() - 1);
+ }
+ int lastNewLine = result.findRev("\n ");
+ if (!result.mid(lastNewLine).stripWhiteSpace().isEmpty()
+ && result.length() - lastNewLine + encLength + 2 > maxLen)
+ result += "\n ";
+ result += "=?";
+ result += cset;
+ result += (useBase64) ? "?b?" : "?q?";
+ result += encodeRFC2047Quoted(codec->fromUnicode(_str.mid(start,
+ stop - start)), useBase64);
+ result += "?=";
+ if (breakLine) result += "\n ";
+ pos = stop;
+ } else {
+ result += _str.mid(pos).latin1();
+ break;
+ }
+ }
+ return result;
+}
+
+
+//-----------------------------------------------------------------------------
+QCString KMMsgBase::encodeRFC2231String( const QString& _str,
+ const QCString& charset )
+{
+ if ( _str.isEmpty() )
+ return QCString();
+
+ QCString cset;
+ if ( charset.isEmpty() )
+ {
+ cset = kmkernel->networkCodec()->mimeName();
+ KPIM::kAsciiToLower( cset.data() );
+ }
+ else
+ cset = charset;
+ const QTextCodec *codec = codecForName( cset );
+ QCString latin;
+ if ( charset == "us-ascii" )
+ latin = toUsAscii( _str );
+ else if ( codec )
+ latin = codec->fromUnicode( _str );
+ else
+ latin = _str.local8Bit();
+
+ char *l;
+ for ( l = latin.data(); *l; ++l ) {
+ if ( ( ( *l & 0xE0 ) == 0 ) || ( *l & 0x80 ) )
+ // *l is control character or 8-bit char
+ break;
+ }
+ if ( !*l )
+ return latin;
+
+ QCString result = cset + "''";
+ for ( l = latin.data(); *l; ++l ) {
+ bool needsQuoting = ( *l & 0x80 );
+ if( !needsQuoting ) {
+ int len = especials.length();
+ for ( int i = 0; i < len; i++ )
+ if ( *l == especials[i] ) {
+ needsQuoting = true;
+ break;
+ }
+ }
+ if ( needsQuoting ) {
+ result += '%';
+ unsigned char hexcode;
+ hexcode = ( ( *l & 0xF0 ) >> 4 ) + 48;
+ if ( hexcode >= 58 )
+ hexcode += 7;
+ result += hexcode;
+ hexcode = ( *l & 0x0F ) + 48;
+ if ( hexcode >= 58 )
+ hexcode += 7;
+ result += hexcode;
+ } else {
+ result += *l;
+ }
+ }
+ return result;
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMsgBase::decodeRFC2231String(const QCString& _str)
+{
+ int p = _str.find('\'');
+ if (p < 0) return kmkernel->networkCodec()->toUnicode(_str);
+
+ QCString charset = _str.left(p);
+
+ QCString st = _str.mid(_str.findRev('\'') + 1);
+ char ch, ch2;
+ p = 0;
+ while (p < (int)st.length())
+ {
+ if (st.at(p) == 37)
+ {
+ ch = st.at(p+1) - 48;
+ if (ch > 16) ch -= 7;
+ ch2 = st.at(p+2) - 48;
+ if (ch2 > 16) ch2 -= 7;
+ st.at(p) = ch * 16 + ch2;
+ st.remove( p+1, 2 );
+ }
+ p++;
+ }
+ QString result;
+ const QTextCodec * codec = codecForName( charset );
+ if ( !codec )
+ codec = kmkernel->networkCodec();
+ return codec->toUnicode( st );
+}
+
+QCString KMMsgBase::extractRFC2231HeaderField( const QCString &aStr, const QCString &field )
+{
+ int n=-1;
+ QCString str;
+ bool found = false;
+ while ( n<=0 || found ) {
+ QString pattern( field );
+ pattern += "[*]"; // match a literal * after the fieldname, as defined by RFC 2231
+ if ( n>=0 ) { // If n<0, check for fieldname*=..., otherwise for fieldname*n=
+ pattern += QString::number(n) + "[*]?";
+ }
+ pattern += "=";
+
+ QRegExp fnamePart( pattern, false );
+ int startPart = fnamePart.search( aStr );
+ int endPart;
+ found = ( startPart >= 0 );
+ if ( found ) {
+ startPart += fnamePart.matchedLength();
+ // Quoted values end at the ending quote
+ if ( aStr[startPart] == '"' ) {
+ startPart++; // the double quote isn't part of the filename
+ endPart = aStr.find('"', startPart) - 1;
+ }
+ else {
+ endPart = aStr.find(';', startPart) - 1;
+ }
+ if (endPart < 0)
+ endPart = 32767;
+ str += aStr.mid( startPart, endPart-startPart+1).stripWhiteSpace();
+ }
+ n++;
+ }
+ return str;
+}
+
+QString KMMsgBase::base64EncodedMD5( const QString & s, bool utf8 ) {
+ if (s.stripWhiteSpace().isEmpty()) return "";
+ if ( utf8 )
+ return base64EncodedMD5( s.stripWhiteSpace().utf8() ); // QCString overload
+ else
+ return base64EncodedMD5( s.stripWhiteSpace().latin1() ); // const char * overload
+}
+
+QString KMMsgBase::base64EncodedMD5( const QCString & s ) {
+ if (s.stripWhiteSpace().isEmpty()) return "";
+ return base64EncodedMD5( s.stripWhiteSpace().data() );
+}
+
+QString KMMsgBase::base64EncodedMD5( const char * s, int len ) {
+ if (!s || !len) return "";
+ static const int Base64EncodedMD5Len = 22;
+ KMD5 md5( s, len );
+ return md5.base64Digest().left( Base64EncodedMD5Len );
+}
+
+
+//-----------------------------------------------------------------------------
+QCString KMMsgBase::autoDetectCharset(const QCString &_encoding, const QStringList &encodingList, const QString &text)
+{
+ QStringList charsets = encodingList;
+ if (!_encoding.isEmpty())
+ {
+ QString currentCharset = QString::fromLatin1(_encoding);
+ charsets.remove(currentCharset);
+ charsets.prepend(currentCharset);
+ }
+
+ QStringList::ConstIterator it = charsets.begin();
+ for (; it != charsets.end(); ++it)
+ {
+ QCString encoding = (*it).latin1();
+ if (encoding == "locale")
+ {
+ encoding = kmkernel->networkCodec()->mimeName();
+ KPIM::kAsciiToLower(encoding.data());
+ }
+ if (text.isEmpty())
+ return encoding;
+ if (encoding == "us-ascii") {
+ bool ok;
+ (void) KMMsgBase::toUsAscii(text, &ok);
+ if (ok)
+ return encoding;
+ }
+ else
+ {
+ const QTextCodec *codec = KMMsgBase::codecForName(encoding);
+ if (!codec) {
+ kdDebug(5006) << "Auto-Charset: Something is wrong and I can not get a codec. [" << encoding << "]" << endl;
+ } else {
+ if (codec->canEncode(text))
+ return encoding;
+ }
+ }
+ }
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+unsigned long KMMsgBase::getMsgSerNum() const
+{
+ unsigned long msn = MessageProperty::serialCache( this );
+ if (msn)
+ return msn;
+ if (mParent) {
+ int index = mParent->find((KMMsgBase*)this);
+ msn = KMMsgDict::instance()->getMsgSerNum(mParent, index);
+ if (msn)
+ MessageProperty::setSerialCache( this, msn );
+ }
+ return msn;
+}
+
+
+//-----------------------------------------------------------------------------
+KMMsgAttachmentState KMMsgBase::attachmentState() const
+{
+ KMMsgStatus st = status();
+ if (st & KMMsgStatusHasAttach)
+ return KMMsgHasAttachment;
+ else if (st & KMMsgStatusHasNoAttach)
+ return KMMsgHasNoAttachment;
+ else
+ return KMMsgAttachmentUnknown;
+}
+
+//-----------------------------------------------------------------------------
+static void swapEndian(QString &str)
+{
+ uint len = str.length();
+ str = QDeepCopy<QString>(str);
+ QChar *unicode = const_cast<QChar*>( str.unicode() );
+ for (uint i = 0; i < len; i++)
+ unicode[i] = kmail_swap_16(unicode[i].unicode());
+}
+
+//-----------------------------------------------------------------------------
+static int g_chunk_length = 0, g_chunk_offset=0;
+static uchar *g_chunk = 0;
+
+namespace {
+ template < typename T > void copy_from_stream( T & x ) {
+ if( g_chunk_offset + int(sizeof(T)) > g_chunk_length ) {
+ g_chunk_offset = g_chunk_length;
+ kdDebug( 5006 ) << "This should never happen.. "
+ << __FILE__ << ":" << __LINE__ << endl;
+ x = 0;
+ } else {
+ // the memcpy is optimized out by the compiler for the values
+ // of sizeof(T) that is called with
+ memcpy( &x, g_chunk + g_chunk_offset, sizeof(T) );
+ g_chunk_offset += sizeof(T);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+QString KMMsgBase::getStringPart(MsgPartType t) const
+{
+retry:
+ QString ret;
+
+ g_chunk_offset = 0;
+ bool using_mmap = false;
+ bool swapByteOrder = storage()->indexSwapByteOrder();
+ if (storage()->indexStreamBasePtr()) {
+ if (g_chunk)
+ free(g_chunk);
+ using_mmap = true;
+ g_chunk = storage()->indexStreamBasePtr() + mIndexOffset;
+ g_chunk_length = mIndexLength;
+ } else {
+ if(!storage()->mIndexStream)
+ return ret;
+ if (g_chunk_length < mIndexLength)
+ g_chunk = (uchar *)realloc(g_chunk, g_chunk_length = mIndexLength);
+ off_t first_off=ftell(storage()->mIndexStream);
+ fseek(storage()->mIndexStream, mIndexOffset, SEEK_SET);
+ fread( g_chunk, mIndexLength, 1, storage()->mIndexStream);
+ fseek(storage()->mIndexStream, first_off, SEEK_SET);
+ }
+
+ MsgPartType type;
+ Q_UINT16 l;
+ while(g_chunk_offset < mIndexLength) {
+ Q_UINT32 tmp;
+ copy_from_stream(tmp);
+ copy_from_stream(l);
+ if (swapByteOrder)
+ {
+ tmp = kmail_swap_32(tmp);
+ l = kmail_swap_16(l);
+ }
+ type = (MsgPartType) tmp;
+ if(g_chunk_offset + l > mIndexLength) {
+ kdDebug(5006) << "This should never happen.. " << __FILE__ << ":" << __LINE__ << endl;
+ if(using_mmap) {
+ g_chunk_length = 0;
+ g_chunk = 0;
+ }
+ storage()->recreateIndex();
+ goto retry;
+ }
+ if(type == t) {
+ // This works because the QString constructor does a memcpy.
+ // Otherwise we would need to be concerned about the alignment.
+ if(l)
+ ret = QString((QChar *)(g_chunk + g_chunk_offset), l/2);
+ break;
+ }
+ g_chunk_offset += l;
+ }
+ if(using_mmap) {
+ g_chunk_length = 0;
+ g_chunk = 0;
+ }
+ // Normally we need to swap the byte order because the QStrings are written
+ // in the style of Qt2 (MSB -> network ordered).
+ // QStrings in Qt3 expect host ordering.
+ // On e.g. Intel host ordering is LSB, on e.g. Sparc it is MSB.
+
+#ifndef WORDS_BIGENDIAN
+ // #warning Byte order is little endian (swap is true)
+ swapEndian(ret);
+#else
+ // #warning Byte order is big endian (swap is false)
+#endif
+
+ return ret;
+}
+
+//-----------------------------------------------------------------------------
+off_t KMMsgBase::getLongPart(MsgPartType t) const
+{
+retry:
+ off_t ret = 0;
+
+ g_chunk_offset = 0;
+ bool using_mmap = false;
+ int sizeOfLong = storage()->indexSizeOfLong();
+ bool swapByteOrder = storage()->indexSwapByteOrder();
+ if (storage()->indexStreamBasePtr()) {
+ if (g_chunk)
+ free(g_chunk);
+ using_mmap = true;
+ g_chunk = storage()->indexStreamBasePtr() + mIndexOffset;
+ g_chunk_length = mIndexLength;
+ } else {
+ if (!storage()->mIndexStream)
+ return ret;
+ assert(mIndexLength >= 0);
+ if (g_chunk_length < mIndexLength)
+ g_chunk = (uchar *)realloc(g_chunk, g_chunk_length = mIndexLength);
+ off_t first_off=ftell(storage()->mIndexStream);
+ fseek(storage()->mIndexStream, mIndexOffset, SEEK_SET);
+ fread( g_chunk, mIndexLength, 1, storage()->mIndexStream);
+ fseek(storage()->mIndexStream, first_off, SEEK_SET);
+ }
+
+ MsgPartType type;
+ Q_UINT16 l;
+ while (g_chunk_offset < mIndexLength) {
+ Q_UINT32 tmp;
+ copy_from_stream(tmp);
+ copy_from_stream(l);
+ if (swapByteOrder)
+ {
+ tmp = kmail_swap_32(tmp);
+ l = kmail_swap_16(l);
+ }
+ type = (MsgPartType) tmp;
+
+ if (g_chunk_offset + l > mIndexLength) {
+ kdDebug(5006) << "This should never happen.. " << __FILE__ << ":" << __LINE__ << endl;
+ if(using_mmap) {
+ g_chunk_length = 0;
+ g_chunk = 0;
+ }
+ storage()->recreateIndex();
+ goto retry;
+ }
+ if(type == t) {
+ assert(sizeOfLong == l);
+ if (sizeOfLong == sizeof(ret))
+ {
+ copy_from_stream(ret);
+ if (swapByteOrder)
+ {
+ if (sizeof(ret) == 4)
+ ret = kmail_swap_32(ret);
+ else
+ ret = kmail_swap_64(ret);
+ }
+ }
+ else if (sizeOfLong == 4)
+ {
+ // Long is stored as 4 bytes in index file, sizeof(long) = 8
+ Q_UINT32 ret_32;
+ copy_from_stream(ret_32);
+ if (swapByteOrder)
+ ret_32 = kmail_swap_32(ret_32);
+ ret = ret_32;
+ }
+ else if (sizeOfLong == 8)
+ {
+ // Long is stored as 8 bytes in index file, sizeof(long) = 4
+ Q_UINT32 ret_1;
+ Q_UINT32 ret_2;
+ copy_from_stream(ret_1);
+ copy_from_stream(ret_2);
+ if (!swapByteOrder)
+ {
+ // Index file order is the same as the order of this CPU.
+#ifndef WORDS_BIGENDIAN
+ // Index file order is little endian
+ ret = ret_1; // We drop the 4 most significant bytes
+#else
+ // Index file order is big endian
+ ret = ret_2; // We drop the 4 most significant bytes
+#endif
+ }
+ else
+ {
+ // Index file order is different from this CPU.
+#ifndef WORDS_BIGENDIAN
+ // Index file order is big endian
+ ret = ret_2; // We drop the 4 most significant bytes
+#else
+ // Index file order is little endian
+ ret = ret_1; // We drop the 4 most significant bytes
+#endif
+ // We swap the result to host order.
+ ret = kmail_swap_32(ret);
+ }
+
+ }
+ break;
+ }
+ g_chunk_offset += l;
+ }
+ if(using_mmap) {
+ g_chunk_length = 0;
+ g_chunk = 0;
+ }
+ return ret;
+}
+
+#ifndef WORDS_BIGENDIAN
+// We need to use swab to swap bytes to network byte order
+#define memcpy_networkorder(to, from, len) swab((char *)(from), (char *)(to), len)
+#else
+// We're already in network byte order
+#define memcpy_networkorder(to, from, len) memcpy(to, from, len)
+#endif
+
+#define STORE_DATA_LEN(type, x, len, network_order) do { \
+ int len2 = (len > 256) ? 256 : len; \
+ if(csize < (length + (len2 + sizeof(short) + sizeof(MsgPartType)))) \
+ ret = (uchar *)realloc(ret, csize += len2+sizeof(short)+sizeof(MsgPartType)); \
+ Q_UINT32 t = (Q_UINT32) type; memcpy(ret+length, &t, sizeof(t)); \
+ Q_UINT16 l = len2; memcpy(ret+length+sizeof(t), &l, sizeof(l)); \
+ if (network_order) \
+ memcpy_networkorder(ret+length+sizeof(t)+sizeof(l), x, len2); \
+ else \
+ memcpy(ret+length+sizeof(t)+sizeof(l), x, len2); \
+ length += len2+sizeof(t)+sizeof(l); \
+ } while(0)
+#define STORE_DATA(type, x) STORE_DATA_LEN(type, &x, sizeof(x), false)
+
+//-----------------------------------------------------------------------------
+const uchar *KMMsgBase::asIndexString(int &length) const
+{
+ unsigned int csize = 256;
+ static uchar *ret = 0; //different static buffer here for we may use the other buffer in the functions below
+ if(!ret)
+ ret = (uchar *)malloc(csize);
+ length = 0;
+
+ unsigned long tmp;
+ QString tmp_str;
+
+ //these is at the beginning because it is queried quite often
+ tmp_str = msgIdMD5().stripWhiteSpace();
+ STORE_DATA_LEN(MsgIdMD5Part, tmp_str.unicode(), tmp_str.length() * 2, true);
+ tmp = mLegacyStatus;
+ STORE_DATA(MsgLegacyStatusPart, tmp);
+
+ //these are completely arbitrary order
+ tmp_str = fromStrip().stripWhiteSpace();
+ STORE_DATA_LEN(MsgFromPart, 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);
+ tmp_str = replyToIdMD5().stripWhiteSpace();
+ STORE_DATA_LEN(MsgReplyToIdMD5Part, tmp_str.unicode(), tmp_str.length() * 2, true);
+ tmp_str = xmark().stripWhiteSpace();
+ STORE_DATA_LEN(MsgXMarkPart, tmp_str.unicode(), tmp_str.length() * 2, true);
+ tmp_str = fileName().stripWhiteSpace();
+ STORE_DATA_LEN(MsgFilePart, tmp_str.unicode(), tmp_str.length() * 2, true);
+ tmp = msgSize();
+ STORE_DATA(MsgSizePart, tmp);
+ tmp = folderOffset();
+ STORE_DATA(MsgOffsetPart, tmp);
+ tmp = date();
+ STORE_DATA(MsgDatePart, tmp);
+ tmp = (signatureState() << 16) | encryptionState();
+ STORE_DATA(MsgCryptoStatePart, tmp);
+ tmp = mdnSentState();
+ STORE_DATA(MsgMDNSentPart, tmp);
+
+ tmp_str = replyToAuxIdMD5().stripWhiteSpace();
+ STORE_DATA_LEN(MsgReplyToAuxIdMD5Part, tmp_str.unicode(), tmp_str.length() * 2, true);
+
+ tmp_str = strippedSubjectMD5().stripWhiteSpace();
+ STORE_DATA_LEN(MsgStrippedSubjectMD5Part, tmp_str.unicode(), tmp_str.length() * 2, true);
+
+ tmp = status();
+ STORE_DATA(MsgStatusPart, tmp);
+
+ tmp = msgSizeServer();
+ STORE_DATA(MsgSizeServerPart, tmp);
+ tmp = UID();
+ STORE_DATA(MsgUIDPart, tmp);
+
+ return ret;
+}
+#undef STORE_DATA_LEN
+#undef STORE_DATA
+
+bool KMMsgBase::syncIndexString() const
+{
+ if(!dirty())
+ return true;
+ int len;
+ const uchar *buffer = asIndexString(len);
+ if (len == mIndexLength) {
+ Q_ASSERT(storage()->mIndexStream);
+ fseek(storage()->mIndexStream, mIndexOffset, SEEK_SET);
+ assert( mIndexOffset > 0 );
+ fwrite( buffer, len, 1, storage()->mIndexStream);
+ return true;
+ }
+ return false;
+}
+
+static QStringList sReplySubjPrefixes, sForwardSubjPrefixes;
+static bool sReplaceSubjPrefix, sReplaceForwSubjPrefix;
+
+//-----------------------------------------------------------------------------
+void KMMsgBase::readConfig()
+{
+ KConfigGroup composerGroup( KMKernel::config(), "Composer" );
+ sReplySubjPrefixes = composerGroup.readListEntry("reply-prefixes", ',');
+ if (sReplySubjPrefixes.isEmpty())
+ sReplySubjPrefixes << "Re\\s*:" << "Re\\[\\d+\\]:" << "Re\\d+:";
+ sReplaceSubjPrefix = composerGroup.readBoolEntry("replace-reply-prefix", true);
+ sForwardSubjPrefixes = composerGroup.readListEntry("forward-prefixes", ',');
+ if (sForwardSubjPrefixes.isEmpty())
+ sForwardSubjPrefixes << "Fwd:" << "FW:";
+ sReplaceForwSubjPrefix = composerGroup.readBoolEntry("replace-forward-prefix", true);
+}
+
+//-----------------------------------------------------------------------------
+// static
+QString KMMsgBase::stripOffPrefixes( const QString& str )
+{
+ return replacePrefixes( str, sReplySubjPrefixes + sForwardSubjPrefixes,
+ true, QString::null ).stripWhiteSpace();
+}
+
+//-----------------------------------------------------------------------------
+// static
+QString KMMsgBase::replacePrefixes( const QString& str,
+ const QStringList& prefixRegExps,
+ bool replace,
+ const QString& newPrefix )
+{
+ bool recognized = false;
+ // construct a big regexp that
+ // 1. is anchored to the beginning of str (sans whitespace)
+ // 2. matches at least one of the part regexps in prefixRegExps
+ QString bigRegExp = QString::fromLatin1("^(?:\\s+|(?:%1))+\\s*")
+ .arg( prefixRegExps.join(")|(?:") );
+ QRegExp rx( bigRegExp, false /*case insens.*/ );
+ if ( !rx.isValid() ) {
+ kdWarning(5006) << "KMMessage::replacePrefixes(): bigRegExp = \""
+ << bigRegExp << "\"\n"
+ << "prefix regexp is invalid!" << endl;
+ // try good ole Re/Fwd:
+ recognized = str.startsWith( newPrefix );
+ } else { // valid rx
+ QString tmp = str;
+ if ( rx.search( tmp ) == 0 ) {
+ recognized = true;
+ if ( replace )
+ return tmp.replace( 0, rx.matchedLength(), newPrefix + ' ' );
+ }
+ }
+ if ( !recognized )
+ return newPrefix + ' ' + str;
+ else
+ return str;
+}
+
+//-----------------------------------------------------------------------------
+QString KMMsgBase::cleanSubject() const
+{
+ return cleanSubject( sReplySubjPrefixes + sForwardSubjPrefixes,
+ true, QString::null ).stripWhiteSpace();
+}
+
+//-----------------------------------------------------------------------------
+QString KMMsgBase::cleanSubject( const QStringList & prefixRegExps,
+ bool replace,
+ const QString & newPrefix ) const
+{
+ return KMMsgBase::replacePrefixes( subject(), prefixRegExps, replace,
+ newPrefix );
+}
+
+//-----------------------------------------------------------------------------
+QString KMMsgBase::forwardSubject() const {
+ return cleanSubject( sForwardSubjPrefixes, sReplaceForwSubjPrefix, "Fwd:" );
+}
+
+//-----------------------------------------------------------------------------
+QString KMMsgBase::replySubject() const {
+ return cleanSubject( sReplySubjPrefixes, sReplaceSubjPrefix, "Re:" );
+}
diff --git a/kmail/kmmsgbase.h b/kmail/kmmsgbase.h
new file mode 100644
index 00000000..35566682
--- /dev/null
+++ b/kmail/kmmsgbase.h
@@ -0,0 +1,472 @@
+/*
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ *
+ * 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 kmmsgbase_h
+#define kmmsgbase_h
+
+// for large file support flags
+#include <config.h>
+#include <sys/types.h>
+#include <qstring.h>
+#include <time.h>
+
+class QCString;
+class QStringList;
+class QTextCodec;
+class KMFolder;
+class KMFolderIndex;
+
+/** The new status format. These can be or'd together.
+ Note, that the KMMsgStatusIgnored implies the
+ status to be Read even if the flags are set
+ to Unread or New. This is done in KMMsgBase::isRead()
+ and related getters. So we can preserve the state
+ when switching a thread to Ignored and back. */
+enum MsgStatus
+{
+ KMMsgStatusUnknown = 0x00000000,
+ KMMsgStatusNew = 0x00000001,
+ KMMsgStatusUnread = 0x00000002,
+ KMMsgStatusRead = 0x00000004,
+ KMMsgStatusOld = 0x00000008,
+ KMMsgStatusDeleted = 0x00000010,
+ KMMsgStatusReplied = 0x00000020,
+ KMMsgStatusForwarded = 0x00000040,
+ KMMsgStatusQueued = 0x00000080,
+ KMMsgStatusSent = 0x00000100,
+ KMMsgStatusFlag = 0x00000200, // flag means important
+ KMMsgStatusWatched = 0x00000400,
+ KMMsgStatusIgnored = 0x00000800, // forces isRead()
+ KMMsgStatusTodo = 0x00001000,
+ KMMsgStatusSpam = 0x00002000,
+ KMMsgStatusHam = 0x00004000,
+ KMMsgStatusHasAttach = 0x00008000,
+ KMMsgStatusHasNoAttach = 0x00010000
+};
+
+typedef uint KMMsgStatus;
+
+/** The old status format, only one at a time possible. Needed
+ for upgrade path purposes. */
+
+typedef enum
+{
+ KMLegacyMsgStatusUnknown=' ',
+ KMLegacyMsgStatusNew='N',
+ KMLegacyMsgStatusUnread='U',
+ KMLegacyMsgStatusRead='R',
+ KMLegacyMsgStatusOld='O',
+ KMLegacyMsgStatusDeleted='D',
+ KMLegacyMsgStatusReplied='A',
+ KMLegacyMsgStatusForwarded='F',
+ KMLegacyMsgStatusQueued='Q',
+ KMLegacyMsgStatusSent='S',
+ KMLegacyMsgStatusFlag='G'
+} KMLegacyMsgStatus;
+
+
+
+/** Flags for the encryption state. */
+typedef enum
+{
+ KMMsgEncryptionStateUnknown=' ',
+ KMMsgNotEncrypted='N',
+ KMMsgPartiallyEncrypted='P',
+ KMMsgFullyEncrypted='F',
+ KMMsgEncryptionProblematic='X'
+} KMMsgEncryptionState;
+
+/** Flags for the signature state. */
+typedef enum
+{
+ KMMsgSignatureStateUnknown=' ',
+ KMMsgNotSigned='N',
+ KMMsgPartiallySigned='P',
+ KMMsgFullySigned='F',
+ KMMsgSignatureProblematic='X'
+} KMMsgSignatureState;
+
+/** Flags for the "MDN sent" state. */
+typedef enum
+{
+ KMMsgMDNStateUnknown = ' ',
+ KMMsgMDNNone = 'N',
+ KMMsgMDNIgnore = 'I',
+ KMMsgMDNDisplayed = 'R',
+ KMMsgMDNDeleted = 'D',
+ KMMsgMDNDispatched = 'F',
+ KMMsgMDNProcessed = 'P',
+ KMMsgMDNDenied = 'X',
+ KMMsgMDNFailed = 'E'
+} KMMsgMDNSentState;
+
+/** Flags for the drag and drop action. */
+typedef enum
+{
+ KMMsgDnDActionMOVE=0,
+ KMMsgDnDActionCOPY=1,
+ KMMsgDnDActionASK=2
+} KMMsgDnDAction;
+
+/** Flags for attachment state */
+typedef enum
+{
+ KMMsgHasAttachment,
+ KMMsgHasNoAttachment,
+ KMMsgAttachmentUnknown
+} KMMsgAttachmentState;
+
+
+class KMMsgBase
+{
+public:
+ KMMsgBase(KMFolder* p=0);
+ virtual ~KMMsgBase();
+
+ /** Return owning storage. */
+ KMFolderIndex* storage() const;
+
+ /** Return owning folder. */
+ KMFolder* parent() const { return mParent; }
+
+ /** Set owning folder. */
+ void setParent(KMFolder* p) { mParent = p; }
+
+ /** Convert the given message status to a string. */
+ static QCString statusToStr(const KMMsgStatus status);
+
+ /** Convert the given message status to a string. */
+ QString statusToSortRank();
+
+ /** Returns TRUE if object is a real message (not KMMsgInfo or KMMsgBase) */
+ virtual bool isMessage(void) const;
+
+ /** Returns TRUE if status unread. Note that new messages are not unread.*/
+ virtual bool isUnread(void) const;
+
+ /** Returns TRUE if status is new. */
+ virtual bool isNew(void) const;
+
+ /** Returns TRUE if status is unknown. */
+ virtual bool isOfUnknownStatus(void) const;
+
+ /** Returns TRUE if status is old. */
+ virtual bool isOld(void) const;
+
+ /** Returns TRUE if status is read. */
+ virtual bool isRead(void) const;
+
+ /** Returns TRUE if status is deleted. */
+ virtual bool isDeleted(void) const;
+
+ /** Returns TRUE if status is replied. */
+ virtual bool isReplied(void) const;
+
+ /** Returns TRUE if status is forwarded. */
+ virtual bool isForwarded(void) const;
+
+ /** Returns TRUE if status is queued. */
+ virtual bool isQueued(void) const;
+
+ /** Returns TRUE if status is todo flaged. */
+ virtual bool isTodo(void) const;
+
+ /** Returns TRUE if status is sent. */
+ virtual bool isSent(void) const;
+
+ /** Returns TRUE if status is important. */
+ virtual bool isImportant(void) const;
+
+ /** Returns TRUE if status is watched. */
+ virtual bool isWatched(void) const;
+
+ /** Returns TRUE if status is ignored. */
+ virtual bool isIgnored(void) const;
+
+ /** Returns TRUE if status is spam. */
+ virtual bool isSpam(void) const;
+
+ /** Returns TRUE if status is not spam. */
+ virtual bool isHam(void) const;
+
+
+ /** Status of the message. */
+ virtual KMMsgStatus status(void) const = 0;
+
+ /** Set status and mark dirty. Optional optimization: @p idx may
+ * specify the index of this message within the parent folder. */
+ virtual void setStatus(const KMMsgStatus status, int idx = -1);
+ virtual void toggleStatus(const KMMsgStatus status, int idx = -1);
+ virtual void setStatus(const char* statusField, const char* xstatusField=0);
+
+ /** Encryption status of the message. */
+ virtual KMMsgEncryptionState encryptionState() const = 0;
+
+ /** Signature status of the message. */
+ virtual KMMsgSignatureState signatureState() const = 0;
+
+ /** "MDN send" status of the message. */
+ virtual KMMsgMDNSentState mdnSentState() const = 0;
+
+ /** Set "MDN sent" status of the message. */
+ virtual void setMDNSentState( KMMsgMDNSentState status, int idx=-1 );
+
+ /** Set encryption status of the message and mark dirty. Optional
+ * optimization: @p idx may specify the index of this message within
+ * the parent folder. */
+ virtual void setEncryptionState(const KMMsgEncryptionState, int idx = -1);
+
+ /** Set signature status of the message and mark dirty. Optional
+ * optimization: @p idx may specify the index of this message within
+ * the parent folder. */
+ virtual void setSignatureState(const KMMsgSignatureState, int idx = -1);
+
+ /** Set encryption status of the message and mark dirty. Optional
+ * optimization: @p idx may specify the index of this message within
+ * the parent folder. */
+ virtual void setEncryptionStateChar( QChar status, int idx = -1 );
+
+ /** Set signature status of the message and mark dirty. Optional
+ * optimization: @p idx may specify the index of this message within
+ * the parent folder. */
+ virtual void setSignatureStateChar( QChar status, int idx = -1 );
+
+ /** Important header fields of the message that are also kept in the index. */
+ virtual QString subject(void) const = 0;
+ virtual QString fromStrip(void) const = 0;
+ virtual QString toStrip(void) const = 0;
+ virtual QString replyToIdMD5(void) const = 0;
+ virtual QString msgIdMD5(void) const = 0;
+ virtual QString replyToAuxIdMD5() const = 0;
+ virtual QString strippedSubjectMD5() const = 0;
+ virtual bool subjectIsPrefixed() const = 0;
+ virtual time_t date(void) const = 0;
+ virtual QString dateStr(void) const;
+ virtual QString xmark(void) const = 0;
+
+ /** Set date. */
+ virtual void setDate(const QCString &aStrDate);
+ virtual void setDate(time_t aUnixTime) = 0;
+
+ /** Returns TRUE if changed since last folder-sync. */
+ virtual bool dirty(void) const { return mDirty; }
+
+ /** Change dirty flag. */
+ void setDirty(bool b) { mDirty = b; }
+
+ /** Set subject/from/date and xmark. */
+ virtual void setSubject(const QString&) = 0;
+ virtual void setXMark(const QString&) = 0;
+
+ /** Calculate strippedSubject */
+ virtual void initStrippedSubjectMD5() = 0;
+
+ /** Return contents as index string. This string is of indexStringLength() size */
+ const uchar *asIndexString(int &len) const;
+
+ /** Get/set offset in mail folder. */
+ virtual off_t folderOffset(void) const = 0;
+ virtual void setFolderOffset(off_t offs) = 0;
+
+ /** Get/set msg filename */
+ virtual QString fileName(void) const = 0;
+ virtual void setFileName(const QString& filename) = 0;
+
+ /** Get/set size of message including the whole header in bytes. */
+ virtual size_t msgSize(void) const = 0;
+ virtual void setMsgSize(size_t sz) = 0;
+
+ /** Get/set size of message on server */
+ virtual size_t msgSizeServer(void) const = 0;
+ virtual void setMsgSizeServer(size_t sz) = 0;
+
+ /** Get/set UID for IMAP */
+ virtual ulong UID(void) const = 0;
+ virtual void setUID(ulong uid) = 0;
+
+ /** offset into index file */
+ virtual void setIndexOffset(off_t off) { mIndexOffset = off; }
+ virtual off_t indexOffset() const { return mIndexOffset; }
+
+ /** size in index file */
+ virtual void setIndexLength(short len) { mIndexLength = len; }
+ virtual short indexLength() const { return mIndexLength; }
+
+ /** Skip leading keyword if keyword has given character at it's end
+ * (e.g. ':' or ',') and skip the then following blanks (if any) too.
+ * If keywordFound is specified it will be TRUE if a keyword was skipped
+ * and FALSE otherwise. */
+ static QString skipKeyword(const QString& str, QChar sepChar=':',
+ bool* keywordFound=0);
+
+ /** Return a QTextCodec for the specified charset.
+ * This function is a bit more tolerant, than QTextCodec::codecForName */
+ static const QTextCodec* codecForName(const QCString& _str);
+
+ /** Convert all non-ascii characters to question marks
+ * If ok is non-null, *ok will be set to true if all characters
+ * where ascii, *ok will be set to false otherwise */
+ static QCString toUsAscii(const QString& _str, bool *ok=0);
+
+ /** Return a list of the supported encodings */
+ static QStringList supportedEncodings(bool usAscii);
+
+ /** Copy all values from other to this object. */
+ void assign(const KMMsgBase* other);
+
+ /** Assignment operator that simply calls assign(). */
+ KMMsgBase& operator=(const KMMsgBase& other);
+
+ /** Copy constructor that simply calls assign(). */
+ KMMsgBase( const KMMsgBase& other );
+
+ /** Helper function for encodeRFC2047String */
+ static QCString encodeRFC2047Quoted(const QCString& aStr, bool base64);
+
+ /** This function handles both encodings described in RFC2047:
+ Base64 ("=?iso-8859-1?b?...?=") and quoted-printable */
+ static QString decodeRFC2047String(const QCString& aStr, const QCString prefCharset = "");
+
+ /** Encode given string as described in RFC2047:
+ using quoted-printable. */
+ static QCString encodeRFC2047String(const QString& aStr,
+ const QCString& charset);
+
+ /** Encode given string as described in RFC2231
+ (parameters in MIME headers) */
+ static QCString encodeRFC2231String(const QString& aStr,
+ const QCString& charset);
+
+ /** Decode given string as described in RFC2231 */
+ static QString decodeRFC2231String(const QCString& 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
+ their value as one string. That string will still be encoded */
+ static QCString extractRFC2231HeaderField( const QCString &aStr, const QCString &field );
+
+ /** Calculate the base64 encoded md5sum (sans the trailing equal
+ signs). If @p utf8 is false, uses QString::latin1() to calculate
+ the md5sum of, else uses QString::utf8() */
+ static QString base64EncodedMD5( const QString & aStr, bool utf8=false );
+ static QString base64EncodedMD5( const QCString & aStr );
+ static QString base64EncodedMD5( const char * aStr, int len=-1 );
+
+ /**
+ * Find out preferred charset for 'text'.
+ * First @p encoding is tried and if that one is not suitable,
+ * the encodings in @p encodingList are tried.
+ */
+ static QCString autoDetectCharset(const QCString &encoding, const QStringList &encodingList, const QString &text);
+
+ /** Returns the message serial number for the message. */
+ virtual unsigned long getMsgSerNum() const;
+
+ /** If undo for this message should be enabled */
+ virtual bool enableUndo() { return mEnableUndo; }
+ virtual void setEnableUndo( bool enable ) { mEnableUndo = enable; }
+
+ /** Return if the message has at least one attachment */
+ virtual KMMsgAttachmentState attachmentState() 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
+ sequence of whitespace-delimited prefixes at the beginning of
+ @p str is replaced by @p newPrefix.
+ **/
+ static QString replacePrefixes( const QString& str,
+ const QStringList& prefixRegExps,
+ bool replace,
+ const QString& newPrefix );
+
+ /** Returns @p str with all "forward" and "reply" prefixes stripped off.
+ **/
+ static QString stripOffPrefixes( const QString& str );
+
+ /** Check for prefixes @p prefixRegExps in #subject(). If none
+ is found, @p newPrefix + ' ' is prepended to the subject and the
+ resulting string is returned. If @p replace is true, any
+ sequence of whitespace-delimited prefixes at the beginning of
+ #subject() is replaced by @p newPrefix
+ **/
+ QString cleanSubject(const QStringList& prefixRegExps, bool replace,
+ const QString& newPrefix) const;
+
+ /** Return this mails subject, with all "forward" and "reply"
+ prefixes removed */
+ QString cleanSubject() const;
+
+ /** Return this mails subject, formatted for "forward" mails */
+ QString forwardSubject() const;
+
+ /** Return this mails subject, formatted for "reply" mails */
+ QString replySubject() const;
+
+ /** Reads config settings from group "Composer" and sets all internal
+ * variables (e.g. indent-prefix, etc.) */
+ static void readConfig();
+
+protected:
+ KMFolder* mParent;
+ off_t mIndexOffset;
+ short mIndexLength;
+ bool mDirty;
+ bool mEnableUndo;
+ mutable KMMsgStatus mStatus;
+ // This is kept to provide an upgrade path from the the old single status
+ // to the new multiple status scheme.
+ mutable KMLegacyMsgStatus mLegacyStatus;
+
+public:
+ enum MsgPartType
+ {
+ MsgNoPart = 0,
+ //unicode strings
+ MsgFromPart = 1,
+ MsgSubjectPart = 2,
+ MsgToPart = 3,
+ MsgReplyToIdMD5Part = 4,
+ MsgIdMD5Part = 5,
+ MsgXMarkPart = 6,
+ //unsigned long
+ MsgOffsetPart = 7,
+ MsgLegacyStatusPart = 8,
+ MsgSizePart = 9,
+ MsgDatePart = 10,
+ MsgFilePart = 11,
+ MsgCryptoStatePart = 12,
+ MsgMDNSentPart = 13,
+ //another two unicode strings
+ MsgReplyToAuxIdMD5Part = 14,
+ MsgStrippedSubjectMD5Part = 15,
+ // and another unsigned long
+ MsgStatusPart = 16,
+ MsgSizeServerPart = 17,
+ MsgUIDPart = 18
+ };
+ /** access to long msgparts */
+ off_t getLongPart(MsgPartType) const;
+ /** access to string msgparts */
+ QString getStringPart(MsgPartType) const;
+ /** sync'ing just one KMMsgBase */
+ bool syncIndexString() const;
+};
+
+#endif /*kmmsgbase_h*/
diff --git a/kmail/kmmsgdict.cpp b/kmail/kmmsgdict.cpp
new file mode 100644
index 00000000..6f834e02
--- /dev/null
+++ b/kmail/kmmsgdict.cpp
@@ -0,0 +1,651 @@
+/* kmail message dictionary */
+/* Author: Ronen Tzur <rtzur@shani.net> */
+
+#include "kmfolderindex.h"
+#include "kmfolder.h"
+#include "kmmsgdict.h"
+#include "kmdict.h"
+#include "globalsettings.h"
+#include "folderstorage.h"
+
+#include <qfileinfo.h>
+
+#include <kdebug.h>
+#include <kstaticdeleter.h>
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <string.h>
+#include <errno.h>
+
+#include <config.h>
+
+#ifdef HAVE_BYTESWAP_H
+#include <byteswap.h>
+#endif
+
+// We define functions as kmail_swap_NN so that we don't get compile errors
+// on platforms where bswap_NN happens to be a function instead of a define.
+
+/* Swap bytes in 32 bit value. */
+#ifdef bswap_32
+#define kmail_swap_32(x) bswap_32(x)
+#else
+#define kmail_swap_32(x) \
+ ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \
+ (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))
+#endif
+
+
+//-----------------------------------------------------------------------------
+
+// Current version of the .index.ids files
+#define IDS_VERSION 1002
+
+// The asterisk at the end is important
+#define IDS_HEADER "# KMail-Index-IDs V%d\n*"
+
+/**
+ * @short an entry in the global message dictionary consisting of a pointer
+ * to a folder and the index of a message in the folder.
+ */
+class KMMsgDictEntry : public KMDictItem
+{
+public:
+ KMMsgDictEntry(const KMFolder *aFolder, int aIndex)
+ : folder( aFolder ), index( aIndex )
+ {}
+
+ const KMFolder *folder;
+ int index;
+};
+
+/**
+ * @short A "reverse entry", consisting of an array of DictEntry pointers.
+ *
+ * Each folder (storage) holds such an entry. That's useful for looking up the
+ * serial number of a message at a certain index in the folder, since that is the
+ * key of these entries.
+ */
+class KMMsgDictREntry
+{
+public:
+ KMMsgDictREntry(int size = 0)
+ {
+ array.resize(size);
+ memset(array.data(), 0, array.size() * sizeof(KMMsgDictEntry *)); // faster than a loop
+ fp = 0;
+ swapByteOrder = false;
+ baseOffset = 0;
+ }
+
+ ~KMMsgDictREntry()
+ {
+ array.resize(0);
+ if (fp)
+ fclose(fp);
+ }
+
+ void set(int index, KMMsgDictEntry *entry)
+ {
+ if (index >= 0) {
+ int size = array.size();
+ if (index >= size) {
+ int newsize = QMAX(size + 25, index + 1);
+ array.resize(newsize);
+ for (int j = size; j < newsize; j++)
+ array.at(j) = 0;
+ }
+ array.at(index) = entry;
+ }
+ }
+
+ KMMsgDictEntry *get(int index)
+ {
+ if (index >= 0 && (unsigned)index < array.size())
+ return array.at(index);
+ return 0;
+ }
+
+ ulong getMsn(int index)
+ {
+ KMMsgDictEntry *entry = get(index);
+ if (entry)
+ return entry->key;
+ return 0;
+ }
+
+ int getRealSize()
+ {
+ int count = array.size() - 1;
+ while (count >= 0) {
+ if (array.at(count))
+ break;
+ count--;
+ }
+ return count + 1;
+ }
+
+ void sync()
+ {
+ fflush(fp);
+ }
+
+public:
+ QMemArray<KMMsgDictEntry *> array;
+ FILE *fp;
+ bool swapByteOrder;
+ off_t baseOffset;
+};
+
+
+static KStaticDeleter<KMMsgDict> msgDict_sd;
+KMMsgDict * KMMsgDict::m_self = 0;
+
+//-----------------------------------------------------------------------------
+
+KMMsgDict::KMMsgDict()
+{
+ int lastSizeOfDict = GlobalSettings::self()->msgDictSizeHint();
+ lastSizeOfDict = ( lastSizeOfDict * 11 ) / 10;
+ GlobalSettings::self()->setMsgDictSizeHint( 0 );
+ dict = new KMDict( lastSizeOfDict );
+ nextMsgSerNum = 1;
+ m_self = this;
+}
+
+//-----------------------------------------------------------------------------
+
+KMMsgDict::~KMMsgDict()
+{
+ delete dict;
+}
+
+//-----------------------------------------------------------------------------
+
+const KMMsgDict* KMMsgDict::instance()
+{
+ if ( !m_self ) {
+ msgDict_sd.setObject( m_self, new KMMsgDict() );
+ }
+ return m_self;
+}
+
+KMMsgDict* KMMsgDict::mutableInstance()
+{
+ if ( !m_self ) {
+ msgDict_sd.setObject( m_self, new KMMsgDict() );
+ }
+ return m_self;
+}
+
+//-----------------------------------------------------------------------------
+
+unsigned long KMMsgDict::getNextMsgSerNum() {
+ unsigned long msn = nextMsgSerNum;
+ nextMsgSerNum++;
+ return msn;
+}
+
+void KMMsgDict::deleteRentry(KMMsgDictREntry *entry)
+{
+ delete entry;
+}
+
+unsigned long KMMsgDict::insert(unsigned long msgSerNum,
+ const KMMsgBase *msg, int index)
+{
+ unsigned long msn = msgSerNum;
+ if (!msn) {
+ msn = getNextMsgSerNum();
+ } else {
+ if (msn >= nextMsgSerNum)
+ nextMsgSerNum = msn + 1;
+ }
+
+ KMFolderIndex* folder = static_cast<KMFolderIndex*>( msg->storage() );
+ if ( !folder ) {
+ kdDebug(5006) << "KMMsgDict::insert: Cannot insert the message, "
+ << "null pointer to storage. Requested serial: " << msgSerNum
+ << endl;
+ kdDebug(5006) << " Message info: Subject: " << msg->subject() << ", To: "
+ << msg->toStrip() << ", Date: " << msg->dateStr() << endl;
+ return 0;
+ }
+
+ if (index == -1)
+ index = folder->find(msg);
+
+ // Should not happen, indicates id file corruption
+ while (dict->find((long)msn)) {
+ msn = getNextMsgSerNum();
+ folder->setDirty( true ); // rewrite id file
+ }
+
+ // Insert into the dict. Don't use dict->replace() as we _know_
+ // there is no entry with the same msn, we just made sure.
+ KMMsgDictEntry *entry = new KMMsgDictEntry(folder->folder(), index);
+ dict->insert((long)msn, entry);
+
+ KMMsgDictREntry *rentry = folder->rDict();
+ if (!rentry) {
+ rentry = new KMMsgDictREntry();
+ folder->setRDict(rentry);
+ }
+ rentry->set(index, entry);
+
+ return msn;
+}
+
+unsigned long KMMsgDict::insert(const KMMsgBase *msg, int index)
+{
+ unsigned long msn = msg->getMsgSerNum();
+ return insert(msn, msg, index);
+}
+
+//-----------------------------------------------------------------------------
+
+void KMMsgDict::replace(unsigned long msgSerNum,
+ const KMMsgBase *msg, int index)
+{
+ KMFolderIndex* folder = static_cast<KMFolderIndex*>( msg->storage() );
+ if ( !folder ) {
+ kdDebug(5006) << "KMMsgDict::replace: Cannot replace the message serial "
+ << "number, null pointer to storage. Requested serial: " << msgSerNum
+ << endl;
+ kdDebug(5006) << " Message info: Subject: " << msg->subject() << ", To: "
+ << msg->toStrip() << ", Date: " << msg->dateStr() << endl;
+ return;
+ }
+
+ if ( index == -1 )
+ index = folder->find( msg );
+
+ remove( msgSerNum );
+ KMMsgDictEntry *entry = new KMMsgDictEntry( folder->folder(), index );
+ dict->insert( (long)msgSerNum, entry );
+
+ KMMsgDictREntry *rentry = folder->rDict();
+ if (!rentry) {
+ rentry = new KMMsgDictREntry();
+ folder->setRDict(rentry);
+ }
+ rentry->set(index, entry);
+}
+
+//-----------------------------------------------------------------------------
+
+void KMMsgDict::remove(unsigned long msgSerNum)
+{
+ long key = (long)msgSerNum;
+ KMMsgDictEntry *entry = (KMMsgDictEntry *)dict->find(key);
+ if (!entry)
+ return;
+
+ if (entry->folder) {
+ KMMsgDictREntry *rentry = entry->folder->storage()->rDict();
+ if (rentry)
+ rentry->set(entry->index, 0);
+ }
+
+ dict->remove((long)key);
+}
+
+unsigned long KMMsgDict::remove(const KMMsgBase *msg)
+{
+ unsigned long msn = msg->getMsgSerNum();
+ remove(msn);
+ return msn;
+}
+
+//-----------------------------------------------------------------------------
+
+void KMMsgDict::update(const KMMsgBase *msg, int index, int newIndex)
+{
+ KMMsgDictREntry *rentry = msg->parent()->storage()->rDict();
+ if (rentry) {
+ KMMsgDictEntry *entry = rentry->get(index);
+ if (entry) {
+ entry->index = newIndex;
+ rentry->set(index, 0);
+ rentry->set(newIndex, entry);
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+void KMMsgDict::getLocation(unsigned long key,
+ KMFolder **retFolder, int *retIndex) const
+{
+ KMMsgDictEntry *entry = (KMMsgDictEntry *)dict->find((long)key);
+ if (entry) {
+ *retFolder = (KMFolder *)entry->folder;
+ *retIndex = entry->index;
+ } else {
+ *retFolder = 0;
+ *retIndex = -1;
+ }
+}
+
+void KMMsgDict::getLocation(const KMMsgBase *msg,
+ KMFolder **retFolder, int *retIndex) const
+{
+ getLocation(msg->getMsgSerNum(), retFolder, retIndex);
+}
+
+void KMMsgDict::getLocation( const KMMessage * msg, KMFolder * *retFolder, int * retIndex ) const
+{
+ getLocation( msg->toMsgBase().getMsgSerNum(), retFolder, retIndex );
+}
+
+//-----------------------------------------------------------------------------
+
+unsigned long KMMsgDict::getMsgSerNum(KMFolder *folder, int index) const
+{
+ unsigned long msn = 0;
+ if ( folder ) {
+ KMMsgDictREntry *rentry = folder->storage()->rDict();
+ if (rentry)
+ msn = rentry->getMsn(index);
+ }
+ return msn;
+}
+
+//-----------------------------------------------------------------------------
+
+//static
+QValueList<unsigned long> KMMsgDict::serNumList(QPtrList<KMMsgBase> msgList)
+{
+ QValueList<unsigned long> ret;
+ for ( unsigned int i = 0; i < msgList.count(); i++ ) {
+ unsigned long serNum = msgList.at(i)->getMsgSerNum();
+ assert( serNum );
+ ret.append( serNum );
+ }
+ return ret;
+}
+
+//-----------------------------------------------------------------------------
+
+QString KMMsgDict::getFolderIdsLocation( const FolderStorage &storage )
+{
+ return storage.indexLocation() + ".ids";
+}
+
+//-----------------------------------------------------------------------------
+
+bool KMMsgDict::isFolderIdsOutdated( const FolderStorage &storage )
+{
+ bool outdated = false;
+
+ QFileInfo indexInfo( storage.indexLocation() );
+ QFileInfo idsInfo( getFolderIdsLocation( storage ) );
+
+ if (!indexInfo.exists() || !idsInfo.exists())
+ outdated = true;
+ if (indexInfo.lastModified() > idsInfo.lastModified())
+ outdated = true;
+
+ return outdated;
+}
+
+//-----------------------------------------------------------------------------
+
+int KMMsgDict::readFolderIds( FolderStorage& storage )
+{
+ if ( isFolderIdsOutdated( storage ) )
+ return -1;
+
+ QString filename = getFolderIdsLocation( storage );
+ FILE *fp = fopen(QFile::encodeName(filename), "r+");
+ if (!fp)
+ return -1;
+
+ int version = 0;
+ fscanf(fp, IDS_HEADER, &version);
+ if (version != IDS_VERSION) {
+ fclose(fp);
+ return -1;
+ }
+
+ bool swapByteOrder;
+ Q_UINT32 byte_order;
+ if (!fread(&byte_order, sizeof(byte_order), 1, fp)) {
+ fclose(fp);
+ return -1;
+ }
+ swapByteOrder = (byte_order == 0x78563412);
+
+ Q_UINT32 count;
+ if (!fread(&count, sizeof(count), 1, fp)) {
+ fclose(fp);
+ return -1;
+ }
+ if (swapByteOrder)
+ count = kmail_swap_32(count);
+
+ // quick consistency check to avoid allocating huge amount of memory
+ // due to reading corrupt file (#71549)
+ long pos = ftell(fp); // store current position
+ fseek(fp, 0, SEEK_END);
+ long fileSize = ftell(fp); // how large is the file ?
+ fseek(fp, pos, SEEK_SET); // back to previous position
+
+ // the file must at least contain what we try to read below
+ if ( (fileSize - pos) < (long)(count * sizeof(Q_UINT32)) ) {
+ fclose(fp);
+ return -1;
+ }
+
+ KMMsgDictREntry *rentry = new KMMsgDictREntry(count);
+
+ for (unsigned int index = 0; index < count; index++) {
+ Q_UINT32 msn;
+
+ bool readOk = fread(&msn, sizeof(msn), 1, fp);
+ if (swapByteOrder)
+ msn = kmail_swap_32(msn);
+
+ if (!readOk || dict->find(msn)) {
+ for (unsigned int i = 0; i < index; i++) {
+ msn = rentry->getMsn(i);
+ dict->remove((long)msn);
+ }
+ delete rentry;
+ fclose(fp);
+ return -1;
+ }
+
+ //if (!msn)
+ //kdDebug(5006) << "Dict found zero serial number in folder " << folder->label() << endl;
+
+ // Insert into the dict. Don't use dict->replace() as we _know_
+ // there is no entry with the same msn, we just made sure.
+ KMMsgDictEntry *entry = new KMMsgDictEntry( storage.folder(), index);
+ dict->insert((long)msn, entry);
+ if (msn >= nextMsgSerNum)
+ nextMsgSerNum = msn + 1;
+
+ rentry->set(index, entry);
+ }
+ // Remember how many items we put into the dict this time so we can create
+ // it with an appropriate size next time.
+ GlobalSettings::setMsgDictSizeHint( GlobalSettings::msgDictSizeHint() + count );
+
+ fclose(fp);
+ storage.setRDict(rentry);
+
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+
+KMMsgDictREntry *KMMsgDict::openFolderIds( const FolderStorage& storage, bool truncate)
+{
+ KMMsgDictREntry *rentry = storage.rDict();
+ if (!rentry) {
+ rentry = new KMMsgDictREntry();
+ storage.setRDict(rentry);
+ }
+
+ if (!rentry->fp) {
+ QString filename = getFolderIdsLocation( storage );
+ FILE *fp = truncate ? 0 : fopen(QFile::encodeName(filename), "r+");
+ if (fp)
+ {
+ int version = 0;
+ fscanf(fp, IDS_HEADER, &version);
+ if (version == IDS_VERSION)
+ {
+ Q_UINT32 byte_order = 0;
+ fread(&byte_order, sizeof(byte_order), 1, fp);
+ rentry->swapByteOrder = (byte_order == 0x78563412);
+ }
+ else
+ {
+ fclose(fp);
+ fp = 0;
+ }
+ }
+
+ if (!fp)
+ {
+ fp = fopen(QFile::encodeName(filename), "w+");
+ if (!fp)
+ {
+ kdDebug(5006) << "Dict '" << filename
+ << "' cannot open with folder " << storage.label() << ": "
+ << strerror(errno) << " (" << errno << ")" << endl;
+ delete rentry;
+ rentry = 0;
+ return 0;
+ }
+ fprintf(fp, IDS_HEADER, IDS_VERSION);
+ Q_UINT32 byteOrder = 0x12345678;
+ fwrite(&byteOrder, sizeof(byteOrder), 1, fp);
+ rentry->swapByteOrder = false;
+ }
+ rentry->baseOffset = ftell(fp);
+ rentry->fp = fp;
+ }
+
+ return rentry;
+}
+
+//-----------------------------------------------------------------------------
+
+int KMMsgDict::writeFolderIds( const FolderStorage &storage )
+{
+ KMMsgDictREntry *rentry = openFolderIds( storage, true );
+ if (!rentry)
+ return 0;
+ FILE *fp = rentry->fp;
+
+ fseek(fp, rentry->baseOffset, SEEK_SET);
+ // kdDebug(5006) << "Dict writing for folder " << storage.label() << endl;
+ Q_UINT32 count = rentry->getRealSize();
+ if (!fwrite(&count, sizeof(count), 1, fp)) {
+ kdDebug(5006) << "Dict cannot write count with folder " << storage.label() << ": "
+ << strerror(errno) << " (" << errno << ")" << endl;
+ return -1;
+ }
+
+ for (unsigned int index = 0; index < count; index++) {
+ Q_UINT32 msn = rentry->getMsn(index);
+ if (!fwrite(&msn, sizeof(msn), 1, fp))
+ return -1;
+ }
+
+ rentry->sync();
+
+ off_t eof = ftell(fp);
+ QString filename = getFolderIdsLocation( storage );
+ truncate(QFile::encodeName(filename), eof);
+ fclose(rentry->fp);
+ rentry->fp = 0;
+
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+
+int KMMsgDict::touchFolderIds( const FolderStorage &storage )
+{
+ KMMsgDictREntry *rentry = openFolderIds( storage, false);
+ if (rentry) {
+ rentry->sync();
+ fclose(rentry->fp);
+ rentry->fp = 0;
+ }
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+
+int KMMsgDict::appendToFolderIds( FolderStorage& storage, int index)
+{
+ KMMsgDictREntry *rentry = openFolderIds( storage, false);
+ if (!rentry)
+ return 0;
+ FILE *fp = rentry->fp;
+
+// kdDebug(5006) << "Dict appending for folder " << storage.label() << endl;
+
+ fseek(fp, rentry->baseOffset, SEEK_SET);
+ Q_UINT32 count;
+ if (!fread(&count, sizeof(count), 1, fp)) {
+ kdDebug(5006) << "Dict cannot read count for folder " << storage.label() << ": "
+ << strerror(errno) << " (" << errno << ")" << endl;
+ return 0;
+ }
+ if (rentry->swapByteOrder)
+ count = kmail_swap_32(count);
+
+ count++;
+
+ if (rentry->swapByteOrder)
+ count = kmail_swap_32(count);
+ fseek(fp, rentry->baseOffset, SEEK_SET);
+ if (!fwrite(&count, sizeof(count), 1, fp)) {
+ kdDebug(5006) << "Dict cannot write count for folder " << storage.label() << ": "
+ << strerror(errno) << " (" << errno << ")" << endl;
+ return 0;
+ }
+
+ long ofs = (count - 1) * sizeof(ulong);
+ if (ofs > 0)
+ fseek(fp, ofs, SEEK_CUR);
+
+ Q_UINT32 msn = rentry->getMsn(index);
+ if (rentry->swapByteOrder)
+ msn = kmail_swap_32(msn);
+ if (!fwrite(&msn, sizeof(msn), 1, fp)) {
+ kdDebug(5006) << "Dict cannot write count for folder " << storage.label() << ": "
+ << strerror(errno) << " (" << errno << ")" << endl;
+ return 0;
+ }
+
+ rentry->sync();
+ fclose(rentry->fp);
+ rentry->fp = 0;
+
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+
+bool KMMsgDict::hasFolderIds( const FolderStorage& storage )
+{
+ return storage.rDict() != 0;
+}
+
+//-----------------------------------------------------------------------------
+
+bool KMMsgDict::removeFolderIds( FolderStorage& storage )
+{
+ storage.setRDict( 0 );
+ QString filename = getFolderIdsLocation( storage );
+ return unlink( QFile::encodeName( filename) );
+}
diff --git a/kmail/kmmsgdict.h b/kmail/kmmsgdict.h
new file mode 100644
index 00000000..0a4978e5
--- /dev/null
+++ b/kmail/kmmsgdict.h
@@ -0,0 +1,168 @@
+/*
+ * This file is part of KMail, the KDE mail client
+ * Copyright (c) Ronen Tzur <rtzur@shani.net>
+ *
+ * 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 __KMMSGDICT
+#define __KMMSGDICT
+
+#include <qvaluelist.h>
+#include <qptrlist.h>
+
+class KMFolder;
+class KMMsgBase;
+class KMMessage;
+class KMMsgDictEntry;
+class KMMsgDictREntry;
+class KMDict;
+class QString;
+class FolderStorage;
+
+/**
+ * @short KMail message dictionary. Keeps location information for every
+ * message. The message serial number is the key for the dictionary.
+ *
+ * The KMMsgDict singleton is used to look up at which index in which folder a
+ * certain serial number can be found. Each folder holds a "reverse entry",
+ * which is an array of message dict entries for that folder and persists that
+ * to disk as an array of serial numbers, the "$folder.index.ids" file.
+ * In effect the whole message dict is therefor persisted per folder
+ * and restored on startup when all folder dict entries are read and re-enter
+ * their respective entries (serial numbers) into the global dict. The code for
+ * creating, deleting and manipulating these files is in this class, rather than
+ * the FolderStorage class, which only holds the pointer to the reverse entry
+ * and otherwise knows nothing of the message dict.
+ *
+ * @author Ronen Tzur <rtzur@shani.net>
+ */
+class KMMsgDict
+{
+ template<class> friend class KStaticDeleter;
+ public:
+ /** Access the globally unique MessageDict */
+ static const KMMsgDict* instance();
+
+ /** Returns the folder the message represented by the serial number @p key is in
+ * and the index in that folder at which it is stored. */
+ void getLocation( unsigned long key, KMFolder **retFolder, int *retIndex ) const;
+ /** Returns the folder the message represented by @p msg is in
+ * and the index in that folder at which it is stored. */
+ void getLocation( const KMMsgBase *msg, KMFolder **retFolder, int *retIndex ) const;
+ /** Returns the folder the message represented by @p msg is in
+ * and the index in that folder at which it is stored. */
+ void getLocation( const KMMessage *msg, KMFolder **retFolder, int *retIndex ) const;
+
+ /** Find the message serial number for the message located at index @p index in folder
+ * @p folder.
+ * @return the message serial number or zero is no such message can be found */
+ unsigned long getMsgSerNum( KMFolder *folder, int index ) const;
+
+ /** Convert a list of KMMsgBase pointers to a list of serial numbers */
+ static QValueList<unsigned long> serNumList(QPtrList<KMMsgBase> msgList);
+
+private:
+ /* FIXME It would be better to do without these, they are the classes
+ * involved in filling and maintaining the dict. The MsgList needs access
+ * because of things it does that should be in FolderIndex, probably, which
+ * the message list is an implementation detail of. */
+ friend class FolderStorage;
+ friend class KMMsgList;
+ friend class KMFolderIndex;
+
+ // Access for those altering the dict, our friend classes
+ static KMMsgDict* mutableInstance();
+
+ /** Insert a new message. The message serial number is specified in
+ * @p msgSerNum and may be zero, in which case a new serial number is
+ * generated. Returns the message serial number. */
+ unsigned long insert(unsigned long msgSerNum, const KMMsgBase *msg, int index = -1);
+
+ /** Insert a new message. The message serial number is taken from
+ * the message, and passed to the other insert(). Returns the message
+ * serial number. */
+ unsigned long insert(const KMMsgBase *msg, int index = -1);
+
+ /** Set the serial number of @p msg to @p msgSerNum */
+ void replace(unsigned long msgSerNum,
+ const KMMsgBase *msg, int index = -1);
+
+ /** Removes a message. */
+ void remove(unsigned long msgSerNum);
+
+ /** Removes a message, and returns its message serial number. */
+ unsigned long remove(const KMMsgBase *msg);
+
+ /** Updates index for a message. */
+ void update(const KMMsgBase *msg, int index, int newIndex);
+
+
+ // ----- per folder serial number on-disk structure handling ("ids files")
+
+ /** Returns the name of the .folder.index.ids file. */
+ static QString getFolderIdsLocation( const FolderStorage &folder );
+
+ /** Returns TRUE if the .folder.index.ids file should not be read. */
+ bool isFolderIdsOutdated( const FolderStorage &folder );
+
+ /** Reads the .folder.index.ids file. Returns 0 on success. */
+ int readFolderIds( FolderStorage & );
+
+ /** Writes the .folder.index.ids file. Returns 0 on success. */
+ int writeFolderIds( const FolderStorage & );
+
+ /** Touches the .folder.index.ids file. Returns 0 on success. */
+ int touchFolderIds( const FolderStorage & );
+
+ /** Appends the message to the .folder.index.ids file.
+ * Returns 0 on success. */
+ int appendToFolderIds( FolderStorage&, int index );
+
+ /** Returns true if the folder has a .folder.index.ids file. */
+ bool hasFolderIds( const FolderStorage & );
+
+ /** Removes the .folder.index.ids file. */
+ bool removeFolderIds( FolderStorage & );
+
+ /** Opens the .folder.index.ids file, and writes the header
+ * information at the beginning of the file. */
+ KMMsgDictREntry *openFolderIds( const FolderStorage &, bool truncate);
+
+
+ // --------- helpers ------------
+
+ /** delete an entry that has been assigned to a folder. Needs to be done from
+ * inside this file, since operator delete is not available outside. */
+ static void deleteRentry(KMMsgDictREntry *entry);
+
+ /** Returns the next message serial number for use. */
+ unsigned long getNextMsgSerNum();
+
+ // prevent creation and deletion, we are a singleton
+ KMMsgDict();
+ ~KMMsgDict();
+
+ /** Highest message serial number we know of. */
+ unsigned long nextMsgSerNum;
+
+ /** The dictionary. */
+ KMDict *dict;
+
+ /** The singleton instance */
+ static KMMsgDict *m_self;
+};
+
+#endif /* __KMMSGDICT */
diff --git a/kmail/kmmsginfo.cpp b/kmail/kmmsginfo.cpp
new file mode 100644
index 00000000..67c33588
--- /dev/null
+++ b/kmail/kmmsginfo.cpp
@@ -0,0 +1,700 @@
+// kmmsginfo.cpp
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "kmmsginfo.h"
+#include "kmmessage.h"
+//#include "kmmsgpart.h" // for encode
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+#include <mimelib/datetime.h>
+
+class KMMsgInfo::KMMsgInfoPrivate
+{
+public:
+ enum {
+ SUBJECT_SET = 0x01, TO_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,
+ SIGNATURE_SET=0x1000, MDN_SET=0x2000, REPLYTOAUX_SET = 0x4000,
+ STRIPPEDSUBJECT_SET = 0x8000, UID_SET = 0x10000,
+
+ ALL_SET = 0xFFFFFF, NONE_SET = 0x000000
+ };
+ uint modifiers;
+ QString subject, from, to, replyToIdMD5, replyToAuxIdMD5,
+ strippedSubjectMD5, msgIdMD5, xmark, file;
+ off_t folderOffset;
+ size_t msgSize, msgSizeServer;
+ time_t date;
+ KMMsgEncryptionState encryptionState;
+ KMMsgSignatureState signatureState;
+ KMMsgMDNSentState mdnSentState;
+ ulong UID;
+
+ KMMsgInfoPrivate() : modifiers(NONE_SET) { }
+ KMMsgInfoPrivate& operator=(const KMMsgInfoPrivate& other) {
+ modifiers = NONE_SET;
+ if (other.modifiers & SUBJECT_SET) {
+ modifiers |= SUBJECT_SET;
+ subject = other.subject;
+ }
+ if (other.modifiers & STRIPPEDSUBJECT_SET) {
+ modifiers |= STRIPPEDSUBJECT_SET;
+ strippedSubjectMD5 = other.strippedSubjectMD5;
+ }
+ if (other.modifiers & FROM_SET) {
+ modifiers |= FROM_SET;
+ from = other.from;
+ }
+ if (other.modifiers & FILE_SET) {
+ modifiers |= FILE_SET;
+ file = other.from;
+ }
+ if (other.modifiers & TO_SET) {
+ modifiers |= TO_SET;
+ to = other.to;
+ }
+ if (other.modifiers & REPLYTO_SET) {
+ modifiers |= REPLYTO_SET;
+ replyToIdMD5 = other.replyToIdMD5;
+ }
+ if (other.modifiers & REPLYTOAUX_SET) {
+ modifiers |= REPLYTOAUX_SET;
+ replyToAuxIdMD5 = other.replyToAuxIdMD5;
+ }
+
+ if(other.modifiers & MSGID_SET) {
+ modifiers |= MSGID_SET;
+ msgIdMD5 = other.msgIdMD5;
+ }
+ if(other.modifiers & XMARK_SET) {
+ modifiers |= XMARK_SET;
+ xmark = other.xmark;
+ }
+ if(other.modifiers & OFFSET_SET) {
+ modifiers |= OFFSET_SET;
+ folderOffset = other.folderOffset;
+ }
+ if(other.modifiers & SIZE_SET) {
+ modifiers |= SIZE_SET;
+ msgSize = other.msgSize;
+ }
+ if(other.modifiers & DATE_SET) {
+ modifiers |= DATE_SET;
+ date = other.date;
+ }
+ if(other.modifiers & ENCRYPTION_SET) {
+ modifiers |= ENCRYPTION_SET;
+ encryptionState = other.encryptionState;
+ }
+ if(other.modifiers & SIGNATURE_SET) {
+ modifiers |= SIGNATURE_SET;
+ signatureState = other.signatureState;
+ }
+ if(other.modifiers & MDN_SET) {
+ modifiers |= MDN_SET;
+ mdnSentState = other.mdnSentState;
+ }
+ if(other.modifiers & SIZESERVER_SET) {
+ modifiers |= SIZESERVER_SET;
+ msgSizeServer = other.msgSizeServer;
+ }
+ if(other.modifiers & UID_SET) {
+ modifiers |= UID_SET;
+ UID = other.UID;
+ }
+ return *this;
+ }
+};
+
+//-----------------------------------------------------------------------------
+KMMsgInfo::KMMsgInfo(KMFolder* p, off_t off, short len) :
+ KMMsgBase(p),
+ kd(0)
+{
+ setIndexOffset(off);
+ setIndexLength(len);
+ setEnableUndo(true);
+}
+
+
+//-----------------------------------------------------------------------------
+KMMsgInfo::~KMMsgInfo()
+{
+ delete kd;
+}
+
+
+#if 0 // currently unused
+//-----------------------------------------------------------------------------
+KMMsgInfo& KMMsgInfo::operator=(const KMMsgInfo& other)
+{
+ KMMsgBase::assign(&other);
+ if(other.kd) {
+ if(!kd)
+ kd = new KMMsgInfoPrivate;
+ *kd = *other.kd;
+ } else {
+ delete kd;
+ kd = 0;
+ }
+ mStatus = other.status();
+ return *this;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+KMMsgInfo& KMMsgInfo::operator=(const KMMessage& msg)
+{
+ KMMsgBase::assign(&msg.toMsgBase());
+ if(!kd)
+ kd = new KMMsgInfoPrivate;
+ kd->modifiers = KMMsgInfoPrivate::ALL_SET;
+ kd->subject = msg.subject();
+ kd->from = msg.fromStrip();
+ kd->to = msg.toStrip();
+ kd->replyToIdMD5 = msg.replyToIdMD5();
+ kd->replyToAuxIdMD5 = msg.replyToAuxIdMD5();
+ kd->strippedSubjectMD5 = msg.strippedSubjectMD5();
+ kd->msgIdMD5 = msg.msgIdMD5();
+ kd->xmark = msg.xmark();
+ mStatus = msg.status();
+ kd->folderOffset = msg.folderOffset();
+ kd->msgSize = msg.msgSize();
+ kd->date = msg.date();
+ kd->file = msg.fileName();
+ kd->encryptionState = msg.encryptionState();
+ kd->signatureState = msg.signatureState();
+ kd->mdnSentState = msg.mdnSentState();
+ kd->msgSizeServer = msg.msgSizeServer();
+ kd->UID = msg.UID();
+ return *this;
+}
+
+//-----------------------------------------------------------------------------
+void KMMsgInfo::init(const QCString& aSubject, const QCString& aFrom,
+ const QCString& aTo, time_t aDate,
+ KMMsgStatus aStatus, const QCString& aXMark,
+ const QCString& replyToId, const QCString& replyToAuxId,
+ const QCString& msgId,
+ KMMsgEncryptionState encryptionState,
+ KMMsgSignatureState signatureState,
+ KMMsgMDNSentState mdnSentState,
+ const QCString& prefCharset,
+ off_t aFolderOffset, size_t aMsgSize,
+ size_t aMsgSizeServer, ulong aUID)
+{
+ mIndexOffset = 0;
+ mIndexLength = 0;
+ if(!kd)
+ 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->replyToIdMD5 = base64EncodedMD5( replyToId );
+ kd->replyToAuxIdMD5 = base64EncodedMD5( replyToAuxId );
+ kd->strippedSubjectMD5 = base64EncodedMD5( KMMessage::stripOffPrefixes( kd->subject ), true /*utf8*/ );
+ kd->msgIdMD5 = base64EncodedMD5( msgId );
+ kd->xmark = aXMark;
+ kd->folderOffset = aFolderOffset;
+ mStatus = aStatus;
+ kd->msgSize = aMsgSize;
+ kd->date = aDate;
+ kd->file = "";
+ kd->encryptionState = encryptionState;
+ kd->signatureState = signatureState;
+ kd->mdnSentState = mdnSentState;
+ kd->msgSizeServer = aMsgSizeServer;
+ kd->UID = aUID;
+ mDirty = false;
+}
+
+void KMMsgInfo::init(const QCString& aSubject, const QCString& aFrom,
+ const QCString& aTo, time_t aDate,
+ KMMsgStatus aStatus, const QCString& aXMark,
+ const QCString& replyToId, const QCString& replyToAuxId,
+ const QCString& msgId,
+ const QCString& aFileName,
+ KMMsgEncryptionState encryptionState,
+ KMMsgSignatureState signatureState,
+ KMMsgMDNSentState mdnSentState,
+ const QCString& prefCharset,
+ size_t aMsgSize,
+ size_t aMsgSizeServer, ulong aUID)
+{
+ // use the "normal" init for most stuff
+ init( aSubject, aFrom, aTo, aDate, aStatus, aXMark, replyToId, replyToAuxId,
+ msgId, encryptionState, signatureState, mdnSentState, prefCharset,
+ (unsigned long)0, aMsgSize, aMsgSizeServer, aUID );
+ kd->file = aFileName;
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMsgInfo::subject(void) const
+{
+ if (kd && kd->modifiers & KMMsgInfoPrivate::SUBJECT_SET)
+ return kd->subject;
+ return getStringPart(MsgSubjectPart);
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMsgInfo::fromStrip(void) const
+{
+ if (kd && kd->modifiers & KMMsgInfoPrivate::FROM_SET)
+ return kd->from;
+ return getStringPart(MsgFromPart);
+}
+
+//-----------------------------------------------------------------------------
+QString KMMsgInfo::fileName(void) const
+{
+ if (kd && kd->modifiers & KMMsgInfoPrivate::FILE_SET)
+ return kd->file;
+ return getStringPart(MsgFilePart);
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMsgInfo::toStrip(void) const
+{
+ if (kd && kd->modifiers & KMMsgInfoPrivate::TO_SET)
+ return kd->to;
+ return getStringPart(MsgToPart);
+}
+
+//-----------------------------------------------------------------------------
+QString KMMsgInfo::xmark(void) const
+{
+ if (kd && kd->modifiers & KMMsgInfoPrivate::XMARK_SET)
+ return kd->xmark;
+ return getStringPart(MsgXMarkPart);
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMsgInfo::replyToIdMD5(void) const
+{
+ if (kd && kd->modifiers & KMMsgInfoPrivate::REPLYTO_SET)
+ return kd->replyToIdMD5;
+ return getStringPart(MsgReplyToIdMD5Part);
+}
+
+//-----------------------------------------------------------------------------
+QString KMMsgInfo::replyToAuxIdMD5() const
+{
+ if( kd && kd->modifiers & KMMsgInfoPrivate::REPLYTOAUX_SET )
+ return kd->replyToAuxIdMD5;
+ return getStringPart( MsgReplyToAuxIdMD5Part );
+}
+
+//-----------------------------------------------------------------------------
+QString KMMsgInfo::strippedSubjectMD5() const
+{
+ if( kd && kd->modifiers & KMMsgInfoPrivate::STRIPPEDSUBJECT_SET )
+ return kd->strippedSubjectMD5;
+ return getStringPart( MsgStrippedSubjectMD5Part );
+}
+
+
+//-----------------------------------------------------------------------------
+bool KMMsgInfo::subjectIsPrefixed() const
+{
+ return strippedSubjectMD5() != base64EncodedMD5( subject().stripWhiteSpace(), true /*utf8*/ );
+}
+
+//-----------------------------------------------------------------------------
+QString KMMsgInfo::msgIdMD5(void) const
+{
+ if (kd && kd->modifiers & KMMsgInfoPrivate::MSGID_SET)
+ return kd->msgIdMD5;
+ return getStringPart(MsgIdMD5Part);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMsgInfo::setSubject(const QString& aSubject)
+{
+ if(aSubject == subject())
+ return;
+
+ if (!kd)
+ kd = new KMMsgInfoPrivate;
+ kd->modifiers |= KMMsgInfoPrivate::SUBJECT_SET;
+ kd->subject = aSubject;
+ mDirty = true;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMsgInfo::setXMark(const QString& aXMark)
+{
+ if (aXMark == xmark())
+ return;
+
+ if (!kd)
+ kd = new KMMsgInfoPrivate;
+ kd->modifiers |= KMMsgInfoPrivate::XMARK_SET;
+ kd->xmark = aXMark;
+ mDirty = true;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMsgInfo::setReplyToIdMD5(const QString& aReplyToIdMD5)
+{
+ if (aReplyToIdMD5 == replyToIdMD5())
+ return;
+
+ if (!kd)
+ kd = new KMMsgInfoPrivate;
+ kd->modifiers |= KMMsgInfoPrivate::REPLYTO_SET;
+ kd->replyToIdMD5 = aReplyToIdMD5;
+ mDirty = true;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMsgInfo::setReplyToAuxIdMD5( const QString& aReplyToAuxIdMD5 )
+{
+ if( aReplyToAuxIdMD5 == replyToAuxIdMD5() )
+ return;
+
+ if( !kd )
+ kd = new KMMsgInfoPrivate;
+ kd->modifiers |= KMMsgInfoPrivate::REPLYTOAUX_SET;
+ kd->replyToAuxIdMD5 = aReplyToAuxIdMD5;
+ mDirty = true;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMsgInfo::initStrippedSubjectMD5()
+{
+ if( kd && kd->modifiers & KMMsgInfoPrivate::STRIPPEDSUBJECT_SET )
+ return;
+ QString rawSubject = KMMessage::stripOffPrefixes( subject() );
+ QString subjectMD5 = base64EncodedMD5( rawSubject, true /*utf8*/ );
+ if( !kd )
+ kd = new KMMsgInfoPrivate;
+ kd->modifiers |= KMMsgInfoPrivate::STRIPPEDSUBJECT_SET;
+ kd->strippedSubjectMD5 = subjectMD5;
+ mDirty = true;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMsgInfo::setMsgIdMD5(const QString& aMsgIdMD5)
+{
+ if (aMsgIdMD5 == msgIdMD5())
+ return;
+
+ if (!kd)
+ kd = new KMMsgInfoPrivate;
+ kd->modifiers |= KMMsgInfoPrivate::MSGID_SET;
+ kd->msgIdMD5 = aMsgIdMD5;
+ mDirty = true;
+}
+
+//-----------------------------------------------------------------------------
+void KMMsgInfo::setEncryptionState( const KMMsgEncryptionState s, int idx )
+{
+ if (s == encryptionState())
+ return;
+
+ if (!kd)
+ kd = new KMMsgInfoPrivate;
+ kd->modifiers |= KMMsgInfoPrivate::ENCRYPTION_SET;
+ kd->encryptionState = s;
+ KMMsgBase::setEncryptionState(s, idx); //base does more "stuff"
+ mDirty = true;
+}
+
+//-----------------------------------------------------------------------------
+void KMMsgInfo::setSignatureState( const KMMsgSignatureState s, int idx )
+{
+ if (s == signatureState())
+ return;
+
+ if (!kd)
+ kd = new KMMsgInfoPrivate;
+ kd->modifiers |= KMMsgInfoPrivate::SIGNATURE_SET;
+ kd->signatureState = s;
+ KMMsgBase::setSignatureState(s, idx); //base does more "stuff"
+ mDirty = true;
+}
+
+//-----------------------------------------------------------------------------
+void KMMsgInfo::setMDNSentState( const KMMsgMDNSentState s, int idx )
+{
+ if (s == mdnSentState())
+ return;
+
+ if (!kd)
+ kd = new KMMsgInfoPrivate;
+ kd->modifiers |= KMMsgInfoPrivate::MDN_SET;
+ kd->mdnSentState = s;
+ KMMsgBase::setMDNSentState(s, idx); //base does more "stuff"
+ mDirty = true;
+}
+
+//-----------------------------------------------------------------------------
+KMMsgStatus KMMsgInfo::status(void) const
+{
+ if (mStatus == KMMsgStatusUnknown) {
+ KMMsgStatus st = (KMMsgStatus)getLongPart(MsgStatusPart);
+ if (!st) {
+ // We are opening an old index for the first time, get the legacy
+ // status and merge it in.
+ mLegacyStatus = (KMLegacyMsgStatus)getLongPart(MsgLegacyStatusPart);
+ st = KMMsgStatusRead;
+ switch (mLegacyStatus) {
+ case KMLegacyMsgStatusUnknown:
+ st = KMMsgStatusUnknown;
+ break;
+ case KMLegacyMsgStatusNew:
+ st = KMMsgStatusNew;
+ break;
+ case KMLegacyMsgStatusUnread:
+ st = KMMsgStatusUnread;
+ break;
+ case KMLegacyMsgStatusRead:
+ st = KMMsgStatusRead;
+ break;
+ case KMLegacyMsgStatusOld:
+ st = KMMsgStatusOld;
+ break;
+ case KMLegacyMsgStatusDeleted:
+ st |= KMMsgStatusDeleted;
+ break;
+ case KMLegacyMsgStatusReplied:
+ st |= KMMsgStatusReplied;
+ break;
+ case KMLegacyMsgStatusForwarded:
+ st |= KMMsgStatusForwarded;
+ break;
+ case KMLegacyMsgStatusQueued:
+ st |= KMMsgStatusQueued;
+ break;
+ case KMLegacyMsgStatusSent:
+ st |= KMMsgStatusSent;
+ break;
+ case KMLegacyMsgStatusFlag:
+ st |= KMMsgStatusFlag;
+ break;
+ default:
+ break;
+ }
+
+ }
+ mStatus = st;
+ }
+ return mStatus;
+}
+
+
+//-----------------------------------------------------------------------------
+KMMsgEncryptionState KMMsgInfo::encryptionState() const
+{
+ if (kd && kd->modifiers & KMMsgInfoPrivate::ENCRYPTION_SET)
+ return kd->encryptionState;
+ unsigned long encState = getLongPart(MsgCryptoStatePart) & 0x0000FFFF;
+ return encState ? (KMMsgEncryptionState)encState : KMMsgEncryptionStateUnknown;
+}
+
+
+KMMsgSignatureState KMMsgInfo::signatureState() const
+{
+ if (kd && kd->modifiers & KMMsgInfoPrivate::SIGNATURE_SET)
+ return kd->signatureState;
+ unsigned long sigState = getLongPart(MsgCryptoStatePart) >> 16;
+ return sigState ? (KMMsgSignatureState)sigState : KMMsgSignatureStateUnknown;
+}
+
+KMMsgMDNSentState KMMsgInfo::mdnSentState() const {
+ if (kd && kd->modifiers & KMMsgInfoPrivate::MDN_SET)
+ return kd->mdnSentState;
+ unsigned long mdnState = getLongPart(MsgMDNSentPart);
+ return mdnState ? (KMMsgMDNSentState)mdnState : KMMsgMDNStateUnknown;
+}
+
+
+//-----------------------------------------------------------------------------
+off_t KMMsgInfo::folderOffset(void) const
+{
+ if (kd && kd->modifiers & KMMsgInfoPrivate::OFFSET_SET)
+ return kd->folderOffset;
+ return getLongPart(MsgOffsetPart);
+}
+
+//-----------------------------------------------------------------------------
+size_t KMMsgInfo::msgSize(void) const
+{
+ if (kd && kd->modifiers & KMMsgInfoPrivate::SIZE_SET)
+ return kd->msgSize;
+ return getLongPart(MsgSizePart);
+}
+
+//-----------------------------------------------------------------------------
+time_t KMMsgInfo::date(void) const
+{
+ time_t res;
+ if (kd && kd->modifiers & KMMsgInfoPrivate::DATE_SET)
+ res = kd->date;
+ else
+ res = getLongPart(MsgDatePart);
+ return res;
+}
+
+//-----------------------------------------------------------------------------
+size_t KMMsgInfo::msgSizeServer(void) const
+{
+ if (kd && kd->modifiers & KMMsgInfoPrivate::SIZESERVER_SET)
+ return kd->msgSizeServer;
+ return getLongPart(MsgSizeServerPart);
+}
+
+//-----------------------------------------------------------------------------
+ulong KMMsgInfo::UID(void) const
+{
+ if (kd && kd->modifiers & KMMsgInfoPrivate::UID_SET)
+ return kd->UID;
+ return getLongPart(MsgUIDPart);
+}
+
+//-----------------------------------------------------------------------------
+void KMMsgInfo::setMsgSize(size_t sz)
+{
+ if (sz == msgSize())
+ return;
+
+ if(!kd)
+ kd = new KMMsgInfoPrivate;
+ kd->modifiers |= KMMsgInfoPrivate::SIZE_SET;
+ kd->msgSize = sz;
+ mDirty = true;
+}
+
+//-----------------------------------------------------------------------------
+void KMMsgInfo::setMsgSizeServer(size_t sz)
+{
+ if (sz == msgSizeServer())
+ return;
+
+ if(!kd)
+ kd = new KMMsgInfoPrivate;
+ kd->modifiers |= KMMsgInfoPrivate::SIZESERVER_SET;
+ kd->msgSizeServer = sz;
+ mDirty = true;
+}
+
+//-----------------------------------------------------------------------------
+void KMMsgInfo::setUID(ulong uid)
+{
+ if (uid == UID())
+ return;
+
+ if(!kd)
+ kd = new KMMsgInfoPrivate;
+ kd->modifiers |= KMMsgInfoPrivate::UID_SET;
+ kd->UID = uid;
+ mDirty = true;
+}
+
+//-----------------------------------------------------------------------------
+void KMMsgInfo::setFolderOffset(off_t offs)
+{
+ if (folderOffset() == offs)
+ return;
+
+ if (!kd)
+ kd = new KMMsgInfoPrivate;
+ kd->modifiers |= KMMsgInfoPrivate::OFFSET_SET;
+ kd->folderOffset = offs;
+ mDirty = true;
+}
+
+//-----------------------------------------------------------------------------
+void KMMsgInfo::setFileName(const QString& file)
+{
+ if (fileName() == file)
+ return;
+
+ if (!kd)
+ kd = new KMMsgInfoPrivate;
+ kd->modifiers |= KMMsgInfoPrivate::FILE_SET;
+ kd->file = file;
+ mDirty = true;
+}
+
+//-----------------------------------------------------------------------------
+void KMMsgInfo::setStatus(const KMMsgStatus aStatus, int idx)
+{
+ if(aStatus == status())
+ return;
+ KMMsgBase::setStatus(aStatus, idx); //base does more "stuff"
+}
+
+//-----------------------------------------------------------------------------
+void KMMsgInfo::setDate(time_t aUnixTime)
+{
+ if(aUnixTime == date())
+ return;
+
+ if(!kd)
+ kd = new KMMsgInfoPrivate;
+ kd->modifiers |= KMMsgInfoPrivate::DATE_SET;
+ kd->date = aUnixTime;
+ mDirty = true;
+}
+
+//--- For compatability with old index files
+void KMMsgInfo::compat_fromOldIndexString(const QCString& str, bool toUtf8)
+{
+ char *start, *offset;
+
+ if(!kd)
+ kd = new KMMsgInfoPrivate;
+ kd->modifiers = KMMsgInfoPrivate::ALL_SET;
+ kd->xmark = str.mid(33, 3).stripWhiteSpace();
+ kd->folderOffset = str.mid(2,9).toULong();
+ kd->msgSize = str.mid(12,9).toULong();
+ kd->date = (time_t)str.mid(22,10).toULong();
+ 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();
+ } else {
+ start = offset = str.data() + 37;
+ while (*start == ' ' && start - offset < 100) start++;
+ kd->subject = QString::fromUtf8(str.mid(start - str.data(),
+ 100 - (start - offset)), 100 - (start - offset));
+ start = offset = str.data() + 138;
+ while (*start == ' ' && start - offset < 50) start++;
+ kd->from = QString::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 = QString::fromUtf8(str.mid(start - str.data(),
+ 50 - (start - offset)), 50 - (start - offset));
+ }
+ kd->replyToIdMD5 = str.mid(240, 22).stripWhiteSpace();
+ kd->msgIdMD5 = str.mid(263, 22).stripWhiteSpace();
+ mDirty = false;
+}
+
+bool KMMsgInfo::dirty(void) const
+{
+ if( KMMsgBase::dirty() )
+ return true;
+ return kd && kd->modifiers != KMMsgInfoPrivate::NONE_SET;
+}
diff --git a/kmail/kmmsginfo.h b/kmail/kmmsginfo.h
new file mode 100644
index 00000000..e4db8d47
--- /dev/null
+++ b/kmail/kmmsginfo.h
@@ -0,0 +1,126 @@
+/*
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ *
+ * 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 kmmsginfo_h
+#define kmmsginfo_h
+
+#include <config.h>
+#include <sys/types.h>
+#include "kmmsgbase.h"
+
+class KMMessage;
+
+class KMMsgInfo: public KMMsgBase
+{
+public:
+ KMMsgInfo(KMFolder* parent, off_t off=0, short len=0);
+ virtual ~KMMsgInfo();
+
+ /** left for old style index files */
+ void compat_fromOldIndexString(const QCString& str, bool toUtf8);
+
+
+ /** Initialize with given values and set dirty flag to FALSE. */
+ virtual void init(const QCString& subject, const QCString& from,
+ const QCString& to, time_t date,
+ KMMsgStatus status, const QCString& xmark,
+ const QCString& replyToId,
+ const QCString& replyToAuxId,
+ const QCString& msgId,
+ KMMsgEncryptionState encryptionState,
+ KMMsgSignatureState signatureState,
+ KMMsgMDNSentState mdnSentState,
+ const QCString& prefCharset,
+ off_t folderOffset=0, size_t msgSize=0,
+ size_t msgSizeServer = 0, ulong UID = 0);
+
+ /** Initialize with given values and set dirty flag to FALSE. */
+ virtual void init(const QCString& subject, const QCString& from,
+ const QCString& to, time_t date,
+ KMMsgStatus status, const QCString& xmark,
+ const QCString& replyToId,
+ const QCString& replyToAuxId,
+ const QCString& msgId,
+ const QCString& fileName,
+ KMMsgEncryptionState encryptionState,
+ KMMsgSignatureState signatureState,
+ KMMsgMDNSentState mdnSentState,
+ const QCString& prefCharset,
+ size_t msgSize=0,
+ size_t msgSizeServer = 0, ulong UID = 0);
+
+ /** Inherited methods (see KMMsgBase for description): */
+ virtual QString subject(void) const;
+ virtual QString fromStrip(void) const;
+ virtual QString toStrip(void) const;
+ virtual QString xmark(void) const;
+ virtual QString replyToIdMD5(void) const;
+ virtual QString replyToAuxIdMD5() const;
+ virtual QString strippedSubjectMD5() const;
+ virtual bool subjectIsPrefixed() const;
+ virtual QString msgIdMD5(void) const;
+ virtual QString fileName(void) const;
+ virtual KMMsgStatus status(void) const;
+ virtual KMMsgEncryptionState encryptionState() const;
+ virtual KMMsgSignatureState signatureState() const;
+ virtual KMMsgMDNSentState mdnSentState() const;
+ virtual off_t folderOffset(void) const;
+ virtual size_t msgSize(void) const;
+ virtual size_t msgSizeServer(void) const;
+ virtual time_t date(void) const;
+ virtual ulong UID(void) const;
+ void setMsgSize(size_t sz);
+ void setMsgSizeServer(size_t sz);
+ void setFolderOffset(off_t offs);
+ void setFileName(const QString& file);
+ virtual void setStatus(const KMMsgStatus status, int idx = -1);
+ virtual void setDate(time_t aUnixTime);
+ virtual void setSubject(const QString&);
+ virtual void setXMark(const QString&);
+ virtual void setReplyToIdMD5(const QString&);
+ virtual void setReplyToAuxIdMD5( const QString& );
+ virtual void initStrippedSubjectMD5();
+ virtual void setMsgIdMD5(const QString&);
+ virtual void setEncryptionState( const KMMsgEncryptionState, int idx = -1 );
+ virtual void setSignatureState( const KMMsgSignatureState, int idx = -1 );
+ virtual void setMDNSentState( const KMMsgMDNSentState, int idx = -1 );
+ virtual void setUID(ulong);
+
+ /** Grr.. c++! */
+ virtual void setStatus(const char* s1, const char* s2=0) { KMMsgBase::setStatus(s1, s2); }
+ virtual void setDate(const char* s1) { KMMsgBase::setDate(s1); }
+
+ virtual bool dirty(void) const;
+
+ /** Copy operators. */
+ KMMsgInfo& operator=(const KMMessage&);
+
+private:
+ // Currently unused
+ KMMsgInfo& operator=(const KMMsgInfo&);
+ KMMsgInfo(const KMMsgInfo&);
+
+ // WARNING: Do not add new member variables to the class. Add them to kd
+ class KMMsgInfoPrivate;
+ KMMsgInfoPrivate *kd;
+};
+
+typedef KMMsgInfo* KMMsgInfoPtr;
+
+#endif /*kmmsginfo_h*/
diff --git a/kmail/kmmsglist.cpp b/kmail/kmmsglist.cpp
new file mode 100644
index 00000000..b5af8489
--- /dev/null
+++ b/kmail/kmmsglist.cpp
@@ -0,0 +1,186 @@
+// kmmsglist.cpp
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "kmmsglist.h"
+#include "kmmsgdict.h" // FIXME Till - move those into kmfolderindex
+#include "kmkernel.h"
+#include <assert.h>
+#include <stdlib.h>
+
+//-----------------------------------------------------------------------------
+KMMsgList::KMMsgList(int initSize)
+ : QMemArray<KMMsgBase*>(initSize),
+ mHigh( 0 ), mCount( 0 )
+{
+ if ( size() > 0 )
+ for (unsigned int i=size(); i>0; i--)
+ QMemArray<KMMsgBase*>::at(i-1) = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+KMMsgList::~KMMsgList()
+{
+ clear(TRUE);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMsgList::clear(bool doDelete, bool syncDict)
+{
+ if ( mHigh > 0 )
+ for (unsigned int i=mHigh; i>0; i--)
+ {
+ KMMsgBase * msg = at(i-1);
+ if (msg) {
+ if ( syncDict )
+ KMMsgDict::mutableInstance()->remove(msg);
+ at(i-1) = 0;
+ if (doDelete) delete msg;
+ }
+ }
+ mHigh = 0;
+ mCount = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+bool KMMsgList::resize(unsigned int aSize)
+{
+ unsigned int i, oldSize = size();
+ KMMsgBase* msg;
+
+ // delete messages that will get lost, if any
+ if (aSize < mHigh)
+ {
+ for (i=aSize; i<mHigh; i++)
+ {
+ msg = at(i);
+ if (msg)
+ {
+ delete msg;
+ mCount--;
+ }
+ mHigh = aSize;
+ }
+ }
+
+ // do the resizing
+ if (!QMemArray<KMMsgBase*>::resize(aSize)) return FALSE;
+
+ // initialize new elements
+ for (i=oldSize; i<aSize; i++)
+ at(i) = 0;
+
+ return TRUE;
+}
+
+
+//-----------------------------------------------------------------------------
+bool KMMsgList::reset(unsigned int aSize)
+{
+ if (!resize(aSize)) return FALSE;
+ clear();
+ return TRUE;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMsgList::set(unsigned int idx, KMMsgBase* aMsg)
+{
+ if (idx >= size())
+ resize( idx > 2 * size() ? idx + 16 : 2 * size() );
+
+ if (!at(idx) && aMsg) mCount++;
+ else if (at(idx) && !aMsg) mCount--;
+
+ at(idx) = aMsg;
+
+ if (!aMsg || idx >= mHigh) rethinkHigh();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMsgList::insert(unsigned int idx, KMMsgBase* aMsg, bool syncDict)
+{
+ if (idx >= size())
+ resize( idx > 2 * size() ? idx + 16 : 2 * size() );
+
+ if (aMsg) mCount++;
+
+ for (unsigned int i=mHigh; i>idx; i--) {
+ if ( syncDict )
+ KMMsgDict::mutableInstance()->remove(at(i - 1));
+ at(i) = at(i-1);
+ if ( syncDict )
+ KMMsgDict::mutableInstance()->insert(at(i), i);
+ }
+
+ at(idx) = aMsg;
+ if ( syncDict )
+ KMMsgDict::mutableInstance()->insert(at(idx), idx);
+
+ mHigh++;
+}
+
+
+//-----------------------------------------------------------------------------
+unsigned int KMMsgList::append(KMMsgBase* aMsg, bool syncDict)
+{
+ const unsigned int idx = mHigh;
+ insert(idx, aMsg, syncDict); // mHigh gets modified in here
+ return idx;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMsgList::remove(unsigned int idx)
+{
+ assert(idx<size());
+ if (at(idx)) {
+ mCount--;
+ KMMsgDict::mutableInstance()->remove(at(idx));
+ }
+
+ mHigh--;
+ for (unsigned int i=idx; i<mHigh; i++) {
+ KMMsgDict::mutableInstance()->update(at(i + 1), i + 1, i);
+ at(i) = at(i+1);
+ }
+
+ at(mHigh) = 0;
+
+ rethinkHigh();
+}
+
+
+//-----------------------------------------------------------------------------
+KMMsgBase* KMMsgList::take(unsigned int idx)
+{
+ KMMsgBase* msg=at(idx);
+ remove(idx);
+ return msg;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMsgList::rethinkHigh()
+{
+ unsigned int sz = size();
+
+ if (mHigh < sz && at(mHigh))
+ {
+ // forward search
+ while (mHigh < sz && at(mHigh))
+ mHigh++;
+ }
+ else
+ {
+ // backward search
+ while (mHigh>0 && !at(mHigh-1))
+ mHigh--;
+ }
+}
diff --git a/kmail/kmmsglist.h b/kmail/kmmsglist.h
new file mode 100644
index 00000000..1319fde4
--- /dev/null
+++ b/kmail/kmmsglist.h
@@ -0,0 +1,96 @@
+/*
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ *
+ * 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 kmmsglist_h
+#define kmmsglist_h
+
+#include "kmmsgbase.h"
+
+#include <qmemarray.h>
+
+/**
+ * @short An abstraction of an array of pointers to messages.
+ *
+ * This class represents an array of pointers to message objects. It
+ * autoresizes and can load a KMMsgDict object from its contents. It's
+ * a pure implementation detail of KMFolderIndex and should not be used by
+ * the layers above that.
+ *
+ * @author Stefan Taferner <taferner@kde.org>
+ */
+class KMMsgList: public QMemArray<KMMsgBase*>
+{
+public:
+
+ /** Constructor with optional initial size. */
+ KMMsgList(int initialSize=32);
+
+ /** Destructor also deletes all messages in the list. */
+ ~KMMsgList();
+
+ /** Remove message at given index without deleting it.
+ Also removes from message dictionary. */
+ void remove(unsigned int idx);
+
+ /** Returns message at given index and removes it from the list.
+ Also removes from message dictionary. */
+ KMMsgBase* take(unsigned int idx);
+
+ /** Insert message at given index. Resizes the array if necessary.
+ If @p syncDict, also updates message dictionary. */
+ void insert(unsigned int idx, KMMsgBase* msg, bool syncDict = true);
+
+ /** Append given message after the last used message. Resizes the
+ array if necessary. Returns index of new position.
+ If @p syncDict, also updates message dictionary. */
+ unsigned int append(KMMsgBase* msg, bool syncDict = true);
+
+ /** Clear messages. If autoDelete is set (default) the messages are
+ deleted. The array is not resized. If @p syncDict, also updates
+ the message dictionary. */
+ void clear(bool autoDelete=TRUE, bool syncDict = false);
+
+ /** Resize array and initialize new elements if any. Returns
+ FALSE if memory cannot be allocated. */
+ bool resize(unsigned int size);
+
+ /** Clear the array and resize it to given size. Returns FALSE
+ if memory cannot be allocated. */
+ bool reset(unsigned int size);
+
+ /** Set message at given index. The array is resized if necessary. If
+ there is already a message at the given index this message is *not*
+ deleted. Does not sync the message dictionary. */
+ void set(unsigned int idx, KMMsgBase* msg);
+
+ /** Returns first unused index (index of last message plus one). */
+ unsigned int high() const { return mHigh; }
+
+ /** Number of messages in the array. */
+ unsigned int count() const { return mCount; }
+
+protected:
+ /** Set mHigh to proper value */
+ void rethinkHigh();
+
+ unsigned int mHigh, mCount;
+};
+
+
+#endif /*kmmsglist_h*/
diff --git a/kmail/kmmsgpart.cpp b/kmail/kmmsgpart.cpp
new file mode 100644
index 00000000..48dc55ca
--- /dev/null
+++ b/kmail/kmmsgpart.cpp
@@ -0,0 +1,602 @@
+// kmmsgpart.cpp
+
+#include <config.h>
+#include <kmimemagic.h>
+#include <kmimetype.h>
+#include <kdebug.h>
+#include <kmdcodec.h>
+
+#include "kmmsgpart.h"
+#include "kmkernel.h"
+#include "kmmessage.h"
+#include "globalsettings.h"
+#include "util.h"
+
+#include <kasciistringtools.h>
+#include <kmime_charfreq.h>
+#include <kmime_codecs.h>
+#include <mimelib/enum.h>
+#include <mimelib/utility.h>
+#include <mimelib/string.h>
+
+#include <kiconloader.h>
+#include <qtextcodec.h>
+
+#include <assert.h>
+
+using namespace KMime;
+
+//-----------------------------------------------------------------------------
+KMMessagePart::KMMessagePart()
+ : mType("text"), mSubtype("plain"), mCte("7bit"), mBodyDecodedSize(0),
+ mParent(0), mLoadHeaders(false), mLoadPart(false)
+{
+}
+
+//-----------------------------------------------------------------------------
+KMMessagePart::KMMessagePart( QDataStream & stream )
+ : mParent(0), mLoadHeaders(false), mLoadPart(false)
+{
+ unsigned long size;
+ stream >> mOriginalContentTypeStr >> mName >> mContentDescription
+ >> mContentDisposition >> mCte >> size >> mPartSpecifier;
+
+ KPIM::kAsciiToLower( mContentDisposition.data() );
+ KPIM::kAsciiToUpper( mOriginalContentTypeStr.data() );
+
+ // set the type
+ int sep = mOriginalContentTypeStr.find('/');
+ mType = mOriginalContentTypeStr.left(sep);
+ mSubtype = mOriginalContentTypeStr.mid(sep+1);
+
+ mBodyDecodedSize = size;
+}
+
+
+//-----------------------------------------------------------------------------
+KMMessagePart::~KMMessagePart()
+{
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessagePart::clear()
+{
+ mOriginalContentTypeStr = QCString();
+ mType = "text";
+ mSubtype = "plain";
+ mCte = "7bit";
+ mContentDescription = QCString();
+ mContentDisposition = QCString();
+ mBody.truncate( 0 );
+ mAdditionalCTypeParamStr = QCString();
+ mName = QString::null;
+ mParameterAttribute = QCString();
+ mParameterValue = QString::null;
+ mCharset = QCString();
+ mPartSpecifier = QString::null;
+ mBodyDecodedSize = 0;
+ mParent = 0;
+ mLoadHeaders = false;
+ mLoadPart = false;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessagePart::duplicate( const KMMessagePart & msgPart )
+{
+ // copy the data of msgPart
+ *this = msgPart;
+ // detach the explicitely shared QByteArray
+ mBody.detach();
+}
+
+//-----------------------------------------------------------------------------
+int KMMessagePart::decodedSize(void) const
+{
+ if (mBodyDecodedSize < 0)
+ mBodyDecodedSize = bodyDecodedBinary().size();
+ return mBodyDecodedSize;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessagePart::setBody(const QCString &aStr)
+{
+ KMail::Util::setFromQCString( mBody, aStr );
+
+ int enc = cte();
+ if (enc == DwMime::kCte7bit || enc == DwMime::kCte8bit || enc == DwMime::kCteBinary)
+ mBodyDecodedSize = mBody.size();
+ else
+ mBodyDecodedSize = -1; // Can't know the decoded size
+}
+
+void KMMessagePart::setBody(const DwString &aStr)
+{
+ mBody.duplicate( aStr.c_str(), aStr.length() );
+
+ int enc = cte();
+ if (enc == DwMime::kCte7bit || enc == DwMime::kCte8bit || enc == DwMime::kCteBinary)
+ mBodyDecodedSize = mBody.size();
+ else
+ mBodyDecodedSize = -1; // Can't know the decoded size
+}
+
+void KMMessagePart::setBody(const QByteArray &aStr)
+{
+ mBody = aStr;
+
+ int enc = cte();
+ if (enc == DwMime::kCte7bit || enc == DwMime::kCte8bit || enc == DwMime::kCteBinary)
+ mBodyDecodedSize = mBody.size();
+ else
+ mBodyDecodedSize = -1; // Can't know the decoded size
+}
+
+void KMMessagePart::setBodyFromUnicode( const QString & str ) {
+ QCString encoding = KMMsgBase::autoDetectCharset( charset(), KMMessage::preferredCharsets(), str );
+ if ( encoding.isEmpty() )
+ encoding = "utf-8";
+ const QTextCodec * codec = KMMsgBase::codecForName( encoding );
+ assert( codec );
+ QValueList<int> dummy;
+ setCharset( encoding );
+ setBodyAndGuessCte( codec->fromUnicode( str ), dummy, false /* no 8bit */ );
+}
+
+const QTextCodec * KMMessagePart::codec() const {
+ const QTextCodec * c = KMMsgBase::codecForName( charset() );
+
+ if ( !c ) {
+ // Ok, no override and nothing in the message, let's use the fallback
+ // the user configured
+ c = KMMsgBase::codecForName( GlobalSettings::self()->fallbackCharacterEncoding().latin1() );
+ }
+ if ( !c )
+ // no charset means us-ascii (RFC 2045), so using local encoding should
+ // be okay
+ c = kmkernel->networkCodec();
+ assert( c );
+ return c;
+}
+
+QString KMMessagePart::bodyToUnicode(const QTextCodec* codec) const {
+ if ( !codec )
+ // No codec was given, so try the charset in the mail
+ codec = this->codec();
+ assert( codec );
+
+ return codec->toUnicode( bodyDecoded() );
+}
+
+void KMMessagePart::setCharset( const QCString & c ) {
+ if ( type() != DwMime::kTypeText )
+ kdWarning()
+ << "KMMessagePart::setCharset(): trying to set a charset for a non-textual mimetype." << endl
+ << "Fix this caller:" << endl
+ << "====================================================================" << endl
+ << kdBacktrace( 5 ) << endl
+ << "====================================================================" << endl;
+ mCharset = c;
+}
+
+//-----------------------------------------------------------------------------
+void KMMessagePart::setBodyEncoded(const QCString& aStr)
+{
+ mBodyDecodedSize = aStr.size() - 1; // same as aStr.length() but faster - assuming no embedded nuls
+ switch (cte())
+ {
+ case DwMime::kCteQuotedPrintable:
+ case DwMime::kCteBase64:
+ {
+ Codec * codec = Codec::codecForName( cteStr() );
+ assert( codec );
+ // we can't use the convenience function here, since aStr is not
+ // a QByteArray...:
+ mBody.resize( codec->maxEncodedSizeFor( mBodyDecodedSize ) );
+ QCString::ConstIterator iit = aStr.data();
+ QCString::ConstIterator iend = aStr.data() + mBodyDecodedSize;
+ QByteArray::Iterator oit = mBody.begin();
+ QByteArray::ConstIterator oend = mBody.end();
+ if ( !codec->encode( iit, iend, oit, oend ) )
+ kdWarning(5006) << codec->name()
+ << " codec lies about it's maxEncodedSizeFor( "
+ << mBodyDecodedSize << " ). Result truncated!" << endl;
+ mBody.truncate( oit - mBody.begin() );
+ break;
+ }
+ default:
+ kdWarning(5006) << "setBodyEncoded: unknown encoding '" << cteStr()
+ << "'. Assuming binary." << endl;
+ // fall through
+ case DwMime::kCte7bit:
+ case DwMime::kCte8bit:
+ case DwMime::kCteBinary:
+ // This is slow and memory hungry - consider using setBodyEncodedBinary instead!
+ mBody.duplicate( aStr.data(), mBodyDecodedSize );
+ break;
+ }
+}
+
+void KMMessagePart::setBodyAndGuessCte(const QByteArray& aBuf,
+ QValueList<int> & allowedCte,
+ bool allow8Bit,
+ bool willBeSigned )
+{
+ mBodyDecodedSize = aBuf.size();
+
+ CharFreq cf( aBuf ); // save to pass null arrays...
+
+ allowedCte = KMMessage::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 );
+}
+
+void KMMessagePart::setBodyAndGuessCte(const QCString& aBuf,
+ QValueList<int> & allowedCte,
+ bool allow8Bit,
+ bool willBeSigned )
+{
+ mBodyDecodedSize = aBuf.size() - 1; // same as aStr.length() but faster - assuming no embedded nuls
+
+ CharFreq cf( aBuf.data(), mBodyDecodedSize ); // save to pass null strings
+
+ allowedCte = KMMessage::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 );
+}
+
+//-----------------------------------------------------------------------------
+void KMMessagePart::setBodyEncodedBinary(const QByteArray& aStr)
+{
+ mBodyDecodedSize = aStr.size();
+ if (aStr.isEmpty())
+ {
+ mBody.resize(0);
+ return;
+ }
+
+ switch (cte())
+ {
+ case DwMime::kCteQuotedPrintable:
+ case DwMime::kCteBase64:
+ {
+ Codec * codec = Codec::codecForName( cteStr() );
+ assert( codec );
+ // Nice: We can use the convenience function :-)
+ mBody = codec->encode( aStr );
+ break;
+ }
+ default:
+ kdWarning(5006) << "setBodyEncodedBinary: unknown encoding '" << cteStr()
+ << "'. Assuming binary." << endl;
+ // fall through
+ case DwMime::kCte7bit:
+ case DwMime::kCte8bit:
+ case DwMime::kCteBinary:
+ //mBody.duplicate( aStr );
+ mBody = aStr;
+ // Caller has to detach before it modifies aStr!
+ break;
+ }
+}
+
+void KMMessagePart::setMessageBody( const QByteArray& aBuf )
+{
+ CharFreq cf( aBuf ); // it's safe to pass null arrays
+ mBodyDecodedSize = aBuf.size();
+
+ int cte;
+ switch ( cf.type() ) {
+ case CharFreq::SevenBitText:
+ case CharFreq::SevenBitData:
+ cte = DwMime::kCte7bit;
+ break;
+ case CharFreq::EightBitText:
+ case CharFreq::EightBitData:
+ cte = DwMime::kCte8bit;
+ break;
+ default:
+ kdWarning(5006) << "Calling " << k_funcinfo
+ << " with something containing neither 7 nor 8 bit text!"
+ << " Fix this caller: " << kdBacktrace() << endl;
+ }
+ setCte( cte );
+ setBodyEncodedBinary( aBuf );
+}
+
+//-----------------------------------------------------------------------------
+QByteArray KMMessagePart::bodyDecodedBinary() const
+{
+ if (mBody.isEmpty()) return QByteArray();
+ QByteArray result;
+
+ switch (cte())
+ {
+ case DwMime::kCte7bit:
+ case DwMime::kCte8bit:
+ case DwMime::kCteBinary:
+ result.duplicate(mBody);
+ break;
+ default:
+ if ( const Codec * codec = Codec::codecForName( cteStr() ) )
+ // Nice: we can use the convenience function :-)
+ result = codec->decode( mBody );
+ else {
+ kdWarning(5006) << "bodyDecodedBinary: unknown encoding '" << cteStr()
+ << "'. Assuming binary." << endl;
+ result.duplicate(mBody);
+ }
+ }
+
+ assert( mBodyDecodedSize < 0
+ || (unsigned int)mBodyDecodedSize == result.size() );
+ if ( mBodyDecodedSize < 0 )
+ mBodyDecodedSize = result.size(); // cache the decoded size.
+
+ return result;
+}
+
+QCString KMMessagePart::bodyDecoded(void) const
+{
+ if (mBody.isEmpty()) return QCString("");
+ bool decodeBinary = false;
+ QCString result;
+ int len;
+
+ switch (cte())
+ {
+ case DwMime::kCte7bit:
+ case DwMime::kCte8bit:
+ case DwMime::kCteBinary:
+ {
+ decodeBinary = true;
+ break;
+ }
+ default:
+ if ( const Codec * codec = Codec::codecForName( cteStr() ) ) {
+ // We can't use the codec convenience functions, since we must
+ // return a QCString, not a QByteArray:
+ int bufSize = codec->maxDecodedSizeFor( mBody.size() ) + 1; // trailing NUL
+ result.resize( bufSize );
+ QByteArray::ConstIterator iit = mBody.begin();
+ QCString::Iterator oit = result.begin();
+ QCString::ConstIterator oend = result.begin() + bufSize;
+ if ( !codec->decode( iit, mBody.end(), oit, oend ) )
+ kdWarning(5006) << codec->name()
+ << " lies about it's maxDecodedSizeFor( "
+ << mBody.size() << " ). Result truncated!" << endl;
+ len = oit - result.begin();
+ result.truncate( len ); // adds trailing NUL
+ } else {
+ kdWarning(5006) << "bodyDecoded: unknown encoding '" << cteStr()
+ << "'. Assuming binary." << endl;
+ decodeBinary = true;
+ }
+ }
+
+ if ( decodeBinary ) {
+ len = mBody.size();
+ KMail::Util::setFromByteArray( result, mBody );
+ }
+
+ // Calls length -> slow
+ //kdWarning( result.length() != (unsigned int)len, 5006 )
+ // << "KMMessagePart::bodyDecoded(): body is binary but used as text!" << endl;
+
+ result = result.replace( "\r\n", "\n" ); // CRLF -> LF conversion
+
+ assert( mBodyDecodedSize < 0 || mBodyDecodedSize == len );
+ if ( mBodyDecodedSize < 0 )
+ mBodyDecodedSize = len; // cache decoded size
+
+ return result;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessagePart::magicSetType(bool aAutoDecode)
+{
+ KMimeMagic::self()->setFollowLinks( true ); // is it necessary ?
+
+ const QByteArray body = ( aAutoDecode ) ? bodyDecodedBinary() : mBody ;
+ KMimeMagicResult * result = KMimeMagic::self()->findBufferType( body );
+
+ QString mimetype = result->mimeType();
+ const int sep = mimetype.find('/');
+ mType = mimetype.left(sep).latin1();
+ mSubtype = mimetype.mid(sep+1).latin1();
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMessagePart::iconName( int size ) const
+{
+ QCString mimeType( mType + "/" + mSubtype );
+ KPIM::kAsciiToLower( mimeType.data() );
+
+ QString fileName =
+ KMimeType::mimeType( mimeType )->icon( QString::null, false );
+ if ( fileName.isEmpty() )
+ {
+ fileName = this->fileName();
+ if ( fileName.isEmpty() ) fileName = this->name();
+ if ( !fileName.isEmpty() )
+ {
+ fileName = KMimeType::findByPath( "/tmp/"+fileName, 0, true )->icon( QString::null, true );
+ }
+ }
+
+ fileName =
+ KGlobal::instance()->iconLoader()->iconPath( fileName, size );
+ return fileName;
+}
+
+
+//-----------------------------------------------------------------------------
+int KMMessagePart::type() const {
+ return DwTypeStrToEnum(DwString(mType));
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessagePart::setType(int aType)
+{
+ DwString dwType;
+ DwTypeEnumToStr(aType, dwType);
+ mType = dwType.c_str();
+}
+
+//-----------------------------------------------------------------------------
+int KMMessagePart::subtype() const {
+ return DwSubtypeStrToEnum(DwString(mSubtype));
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessagePart::setSubtype(int aSubtype)
+{
+ DwString dwSubtype;
+ DwSubtypeEnumToStr(aSubtype, dwSubtype);
+ mSubtype = dwSubtype.c_str();
+}
+
+//-----------------------------------------------------------------------------
+QCString KMMessagePart::parameterAttribute(void) const
+{
+ return mParameterAttribute;
+}
+
+//-----------------------------------------------------------------------------
+QString KMMessagePart::parameterValue(void) const
+{
+ return mParameterValue;
+}
+
+//-----------------------------------------------------------------------------
+void KMMessagePart::setParameter(const QCString &attribute,
+ const QString &value)
+{
+ mParameterAttribute = attribute;
+ mParameterValue = value;
+}
+
+//-----------------------------------------------------------------------------
+QCString KMMessagePart::contentTransferEncodingStr(void) const
+{
+ return mCte;
+}
+
+
+//-----------------------------------------------------------------------------
+int KMMessagePart::contentTransferEncoding(void) const
+{
+ return DwCteStrToEnum(DwString(mCte));
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessagePart::setContentTransferEncodingStr(const QCString &aStr)
+{
+ mCte = aStr;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessagePart::setContentTransferEncoding(int aCte)
+{
+ DwString dwCte;
+ DwCteEnumToStr(aCte, dwCte);
+ mCte = dwCte.c_str();
+
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMessagePart::contentDescription(void) const
+{
+ return KMMsgBase::decodeRFC2047String(mContentDescription, charset());
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMessagePart::setContentDescription(const QString &aStr)
+{
+ QCString encoding = KMMsgBase::autoDetectCharset(charset(),
+ KMMessage::preferredCharsets(), aStr);
+ if (encoding.isEmpty()) encoding = "utf-8";
+ mContentDescription = KMMsgBase::encodeRFC2047String(aStr, encoding);
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMMessagePart::fileName(void) const
+{
+ QCString str;
+
+ // Allow for multiple filname*0, filename*1, ... params (defined by RFC 2231)
+ // in the Content-Disposision
+ if ( mContentDisposition.contains( "filename*", false ) ) {
+
+ // It's RFC 2231 encoded, so extract the file name with the 2231 method
+ str = KMMsgBase::extractRFC2231HeaderField( mContentDisposition, "filename" );
+ return KMMsgBase::decodeRFC2231String(str);
+
+ } else {
+
+ // Standard RFC 2047-encoded
+ // search the start of the filename
+ int startOfFilename = mContentDisposition.find("filename=", 0, false);
+ if (startOfFilename < 0)
+ return QString::null;
+ startOfFilename += 9;
+
+ // search the end of the filename
+ int endOfFilename;
+ if ( '"' == mContentDisposition[startOfFilename] ) {
+ startOfFilename++; // the double quote isn't part of the filename
+ endOfFilename = mContentDisposition.find('"', startOfFilename) - 1;
+ }
+ else {
+ endOfFilename = mContentDisposition.find(';', startOfFilename) - 1;
+ }
+ if (endOfFilename < 0)
+ endOfFilename = 32767;
+
+ const QCString str = mContentDisposition.mid(startOfFilename,
+ endOfFilename-startOfFilename+1)
+ .stripWhiteSpace();
+ return KMMsgBase::decodeRFC2047String(str, charset());
+ }
+
+ return QString::null;
+}
+
+QCString KMMessagePart::body() const
+{
+ return QCString( mBody.data(), mBody.size() + 1 ); // space for trailing NUL
+}
+
+DwString KMMessagePart::dwBody() const
+{
+ return KMail::Util::dwString( mBody );
+}
diff --git a/kmail/kmmsgpart.h b/kmail/kmmsgpart.h
new file mode 100644
index 00000000..a2823817
--- /dev/null
+++ b/kmail/kmmsgpart.h
@@ -0,0 +1,255 @@
+/* -*- mode: C++ -*-
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ *
+ * 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 kmmsgpart_h
+#define kmmsgpart_h
+
+#include <kiconloader.h>
+
+#include <qstring.h>
+#include <qcstring.h>
+#include <qdict.h>
+
+template <typename T>
+class QValueList;
+class QTextCodec;
+class DwString;
+
+class KMMessagePart
+{
+public:
+ KMMessagePart();
+ KMMessagePart( QDataStream & stream );
+ virtual ~KMMessagePart();
+
+ /** Reset to text/plain with 7bit cte and clear all other properties. */
+ void clear();
+
+ /** Obtains an independant copy (i.e. without explicitely shared data) of the
+ data contained in msgPart. */
+ void duplicate( const KMMessagePart & msgPart );
+
+ /** Get or set the message body */
+ QCString body(void) const;
+ void setBody(const QCString &aStr);
+ DwString dwBody() const;
+ void setBody(const DwString &aStr);
+ // warning, doesn't detach from 'arr'
+ void setBody(const QByteArray &arr);
+
+ /** Sets this body part's content to @p str. @p str is subject to
+ automatic charset and CTE detection.
+ **/
+ void setBodyFromUnicode( const QString & str );
+
+ /** Returns the body part decoded to unicode.
+ **/
+ QString bodyToUnicode(const QTextCodec* codec=0) const;
+
+ /** Returns body as decoded string. Assumes that content-transfer-encoding
+ contains the correct encoding. This routine is meant for binary data.
+ No trailing 0 is appended. */
+ QByteArray bodyDecodedBinary(void) const;
+
+ /** Returns body as decoded string. Assumes that content-transfer-encoding
+ contains the correct encoding. This routine is meant for text strings! */
+ QCString bodyDecoded(void) const;
+
+ /** 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
+ */
+ void setBodyAndGuessCte(const QByteArray& aBuf,
+ QValueList<int>& allowedCte,
+ bool allow8Bit = false,
+ bool willBeSigned = false);
+ /** Same for text */
+ void setBodyAndGuessCte(const QCString& aBuf,
+ QValueList<int>& allowedCte,
+ bool allow8Bit = false,
+ bool willBeSigned = false);
+
+ /** Sets body, encoded according to the content-transfer-encoding.
+ BEWARE: The entire aStr is used including trailing 0 of text strings!
+ This version is faster than setBodyEncoded, no duplication necessary.
+ */
+ void setBodyEncodedBinary(const QByteArray& aStr);
+
+ /** Sets body, encoded according to the content-transfer-encoding.
+ This one is for text strings, the trailing 0 is not used.
+
+ For speed reasons, prefer setBodyEncodedBinary.
+ When possible (the QCString isn't used afterwards), change setBodyEncoded(myQCString) into:
+ setBodyEncodedBinary(byteArrayFromQCStringNoDetach(myQCString));
+ */
+ void setBodyEncoded(const QCString& aStr);
+
+ /** Set a full message string as the body of the message part,
+ disallowing anything but 7bit or 8bit encoding.
+ (RFC 1521 section 7.3)
+ */
+ void setMessageBody( const QByteArray & aBuf );
+
+ /** Returns decoded length of body. */
+ int decodedSize(void) const;
+
+ /** Get or set the 'Content-Type' header field
+ The member functions that involve enumerated types (ints)
+ will work only for well-known types or subtypes. */
+ QCString originalContentTypeStr(void) const { return mOriginalContentTypeStr; }
+ void setOriginalContentTypeStr( const QCString& txt )
+ {
+ mOriginalContentTypeStr = txt;
+ }
+ QCString typeStr() const { return mType; }
+ void setTypeStr( const QCString & aStr ) { mType = aStr; }
+ int type() const;
+ void setType(int aType);
+ /** Subtype */
+ QCString subtypeStr() const { return mSubtype; }
+ void setSubtypeStr( const QCString & aStr ) { mSubtype = aStr; }
+ int subtype() const;
+ void setSubtype(int aSubtype);
+
+ /** Content-Id */
+ QCString contentId() const { return mContentId; }
+ void setContentId( const QCString & aStr ) { mContentId = aStr; }
+
+ /** Set the 'Content-Type' by mime-magic from the contents of the body.
+ If autoDecode is TRUE the decoded body will be used for mime type
+ determination (this does not change the body itself). */
+ void magicSetType(bool autoDecode=TRUE);
+
+ /** Get or set a custom content type parameter, consisting of an attribute
+ name and a corresponding value. */
+ QCString parameterAttribute(void) const;
+ QString parameterValue(void) const;
+ void setParameter(const QCString &attribute, const QString &value);
+
+ QCString additionalCTypeParamStr(void) const
+ {
+ return mAdditionalCTypeParamStr;
+ }
+ void setAdditionalCTypeParamStr( const QCString &param )
+ {
+ mAdditionalCTypeParamStr = param;
+ }
+
+ /** Tries to find a good icon for the 'Content-Type' by scanning
+ the installed mimelnk files. Returns the found icon. If no matching
+ icon is found, the one for application/octet-stream is returned. */
+ QString iconName( int size = KIcon::Desktop ) const;
+
+ /** Get or set the 'Content-Transfer-Encoding' header field
+ The member functions that involve enumerated types (ints)
+ will work only for well-known encodings. */
+ QCString contentTransferEncodingStr(void) const;
+ int contentTransferEncoding(void) const;
+ void setContentTransferEncodingStr(const QCString &aStr);
+ void setContentTransferEncoding(int aCte);
+
+ /** Cte is short for ContentTransferEncoding.
+ These functions are an alternative to the ones with longer names. */
+ QCString cteStr(void) const { return contentTransferEncodingStr(); }
+ int cte(void) const { return contentTransferEncoding(); }
+ void setCteStr(const QCString& aStr) { setContentTransferEncodingStr(aStr); }
+ void setCte(int aCte) { setContentTransferEncoding(aCte); }
+
+
+ /** Get or set the 'Content-Description' header field */
+ QString contentDescription() const;
+ QCString contentDescriptionEncoded() const { return mContentDescription; }
+ void setContentDescription(const QString &aStr);
+
+ /** Get or set the 'Content-Disposition' header field */
+ QCString contentDisposition() const { return mContentDisposition; }
+ void setContentDisposition( const QCString & cd ) { mContentDisposition = cd; }
+
+ /** Get the message part charset.*/
+ QCString charset() const { return mCharset; }
+
+ /** Set the message part charset. */
+ void setCharset( const QCString & c );
+
+ /** Get a QTextCodec suitable for this message part */
+ const QTextCodec * codec() const;
+
+ /** Get or set name parameter */
+ QString name() const { return mName; }
+ void setName( const QString & name ) { mName = name; }
+
+ /** Returns name of filename part of 'Content-Disposition' header field,
+ if present. */
+ QString fileName(void) const;
+
+ /** Returns the part number */
+ QString partSpecifier() const { return mPartSpecifier; }
+
+ /** Sets the part number */
+ void setPartSpecifier( const QString & part ) { mPartSpecifier = part; }
+
+ /** If this part is complete (contains a body) */
+ bool isComplete() { return (!mBody.isNull()); }
+
+ /** Returns the parent part */
+ KMMessagePart* parent() { return mParent; }
+
+ /** Set the parent of this part */
+ void setParent( KMMessagePart* part ) { mParent = part; }
+
+ /** Returns true if the headers should be loaded */
+ bool loadHeaders() { return mLoadHeaders; }
+
+ /** Set to true if the headers should be loaded */
+ void setLoadHeaders( bool load ) { mLoadHeaders = load; }
+
+ /** Returns true if the part itself (as returned by kioslave) should be loaded */
+ bool loadPart() { return mLoadPart; }
+
+ /** Set to true if the part itself should be loaded */
+ void setLoadPart( bool load ) { mLoadPart = load; }
+
+protected:
+ QCString mOriginalContentTypeStr;
+ QCString mType;
+ QCString mSubtype;
+ QCString mCte;
+ QCString mContentDescription;
+ QCString mContentDisposition;
+ QCString mContentId;
+ QByteArray mBody;
+ QCString mAdditionalCTypeParamStr;
+ QString mName;
+ QCString mParameterAttribute;
+ QString mParameterValue;
+ QCString mCharset;
+ QString mPartSpecifier;
+ mutable int mBodyDecodedSize;
+ KMMessagePart* mParent;
+ bool mLoadHeaders;
+ bool mLoadPart;
+};
+
+
+#endif /*kmmsgpart_h*/
diff --git a/kmail/kmmsgpartdlg.cpp b/kmail/kmmsgpartdlg.cpp
new file mode 100644
index 00000000..63407aee
--- /dev/null
+++ b/kmail/kmmsgpartdlg.cpp
@@ -0,0 +1,452 @@
+// kmmsgpartdlg.cpp
+
+
+// my includes:
+#include <config.h>
+#include "kmmsgpartdlg.h"
+
+// other KMail includes:
+#include "kmmessage.h"
+#include "kmmsgpart.h"
+#include "kcursorsaver.h"
+
+// other kdenetwork includes: (none)
+
+// other KDE includes:
+#include <kmimetype.h>
+#include <kapplication.h>
+#include <kiconloader.h>
+#include <kaboutdata.h>
+#include <kstringvalidator.h>
+#include <kcombobox.h>
+#include <kdebug.h>
+
+// other Qt includes:
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qwhatsthis.h>
+#include <klineedit.h>
+#include <qcheckbox.h>
+
+// other includes:
+#include <assert.h>
+
+static const struct {
+ KMMsgPartDialog::Encoding encoding;
+ const char * displayName;
+} encodingTypes[] = {
+ { KMMsgPartDialog::SevenBit, I18N_NOOP("None (7-bit text)") },
+ { KMMsgPartDialog::EightBit, I18N_NOOP("None (8-bit text)") },
+ { KMMsgPartDialog::QuotedPrintable, I18N_NOOP("Quoted Printable") },
+ { KMMsgPartDialog::Base64, I18N_NOOP("Base 64") },
+};
+static const int numEncodingTypes =
+ sizeof encodingTypes / sizeof *encodingTypes;
+
+KMMsgPartDialog::KMMsgPartDialog( const QString & caption,
+ QWidget * parent, const char * name )
+ : KDialogBase( Plain,
+ caption.isEmpty() ? i18n("Message Part Properties") : caption,
+ Ok|Cancel|Help, Ok, parent, name, true, true)
+{
+ // tmp vars:
+ QGridLayout * glay;
+ QLabel * label;
+ QString msg;
+
+ setHelp( QString::fromLatin1("attachments") );
+
+ for ( int i = 0 ; i < numEncodingTypes ; ++i )
+ mI18nizedEncodings << i18n( encodingTypes[i].displayName );
+
+ glay = new QGridLayout( plainPage(), 9 /*rows*/, 2 /*cols*/, spacingHint() );
+ glay->setColStretch( 1, 1 );
+ glay->setRowStretch( 8, 1 );
+
+ // mimetype icon:
+ mIcon = new QLabel( plainPage() );
+ mIcon->setPixmap( DesktopIcon("unknown") );
+ glay->addMultiCellWidget( mIcon, 0, 1, 0, 0 );
+
+ // row 0: Type combobox:
+ mMimeType = new KComboBox( true, plainPage() );
+ mMimeType->setInsertionPolicy( QComboBox::NoInsertion );
+ mMimeType->setValidator( new KMimeTypeValidator( mMimeType ) );
+ mMimeType->insertStringList( QStringList()
+ << QString::fromLatin1("text/html")
+ << QString::fromLatin1("text/plain")
+ << QString::fromLatin1("image/gif")
+ << QString::fromLatin1("image/jpeg")
+ << QString::fromLatin1("image/png")
+ << QString::fromLatin1("application/octet-stream")
+ << QString::fromLatin1("application/x-gunzip")
+ << QString::fromLatin1("application/zip") );
+ connect( mMimeType, SIGNAL(textChanged(const QString&)),
+ this, SLOT(slotMimeTypeChanged(const QString&)) );
+ glay->addWidget( mMimeType, 0, 1 );
+
+ msg = i18n("<qt><p>The <em>MIME type</em> of the file:</p>"
+ "<p>normally, you do not need to touch this setting, since the "
+ "type of the file is automatically checked; but, sometimes, %1 "
+ "may not detect the type correctly -- here is where you can fix "
+ "that.</p></qt>").arg( kapp->aboutData()->programName() );
+ QWhatsThis::add( mMimeType, msg );
+
+ // row 1: Size label:
+ mSize = new QLabel( plainPage() );
+ setSize( KIO::filesize_t(0) );
+ glay->addWidget( mSize, 1, 1 );
+
+ msg = i18n("<qt><p>The size of the part:</p>"
+ "<p>sometimes, %1 will only give an estimated size here, "
+ "because calculating the exact size would take too much time; "
+ "when this is the case, it will be made visible by adding "
+ "\"(est.)\" to the size displayed.</p></qt>")
+ .arg( kapp->aboutData()->programName() );
+ QWhatsThis::add( mSize, msg );
+
+ // row 2: "Name" lineedit and label:
+ mFileName = new KLineEdit( plainPage() );
+ label = new QLabel( mFileName, i18n("&Name:"), plainPage() );
+ glay->addWidget( label, 2, 0 );
+ glay->addWidget( mFileName, 2, 1 );
+
+ msg = i18n("<qt><p>The file name of the part:</p>"
+ "<p>although this defaults to the name of the attached file, "
+ "it does not specify the file to be attached; rather, it "
+ "suggests a file name to be used by the recipient's mail agent "
+ "when saving the part to disk.</p></qt>");
+ QWhatsThis::add( label, msg );
+ QWhatsThis::add( mFileName, msg );
+
+ // row 3: "Description" lineedit and label:
+ mDescription = new KLineEdit( plainPage() );
+ label = new QLabel( mDescription, i18n("&Description:"), plainPage() );
+ glay->addWidget( label, 3, 0 );
+ glay->addWidget( mDescription, 3, 1 );
+
+ msg = i18n("<qt><p>A description of the part:</p>"
+ "<p>this is just an informational description of the part, "
+ "much like the Subject is for the whole message; most "
+ "mail agents will show this information in their message "
+ "previews alongside the attachment's icon.</p></qt>");
+ QWhatsThis::add( label, msg );
+ QWhatsThis::add( mDescription, msg );
+
+ // row 4: "Encoding" combobox and label:
+ mEncoding = new QComboBox( false, plainPage() );
+ mEncoding->insertStringList( mI18nizedEncodings );
+ label = new QLabel( mEncoding, i18n("&Encoding:"), plainPage() );
+ glay->addWidget( label, 4, 0 );
+ glay->addWidget( mEncoding, 4, 1 );
+
+ msg = i18n("<qt><p>The transport encoding of this part:</p>"
+ "<p>normally, you do not need to change this, since %1 will use "
+ "a decent default encoding, depending on the MIME type; yet, "
+ "sometimes, you can significantly reduce the size of the "
+ "resulting message, e.g. if a PostScript file does not contain "
+ "binary data, but consists of pure text -- in this case, choosing "
+ "\"quoted-printable\" over the default \"base64\" will save up "
+ "to 25% in resulting message size.</p></qt>")
+ .arg( kapp->aboutData()->programName() );
+ QWhatsThis::add( label, msg );
+ QWhatsThis::add( mEncoding, msg );
+
+ // row 5: "Suggest automatic display..." checkbox:
+ mInline = new QCheckBox( i18n("Suggest &automatic display"), plainPage() );
+ glay->addMultiCellWidget( mInline, 5, 5, 0, 1 );
+
+ msg = i18n("<qt><p>Check this option if you want to suggest to the "
+ "recipient the automatic (inline) display of this part in the "
+ "message preview, instead of the default icon view;</p>"
+ "<p>technically, this is carried out by setting this part's "
+ "<em>Content-Disposition</em> header field to \"inline\" "
+ "instead of the default \"attachment\".</p></qt>");
+ QWhatsThis::add( mInline, msg );
+
+ // row 6: "Sign" checkbox:
+ mSigned = new QCheckBox( i18n("&Sign this part"), plainPage() );
+ glay->addMultiCellWidget( mSigned, 6, 6, 0, 1 );
+
+ msg = i18n("<qt><p>Check this option if you want this message part to be "
+ "signed;</p>"
+ "<p>the signature will be made with the key that you associated "
+ "with the currently-selected identity.</p></qt>");
+ QWhatsThis::add( mSigned, msg );
+
+ // row 7: "Encrypt" checkbox:
+ mEncrypted = new QCheckBox( i18n("Encr&ypt this part"), plainPage() );
+ glay->addMultiCellWidget( mEncrypted, 7, 7, 0, 1 );
+
+ msg = i18n("<qt><p>Check this option if you want this message part to be "
+ "encrypted;</p>"
+ "<p>the part will be encrypted for the recipients of this "
+ "message</p></qt>");
+ QWhatsThis::add( mEncrypted, msg );
+ // (row 8: spacer)
+}
+
+
+KMMsgPartDialog::~KMMsgPartDialog() {}
+
+
+QString KMMsgPartDialog::mimeType() const {
+ return mMimeType->currentText();
+}
+
+void KMMsgPartDialog::setMimeType( const QString & mimeType ) {
+ int dummy = 0;
+ QString tmp = mimeType; // get rid of const'ness
+ if ( mMimeType->validator() && mMimeType->validator()->validate( tmp, dummy ) )
+ for ( int i = 0 ; i < mMimeType->count() ; ++i )
+ if ( mMimeType->text( i ) == mimeType ) {
+ mMimeType->setCurrentItem( i );
+ return;
+ }
+ mMimeType->insertItem( mimeType, 0 );
+ mMimeType->setCurrentItem( 0 );
+ slotMimeTypeChanged( mimeType );
+}
+
+void KMMsgPartDialog::setMimeType( const QString & type,
+ const QString & subtype ) {
+ setMimeType( QString::fromLatin1("%1/%2").arg(type).arg(subtype) );
+}
+
+void KMMsgPartDialog::setMimeTypeList( const QStringList & mimeTypes ) {
+ mMimeType->insertStringList( mimeTypes );
+}
+
+void KMMsgPartDialog::setSize( KIO::filesize_t size, bool estimated ) {
+ QString text = KIO::convertSize( size );
+ if ( estimated )
+ text = i18n("%1: a filesize incl. unit (e.g. \"1.3 KB\")",
+ "%1 (est.)").arg( text );
+ mSize->setText( text );
+}
+
+QString KMMsgPartDialog::fileName() const {
+ return mFileName->text();
+}
+
+void KMMsgPartDialog::setFileName( const QString & fileName ) {
+ mFileName->setText( fileName );
+}
+
+QString KMMsgPartDialog::description() const {
+ return mDescription->text();
+}
+
+void KMMsgPartDialog::setDescription( const QString & description ) {
+ mDescription->setText( description );
+}
+
+KMMsgPartDialog::Encoding KMMsgPartDialog::encoding() const {
+ QString s = mEncoding->currentText();
+ for ( unsigned int i = 0 ; i < mI18nizedEncodings.count() ; ++i )
+ if ( s == *mI18nizedEncodings.at(i) )
+ return encodingTypes[i].encoding;
+ kdFatal(5006) << "KMMsgPartDialog::encoding(): Unknown encoding encountered!"
+ << endl;
+ return None; // keep compiler happy
+}
+
+void KMMsgPartDialog::setEncoding( Encoding encoding ) {
+ for ( int i = 0 ; i < numEncodingTypes ; ++i )
+ if ( encodingTypes[i].encoding == encoding ) {
+ QString text = *mI18nizedEncodings.at(i);
+ for ( int j = 0 ; j < mEncoding->count() ; ++j )
+ if ( mEncoding->text(j) == text ) {
+ mEncoding->setCurrentItem( j );
+ return;
+ }
+ mEncoding->insertItem( text, 0 );
+ mEncoding->setCurrentItem( 0 );
+ }
+ kdFatal(5006) << "KMMsgPartDialog::setEncoding(): "
+ "Unknown encoding encountered!" << endl;
+}
+
+void KMMsgPartDialog::setShownEncodings( int encodings ) {
+ mEncoding->clear();
+ for ( int i = 0 ; i < numEncodingTypes ; ++i )
+ if ( encodingTypes[i].encoding & encodings )
+ mEncoding->insertItem( *mI18nizedEncodings.at(i) );
+}
+
+bool KMMsgPartDialog::isInline() const {
+ return mInline->isChecked();
+}
+
+void KMMsgPartDialog::setInline( bool inlined ) {
+ mInline->setChecked( inlined );
+}
+
+bool KMMsgPartDialog::isEncrypted() const {
+ return mEncrypted->isChecked();
+}
+
+void KMMsgPartDialog::setEncrypted( bool encrypted ) {
+ mEncrypted->setChecked( encrypted );
+}
+
+void KMMsgPartDialog::setCanEncrypt( bool enable ) {
+ mEncrypted->setEnabled( enable );
+}
+
+bool KMMsgPartDialog::isSigned() const {
+ return mSigned->isChecked();
+}
+
+void KMMsgPartDialog::setSigned( bool sign ) {
+ mSigned->setChecked( sign );
+}
+
+void KMMsgPartDialog::setCanSign( bool enable ) {
+ mSigned->setEnabled( enable );
+}
+
+void KMMsgPartDialog::slotMimeTypeChanged( const QString & mimeType ) {
+ // message subparts MUST have 7bit or 8bit encoding...
+#if 0
+ // ...but until KMail can recode 8bit messages on attach, so that
+ // they can be signed, we can't enforce this :-(
+ if ( mimeType.startsWith("message/") ) {
+ setEncoding( SevenBit );
+ mEncoding->setEnabled( false );
+ } else {
+ mEncoding->setEnabled( !mReadOnly );
+ }
+#endif
+ // find a mimetype icon:
+ int dummy = 0;
+ QString tmp = mimeType; // get rid of const'ness
+ if ( mMimeType->validator() && mMimeType->validator()->validate( tmp, dummy )
+ == QValidator::Acceptable )
+ mIcon->setPixmap( KMimeType::mimeType( mimeType )->pixmap( KIcon::Desktop ) );
+ else
+ mIcon->setPixmap( DesktopIcon("unknown") );
+}
+
+
+
+
+KMMsgPartDialogCompat::KMMsgPartDialogCompat( QWidget * parent, const char *, bool readOnly)
+ : KMMsgPartDialog(QString::null, parent ), mMsgPart( 0 )
+{
+ setShownEncodings( SevenBit|EightBit|QuotedPrintable|Base64 );
+ if (readOnly)
+ {
+ mMimeType->setEditable(false);
+ mMimeType->setEnabled(false);
+ mFileName->setReadOnly(true);
+ mDescription->setReadOnly(true);
+ mEncoding->setEnabled(false);
+ mInline->setEnabled(false);
+ mEncrypted->setEnabled(false);
+ mSigned->setEnabled(false);
+ }
+}
+
+KMMsgPartDialogCompat::~KMMsgPartDialogCompat() {}
+
+void KMMsgPartDialogCompat::setMsgPart( KMMessagePart * aMsgPart )
+{
+ mMsgPart = aMsgPart;
+ assert( mMsgPart );
+
+ QCString enc = mMsgPart->cteStr();
+ if ( enc == "7bit" )
+ setEncoding( SevenBit );
+ else if ( enc == "8bit" )
+ setEncoding( EightBit );
+ else if ( enc == "quoted-printable" )
+ setEncoding( QuotedPrintable );
+ else
+ setEncoding( Base64 );
+
+ setDescription( mMsgPart->contentDescription() );
+ setFileName( mMsgPart->fileName() );
+ setMimeType( mMsgPart->typeStr(), mMsgPart->subtypeStr() );
+ setSize( mMsgPart->decodedSize() );
+ setInline( mMsgPart->contentDisposition()
+ .find( QRegExp("^\\s*inline", false) ) >= 0 );
+}
+
+
+void KMMsgPartDialogCompat::applyChanges()
+{
+ if (!mMsgPart) return;
+
+ KCursorSaver busy(KBusyPtr::busy());
+
+ // apply Content-Disposition:
+ QCString cDisp;
+ if ( isInline() )
+ cDisp = "inline;";
+ else
+ cDisp = "attachment;";
+
+ QString name = fileName();
+ if ( !name.isEmpty() || !mMsgPart->name().isEmpty()) {
+ mMsgPart->setName( name );
+ QCString encoding = KMMsgBase::autoDetectCharset( mMsgPart->charset(),
+ KMMessage::preferredCharsets(), name );
+ if ( encoding.isEmpty() ) encoding = "utf-8";
+ QCString encName = KMMsgBase::encodeRFC2231String( name, encoding );
+
+ cDisp += "\n\tfilename";
+ if ( name != QString( encName ) )
+ cDisp += "*=" + encName;
+ else
+ cDisp += "=\"" + encName.replace( '\\', "\\\\" ).replace( '"', "\\\"" ) + '"';
+ mMsgPart->setContentDisposition( cDisp );
+ }
+
+ // apply Content-Description"
+ QString desc = description();
+ if ( !desc.isEmpty() || !mMsgPart->contentDescription().isEmpty() )
+ mMsgPart->setContentDescription( desc );
+
+ // apply Content-Type:
+ QCString type = mimeType().latin1();
+ QCString subtype;
+ int idx = type.find('/');
+ if ( idx < 0 )
+ subtype = "";
+ else {
+ subtype = type.mid( idx+1 );
+ type = type.left( idx );
+ }
+ mMsgPart->setTypeStr(type);
+ mMsgPart->setSubtypeStr(subtype);
+
+ // apply Content-Transfer-Encoding:
+ QCString cte;
+ if (subtype == "rfc822" && type == "message")
+ kdWarning( encoding() != SevenBit && encoding() != EightBit, 5006 )
+ << "encoding on rfc822/message must be \"7bit\" or \"8bit\"" << endl;
+ switch ( encoding() ) {
+ case SevenBit: cte = "7bit"; break;
+ case EightBit: cte = "8bit"; break;
+ case QuotedPrintable: cte = "quoted-printable"; break;
+ case Base64: default: cte = "base64"; break;
+ }
+ if ( cte != mMsgPart->cteStr().lower() ) {
+ QByteArray body = mMsgPart->bodyDecodedBinary();
+ mMsgPart->setCteStr( cte );
+ mMsgPart->setBodyEncodedBinary( body );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMMsgPartDialogCompat::slotOk()
+{
+ applyChanges();
+ KMMsgPartDialog::slotOk();
+}
+
+
+//-----------------------------------------------------------------------------
+#include "kmmsgpartdlg.moc"
diff --git a/kmail/kmmsgpartdlg.h b/kmail/kmmsgpartdlg.h
new file mode 100644
index 00000000..c913799c
--- /dev/null
+++ b/kmail/kmmsgpartdlg.h
@@ -0,0 +1,163 @@
+/*
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ *
+ * 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 kmmsgpartdlg_h
+#define kmmsgpartdlg_h
+
+#include <kdialogbase.h>
+#include <kio/global.h>
+
+class KMMessagePart;
+class QPushButton;
+class KComboBox;
+class QComboBox;
+class QCheckBox;
+class QLabel;
+class QLineEdit;
+
+#undef None
+
+/** @short GUI for KMMsgPartDialog
+ @author Marc Mutz <mutz@kde.org>
+*/
+class KMMsgPartDialog: public KDialogBase
+{
+ Q_OBJECT
+
+public:
+ KMMsgPartDialog( const QString & caption=QString::null,
+ QWidget * parent=0, const char * name=0 );
+ virtual ~KMMsgPartDialog();
+
+ /** Get the currently selected mimetype */
+ QString mimeType() const;
+ /** Sets the mime type to be displayed. */
+ void setMimeType( const QString & type, const QString & subtype );
+ /** This is an overloaded member function, provided for
+ convenience. It behaves essentially like the above function.
+
+ Sets the mime type to be displayed, but only if @p mimeType
+ passes KMimeTypeValidator's test. */
+ void setMimeType( const QString & mimeType );
+ /** Sets the initial list of mime types to be displayed in the
+ combobox. The items are @em not validated. */
+ void setMimeTypeList( const QStringList & mimeTypes );
+
+ /** Sets the size of the file to be attached in bytes. This is
+ strictly informational and thus can't be queried. If @p approx
+ is true, the size is an estimation based on typical */
+ void setSize( KIO::filesize_t size, bool estimated=false );
+
+ /** Returns the current file name of the attachment. Note that this
+ doesn't define which file is being attached. It only defines
+ what the attachment's filename parameter should contain. */
+ QString fileName() const;
+ /** Sets the file name of the attachment. Note that this doesn't
+ define which file is being attached. It only defines what the
+ attachment's filename parameter should contain. */
+ void setFileName( const QString & fileName );
+
+ /** Returns the content of the Content-Description header
+ field. This field is only informational. */
+ QString description() const;
+ /** Sets the description of the attachment, ie. the content of the
+ Content-Description header field. */
+ void setDescription( const QString & description );
+
+ /** The list of supported encodings */
+ enum Encoding {
+ None = 0x00,
+ SevenBit = 0x01,
+ EightBit = 0x02,
+ QuotedPrintable = 0x04,
+ Base64 = 0x08
+ };
+
+ /** Returns the current encoding */
+ Encoding encoding() const;
+ /** Sets the encoding to use */
+ void setEncoding( Encoding encoding );
+ /** Sets the list of encodings to be shown. @p encodings is the
+ bitwise OR of Encoding flags */
+ void setShownEncodings( int encodings );
+
+ /** Returns true if the attchment has a content-disposition of
+ "inline", false otherwise. */
+ bool isInline() const;
+ /** Sets whether this attachment has a content-disposition of
+ "inline" */
+ void setInline( bool inlined );
+
+ /** Returns whether or not this attachment is or shall be encrypted */
+ bool isEncrypted() const;
+ /** Sets whether or not this attachment is or should be encrypted */
+ void setEncrypted( bool encrypted );
+ /** Sets whether or not this attachment can be encrypted */
+ void setCanEncrypt( bool enable );
+
+ /** Returns whether or not this attachment is or shall be signed */
+ bool isSigned() const;
+ /** Sets whether or not this attachment is or should be signed */
+ void setSigned( bool sign );
+ /** Sets whether or not this attachment can be signed */
+ void setCanSign( bool enable );
+
+protected slots:
+ void slotMimeTypeChanged( const QString & mimeType );
+
+protected:
+ KComboBox *mMimeType;
+ QLabel *mIcon;
+ QLabel *mSize;
+ QLineEdit *mFileName;
+ QLineEdit *mDescription;
+ QComboBox *mEncoding;
+ QCheckBox *mInline;
+ QCheckBox *mEncrypted;
+ QCheckBox *mSigned;
+ QStringList mI18nizedEncodings;
+};
+
+/** @short The attachment dialog with convenience backward compatible methods
+ @author Marc Mutz <mutz@kde.org>
+*/
+class KMMsgPartDialogCompat : public KMMsgPartDialog {
+ Q_OBJECT
+public:
+ KMMsgPartDialogCompat( QWidget * parent=0, const char * caption=0, bool=FALSE );
+ virtual ~KMMsgPartDialogCompat();
+
+ /** Display information about this message part. */
+ void setMsgPart(KMMessagePart* msgPart);
+
+ /** Returns the (possibly modified) message part. */
+ KMMessagePart* msgPart(void) const { return mMsgPart; }
+
+protected slots:
+ void slotOk();
+
+protected:
+ /** Applies changes from the dialog to the message part. Called
+ when the Ok button is pressed. */
+ void applyChanges(void);
+
+ KMMessagePart *mMsgPart;
+};
+
+#endif /*kmmsgpartdlg_h*/
diff --git a/kmail/kmpopfiltercnfrmdlg.cpp b/kmail/kmpopfiltercnfrmdlg.cpp
new file mode 100644
index 00000000..a51333e2
--- /dev/null
+++ b/kmail/kmpopfiltercnfrmdlg.cpp
@@ -0,0 +1,470 @@
+/***************************************************************************
+ kmpopheadersdlg.cpp
+ -------------------
+ begin : Mon Oct 22 2001
+ copyright : (C) 2001 by Heiko Hund
+ Thorsten Zachmann
+ email : heiko@ist.eigentlich.net
+ T.Zachmann@zagge.de
+ ***************************************************************************/
+
+#include <config.h>
+#include "kmpopfiltercnfrmdlg.h"
+#include "kmheaders.h"
+
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qheader.h>
+#include <qcheckbox.h>
+#include <qvgroupbox.h>
+#include <qtimer.h>
+
+#include <klocale.h>
+#include <kio/global.h>
+
+#include <assert.h>
+
+////////////////////////////////////////
+/// view
+KMPopHeadersView::KMPopHeadersView(QWidget *aParent, KMPopFilterCnfrmDlg *aDialog)
+ : KListView(aParent)
+{
+ mDialog=aDialog;
+ int mDownIndex=addColumn(QIconSet(QPixmap(mDown)), QString::null, 24);
+ assert( mDownIndex == Down ); //This code relies on the fact that radiobuttons are the first three columns for easier Column-Action mapping
+ //it does not necessarily be true - you could redefine mapToColumn and mapToAction to eg. shift those numbers by 1
+ addColumn(QIconSet(QPixmap(mLater)), QString::null, 24);
+ addColumn(QIconSet(QPixmap(mDel)), QString::null, 24);
+
+ /*int subjCol =*/ addColumn(i18n("Subject"), 180);
+ /*int sendCol =*/ addColumn(i18n("Sender"), 150);
+ /*int recvCol =*/ addColumn(i18n("Receiver"), 150);
+ int dateCol = addColumn(i18n("Date"), 120);
+ int sizeCol = addColumn(i18n("Size"), 80);
+
+ setAllColumnsShowFocus(true);
+
+ setColumnAlignment(Down, Qt::AlignHCenter);
+ setColumnAlignment(Later, Qt::AlignHCenter);
+ setColumnAlignment(Delete, Qt::AlignHCenter);
+ setColumnAlignment(sizeCol, Qt::AlignRight);
+
+ setSorting(dateCol, false);
+ setShowSortIndicator(true);
+ header()->setResizeEnabled(false, Down);
+ header()->setResizeEnabled(false, Later);
+ header()->setResizeEnabled(false, Delete);
+ header()->setClickEnabled(false, Down);
+ header()->setClickEnabled(false, Later);
+ header()->setClickEnabled(false, Delete);
+
+ //we rely on fixed column order, so we forbid this
+ header()->setMovingEnabled(false);
+
+ connect(this, SIGNAL(pressed(QListViewItem*, const QPoint&, int)),
+ SLOT(slotPressed(QListViewItem*, const QPoint&, int)));
+}
+
+KMPopHeadersView::~KMPopHeadersView()
+{
+}
+
+//Handle keystrokes - Left and Right key select previous/next action correspondingly
+void KMPopHeadersView::keyPressEvent( QKeyEvent *e )
+{
+ if (e->key() == Key_Left) {
+ KMPopHeadersViewItem *item = dynamic_cast<KMPopHeadersViewItem*>( selectedItem() );
+ if (item&&mDialog) {
+ if (item->action()) { //here we rely on the fact that the leftmost action is equal to 0!
+ item->setAction((KMPopFilterAction)((int)item->action()-1));
+ mDialog->setAction( selectedItem(), item->action());
+ }
+ }
+ } else if (e->key() == Key_Right) {
+ KMPopHeadersViewItem *item = dynamic_cast<KMPopHeadersViewItem*>( selectedItem() );
+ if (item&&mDialog) {
+ if (item->action()<NoAction-1) { //here we rely on the fact that right most action is one less than NoAction!
+ item->setAction((KMPopFilterAction)((int)item->action()+1));
+ mDialog->setAction( selectedItem(), item->action());
+ }
+ }
+ } else {
+ QListView::keyPressEvent( e );
+ }
+}
+
+void KMPopHeadersView::slotPressed(QListViewItem* aItem, const QPoint&, int aColumn) {
+ if ( !( aItem && aColumn>=0 && aColumn<NoAction ) ) return;
+ KMPopHeadersViewItem *item = dynamic_cast<KMPopHeadersViewItem*>(aItem);
+ assert( item );
+ item->setAction(mapToAction(aColumn));
+}
+
+const char *KMPopHeadersView::mUnchecked[26] = {
+"19 16 9 1",
+" c None",
+"# c #000000",
+". c #ffffff",
+"a c #4a4c4a",
+"b c #524c52",
+"c c #efefef",
+"e c #fff2ff",
+"f c #f6f2f6",
+"g c #fff6ff",
+" ",
+" ",
+" aaaa ",
+" ba####aa ",
+" a##....aac ",
+" a#......ec ",
+" a#........fc ",
+" a#........gc ",
+" a#........fc ",
+" b#........gc ",
+" a#......gc ",
+" age....gec ",
+" ccfgfgcc ",
+" cccc ",
+" ",
+" ",};
+
+const char *KMPopHeadersView::mChecked[26] = {
+"19 16 9 1",
+" c None",
+"# c #000000",
+". c #ffffff",
+"a c #4a4c4a",
+"b c #524c52",
+"c c #efefef",
+"e c #fff2ff",
+"f c #f6f2f6",
+"g c #fff6ff",
+" ",
+" ",
+" aaaa ",
+" ba####aa ",
+" a##....aac ",
+" a#......ec ",
+" a#...##...fc ",
+" a#..####..gc ",
+" a#..####..fc ",
+" b#...##...gc ",
+" a#......gc ",
+" age....gec ",
+" ccfgfgcc ",
+" cccc ",
+" ",
+" ",};
+
+const char *KMPopHeadersView::mLater[25] = {
+"16 16 8 1",
+". c None",
+"g c #303030",
+"c c #585858",
+"f c #a0a0a0",
+"b c #c0c000",
+"e c #dcdcdc",
+"a c #ffff00",
+"d c #ffffff",
+"................",
+"...........eaa..",
+"..........eaaa..",
+".........ebaab..",
+".........eaaa...",
+"........eaaab...",
+"........eaaa....",
+".......eaaab....",
+"eaae..ebaccccc..",
+"eaaae.eaacdedc..",
+"ebaaabaaabcdc...",
+".ebaaaaaa.fgf...",
+"..ebaaaa..cec...",
+"...ebaab.cdedc..",
+"........eccccc..",
+"................"};
+
+const char *KMPopHeadersView::mDown[20] = {
+"16 16 3 1",
+". c None",
+"a c #008000",
+"b c #00c000",
+"................",
+"............aa..",
+"...........aaa..",
+"..........baab..",
+"..........aaa...",
+".........baab...",
+".........aaa....",
+"........aaab....",
+".aa....baaa.....",
+".aaa...aaa......",
+".baaabaaab......",
+"..baaaaaa.......",
+"...baaaa........",
+"....baab........",
+"................",
+"................"};
+
+const char *KMPopHeadersView::mDel[19] = {
+"16 16 2 1",
+". c None",
+"# c #c00000",
+"................",
+"................",
+"..##.......##...",
+"..###.....###...",
+"...###...###....",
+"....###.###.....",
+".....#####......",
+"......###.......",
+".....#####......",
+"....###.###.....",
+"...###...###....",
+"..###.....###...",
+"..##.......##...",
+"................",
+"................",
+"................"};
+
+
+/////////////////////////////////////////
+/////////////////////////////////////////
+/// viewitem
+/////////////////////////////////////////
+/////////////////////////////////////////
+KMPopHeadersViewItem::KMPopHeadersViewItem(KMPopHeadersView *aParent, KMPopFilterAction aAction)
+ : KListViewItem(aParent)
+{
+ mParent = aParent;
+ mAction = NoAction;
+
+ setPixmap(mParent->mapToColumn(Delete), QPixmap(KMPopHeadersView::mUnchecked));
+ setPixmap(mParent->mapToColumn(Down), QPixmap(KMPopHeadersView::mUnchecked));
+ setPixmap(mParent->mapToColumn(Later), QPixmap(KMPopHeadersView::mUnchecked));
+
+ setAction( aAction );
+}
+
+KMPopHeadersViewItem::~KMPopHeadersViewItem()
+{
+}
+
+void KMPopHeadersViewItem::setAction(KMPopFilterAction aAction)
+{
+ if(aAction != NoAction && aAction!=mAction)
+ {
+ if ( mAction!=NoAction ) setPixmap(mParent->mapToColumn(mAction), QPixmap(KMPopHeadersView::mUnchecked));
+ setPixmap(mParent->mapToColumn(aAction), QPixmap(KMPopHeadersView::mChecked));
+ mAction=aAction;
+ }
+}
+
+void KMPopHeadersViewItem::paintFocus(QPainter *, const QColorGroup &, const QRect &)
+{
+}
+
+QString KMPopHeadersViewItem::key(int col, bool) const
+{
+ if (col == 3) return KMMsgBase::skipKeyword(text(col).lower());
+ if (col == 6) return text(8);
+ if (col == 7)
+ return text(col).rightJustify( 10, '0', false);
+ return text(col);
+}
+
+/////////////////////////////////////////
+/////////////////////////////////////////
+/// dlg
+/////////////////////////////////////////
+/////////////////////////////////////////
+KMPopFilterCnfrmDlg::KMPopFilterCnfrmDlg(QPtrList<KMPopHeaders> *aHeaders, const QString &aAccount, bool aShowLaterMsgs, QWidget *aParent, const char *aName)
+ : KDialogBase(aParent, aName, true, i18n("POP Filter"), Ok | Help, Ok, false)
+{
+ unsigned int rulesetCount = 0;
+ //mHeaders = aHeaders;
+ mShowLaterMsgs = aShowLaterMsgs;
+ mLowerBoxVisible = false;
+
+ QWidget *w = new QWidget(this);
+ setMainWidget(w);
+
+ QVBoxLayout *vbl = new QVBoxLayout(w, 0, spacingHint());
+
+ QLabel *l = new QLabel(i18n("Messages to filter found on POP Account: <b>%1</b><p>"
+ "The messages shown exceed the maximum size limit you defined for this account.<br>You can select "
+ "what you want to do with them by checking the appropriate button.").arg(aAccount), w);
+ vbl->addWidget(l);
+
+ QVGroupBox *upperBox = new QVGroupBox(i18n("Messages Exceeding Size"), w);
+ upperBox->hide();
+ KMPopHeadersView *lv = new KMPopHeadersView(upperBox, this);
+ vbl->addWidget(upperBox);
+
+ QVGroupBox *lowerBox = new QVGroupBox(i18n("Ruleset Filtered Messages: none"), w);
+ QString checkBoxText((aShowLaterMsgs)?
+ i18n("Show messages matched by a ruleset and tagged 'Download' or 'Delete'"):
+ i18n("Show messages matched by a filter ruleset"));
+ QCheckBox* cb = new QCheckBox(checkBoxText, lowerBox);
+ cb->setEnabled(false);
+ mFilteredHeaders = new KMPopHeadersView(lowerBox, this);
+ mFilteredHeaders->hide();
+ vbl->addWidget(lowerBox);
+
+ mFilteredHeaders->header()->setResizeEnabled(false, 8);
+ mFilteredHeaders->setColumnWidth(8, 0);
+
+ // fill the listviews with data from the headers
+ KMPopHeaders *headers;
+ for(headers = aHeaders->first(); headers; headers = aHeaders->next())
+ {
+ KMPopHeadersViewItem *lvi = 0;
+
+ if(headers->ruleMatched())
+ {
+ if(aShowLaterMsgs && headers->action() == Later)
+ {
+ // insert messages tagged 'later' only
+ lvi = new KMPopHeadersViewItem(mFilteredHeaders, headers->action());
+ mFilteredHeaders->show();
+ mLowerBoxVisible = true;
+ }
+ else if(aShowLaterMsgs)
+ {
+ // enable checkbox to show 'delete' and 'download' msgs
+ // but don't insert them into the listview yet
+ mDDLList.append(headers);
+ cb->setEnabled(true);
+ }
+ else if(!aShowLaterMsgs)
+ {
+ // insert all messaged tagged by a ruleset, enable
+ // the checkbox, but don't show the listview yet
+ lvi = new KMPopHeadersViewItem(mFilteredHeaders, headers->action());
+ cb->setEnabled(true);
+ }
+ rulesetCount++;
+ }
+ else
+ {
+ // insert all messages not tagged by a ruleset
+ // into the upper listview
+ lvi = new KMPopHeadersViewItem(lv, headers->action());
+ upperBox->show();
+ }
+
+ if(lvi)
+ {
+ mItemMap[lvi] = headers;
+ setupLVI(lvi,headers->header());
+ }
+ }
+
+ if(rulesetCount)
+ lowerBox->setTitle(i18n("Ruleset Filtered Messages: %1").arg(rulesetCount));
+
+ // connect signals and slots
+ connect(lv, SIGNAL(pressed(QListViewItem*, const QPoint&, int)),
+ this, SLOT(slotPressed(QListViewItem*, const QPoint&, int)));
+ connect(mFilteredHeaders, SIGNAL(pressed(QListViewItem*, const QPoint&, int)),
+ this, SLOT(slotPressed(QListViewItem*, const QPoint&, int)));
+ connect(cb, SIGNAL(toggled(bool)),
+ this, SLOT(slotToggled(bool)));
+
+ adjustSize();
+ QTimer::singleShot(0, this, SLOT(slotUpdateMinimumSize()));
+}
+
+KMPopFilterCnfrmDlg::~KMPopFilterCnfrmDlg()
+{
+}
+
+void KMPopFilterCnfrmDlg::setupLVI(KMPopHeadersViewItem *lvi, KMMessage *msg)
+{
+ // set the subject
+ QString tmp = msg->subject();
+ if(tmp.isEmpty())
+ tmp = i18n("no subject");
+ lvi->setText(3, tmp);
+
+ // set the sender
+ tmp = msg->fromStrip();
+ if(tmp.isEmpty())
+ tmp = i18n("unknown");
+ lvi->setText(4, tmp);
+
+ // set the receiver
+ tmp = msg->toStrip();
+ if(tmp.isEmpty())
+ tmp = i18n("unknown");
+ lvi->setText(5, tmp);
+
+ // set the date
+ lvi->setText(6, KMime::DateFormatter::formatDate( KMime::DateFormatter::Fancy, msg->date() ) );
+ // set the size
+ lvi->setText(7, KIO::convertSize(msg->msgLength()));
+ // Date for sorting
+ lvi->setText(8, msg->dateIsoStr());
+}
+
+void KMPopFilterCnfrmDlg::setAction(QListViewItem *aItem, KMPopFilterAction aAction)
+{
+ mItemMap[aItem]->setAction(aAction);
+}
+/**
+ This Slot is called whenever a ListView item was pressed.
+ It checks for the column the button was pressed in and changes the action if the
+ click happened over a radio button column.
+ Of course the radio button state is changed as well if the above is true.
+*/
+void KMPopFilterCnfrmDlg::slotPressed(QListViewItem *aItem, const QPoint &, int aColumn)
+{
+ if ( aColumn>=0 && aColumn<NoAction ) setAction(aItem,KMPopHeadersView::mapToAction(aColumn));
+}
+
+void KMPopFilterCnfrmDlg::slotToggled(bool aOn)
+{
+ if(aOn)
+ {
+ if(mShowLaterMsgs)
+ {
+ // show download and delete msgs in the list view too
+ for(KMPopHeaders* headers = mDDLList.first(); headers; headers = mDDLList.next())
+ {
+ KMPopHeadersViewItem *lvi = new KMPopHeadersViewItem(mFilteredHeaders, headers->action());
+ mItemMap[lvi] = headers;
+ mDelList.append(lvi);
+ setupLVI(lvi,headers->header());
+ }
+ }
+
+ if(!mLowerBoxVisible)
+ {
+ mFilteredHeaders->show();
+ }
+ }
+ else
+ {
+ if(mShowLaterMsgs)
+ {
+ // delete download and delete msgs from the lower listview
+ for(KMPopHeadersViewItem* item = mDelList.first(); item; item = mDelList.next())
+ {
+ mFilteredHeaders->takeItem(item);
+ }
+ mDelList.clear();
+ }
+
+ if(!mLowerBoxVisible)
+ {
+ mFilteredHeaders->hide();
+ }
+ }
+ QTimer::singleShot(0, this, SLOT(slotUpdateMinimumSize()));
+}
+
+void KMPopFilterCnfrmDlg::slotUpdateMinimumSize()
+{
+ mainWidget()->setMinimumSize(mainWidget()->sizeHint());
+}
+
+#include "kmpopfiltercnfrmdlg.moc"
diff --git a/kmail/kmpopfiltercnfrmdlg.h b/kmail/kmpopfiltercnfrmdlg.h
new file mode 100644
index 00000000..745fdcdd
--- /dev/null
+++ b/kmail/kmpopfiltercnfrmdlg.h
@@ -0,0 +1,111 @@
+/***************************************************************************
+ kmpopheadersdlg.h - description
+ -------------------
+ begin : Sat Nov 3 2001
+ copyright : (C) 2001 by Heiko Hund
+ email : heiko@ist.eigentlich.net
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+#ifndef KMPOPHEADERSDLG_H
+#define KMPOPHEADERSDLG_H
+
+#include "kmpopheaders.h"
+
+#include <kdialogbase.h>
+#include <klistview.h>
+
+#include <qptrlist.h>
+#include <qmap.h>
+
+class QWidget;
+class QString;
+
+class KMPopFilterCnfrmDlg;
+/**
+ * @author Heiko Hund
+ */
+class KMPopHeadersView : public KListView
+{
+ Q_OBJECT
+
+public:
+ KMPopHeadersView(QWidget *aParent=0, KMPopFilterCnfrmDlg *aDialog=0);
+ ~KMPopHeadersView();
+ static const KMPopFilterAction mapToAction(int aColumn) { return (KMPopFilterAction)aColumn;};
+ static const int mapToColumn(KMPopFilterAction aAction) { return (int)aAction;};
+ static const char *mUnchecked[26];
+ static const char *mChecked[26];
+protected:
+ static const char *mLater[25];
+ static const char *mDown[20];
+ static const char *mDel[19];
+ void keyPressEvent( QKeyEvent *k);
+
+protected slots: // Protected slots
+ void slotPressed(QListViewItem* aItem, const QPoint& aPoint, int aColumn);
+
+private:
+ KMPopFilterCnfrmDlg *mDialog;
+};
+
+
+
+class KMPopHeadersViewItem : public KListViewItem
+{
+public:
+ KMPopHeadersViewItem(KMPopHeadersView *aParent, KMPopFilterAction aAction);
+ ~KMPopHeadersViewItem();
+ void setAction(KMPopFilterAction aAction);
+ KMPopFilterAction action() { return mAction; };
+ virtual void paintFocus(QPainter *, const QColorGroup & cg, const QRect &r);
+ virtual QString key(int col, bool ascending) const;
+protected:
+ KMPopHeadersView *mParent;
+ KMPopFilterAction mAction;
+};
+
+
+class KMPopFilterCnfrmDlg : public KDialogBase
+{
+ friend class ::KMPopHeadersView;
+ Q_OBJECT
+protected:
+ KMPopFilterCnfrmDlg() { };
+ QMap<QListViewItem*, KMPopHeaders*> mItemMap;
+ QPtrList<KMPopHeadersViewItem> mDelList;
+ QPtrList<KMPopHeaders> mDDLList;
+ KMPopHeadersView *mFilteredHeaders;
+ bool mLowerBoxVisible;
+ bool mShowLaterMsgs;
+ void setupLVI(KMPopHeadersViewItem *lvi, KMMessage *msg);
+
+
+public:
+ KMPopFilterCnfrmDlg(QPtrList<KMPopHeaders> *aHeaders, const QString &aAccount, bool aShowLaterMsgs = false, QWidget *aParent=0, const char *aName=0);
+ ~KMPopFilterCnfrmDlg();
+
+public:
+ void setAction(QListViewItem *aItem, KMPopFilterAction aAction);
+
+protected slots: // Protected slots
+ /**
+ This Slot is called whenever a ListView item was pressed.
+ It checks for the column the button was pressed in and changes the action if the
+ click happened over a radio button column.
+ Of course the radio button state is changed as well if the above is true.
+*/
+ void slotPressed(QListViewItem *aItem, const QPoint &aPnt, int aColumn);
+ void slotToggled(bool aOn);
+ void slotUpdateMinimumSize();
+};
+
+#endif
diff --git a/kmail/kmpopheaders.cpp b/kmail/kmpopheaders.cpp
new file mode 100644
index 00000000..8c586600
--- /dev/null
+++ b/kmail/kmpopheaders.cpp
@@ -0,0 +1,75 @@
+/***************************************************************************
+ kmpopheaders.cpp - description
+ -------------------
+ begin : Mon Oct 22 2001
+ copyright : (C) 2001 by Heiko Hund
+ Thorsten Zachmann
+ email : heiko@ist.eigentlich.net
+ T.Zachmann@zagge.de
+ ***************************************************************************/
+
+#include <config.h>
+#include "kmpopheaders.h"
+#include <kdebug.h>
+
+KMPopHeaders::KMPopHeaders()
+ : mAction(NoAction),
+ mId(),
+ mUid(),
+ mRuleMatched(false),
+ mHeader(0)
+{
+}
+
+KMPopHeaders::~KMPopHeaders(){
+ if (mHeader)
+ delete mHeader;
+}
+
+/** No descriptions */
+KMPopHeaders::KMPopHeaders(const QString& aId, const QString& aUid, KMPopFilterAction aAction)
+ : mAction(aAction),
+ mId(aId),
+ mUid(aUid),
+ mRuleMatched(false),
+ mHeader(0)
+{
+}
+
+/** No descriptions */
+QString KMPopHeaders::id() const{
+ return mId;
+}
+
+/** No descriptions */
+QString KMPopHeaders::uid() const{
+ return mUid;
+}
+
+/** No descriptions */
+KMMessage * KMPopHeaders::header() const{
+ return mHeader;
+}
+
+/** No descriptions */
+void KMPopHeaders::setHeader(KMMessage *aHeader){
+ mHeader = aHeader;
+}
+
+/** No descriptions */
+KMPopFilterAction KMPopHeaders::action() const{
+ return mAction;
+}
+
+/** No descriptions */
+void KMPopHeaders::setAction(KMPopFilterAction aAction){
+ mAction = aAction;
+}
+/** No descriptions */
+void KMPopHeaders::setRuleMatched(bool b){
+ mRuleMatched = b;
+}
+/** No descriptions */
+bool KMPopHeaders::ruleMatched(){
+ return mRuleMatched;
+}
diff --git a/kmail/kmpopheaders.h b/kmail/kmpopheaders.h
new file mode 100644
index 00000000..0106319f
--- /dev/null
+++ b/kmail/kmpopheaders.h
@@ -0,0 +1,63 @@
+/***************************************************************************
+ kmpopheaders.h
+ -------------------
+ begin : Mon Oct 22 2001
+ copyright : (C) 2001 by Heiko Hund
+ Thorsten Zachmann
+ email : heiko@ist.eigentlich.net
+ T.Zachmann@zagge.de
+ ***************************************************************************/
+
+#ifndef KMPopHeaders_H
+#define KMPopHeaders_H
+
+#include <qstring.h>
+#include "kmmessage.h"
+
+
+enum KMPopFilterAction {Down=0, Later=1, Delete=2, NoAction=3}; //Keep these corresponding to the column numbers in the dialog for easier coding
+ //or change mapToAction and mapToColumn in KMPopHeadersView
+
+class KMPopHeaders {
+public:
+
+ KMPopHeaders();
+ ~KMPopHeaders();
+ /** constructor */
+ KMPopHeaders(const QString& aId, const QString& aUid, KMPopFilterAction aAction);
+
+ /** returns the id of the message */
+ QString id() const;
+
+ /** returns the uid of the message */
+ QString uid() const;
+
+ /** returns the header of the message */
+ KMMessage * header() const;
+
+ /** set the header of the message */
+ void setHeader(KMMessage *aHeader);
+
+ /** No descriptions */
+ KMPopFilterAction action() const;
+
+ /** No descriptions */
+ void setAction(KMPopFilterAction aAction);
+ /** No descriptions */
+ bool ruleMatched();
+ /** No descriptions */
+ void setRuleMatched(bool b);
+protected: // Protected attributes
+ /** */
+ KMPopFilterAction mAction;
+ /** */
+ QString mId;
+ /** */
+ QString mUid;
+ /** */
+ bool mRuleMatched;
+ /** */
+ KMMessage *mHeader;
+};
+
+#endif
diff --git a/kmail/kmreadermainwin.cpp b/kmail/kmreadermainwin.cpp
new file mode 100644
index 00000000..655e0918
--- /dev/null
+++ b/kmail/kmreadermainwin.cpp
@@ -0,0 +1,535 @@
+/*
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2002 Don Sanders <sanders@kde.org>
+
+ 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
+*/
+//
+// A toplevel KMainWindow derived class for displaying
+// single messages or single message parts.
+//
+// Could be extended to include support for normal main window
+// widgets like a toolbar.
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <qaccel.h>
+#include <kapplication.h>
+#include <kedittoolbar.h>
+#include <klocale.h>
+#include <kstdaccel.h>
+#include <kwin.h>
+#include <kaction.h>
+#include <kiconloader.h>
+#include <kdebug.h>
+#include "kmcommands.h"
+#include "kmenubar.h"
+#include "kpopupmenu.h"
+#include "kmreaderwin.h"
+#include "kmfolder.h"
+#include "kmmainwidget.h"
+#include "kmfoldertree.h"
+#include "kmmsgdict.h"
+#include "csshelper.h"
+#include "messageactions.h"
+
+#include "globalsettings.h"
+
+#include "kmreadermainwin.h"
+
+KMReaderMainWin::KMReaderMainWin( bool htmlOverride, bool htmlLoadExtOverride,
+ char *name )
+ : KMail::SecondaryWindow( name ? name : "readerwindow#" ),
+ mMsg( 0 )
+{
+ mReaderWin = new KMReaderWin( this, this, actionCollection() );
+ //mReaderWin->setShowCompleteMessage( true );
+ mReaderWin->setAutoDelete( true );
+ mReaderWin->setHtmlOverride( htmlOverride );
+ mReaderWin->setHtmlLoadExtOverride( htmlLoadExtOverride );
+ mReaderWin->setDecryptMessageOverwrite( true );
+ mReaderWin->setShowSignatureDetails( false );
+ initKMReaderMainWin();
+}
+
+
+//-----------------------------------------------------------------------------
+KMReaderMainWin::KMReaderMainWin( char *name )
+ : KMail::SecondaryWindow( name ? name : "readerwindow#" ),
+ mMsg( 0 )
+{
+ mReaderWin = new KMReaderWin( this, this, actionCollection() );
+ mReaderWin->setAutoDelete( true );
+ initKMReaderMainWin();
+}
+
+
+//-----------------------------------------------------------------------------
+KMReaderMainWin::KMReaderMainWin(KMMessagePart* aMsgPart,
+ bool aHTML, const QString& aFileName, const QString& pname,
+ const QString & encoding, char *name )
+ : KMail::SecondaryWindow( name ? name : "readerwindow#" ),
+ mMsg( 0 )
+{
+ mReaderWin = new KMReaderWin( this, this, actionCollection() );
+ mReaderWin->setOverrideEncoding( encoding );
+ mReaderWin->setMsgPart( aMsgPart, aHTML, aFileName, pname );
+ initKMReaderMainWin();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMReaderMainWin::initKMReaderMainWin() {
+ setCentralWidget( mReaderWin );
+ setupAccel();
+ setupGUI( Keys | StatusBar | Create, "kmreadermainwin.rc" );
+ setupForwardingActionsList();
+ applyMainWindowSettings( KMKernel::config(), "Separate Reader Window" );
+ if ( ! mReaderWin->message() ) {
+ menuBar()->hide();
+ toolBar( "mainToolBar" )->hide();
+ }
+
+ connect( kmkernel, SIGNAL( configChanged() ),
+ this, SLOT( slotConfigChanged() ) );
+}
+
+void KMReaderMainWin::setupForwardingActionsList()
+{
+ QPtrList<KAction> mForwardActionList;
+ if ( GlobalSettings::self()->forwardingInlineByDefault() ) {
+ unplugActionList( "forward_action_list" );
+ mForwardActionList.append( mForwardInlineAction );
+ mForwardActionList.append( mForwardAttachedAction );
+ mForwardActionList.append( mForwardDigestAction );
+ mForwardActionList.append( mRedirectAction );
+ plugActionList( "forward_action_list", mForwardActionList );
+ } else {
+ unplugActionList( "forward_action_list" );
+ mForwardActionList.append( mForwardAttachedAction );
+ mForwardActionList.append( mForwardInlineAction );
+ mForwardActionList.append( mForwardDigestAction );
+ mForwardActionList.append( mRedirectAction );
+ plugActionList( "forward_action_list", mForwardActionList );
+ }
+}
+
+//-----------------------------------------------------------------------------
+KMReaderMainWin::~KMReaderMainWin()
+{
+ saveMainWindowSettings( KMKernel::config(), "Separate Reader Window" );
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderMainWin::setUseFixedFont( bool useFixedFont )
+{
+ mReaderWin->setUseFixedFont( useFixedFont );
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderMainWin::showMsg( const QString & encoding, KMMessage *msg )
+{
+ mReaderWin->setOverrideEncoding( encoding );
+ mReaderWin->setMsg( msg, true );
+ mReaderWin->slotTouchMessage();
+ setCaption( msg->subject() );
+ mMsg = msg;
+ mMsgActions->setCurrentMessage( msg );
+ menuBar()->show();
+ toolBar( "mainToolBar" )->show();
+
+ connect ( msg->parent(), SIGNAL( destroyed( QObject* ) ), this, SLOT( slotFolderRemoved( QObject* ) ) );
+
+}
+
+void KMReaderMainWin::slotFolderRemoved( QObject* folderPtr )
+{
+ assert(mMsg);
+ assert(folderPtr == mMsg->parent());
+ if( mMsg && folderPtr == mMsg->parent() )
+ mMsg->setParent( 0 );
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderMainWin::slotTrashMsg()
+{
+ if ( !mMsg )
+ return;
+ // find the real msg by its sernum
+ KMFolder* parent;
+ int index;
+ KMMsgDict::instance()->getLocation( mMsg->getMsgSerNum(), &parent, &index );
+ if ( parent && !parent->isTrash() ) {
+ // open the folder (ref counted)
+ parent->open("trashmsg");
+ KMMessage *msg = parent->getMsg( index );
+ if (msg) {
+ KMDeleteMsgCommand *command = new KMDeleteMsgCommand( parent, msg );
+ command->start();
+ }
+ parent->close("trashmsg");
+ }
+ close();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderMainWin::slotFind()
+{
+ mReaderWin->slotFind();
+}
+
+void KMReaderMainWin::slotFindNext()
+{
+ mReaderWin->slotFindNext();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderMainWin::slotCopy()
+{
+ mReaderWin->slotCopySelectedText();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderMainWin::slotMarkAll()
+{
+ mReaderWin->selectAll();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderMainWin::slotPrintMsg()
+{
+ KMPrintCommand *command = new KMPrintCommand( this, mReaderWin->message(),
+ mReaderWin->htmlOverride(), mReaderWin->htmlLoadExtOverride(),
+ mReaderWin->isFixedFont(), mReaderWin->overrideEncoding() );
+ command->setOverrideFont( mReaderWin->cssHelper()->bodyFont( mReaderWin->isFixedFont(), true /*printing*/ ) );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderMainWin::slotForwardInlineMsg()
+{
+ KMCommand *command = 0;
+ if ( mReaderWin->message() && mReaderWin->message()->parent() ) {
+ command = new KMForwardInlineCommand( this, mReaderWin->message(),
+ mReaderWin->message()->parent()->identity() );
+ } else {
+ command = new KMForwardInlineCommand( this, mReaderWin->message() );
+ }
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderMainWin::slotForwardAttachedMsg()
+{
+ KMCommand *command = 0;
+ if ( mReaderWin->message() && mReaderWin->message()->parent() ) {
+ command = new KMForwardAttachedCommand( this, mReaderWin->message(),
+ mReaderWin->message()->parent()->identity() );
+ } else {
+ command = new KMForwardAttachedCommand( this, mReaderWin->message() );
+ }
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderMainWin::slotForwardDigestMsg()
+{
+ KMCommand *command = 0;
+ if ( mReaderWin->message() && mReaderWin->message()->parent() ) {
+ command = new KMForwardDigestCommand( this, mReaderWin->message(),
+ mReaderWin->message()->parent()->identity() );
+ } else {
+ command = new KMForwardDigestCommand( this, mReaderWin->message() );
+ }
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderMainWin::slotRedirectMsg()
+{
+ KMCommand *command = new KMRedirectCommand( this, mReaderWin->message() );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderMainWin::slotShowMsgSrc()
+{
+ KMMessage *msg = mReaderWin->message();
+ if ( !msg )
+ return;
+ KMCommand *command = new KMShowMsgSrcCommand( this, msg,
+ mReaderWin->isFixedFont() );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderMainWin::slotConfigChanged()
+{
+ //readConfig();
+}
+
+void KMReaderMainWin::setupAccel()
+{
+ if ( kmkernel->xmlGuiInstance() )
+ setInstance( kmkernel->xmlGuiInstance() );
+
+ mMsgActions = new KMail::MessageActions( actionCollection(), this );
+ mMsgActions->setMessageView( mReaderWin );
+ //----- File Menu
+ //mOpenAction = KStdAction::open( this, SLOT( slotOpenMsg() ),
+ // actionCollection() );
+
+ //mSaveAsAction = new KAction( i18n("Save &As..."), "filesave",
+ // KStdAccel::shortcut( KStdAccel::Save ),
+ // this, SLOT( slotSaveMsg() ),
+ // actionCollection(), "file_save_as" );
+
+ mSaveAsAction = KStdAction::saveAs( mReaderWin, SLOT( slotSaveMsg() ),
+ actionCollection() );
+ mSaveAsAction->setShortcut( KStdAccel::shortcut( KStdAccel::Save ) );
+ mPrintAction = KStdAction::print( this, SLOT( slotPrintMsg() ),
+ actionCollection() );
+
+ KAction *closeAction = KStdAction::close( this, SLOT( close() ), actionCollection() );
+ KShortcut closeShortcut = closeAction->shortcut();
+ closeShortcut.append( KKey(Key_Escape));
+ closeAction->setShortcut(closeShortcut);
+
+ //----- Edit Menu
+ KStdAction::copy( this, SLOT( slotCopy() ), actionCollection() );
+ KStdAction::selectAll( this, SLOT( slotMarkAll() ), actionCollection() );
+ KStdAction::find( this, SLOT(slotFind()), actionCollection() );
+ KStdAction::findNext( this, SLOT( slotFindNext() ), actionCollection() );
+ mTrashAction = new KAction( KGuiItem( i18n( "&Move to Trash" ), "edittrash",
+ i18n( "Move message to trashcan" ) ),
+ Key_Delete, this, SLOT( slotTrashMsg() ),
+ actionCollection(), "move_to_trash" );
+
+ //----- View Menu
+ mViewSourceAction = new KAction( i18n("&View Source"), Key_V, this,
+ SLOT(slotShowMsgSrc()), actionCollection(),
+ "view_source" );
+
+
+ mForwardActionMenu = new KActionMenu( i18n("Message->","&Forward"),
+ "mail_forward", actionCollection(),
+ "message_forward" );
+ mForwardInlineAction = new KAction( i18n("&Inline..."),
+ "mail_forward", SHIFT+Key_F, this,
+ SLOT(slotForwardInlineMsg()),
+ actionCollection(),
+ "message_forward_inline" );
+
+ mForwardAttachedAction = new KAction( i18n("Message->Forward->","As &Attachment..."),
+ "mail_forward", Key_F, this,
+ SLOT(slotForwardAttachedMsg()),
+ actionCollection(),
+ "message_forward_as_attachment" );
+
+ mForwardDigestAction = new KAction( i18n("Message->Forward->","As Di&gest..."),
+ "mail_forward", 0, this,
+ SLOT(slotForwardDigestMsg()),
+ actionCollection(),
+ "message_forward_as_digest" );
+
+ mRedirectAction = new KAction( i18n("Message->Forward->","&Redirect..."),
+ "mail_forward", Key_E, this,
+ SLOT(slotRedirectMsg()),
+ 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, SIGNAL(activated()), this,
+ SLOT(slotForwardInlineMsg()) );
+ } else {
+ mForwardActionMenu->insert( mForwardAttachedAction );
+ mForwardActionMenu->insert( mForwardInlineAction );
+ mForwardInlineAction->setShortcut( SHIFT+Key_F );
+ mForwardAttachedAction->setShortcut( Key_F );
+ connect( mForwardActionMenu, SIGNAL(activated()), this,
+ SLOT(slotForwardAttachedMsg()) );
+ }
+
+ mForwardActionMenu->insert( mForwardDigestAction );
+ mForwardActionMenu->insert( mRedirectAction );
+
+ fontAction = new KFontAction( "Select Font", 0, actionCollection(),
+ "text_font" );
+ fontAction->setFont( mReaderWin->cssHelper()->bodyFont().family() );
+ connect( fontAction, SIGNAL( activated( const QString& ) ),
+ SLOT( slotFontAction( const QString& ) ) );
+ fontSizeAction = new KFontSizeAction( "Select Size", 0, actionCollection(),
+ "text_size" );
+ fontSizeAction->setFontSize( mReaderWin->cssHelper()->bodyFont().pointSize() );
+ connect( fontSizeAction, SIGNAL( fontSizeChanged( int ) ),
+ SLOT( slotSizeAction( int ) ) );
+
+ QAccel *accel = new QAccel(mReaderWin, "showMsg()");
+ accel->connectItem(accel->insertItem(Key_Up),
+ mReaderWin, SLOT(slotScrollUp()));
+ accel->connectItem(accel->insertItem(Key_Down),
+ mReaderWin, SLOT(slotScrollDown()));
+ accel->connectItem(accel->insertItem(Key_Prior),
+ mReaderWin, SLOT(slotScrollPrior()));
+ accel->connectItem(accel->insertItem(Key_Next),
+ mReaderWin, SLOT(slotScrollNext()));
+ accel->connectItem(accel->insertItem(KStdAccel::shortcut(KStdAccel::Copy)),
+ mReaderWin, SLOT(slotCopySelectedText()));
+ connect( mReaderWin, SIGNAL(popupMenu(KMMessage&,const KURL&,const QPoint&)),
+ this, SLOT(slotMsgPopup(KMMessage&,const KURL&,const QPoint&)));
+ connect(mReaderWin, SIGNAL(urlClicked(const KURL&,int)),
+ mReaderWin, SLOT(slotUrlClicked()));
+
+ setStandardToolBarMenuEnabled(true);
+ KStdAction::configureToolbars(this, SLOT(slotEditToolbars()), actionCollection());
+}
+
+
+void KMReaderMainWin::slotMsgPopup(KMMessage &aMsg, const KURL &aUrl, const QPoint& aPoint)
+{
+ KPopupMenu * menu = new KPopupMenu;
+ mUrl = aUrl;
+ mMsg = &aMsg;
+ bool urlMenuAdded=false;
+
+ if (!aUrl.isEmpty())
+ {
+ if (aUrl.protocol() == "mailto") {
+ // popup on a mailto URL
+ mReaderWin->mailToComposeAction()->plug( menu );
+ if ( mMsg ) {
+ mReaderWin->mailToReplyAction()->plug( menu );
+ mReaderWin->mailToForwardAction()->plug( menu );
+ menu->insertSeparator();
+ }
+ mReaderWin->addAddrBookAction()->plug( menu );
+ mReaderWin->openAddrBookAction()->plug( menu );
+ mReaderWin->copyAction()->plug( menu );
+ } else {
+ // popup on a not-mailto URL
+ mReaderWin->urlOpenAction()->plug( menu );
+ mReaderWin->addBookmarksAction()->plug( menu );
+ mReaderWin->urlSaveAsAction()->plug( menu );
+ mReaderWin->copyURLAction()->plug( menu );
+ }
+ urlMenuAdded=true;
+ }
+ if(mReaderWin && !mReaderWin->copyText().isEmpty()) {
+ if ( urlMenuAdded )
+ menu->insertSeparator();
+ mMsgActions->replyMenu()->plug( menu );
+ menu->insertSeparator();
+
+ mReaderWin->copyAction()->plug( menu );
+ mReaderWin->selectAllAction()->plug( menu );
+ } else if ( !urlMenuAdded )
+ {
+ // popup somewhere else (i.e., not a URL) on the message
+
+ if (!mMsg) // no message
+ {
+ delete menu;
+ return;
+ }
+
+ if ( ! ( aMsg.parent() && ( aMsg.parent()->isSent() ||
+ aMsg.parent()->isDrafts() ||
+ aMsg.parent()->isTemplates() ) ) ) {
+ // add the reply and forward actions only if we are not in a sent-mail,
+ // templates or drafts folder
+ //
+ // FIXME: needs custom templates added to menu
+ // (see KMMainWidget::updateCustomTemplateMenus)
+ mMsgActions->replyMenu()->plug( menu );
+ mForwardActionMenu->plug( menu );
+ menu->insertSeparator();
+ }
+
+ QPopupMenu* copyMenu = new QPopupMenu(menu);
+ KMMainWidget* mainwin = kmkernel->getKMMainWidget();
+ if ( mainwin )
+ mainwin->folderTree()->folderToPopupMenu( KMFolderTree::CopyMessage, this,
+ &mMenuToFolder, copyMenu );
+ menu->insertItem( i18n("&Copy To" ), copyMenu );
+ menu->insertSeparator();
+ mViewSourceAction->plug( menu );
+ mReaderWin->toggleFixFontAction()->plug( menu );
+ menu->insertSeparator();
+ mPrintAction->plug( menu );
+ mSaveAsAction->plug( menu );
+ menu->insertItem( i18n("Save Attachments..."), mReaderWin, SLOT(slotSaveAttachments()) );
+ mMsgActions->createTodoAction()->plug( menu );
+ }
+ menu->exec(aPoint, 0);
+ delete menu;
+}
+
+void KMReaderMainWin::copySelectedToFolder( int menuId )
+{
+ if (!mMenuToFolder[menuId])
+ return;
+
+ KMCommand *command = new KMCopyCommand( mMenuToFolder[menuId], mMsg );
+ command->start();
+}
+
+void KMReaderMainWin::slotFontAction( const QString& font)
+{
+ QFont f( mReaderWin->cssHelper()->bodyFont() );
+ f.setFamily( font );
+ mReaderWin->cssHelper()->setBodyFont( f );
+ mReaderWin->cssHelper()->setPrintFont( f );
+ mReaderWin->saveRelativePosition();
+ mReaderWin->update();
+}
+
+void KMReaderMainWin::slotSizeAction( int size )
+{
+ QFont f( mReaderWin->cssHelper()->bodyFont() );
+ f.setPointSize( size );
+ mReaderWin->cssHelper()->setBodyFont( f );
+ mReaderWin->cssHelper()->setPrintFont( f );
+ mReaderWin->saveRelativePosition();
+ mReaderWin->update();
+}
+
+void KMReaderMainWin::slotCreateTodo()
+{
+ if ( !mMsg )
+ return;
+ KMCommand *command = new CreateTodoCommand( this, mMsg );
+ command->start();
+}
+
+void KMReaderMainWin::slotEditToolbars()
+{
+ saveMainWindowSettings( KMKernel::config(), "ReaderWindow" );
+ KEditToolbar dlg( guiFactory(), this );
+ connect( &dlg, SIGNAL(newToolbarConfig()), SLOT(slotUpdateToolbars()) );
+ dlg.exec();
+}
+
+void KMReaderMainWin::slotUpdateToolbars()
+{
+ createGUI("kmreadermainwin.rc");
+ applyMainWindowSettings(KMKernel::config(), "ReaderWindow");
+}
+
+#include "kmreadermainwin.moc"
diff --git a/kmail/kmreadermainwin.h b/kmail/kmreadermainwin.h
new file mode 100644
index 00000000..1e62a4e7
--- /dev/null
+++ b/kmail/kmreadermainwin.h
@@ -0,0 +1,92 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+
+#ifndef KMReaderMainWin_h
+#define KMReaderMainWin_h
+
+#include "secondarywindow.h"
+
+#include <kurl.h>
+
+class KMReaderWin;
+class KMMessage;
+class KMMessagePart;
+class KAction;
+class KActionMenu;
+class KMFolderIndex;
+class KMFolder;
+class KFontAction;
+class KFontSizeAction;
+template <typename T, typename S> class QMap;
+
+namespace KMail {
+class MessageActions;
+}
+
+class KMReaderMainWin : public KMail::SecondaryWindow
+{
+ Q_OBJECT
+
+public:
+ KMReaderMainWin( bool htmlOverride, bool htmlLoadExtOverride, char *name = 0 );
+ KMReaderMainWin( char *name = 0 );
+ KMReaderMainWin(KMMessagePart* aMsgPart,
+ bool aHTML, const QString& aFileName, const QString& pname,
+ const QString & encoding, char *name = 0 );
+ virtual ~KMReaderMainWin();
+
+ void setUseFixedFont( bool useFixedFont );
+
+ // take ownership of and show @param msg
+ void showMsg( const QString & encoding, KMMessage *msg );
+
+ /**
+ * Sets up action list for forward menu.
+ */
+ void setupForwardingActionsList();
+
+private slots:
+ void slotMsgPopup(KMMessage &aMsg, const KURL &aUrl, const QPoint& aPoint);
+
+ /** Copy selected messages to folder with corresponding to given menuid */
+ void copySelectedToFolder( int menuId );
+ void slotTrashMsg();
+ void slotPrintMsg();
+ void slotForwardInlineMsg();
+ void slotForwardAttachedMsg();
+ void slotForwardDigestMsg();
+ void slotRedirectMsg();
+ void slotShowMsgSrc();
+ void slotMarkAll();
+ void slotCopy();
+ void slotFind();
+ void slotFindNext();
+ void slotFontAction(const QString &);
+ void slotSizeAction(int);
+ void slotCreateTodo();
+ void slotEditToolbars();
+
+ void slotConfigChanged();
+ void slotUpdateToolbars();
+
+ void slotFolderRemoved( QObject* folderPtr );
+
+private:
+ void initKMReaderMainWin();
+ void setupAccel();
+
+ KMReaderWin *mReaderWin;
+ KMMessage *mMsg;
+ KURL mUrl;
+ QMap<int,KMFolder*> mMenuToFolder;
+ // a few actions duplicated from kmmainwidget
+ KAction *mTrashAction, *mPrintAction, *mSaveAsAction, *mForwardInlineAction,
+ *mForwardAttachedAction, *mForwardDigestAction, *mRedirectAction,
+ *mViewSourceAction;
+ KActionMenu *mForwardActionMenu;
+ KFontAction *fontAction;
+ KFontSizeAction *fontSizeAction;
+ KMail::MessageActions *mMsgActions;
+
+};
+
+#endif /*KMReaderMainWin_h*/
diff --git a/kmail/kmreadermainwin.rc b/kmail/kmreadermainwin.rc
new file mode 100644
index 00000000..a6199ec3
--- /dev/null
+++ b/kmail/kmreadermainwin.rc
@@ -0,0 +1,75 @@
+<!DOCTYPE kpartgui>
+<kpartgui version="13" name="kmreadermainwin" >
+ <MenuBar>
+ <Menu noMerge="1" name="file" >
+ <text>&amp;File</text>
+ <Action name="file_open" />
+ <Action name="file_save_as" />
+ <Action name="file_print" />
+ <Separator/>
+ <Action name="file_close" />
+ </Menu>
+ <Menu name="edit" >
+ <text>&amp;Edit</text>
+ <Action name="move_to_trash" />
+ </Menu>
+
+ <Menu noMerge="1" name="view">
+ <text>&amp;View</text>
+ <Action name="view_headers"/>
+ <Action name="view_attachments"/>
+ <Separator/>
+ <Action name="view_source"/>
+ <Separator/>
+ <Action name="toggle_fixedfont"/>
+ <Action name="encoding" />
+ </Menu>
+ <Menu noMerge="1" name="message" >
+ <text>&amp;Message</text>
+ <Action name="new_message" />
+ <Action name="post_message" />
+ <Separator/>
+ <Action name="reply" />
+ <Action name="reply_all" />
+ <Menu noMerge="1" name="reply_special" >
+ <text>Reply Special</text>
+ <Action name="reply_author" />
+ <Action name="reply_list" />
+ <Action name="noquotereply" />
+ </Menu>
+ <Menu name="menubar_message_forward">
+ <text>&amp;Forward</text>
+ <ActionList name="forward_action_list"/>
+ </Menu>
+ <Action name="send_again" />
+ <Action name="edit"/>
+ <Separator/>
+ <Action name="copy_to" />
+ <Separator/>
+ <Action name="set_status" />
+ <Separator/>
+ <Action name="create_todo"/>
+ </Menu>
+ <Menu noMerge="1" name="settings">
+ <text>&amp;Settings</text>
+ <Merge name="StandardToolBarMenuHandler"/>
+ <Separator/>
+ <Action name="options_configure_keybinding" group="settings_configure"/>
+ <Action name="options_configure_toolbars" group="settings_configure" />
+ <Action name="kmail_configure_kmail" group="settings_configure"/>
+ </Menu>
+ </MenuBar>
+ <ToolBar noMerge="1" name="mainToolBar" fullWidth="true" >
+ <text>Main Toolbar</text>
+ <Action name="file_print" />
+ <Separator/>
+ <Action name="message_reply_menu" />
+ <Action name="message_forward"/>
+ <Separator/>
+ <Action name="move_to_trash" />
+ <Action name="create_todo"/>
+ <Separator/>
+ <Action name="text_font" />
+ <Action name="text_size" />
+ </ToolBar>
+</kpartgui>
diff --git a/kmail/kmreaderwin.cpp b/kmail/kmreaderwin.cpp
new file mode 100644
index 00000000..c3030304
--- /dev/null
+++ b/kmail/kmreaderwin.cpp
@@ -0,0 +1,2767 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+// kmreaderwin.cpp
+// Author: Markus Wuebben <markus.wuebben@kde.org>
+
+// define this to copy all html that is written to the readerwindow to
+// filehtmlwriter.out in the current working directory
+//#define KMAIL_READER_HTML_DEBUG 1
+
+#include <config.h>
+
+#include "kmreaderwin.h"
+
+#include "globalsettings.h"
+#include "kmversion.h"
+#include "kmmainwidget.h"
+#include "kmreadermainwin.h"
+#include <libkdepim/kfileio.h>
+#include "kmfolderindex.h"
+#include "kmcommands.h"
+#include "kmmsgpartdlg.h"
+#include "mailsourceviewer.h"
+using KMail::MailSourceViewer;
+#include "partNode.h"
+#include "kmmsgdict.h"
+#include "messagesender.h"
+#include "kcursorsaver.h"
+#include "kmfolder.h"
+#include "vcardviewer.h"
+using KMail::VCardViewer;
+#include "objecttreeparser.h"
+using KMail::ObjectTreeParser;
+#include "partmetadata.h"
+using KMail::PartMetaData;
+#include "attachmentstrategy.h"
+using KMail::AttachmentStrategy;
+#include "headerstrategy.h"
+using KMail::HeaderStrategy;
+#include "headerstyle.h"
+using KMail::HeaderStyle;
+#include "khtmlparthtmlwriter.h"
+using KMail::HtmlWriter;
+using KMail::KHtmlPartHtmlWriter;
+#include "htmlstatusbar.h"
+using KMail::HtmlStatusBar;
+#include "folderjob.h"
+using KMail::FolderJob;
+#include "csshelper.h"
+using KMail::CSSHelper;
+#include "isubject.h"
+using KMail::ISubject;
+#include "urlhandlermanager.h"
+using KMail::URLHandlerManager;
+#include "interfaces/observable.h"
+#include "util.h"
+
+#include "broadcaststatus.h"
+
+#include <kmime_mdn.h>
+using namespace KMime;
+#ifdef KMAIL_READER_HTML_DEBUG
+#include "filehtmlwriter.h"
+using KMail::FileHtmlWriter;
+#include "teehtmlwriter.h"
+using KMail::TeeHtmlWriter;
+#endif
+
+#include <kasciistringtools.h>
+#include <kstringhandler.h>
+
+#include <mimelib/mimepp.h>
+#include <mimelib/body.h>
+#include <mimelib/utility.h>
+
+#include <kleo/specialjob.h>
+#include <kleo/cryptobackend.h>
+#include <kleo/cryptobackendfactory.h>
+
+// KABC includes
+#include <kabc/addressee.h>
+#include <kabc/vcardconverter.h>
+
+// khtml headers
+#include <khtml_part.h>
+#include <khtmlview.h> // So that we can get rid of the frames
+#include <dom/html_element.h>
+#include <dom/html_block.h>
+#include <dom/html_document.h>
+#include <dom/dom_string.h>
+
+
+#include <kapplication.h>
+// for the click on attachment stuff (dnaber):
+#include <kuserprofile.h>
+#include <kcharsets.h>
+#include <kpopupmenu.h>
+#include <kstandarddirs.h> // Sven's : for access and getpid
+#include <kcursor.h>
+#include <kdebug.h>
+#include <kfiledialog.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kglobalsettings.h>
+#include <krun.h>
+#include <ktempfile.h>
+#include <kprocess.h>
+#include <kdialog.h>
+#include <kaction.h>
+#include <kiconloader.h>
+#include <kmdcodec.h>
+#include <kasciistricmp.h>
+#include <kurldrag.h>
+
+#include <qclipboard.h>
+#include <qhbox.h>
+#include <qtextcodec.h>
+#include <qpaintdevicemetrics.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qsplitter.h>
+#include <qstyle.h>
+
+// X headers...
+#undef Never
+#undef Always
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+
+#ifdef HAVE_PATHS_H
+#include <paths.h>
+#endif
+
+class NewByteArray : public QByteArray
+{
+public:
+ NewByteArray &appendNULL();
+ NewByteArray &operator+=( const char * );
+ NewByteArray &operator+=( const QByteArray & );
+ NewByteArray &operator+=( const QCString & );
+ QByteArray& qByteArray();
+};
+
+NewByteArray& NewByteArray::appendNULL()
+{
+ QByteArray::detach();
+ uint len1 = size();
+ if ( !QByteArray::resize( len1 + 1 ) )
+ return *this;
+ *(data() + len1) = '\0';
+ return *this;
+}
+NewByteArray& NewByteArray::operator+=( const char * newData )
+{
+ if ( !newData )
+ return *this;
+ QByteArray::detach();
+ uint len1 = size();
+ uint len2 = qstrlen( newData );
+ if ( !QByteArray::resize( len1 + len2 ) )
+ return *this;
+ memcpy( data() + len1, newData, len2 );
+ return *this;
+}
+NewByteArray& NewByteArray::operator+=( const QByteArray & newData )
+{
+ if ( newData.isNull() )
+ return *this;
+ QByteArray::detach();
+ uint len1 = size();
+ uint len2 = newData.size();
+ if ( !QByteArray::resize( len1 + len2 ) )
+ return *this;
+ memcpy( data() + len1, newData.data(), len2 );
+ return *this;
+}
+NewByteArray& NewByteArray::operator+=( const QCString & newData )
+{
+ if ( newData.isEmpty() )
+ return *this;
+ QByteArray::detach();
+ uint len1 = size();
+ uint len2 = newData.length(); // forget about the trailing 0x00 !
+ if ( !QByteArray::resize( len1 + len2 ) )
+ return *this;
+ memcpy( data() + len1, newData.data(), len2 );
+ return *this;
+}
+QByteArray& NewByteArray::qByteArray()
+{
+ return *((QByteArray*)this);
+}
+
+// This function returns the complete data that were in this
+// message parts - *after* all encryption has been removed that
+// could be removed.
+// - This is used to store the message in decrypted form.
+void KMReaderWin::objectTreeToDecryptedMsg( partNode* node,
+ NewByteArray& resultingData,
+ KMMessage& theMessage,
+ bool weAreReplacingTheRootNode,
+ int recCount )
+{
+ kdDebug(5006) << QString("-------------------------------------------------" ) << endl;
+ kdDebug(5006) << QString("KMReaderWin::objectTreeToDecryptedMsg( %1 ) START").arg( recCount ) << endl;
+ if( node ) {
+ partNode* curNode = node;
+ partNode* dataNode = curNode;
+ partNode * child = node->firstChild();
+ bool bIsMultipart = 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;
+ 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;
+ }
+ }
+ 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;
+ }
+ break;
+ }
+ }
+ 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;
+ 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 )
+ dataNode = child;
+ }
+ break;
+ }
+ }
+ 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;
+ }
+
+
+ DwHeaders& rootHeaders( theMessage.headers() );
+ DwBodyPart * part = dataNode->dwPart() ? dataNode->dwPart() : 0;
+ DwHeaders * headers(
+ (part && part->hasHeaders())
+ ? &part->Headers()
+ : ( (weAreReplacingTheRootNode || !dataNode->parentNode())
+ ? &rootHeaders
+ : 0 ) );
+ if( dataNode == curNode ) {
+kdDebug(5006) << "dataNode == curNode: Save curNode without replacing it." << endl;
+
+ // A) Store the headers of this part IF curNode is not the root node
+ // AND we are not replacing a node that already *has* replaced
+ // the root node in previous recursion steps of this function...
+ if( headers ) {
+ if( dataNode->parentNode() && !weAreReplacingTheRootNode ) {
+kdDebug(5006) << "dataNode is NOT replacing the root node: Store the headers." << endl;
+ resultingData += headers->AsString().c_str();
+ } else if( weAreReplacingTheRootNode && part && part->hasHeaders() ){
+kdDebug(5006) << "dataNode replace the root node: Do NOT store the headers but change" << endl;
+kdDebug(5006) << " the Message's headers accordingly." << endl;
+kdDebug(5006) << " old Content-Type = " << rootHeaders.ContentType().AsString().c_str() << endl;
+kdDebug(5006) << " new Content-Type = " << headers->ContentType( ).AsString().c_str() << endl;
+ rootHeaders.ContentType() = headers->ContentType();
+ theMessage.setContentTransferEncodingStr(
+ headers->HasContentTransferEncoding()
+ ? headers->ContentTransferEncoding().AsString().c_str()
+ : "" );
+ rootHeaders.ContentDescription() = headers->ContentDescription();
+ rootHeaders.ContentDisposition() = headers->ContentDisposition();
+ theMessage.setNeedsAssembly();
+ }
+ }
+
+ // B) Store the body of this part.
+ if( headers && bIsMultipart && dataNode->firstChild() ) {
+kdDebug(5006) << "is valid Multipart, processing children:" << endl;
+ QCString boundary = headers->ContentType().Boundary().c_str();
+ curNode = dataNode->firstChild();
+ // store children of multipart
+ while( curNode ) {
+kdDebug(5006) << "--boundary" << endl;
+ if( resultingData.size() &&
+ ( '\n' != resultingData.at( resultingData.size()-1 ) ) )
+ resultingData += QCString( "\n" );
+ resultingData += QCString( "\n" );
+ resultingData += "--";
+ resultingData += boundary;
+ resultingData += "\n";
+ // note: We are processing a harmless multipart that is *not*
+ // to be replaced by one of it's children, therefor
+ // we set their doStoreHeaders to true.
+ objectTreeToDecryptedMsg( curNode,
+ resultingData,
+ theMessage,
+ false,
+ recCount + 1 );
+ curNode = curNode->nextSibling();
+ }
+kdDebug(5006) << "--boundary--" << endl;
+ resultingData += "\n--";
+ resultingData += boundary;
+ resultingData += "--\n\n";
+kdDebug(5006) << "Multipart processing children - DONE" << endl;
+ } else if( part ){
+ // store simple part
+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();
+ if( rootNodeReplaceFlag ) {
+kdDebug(5006) << " Root node will be replaced." << endl;
+ } else {
+kdDebug(5006) << " Root node will NOT be replaced." << endl;
+ }
+ // store special data to replace the current part
+ // (e.g. decrypted data or embedded RfC 822 data)
+ objectTreeToDecryptedMsg( dataNode,
+ resultingData,
+ theMessage,
+ rootNodeReplaceFlag,
+ recCount + 1 );
+ }
+ }
+ kdDebug(5006) << QString("\nKMReaderWin::objectTreeToDecryptedMsg( %1 ) END").arg( recCount ) << endl;
+}
+
+
+/*
+ ===========================================================================
+
+
+ E N D O F T E M P O R A R Y M I M E C O D E
+
+
+ ===========================================================================
+*/
+
+
+
+
+
+
+
+
+
+
+
+void KMReaderWin::createWidgets() {
+ QVBoxLayout * vlay = new QVBoxLayout( this );
+ mSplitter = new QSplitter( Qt::Vertical, this, "mSplitter" );
+ vlay->addWidget( mSplitter );
+ mMimePartTree = new KMMimePartTree( this, mSplitter, "mMimePartTree" );
+ mBox = new QHBox( mSplitter, "mBox" );
+ setStyleDependantFrameWidth();
+ mBox->setFrameStyle( mMimePartTree->frameStyle() );
+ mColorBar = new HtmlStatusBar( mBox, "mColorBar" );
+ mViewer = new KHTMLPart( mBox, "mViewer" );
+ mSplitter->setOpaqueResize( KGlobalSettings::opaqueResize() );
+ mSplitter->setResizeMode( mMimePartTree, QSplitter::KeepSize );
+}
+
+const int KMReaderWin::delay = 150;
+
+//-----------------------------------------------------------------------------
+KMReaderWin::KMReaderWin(QWidget *aParent,
+ QWidget *mainWindow,
+ KActionCollection* actionCollection,
+ const char *aName,
+ int aFlags )
+ : QWidget(aParent, aName, aFlags | Qt::WDestructiveClose),
+ mAttachmentStrategy( 0 ),
+ mHeaderStrategy( 0 ),
+ mHeaderStyle( 0 ),
+ mUpdateReaderWinTimer( 0, "mUpdateReaderWinTimer" ),
+ mResizeTimer( 0, "mResizeTimer" ),
+ mDelayedMarkTimer( 0, "mDelayedMarkTimer" ),
+ mOldGlobalOverrideEncoding( "---" ), // init with dummy value
+ mCSSHelper( 0 ),
+ mRootNode( 0 ),
+ mMainWindow( mainWindow ),
+ mActionCollection( actionCollection ),
+ mMailToComposeAction( 0 ),
+ mMailToReplyAction( 0 ),
+ mMailToForwardAction( 0 ),
+ mAddAddrBookAction( 0 ),
+ mOpenAddrBookAction( 0 ),
+ mCopyAction( 0 ),
+ mCopyURLAction( 0 ),
+ mUrlOpenAction( 0 ),
+ mUrlSaveAsAction( 0 ),
+ mAddBookmarksAction( 0 ),
+ mStartIMChatAction( 0 ),
+ mSelectAllAction( 0 ),
+ mSelectEncodingAction( 0 ),
+ mToggleFixFontAction( 0 ),
+ mHtmlWriter( 0 ),
+ mSavedRelativePosition( 0 ),
+ mDecrytMessageOverwrite( false ),
+ mShowSignatureDetails( false ),
+ mShowAttachmentQuicklist( true )
+{
+ mSplitterSizes << 180 << 100;
+ mMimeTreeMode = 1;
+ mMimeTreeAtBottom = true;
+ mAutoDelete = false;
+ mLastSerNum = 0;
+ mWaitingForSerNum = 0;
+ mMessage = 0;
+ mLastStatus = KMMsgStatusUnknown;
+ mMsgDisplay = true;
+ mPrinting = false;
+ mShowColorbar = false;
+ mAtmUpdate = false;
+
+ createWidgets();
+ createActions( actionCollection );
+ initHtmlWidget();
+ readConfig();
+
+ mHtmlOverride = false;
+ mHtmlLoadExtOverride = false;
+
+ mLevelQuote = GlobalSettings::self()->collapseQuoteLevelSpin() - 1;
+
+ connect( &mUpdateReaderWinTimer, SIGNAL(timeout()),
+ this, SLOT(updateReaderWin()) );
+ connect( &mResizeTimer, SIGNAL(timeout()),
+ this, SLOT(slotDelayedResize()) );
+ connect( &mDelayedMarkTimer, SIGNAL(timeout()),
+ this, SLOT(slotTouchMessage()) );
+
+}
+
+void KMReaderWin::createActions( KActionCollection * ac ) {
+ if ( !ac )
+ return;
+
+ KRadioAction *raction = 0;
+
+ // header style
+ KActionMenu *headerMenu =
+ new KActionMenu( i18n("View->", "&Headers"), ac, "view_headers" );
+ headerMenu->setToolTip( i18n("Choose display style of message headers") );
+
+ connect( headerMenu, SIGNAL(activated()),
+ this, SLOT(slotCycleHeaderStyles()) );
+
+ raction = new KRadioAction( i18n("View->headers->", "&Enterprise Headers"), 0,
+ this, SLOT(slotEnterpriseHeaders()),
+ ac, "view_headers_enterprise" );
+ raction->setToolTip( i18n("Show the list of headers in Enterprise style") );
+ raction->setExclusiveGroup( "view_headers_group" );
+ headerMenu->insert(raction);
+
+ raction = new KRadioAction( i18n("View->headers->", "&Fancy Headers"), 0,
+ this, SLOT(slotFancyHeaders()),
+ ac, "view_headers_fancy" );
+ raction->setToolTip( i18n("Show the list of headers in a fancy format") );
+ raction->setExclusiveGroup( "view_headers_group" );
+ headerMenu->insert( raction );
+
+ raction = new KRadioAction( i18n("View->headers->", "&Brief Headers"), 0,
+ this, SLOT(slotBriefHeaders()),
+ ac, "view_headers_brief" );
+ raction->setToolTip( i18n("Show brief list of message headers") );
+ raction->setExclusiveGroup( "view_headers_group" );
+ headerMenu->insert( raction );
+
+ raction = new KRadioAction( i18n("View->headers->", "&Standard Headers"), 0,
+ this, SLOT(slotStandardHeaders()),
+ ac, "view_headers_standard" );
+ raction->setToolTip( i18n("Show standard list of message headers") );
+ raction->setExclusiveGroup( "view_headers_group" );
+ headerMenu->insert( raction );
+
+ raction = new KRadioAction( i18n("View->headers->", "&Long Headers"), 0,
+ this, SLOT(slotLongHeaders()),
+ ac, "view_headers_long" );
+ raction->setToolTip( i18n("Show long list of message headers") );
+ raction->setExclusiveGroup( "view_headers_group" );
+ headerMenu->insert( raction );
+
+ raction = new KRadioAction( i18n("View->headers->", "&All Headers"), 0,
+ this, SLOT(slotAllHeaders()),
+ ac, "view_headers_all" );
+ raction->setToolTip( i18n("Show all message headers") );
+ raction->setExclusiveGroup( "view_headers_group" );
+ headerMenu->insert( raction );
+
+ // attachment style
+ KActionMenu *attachmentMenu =
+ new KActionMenu( i18n("View->", "&Attachments"), ac, "view_attachments" );
+ attachmentMenu->setToolTip( i18n("Choose display style of attachments") );
+ connect( attachmentMenu, SIGNAL(activated()),
+ this, SLOT(slotCycleAttachmentStrategy()) );
+
+ raction = new KRadioAction( i18n("View->attachments->", "&As Icons"), 0,
+ this, SLOT(slotIconicAttachments()),
+ ac, "view_attachments_as_icons" );
+ raction->setToolTip( i18n("Show all attachments as icons. Click to see them.") );
+ raction->setExclusiveGroup( "view_attachments_group" );
+ attachmentMenu->insert( raction );
+
+ raction = new KRadioAction( i18n("View->attachments->", "&Smart"), 0,
+ this, SLOT(slotSmartAttachments()),
+ ac, "view_attachments_smart" );
+ raction->setToolTip( i18n("Show attachments as suggested by sender.") );
+ raction->setExclusiveGroup( "view_attachments_group" );
+ attachmentMenu->insert( raction );
+
+ raction = new KRadioAction( i18n("View->attachments->", "&Inline"), 0,
+ this, SLOT(slotInlineAttachments()),
+ ac, "view_attachments_inline" );
+ raction->setToolTip( i18n("Show all attachments inline (if possible)") );
+ raction->setExclusiveGroup( "view_attachments_group" );
+ attachmentMenu->insert( raction );
+
+ raction = new KRadioAction( i18n("View->attachments->", "&Hide"), 0,
+ this, SLOT(slotHideAttachments()),
+ ac, "view_attachments_hide" );
+ raction->setToolTip( i18n("Do not show attachments in the message viewer") );
+ raction->setExclusiveGroup( "view_attachments_group" );
+ attachmentMenu->insert( raction );
+
+ // Set Encoding submenu
+ mSelectEncodingAction = new KSelectAction( i18n( "&Set Encoding" ), "charset", 0,
+ this, SLOT( slotSetEncoding() ),
+ ac, "encoding" );
+ QStringList encodings = KMMsgBase::supportedEncodings( false );
+ encodings.prepend( i18n( "Auto" ) );
+ mSelectEncodingAction->setItems( encodings );
+ mSelectEncodingAction->setCurrentItem( 0 );
+
+ mMailToComposeAction = new KAction( i18n("New Message To..."), "mail_new",
+ 0, this, SLOT(slotMailtoCompose()), ac,
+ "mailto_compose" );
+ mMailToReplyAction = new KAction( i18n("Reply To..."), "mail_reply",
+ 0, this, SLOT(slotMailtoReply()), ac,
+ "mailto_reply" );
+ mMailToForwardAction = new KAction( i18n("Forward To..."), "mail_forward",
+ 0, this, SLOT(slotMailtoForward()), ac,
+ "mailto_forward" );
+ mAddAddrBookAction = new KAction( i18n("Add to Address Book"),
+ 0, this, SLOT(slotMailtoAddAddrBook()),
+ ac, "add_addr_book" );
+ mOpenAddrBookAction = new KAction( i18n("Open in Address Book"),
+ 0, this, SLOT(slotMailtoOpenAddrBook()),
+ ac, "openin_addr_book" );
+ mCopyAction = KStdAction::copy( this, SLOT(slotCopySelectedText()), ac, "kmail_copy");
+ mSelectAllAction = new KAction( i18n("Select All Text"), CTRL+SHIFT+Key_A, this,
+ SLOT(selectAll()), ac, "mark_all_text" );
+ mCopyURLAction = new KAction( i18n("Copy Link Address"), 0, this,
+ SLOT(slotUrlCopy()), ac, "copy_url" );
+ mUrlOpenAction = new KAction( i18n("Open URL"), 0, this,
+ SLOT(slotUrlOpen()), ac, "open_url" );
+ mAddBookmarksAction = new KAction( i18n("Bookmark This Link"),
+ "bookmark_add",
+ 0, this, SLOT(slotAddBookmarks()),
+ ac, "add_bookmarks" );
+ mUrlSaveAsAction = new KAction( i18n("Save Link As..."), 0, this,
+ SLOT(slotUrlSave()), ac, "saveas_url" );
+
+ mToggleFixFontAction = new KToggleAction( i18n("Use Fi&xed Font"),
+ Key_X, this, SLOT(slotToggleFixedFont()),
+ ac, "toggle_fixedfont" );
+
+ mStartIMChatAction = new KAction( i18n("Chat &With..."), 0, this,
+ SLOT(slotIMChat()), ac, "start_im_chat" );
+}
+
+// little helper function
+KRadioAction *KMReaderWin::actionForHeaderStyle( const HeaderStyle * style, const HeaderStrategy * strategy ) {
+ if ( !mActionCollection )
+ return 0;
+ const char * actionName = 0;
+ if ( style == HeaderStyle::enterprise() )
+ actionName = "view_headers_enterprise";
+ if ( style == HeaderStyle::fancy() )
+ actionName = "view_headers_fancy";
+ else if ( style == HeaderStyle::brief() )
+ actionName = "view_headers_brief";
+ else if ( style == HeaderStyle::plain() ) {
+ if ( strategy == HeaderStrategy::standard() )
+ actionName = "view_headers_standard";
+ else if ( strategy == HeaderStrategy::rich() )
+ actionName = "view_headers_long";
+ else if ( strategy == HeaderStrategy::all() )
+ actionName = "view_headers_all";
+ }
+ if ( actionName )
+ return static_cast<KRadioAction*>(mActionCollection->action(actionName));
+ else
+ return 0;
+}
+
+KRadioAction *KMReaderWin::actionForAttachmentStrategy( const AttachmentStrategy * as ) {
+ if ( !mActionCollection )
+ return 0;
+ const char * actionName = 0;
+ if ( as == AttachmentStrategy::iconic() )
+ actionName = "view_attachments_as_icons";
+ else if ( as == AttachmentStrategy::smart() )
+ actionName = "view_attachments_smart";
+ else if ( as == AttachmentStrategy::inlined() )
+ actionName = "view_attachments_inline";
+ else if ( as == AttachmentStrategy::hidden() )
+ actionName = "view_attachments_hide";
+
+ if ( actionName )
+ return static_cast<KRadioAction*>(mActionCollection->action(actionName));
+ else
+ return 0;
+}
+
+void KMReaderWin::slotEnterpriseHeaders() {
+ setHeaderStyleAndStrategy( HeaderStyle::enterprise(),
+ HeaderStrategy::rich() );
+}
+
+void KMReaderWin::slotFancyHeaders() {
+ setHeaderStyleAndStrategy( HeaderStyle::fancy(),
+ HeaderStrategy::rich() );
+}
+
+void KMReaderWin::slotBriefHeaders() {
+ setHeaderStyleAndStrategy( HeaderStyle::brief(),
+ HeaderStrategy::brief() );
+}
+
+void KMReaderWin::slotStandardHeaders() {
+ setHeaderStyleAndStrategy( HeaderStyle::plain(),
+ HeaderStrategy::standard());
+}
+
+void KMReaderWin::slotLongHeaders() {
+ setHeaderStyleAndStrategy( HeaderStyle::plain(),
+ HeaderStrategy::rich() );
+}
+
+void KMReaderWin::slotAllHeaders() {
+ setHeaderStyleAndStrategy( HeaderStyle::plain(),
+ HeaderStrategy::all() );
+}
+
+void KMReaderWin::slotLevelQuote( int l )
+{
+ kdDebug( 5006 ) << "Old Level: " << mLevelQuote << " New Level: " << l << endl;
+
+ mLevelQuote = l;
+ saveRelativePosition();
+ update(true);
+}
+
+void KMReaderWin::slotCycleHeaderStyles() {
+ const HeaderStrategy * strategy = headerStrategy();
+ const HeaderStyle * style = headerStyle();
+
+ const char * actionName = 0;
+ if ( style == HeaderStyle::enterprise() ) {
+ slotFancyHeaders();
+ actionName = "view_headers_fancy";
+ }
+ if ( style == HeaderStyle::fancy() ) {
+ slotBriefHeaders();
+ actionName = "view_headers_brief";
+ } else if ( style == HeaderStyle::brief() ) {
+ slotStandardHeaders();
+ actionName = "view_headers_standard";
+ } else if ( style == HeaderStyle::plain() ) {
+ if ( strategy == HeaderStrategy::standard() ) {
+ slotLongHeaders();
+ actionName = "view_headers_long";
+ } else if ( strategy == HeaderStrategy::rich() ) {
+ slotAllHeaders();
+ actionName = "view_headers_all";
+ } else if ( strategy == HeaderStrategy::all() ) {
+ slotEnterpriseHeaders();
+ actionName = "view_headers_enterprise";
+ }
+ }
+
+ if ( actionName )
+ static_cast<KRadioAction*>( mActionCollection->action( actionName ) )->setChecked( true );
+}
+
+
+void KMReaderWin::slotIconicAttachments() {
+ setAttachmentStrategy( AttachmentStrategy::iconic() );
+}
+
+void KMReaderWin::slotSmartAttachments() {
+ setAttachmentStrategy( AttachmentStrategy::smart() );
+}
+
+void KMReaderWin::slotInlineAttachments() {
+ setAttachmentStrategy( AttachmentStrategy::inlined() );
+}
+
+void KMReaderWin::slotHideAttachments() {
+ setAttachmentStrategy( AttachmentStrategy::hidden() );
+}
+
+void KMReaderWin::slotCycleAttachmentStrategy() {
+ setAttachmentStrategy( attachmentStrategy()->next() );
+ KRadioAction * action = actionForAttachmentStrategy( attachmentStrategy() );
+ assert( action );
+ action->setChecked( true );
+}
+
+
+//-----------------------------------------------------------------------------
+KMReaderWin::~KMReaderWin()
+{
+ delete mHtmlWriter; mHtmlWriter = 0;
+ delete mCSSHelper;
+ if (mAutoDelete) delete message();
+ delete mRootNode; mRootNode = 0;
+ removeTempFiles();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotMessageArrived( KMMessage *msg )
+{
+ if (msg && ((KMMsgBase*)msg)->isMessage()) {
+ if ( msg->getMsgSerNum() == mWaitingForSerNum ) {
+ setMsg( msg, true );
+ } else {
+ kdDebug( 5006 ) << "KMReaderWin::slotMessageArrived - ignoring update" << endl;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::update( KMail::Interface::Observable * observable )
+{
+ if ( !mAtmUpdate ) {
+ // reparse the msg
+ kdDebug(5006) << "KMReaderWin::update - message" << endl;
+ updateReaderWin();
+ return;
+ }
+
+ if ( !mRootNode )
+ return;
+
+ KMMessage* msg = static_cast<KMMessage*>( observable );
+ assert( msg != 0 );
+
+ // find our partNode and update it
+ if ( !msg->lastUpdatedPart() ) {
+ kdDebug(5006) << "KMReaderWin::update - no updated part" << endl;
+ return;
+ }
+ partNode* node = mRootNode->findNodeForDwPart( msg->lastUpdatedPart() );
+ if ( !node ) {
+ kdDebug(5006) << "KMReaderWin::update - can't find node for part" << endl;
+ return;
+ }
+ node->setDwPart( msg->lastUpdatedPart() );
+
+ // update the tmp file
+ // we have to set it writeable temporarily
+ ::chmod( QFile::encodeName( mAtmCurrentName ), S_IRWXU );
+ QByteArray data = node->msgPart().bodyDecodedBinary();
+ size_t size = data.size();
+ if ( node->msgPart().type() == DwMime::kTypeText && size) {
+ size = KMail::Util::crlf2lf( data.data(), size );
+ }
+ KPIM::kBytesToFile( data.data(), size, mAtmCurrentName, false, false, false );
+ ::chmod( QFile::encodeName( mAtmCurrentName ), S_IRUSR );
+
+ mAtmUpdate = false;
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::removeTempFiles()
+{
+ for (QStringList::Iterator it = mTempFiles.begin(); it != mTempFiles.end();
+ it++)
+ {
+ QFile::remove(*it);
+ }
+ mTempFiles.clear();
+ for (QStringList::Iterator it = mTempDirs.begin(); it != mTempDirs.end();
+ it++)
+ {
+ QDir(*it).rmdir(*it);
+ }
+ mTempDirs.clear();
+}
+
+
+//-----------------------------------------------------------------------------
+bool KMReaderWin::event(QEvent *e)
+{
+ if (e->type() == QEvent::ApplicationPaletteChange)
+ {
+ delete mCSSHelper;
+ mCSSHelper = new KMail::CSSHelper( QPaintDeviceMetrics( mViewer->view() ) );
+ if (message())
+ message()->readConfig();
+ update( true ); // Force update
+ return true;
+ }
+ return QWidget::event(e);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::readConfig(void)
+{
+ const KConfigGroup mdnGroup( KMKernel::config(), "MDN" );
+ /*should be: const*/ KConfigGroup reader( KMKernel::config(), "Reader" );
+
+ delete mCSSHelper;
+ mCSSHelper = new KMail::CSSHelper( QPaintDeviceMetrics( mViewer->view() ) );
+
+ mNoMDNsWhenEncrypted = mdnGroup.readBoolEntry( "not-send-when-encrypted", true );
+
+ mUseFixedFont = reader.readBoolEntry( "useFixedFont", false );
+ if ( mToggleFixFontAction )
+ mToggleFixFontAction->setChecked( mUseFixedFont );
+
+ mHtmlMail = reader.readBoolEntry( "htmlMail", false );
+ mHtmlLoadExternal = reader.readBoolEntry( "htmlLoadExternal", false );
+
+ setHeaderStyleAndStrategy( HeaderStyle::create( reader.readEntry( "header-style", "fancy" ) ),
+ HeaderStrategy::create( reader.readEntry( "header-set-displayed", "rich" ) ) );
+ KRadioAction *raction = actionForHeaderStyle( headerStyle(), headerStrategy() );
+ if ( raction )
+ raction->setChecked( true );
+
+ setAttachmentStrategy( AttachmentStrategy::create( reader.readEntry( "attachment-strategy", "smart" ) ) );
+ raction = actionForAttachmentStrategy( attachmentStrategy() );
+ if ( raction )
+ raction->setChecked( true );
+
+ // if the user uses OpenPGP then the color bar defaults to enabled
+ // else it defaults to disabled
+ mShowColorbar = reader.readBoolEntry( "showColorbar", Kpgp::Module::getKpgp()->usePGP() );
+ // if the value defaults to enabled and KMail (with color bar) is used for
+ // the first time the config dialog doesn't know this if we don't save the
+ // value now
+ reader.writeEntry( "showColorbar", mShowColorbar );
+
+ mMimeTreeAtBottom = reader.readEntry( "MimeTreeLocation", "bottom" ) != "top";
+ const QString s = reader.readEntry( "MimeTreeMode", "smart" );
+ if ( s == "never" )
+ mMimeTreeMode = 0;
+ else if ( s == "always" )
+ mMimeTreeMode = 2;
+ else
+ mMimeTreeMode = 1;
+
+ const int mimeH = reader.readNumEntry( "MimePaneHeight", 100 );
+ const int messageH = reader.readNumEntry( "MessagePaneHeight", 180 );
+ mSplitterSizes.clear();
+ if ( mMimeTreeAtBottom )
+ mSplitterSizes << messageH << mimeH;
+ else
+ mSplitterSizes << mimeH << messageH;
+
+ adjustLayout();
+
+ readGlobalOverrideCodec();
+
+ if (message())
+ update();
+ KMMessage::readConfig();
+}
+
+
+void KMReaderWin::adjustLayout() {
+ if ( mMimeTreeAtBottom )
+ mSplitter->moveToLast( mMimePartTree );
+ else
+ mSplitter->moveToFirst( mMimePartTree );
+ mSplitter->setSizes( mSplitterSizes );
+
+ if ( mMimeTreeMode == 2 && mMsgDisplay )
+ mMimePartTree->show();
+ else
+ mMimePartTree->hide();
+
+ if ( mShowColorbar && mMsgDisplay )
+ mColorBar->show();
+ else
+ mColorBar->hide();
+}
+
+
+void KMReaderWin::saveSplitterSizes( KConfigBase & c ) const {
+ if ( !mSplitter || !mMimePartTree )
+ return;
+ if ( mMimePartTree->isHidden() )
+ return; // don't rely on QSplitter maintaining sizes for hidden widgets.
+
+ c.writeEntry( "MimePaneHeight", mSplitter->sizes()[ mMimeTreeAtBottom ? 1 : 0 ] );
+ c.writeEntry( "MessagePaneHeight", mSplitter->sizes()[ mMimeTreeAtBottom ? 0 : 1 ] );
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::writeConfig( bool sync ) const {
+ KConfigGroup reader( KMKernel::config(), "Reader" );
+
+ reader.writeEntry( "useFixedFont", mUseFixedFont );
+ if ( headerStyle() )
+ reader.writeEntry( "header-style", headerStyle()->name() );
+ if ( headerStrategy() )
+ reader.writeEntry( "header-set-displayed", headerStrategy()->name() );
+ if ( attachmentStrategy() )
+ reader.writeEntry( "attachment-strategy", attachmentStrategy()->name() );
+
+ saveSplitterSizes( reader );
+
+ if ( sync )
+ kmkernel->slotRequestConfigSync();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::initHtmlWidget(void)
+{
+ mViewer->widget()->setFocusPolicy(WheelFocus);
+ // Let's better be paranoid and disable plugins (it defaults to enabled):
+ mViewer->setPluginsEnabled(false);
+ mViewer->setJScriptEnabled(false); // just make this explicit
+ mViewer->setJavaEnabled(false); // just make this explicit
+ mViewer->setMetaRefreshEnabled(false);
+ mViewer->setURLCursor(KCursor::handCursor());
+ // Espen 2000-05-14: Getting rid of thick ugly frames
+ mViewer->view()->setLineWidth(0);
+ // register our own event filter for shift-click
+ mViewer->view()->viewport()->installEventFilter( this );
+
+ if ( !htmlWriter() )
+#ifdef KMAIL_READER_HTML_DEBUG
+ mHtmlWriter = new TeeHtmlWriter( new FileHtmlWriter( QString::null ),
+ new KHtmlPartHtmlWriter( mViewer, 0 ) );
+#else
+ mHtmlWriter = new KHtmlPartHtmlWriter( mViewer, 0 );
+#endif
+
+ connect(mViewer->browserExtension(),
+ SIGNAL(openURLRequest(const KURL &, const KParts::URLArgs &)),this,
+ SLOT(slotUrlOpen(const KURL &)));
+ connect(mViewer->browserExtension(),
+ SIGNAL(createNewWindow(const KURL &, const KParts::URLArgs &)),this,
+ SLOT(slotUrlOpen(const KURL &)));
+ connect(mViewer,SIGNAL(onURL(const QString &)),this,
+ SLOT(slotUrlOn(const QString &)));
+ connect(mViewer,SIGNAL(popupMenu(const QString &, const QPoint &)),
+ SLOT(slotUrlPopup(const QString &, const QPoint &)));
+ connect( kmkernel->imProxy(), SIGNAL( sigContactPresenceChanged( const QString & ) ),
+ this, SLOT( contactStatusChanged( const QString & ) ) );
+ connect( kmkernel->imProxy(), SIGNAL( sigPresenceInfoExpired() ),
+ this, SLOT( updateReaderWin() ) );
+}
+
+void KMReaderWin::contactStatusChanged( const QString &uid)
+{
+// kdDebug( 5006 ) << k_funcinfo << " got a presence change for " << uid << endl;
+ // get the list of nodes for this contact from the htmlView
+ DOM::NodeList presenceNodes = mViewer->htmlDocument()
+ .getElementsByName( DOM::DOMString( QString::fromLatin1("presence-") + uid ) );
+ for ( unsigned int i = 0; i < presenceNodes.length(); ++i ) {
+ DOM::Node n = presenceNodes.item( i );
+ kdDebug( 5006 ) << "name is " << n.nodeName().string() << endl;
+ kdDebug( 5006 ) << "value of content was " << n.firstChild().nodeValue().string() << endl;
+ QString newPresence = kmkernel->imProxy()->presenceString( uid );
+ if ( newPresence.isNull() ) // KHTML crashes if you setNodeValue( QString::null )
+ newPresence = QString::fromLatin1( "ENOIMRUNNING" );
+ n.firstChild().setNodeValue( newPresence );
+// kdDebug( 5006 ) << "value of content is now " << n.firstChild().nodeValue().string() << endl;
+ }
+// kdDebug( 5006 ) << "and we updated the above presence nodes" << uid << endl;
+}
+
+void KMReaderWin::setAttachmentStrategy( const AttachmentStrategy * strategy ) {
+ mAttachmentStrategy = strategy ? strategy : AttachmentStrategy::smart();
+ update( true );
+}
+
+void KMReaderWin::setHeaderStyleAndStrategy( const HeaderStyle * style,
+ const HeaderStrategy * strategy ) {
+ mHeaderStyle = style ? style : HeaderStyle::fancy();
+ mHeaderStrategy = strategy ? strategy : HeaderStrategy::rich();
+ update( true );
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::setOverrideEncoding( const QString & encoding )
+{
+ if ( encoding == mOverrideEncoding )
+ return;
+
+ mOverrideEncoding = encoding;
+ if ( mSelectEncodingAction ) {
+ if ( encoding.isEmpty() ) {
+ mSelectEncodingAction->setCurrentItem( 0 );
+ }
+ else {
+ QStringList encodings = mSelectEncodingAction->items();
+ uint i = 0;
+ for ( QStringList::const_iterator it = encodings.begin(), end = encodings.end(); it != end; ++it, ++i ) {
+ if ( KGlobal::charsets()->encodingForName( *it ) == encoding ) {
+ mSelectEncodingAction->setCurrentItem( i );
+ break;
+ }
+ }
+ if ( i == encodings.size() ) {
+ // the value of encoding is unknown => use Auto
+ kdWarning(5006) << "Unknown override character encoding \"" << encoding
+ << "\". Using Auto instead." << endl;
+ mSelectEncodingAction->setCurrentItem( 0 );
+ mOverrideEncoding = QString::null;
+ }
+ }
+ }
+ update( true );
+}
+
+
+void KMReaderWin::setPrintFont( const QFont& font )
+{
+
+ mCSSHelper->setPrintFont( font );
+}
+
+//-----------------------------------------------------------------------------
+const QTextCodec * KMReaderWin::overrideCodec() const
+{
+ kdDebug(5006) << k_funcinfo << " mOverrideEncoding == '" << mOverrideEncoding << "'" << endl;
+ if ( mOverrideEncoding.isEmpty() || mOverrideEncoding == "Auto" ) // Auto
+ return 0;
+ else
+ return KMMsgBase::codecForName( mOverrideEncoding.latin1() );
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotSetEncoding()
+{
+ if ( mSelectEncodingAction->currentItem() == 0 ) // Auto
+ mOverrideEncoding = QString();
+ else
+ mOverrideEncoding = KGlobal::charsets()->encodingForName( mSelectEncodingAction->currentText() );
+ update( true );
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::readGlobalOverrideCodec()
+{
+ // if the global character encoding wasn't changed then there's nothing to do
+ if ( GlobalSettings::self()->overrideCharacterEncoding() == mOldGlobalOverrideEncoding )
+ return;
+
+ setOverrideEncoding( GlobalSettings::self()->overrideCharacterEncoding() );
+ mOldGlobalOverrideEncoding = GlobalSettings::self()->overrideCharacterEncoding();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::setMsg(KMMessage* aMsg, bool force)
+{
+ if (aMsg)
+ kdDebug(5006) << "(" << aMsg->getMsgSerNum() << ", last " << mLastSerNum << ") " << aMsg->subject() << " "
+ << aMsg->fromStrip() << ", readyToShow " << (aMsg->readyToShow()) << endl;
+
+ //Reset the level quote if the msg has changed.
+ if (aMsg && aMsg->getMsgSerNum() != mLastSerNum ){
+ mLevelQuote = GlobalSettings::self()->collapseQuoteLevelSpin()-1;
+ }
+ if ( mPrinting )
+ mLevelQuote = -1;
+
+ bool complete = true;
+ if ( aMsg &&
+ !aMsg->readyToShow() &&
+ (aMsg->getMsgSerNum() != mLastSerNum) &&
+ !aMsg->isComplete() )
+ complete = false;
+
+ // If not forced and there is aMsg and aMsg is same as mMsg then return
+ if (!force && aMsg && mLastSerNum != 0 && aMsg->getMsgSerNum() == mLastSerNum)
+ return;
+
+ // (de)register as observer
+ if (aMsg && message())
+ message()->detach( this );
+ if (aMsg)
+ aMsg->attach( this );
+ mAtmUpdate = false;
+
+ // connect to the updates if we have hancy headers
+
+ mDelayedMarkTimer.stop();
+
+ mMessage = 0;
+ if ( !aMsg ) {
+ mWaitingForSerNum = 0; // otherwise it has been set
+ mLastSerNum = 0;
+ } else {
+ mLastSerNum = aMsg->getMsgSerNum();
+ // Check if the serial number can be used to find the assoc KMMessage
+ // If so, keep only the serial number (and not mMessage), to avoid a dangling mMessage
+ // when going to another message in the mainwindow.
+ // Otherwise, keep only mMessage, this is fine for standalone KMReaderMainWins since
+ // we're working on a copy of the KMMessage, which we own.
+ if (message() != aMsg) {
+ mMessage = aMsg;
+ mLastSerNum = 0;
+ }
+ }
+
+ 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
+ // otherwise we'll get flickering with progressively loaded messages
+ if ( complete )
+ {
+ // Avoid flicker, somewhat of a cludge
+ if (force) {
+ // stop the timer to avoid calling updateReaderWin twice
+ mUpdateReaderWinTimer.stop();
+ updateReaderWin();
+ }
+ else if (mUpdateReaderWinTimer.isActive())
+ mUpdateReaderWinTimer.changeInterval( delay );
+ else
+ mUpdateReaderWinTimer.start( 0, true );
+ }
+
+ if ( aMsg && (aMsg->isUnread() || aMsg->isNew()) && GlobalSettings::self()->delayedMarkAsRead() ) {
+ if ( GlobalSettings::self()->delayedMarkTime() != 0 )
+ mDelayedMarkTimer.start( GlobalSettings::self()->delayedMarkTime() * 1000, true );
+ else
+ slotTouchMessage();
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::clearCache()
+{
+ mUpdateReaderWinTimer.stop();
+ clear();
+ mDelayedMarkTimer.stop();
+ mLastSerNum = 0;
+ mWaitingForSerNum = 0;
+ mMessage = 0;
+}
+
+// enter items for the "Important changes" list here:
+static const char * const kmailChanges[] = {
+ ""
+};
+static const int numKMailChanges =
+ sizeof kmailChanges / sizeof *kmailChanges;
+
+// enter items for the "new features" list here, so the main body of
+// the welcome page can be left untouched (probably much easier for
+// the translators). Note that the <li>...</li> tags are added
+// automatically below:
+static const char * const kmailNewFeatures[] = {
+ I18N_NOOP("Full namespace support for IMAP"),
+ I18N_NOOP("Offline mode"),
+ I18N_NOOP("Sieve script management and editing"),
+ I18N_NOOP("Account specific filtering"),
+ I18N_NOOP("Filtering of incoming mail for online IMAP accounts"),
+ I18N_NOOP("Online IMAP folders can be used when filtering into folders"),
+ I18N_NOOP("Automatically delete older mails on POP servers")
+};
+static const int numKMailNewFeatures =
+ sizeof kmailNewFeatures / sizeof *kmailNewFeatures;
+
+
+//-----------------------------------------------------------------------------
+//static
+QString KMReaderWin::newFeaturesMD5()
+{
+ QCString str;
+ for ( int i = 0 ; i < numKMailChanges ; ++i )
+ str += kmailChanges[i];
+ for ( int i = 0 ; i < numKMailNewFeatures ; ++i )
+ str += kmailNewFeatures[i];
+ KMD5 md5( str );
+ return md5.base64Digest();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::displaySplashPage( const QString &info )
+{
+ mMsgDisplay = false;
+ adjustLayout();
+
+ QString location = locate("data", "kmail/about/main.html");
+ QString content = KPIM::kFileToString(location);
+ content = content.arg( locate( "data", "libkdepim/about/kde_infopage.css" ) );
+ if ( kapp->reverseLayout() )
+ content = content.arg( "@import \"%1\";" ).arg( locate( "data", "libkdepim/about/kde_infopage_rtl.css" ) );
+ else
+ content = content.arg( "" );
+
+ mViewer->begin(KURL( location ));
+
+ QString fontSize = QString::number( pointsToPixel( mCSSHelper->bodyFont().pointSize() ) );
+ QString appTitle = i18n("KMail");
+ QString catchPhrase = ""; //not enough space for a catch phrase at default window size i18n("Part of the Kontact Suite");
+ QString quickDescription = i18n("The email client for the K Desktop Environment.");
+ mViewer->write(content.arg(fontSize).arg(appTitle).arg(catchPhrase).arg(quickDescription).arg(info));
+ mViewer->end();
+}
+
+void KMReaderWin::displayBusyPage()
+{
+ QString info =
+ i18n( "<h2 style='margin-top: 0px;'>Retrieving Folder Contents</h2><p>Please wait . . .</p>&nbsp;" );
+
+ displaySplashPage( info );
+}
+
+void KMReaderWin::displayOfflinePage()
+{
+ QString info =
+ i18n( "<h2 style='margin-top: 0px;'>Offline</h2><p>KMail is currently in offline mode. "
+ "Click <a href=\"kmail:goOnline\">here</a> to go online . . .</p>&nbsp;" );
+
+ displaySplashPage( info );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::displayAboutPage()
+{
+ QString info =
+ i18n("%1: KMail version; %2: help:// URL; %3: homepage URL; "
+ "%4: prior KMail version; %5: prior KDE version; "
+ "%6: generated list of new features; "
+ "%7: First-time user text (only shown on first start); "
+ "%8: generated list of important changes; "
+ "--- end of comment ---",
+ "<h2 style='margin-top: 0px;'>Welcome to KMail %1</h2><p>KMail is the email client for the K "
+ "Desktop Environment. It is designed to be fully compatible with "
+ "Internet mailing standards including MIME, SMTP, POP3 and IMAP."
+ "</p>\n"
+ "<ul><li>KMail has many powerful features which are described in the "
+ "<a href=\"%2\">documentation</a></li>\n"
+ "<li>The <a href=\"%3\">KMail homepage</A> offers information about "
+ "new versions of KMail</li></ul>\n"
+ "%8\n" // important changes
+ "<p>Some of the new features in this release of KMail include "
+ "(compared to KMail %4, which is part of KDE %5):</p>\n"
+ "<ul>\n%6</ul>\n"
+ "%7\n"
+ "<p>We hope that you will enjoy KMail.</p>\n"
+ "<p>Thank you,</p>\n"
+ "<p style='margin-bottom: 0px'>&nbsp; &nbsp; The KMail Team</p>")
+ .arg(KMAIL_VERSION) // KMail version
+ .arg("help:/kmail/index.html") // KMail help:// URL
+ .arg("http://kontact.kde.org/kmail/") // KMail homepage URL
+ .arg("1.8").arg("3.4"); // prior KMail and KDE version
+
+ QString featureItems;
+ for ( int i = 0 ; i < numKMailNewFeatures ; i++ )
+ featureItems += i18n("<li>%1</li>\n").arg( i18n( kmailNewFeatures[i] ) );
+
+ info = info.arg( featureItems );
+
+ if( kmkernel->firstStart() ) {
+ info = info.arg( i18n("<p>Please take a moment to fill in the KMail "
+ "configuration panel at Settings-&gt;Configure "
+ "KMail.\n"
+ "You need to create at least a default identity and "
+ "an incoming as well as outgoing mail account."
+ "</p>\n") );
+ } else {
+ info = info.arg( QString::null );
+ }
+
+ if ( ( numKMailChanges > 1 ) || ( numKMailChanges == 1 && strlen(kmailChanges[0]) > 0 ) ) {
+ QString changesText =
+ i18n("<p><span style='font-size:125%; font-weight:bold;'>"
+ "Important changes</span> (compared to KMail %1):</p>\n")
+ .arg("1.8");
+ changesText += "<ul>\n";
+ for ( int i = 0 ; i < numKMailChanges ; i++ )
+ changesText += i18n("<li>%1</li>\n").arg( i18n( kmailChanges[i] ) );
+ changesText += "</ul>\n";
+ info = info.arg( changesText );
+ }
+ else
+ info = info.arg(""); // remove the %8
+
+ displaySplashPage( info );
+}
+
+void KMReaderWin::enableMsgDisplay() {
+ mMsgDisplay = true;
+ adjustLayout();
+}
+
+
+//-----------------------------------------------------------------------------
+
+void KMReaderWin::updateReaderWin()
+{
+ if (!mMsgDisplay) return;
+
+ mViewer->setOnlyLocalReferences(!htmlLoadExternal());
+
+ htmlWriter()->reset();
+
+ KMFolder* folder = 0;
+ if (message(&folder))
+ {
+ if ( mShowColorbar )
+ mColorBar->show();
+ else
+ mColorBar->hide();
+ displayMessage();
+ }
+ else
+ {
+ mColorBar->hide();
+ mMimePartTree->hide();
+ mMimePartTree->clear();
+ htmlWriter()->begin( mCSSHelper->cssDefinitions( isFixedFont() ) );
+ htmlWriter()->write( mCSSHelper->htmlHead( isFixedFont() ) + "</body></html>" );
+ htmlWriter()->end();
+ }
+
+ if (mSavedRelativePosition)
+ {
+ QScrollView * scrollview = static_cast<QScrollView *>(mViewer->widget());
+ scrollview->setContentsPos( 0,
+ qRound( scrollview->contentsHeight() * mSavedRelativePosition ) );
+ mSavedRelativePosition = 0;
+ }
+}
+
+//-----------------------------------------------------------------------------
+int KMReaderWin::pointsToPixel(int pointSize) const
+{
+ const QPaintDeviceMetrics pdm(mViewer->view());
+
+ return (pointSize * pdm.logicalDpiY() + 36) / 72;
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::showHideMimeTree( bool isPlainTextTopLevel ) {
+ if ( mMimeTreeMode == 2 ||
+ ( mMimeTreeMode == 1 && !isPlainTextTopLevel ) )
+ mMimePartTree->show();
+ else {
+ // don't rely on QSplitter maintaining sizes for hidden widgets:
+ KConfigGroup reader( KMKernel::config(), "Reader" );
+ saveSplitterSizes( reader );
+ mMimePartTree->hide();
+ }
+}
+
+void KMReaderWin::displayMessage() {
+ KMMessage * msg = message();
+
+ mMimePartTree->clear();
+ showHideMimeTree( !msg || // treat no message as "text/plain"
+ ( msg->type() == DwMime::kTypeText
+ && msg->subtype() == DwMime::kSubtypePlain ) );
+
+ if ( !msg )
+ return;
+
+ msg->setOverrideCodec( overrideCodec() );
+
+ htmlWriter()->begin( mCSSHelper->cssDefinitions( isFixedFont() ) );
+ htmlWriter()->queue( mCSSHelper->htmlHead( isFixedFont() ) );
+
+ if (!parent())
+ setCaption(msg->subject());
+
+ removeTempFiles();
+
+ mColorBar->setNeutralMode();
+
+ parseMsg(msg);
+
+ if( mColorBar->isNeutral() )
+ mColorBar->setNormalMode();
+
+ htmlWriter()->queue("</body></html>");
+ htmlWriter()->flush();
+
+ QTimer::singleShot( 1, this, SLOT(injectAttachments()) );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::parseMsg(KMMessage* aMsg)
+{
+#ifndef NDEBUG
+ kdDebug( 5006 )
+ << "parseMsg(KMMessage* aMsg "
+ << ( aMsg == message() ? "==" : "!=" )
+ << " aMsg )" << endl;
+#endif
+
+ KMMessagePart msgPart;
+ QCString subtype, contDisp;
+ QByteArray str;
+
+ assert(aMsg!=0);
+
+ aMsg->setIsBeingParsed( true );
+
+ if ( mRootNode && !mRootNode->processed() )
+ {
+ kdWarning() << "The root node is not yet processed! Danger!\n";
+ return;
+ } else
+ delete mRootNode;
+ mRootNode = partNode::fromMessage( aMsg );
+ const QCString mainCntTypeStr = mRootNode->typeString() + '/' + mRootNode->subTypeString();
+
+ QString cntDesc = aMsg->subject();
+ if( cntDesc.isEmpty() )
+ cntDesc = i18n("( body part )");
+ KIO::filesize_t cntSize = aMsg->msgSize();
+ QString cntEnc;
+ if( aMsg->contentTransferEncodingStr().isEmpty() )
+ cntEnc = "7bit";
+ else
+ cntEnc = aMsg->contentTransferEncodingStr();
+
+ // fill the MIME part tree viewer
+ mRootNode->fillMimePartTree( 0,
+ mMimePartTree,
+ cntDesc,
+ mainCntTypeStr,
+ cntEnc,
+ cntSize );
+
+ partNode* vCardNode = mRootNode->findType( DwMime::kTypeText, DwMime::kSubtypeXVCard );
+ bool hasVCard = false;
+ 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 QString vcard = vCardNode->msgPart().bodyToUnicode( overrideCodec() );
+ KABC::VCardConverter t;
+ if ( !t.parseVCards( vcard ).empty() ) {
+ hasVCard = true;
+ kdDebug(5006) << "FOUND A VALID VCARD" << endl;
+ writeMessagePartToTempFile( &vCardNode->msgPart(), vCardNode->nodeId() );
+ }
+ }
+ htmlWriter()->queue( writeMsgHeader(aMsg, hasVCard, true ) );
+
+ // show message content
+ ObjectTreeParser otp( this );
+ 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();
+ 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).
+ if ( signatureState != KMMsgNotSigned ||
+ aMsg->signatureState() == KMMsgSignatureStateUnknown ) {
+ aMsg->setSignatureState( signatureState );
+ }
+
+ bool emitReplaceMsgByUnencryptedVersion = false;
+ const KConfigGroup reader( KMKernel::config(), "Reader" );
+ if ( reader.readBoolEntry( "store-displayed-messages-unencrypted", false ) ) {
+
+ // Hack to make sure the S/MIME CryptPlugs follows the strict requirement
+ // of german government:
+ // --> All received encrypted messages *must* be stored in unencrypted form
+ // after they have been decrypted once the user has read them.
+ // ( "Aufhebung der Verschluesselung nach dem Lesen" )
+ //
+ // note: Since there is no configuration option for this, we do that for
+ // all kinds of encryption now - *not* just for S/MIME.
+ // This could be changed in the objectTreeToDecryptedMsg() function
+ // by deciding when (or when not, resp.) to set the 'dataNode' to
+ // something different than 'curNode'.
+
+
+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) << " (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())
+ // 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())
+ // only proceed if this message is (at least partially) encrypted
+ && ( (KMMsgFullyEncrypted == encryptionState)
+ || (KMMsgPartiallyEncrypted == encryptionState) ) ) {
+
+kdDebug(5006) << "KMReaderWin - calling objectTreeToDecryptedMsg()" << endl;
+
+ NewByteArray decryptedData;
+ // note: The following call may change the message's headers.
+ objectTreeToDecryptedMsg( mRootNode, decryptedData, *aMsg );
+ // add a \0 to the data
+ decryptedData.appendNULL();
+ QCString resultString( decryptedData.data() );
+kdDebug(5006) << "KMReaderWin - resulting data:" << resultString << endl;
+
+ if( !resultString.isEmpty() ) {
+kdDebug(5006) << "KMReaderWin - composing unencrypted message" << endl;
+ // try this:
+ aMsg->setBody( resultString );
+ KMMessage* unencryptedMessage = new KMMessage( *aMsg );
+ unencryptedMessage->setParent( 0 );
+ // because this did not work:
+ /*
+ DwMessage dwMsg( aMsg->asDwString() );
+ dwMsg.Body() = DwBody( DwString( resultString.data() ) );
+ dwMsg.Body().Parse();
+ KMMessage* unencryptedMessage = new KMMessage( &dwMsg );
+ */
+ //kdDebug(5006) << "KMReaderWin - resulting message:" << unencryptedMessage->asString() << endl;
+ kdDebug(5006) << "KMReaderWin - attach unencrypted message to aMsg" << endl;
+ aMsg->setUnencryptedMsg( unencryptedMessage );
+ emitReplaceMsgByUnencryptedVersion = true;
+ }
+ }
+ }
+
+ // save current main Content-Type before deleting mRootNode
+ const int rootNodeCntType = mRootNode ? mRootNode->type() : DwMime::kTypeText;
+ const int rootNodeCntSubtype = mRootNode ? mRootNode->subType() : DwMime::kSubtypePlain;
+
+ // store message id to avoid endless recursions
+ setIdOfLastViewedMessage( aMsg->msgId() );
+
+ if( emitReplaceMsgByUnencryptedVersion ) {
+ kdDebug(5006) << "KMReaderWin - invoce saving in decrypted form:" << endl;
+ emit replaceMsgByUnencryptedVersion();
+ } else {
+ kdDebug(5006) << "KMReaderWin - finished parsing and displaying of message." << endl;
+ showHideMimeTree( rootNodeCntType == DwMime::kTypeText &&
+ rootNodeCntSubtype == DwMime::kSubtypePlain );
+ }
+
+ aMsg->setIsBeingParsed( false );
+}
+
+
+//-----------------------------------------------------------------------------
+QString KMReaderWin::writeMsgHeader(KMMessage* aMsg, bool hasVCard, 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;
+ QString href;
+ if (hasVCard)
+ href = QString("file:") + KURL::encode_string( mTempFiles.last() );
+
+ return headerStyle()->format( aMsg, headerStrategy(), href, mPrinting, topLevel );
+}
+
+
+
+//-----------------------------------------------------------------------------
+QString KMReaderWin::writeMessagePartToTempFile( KMMessagePart* aMsgPart,
+ int aPartNum )
+{
+ QString fileName = aMsgPart->fileName();
+ if( fileName.isEmpty() )
+ fileName = aMsgPart->name();
+
+ //--- Sven's save attachments to /tmp start ---
+ QString fname = createTempDir( QString::number( aPartNum ) );
+ if ( fname.isEmpty() )
+ return QString();
+
+ // strip off a leading path
+ int slashPos = fileName.findRev( '/' );
+ if( -1 != slashPos )
+ fileName = fileName.mid( slashPos + 1 );
+ if( fileName.isEmpty() )
+ fileName = "unnamed";
+ fname += "/" + fileName;
+
+ QByteArray data = aMsgPart->bodyDecodedBinary();
+ size_t size = data.size();
+ if ( aMsgPart->type() == DwMime::kTypeText && size) {
+ // convert CRLF to LF before writing text attachments to disk
+ size = KMail::Util::crlf2lf( data.data(), size );
+ }
+ if( !KPIM::kBytesToFile( data.data(), size, fname, false, false, false ) )
+ return QString::null;
+
+ mTempFiles.append( fname );
+ // make file read-only so that nobody gets the impression that he might
+ // edit attached files (cf. bug #52813)
+ ::chmod( QFile::encodeName( fname ), S_IRUSR );
+
+ return fname;
+}
+
+QString KMReaderWin::createTempDir( const QString &param )
+{
+ KTempFile *tempFile = new KTempFile( QString::null, "." + param );
+ tempFile->setAutoDelete( true );
+ QString fname = tempFile->name();
+ delete tempFile;
+
+ if( ::access( QFile::encodeName( fname ), W_OK ) != 0 )
+ // Not there or not writable
+ if( ::mkdir( QFile::encodeName( fname ), 0 ) != 0
+ || ::chmod( QFile::encodeName( fname ), S_IRWXU ) != 0 )
+ return QString::null; //failed create
+
+ assert( !fname.isNull() );
+
+ mTempDirs.append( fname );
+ return fname;
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::showVCard( KMMessagePart * msgPart ) {
+ const QString vCard = msgPart->bodyToUnicode( overrideCodec() );
+
+ VCardViewer *vcv = new VCardViewer(this, vCard, "vCardDialog");
+ vcv->show();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::printMsg()
+{
+ if (!message()) return;
+ mViewer->view()->print();
+}
+
+
+//-----------------------------------------------------------------------------
+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;
+
+ QString path = aUrl.path();
+ uint right = path.findRev('/');
+ uint left = path.findRev('.', right);
+
+ int res = path.mid(left + 1, right - left - 1).toInt(&ok);
+ return (ok) ? res : -1;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::resizeEvent(QResizeEvent *)
+{
+ if( !mResizeTimer.isActive() )
+ {
+ //
+ // Combine all resize operations that are requested as long a
+ // the timer runs.
+ //
+ mResizeTimer.start( 100, true );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotDelayedResize()
+{
+ mSplitter->setGeometry(0, 0, width(), height());
+}
+
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotTouchMessage()
+{
+ if ( !message() )
+ return;
+
+ if ( !message()->isNew() && !message()->isUnread() )
+ return;
+
+ SerNumList serNums;
+ serNums.append( message()->getMsgSerNum() );
+ KMCommand *command = new KMSetStatusCommand( KMMsgStatusRead, serNums );
+ command->start();
+
+ // should we send an MDN?
+ if ( mNoMDNsWhenEncrypted &&
+ message()->encryptionState() != KMMsgNotEncrypted &&
+ message()->encryptionState() != KMMsgEncryptionStateUnknown )
+ return;
+
+ KMFolder *folder = message()->parent();
+ if (folder &&
+ (folder->isOutbox() || folder->isSent() || folder->isTrash() ||
+ folder->isDrafts() || folder->isTemplates() ) )
+ return;
+
+ if ( KMMessage * receipt = message()->createMDN( MDN::ManualAction,
+ MDN::Displayed,
+ true /* allow GUI */ ) )
+ if ( !kmkernel->msgSender()->send( receipt ) ) // send or queue
+ KMessageBox::error( this, i18n("Could not send MDN.") );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::closeEvent(QCloseEvent *e)
+{
+ QWidget::closeEvent(e);
+ writeConfig();
+}
+
+
+bool foundSMIMEData( const QString aUrl,
+ QString& displayName,
+ QString& libName,
+ QString& keyId )
+{
+ static QString showCertMan("showCertificate#");
+ displayName = "";
+ libName = "";
+ keyId = "";
+ int i1 = aUrl.find( showCertMan );
+ if( -1 < i1 ) {
+ i1 += showCertMan.length();
+ int i2 = aUrl.find(" ### ", i1);
+ if( i1 < i2 )
+ {
+ displayName = aUrl.mid( i1, i2-i1 );
+ i1 = i2+5;
+ i2 = aUrl.find(" ### ", i1);
+ if( i1 < i2 )
+ {
+ libName = aUrl.mid( i1, i2-i1 );
+ i2 += 5;
+
+ keyId = aUrl.mid( i2 );
+ /*
+ int len = aUrl.length();
+ if( len > i2+1 ) {
+ keyId = aUrl.mid( i2, 2 );
+ i2 += 2;
+ while( len > i2+1 ) {
+ keyId += ':';
+ keyId += aUrl.mid( i2, 2 );
+ i2 += 2;
+ }
+ }
+ */
+ }
+ }
+ }
+ return !keyId.isEmpty();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotUrlOn(const QString &aUrl)
+{
+ const KURL url(aUrl);
+ if ( url.protocol() == "kmail" || url.protocol() == "x-kmail"
+ || (url.protocol().isEmpty() && url.path().isEmpty()) ) {
+ mViewer->setDNDEnabled( false );
+ } else {
+ mViewer->setDNDEnabled( true );
+ }
+
+ if ( aUrl.stripWhiteSpace().isEmpty() ) {
+ KPIM::BroadcastStatus::instance()->reset();
+ return;
+ }
+
+ mUrlClicked = url;
+
+ const QString msg = URLHandlerManager::instance()->statusBarMessage( url, this );
+
+ kdWarning( msg.isEmpty(), 5006 ) << "KMReaderWin::slotUrlOn(): Unhandled URL hover!" << endl;
+ KPIM::BroadcastStatus::instance()->setTransientStatusMsg( msg );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotUrlOpen(const KURL &aUrl, const KParts::URLArgs &)
+{
+ mUrlClicked = aUrl;
+
+ if ( URLHandlerManager::instance()->handleClick( aUrl, this ) )
+ return;
+
+ kdWarning( 5006 ) << "KMReaderWin::slotOpenUrl(): Unhandled URL click!" << endl;
+ emit urlClicked( aUrl, Qt::LeftButton );
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotUrlPopup(const QString &aUrl, const QPoint& aPos)
+{
+ const KURL url( aUrl );
+ mUrlClicked = url;
+
+ if ( URLHandlerManager::instance()->handleContextMenuRequest( url, aPos, this ) )
+ return;
+
+ if ( message() ) {
+ kdWarning( 5006 ) << "KMReaderWin::slotUrlPopup(): Unhandled URL right-click!" << endl;
+ emit popupMenu( *message(), url, aPos );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::showAttachmentPopup( int id, const QString & name, const QPoint & p )
+{
+ mAtmCurrent = id;
+ mAtmCurrentName = name;
+ KPopupMenu *menu = new KPopupMenu();
+ menu->insertItem(SmallIcon("fileopen"),i18n("to open", "Open"), 1);
+ menu->insertItem(i18n("Open With..."), 2);
+ 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() )
+ menu->insertItem(SmallIcon("edit"), i18n("Edit Attachment"), 8 );
+ if ( GlobalSettings::self()->allowAttachmentDeletion() )
+ 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);
+ connect(menu, SIGNAL(activated(int)), this, SLOT(slotHandleAttachment(int)));
+ menu->exec( p ,0 );
+ delete menu;
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::setStyleDependantFrameWidth()
+{
+ if ( !mBox )
+ return;
+ // set the width of the frame to a reasonable value for the current GUI style
+ int frameWidth;
+ if( style().isA("KeramikStyle") )
+ frameWidth = style().pixelMetric( QStyle::PM_DefaultFrameWidth ) - 1;
+ else
+ frameWidth = style().pixelMetric( QStyle::PM_DefaultFrameWidth );
+ if ( frameWidth < 0 )
+ frameWidth = 0;
+ if ( frameWidth != mBox->lineWidth() )
+ mBox->setLineWidth( frameWidth );
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::styleChange( QStyle& oldStyle )
+{
+ setStyleDependantFrameWidth();
+ QWidget::styleChange( oldStyle );
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotHandleAttachment( int choice )
+{
+ mAtmUpdate = true;
+ partNode* node = mRootNode ? mRootNode->findId( mAtmCurrent ) : 0;
+ if ( mAtmCurrentName.isEmpty() && node )
+ mAtmCurrentName = tempFileUrlFromPartNode( node ).path();
+ if ( choice < 7 ) {
+ KMHandleAttachmentCommand* command = new KMHandleAttachmentCommand(
+ node, message(), mAtmCurrent, mAtmCurrentName,
+ KMHandleAttachmentCommand::AttachmentAction( choice ), 0, this );
+ connect( command, SIGNAL( showAttachment( int, const QString& ) ),
+ this, SLOT( slotAtmView( int, const QString& ) ) );
+ command->start();
+ } else if ( choice == 7 ) {
+ slotDeleteAttachment( node );
+ } else if ( choice == 8 ) {
+ slotEditAttachment( node );
+ } else if ( choice == 9 ) {
+ if ( !node ) return;
+ KURL::List urls;
+ KURL url = tempFileUrlFromPartNode( node );
+ if (!url.isValid() ) return;
+ urls.append( url );
+ KURLDrag* drag = new KURLDrag( urls, this );
+ QApplication::clipboard()->setData( drag, QClipboard::Clipboard );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotFind()
+{
+ mViewer->findText();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotFindNext()
+{
+ mViewer->findTextNext();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotToggleFixedFont()
+{
+ mUseFixedFont = !mUseFixedFont;
+ saveRelativePosition();
+ update(true);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotCopySelectedText()
+{
+ kapp->clipboard()->setText( mViewer->selectedText() );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::atmViewMsg(KMMessagePart* aMsgPart)
+{
+ assert(aMsgPart!=0);
+ KMMessage* msg = new KMMessage;
+ msg->fromString(aMsgPart->bodyDecoded());
+ assert(msg != 0);
+ msg->setMsgSerNum( 0 ); // because lookups will fail
+ // some information that is needed for imap messages with LOD
+ msg->setParent( message()->parent() );
+ msg->setUID(message()->UID());
+ msg->setReadyToShow(true);
+ KMReaderMainWin *win = new KMReaderMainWin();
+ win->showMsg( overrideEncoding(), msg );
+ win->show();
+}
+
+
+void KMReaderWin::setMsgPart( partNode * node ) {
+ htmlWriter()->reset();
+ mColorBar->hide();
+ htmlWriter()->begin( mCSSHelper->cssDefinitions( isFixedFont() ) );
+ htmlWriter()->write( mCSSHelper->htmlHead( isFixedFont() ) );
+ // end ###
+ if ( node ) {
+ ObjectTreeParser otp( this, 0, true );
+ otp.parseObjectTree( node );
+ }
+ // ### this, too
+ htmlWriter()->queue( "</body></html>" );
+ htmlWriter()->flush();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::setMsgPart( KMMessagePart* aMsgPart, bool aHTML,
+ const QString& aFileName, const QString& pname )
+{
+ KCursorSaver busy(KBusyPtr::busy());
+ if (kasciistricmp(aMsgPart->typeStr(), "message")==0) {
+ // if called from compose win
+ KMMessage* msg = new KMMessage;
+ assert(aMsgPart!=0);
+ msg->fromString(aMsgPart->bodyDecoded());
+ mMainWindow->setCaption(msg->subject());
+ setMsg(msg, true);
+ setAutoDelete(true);
+ } else if (kasciistricmp(aMsgPart->typeStr(), "text")==0) {
+ if (kasciistricmp(aMsgPart->subtypeStr(), "x-vcard") == 0) {
+ showVCard( aMsgPart );
+ return;
+ }
+ htmlWriter()->begin( mCSSHelper->cssDefinitions( isFixedFont() ) );
+ htmlWriter()->queue( mCSSHelper->htmlHead( isFixedFont() ) );
+
+ if (aHTML && (kasciistricmp(aMsgPart->subtypeStr(), "html")==0)) { // HTML
+ // ### this is broken. It doesn't stip off the HTML header and footer!
+ htmlWriter()->queue( aMsgPart->bodyToUnicode( overrideCodec() ) );
+ mColorBar->setHtmlMode();
+ } else { // plain text
+ const QCString str = aMsgPart->bodyDecoded();
+ ObjectTreeParser otp( this );
+ otp.writeBodyStr( str,
+ overrideCodec() ? overrideCodec() : aMsgPart->codec(),
+ message() ? message()->from() : QString::null );
+ }
+ htmlWriter()->queue("</body></html>");
+ htmlWriter()->flush();
+ mMainWindow->setCaption(i18n("View Attachment: %1").arg(pname));
+ } else if (kasciistricmp(aMsgPart->typeStr(), "image")==0 ||
+ (kasciistricmp(aMsgPart->typeStr(), "application")==0 &&
+ kasciistricmp(aMsgPart->subtypeStr(), "postscript")==0))
+ {
+ if (aFileName.isEmpty()) return; // prevent crash
+ // Open the window with a size so the image fits in (if possible):
+ QImageIO *iio = new QImageIO();
+ iio->setFileName(aFileName);
+ if( iio->read() ) {
+ QImage img = iio->image();
+ QRect desk = KGlobalSettings::desktopGeometry(mMainWindow);
+ // determine a reasonable window size
+ int width, height;
+ if( img.width() < 50 )
+ width = 70;
+ else if( img.width()+20 < desk.width() )
+ width = img.width()+20;
+ else
+ width = desk.width();
+ if( img.height() < 50 )
+ height = 70;
+ else if( img.height()+20 < desk.height() )
+ height = img.height()+20;
+ else
+ height = desk.height();
+ mMainWindow->resize( width, height );
+ }
+ // Just write the img tag to HTML:
+ htmlWriter()->begin( mCSSHelper->cssDefinitions( isFixedFont() ) );
+ htmlWriter()->write( mCSSHelper->htmlHead( isFixedFont() ) );
+ htmlWriter()->write( "<img src=\"file:" +
+ KURL::encode_string( aFileName ) +
+ "\" border=\"0\">\n"
+ "</body></html>\n" );
+ htmlWriter()->end();
+ setCaption( i18n("View Attachment: %1").arg( pname ) );
+ show();
+ } else {
+ htmlWriter()->begin( mCSSHelper->cssDefinitions( isFixedFont() ) );
+ htmlWriter()->queue( mCSSHelper->htmlHead( isFixedFont() ) );
+ htmlWriter()->queue( "<pre>" );
+
+ QString str = aMsgPart->bodyDecoded();
+ // A QString cannot handle binary data. So if it's shorter than the
+ // attachment, we assume the attachment is binary:
+ if( str.length() < (unsigned) aMsgPart->decodedSize() ) {
+ str.prepend( i18n("[KMail: Attachment contains binary data. Trying to show first character.]",
+ "[KMail: Attachment contains binary data. Trying to show first %n characters.]",
+ str.length()) + QChar('\n') );
+ }
+ htmlWriter()->queue( QStyleSheet::escape( str ) );
+ htmlWriter()->queue( "</pre>" );
+ htmlWriter()->queue("</body></html>");
+ htmlWriter()->flush();
+ mMainWindow->setCaption(i18n("View Attachment: %1").arg(pname));
+ }
+ // ---Sven's view text, html and image attachments in html widget end ---
+}
+
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotAtmView( int id, const QString& name )
+{
+ partNode* node = mRootNode ? mRootNode->findId( id ) : 0;
+ if( node ) {
+ mAtmCurrent = id;
+ mAtmCurrentName = name;
+ if ( mAtmCurrentName.isEmpty() )
+ mAtmCurrentName = tempFileUrlFromPartNode( node ).path();
+
+ KMMessagePart& msgPart = node->msgPart();
+ QString pname = msgPart.fileName();
+ if (pname.isEmpty()) pname=msgPart.name();
+ if (pname.isEmpty()) pname=msgPart.contentDescription();
+ if (pname.isEmpty()) pname="unnamed";
+ // image Attachment is saved already
+ if (kasciistricmp(msgPart.typeStr(), "message")==0) {
+ atmViewMsg(&msgPart);
+ } else if ((kasciistricmp(msgPart.typeStr(), "text")==0) &&
+ (kasciistricmp(msgPart.subtypeStr(), "x-vcard")==0)) {
+ setMsgPart( &msgPart, htmlMail(), name, pname );
+ } else {
+ KMReaderMainWin *win = new KMReaderMainWin(&msgPart, htmlMail(),
+ name, pname, overrideEncoding() );
+ win->show();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::openAttachment( int id, const QString & name )
+{
+ mAtmCurrentName = name;
+ mAtmCurrent = id;
+
+ QString str, pname, cmd, fileName;
+
+ partNode* node = mRootNode ? mRootNode->findId( id ) : 0;
+ if( !node ) {
+ kdWarning(5006) << "KMReaderWin::openAttachment - could not find node " << id << endl;
+ return;
+ }
+ if ( mAtmCurrentName.isEmpty() )
+ mAtmCurrentName = tempFileUrlFromPartNode( node ).path();
+
+ KMMessagePart& msgPart = node->msgPart();
+ if (kasciistricmp(msgPart.typeStr(), "message")==0)
+ {
+ atmViewMsg(&msgPart);
+ return;
+ }
+
+ QCString contentTypeStr( msgPart.typeStr() + '/' + msgPart.subtypeStr() );
+ KPIM::kAsciiToLower( contentTypeStr.data() );
+
+ if ( qstrcmp( contentTypeStr, "text/x-vcard" ) == 0 ) {
+ showVCard( &msgPart );
+ return;
+ }
+
+ // determine the MIME type of the attachment
+ KMimeType::Ptr mimetype;
+ // prefer the value of the Content-Type header
+ mimetype = KMimeType::mimeType( QString::fromLatin1( contentTypeStr ) );
+ if ( mimetype->name() == "application/octet-stream" ) {
+ // consider the filename if Content-Type is application/octet-stream
+ mimetype = KMimeType::findByPath( name, 0, true /* no disk access */ );
+ }
+ if ( ( mimetype->name() == "application/octet-stream" )
+ && msgPart.isComplete() ) {
+ // consider the attachment's contents if neither the Content-Type header
+ // nor the filename give us a clue
+ mimetype = KMimeType::findByFileContent( name );
+ }
+
+ KService::Ptr offer =
+ KServiceTypeProfile::preferredService( mimetype->name(), "Application" );
+
+ QString open_text;
+ QString filenameText = msgPart.fileName();
+ if ( filenameText.isEmpty() )
+ filenameText = msgPart.name();
+ if ( offer ) {
+ open_text = i18n("&Open with '%1'").arg( offer->name() );
+ } else {
+ open_text = i18n("&Open With...");
+ }
+ const QString text = i18n("Open attachment '%1'?\n"
+ "Note that opening an attachment may compromise "
+ "your system's security.")
+ .arg( filenameText );
+ const int choice = KMessageBox::questionYesNoCancel( this, text,
+ i18n("Open Attachment?"), KStdGuiItem::saveAs(), open_text,
+ QString::fromLatin1("askSave") + mimetype->name() ); // dontAskAgainName
+
+ if( choice == KMessageBox::Yes ) { // Save
+ mAtmUpdate = true;
+ KMHandleAttachmentCommand* command = new KMHandleAttachmentCommand( node,
+ message(), mAtmCurrent, mAtmCurrentName, KMHandleAttachmentCommand::Save,
+ offer, this );
+ connect( command, SIGNAL( showAttachment( int, const QString& ) ),
+ this, SLOT( slotAtmView( int, const QString& ) ) );
+ command->start();
+ }
+ else if( choice == KMessageBox::No ) { // Open
+ KMHandleAttachmentCommand::AttachmentAction action = ( offer ?
+ KMHandleAttachmentCommand::Open : KMHandleAttachmentCommand::OpenWith );
+ mAtmUpdate = true;
+ KMHandleAttachmentCommand* command = new KMHandleAttachmentCommand( node,
+ message(), mAtmCurrent, mAtmCurrentName, action, offer, this );
+ connect( command, SIGNAL( showAttachment( int, const QString& ) ),
+ this, SLOT( slotAtmView( int, const QString& ) ) );
+ command->start();
+ } else { // Cancel
+ kdDebug(5006) << "Canceled opening attachment" << endl;
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotScrollUp()
+{
+ static_cast<QScrollView *>(mViewer->widget())->scrollBy(0, -10);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotScrollDown()
+{
+ static_cast<QScrollView *>(mViewer->widget())->scrollBy(0, 10);
+}
+
+bool KMReaderWin::atBottom() const
+{
+ const QScrollView *view = static_cast<const QScrollView *>(mViewer->widget());
+ return view->contentsY() + view->visibleHeight() >= view->contentsHeight();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotJumpDown()
+{
+ QScrollView *view = static_cast<QScrollView *>(mViewer->widget());
+ int offs = (view->clipper()->height() < 30) ? view->clipper()->height() : 30;
+ view->scrollBy( 0, view->clipper()->height() - offs );
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotScrollPrior()
+{
+ static_cast<QScrollView *>(mViewer->widget())->scrollBy(0, -(int)(height()*0.8));
+}
+
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotScrollNext()
+{
+ static_cast<QScrollView *>(mViewer->widget())->scrollBy(0, (int)(height()*0.8));
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotDocumentChanged()
+{
+
+}
+
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotTextSelected(bool)
+{
+ QString temp = mViewer->selectedText();
+ kapp->clipboard()->setText(temp);
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::selectAll()
+{
+ mViewer->selectAll();
+}
+
+//-----------------------------------------------------------------------------
+QString KMReaderWin::copyText()
+{
+ QString temp = mViewer->selectedText();
+ return temp;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotDocumentDone()
+{
+ // mSbVert->setValue(0);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::setHtmlOverride(bool override)
+{
+ mHtmlOverride = override;
+ if (message())
+ message()->setDecodeHTML(htmlMail());
+}
+
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::setHtmlLoadExtOverride(bool override)
+{
+ mHtmlLoadExtOverride = override;
+ //if (message())
+ // message()->setDecodeHTML(htmlMail());
+}
+
+
+//-----------------------------------------------------------------------------
+bool KMReaderWin::htmlMail()
+{
+ return ((mHtmlMail && !mHtmlOverride) || (!mHtmlMail && mHtmlOverride));
+}
+
+
+//-----------------------------------------------------------------------------
+bool KMReaderWin::htmlLoadExternal()
+{
+ return ((mHtmlLoadExternal && !mHtmlLoadExtOverride) ||
+ (!mHtmlLoadExternal && mHtmlLoadExtOverride));
+}
+
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::saveRelativePosition()
+{
+ const QScrollView * scrollview = static_cast<QScrollView *>( mViewer->widget() );
+ mSavedRelativePosition =
+ static_cast<float>( scrollview->contentsY() ) / scrollview->contentsHeight();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::update( bool force )
+{
+ KMMessage* msg = message();
+ if ( msg )
+ setMsg( msg, force );
+}
+
+
+//-----------------------------------------------------------------------------
+KMMessage* KMReaderWin::message( KMFolder** aFolder ) const
+{
+ KMFolder* tmpFolder;
+ KMFolder*& folder = aFolder ? *aFolder : tmpFolder;
+ folder = 0;
+ if (mMessage)
+ return mMessage;
+ if (mLastSerNum) {
+ KMMessage *message = 0;
+ int index;
+ KMMsgDict::instance()->getLocation( mLastSerNum, &folder, &index );
+ if (folder )
+ message = folder->getMsg( index );
+ if (!message)
+ kdWarning(5006) << "Attempt to reference invalid serial number " << mLastSerNum << "\n" << endl;
+ return message;
+ }
+ return 0;
+}
+
+
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotUrlClicked()
+{
+ KMMainWidget *mainWidget = dynamic_cast<KMMainWidget*>(mMainWindow);
+ uint identity = 0;
+ if ( message() && message()->parent() ) {
+ identity = message()->parent()->identity();
+ }
+
+ KMCommand *command = new KMUrlClickedCommand( mUrlClicked, identity, this,
+ false, mainWidget );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotMailtoCompose()
+{
+ KMCommand *command = new KMMailtoComposeCommand( mUrlClicked, message() );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotMailtoForward()
+{
+ KMCommand *command = new KMMailtoForwardCommand( mMainWindow, mUrlClicked,
+ message() );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotMailtoAddAddrBook()
+{
+ KMCommand *command = new KMMailtoAddAddrBookCommand( mUrlClicked,
+ mMainWindow);
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotMailtoOpenAddrBook()
+{
+ KMCommand *command = new KMMailtoOpenAddrBookCommand( mUrlClicked,
+ mMainWindow );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+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,
+ dynamic_cast<KMMainWidget*>( mMainWindow ) );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotUrlOpen( const KURL &url )
+{
+ if ( !url.isEmpty() )
+ mUrlClicked = url;
+ KMCommand *command = new KMUrlOpenCommand( mUrlClicked, this );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotAddBookmarks()
+{
+ KMCommand *command = new KMAddBookmarksCommand( mUrlClicked, this );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotUrlSave()
+{
+ KMCommand *command = new KMUrlSaveCommand( mUrlClicked, mMainWindow );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotMailtoReply()
+{
+ KMCommand *command = new KMMailtoReplyCommand( mMainWindow, mUrlClicked,
+ message(), copyText() );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+partNode * KMReaderWin::partNodeFromUrl( const KURL & url ) {
+ return mRootNode ? mRootNode->findId( msgPartFromUrl( url ) ) : 0 ;
+}
+
+partNode * KMReaderWin::partNodeForId( int id ) {
+ return mRootNode ? mRootNode->findId( id ) : 0 ;
+}
+
+
+KURL KMReaderWin::tempFileUrlFromPartNode( const partNode * node )
+{
+ if (!node) return KURL();
+ QStringList::const_iterator it = mTempFiles.begin();
+ QStringList::const_iterator end = mTempFiles.end();
+
+ while ( it != end ) {
+ QString path = *it;
+ it++;
+ uint right = path.findRev('/');
+ uint left = path.findRev('.', right);
+
+ bool ok;
+ int res = path.mid(left + 1, right - left - 1).toInt(&ok);
+ if ( res == node->nodeId() )
+ return KURL( path );
+ }
+ return KURL();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotSaveAttachments()
+{
+ mAtmUpdate = true;
+ KMSaveAttachmentsCommand *saveCommand = new KMSaveAttachmentsCommand( mMainWindow,
+ message() );
+ saveCommand->start();
+}
+
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotSaveMsg()
+{
+ KMSaveMsgCommand *saveCommand = new KMSaveMsgCommand( mMainWindow, message() );
+
+ if (saveCommand->url().isEmpty())
+ delete saveCommand;
+ else
+ saveCommand->start();
+}
+//-----------------------------------------------------------------------------
+void KMReaderWin::slotIMChat()
+{
+ KMCommand *command = new KMIMChatCommand( mUrlClicked, message() );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+bool KMReaderWin::eventFilter( QObject *, QEvent *e )
+{
+ if ( e->type() == QEvent::MouseButtonPress ) {
+ QMouseEvent* me = static_cast<QMouseEvent*>(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
+ }
+ }
+ // standard event processing
+ return false;
+}
+
+void KMReaderWin::slotDeleteAttachment(partNode * node)
+{
+ if ( KMessageBox::warningContinueCancel( this,
+ i18n("Deleting an attachment might invalidate any digital signature on this message."),
+ i18n("Delete Attachment"), KStdGuiItem::del(), "DeleteAttachmentSignatureWarning" )
+ != KMessageBox::Continue ) {
+ return;
+ }
+ KMDeleteAttachmentCommand* command = new KMDeleteAttachmentCommand( node, message(), this );
+ command->start();
+}
+
+void KMReaderWin::slotEditAttachment(partNode * node)
+{
+ if ( KMessageBox::warningContinueCancel( this,
+ i18n("Modifying an attachment might invalidate any digital signature on this message."),
+ i18n("Edit Attachment"), KGuiItem( i18n("Edit"), "edit" ), "EditAttachmentSignatureWarning" )
+ != KMessageBox::Continue ) {
+ return;
+ }
+ KMEditAttachmentCommand* command = new KMEditAttachmentCommand( node, message(), this );
+ command->start();
+}
+
+KMail::CSSHelper* KMReaderWin::cssHelper()
+{
+ return mCSSHelper;
+}
+
+bool KMReaderWin::decryptMessage() const
+{
+ if ( !GlobalSettings::self()->alwaysDecrypt() )
+ return mDecrytMessageOverwrite;
+ return true;
+}
+
+void KMReaderWin::injectAttachments()
+{
+ // inject attachments in header view
+ // we have to do that after the otp has run so we also see encrypted parts
+ DOM::Document doc = mViewer->htmlDocument();
+ DOM::Element injectionPoint = doc.getElementById( "attachmentInjectionPoint" );
+ if ( injectionPoint.isNull() )
+ return;
+
+ QString imgpath( locate("data","kmail/pics/") );
+ QString visibility;
+ QString urlHandle;
+ QString imgSrc;
+ if( !showAttachmentQuicklist() )
+ {
+ urlHandle.append( "kmail:showAttachmentQuicklist" );
+ imgSrc.append( "attachmentQuicklistClosed.png" );
+ } else {
+ urlHandle.append( "kmail:hideAttachmentQuicklist" );
+ imgSrc.append( "attachmentQuicklistOpened.png" );
+ }
+
+ QString html = renderAttachments( mRootNode, QApplication::palette().active().background() );
+ if ( html.isEmpty() )
+ return;
+
+ if ( headerStyle() == HeaderStyle::fancy() )
+ html.prepend( QString::fromLatin1("<div style=\"float:left;\">%1&nbsp;</div>" ).arg(i18n("Attachments:")) );
+
+ if ( headerStyle() == HeaderStyle::enterprise() ) {
+ QString link("");
+ link += "<div style=\"text-align: right;\"><a href=\""+urlHandle+"\"><img src=\""+imgpath+imgSrc+"\"/></a></div>";
+ html.prepend( link );
+ }
+
+ assert( injectionPoint.tagName() == "div" );
+ static_cast<DOM::HTMLElement>( injectionPoint ).setInnerHTML( html );
+}
+
+static QColor nextColor( const QColor & c )
+{
+ int h, s, v;
+ c.hsv( &h, &s, &v );
+ return QColor( (h + 50) % 360, QMAX(s, 64), v, QColor::Hsv );
+}
+
+QString KMReaderWin::renderAttachments(partNode * node, const QColor &bgColor )
+{
+ if ( !node )
+ return QString();
+
+ QString html;
+ if ( node->firstChild() ) {
+ QString subHtml = renderAttachments( node->firstChild(), nextColor( bgColor ) );
+ if ( !subHtml.isEmpty() ) {
+
+ QString visibility;
+ if( !showAttachmentQuicklist() )
+ {
+ visibility.append( "display:none;" );
+ }
+
+ QString margin;
+ if ( node != mRootNode || headerStyle() != HeaderStyle::enterprise() )
+ margin = "padding:2px; margin:2px; ";
+ if ( node->msgPart().typeStr() == "message" || node == mRootNode )
+ html += QString::fromLatin1("<div style=\"background:%1; %2"
+ "vertical-align:middle; float:left; %3\">").arg( bgColor.name() ).arg( margin ).arg( visibility );
+ html += subHtml;
+ if ( node->msgPart().typeStr() == "message" || node == mRootNode )
+ html += "</div>";
+ }
+ } else {
+ QString 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 ) {
+ html += "<div style=\"float:left;\">";
+ html += QString::fromLatin1( "<span style=\"white-space:nowrap; border-width: 0px; border-left-width: 5px; border-color: %1; 2px; border-left-style: solid;\">" ).arg( bgColor.name() );
+ html += QString::fromLatin1( "<a href=\"#att%1\">" ).arg( node->nodeId() );
+ html += "<img style=\"vertical-align:middle;\" src=\"" + icon + "\"/>&nbsp;";
+ if ( headerStyle() == HeaderStyle::enterprise() ) {
+ QFont bodyFont = mCSSHelper->bodyFont( isFixedFont() );
+ QFontMetrics fm( bodyFont );
+ html += KStringHandler::rPixelSqueeze( label, fm, 140 );
+ } else
+ html += label;
+ html += "</a></span></div> ";
+ }
+ }
+
+ html += renderAttachments( node->nextSibling(), nextColor ( bgColor ) );
+ return html;
+}
+
+#include "kmreaderwin.moc"
+
+
diff --git a/kmail/kmreaderwin.h b/kmail/kmreaderwin.h
new file mode 100644
index 00000000..2942d77c
--- /dev/null
+++ b/kmail/kmreaderwin.h
@@ -0,0 +1,546 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+// Header for kmreaderwin the kmail reader
+// written by Markus Wuebben <markus.wuebben@kde.org>
+
+#ifndef KMREADERWIN_H
+#define KMREADERWIN_H
+
+#include <qwidget.h>
+#include <qtimer.h>
+#include <qstringlist.h>
+#include <kurl.h>
+#include <kservice.h>
+#include "kmmsgbase.h"
+#include "kmmimeparttree.h" // Needed for friend declaration.
+#include "interfaces/observer.h"
+
+class QFrame;
+class QSplitter;
+class QHBox;
+class QListViewItem;
+class QScrollBar;
+class QString;
+class QTabDialog;
+class QTextCodec;
+
+class DwHeaders;
+class DwMediaType;
+
+class KActionCollection;
+class KAction;
+class KActionMenu;
+class KSelectAction;
+class KRadioAction;
+class KToggleAction;
+class KConfigBase;
+class KHTMLPart;
+class KURL;
+
+class KMFolder;
+class KMMessage;
+class KMMessagePart;
+namespace KMail {
+ namespace Interface {
+ class Observable;
+ }
+ class PartMetaData;
+ class ObjectTreeParser;
+ class AttachmentStrategy;
+ class HeaderStrategy;
+ class HeaderStyle;
+ class HtmlWriter;
+ class KHtmlPartHtmlWriter;
+ class ISubject;
+ class HtmlStatusBar;
+ class FolderJob;
+ class CSSHelper;
+}
+
+class partNode; // might be removed when KMime is used instead of mimelib
+ // (khz, 29.11.2001)
+
+class NewByteArray; // providing operator+ on a QByteArray (khz, 21.06.2002)
+
+namespace KParts {
+ struct URLArgs;
+}
+
+/**
+ This class implements a "reader window", that is a window
+ used for reading or viewing messages.
+*/
+
+class KMReaderWin: public QWidget, public KMail::Interface::Observer {
+ Q_OBJECT
+
+ friend void KMMimePartTree::itemClicked( QListViewItem* item );
+ friend void KMMimePartTree::itemRightClicked( QListViewItem* item, const QPoint & );
+ friend void KMMimePartTree::slotSaveAs();
+ friend void KMMimePartTree::startDrag();
+
+ friend class KMail::ObjectTreeParser;
+ friend class KMail::KHtmlPartHtmlWriter;
+
+public:
+ KMReaderWin( QWidget *parent,
+ QWidget *mainWindow,
+ KActionCollection *actionCollection,
+ const char *name=0,
+ int f=0 );
+ virtual ~KMReaderWin();
+
+ /**
+ \reimp from Interface::Observer
+ Updates the current message
+ */
+ void update( KMail::Interface::Observable * );
+
+ /** Read settings from app's config file. */
+ void readConfig();
+
+ /** Write settings to app's config file. Calls sync() if withSync is TRUE. */
+ void writeConfig( bool withSync=true ) const;
+
+ const KMail::HeaderStyle * headerStyle() const {
+ return mHeaderStyle;
+ }
+ /** Set the header style and strategy. We only want them to be set
+ together. */
+ void setHeaderStyleAndStrategy( const KMail::HeaderStyle * style,
+ const KMail::HeaderStrategy * strategy );
+
+ /** Getthe message header strategy. */
+ const KMail::HeaderStrategy * headerStrategy() const {
+ return mHeaderStrategy;
+ }
+
+ /** Get/set the message attachment strategy. */
+ const KMail::AttachmentStrategy * attachmentStrategy() const {
+ return mAttachmentStrategy;
+ }
+ void setAttachmentStrategy( const KMail::AttachmentStrategy * strategy );
+
+ /** Get selected override character encoding.
+ @return The encoding selected by the user or an empty string if auto-detection
+ is selected. */
+ QString overrideEncoding() const { return mOverrideEncoding; }
+
+ /** Set the override character encoding. */
+ void setOverrideEncoding( const QString & encoding );
+
+ void setPrintFont( const QFont& font );
+
+ /** Get codec corresponding to the currently selected override character encoding.
+ @return The override codec or 0 if auto-detection is selected. */
+ const QTextCodec * overrideCodec() const;
+
+ /** Set printing mode */
+ virtual void setPrinting(bool enable) { mPrinting = enable; }
+
+ /** Set the message that shall be shown. If msg is 0, an empty page is
+ displayed. */
+ virtual void setMsg(KMMessage* msg, bool force = false);
+
+ /** Instead of settings a message to be shown sets a message part
+ to be shown */
+ void setMsgPart( KMMessagePart* aMsgPart, bool aHTML,
+ const QString& aFileName, const QString& pname );
+
+ void setMsgPart( partNode * node );
+
+ /** Show or hide the Mime Tree Viewer if configuration
+ is set to smart mode. */
+ void showHideMimeTree( bool isPlainTextTopLevel );
+
+ /** Store message id of last viewed message,
+ normally no need to call this function directly,
+ since correct value is set automatically in
+ parseMsg(KMMessage* aMsg, bool onlyProcessHeaders). */
+ void setIdOfLastViewedMessage( const QString & msgId )
+ { mIdOfLastViewedMessage = msgId; }
+
+ /** Clear the reader and discard the current message. */
+ void clear(bool force = false) { setMsg(0, force); }
+
+ /** Saves the relative position of the scroll view. Call this before calling update()
+ if you want to preserve the current view. */
+ void saveRelativePosition();
+
+ /** Re-parse the current message. */
+ void update(bool force = false);
+
+ /** Print current message. */
+ virtual void printMsg(void);
+
+ /** Return selected text */
+ QString copyText();
+
+ /** Get/set auto-delete msg flag. */
+ bool autoDelete(void) const { return mAutoDelete; }
+ void setAutoDelete(bool f) { mAutoDelete=f; }
+
+ /** Override default html mail setting */
+ bool htmlOverride() const { return mHtmlOverride; }
+ void setHtmlOverride( bool override );
+
+ /** Override default load external references setting */
+ bool htmlLoadExtOverride() const { return mHtmlLoadExtOverride; }
+ void setHtmlLoadExtOverride( bool override );
+
+ /** Is html mail to be supported? Takes into account override */
+ bool htmlMail();
+
+ /** Is loading ext. references to be supported? Takes into account override */
+ bool htmlLoadExternal();
+
+ /** Returns the MD5 hash for the list of new features */
+ static QString newFeaturesMD5();
+
+ /** Display a generic HTML splash page instead of a message */
+ void displaySplashPage( const QString &info );
+
+ /** Display the about page instead of a message */
+ void displayAboutPage();
+
+ /** Display the 'please wait' page instead of a message */
+ void displayBusyPage();
+ /** Display the 'we are currently in offline mode' page instead of a message */
+ void displayOfflinePage();
+
+ /** 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);
+
+ bool atBottom() const;
+
+ bool isFixedFont() { return mUseFixedFont; }
+ void setUseFixedFont( bool useFixedFont ) { mUseFixedFont = useFixedFont; }
+
+ /** Return the HtmlWriter connected to the KHTMLPart we use */
+ KMail::HtmlWriter * htmlWriter() { return mHtmlWriter; }
+
+ // Action to reply to a message
+ // but action( "some_name" ) some name could be used instead.
+ KToggleAction *toggleFixFontAction() { return mToggleFixFontAction; }
+ KAction *mailToComposeAction() { return mMailToComposeAction; }
+ KAction *mailToReplyAction() { return mMailToReplyAction; }
+ KAction *mailToForwardAction() { return mMailToForwardAction; }
+ KAction *addAddrBookAction() { return mAddAddrBookAction; }
+ KAction *openAddrBookAction() { return mOpenAddrBookAction; }
+ KAction *copyAction() { return mCopyAction; }
+ KAction *selectAllAction() { return mSelectAllAction; }
+ KAction *copyURLAction() { return mCopyURLAction; }
+ KAction *urlOpenAction() { return mUrlOpenAction; }
+ KAction *urlSaveAsAction() { return mUrlSaveAsAction; }
+ KAction *addBookmarksAction() { return mAddBookmarksAction;}
+ KAction *startImChatAction() { return mStartIMChatAction; }
+ // This function returns the complete data that were in this
+ // message parts - *after* all encryption has been removed that
+ // could be removed.
+ // - This is used to store the message in decrypted form.
+ void objectTreeToDecryptedMsg( partNode* node,
+ NewByteArray& resultingData,
+ KMMessage& theMessage,
+ bool weAreReplacingTheRootNode = false,
+ int recCount = 0 );
+
+ /** Returns message part from given URL or null if invalid. */
+ partNode* partNodeFromUrl(const KURL &url);
+
+ partNode * partNodeForId( int id );
+
+ KURL tempFileUrlFromPartNode( const partNode * node );
+
+ /** Returns id of message part from given URL or -1 if invalid. */
+ static int msgPartFromUrl(const KURL &url);
+
+ void setUpdateAttachment( bool update = true ) { mAtmUpdate = update; }
+
+ /** Access to the KHTMLPart used for the viewer. Use with
+ care! */
+ KHTMLPart * htmlPart() const { return mViewer; }
+
+ /** Returns the current message or 0 if none. */
+ KMMessage* message(KMFolder** folder=0) const;
+
+ void openAttachment( int id, const QString & name );
+
+ void emitUrlClicked( const KURL & url, int button ) {
+ emit urlClicked( url, button );
+ }
+
+ void emitPopupMenu( const KURL & url, const QPoint & p ) {
+ if ( message() )
+ emit popupMenu( *message(), url, p );
+ }
+
+ void showAttachmentPopup( int id, const QString & name, const QPoint & p );
+
+ /** Set the serial number of the message this reader window is currently
+ * waiting for. Used to discard updates for already deselected messages. */
+ void setWaitingForSerNum( unsigned long serNum ) { mWaitingForSerNum = serNum; }
+
+ QWidget* mainWindow() { return mMainWindow; }
+
+ /** Returns wether the message should be decryted. */
+ bool decryptMessage() const;
+
+ /** Enforce message decryption. */
+ void setDecryptMessageOverwrite( bool overwrite = true ) { mDecrytMessageOverwrite = overwrite; }
+
+ /** Show signature details. */
+ bool showSignatureDetails() const { return mShowSignatureDetails; }
+
+ /** Show signature details. */
+ void setShowSignatureDetails( bool showDetails = true ) { mShowSignatureDetails = showDetails; }
+
+ /* show or hide the list that points to the attachments */
+ bool showAttachmentQuicklist() const { return mShowAttachmentQuicklist; }
+
+ /* show or hide the list that points to the attachments */
+ void setShowAttachmentQuicklist( bool showAttachmentQuicklist = true ) { mShowAttachmentQuicklist = showAttachmentQuicklist; }
+
+signals:
+ /** Emitted after parsing of a message to have it stored
+ in unencrypted state in it's folder. */
+ void replaceMsgByUnencryptedVersion();
+
+ /** The user presses the right mouse button. 'url' may be 0. */
+ void popupMenu(KMMessage &msg, const KURL &url, const QPoint& mousePos);
+
+ /** The user has clicked onto an URL that is no attachment. */
+ void urlClicked(const KURL &url, int button);
+
+ /** Pgp displays a password dialog */
+ void noDrag(void);
+
+public slots:
+
+ /** Select message body. */
+ void selectAll();
+
+ /** Force update even if message is the same */
+ void clearCache();
+
+ /** Refresh the reader window */
+ void updateReaderWin();
+
+ /** HTML Widget scrollbar and layout handling. */
+ void slotScrollUp();
+ void slotScrollDown();
+ void slotScrollPrior();
+ void slotScrollNext();
+ void slotJumpDown();
+ void slotDocumentChanged();
+ void slotDocumentDone();
+ void slotTextSelected(bool);
+
+ /** An URL has been activate with a click. */
+ void slotUrlOpen(const KURL &url, const KParts::URLArgs &args);
+
+ /** The mouse has moved on or off an URL. */
+ void slotUrlOn(const QString &url);
+
+ /** The user presses the right mouse button on an URL. */
+ void slotUrlPopup(const QString &, const QPoint& mousePos);
+
+ /** The user selected "Find" from the menu. */
+ void slotFind();
+ /** The user selected "Find Next" from the menu. */
+ void slotFindNext();
+
+ /** The user toggled the "Fixed Font" flag from the view menu. */
+ void slotToggleFixedFont();
+
+ /** Copy the selected text to the clipboard */
+ void slotCopySelectedText();
+
+ void slotUrlClicked();
+
+ /** Operations on mailto: URLs. */
+ void slotMailtoReply();
+ void slotMailtoCompose();
+ void slotMailtoForward();
+ void slotMailtoAddAddrBook();
+ void slotMailtoOpenAddrBook();
+ /** Copy URL in mUrlCurrent to clipboard. Removes "mailto:" at
+ beginning of URL before copying. */
+ void slotUrlCopy();
+ void slotUrlOpen( const KURL &url = KURL() );
+ /** Save the page to a file */
+ void slotUrlSave();
+ void slotAddBookmarks();
+ void slotSaveMsg();
+ void slotSaveAttachments();
+
+ void slotMessageArrived( KMMessage *msg );
+ /** start IM Chat with addressee */
+ void slotIMChat();
+ void contactStatusChanged( const QString &uid);
+
+ void slotLevelQuote( int l );
+ void slotTouchMessage();
+
+ void slotDeleteAttachment( partNode* node );
+ void slotEditAttachment( partNode* node );
+
+ KMail::CSSHelper* cssHelper();
+
+protected slots:
+ void slotCycleHeaderStyles();
+ void slotBriefHeaders();
+ void slotFancyHeaders();
+ void slotEnterpriseHeaders();
+ void slotStandardHeaders();
+ void slotLongHeaders();
+ void slotAllHeaders();
+
+ void slotCycleAttachmentStrategy();
+ void slotIconicAttachments();
+ void slotSmartAttachments();
+ void slotInlineAttachments();
+ void slotHideAttachments();
+
+ /** Some attachment operations. */
+ void slotAtmView( int id, const QString& name );
+ void slotDelayedResize();
+ void slotHandleAttachment( int );
+
+protected:
+ /** reimplemented in order to update the frame width in case of a changed
+ GUI style */
+ void styleChange( QStyle& oldStyle );
+
+ /** Set the width of the frame to a reasonable value for the current GUI
+ style */
+ void setStyleDependantFrameWidth();
+
+ /** Watch for palette changes */
+ virtual bool event(QEvent *e);
+
+ /** Calculate the pixel size */
+ int pointsToPixel(int pointSize) const;
+
+ /** Feeds the HTML viewer with the contents of the given message.
+ HTML begin/end parts are written around the message. */
+ void displayMessage();
+
+ /** Parse given message and add it's contents to the reader window. */
+ virtual void parseMsg( KMMessage* msg );
+
+ /** Creates a nice mail header depending on the current selected
+ header style. */
+ QString writeMsgHeader(KMMessage* aMsg, bool hasVCard=false, bool topLevel=false);
+
+ /** Writes the given message part to a temporary file and returns the
+ name of this file or QString::null if writing failed.
+ */
+ QString writeMessagePartToTempFile( KMMessagePart* msgPart, int partNumber );
+
+ /**
+ Creates a temporary dir for saving attachments, etc.
+ Will be automatically deleted when another message is viewed.
+ @param param Optional part of the directory name.
+ */
+ QString createTempDir( const QString &param = QString() );
+
+ /** show window containing infos about a vCard. */
+ void showVCard(KMMessagePart *msgPart);
+
+ /** HTML initialization. */
+ virtual void initHtmlWidget(void);
+
+ /** Some necessary event handling. */
+ virtual void closeEvent(QCloseEvent *);
+ virtual void resizeEvent(QResizeEvent *);
+
+ /** Cleanup the attachment temp files */
+ virtual void removeTempFiles();
+
+ /** Event filter */
+ bool eventFilter( QObject *obj, QEvent *ev );
+
+private slots:
+ void slotSetEncoding();
+ void injectAttachments();
+
+private:
+ void adjustLayout();
+ void createWidgets();
+ void createActions( KActionCollection * ac );
+ void saveSplitterSizes( KConfigBase & c ) const;
+
+ KRadioAction * actionForHeaderStyle( const KMail::HeaderStyle *,
+ const KMail::HeaderStrategy * );
+ KRadioAction * actionForAttachmentStrategy( const KMail::AttachmentStrategy * );
+ /** Read override codec from configuration */
+ void readGlobalOverrideCodec();
+
+ QString renderAttachments( partNode *node, const QColor &bgColor );
+
+private:
+ bool mHtmlMail, mHtmlLoadExternal, mHtmlOverride, mHtmlLoadExtOverride;
+ int mAtmCurrent;
+ QString mAtmCurrentName;
+ KMMessage *mMessage;
+ // widgets:
+ QSplitter * mSplitter;
+ QHBox *mBox;
+ KMail::HtmlStatusBar *mColorBar;
+ KMMimePartTree* mMimePartTree;
+ KHTMLPart *mViewer;
+
+ const KMail::AttachmentStrategy * mAttachmentStrategy;
+ const KMail::HeaderStrategy * mHeaderStrategy;
+ const KMail::HeaderStyle * mHeaderStyle;
+ bool mAutoDelete;
+ /** where did the user save the attachment last time */
+ QString mSaveAttachDir;
+ static const int delay;
+ QTimer mUpdateReaderWinTimer;
+ QTimer mResizeTimer;
+ QTimer mDelayedMarkTimer;
+ QString mOverrideEncoding;
+ QString mOldGlobalOverrideEncoding; // used to detect changes of the global override character encoding
+ bool mMsgDisplay;
+ bool mNoMDNsWhenEncrypted;
+ unsigned long mLastSerNum;
+ KMMsgStatus mLastStatus;
+
+ KMail::CSSHelper * mCSSHelper;
+ bool mUseFixedFont;
+ bool mPrinting;
+ bool mShowColorbar;
+ //bool mShowCompleteMessage;
+ QStringList mTempFiles;
+ QStringList mTempDirs;
+ int mMimeTreeMode;
+ bool mMimeTreeAtBottom;
+ QValueList<int> mSplitterSizes;
+ partNode* mRootNode;
+ QString mIdOfLastViewedMessage;
+ QWidget *mMainWindow;
+ KActionCollection *mActionCollection;
+ KAction *mMailToComposeAction, *mMailToReplyAction, *mMailToForwardAction,
+ *mAddAddrBookAction, *mOpenAddrBookAction, *mCopyAction, *mCopyURLAction,
+ *mUrlOpenAction, *mUrlSaveAsAction, *mAddBookmarksAction, *mStartIMChatAction, *mSelectAllAction;
+ KSelectAction *mSelectEncodingAction;
+ KToggleAction *mToggleFixFontAction;
+ KURL mUrlClicked;
+ KMail::HtmlWriter * mHtmlWriter;
+ // an attachment should be updated
+ bool mAtmUpdate;
+ int mChoice;
+ unsigned long mWaitingForSerNum;
+ float mSavedRelativePosition;
+ int mLevelQuote;
+ bool mDecrytMessageOverwrite;
+ bool mShowSignatureDetails;
+ bool mShowAttachmentQuicklist;
+};
+
+
+#endif
+
diff --git a/kmail/kmsearchpattern.cpp b/kmail/kmsearchpattern.cpp
new file mode 100644
index 00000000..1b9e3653
--- /dev/null
+++ b/kmail/kmsearchpattern.cpp
@@ -0,0 +1,929 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+// kmsearchpattern.cpp
+// Author: Marc Mutz <Marc@Mutz.com>
+// This code is under GPL!
+
+#include <config.h>
+
+#include "kmaddrbook.h"
+#include "kmsearchpattern.h"
+#include "kmmsgdict.h"
+#include "filterlog.h"
+#include "kmkernel.h"
+#include "kmmsgdict.h"
+#include "kmfolder.h"
+using KMail::FilterLog;
+
+#include <libemailfunctions/email.h>
+
+#include <kglobal.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kconfig.h>
+
+#include <kabc/stdaddressbook.h>
+
+#include <qregexp.h>
+
+#include <mimelib/string.h>
+#include <mimelib/boyermor.h>
+
+#include <assert.h>
+
+static const char* funcConfigNames[] =
+ { "contains", "contains-not", "equals", "not-equal", "regexp",
+ "not-regexp", "greater", "less-or-equal", "less", "greater-or-equal",
+ "is-in-addressbook", "is-not-in-addressbook" , "is-in-category", "is-not-in-category",
+ "has-attachment", "has-no-attachment"};
+static const int numFuncConfigNames = sizeof funcConfigNames / sizeof *funcConfigNames;
+
+struct _statusNames {
+ const char* name;
+ KMMsgStatus status;
+};
+
+static struct _statusNames statusNames[] = {
+ { "Important", KMMsgStatusFlag },
+ { "New", KMMsgStatusNew },
+ { "Unread", KMMsgStatusUnread | KMMsgStatusNew },
+ { "Read", KMMsgStatusRead },
+ { "Old", KMMsgStatusOld },
+ { "Deleted", KMMsgStatusDeleted },
+ { "Replied", KMMsgStatusReplied },
+ { "Forwarded", KMMsgStatusForwarded },
+ { "Queued", KMMsgStatusQueued },
+ { "Sent", KMMsgStatusSent },
+ { "Watched", KMMsgStatusWatched },
+ { "Ignored", KMMsgStatusIgnored },
+ { "To Do", KMMsgStatusTodo },
+ { "Spam", KMMsgStatusSpam },
+ { "Ham", KMMsgStatusHam },
+ { "Has Attachment", KMMsgStatusHasAttach }
+};
+
+static const int numStatusNames = sizeof statusNames / sizeof ( struct _statusNames );
+
+
+//==================================================
+//
+// class KMSearchRule (was: KMFilterRule)
+//
+//==================================================
+
+KMSearchRule::KMSearchRule( const QCString & field, Function func, const QString & contents )
+ : mField( field ),
+ mFunction( func ),
+ mContents( contents )
+{
+}
+
+KMSearchRule::KMSearchRule( const KMSearchRule & other )
+ : mField( other.mField ),
+ mFunction( other.mFunction ),
+ mContents( other.mContents )
+{
+}
+
+const KMSearchRule & KMSearchRule::operator=( const KMSearchRule & other ) {
+ if ( this == &other )
+ return *this;
+
+ mField = other.mField;
+ mFunction = other.mFunction;
+ mContents = other.mContents;
+
+ return *this;
+}
+
+KMSearchRule * KMSearchRule::createInstance( const QCString & field,
+ Function func,
+ const QString & contents )
+{
+ KMSearchRule *ret = 0;
+ if (field == "<status>")
+ ret = new KMSearchRuleStatus( field, func, contents );
+ else if ( field == "<age in days>" || field == "<size>" )
+ ret = new KMSearchRuleNumerical( field, func, contents );
+ else
+ ret = new KMSearchRuleString( field, func, contents );
+
+ return ret;
+}
+
+KMSearchRule * KMSearchRule::createInstance( const QCString & field,
+ const char *func,
+ const QString & contents )
+{
+ return ( createInstance( field, configValueToFunc( func ), contents ) );
+}
+
+KMSearchRule * KMSearchRule::createInstance( const KMSearchRule & other )
+{
+ return ( createInstance( other.field(), other.function(), other.contents() ) );
+}
+
+KMSearchRule * KMSearchRule::createInstanceFromConfig( const KConfig * config, int aIdx )
+{
+ const char cIdx = char( int('A') + aIdx );
+
+ static const QString & field = KGlobal::staticQString( "field" );
+ static const QString & func = KGlobal::staticQString( "func" );
+ static const QString & contents = KGlobal::staticQString( "contents" );
+
+ const QCString &field2 = config->readEntry( field + cIdx ).latin1();
+ Function func2 = configValueToFunc( config->readEntry( func + cIdx ).latin1() );
+ const QString & contents2 = config->readEntry( contents + cIdx );
+
+ if ( field2 == "<To or Cc>" ) // backwards compat
+ return KMSearchRule::createInstance( "<recipients>", func2, contents2 );
+ else
+ return KMSearchRule::createInstance( field2, func2, contents2 );
+}
+
+KMSearchRule::Function KMSearchRule::configValueToFunc( const char * str ) {
+ if ( !str )
+ return FuncNone;
+
+ for ( int i = 0 ; i < numFuncConfigNames ; ++i )
+ if ( qstricmp( funcConfigNames[i], str ) == 0 ) return (Function)i;
+
+ return FuncNone;
+}
+
+QString KMSearchRule::functionToString( Function function )
+{
+ if ( function != FuncNone )
+ return funcConfigNames[int( function )];
+ else
+ return "invalid";
+}
+
+void KMSearchRule::writeConfig( KConfig * config, int aIdx ) const {
+ const char cIdx = char('A' + aIdx);
+ static const QString & field = KGlobal::staticQString( "field" );
+ static const QString & func = KGlobal::staticQString( "func" );
+ static const QString & contents = KGlobal::staticQString( "contents" );
+
+ config->writeEntry( field + cIdx, QString(mField) );
+ config->writeEntry( func + cIdx, functionToString( mFunction ) );
+ config->writeEntry( contents + cIdx, mContents );
+}
+
+bool KMSearchRule::matches( const DwString & aStr, KMMessage & msg,
+ const DwBoyerMoore *, int ) const
+{
+ if ( !msg.isComplete() ) {
+ msg.fromDwString( aStr );
+ msg.setComplete( true );
+ }
+ return matches( &msg );
+}
+
+const QString KMSearchRule::asString() const
+{
+ QString result = "\"" + mField + "\" <";
+ result += functionToString( mFunction );
+ result += "> \"" + mContents + "\"";
+
+ return result;
+}
+
+//==================================================
+//
+// class KMSearchRuleString
+//
+//==================================================
+
+KMSearchRuleString::KMSearchRuleString( const QCString & field,
+ Function func, const QString & contents )
+ : KMSearchRule(field, func, contents)
+{
+ if ( field.isEmpty() || field[0] == '<' )
+ mBmHeaderField = 0;
+ else // make sure you handle the unrealistic case of the message starting with mField
+ mBmHeaderField = new DwBoyerMoore(("\n" + field + ": ").data());
+}
+
+KMSearchRuleString::KMSearchRuleString( const KMSearchRuleString & other )
+ : KMSearchRule( other ),
+ mBmHeaderField( 0 )
+{
+ if ( other.mBmHeaderField )
+ mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField );
+}
+
+const KMSearchRuleString & KMSearchRuleString::operator=( const KMSearchRuleString & other )
+{
+ if ( this == &other )
+ return *this;
+
+ setField( other.field() );
+ mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField );
+ setFunction( other.function() );
+ setContents( other.contents() );
+ delete mBmHeaderField; mBmHeaderField = 0;
+ if ( other.mBmHeaderField )
+ mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField );
+
+ return *this;
+}
+
+KMSearchRuleString::~KMSearchRuleString()
+{
+ delete mBmHeaderField;
+ mBmHeaderField = 0;
+}
+
+bool KMSearchRuleString::isEmpty() const
+{
+ return field().stripWhiteSpace().isEmpty() || contents().isEmpty();
+}
+
+bool KMSearchRuleString::requiresBody() const
+{
+ if (mBmHeaderField || (field() == "<recipients>" ))
+ return false;
+ return true;
+}
+
+bool KMSearchRuleString::matches( const DwString & aStr, KMMessage & msg,
+ const DwBoyerMoore * aHeaderField, int aHeaderLen ) const
+{
+ if ( isEmpty() )
+ return false;
+
+ bool rc = false;
+
+ const DwBoyerMoore * headerField = aHeaderField ? aHeaderField : mBmHeaderField ;
+
+ const int headerLen = ( aHeaderLen > -1 ? aHeaderLen : field().length() ) + 2 ; // +1 for ': '
+
+ if ( headerField ) {
+ static const DwBoyerMoore lflf( "\n\n" );
+ static const DwBoyerMoore lfcrlf( "\n\r\n" );
+
+ size_t endOfHeader = lflf.FindIn( aStr, 0 );
+ if ( endOfHeader == DwString::npos )
+ endOfHeader = lfcrlf.FindIn( aStr, 0 );
+ const DwString headers = ( endOfHeader == DwString::npos ) ? aStr : aStr.substr( 0, endOfHeader );
+ // In case the searched header is at the beginning, we have to prepend
+ // a newline - see the comment in KMSearchRuleString constructor
+ DwString fakedHeaders( "\n" );
+ size_t start = headerField->FindIn( fakedHeaders.append( headers ), 0, false );
+ // if the header field doesn't exist then return false for positive
+ // functions and true for negated functions (e.g. "does not
+ // contain"); note that all negated string functions correspond
+ // to an odd value
+ if ( start == DwString::npos )
+ rc = ( ( function() & 1 ) == 1 );
+ else {
+ start += headerLen;
+ size_t stop = aStr.find( '\n', start );
+ char ch = '\0';
+ 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 QCString codedValue( aStr.data() + start, len + 1 );
+ const QString msgContents = KMMsgBase::decodeRFC2047String( codedValue ).stripWhiteSpace(); // FIXME: This needs to be changed for IDN support.
+ rc = matchesInternal( msgContents );
+ }
+ } else if ( field() == "<recipients>" ) {
+ static const DwBoyerMoore to("\nTo: ");
+ static const DwBoyerMoore cc("\nCc: ");
+ static const DwBoyerMoore bcc("\nBcc: ");
+ // <recipients> "contains" "foo" is true if any of the fields contains
+ // "foo", while <recipients> "does not contain" "foo" is true if none
+ // of the fields contains "foo"
+ if ( ( function() & 1 ) == 0 ) {
+ // positive function, e.g. "contains"
+ rc = ( matches( aStr, msg, &to, 2 ) ||
+ matches( aStr, msg, &cc, 2 ) ||
+ matches( aStr, msg, &bcc, 3 ) );
+ }
+ else {
+ // negated function, e.g. "does not contain"
+ rc = ( matches( aStr, msg, &to, 2 ) &&
+ matches( aStr, msg, &cc, 2 ) &&
+ matches( aStr, msg, &bcc, 3 ) );
+ }
+ }
+ if ( FilterLog::instance()->isLogging() ) {
+ QString msg = ( rc ? "<font color=#00FF00>1 = </font>"
+ : "<font color=#FF0000>0 = </font>" );
+ msg += FilterLog::recode( asString() );
+ // only log headers bcause messages and bodies can be pretty large
+// FIXME We have to separate the text which is used for filtering to be able to show it in the log
+// if ( logContents )
+// msg += " (<i>" + FilterLog::recode( msgContents ) + "</i>)";
+ FilterLog::instance()->add( msg, FilterLog::ruleResult );
+ }
+ return rc;
+}
+
+bool KMSearchRuleString::matches( const KMMessage * msg ) const
+{
+ assert( msg );
+
+ if ( isEmpty() )
+ return false;
+
+ QString msgContents;
+ // Show the value used to compare the rules against in the log.
+ // Overwrite the value for complete messages and all headers!
+ bool logContents = true;
+
+ if( field() == "<message>" ) {
+ msgContents = msg->asString();
+ logContents = false;
+ } else if ( field() == "<body>" ) {
+ msgContents = msg->bodyToUnicode();
+ logContents = false;
+ } else if ( field() == "<any header>" ) {
+ msgContents = msg->headerAsString();
+ logContents = false;
+ } else if ( field() == "<recipients>" ) {
+ // (mmutz 2001-11-05) hack to fix "<recipients> !contains foo" to
+ // meet user's expectations. See FAQ entry in KDE 2.2.2's KMail
+ // handbook
+ if ( function() == FuncEquals || function() == FuncNotEqual )
+ // do we need to treat this case specially? Ie.: What shall
+ // "equality" mean for recipients.
+ return matchesInternal( msg->headerField("To") )
+ || matchesInternal( msg->headerField("Cc") )
+ || matchesInternal( msg->headerField("Bcc") )
+ // sometimes messages have multiple Cc headers
+ || matchesInternal( msg->cc() );
+
+ msgContents = msg->headerField("To");
+ if ( !msg->headerField("Cc").compare( msg->cc() ) )
+ msgContents += ", " + msg->headerField("Cc");
+ else
+ msgContents += ", " + msg->cc();
+ msgContents += ", " + msg->headerField("Bcc");
+ } else {
+ // make sure to treat messages with multiple header lines for
+ // the same header correctly
+ msgContents = msg->headerFields( field() ).join( " " );
+ }
+
+ if ( function() == FuncIsInAddressbook ||
+ function() == FuncIsNotInAddressbook ) {
+ // I think only the "from"-field makes sense.
+ msgContents = msg->headerField( field() );
+ if ( msgContents.isEmpty() )
+ return ( function() == FuncIsInAddressbook ) ? false : true;
+ }
+
+ // these two functions need the kmmessage therefore they don't call matchesInternal
+ if ( function() == FuncHasAttachment )
+ return ( msg->toMsgBase().attachmentState() == KMMsgHasAttachment );
+ if ( function() == FuncHasNoAttachment )
+ return ( ((KMMsgAttachmentState) msg->toMsgBase().attachmentState()) == KMMsgHasNoAttachment );
+
+ bool rc = matchesInternal( msgContents );
+ if ( FilterLog::instance()->isLogging() ) {
+ QString msg = ( rc ? "<font color=#00FF00>1 = </font>"
+ : "<font color=#FF0000>0 = </font>" );
+ msg += FilterLog::recode( asString() );
+ // only log headers bcause messages and bodies can be pretty large
+ if ( logContents )
+ msg += " (<i>" + FilterLog::recode( msgContents ) + "</i>)";
+ FilterLog::instance()->add( msg, FilterLog::ruleResult );
+ }
+ return rc;
+}
+
+// helper, does the actual comparing
+bool KMSearchRuleString::matchesInternal( const QString & msgContents ) const
+{
+ switch ( function() ) {
+ case KMSearchRule::FuncEquals:
+ return ( QString::compare( msgContents.lower(), contents().lower() ) == 0 );
+
+ case KMSearchRule::FuncNotEqual:
+ return ( QString::compare( msgContents.lower(), contents().lower() ) != 0 );
+
+ case KMSearchRule::FuncContains:
+ return ( msgContents.find( contents(), 0, false ) >= 0 );
+
+ case KMSearchRule::FuncContainsNot:
+ return ( msgContents.find( contents(), 0, false ) < 0 );
+
+ case KMSearchRule::FuncRegExp:
+ {
+ QRegExp regexp( contents(), false );
+ return ( regexp.search( msgContents ) >= 0 );
+ }
+
+ case KMSearchRule::FuncNotRegExp:
+ {
+ QRegExp regexp( contents(), false );
+ return ( regexp.search( msgContents ) < 0 );
+ }
+
+ case FuncIsGreater:
+ return ( QString::compare( msgContents.lower(), contents().lower() ) > 0 );
+
+ case FuncIsLessOrEqual:
+ return ( QString::compare( msgContents.lower(), contents().lower() ) <= 0 );
+
+ case FuncIsLess:
+ return ( QString::compare( msgContents.lower(), contents().lower() ) < 0 );
+
+ case FuncIsGreaterOrEqual:
+ return ( QString::compare( msgContents.lower(), contents().lower() ) >= 0 );
+
+ case FuncIsInAddressbook: {
+ KABC::AddressBook *stdAb = KABC::StdAddressBook::self( true );
+ QStringList addressList =
+ KPIM::splitEmailAddrList( msgContents.lower() );
+ for( QStringList::ConstIterator it = addressList.begin();
+ ( it != addressList.end() );
+ ++it ) {
+ if ( !stdAb->findByEmail( KPIM::getEmailAddress( *it ) ).isEmpty() )
+ return true;
+ }
+ return false;
+ }
+
+ case FuncIsNotInAddressbook: {
+ KABC::AddressBook *stdAb = KABC::StdAddressBook::self( true );
+ QStringList addressList =
+ KPIM::splitEmailAddrList( msgContents.lower() );
+ for( QStringList::ConstIterator it = addressList.begin();
+ ( it != addressList.end() );
+ ++it ) {
+ if ( stdAb->findByEmail( KPIM::getEmailAddress( *it ) ).isEmpty() )
+ return true;
+ }
+ return false;
+ }
+
+ case FuncIsInCategory: {
+ QString category = contents();
+ QStringList addressList = KPIM::splitEmailAddrList( msgContents.lower() );
+ KABC::AddressBook *stdAb = KABC::StdAddressBook::self( true );
+
+ for( QStringList::ConstIterator it = addressList.begin();
+ it != addressList.end(); ++it ) {
+ KABC::Addressee::List addresses = stdAb->findByEmail( KPIM::getEmailAddress( *it ) );
+
+ for ( KABC::Addressee::List::Iterator itAd = addresses.begin(); itAd != addresses.end(); ++itAd )
+ if ( (*itAd).hasCategory(category) )
+ return true;
+
+ }
+ return false;
+ }
+
+ case FuncIsNotInCategory: {
+ QString category = contents();
+ QStringList addressList = KPIM::splitEmailAddrList( msgContents.lower() );
+ KABC::AddressBook *stdAb = KABC::StdAddressBook::self( true );
+
+ for( QStringList::ConstIterator it = addressList.begin();
+ it != addressList.end(); ++it ) {
+ KABC::Addressee::List addresses = stdAb->findByEmail( KPIM::getEmailAddress( *it ) );
+
+ for ( KABC::Addressee::List::Iterator itAd = addresses.begin(); itAd != addresses.end(); ++itAd )
+ if ( (*itAd).hasCategory(category) )
+ return false;
+
+ }
+ return true;
+ }
+ default:
+ ;
+ }
+
+ return false;
+}
+
+
+//==================================================
+//
+// class KMSearchRuleNumerical
+//
+//==================================================
+
+KMSearchRuleNumerical::KMSearchRuleNumerical( const QCString & field,
+ Function func, const QString & contents )
+ : KMSearchRule(field, func, contents)
+{
+}
+
+bool KMSearchRuleNumerical::isEmpty() const
+{
+ bool ok = false;
+ contents().toInt( &ok );
+
+ return !ok;
+}
+
+
+bool KMSearchRuleNumerical::matches( const KMMessage * msg ) const
+{
+
+ QString msgContents;
+ int numericalMsgContents = 0;
+ int numericalValue = 0;
+
+ if ( field() == "<size>" ) {
+ numericalMsgContents = int( msg->msgLength() );
+ numericalValue = contents().toInt();
+ msgContents.setNum( numericalMsgContents );
+ } else if ( field() == "<age in days>" ) {
+ QDateTime msgDateTime;
+ msgDateTime.setTime_t( msg->date() );
+ numericalMsgContents = msgDateTime.daysTo( QDateTime::currentDateTime() );
+ numericalValue = contents().toInt();
+ msgContents.setNum( numericalMsgContents );
+ }
+ bool rc = matchesInternal( numericalValue, numericalMsgContents, msgContents );
+ if ( FilterLog::instance()->isLogging() ) {
+ QString msg = ( rc ? "<font color=#00FF00>1 = </font>"
+ : "<font color=#FF0000>0 = </font>" );
+ msg += FilterLog::recode( asString() );
+ msg += " ( <i>" + QString::number( numericalMsgContents ) + "</i> )";
+ FilterLog::instance()->add( msg, FilterLog::ruleResult );
+ }
+ return rc;
+}
+
+bool KMSearchRuleNumerical::matchesInternal( long numericalValue,
+ long numericalMsgContents, const QString & msgContents ) const
+{
+ switch ( function() ) {
+ case KMSearchRule::FuncEquals:
+ return ( numericalValue == numericalMsgContents );
+
+ case KMSearchRule::FuncNotEqual:
+ return ( numericalValue != numericalMsgContents );
+
+ case KMSearchRule::FuncContains:
+ return ( msgContents.find( contents(), 0, false ) >= 0 );
+
+ case KMSearchRule::FuncContainsNot:
+ return ( msgContents.find( contents(), 0, false ) < 0 );
+
+ case KMSearchRule::FuncRegExp:
+ {
+ QRegExp regexp( contents(), false );
+ return ( regexp.search( msgContents ) >= 0 );
+ }
+
+ case KMSearchRule::FuncNotRegExp:
+ {
+ QRegExp regexp( contents(), false );
+ return ( regexp.search( msgContents ) < 0 );
+ }
+
+ case FuncIsGreater:
+ return ( numericalMsgContents > numericalValue );
+
+ case FuncIsLessOrEqual:
+ return ( numericalMsgContents <= numericalValue );
+
+ case FuncIsLess:
+ return ( numericalMsgContents < numericalValue );
+
+ case FuncIsGreaterOrEqual:
+ return ( numericalMsgContents >= numericalValue );
+
+ case FuncIsInAddressbook: // since email-addresses are not numerical, I settle for false here
+ return false;
+
+ case FuncIsNotInAddressbook:
+ return false;
+
+ default:
+ ;
+ }
+
+ return false;
+}
+
+
+
+//==================================================
+//
+// class KMSearchRuleStatus
+//
+//==================================================
+QString englishNameForStatus( const KMMsgStatus& status )
+{
+ for ( int i=0; i< numStatusNames; i++ ) {
+ if ( statusNames[i].status == status ) {
+ return statusNames[i].name;
+ }
+ }
+ return QString::null;
+}
+
+KMSearchRuleStatus::KMSearchRuleStatus( const QCString & field,
+ Function func, const QString & aContents )
+ : KMSearchRule(field, func, aContents)
+{
+ // the values are always in english, both from the conf file as well as
+ // the patternedit gui
+ mStatus = statusFromEnglishName( aContents );
+}
+
+KMSearchRuleStatus::KMSearchRuleStatus( int status, Function func )
+: KMSearchRule( "<status>", func, englishNameForStatus( status ) )
+{
+ mStatus = status;
+}
+
+KMMsgStatus KMSearchRuleStatus::statusFromEnglishName( const QString & aStatusString )
+{
+ for ( int i=0; i< numStatusNames; i++ ) {
+ if ( !aStatusString.compare( statusNames[i].name ) ) {
+ return statusNames[i].status;
+ }
+ }
+ return KMMsgStatusUnknown;
+}
+
+bool KMSearchRuleStatus::isEmpty() const
+{
+ return field().stripWhiteSpace().isEmpty() || contents().isEmpty();
+}
+
+bool KMSearchRuleStatus::matches( const DwString &, KMMessage &,
+ const DwBoyerMoore *, int ) const
+{
+ assert( 0 );
+ return false; // don't warn
+}
+
+bool KMSearchRuleStatus::matches( const KMMessage * msg ) const
+{
+
+ KMMsgStatus msgStatus = msg->status();
+ bool rc = false;
+
+ switch ( function() ) {
+ case FuncEquals: // fallthrough. So that "<status> 'is' 'read'" works
+ case FuncContains:
+ if (msgStatus & mStatus)
+ rc = true;
+ break;
+ case FuncNotEqual: // fallthrough. So that "<status> 'is not' 'read'" works
+ case FuncContainsNot:
+ if (! (msgStatus & mStatus) )
+ rc = true;
+ break;
+ // FIXME what about the remaining funcs, how can they make sense for
+ // stati?
+ default:
+ break;
+ }
+
+ if ( FilterLog::instance()->isLogging() ) {
+ QString msg = ( rc ? "<font color=#00FF00>1 = </font>"
+ : "<font color=#FF0000>0 = </font>" );
+ msg += FilterLog::recode( asString() );
+ FilterLog::instance()->add( msg, FilterLog::ruleResult );
+ }
+ return rc;
+}
+
+// ----------------------------------------------------------------------------
+
+//==================================================
+//
+// class KMSearchPattern
+//
+//==================================================
+
+KMSearchPattern::KMSearchPattern( const KConfig * config )
+ : QPtrList<KMSearchRule>()
+{
+ setAutoDelete( true );
+ if ( config )
+ readConfig( config );
+ else
+ init();
+}
+
+KMSearchPattern::~KMSearchPattern()
+{
+}
+
+bool KMSearchPattern::matches( const KMMessage * msg, bool ignoreBody ) const
+{
+ if ( isEmpty() )
+ return true;
+
+ QPtrListIterator<KMSearchRule> it( *this );
+ switch ( mOperator ) {
+ case OpAnd: // all rules must match
+ for ( it.toFirst() ; it.current() ; ++it )
+ if ( !((*it)->requiresBody() && ignoreBody) )
+ if ( !(*it)->matches( msg ) )
+ return false;
+ return true;
+ case OpOr: // at least one rule must match
+ for ( it.toFirst() ; it.current() ; ++it )
+ if ( !((*it)->requiresBody() && ignoreBody) )
+ if ( (*it)->matches( msg ) )
+ return true;
+ // fall through
+ default:
+ return false;
+ }
+}
+
+bool KMSearchPattern::matches( const DwString & aStr, bool ignoreBody ) const
+{
+ if ( isEmpty() )
+ return true;
+
+ KMMessage msg;
+ QPtrListIterator<KMSearchRule> it( *this );
+ switch ( mOperator ) {
+ case OpAnd: // all rules must match
+ for ( it.toFirst() ; it.current() ; ++it )
+ if ( !((*it)->requiresBody() && ignoreBody) )
+ if ( !(*it)->matches( aStr, msg ) )
+ return false;
+ return true;
+ case OpOr: // at least one rule must match
+ for ( it.toFirst() ; it.current() ; ++it )
+ if ( !((*it)->requiresBody() && ignoreBody) )
+ if ( (*it)->matches( aStr, msg ) )
+ return true;
+ // fall through
+ default:
+ return false;
+ }
+}
+
+bool KMSearchPattern::matches( Q_UINT32 serNum, bool ignoreBody ) const
+{
+ if ( isEmpty() )
+ return true;
+
+ bool res;
+ int idx = -1;
+ KMFolder *folder = 0;
+ KMMsgDict::instance()->getLocation(serNum, &folder, &idx);
+ if (!folder || (idx == -1) || (idx >= folder->count())) {
+ return false;
+ }
+
+ KMFolderOpener openFolder(folder, "searchptr");
+ KMMsgBase *msgBase = folder->getMsgBase(idx);
+ if (requiresBody() && !ignoreBody) {
+ bool unGet = !msgBase->isMessage();
+ KMMessage *msg = folder->getMsg(idx);
+ res = false;
+ if ( msg ) {
+ res = matches( msg, ignoreBody );
+ if (unGet)
+ folder->unGetMsg(idx);
+ }
+ } else {
+ res = matches( folder->getDwString(idx), ignoreBody );
+ }
+ return res;
+}
+
+bool KMSearchPattern::requiresBody() const {
+ QPtrListIterator<KMSearchRule> it( *this );
+ for ( it.toFirst() ; it.current() ; ++it )
+ if ( (*it)->requiresBody() )
+ return true;
+ return false;
+}
+
+void KMSearchPattern::purify() {
+ QPtrListIterator<KMSearchRule> it( *this );
+ it.toLast();
+ while ( it.current() )
+ if ( (*it)->isEmpty() ) {
+#ifndef NDEBUG
+ kdDebug(5006) << "KMSearchPattern::purify(): removing " << (*it)->asString() << endl;
+#endif
+ remove( *it );
+ } else {
+ --it;
+ }
+}
+
+void KMSearchPattern::readConfig( const KConfig * config ) {
+ init();
+
+ mName = config->readEntry("name");
+ if ( !config->hasKey("rules") ) {
+ kdDebug(5006) << "KMSearchPattern::readConfig: found legacy config! Converting." << endl;
+ importLegacyConfig( config );
+ return;
+ }
+
+ mOperator = config->readEntry("operator") == "or" ? OpOr : OpAnd;
+
+ const int nRules = config->readNumEntry( "rules", 0 );
+
+ for ( int i = 0 ; i < nRules ; i++ ) {
+ KMSearchRule * r = KMSearchRule::createInstanceFromConfig( config, i );
+ if ( r->isEmpty() )
+ delete r;
+ else
+ append( r );
+ }
+}
+
+void KMSearchPattern::importLegacyConfig( const KConfig * config ) {
+ KMSearchRule * rule = KMSearchRule::createInstance( config->readEntry("fieldA").latin1(),
+ config->readEntry("funcA").latin1(),
+ config->readEntry("contentsA") );
+ if ( rule->isEmpty() ) {
+ // if the first rule is invalid,
+ // we really can't do much heuristics...
+ delete rule;
+ return;
+ }
+ append( rule );
+
+ const QString sOperator = config->readEntry("operator");
+ if ( sOperator == "ignore" ) return;
+
+ rule = KMSearchRule::createInstance( config->readEntry("fieldB").latin1(),
+ config->readEntry("funcB").latin1(),
+ config->readEntry("contentsB") );
+ if ( rule->isEmpty() ) {
+ delete rule;
+ return;
+ }
+ append( rule );
+
+ if ( sOperator == "or" ) {
+ mOperator = OpOr;
+ return;
+ }
+ // This is the interesting case...
+ if ( sOperator == "unless" ) { // meaning "and not", ie we need to...
+ // ...invert the function (e.g. "equals" <-> "doesn't equal")
+ // We simply toggle the last bit (xor with 0x1)... This assumes that
+ // KMSearchRule::Function's come in adjacent pairs of pros and cons
+ KMSearchRule::Function func = last()->function();
+ unsigned int intFunc = (unsigned int)func;
+ func = KMSearchRule::Function( intFunc ^ 0x1 );
+
+ last()->setFunction( func );
+ }
+
+ // treat any other case as "and" (our default).
+}
+
+void KMSearchPattern::writeConfig( KConfig * config ) const {
+ config->writeEntry("name", mName);
+ config->writeEntry("operator", (mOperator == KMSearchPattern::OpOr) ? "or" : "and" );
+
+ int i = 0;
+ for ( QPtrListIterator<KMSearchRule> it( *this ) ; it.current() && i < FILTER_MAX_RULES ; ++i , ++it )
+ // we could do this ourselves, but we want the rules to be extensible,
+ // so we give the rule it's number and let it do the rest.
+ (*it)->writeConfig( config, i );
+
+ // save the total number of rules.
+ config->writeEntry( "rules", i );
+}
+
+void KMSearchPattern::init() {
+ clear();
+ mOperator = OpAnd;
+ mName = '<' + i18n("name used for a virgin filter","unknown") + '>';
+}
+
+QString KMSearchPattern::asString() const {
+ QString result;
+ if ( mOperator == OpOr )
+ result = i18n("(match any of the following)");
+ else
+ result = i18n("(match all of the following)");
+
+ for ( QPtrListIterator<KMSearchRule> it( *this ) ; it.current() ; ++it )
+ result += "\n\t" + FilterLog::recode( (*it)->asString() );
+
+ return result;
+}
+
+const KMSearchPattern & KMSearchPattern::operator=( const KMSearchPattern & other ) {
+ if ( this == &other )
+ return *this;
+
+ setOp( other.op() );
+ setName( other.name() );
+
+ clear(); // ###
+
+ for ( QPtrListIterator<KMSearchRule> it( other ) ; it.current() ; ++it ) {
+ KMSearchRule * rule = KMSearchRule::createInstance( **it ); // deep copy
+ append( rule );
+ }
+
+ return *this;
+}
diff --git a/kmail/kmsearchpattern.h b/kmail/kmsearchpattern.h
new file mode 100644
index 00000000..bb9a9600
--- /dev/null
+++ b/kmail/kmsearchpattern.h
@@ -0,0 +1,407 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+// kmsearchpattern.h
+// Author: Marc Mutz <Marc@Mutz.com>
+// This code is under GPL!
+
+#ifndef _kmsearchpattern_h_
+#define _kmsearchpattern_h_
+
+#include <klocale.h>
+#include <qptrlist.h>
+#include <qstring.h>
+#include <qcstring.h>
+#include "kmmsgbase.h" // for KMMsgStatus
+
+class KMMessage;
+class KConfig;
+class DwBoyerMoore;
+class DwString;
+
+
+// maximum number of filter rules per filter
+const int FILTER_MAX_RULES=8;
+
+/** Incoming mail is sent through the list of mail filter
+ rules before it is placed in the associated mail folder (usually "inbox").
+ This class represents one mail filter rule. It is also used to represent
+ a search rule as used by the search dialog and folders.
+
+ @short This class represents one search pattern rule.
+*/
+class KMSearchRule
+{
+public:
+ /** Operators for comparison of field and contents.
+ If you change the order or contents of the enum: do not forget
+ to change funcConfigNames[], sFilterFuncList and matches()
+ in KMSearchRule, too.
+ Also, it is assumed that these functions come in pairs of logical
+ opposites (ie. "=" <-> "!=", ">" <-> "<=", etc.).
+ */
+ enum Function { FuncNone = -1,
+ FuncContains=0, FuncContainsNot,
+ FuncEquals, FuncNotEqual,
+ FuncRegExp, FuncNotRegExp,
+ FuncIsGreater, FuncIsLessOrEqual,
+ FuncIsLess, FuncIsGreaterOrEqual,
+ FuncIsInAddressbook, FuncIsNotInAddressbook,
+ FuncIsInCategory, FuncIsNotInCategory,
+ FuncHasAttachment, FuncHasNoAttachment};
+ KMSearchRule ( const QCString & field=0, Function=FuncContains,
+ const QString &contents=QString::null );
+ KMSearchRule ( const KMSearchRule &other );
+
+ const KMSearchRule & operator=( const KMSearchRule & other );
+
+ /** Create a search rule of a certain type by instantiating the appro-
+ priate subclass depending on the @p field. */
+ static KMSearchRule* createInstance( const QCString & field=0,
+ Function function=FuncContains,
+ const QString & contents=QString::null );
+
+ static KMSearchRule* createInstance( const QCString & field,
+ const char * function,
+ const QString & contents );
+
+ static KMSearchRule * createInstance( const KMSearchRule & other );
+
+ /** Initialize the object from a given config file. The group must
+ be preset. @p aIdx is an identifier that is used to distinguish
+ rules within a single config group. This function does no
+ validation of the data obtained from the config file. You should
+ call isEmpty yourself if you need valid rules. */
+ static KMSearchRule* createInstanceFromConfig( const KConfig * config, int aIdx );
+
+ virtual ~KMSearchRule() {};
+
+ /** Tries to match the rule against the given KMMessage.
+ @return TRUE if the rule matched, FALSE otherwise. Must be
+ implemented by subclasses.
+ */
+ virtual bool matches( const KMMessage * msg ) const = 0;
+
+ /** Optimized version tries to match the rule against the given
+ @see DwString.
+ @return TRUE if the rule matched, FALSE otherwise.
+ */
+ virtual bool matches( const DwString & str, KMMessage & msg,
+ const DwBoyerMoore * headerField=0,
+ int headerLen=-1 ) const;
+
+ /** Determine whether the rule is worth considering. It isn't if
+ either the field is not set or the contents is empty.
+ KFilter should make sure that it's rule list contains
+ only non-empty rules, as matches doesn't check this. */
+ virtual bool isEmpty() const = 0;
+
+ /** Returns true if the rule depends on a complete message,
+ otherwise returns false. */
+ virtual bool requiresBody() const { return true; }
+
+
+ /** Save the object into a given config file. The group must be
+ preset. @p aIdx is an identifier that is used to distinguish
+ rules within a single config group. This function will happily
+ write itself even when it's not valid, assuming higher layers to
+ Do The Right Thing(TM). */
+ void writeConfig( KConfig * config, int aIdx ) const;
+
+ /** Return filter function. This can be any of the operators
+ defined in Function. */
+ Function function() const { return mFunction; }
+
+ /** Set filter function. */
+ void setFunction( Function aFunction ) { mFunction = aFunction; }
+
+ /** Return message header field name (without the trailing ':').
+ There are also six pseudo-headers:
+ @li \<message\>: Try to match against the whole message.
+ @li \<body\>: Try to match against the body of the message.
+ @li \<any header\>: Try to match against any header field.
+ @li \<recipients\>: Try to match against both To: and Cc: header fields.
+ @li \<size\>: Try to match against size of message (numerical).
+ @li \<age in days\>: Try to match against age of message (numerical).
+ @li \<status\>: Try to match against status of message (status).
+ */
+ QCString field() const { return mField; }
+
+ /** Set message header field name (make sure there's no trailing
+ colon ':') */
+ void setField( const QCString & field ) { mField = field; }
+
+ /** Return the value. This can be either a substring to search for in
+ or a regexp pattern to match against the header. */
+ QString contents() const { return mContents; }
+ /** Set the value. */
+ void setContents( const QString & aContents ) { mContents = aContents; }
+
+ /** Returns the rule as string. For debugging.*/
+ const QString asString() const;
+
+private:
+ static Function configValueToFunc( const char * str );
+ static QString functionToString( Function function );
+
+ QCString mField;
+ Function mFunction;
+ QString mContents;
+};
+
+
+// subclasses representing the different kinds of searches
+
+/** This class represents a search to be performed against a string.
+ * The string can be either a message header, or a pseudo header, such
+ * as \<body\>
+ @short This class represents a search pattern rule operating on a string.
+*/
+
+class KMSearchRuleString : public KMSearchRule
+{
+public:
+ KMSearchRuleString( const QCString & field=0, Function function=FuncContains,
+ const QString & contents=QString::null );
+ KMSearchRuleString( const KMSearchRuleString & other );
+ const KMSearchRuleString & operator=( const KMSearchRuleString & other );
+
+ virtual ~KMSearchRuleString();
+ virtual bool isEmpty() const ;
+ virtual bool requiresBody() const;
+
+ virtual bool matches( const KMMessage * msg ) const;
+
+ /** Optimized version tries to match the rule against the given DwString.
+ @return TRUE if the rule matched, FALSE otherwise.
+ */
+ virtual bool matches( const DwString & str, KMMessage & msg,
+ const DwBoyerMoore * headerField=0,
+ int headerLen=-1 ) const;
+
+ /** Helper for the main matches() method. Does the actual comparing. */
+ bool matchesInternal( const QString & msgContents ) const;
+
+private:
+ const DwBoyerMoore *mBmHeaderField;
+};
+
+
+/** This class represents a search to be performed against a numerical value,
+ * such as the age of the message in days or its size.
+ @short This class represents a search pattern rule operating on numerical
+ values.
+*/
+
+class KMSearchRuleNumerical : public KMSearchRule
+{
+public:
+ KMSearchRuleNumerical( const QCString & field=0, Function function=FuncContains,
+ const QString & contents=QString::null );
+ virtual bool isEmpty() const ;
+
+ virtual bool matches( const KMMessage * msg ) const;
+
+ /** Helper for the main matches() method. Does the actual comparing. */
+ bool matchesInternal( long numericalValue, long numericalMsgContents,
+ const QString & msgContents ) const;
+};
+
+
+namespace KMail {
+// The below are used in several places and here so they are accessible.
+ struct MessageStatus {
+ const char * const text;
+ const char * const icon;
+ };
+
+ // If you change the ordering here; also do it in the enum below
+ static const MessageStatus StatusValues[] = {
+ { I18N_NOOP( "Important" ), "kmmsgflag" },
+ { I18N_NOOP( "New" ), "kmmsgnew" },
+ { I18N_NOOP( "Unread" ), "kmmsgunseen" },
+ { I18N_NOOP( "Read" ), "kmmsgread" },
+ { I18N_NOOP( "Old" ), 0 },
+ { I18N_NOOP( "Deleted" ), "kmmsgdel" },
+ { I18N_NOOP( "Replied" ), "kmmsgreplied" },
+ { I18N_NOOP( "Forwarded" ), "kmmsgforwarded" },
+ { I18N_NOOP( "Queued" ), "kmmsgqueued" },
+ { I18N_NOOP( "Sent" ), "kmmsgsent" },
+ { I18N_NOOP( "Watched" ), "kmmsgwatched" },
+ { I18N_NOOP( "Ignored" ), "kmmsgignored" },
+ { I18N_NOOP( "Spam" ), "kmmsgspam" },
+ { I18N_NOOP( "Ham" ), "kmmsgham" },
+ { I18N_NOOP( "To Do" ), "kmmsgtodo" },
+ { I18N_NOOP( "Has Attachment"), "kmmsgattachment" }
+ };
+ // If you change the ordering here; also do it in the array above
+ enum StatusValueTypes {
+ StatusImportant = 0,
+ StatusNew = 1,
+ StatusUnread = 2,
+ StatusRead = 3,
+ StatusOld = 4,
+ StatusDeleted = 5,
+ StatusReplied = 6,
+ StatusForwarded = 7,
+ StatusQueued = 8,
+ StatusSent = 9,
+ StatusWatched = 10,
+ StatusIgnored = 11,
+ StatusSpam = 12,
+ StatusHam = 13,
+ StatusToDo = 14,
+ StatusHasAttachment = 15
+ };
+
+ static const int StatusValueCount =
+ sizeof( StatusValues ) / sizeof( MessageStatus );
+ // we want to show all status entries in the quick search bar, but only the
+ // ones up to attachment in the search/filter dialog, because there the
+ // attachment case is handled separately.
+ static const int StatusValueCountWithoutHidden = StatusValueCount - 1;
+}
+
+/** This class represents a search to be performed against the status of a
+ * messsage. The status is represented by a bitfield.
+ @short This class represents a search pattern rule operating on message
+ status.
+*/
+class KMSearchRuleStatus : public KMSearchRule
+{
+public:
+ KMSearchRuleStatus( const QCString & field=0, Function function=FuncContains,
+ const QString & contents=QString::null );
+ KMSearchRuleStatus( int status, Function function=FuncContains );
+
+ virtual bool isEmpty() const ;
+ virtual bool matches( const KMMessage * msg ) const;
+ //Not possible to implement this form for status searching
+ virtual bool matches( const DwString &, KMMessage &,
+ const DwBoyerMoore *,
+ int ) const;
+ static KMMsgStatus statusFromEnglishName(const QString&);
+ private:
+ KMMsgStatus mStatus;
+};
+
+// ------------------------------------------------------------------------
+
+/** This class is an abstraction of a search over messages. It is
+ intended to be used inside a KFilter (which adds KFilterAction's),
+ as well as in KMSearch. It can read and write itself into a
+ 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 QPtrList 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.
+
+ As a QPtrList with autoDelete enabled, it assumes that it is the
+ central repository for the rules it contains. So if you want to
+ reuse a rule in another pattern, make a deep copy of that rule.
+
+ @short An abstraction of a search over messages.
+ @author Marc Mutz <Marc@Mutz.com>
+*/
+class KMSearchPattern : public QPtrList<KMSearchRule>
+{
+
+public:
+ /** Boolean operators that connect the return values of the
+ individual rules. A pattern with @p OpAnd will match iff all
+ it's rules match, whereas a pattern with @p OpOr will match iff
+ any of it's rules matches.
+ */
+ enum Operator { OpAnd, OpOr };
+ /** Constructor that initializes from a given KConfig group, if
+ given. This feature is mainly (solely?) used in KMFilter,
+ as we don't allow to store search patterns in the config (yet).
+ If config is 0, provides a pattern with minimal, but
+ sufficient initialization. Unmodified, such a pattern will fail
+ to match any KMMessage. You can query for such an empty
+ rule by using isEmpty, which is inherited from QPtrList.
+ */
+ KMSearchPattern( const KConfig * config=0 );
+
+ /** Destructor. Deletes all stored rules! */
+ ~KMSearchPattern();
+
+ /** The central function of this class. Tries to match the set of
+ rules against a KMMessage. It's virtual to allow derived
+ classes with added rules to reimplement it, yet reimplemented
+ methods should and (&&) the result of this function with their
+ own result or else most functionality is lacking, or has to be
+ reimplemented, since the rules are private to this class.
+
+ @return TRUE if the match was successful, FALSE otherwise.
+ */
+ bool matches( const KMMessage * msg, bool ignoreBody = false ) const;
+ bool matches( const DwString & str, bool ignoreBody = false ) const;
+ bool matches( Q_UINT32 sernum, bool ignoreBody = false ) const;
+
+ /** Returns true if the pattern only depends the DwString that backs
+ a message */
+ bool requiresBody() const;
+
+ /** Removes all empty rules from the list. You should call this
+ method whenever the user had had control of the rules outside of
+ this class. (e.g. after editing it with KMSearchPatternEdit).
+ */
+ void purify();
+
+ /** Reads a search pattern from a KConfig. The group has to be
+ preset. If it does not find a valid saerch pattern in the preset
+ group, initializes the pattern as if it were constructed using
+ the default constructor.
+
+ For backwards compatibility with previous versions of KMail, it
+ checks for old-style filter rules (e.g. using @p OpIgnore)
+ in @p config und converts them to the new format on writeConfig.
+
+ Derived classes reimplementing readConfig() should also call this
+ method, or else the rules will not be loaded.
+ */
+ void readConfig( const KConfig * config );
+ /** Writes itself into @p config. The group has to be preset. Tries
+ to delete old-style keys by overwriting them with QString::null.
+
+ Derived classes reimplementing writeConfig() should also call this
+ method, or else the rules will not be stored.
+ */
+ void writeConfig( KConfig * config ) const;
+
+ /** Get the name of the search pattern. */
+ QString name() const { return mName; }
+ /** Set the name of the search pattern. KMFilter uses this to
+ store it's own name, too. */
+ void setName( const QString & newName ) { mName = newName ; }
+
+ /** Get the filter operator */
+ KMSearchPattern::Operator op() const { return mOperator; }
+ /** Set the filter operator */
+ void setOp( KMSearchPattern::Operator aOp ) { mOperator = aOp; }
+
+ /** Returns the pattern as string. For debugging.*/
+ QString asString() const;
+
+ /** Overloaded assignment operator. Makes a deep copy. */
+ const KMSearchPattern & operator=( const KMSearchPattern & aPattern );
+
+private:
+ /** Tries to import a legacy search pattern, ie. one that still has
+ e.g. the @p unless or @p ignore operator which were useful as
+ long as the number of rules was restricted to two. This method
+ is called from readConfig, which detects legacy configurations
+ and also makes sure that this method is called from an initialized
+ object.
+ */
+ void importLegacyConfig( const KConfig * config );
+ /** Initializes the object. Clears the list of rules, sets the name
+ to "<i18n("unnamed")>", and the boolean operator to @p OpAnd. */
+ void init();
+
+ QString mName;
+ Operator mOperator;
+};
+
+#endif /* _kmsearchpattern_h_ */
diff --git a/kmail/kmsearchpatternedit.cpp b/kmail/kmsearchpatternedit.cpp
new file mode 100644
index 00000000..df09bbd2
--- /dev/null
+++ b/kmail/kmsearchpatternedit.cpp
@@ -0,0 +1,483 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+// kmsearchpatternedit.cpp
+// Author: Marc Mutz <Marc@Mutz.com>
+// This code is under GPL
+
+#include <config.h>
+#include "kmsearchpatternedit.h"
+
+#include "kmsearchpattern.h"
+#include "rulewidgethandlermanager.h"
+using KMail::RuleWidgetHandlerManager;
+
+#include <klocale.h>
+#include <kdialog.h>
+#include <kdebug.h>
+
+#include <qradiobutton.h>
+#include <qcombobox.h>
+#include <qbuttongroup.h>
+#include <qwidgetstack.h>
+#include <qlayout.h>
+
+#include <assert.h>
+
+// Definition of special rule field strings
+// Note: Also see KMSearchRule::matches() and ruleFieldToEnglish() if
+// you change the following i18n-ized strings!
+// Note: The index of the values in the following array has to correspond to
+// the value of the entries in the enum in KMSearchRuleWidget.
+static const struct {
+ const char *internalName;
+ const char *displayName;
+} SpecialRuleFields[] = {
+ { "<message>", I18N_NOOP( "Complete Message" ) },
+ { "<body>", I18N_NOOP( "Body of Message" ) },
+ { "<any header>", I18N_NOOP( "Anywhere in Headers" ) },
+ { "<recipients>", I18N_NOOP( "All Recipients" ) },
+ { "<size>", I18N_NOOP( "Size in Bytes" ) },
+ { "<age in days>", I18N_NOOP( "Age in Days" ) },
+ { "<status>", I18N_NOOP( "Message Status" ) }
+};
+static const int SpecialRuleFieldsCount =
+ sizeof( SpecialRuleFields ) / sizeof( *SpecialRuleFields );
+
+//=============================================================================
+//
+// class KMSearchRuleWidget
+//
+//=============================================================================
+
+KMSearchRuleWidget::KMSearchRuleWidget( QWidget *parent, KMSearchRule *aRule,
+ const char *name, bool headersOnly,
+ bool absoluteDates )
+ : QWidget( parent, name ),
+ mRuleField( 0 ),
+ mFunctionStack( 0 ),
+ mValueStack( 0 ),
+ mAbsoluteDates( absoluteDates )
+{
+ initFieldList( headersOnly, absoluteDates );
+ initWidget();
+
+ if ( aRule )
+ setRule( aRule );
+ else
+ reset();
+}
+
+void KMSearchRuleWidget::setHeadersOnly( bool headersOnly )
+{
+ KMSearchRule* srule = rule();
+ QCString currentText = srule->field();
+ delete srule;
+ initFieldList( headersOnly, mAbsoluteDates );
+
+ mRuleField->clear();
+ mRuleField->insertStringList( mFilterFieldList );
+ mRuleField->setSizeLimit( mRuleField->count() );
+ mRuleField->adjustSize();
+
+ if (( currentText != "<message>") &&
+ ( currentText != "<body>"))
+ mRuleField->changeItem( QString::fromAscii( currentText ), 0 );
+ else
+ mRuleField->changeItem( QString::null, 0 );
+}
+
+void KMSearchRuleWidget::initWidget()
+{
+ QHBoxLayout * hlay = new QHBoxLayout( this, 0, KDialog::spacingHint() );
+
+ // initialize the header field combo box
+ mRuleField = new QComboBox( true, this, "mRuleField" );
+ mRuleField->insertStringList( mFilterFieldList );
+ // don't show sliders when popping up this menu
+ mRuleField->setSizeLimit( mRuleField->count() );
+ mRuleField->adjustSize();
+ hlay->addWidget( mRuleField );
+
+ // initialize the function/value widget stack
+ mFunctionStack = new QWidgetStack( this, "mFunctionStack" );
+ //Don't expand the widget in vertical direction
+ mFunctionStack->setSizePolicy( QSizePolicy::Preferred,QSizePolicy::Fixed );
+
+ hlay->addWidget( mFunctionStack );
+
+ mValueStack = new QWidgetStack( this, "mValueStack" );
+ mValueStack->setSizePolicy( QSizePolicy::Preferred,QSizePolicy::Fixed );
+ hlay->addWidget( mValueStack );
+ hlay->setStretchFactor( mValueStack, 10 );
+
+ RuleWidgetHandlerManager::instance()->createWidgets( mFunctionStack,
+ mValueStack,
+ this );
+
+ // redirect focus to the header field combo box
+ setFocusProxy( mRuleField );
+
+ connect( mRuleField, SIGNAL( activated( const QString & ) ),
+ this, SLOT( slotRuleFieldChanged( const QString & ) ) );
+ connect( mRuleField, SIGNAL( textChanged( const QString & ) ),
+ this, SLOT( slotRuleFieldChanged( const QString & ) ) );
+ connect( mRuleField, SIGNAL( textChanged( const QString & ) ),
+ this, SIGNAL( fieldChanged( const QString & ) ) );
+}
+
+void KMSearchRuleWidget::setRule( KMSearchRule *aRule )
+{
+ assert ( aRule );
+
+// kdDebug(5006) << "KMSearchRuleWidget::setRule( "
+// << aRule->asString() << " )" << endl;
+
+ //--------------set the field
+ int i = indexOfRuleField( aRule->field() );
+
+ mRuleField->blockSignals( true );
+
+ if ( i < 0 ) { // not found -> user defined field
+ mRuleField->changeItem( QString::fromLatin1( aRule->field() ), 0 );
+ i = 0;
+ } else { // found in the list of predefined fields
+ mRuleField->changeItem( QString::null, 0 );
+ }
+
+ mRuleField->setCurrentItem( i );
+ mRuleField->blockSignals( false );
+
+ RuleWidgetHandlerManager::instance()->setRule( mFunctionStack, mValueStack,
+ aRule );
+}
+
+KMSearchRule* KMSearchRuleWidget::rule() const {
+ const QCString ruleField = ruleFieldToEnglish( mRuleField->currentText() );
+ const KMSearchRule::Function function =
+ RuleWidgetHandlerManager::instance()->function( ruleField,
+ mFunctionStack );
+ const QString value =
+ RuleWidgetHandlerManager::instance()->value( ruleField, mFunctionStack,
+ mValueStack );
+
+ return KMSearchRule::createInstance( ruleField, function, value );
+}
+
+void KMSearchRuleWidget::reset()
+{
+ mRuleField->blockSignals( true );
+ mRuleField->changeItem( "", 0 );
+ mRuleField->setCurrentItem( 0 );
+ mRuleField->blockSignals( false );
+
+ RuleWidgetHandlerManager::instance()->reset( mFunctionStack, mValueStack );
+}
+
+void KMSearchRuleWidget::slotFunctionChanged()
+{
+ const QCString ruleField = ruleFieldToEnglish( mRuleField->currentText() );
+ RuleWidgetHandlerManager::instance()->update( ruleField,
+ mFunctionStack,
+ mValueStack );
+}
+
+void KMSearchRuleWidget::slotValueChanged()
+{
+ const QCString ruleField = ruleFieldToEnglish( mRuleField->currentText() );
+ const QString prettyValue =
+ RuleWidgetHandlerManager::instance()->prettyValue( ruleField,
+ mFunctionStack,
+ mValueStack );
+ emit contentsChanged( prettyValue );
+}
+
+QCString KMSearchRuleWidget::ruleFieldToEnglish( const QString & i18nVal )
+{
+ for ( int i = 0; i < SpecialRuleFieldsCount; ++i ) {
+ if ( i18nVal == i18n( SpecialRuleFields[i].displayName ) )
+ return SpecialRuleFields[i].internalName;
+ }
+ return i18nVal.latin1();
+}
+
+int KMSearchRuleWidget::ruleFieldToId( const QString & i18nVal )
+{
+ for ( int i = 0; i < SpecialRuleFieldsCount; ++i ) {
+ if ( i18nVal == i18n( SpecialRuleFields[i].displayName ) )
+ return i;
+ }
+ return -1; // no pseudo header
+}
+
+static QString displayNameFromInternalName( const QString & internal )
+{
+ for ( int i = 0; i < SpecialRuleFieldsCount; ++i ) {
+ if ( internal == SpecialRuleFields[i].internalName )
+ return i18n(SpecialRuleFields[i].displayName);
+ }
+ return internal.latin1();
+}
+
+
+
+int KMSearchRuleWidget::indexOfRuleField( const QCString & aName ) const
+{
+ if ( aName.isEmpty() )
+ return -1;
+
+ QString i18n_aName = displayNameFromInternalName( aName );
+
+ for ( int i = 1; i < mRuleField->count(); ++i ) {
+ if ( mRuleField->text( i ) == i18n_aName )
+ return i;
+ }
+
+ return -1;
+}
+
+void KMSearchRuleWidget::initFieldList( bool headersOnly, bool absoluteDates )
+{
+ mFilterFieldList.clear();
+ mFilterFieldList.append(""); // empty entry for user input
+ if( !headersOnly ) {
+ mFilterFieldList.append( i18n( SpecialRuleFields[Message].displayName ) );
+ mFilterFieldList.append( i18n( SpecialRuleFields[Body].displayName ) );
+ }
+ mFilterFieldList.append( i18n( SpecialRuleFields[AnyHeader].displayName ) );
+ mFilterFieldList.append( i18n( SpecialRuleFields[Recipients].displayName ) );
+ mFilterFieldList.append( i18n( SpecialRuleFields[Size].displayName ) );
+ if ( !absoluteDates )
+ mFilterFieldList.append( i18n( SpecialRuleFields[AgeInDays].displayName ) );
+ mFilterFieldList.append( i18n( SpecialRuleFields[Status].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");
+ mFilterFieldList.append("X-Spam-Flag");
+}
+
+void KMSearchRuleWidget::slotRuleFieldChanged( const QString & field )
+{
+ RuleWidgetHandlerManager::instance()->update( ruleFieldToEnglish( field ),
+ mFunctionStack,
+ mValueStack );
+}
+
+//=============================================================================
+//
+// class KMFilterActionWidgetLister (the filter action editor)
+//
+//=============================================================================
+
+KMSearchRuleWidgetLister::KMSearchRuleWidgetLister( QWidget *parent, const char* name, bool headersOnly, bool absoluteDates )
+ : KWidgetLister( 2, FILTER_MAX_RULES, parent, name )
+{
+ mRuleList = 0;
+ mHeadersOnly = headersOnly;
+ mAbsoluteDates = absoluteDates;
+}
+
+KMSearchRuleWidgetLister::~KMSearchRuleWidgetLister()
+{
+}
+
+void KMSearchRuleWidgetLister::setRuleList( QPtrList<KMSearchRule> *aList )
+{
+ assert ( aList );
+
+ if ( mRuleList && mRuleList != aList )
+ regenerateRuleListFromWidgets();
+
+ mRuleList = aList;
+
+ if ( mWidgetList.first() ) // move this below next 'if'?
+ mWidgetList.first()->blockSignals(true);
+
+ if ( aList->count() == 0 ) {
+ slotClear();
+ mWidgetList.first()->blockSignals(false);
+ return;
+ }
+
+ int superfluousItems = (int)mRuleList->count() - mMaxWidgets ;
+ if ( superfluousItems > 0 ) {
+ kdDebug(5006) << "KMSearchRuleWidgetLister: Clipping rule list to "
+ << mMaxWidgets << " items!" << endl;
+
+ for ( ; superfluousItems ; superfluousItems-- )
+ mRuleList->removeLast();
+ }
+
+ // HACK to workaround regression in Qt 3.1.3 and Qt 3.2.0 (fixes bug #63537)
+ setNumberOfShownWidgetsTo( QMAX((int)mRuleList->count(),mMinWidgets)+1 );
+ // set the right number of widgets
+ setNumberOfShownWidgetsTo( QMAX((int)mRuleList->count(),mMinWidgets) );
+
+ // load the actions into the widgets
+ QPtrListIterator<KMSearchRule> rIt( *mRuleList );
+ QPtrListIterator<QWidget> wIt( mWidgetList );
+ for ( rIt.toFirst(), wIt.toFirst() ;
+ rIt.current() && wIt.current() ; ++rIt, ++wIt ) {
+ static_cast<KMSearchRuleWidget*>(*wIt)->setRule( (*rIt) );
+ }
+ for ( ; wIt.current() ; ++wIt )
+ ((KMSearchRuleWidget*)(*wIt))->reset();
+
+ assert( mWidgetList.first() );
+ mWidgetList.first()->blockSignals(false);
+}
+
+void KMSearchRuleWidgetLister::setHeadersOnly( bool headersOnly )
+{
+ QPtrListIterator<QWidget> wIt( mWidgetList );
+ for ( wIt.toFirst() ; wIt.current() ; ++wIt ) {
+ (static_cast<KMSearchRuleWidget*>(*wIt))->setHeadersOnly( headersOnly );
+ }
+}
+
+void KMSearchRuleWidgetLister::reset()
+{
+ if ( mRuleList )
+ regenerateRuleListFromWidgets();
+
+ mRuleList = 0;
+ slotClear();
+}
+
+QWidget* KMSearchRuleWidgetLister::createWidget( QWidget *parent )
+{
+ return new KMSearchRuleWidget(parent, 0, 0, mHeadersOnly, mAbsoluteDates);
+}
+
+void KMSearchRuleWidgetLister::clearWidget( QWidget *aWidget )
+{
+ if ( aWidget )
+ ((KMSearchRuleWidget*)aWidget)->reset();
+}
+
+void KMSearchRuleWidgetLister::regenerateRuleListFromWidgets()
+{
+ if ( !mRuleList ) return;
+
+ mRuleList->clear();
+
+ QPtrListIterator<QWidget> it( mWidgetList );
+ for ( it.toFirst() ; it.current() ; ++it ) {
+ KMSearchRule *r = ((KMSearchRuleWidget*)(*it))->rule();
+ if ( r )
+ mRuleList->append( r );
+ }
+}
+
+
+
+
+//=============================================================================
+//
+// class KMSearchPatternEdit
+//
+//=============================================================================
+
+KMSearchPatternEdit::KMSearchPatternEdit(QWidget *parent, const char *name, bool headersOnly, bool absoluteDates )
+ : QGroupBox( 1/*columns*/, Horizontal, parent, name )
+{
+ setTitle( i18n("Search Criteria") );
+ initLayout( headersOnly, absoluteDates );
+}
+
+KMSearchPatternEdit::KMSearchPatternEdit(const QString & title, QWidget *parent, const char *name, bool headersOnly, bool absoluteDates)
+ : QGroupBox( 1/*column*/, Horizontal, title, parent, name )
+{
+ initLayout( headersOnly, absoluteDates );
+}
+
+KMSearchPatternEdit::~KMSearchPatternEdit()
+{
+}
+
+void KMSearchPatternEdit::initLayout(bool headersOnly, bool absoluteDates)
+{
+ //------------the radio buttons
+ mAllRBtn = new QRadioButton( i18n("Match a&ll of the following"), this, "mAllRBtn" );
+ mAnyRBtn = new QRadioButton( i18n("Match an&y of the following"), this, "mAnyRBtn" );
+
+ mAllRBtn->setChecked(true);
+ mAnyRBtn->setChecked(false);
+
+ QButtonGroup *bg = new QButtonGroup( this );
+ bg->hide();
+ bg->insert( mAllRBtn, (int)KMSearchPattern::OpAnd );
+ bg->insert( mAnyRBtn, (int)KMSearchPattern::OpOr );
+
+ //------------the list of KMSearchRuleWidget's
+ mRuleLister = new KMSearchRuleWidgetLister( this, "swl", headersOnly, absoluteDates );
+ mRuleLister->slotClear();
+
+ //------------connect a few signals
+ connect( bg, SIGNAL(clicked(int)),
+ this, SLOT(slotRadioClicked(int)) );
+
+ KMSearchRuleWidget *srw = (KMSearchRuleWidget*)mRuleLister->mWidgetList.first();
+ if ( srw ) {
+ connect( srw, SIGNAL(fieldChanged(const QString &)),
+ this, SLOT(slotAutoNameHack()) );
+ connect( srw, SIGNAL(contentsChanged(const QString &)),
+ this, SLOT(slotAutoNameHack()) );
+ } else
+ kdDebug(5006) << "KMSearchPatternEdit: no first KMSearchRuleWidget, though slotClear() has been called!" << endl;
+}
+
+void KMSearchPatternEdit::setSearchPattern( KMSearchPattern *aPattern )
+{
+ assert( aPattern );
+
+ mRuleLister->setRuleList( aPattern );
+
+ mPattern = aPattern;
+
+ blockSignals(true);
+ if ( mPattern->op() == KMSearchPattern::OpOr )
+ mAnyRBtn->setChecked(true);
+ else
+ mAllRBtn->setChecked(true);
+ blockSignals(false);
+
+ setEnabled( true );
+}
+
+void KMSearchPatternEdit::setHeadersOnly( bool headersOnly )
+{
+ mRuleLister->setHeadersOnly( headersOnly );
+}
+
+void KMSearchPatternEdit::reset()
+{
+ mRuleLister->reset();
+
+ blockSignals(true);
+ mAllRBtn->setChecked( true );
+ blockSignals(false);
+
+ setEnabled( false );
+}
+
+void KMSearchPatternEdit::slotRadioClicked(int aIdx)
+{
+ if ( mPattern )
+ mPattern->setOp( (KMSearchPattern::Operator)aIdx );
+}
+
+void KMSearchPatternEdit::slotAutoNameHack()
+{
+ mRuleLister->regenerateRuleListFromWidgets();
+ emit maybeNameChanged();
+}
+
+#include "kmsearchpatternedit.moc"
diff --git a/kmail/kmsearchpatternedit.h b/kmail/kmsearchpatternedit.h
new file mode 100644
index 00000000..7a3c3614
--- /dev/null
+++ b/kmail/kmsearchpatternedit.h
@@ -0,0 +1,211 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+// kmfilterrulesedit.h
+// Author: Marc Mutz <Marc@Mutz.com>
+// This code is under GPL
+
+#ifndef KMFILTERRULESEDIT_H
+#define KMFILTERRULESEDIT_H
+
+#include "kwidgetlister.h"
+
+#include <qgroupbox.h>
+#include <qstringlist.h>
+
+class KMSearchRule;
+class KMSearchPattern;
+
+template <typename T> class QPtrList;
+class QString;
+class QComboBox;
+class QLineEdit;
+class QRadioButton;
+class QWidgetStack;
+class QLabel;
+class KMSearchPatternEdit;
+
+/** A widget to edit a single KMSearchRule.
+ It consists of an editable QComboBox for the field,
+ a read-only QComboBox for the function and
+ a QLineEdit for the content or the pattern (in case of regexps).
+ It manages the i18n itself, so field name should be in it's english form.
+
+ To use, you essentially give it the reference to a KMSearchRule and
+ it does the rest. It will never delete the rule itself, as it assumes
+ that something outside of it manages this.
+
+ @short A widget to edit a single KMSearchRule.
+ @author Marc Mutz <Marc@Mutz.com>
+*/
+
+class KMSearchRuleWidget : public QWidget
+{
+ Q_OBJECT
+public:
+ /** Constructor. You can give a KMSearchRule as parameter, which will
+ be used to initialize the widget. */
+ KMSearchRuleWidget( QWidget* parent=0, KMSearchRule* aRule=0, const char* name=0, bool headersOnly = false, bool absoluteDates = false );
+
+ enum { Message, Body, AnyHeader, Recipients, Size, AgeInDays, Status };
+
+ /** Set whether only header fields can be searched. If @p is true only
+ header fields can be searched otherwise \<message\> and \<body\> searches
+ are available also. */
+ void setHeadersOnly( bool headersOnly );
+ /** Set the rule. The rule is accepted regardless of the return
+ value of KMSearchRule::isEmpty. This widget makes a shallow
+ copy of @p aRule and operates directly on it. If @p aRule is
+ 0, resets itself, taks user input, but does essentially
+ nothing. If you pass 0, you should probably disable it. */
+ void setRule( KMSearchRule* aRule );
+ /** Return a reference to the currently-worked-on KMSearchRule. */
+ KMSearchRule* rule() const;
+ /** Resets the rule currently worked on and updates the widget
+ accordingly. */
+ void reset();
+ static int ruleFieldToId( const QString & i18nVal );
+
+public slots:
+ void slotFunctionChanged();
+ void slotValueChanged();
+
+signals:
+ /** This signal is emitted whenever the user alters the field. The
+ pseudo-headers <...> are returned in their i18n form, but stored
+ in their english form in the rule. */
+ void fieldChanged( const QString & );
+
+ /** This signal is emitted whenever the user alters the
+ contents/value of the rule. */
+ void contentsChanged( const QString & );
+
+protected:
+ /** Used internally to translate i18n-ized pseudo-headers back to
+ english. */
+ static QCString ruleFieldToEnglish(const QString & i18nVal);
+ /** Used internally to find the corresponding index into the field
+ ComboBox. Returns the index if found or -1 if the search failed, */
+ int indexOfRuleField( const QCString & aName ) const;
+
+protected slots:
+ void slotRuleFieldChanged( const QString & );
+
+private:
+ void initWidget();
+ void initFieldList( bool headersOnly, bool absoluteDates );
+
+ QStringList mFilterFieldList;
+ QComboBox *mRuleField;
+ QWidgetStack *mFunctionStack;
+ QWidgetStack *mValueStack;
+ bool mAbsoluteDates;
+};
+
+
+class KMSearchRuleWidgetLister : public KWidgetLister
+{
+ Q_OBJECT
+
+ friend class ::KMSearchPatternEdit;
+
+public:
+ KMSearchRuleWidgetLister( QWidget *parent=0, const char* name=0, bool headersOnly = false, bool absoluteDates = false );
+
+ virtual ~KMSearchRuleWidgetLister();
+
+ void setRuleList( QPtrList<KMSearchRule> * aList );
+ void setHeadersOnly( bool headersOnly );
+
+public slots:
+ void reset();
+
+protected:
+ virtual void clearWidget( QWidget *aWidget );
+ virtual QWidget* createWidget( QWidget *parent );
+
+private:
+ void regenerateRuleListFromWidgets();
+ QPtrList<KMSearchRule> *mRuleList;
+ bool mHeadersOnly;
+ bool mAbsoluteDates;
+};
+
+
+/** This widget is intended to be used in the filter configuration as
+ well as in the message search dialogs. It consists of a frame,
+ inside which there are placed two radio buttons entitled "Match
+ {all,any} of the following", followed by a vertical stack of
+ KMSearchRuleWidgets (initially two) and two buttons to add and
+ remove, resp., additional KMSearchWidget 's.
+
+ To set the widget according to a given KMSearchPattern, use
+ setSearchPattern; to initialize it (e.g. for a new, virgin
+ rule), use setSearchPattern with a 0 argument. The widget
+ operates directly on a shallow(!) copy of the search rule. So
+ while you actually don't really need searchPattern, because
+ you can always store a pointer to the current pattern yourself,
+ you must not modify the currently-worked-on pattern yourself while
+ this widget holds a reference to it. The only exceptions are:
+
+ @li If you edit a derived class, you can change aspects of the
+ class that don't interfere with the KMSearchPattern part. An
+ example is KMFilter, whose actions you can still edit while
+ the KMSearchPattern part of it is being acted upon by this
+ widget.
+
+ @li You can change the name of the pattern, but only using (this
+ widget's) setName. You cannot change the pattern's name
+ directly, although this widget in itself doesn't let the user
+ change it. This is because it auto-names the pattern to
+ "<$field>:$contents" iff the pattern begins with "<".
+
+ @short A widget which allows editing a set of KMSearchRule's.
+ @author Marc Mutz <Marc@Mutz.com>
+*/
+
+class KMSearchPatternEdit : public QGroupBox {
+ Q_OBJECT
+public:
+ /** Constructor. The parent and name parameters are passed to the underlying
+ QGroupBox, as usual. */
+ KMSearchPatternEdit(QWidget *parent=0, const char *name=0, bool headersOnly = false, bool absoluteDates = false);
+ /** Constructor. This one allows you to set a title different from
+ i18n("Search Criteria"). */
+ KMSearchPatternEdit(const QString & title, QWidget *parent=0, const char *name=0, bool headersOnly = false, bool absoluteDates = false);
+ ~KMSearchPatternEdit();
+
+ /** Set the search pattern. Rules are inserted regardless of the
+ return value of each rules' KMSearchRule::isEmpty. This
+ widget makes a shallow copy of @p aPattern and operates directly
+ on it. */
+ void setSearchPattern( KMSearchPattern* aPattern );
+ /** Set whether only header fields can be searched. If @p is true only
+ header fields can be searched otherwise \<message\> and \<body\> searches
+ are available also. */
+ void setHeadersOnly( bool headersOnly );
+
+ /** Updates the search pattern according to the current widget values */
+ void updateSearchPattern() { mRuleLister->regenerateRuleListFromWidgets(); }
+
+public slots:
+ /** Called when the widget should let go of the currently referenced
+ filter and disable itself. */
+ void reset();
+
+signals:
+ /** This signal is emitted whenever the name of the processed
+ search pattern may have changed. */
+ void maybeNameChanged();
+
+private slots:
+ void slotRadioClicked(int aIdx);
+ void slotAutoNameHack();
+
+private:
+ void initLayout( bool headersOnly, bool absoluteDates );
+
+ KMSearchPattern *mPattern;
+ QRadioButton *mAllRBtn, *mAnyRBtn;
+ KMSearchRuleWidgetLister *mRuleLister;
+};
+
+#endif
diff --git a/kmail/kmsender.cpp b/kmail/kmsender.cpp
new file mode 100644
index 00000000..fc2bfba1
--- /dev/null
+++ b/kmail/kmsender.cpp
@@ -0,0 +1,1231 @@
+// kmsender.cpp
+
+#include <config.h>
+
+#define REALLY_WANT_KMSENDER
+#include "kmsender.h"
+#include "kmsender_p.h"
+#undef REALLY_WANT_KMSENDER
+
+#include <kmime_header_parsing.h>
+using namespace KMime::Types;
+
+#include <kio/passdlg.h>
+#include <kio/scheduler.h>
+#include <kapplication.h>
+#include <kmessagebox.h>
+#include <kdeversion.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kconfig.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include "globalsettings.h"
+#include "kmfiltermgr.h"
+
+#include "kcursorsaver.h"
+#include <libkpimidentities/identity.h>
+#include <libkpimidentities/identitymanager.h>
+#include "progressmanager.h"
+#include "kmaccount.h"
+#include "kmtransport.h"
+#include "kmfolderindex.h"
+#include "kmfoldermgr.h"
+#include "kmmsgdict.h"
+#include "kmmsgpart.h"
+#include "protocols.h"
+#include "kmcommands.h"
+#include <mimelib/mediatyp.h>
+#include <mimelib/enum.h>
+#include <mimelib/param.h>
+
+#define SENDER_GROUP "sending mail"
+
+//-----------------------------------------------------------------------------
+KMSender::KMSender()
+ : mOutboxFolder( 0 ), mSentFolder( 0 )
+{
+ mPrecommand = 0;
+ mSendProc = 0;
+ mSendProcStarted = false;
+ mSendInProgress = false;
+ mCurrentMsg = 0;
+ mTransportInfo = new KMTransportInfo();
+ readConfig();
+ mSendAborted = false;
+ mSentMessages = 0;
+ mTotalMessages = 0;
+ mFailedMessages = 0;
+ mSentBytes = 0;
+ mTotalBytes = 0;
+ mProgressItem = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+KMSender::~KMSender()
+{
+ writeConfig(false);
+ delete mSendProc;
+ delete mPrecommand;
+ delete mTransportInfo;
+}
+
+//-----------------------------------------------------------------------------
+void KMSender::setStatusMsg(const QString &msg)
+{
+ if ( mProgressItem )
+ mProgressItem->setStatus(msg);
+}
+
+//-----------------------------------------------------------------------------
+void KMSender::readConfig(void)
+{
+ QString str;
+ KConfigGroup config(KMKernel::config(), SENDER_GROUP);
+
+ mSendImmediate = config.readBoolEntry("Immediate", true);
+ mSendQuotedPrintable = config.readBoolEntry("Quoted-Printable", true);
+}
+
+
+//-----------------------------------------------------------------------------
+void KMSender::writeConfig(bool aWithSync)
+{
+ KConfigGroup config(KMKernel::config(), SENDER_GROUP);
+
+ config.writeEntry("Immediate", mSendImmediate);
+ config.writeEntry("Quoted-Printable", mSendQuotedPrintable);
+
+ if (aWithSync) config.sync();
+}
+
+
+//-----------------------------------------------------------------------------
+bool KMSender::settingsOk() const
+{
+ if (KMTransportInfo::availableTransports().isEmpty())
+ {
+ KMessageBox::information(0,i18n("Please create an account for sending and try again."));
+ return false;
+ }
+ return true;
+}
+
+static void handleRedirections( KMMessage * m ) {
+ const QString from = m->headerField("X-KMail-Redirect-From");
+ const QString msgId = m->msgId();
+ if( from.isEmpty() || msgId.isEmpty() )
+ m->setMsgId( KMMessage::generateMessageId( m->sender() ) );
+}
+
+//-----------------------------------------------------------------------------
+bool KMSender::doSend(KMMessage* aMsg, short sendNow)
+{
+ if(!aMsg)
+ return false;
+
+ if (!settingsOk()) return false;
+
+ if (aMsg->to().isEmpty())
+ {
+ // RFC822 says:
+ // Note that the "Bcc" field may be empty, while the "To" field is required to
+ // have at least one address.
+ //
+ // however:
+ //
+ // The following string is accepted according to RFC 2822,
+ // section 3.4 "Address Specification" where they say:
+ //
+ // "An address may either be an individual mailbox,
+ // or a group of mailboxes."
+ // and:
+ // "group + display-name ":" [mailbox-list / CFWS] ";"
+ // [CFWS]"
+ //
+ // In this syntax our "undisclosed-recipients: ;"
+ // just specifies an empty group.
+ //
+ // In further explanations RFC 2822 states that it *is*
+ // allowed to have a ZERO number of mailboxes in the "mailbox-list".
+ aMsg->setTo("Undisclosed.Recipients: ;");
+ }
+
+ handleRedirections( aMsg );
+
+ if (sendNow==-1) sendNow = mSendImmediate;
+
+ KMFolder * const outbox = kmkernel->outboxFolder();
+ const KMFolderOpener openOutbox( outbox, "outbox" );
+
+ aMsg->setStatus(KMMsgStatusQueued);
+
+ if ( const int err = outbox->addMsg(aMsg) ) {
+ Q_UNUSED( err );
+ KMessageBox::information(0,i18n("Cannot add message to outbox folder"));
+ return false;
+ }
+
+ //Ensure the message is correctly and fully parsed
+
+ /* The above was added by Marc and seems to be necessary to ensure
+ * the mail is in a sane state before sending. The unGet makes the
+ * attached unencrypted version of the mail (if there is one ) disappear.
+ * though, so we need to make sure to keep it around and restore it
+ * afterwards. The real fix would be to replace the unGet with
+ * whatever parsing is triggered by it, but I'm too chicken to do that,
+ * in this branch.
+ * Note that the unencrypted mail will be lost if the mail remains in
+ * the outbox across a restart anyhow, but that never worked, afaikt. */
+ const int idx = outbox->count() - 1;
+ KMMessage * const unencryptedMsg = aMsg->unencryptedMsg();
+ outbox->unGetMsg( idx );
+ KMMessage * const tempMsg = outbox->getMsg( idx );
+ tempMsg->setUnencryptedMsg( unencryptedMsg );
+
+ if ( !sendNow || mSendInProgress )
+ return true;
+
+ return sendQueued();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMSender::outboxMsgAdded(int idx)
+{
+ ++mTotalMessages;
+ KMMsgBase* msg = kmkernel->outboxFolder()->getMsgBase(idx);
+ Q_ASSERT(msg);
+ if ( msg )
+ mTotalBytes += msg->msgSize();
+}
+
+
+//-----------------------------------------------------------------------------
+bool KMSender::doSendQueued( const QString &customTransport )
+{
+ if (!settingsOk()) return false;
+
+ if (mSendInProgress)
+ {
+ return false;
+ }
+
+ // open necessary folders
+ mOutboxFolder = kmkernel->outboxFolder();
+ mOutboxFolder->open("dosendoutbox");
+ mTotalMessages = mOutboxFolder->count();
+ if (mTotalMessages == 0) {
+ // Nothing in the outbox. We are done.
+ mOutboxFolder->close("dosendoutbox");
+ mOutboxFolder = 0;
+ return true;
+ }
+ mTotalBytes = 0;
+ for( int i = 0 ; i<mTotalMessages ; ++i )
+ mTotalBytes += mOutboxFolder->getMsgBase(i)->msgSize();
+
+ connect( mOutboxFolder, SIGNAL(msgAdded(int)),
+ this, SLOT(outboxMsgAdded(int)) );
+ mCurrentMsg = 0;
+
+ mSentFolder = kmkernel->sentFolder();
+ mSentFolder->open("dosendsent");
+ kmkernel->filterMgr()->ref();
+
+ // start sending the messages
+ mCustomTransport = customTransport;
+ doSendMsg();
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+void KMSender::emitProgressInfo( int currentFileProgress )
+{
+ int percent = (mTotalBytes) ? ( 100 * (mSentBytes+currentFileProgress) / mTotalBytes ) : 0;
+ if (percent > 100) percent = 100;
+ mProgressItem->setProgress(percent);
+}
+
+static bool messageIsDispositionNotificationReport( KMMessage *msg )
+{
+ if ( msg->type() == DwMime::kTypeMessage &&
+ msg->subtype() == DwMime::kSubtypeDispositionNotification )
+ return true;
+
+ if ( msg->type() != DwMime::kTypeMultipart ||
+ msg->subtype() != DwMime::kSubtypeReport )
+ return false;
+
+ DwMediaType& ct = msg->dwContentType();
+ DwParameter *param = ct.FirstParameter();
+ while( param ) {
+ if ( !qstricmp( param->Attribute().c_str(), "report-type")
+ && !qstricmp( param->Value().c_str(), "disposition-notification" ) )
+ return true;
+ else
+ param = param->Next();
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+void KMSender::doSendMsg()
+{
+ if (!kmkernel) //To handle message sending in progress when kaplan is exited
+ return; //TODO: handle this case better
+
+ const bool someSent = mCurrentMsg;
+ if (someSent) {
+ mSentMessages++;
+ mSentBytes += mCurrentMsg->msgSize();
+ }
+
+ // Post-process sent message (filtering)
+ KMFolder *sentFolder = 0, *imapSentFolder = 0;
+ if (mCurrentMsg && kmkernel->filterMgr())
+ {
+ mCurrentMsg->setTransferInProgress( false );
+ if( mCurrentMsg->hasUnencryptedMsg() ) {
+ kdDebug(5006) << "KMSender::doSendMsg() post-processing: replace mCurrentMsg body by unencryptedMsg data" << endl;
+ // delete all current body parts
+ mCurrentMsg->deleteBodyParts();
+ // copy Content-[..] headers from unencrypted message to current one
+ KMMessage & newMsg( *mCurrentMsg->unencryptedMsg() );
+ mCurrentMsg->dwContentType() = newMsg.dwContentType();
+ mCurrentMsg->setContentTransferEncodingStr( newMsg.contentTransferEncodingStr() );
+ QCString newDispo = newMsg.headerField("Content-Disposition").latin1();
+ if( newDispo.isEmpty() )
+ mCurrentMsg->removeHeaderField( "Content-Disposition" );
+ else
+ mCurrentMsg->setHeaderField( "Content-Disposition", newDispo );
+ // copy the body
+ mCurrentMsg->setBody( newMsg.body() );
+ // copy all the body parts
+ KMMessagePart msgPart;
+ for( int i = 0; i < newMsg.numBodyParts(); ++i ) {
+ newMsg.bodyPart( i, &msgPart );
+ mCurrentMsg->addBodyPart( &msgPart );
+ }
+ }
+ mCurrentMsg->setStatus(KMMsgStatusSent);
+ mCurrentMsg->setStatus(KMMsgStatusRead); // otherwise it defaults to new on imap
+ mCurrentMsg->updateAttachmentState();
+
+ const KPIM::Identity & id = kmkernel->identityManager()
+ ->identityForUoidOrDefault( mCurrentMsg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt() );
+ if ( !mCurrentMsg->fcc().isEmpty() )
+ {
+ sentFolder = kmkernel->folderMgr()->findIdString( mCurrentMsg->fcc() );
+ if ( sentFolder == 0 )
+ // This is *NOT* supposed to be imapSentFolder!
+ sentFolder =
+ kmkernel->dimapFolderMgr()->findIdString( mCurrentMsg->fcc() );
+ if ( sentFolder == 0 )
+ imapSentFolder =
+ kmkernel->imapFolderMgr()->findIdString( mCurrentMsg->fcc() );
+ }
+ // No, or no usable sentFolder, and no, or no usable imapSentFolder,
+ // let's try the on in the identity
+ if ( ( sentFolder == 0 || sentFolder->isReadOnly() )
+ && ( imapSentFolder == 0 || imapSentFolder->isReadOnly() )
+ && !id.fcc().isEmpty() )
+ {
+ sentFolder = kmkernel->folderMgr()->findIdString( id.fcc() );
+ if ( sentFolder == 0 )
+ // This is *NOT* supposed to be imapSentFolder!
+ sentFolder = kmkernel->dimapFolderMgr()->findIdString( id.fcc() );
+ if ( sentFolder == 0 )
+ imapSentFolder = kmkernel->imapFolderMgr()->findIdString( id.fcc() );
+ }
+ if (imapSentFolder
+ && ( imapSentFolder->noContent() || imapSentFolder->isReadOnly() ) )
+ imapSentFolder = 0;
+
+ if ( sentFolder == 0 || sentFolder->isReadOnly() )
+ sentFolder = kmkernel->sentFolder();
+
+ if ( sentFolder ) {
+ if ( const int err = sentFolder->open("sentFolder") ) {
+ Q_UNUSED( err );
+ cleanup();
+ return;
+ }
+ }
+
+ // Disable the emitting of msgAdded signal, because the message is taken out of the
+ // current folder (outbox) and re-added, to make filter actions changing the message
+ // work. We don't want that to screw up message counts.
+ if ( mCurrentMsg->parent() ) mCurrentMsg->parent()->quiet( true );
+ const int processResult = kmkernel->filterMgr()->process(mCurrentMsg,KMFilterMgr::Outbound);
+ if ( mCurrentMsg->parent() ) mCurrentMsg->parent()->quiet( false );
+
+ // 0==processed ok, 1==no filter matched, 2==critical error, abort!
+ switch (processResult) {
+ case 2:
+ perror("Critical error: Unable to process sent mail (out of space?)");
+ KMessageBox::information(0, i18n("Critical error: "
+ "Unable to process sent mail (out of space?)"
+ "Moving failing message to \"sent-mail\" folder."));
+ if ( sentFolder ) {
+ sentFolder->moveMsg(mCurrentMsg);
+ sentFolder->close("sentFolder");
+ }
+ cleanup();
+ return;
+ case 1:
+ if ( sentFolder && sentFolder->moveMsg(mCurrentMsg) != 0 )
+ {
+ KMessageBox::error(0, i18n("Moving the sent message \"%1\" from the "
+ "\"outbox\" to the \"sent-mail\" folder failed.\n"
+ "Possible reasons are lack of disk space or write permission. "
+ "Please try to fix the problem and move the message manually.")
+ .arg(mCurrentMsg->subject()));
+ cleanup();
+ return;
+ }
+ if (imapSentFolder) {
+ // Does proper folder refcounting and message locking
+ KMCommand *command = new KMMoveCommand( imapSentFolder, mCurrentMsg );
+ command->keepFolderOpen( sentFolder ); // will open it, and close it once done
+ command->start();
+ }
+ default:
+ break;
+ }
+ setStatusByLink( mCurrentMsg );
+ if (mCurrentMsg->parent() && !imapSentFolder) {
+ // for speed optimization, this code assumes that mCurrentMsg is the
+ // last one in it's parent folder; make sure that's really the case:
+ assert( mCurrentMsg->parent()->find( mCurrentMsg )
+ == mCurrentMsg->parent()->count() - 1 );
+ // unGet this message:
+ mCurrentMsg->parent()->unGetMsg( mCurrentMsg->parent()->count() -1 );
+ }
+
+ mCurrentMsg = 0;
+ }
+
+ // See if there is another queued message
+ mCurrentMsg = mOutboxFolder->getMsg(mFailedMessages);
+ if ( mCurrentMsg && !mCurrentMsg->transferInProgress() &&
+ mCurrentMsg->sender().isEmpty() ) {
+ // if we do not have a sender address then use the email address of the
+ // message's identity or of the default identity unless those two are also
+ // empty
+ const KPIM::Identity & id = kmkernel->identityManager()
+ ->identityForUoidOrDefault( mCurrentMsg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt() );
+ if ( !id.emailAddr().isEmpty() ) {
+ mCurrentMsg->setFrom( id.fullEmailAddr() );
+ }
+ else if ( !kmkernel->identityManager()->defaultIdentity().emailAddr().isEmpty() ) {
+ mCurrentMsg->setFrom( kmkernel->identityManager()->defaultIdentity().fullEmailAddr() );
+ }
+ else {
+ KMessageBox::sorry( 0, i18n( "It's not possible to send messages "
+ "without specifying a sender address.\n"
+ "Please set the email address of "
+ "identity '%1' in the Identities "
+ "section of the configuration dialog "
+ "and then try again." )
+ .arg( id.identityName() ) );
+ mOutboxFolder->unGetMsg( mFailedMessages );
+ mCurrentMsg = 0;
+ }
+ }
+ if (!mCurrentMsg || mCurrentMsg->transferInProgress())
+ {
+ // a message is locked finish the send
+ if (mCurrentMsg && mCurrentMsg->transferInProgress())
+ mCurrentMsg = 0;
+ // no more message: cleanup and done
+ if ( sentFolder != 0 )
+ sentFolder->close("sentFolder");
+ if ( someSent ) {
+ if ( mSentMessages == mTotalMessages ) {
+ setStatusMsg(i18n("%n queued message successfully sent.",
+ "%n queued messages successfully sent.",
+ mSentMessages));
+ } else {
+ setStatusMsg(i18n("%1 of %2 queued messages successfully sent.")
+ .arg(mSentMessages).arg( mTotalMessages ));
+ }
+ }
+ cleanup();
+ return;
+ }
+ mCurrentMsg->setTransferInProgress( true );
+
+ // start the sender process or initialize communication
+ if (!mSendInProgress)
+ {
+ Q_ASSERT( !mProgressItem );
+ mProgressItem = KPIM::ProgressManager::createProgressItem(
+ "Sender",
+ i18n( "Sending messages" ),
+ i18n("Initiating sender process..."),
+ true );
+ connect( mProgressItem, SIGNAL(progressItemCanceled(KPIM::ProgressItem*)),
+ this, SLOT( slotAbortSend() ) );
+ kapp->ref();
+ mSendInProgress = true;
+ }
+
+ QString msgTransport = mCustomTransport;
+ if ( msgTransport.isEmpty() ) {
+ msgTransport = mCurrentMsg->headerField("X-KMail-Transport");
+ }
+ if ( msgTransport.isEmpty() ) {
+ const QStringList sl = KMTransportInfo::availableTransports();
+ if (!sl.empty()) msgTransport = sl.front();
+ }
+
+ if (!mSendProc || msgTransport != mMethodStr) {
+ if (mSendProcStarted && mSendProc) {
+ mSendProc->finish();
+ mSendProcStarted = false;
+ }
+
+ mSendProc = createSendProcFromString(msgTransport);
+ mMethodStr = msgTransport;
+
+ if( mTransportInfo->encryption == "TLS" || mTransportInfo->encryption == "SSL" ) {
+ mProgressItem->setUsesCrypto( true );
+ } else if ( !mCustomTransport.isEmpty() ) {
+ int result = KMessageBox::warningContinueCancel( 0,
+ i18n( "You have chosen to send all queued email using an unencrypted transport, do you want to continue? "),
+ i18n( "Security Warning" ),
+ i18n( "Send Unencrypted" ),
+ "useCustomTransportWithoutAsking", false);
+
+ if( result == KMessageBox::Cancel ) {
+ mProgressItem->cancel();
+ mProgressItem->setComplete();
+ slotAbortSend();
+ cleanup();
+ return;
+ }
+ }
+
+ if (!mSendProc)
+ sendProcStarted(false);
+ else {
+ connect(mSendProc, SIGNAL(idle()), SLOT(slotIdle()));
+ connect(mSendProc, SIGNAL(started(bool)), SLOT(sendProcStarted(bool)));
+
+ // Run the precommand if there is one
+ if ( !mTransportInfo->precommand.isEmpty() ) {
+ runPrecommand( mTransportInfo->precommand );
+ return;
+ }
+
+ mSendProc->start();
+ }
+ }
+ else if (!mSendProcStarted)
+ mSendProc->start();
+ else
+ doSendMsgAux();
+}
+
+bool KMSender::runPrecommand( const QString & cmd ) {
+ setStatusMsg( i18n("Executing precommand %1").arg( cmd ) );
+ mPrecommand = new KMPrecommand( cmd );
+ connect( mPrecommand, SIGNAL(finished(bool)),
+ SLOT(slotPrecommandFinished(bool)) );
+ if ( !mPrecommand->start() ) {
+ delete mPrecommand; mPrecommand = 0;
+ return false;
+ }
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+void KMSender::sendProcStarted(bool success)
+{
+ if (!success) {
+ if (mSendProc)
+ mSendProc->finish();
+ else
+ setStatusMsg(i18n("Unrecognized transport protocol. Unable to send message."));
+ mSendProc = 0;
+ mSendProcStarted = false;
+ cleanup();
+ return;
+ }
+ doSendMsgAux();
+}
+
+
+static QStringList addrSpecListToStringList( const AddrSpecList & l, bool allowEmpty=false ) {
+ QStringList result;
+ for ( AddrSpecList::const_iterator it = l.begin(), end = l.end() ; it != end ; ++it ) {
+ const QString s = (*it).asString();
+ if ( allowEmpty || !s.isEmpty() )
+ result.push_back( s );
+ }
+ return result;
+}
+
+static void extractSenderToCCAndBcc( KMMessage * aMsg, QString * sender, QStringList * to, QStringList * cc, QStringList * bcc ) {
+ if ( sender ) *sender = aMsg->sender();
+ if( !aMsg->headerField("X-KMail-Recipients").isEmpty() ) {
+ // extended BCC handling to prevent TOs and CCs from seeing
+ // BBC information by looking at source of an OpenPGP encrypted mail
+ if ( to ) *to = addrSpecListToStringList( aMsg->extractAddrSpecs( "X-KMail-Recipients" ) );
+ aMsg->removeHeaderField( "X-KMail-Recipients" );
+ } else {
+ if ( to ) *to = addrSpecListToStringList( aMsg->extractAddrSpecs( "To" ) );
+ if ( cc ) *cc = addrSpecListToStringList( aMsg->extractAddrSpecs( "Cc" ) );
+ if ( bcc ) *bcc = addrSpecListToStringList( aMsg->extractAddrSpecs( "Bcc" ) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMSender::doSendMsgAux()
+{
+ mSendProcStarted = true;
+
+ // start sending the current message
+
+ setStatusMsg(i18n("%3: subject of message","Sending message %1 of %2: %3")
+ .arg(mSentMessages+mFailedMessages+1).arg(mTotalMessages)
+ .arg(mCurrentMsg->subject()));
+ QStringList to, cc, bcc;
+ QString sender;
+ extractSenderToCCAndBcc( mCurrentMsg, &sender, &to, &cc, &bcc );
+
+ // MDNs are required to have an empty envelope from as per RFC2298.
+ if ( messageIsDispositionNotificationReport( mCurrentMsg ) && GlobalSettings::self()->sendMDNsWithEmptySender() )
+ sender = "<>";
+
+ const QByteArray message = mCurrentMsg->asSendableString();
+ if ( sender.isEmpty() || !mSendProc->send( sender, to, cc, bcc, message ) ) {
+ if ( mCurrentMsg )
+ mCurrentMsg->setTransferInProgress( false );
+ if ( mOutboxFolder )
+ mOutboxFolder->unGetMsg( mFailedMessages );
+ mCurrentMsg = 0;
+ cleanup();
+ setStatusMsg(i18n("Failed to send (some) queued messages."));
+ return;
+ }
+ // Do *not* add code here, after send(). It can happen that this method
+ // is called recursively if send() emits the idle signal directly.
+}
+
+
+//-----------------------------------------------------------------------------
+void KMSender::cleanup(void)
+{
+ kdDebug(5006) << k_funcinfo << endl;
+ if (mSendProc && mSendProcStarted) mSendProc->finish();
+ mSendProc = 0;
+ mSendProcStarted = false;
+ if (mSendInProgress) kapp->deref();
+ mSendInProgress = false;
+ if (mCurrentMsg)
+ {
+ mCurrentMsg->setTransferInProgress( false );
+ mCurrentMsg = 0;
+ }
+ if ( mSentFolder ) {
+ mSentFolder->close("dosendsent");
+ mSentFolder = 0;
+ }
+ if ( mOutboxFolder ) {
+ disconnect( mOutboxFolder, SIGNAL(msgAdded(int)),
+ this, SLOT(outboxMsgAdded(int)) );
+ mOutboxFolder->close("dosendoutbox");
+ if ( mOutboxFolder->count( true ) == 0 ) {
+ mOutboxFolder->expunge();
+ }
+ else if ( mOutboxFolder->needsCompacting() ) {
+ mOutboxFolder->compact( KMFolder::CompactSilentlyNow );
+ }
+ mOutboxFolder = 0;
+ }
+
+ mSendAborted = false;
+ mSentMessages = 0;
+ mFailedMessages = 0;
+ mSentBytes = 0;
+ if ( mProgressItem )
+ mProgressItem->setComplete();
+ mProgressItem = 0;
+ kmkernel->filterMgr()->deref();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMSender::slotAbortSend()
+{
+ mSendAborted = true;
+ delete mPrecommand;
+ mPrecommand = 0;
+ if (mSendProc) mSendProc->abort();
+}
+
+//-----------------------------------------------------------------------------
+void KMSender::slotIdle()
+{
+ assert(mSendProc != 0);
+
+ QString msg;
+ QString errString;
+ if (mSendProc)
+ errString = mSendProc->lastErrorMessage();
+
+ if (mSendAborted) {
+ // sending of message aborted
+ if ( mCurrentMsg ) {
+ mCurrentMsg->setTransferInProgress( false );
+ if ( mOutboxFolder )
+ mOutboxFolder->unGetMsg( mFailedMessages );
+ mCurrentMsg = 0;
+ }
+ msg = i18n("Sending aborted:\n%1\n"
+ "The message will stay in the 'outbox' folder until you either "
+ "fix the problem (e.g. a broken address) or remove the message "
+ "from the 'outbox' folder.\n"
+ "The following transport protocol was used:\n %2")
+ .arg(errString)
+ .arg(mMethodStr);
+ if (!errString.isEmpty()) KMessageBox::error(0,msg);
+ setStatusMsg( i18n( "Sending aborted." ) );
+ } else {
+ if (!mSendProc->sendOk()) {
+ if ( mCurrentMsg )
+ mCurrentMsg->setTransferInProgress( false );
+ if ( mOutboxFolder )
+ mOutboxFolder->unGetMsg( mFailedMessages );
+ mCurrentMsg = 0;
+ mFailedMessages++;
+ // reset cached password
+ QMapIterator <QString,QString> pc;
+ if ( (pc = mPasswdCache.find( mMethodStr )) != mPasswdCache.end() ) {
+ mPasswdCache.erase(pc);
+ }
+ // Sending of message failed.
+ if (!errString.isEmpty()) {
+ int res = KMessageBox::Yes;
+ if (mSentMessages+mFailedMessages != mTotalMessages) {
+ msg = i18n("<p>Sending failed:</p>"
+ "<p>%1</p>"
+ "<p>The message will stay in the 'outbox' folder until you either "
+ "fix the problem (e.g. a broken address) or remove the message "
+ "from the 'outbox' folder.</p>"
+ "<p>The following transport protocol was used: %2</p>"
+ "<p>Do you want me to continue sending the remaining messages?</p>")
+ .arg(errString)
+ .arg(mMethodStr);
+ res = KMessageBox::warningYesNo( 0 , msg ,
+ i18n( "Continue Sending" ), i18n( "&Continue Sending" ),
+ i18n("&Abort Sending") );
+ } else {
+ msg = i18n("Sending failed:\n%1\n"
+ "The message will stay in the 'outbox' folder until you either "
+ "fix the problem (e.g. a broken address) or remove the message "
+ "from the 'outbox' folder.\n"
+ "The following transport protocol was used:\n %2")
+ .arg(errString)
+ .arg(mMethodStr);
+ KMessageBox::error(0,msg);
+ }
+ if (res == KMessageBox::Yes) {
+ // Try the next one.
+ doSendMsg();
+ return;
+ } else {
+ setStatusMsg( i18n( "Sending aborted." ) );
+ }
+ }
+ } else {
+ // Sending suceeded.
+ doSendMsg();
+ return;
+ }
+ }
+ mSendProc->finish();
+ mSendProc = 0;
+ mSendProcStarted = false;
+
+ cleanup();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMSender::slotPrecommandFinished(bool normalExit)
+{
+ delete mPrecommand;
+ mPrecommand = 0;
+ if (normalExit) mSendProc->start();
+ else slotIdle();
+}
+
+
+//-----------------------------------------------------------------------------
+void KMSender::setSendImmediate(bool aSendImmediate)
+{
+ mSendImmediate = aSendImmediate;
+}
+
+
+//-----------------------------------------------------------------------------
+void KMSender::setSendQuotedPrintable(bool aSendQuotedPrintable)
+{
+ mSendQuotedPrintable = aSendQuotedPrintable;
+}
+
+
+//-----------------------------------------------------------------------------
+KMSendProc* KMSender::createSendProcFromString( const QString & transport )
+{
+ mTransportInfo->type = QString::null;
+ int nr = KMTransportInfo::findTransport(transport);
+ if (nr)
+ {
+ mTransportInfo->readConfig(nr);
+ } else {
+ if (transport.startsWith("smtp://")) // should probably use KURL and SMTP_PROTOCOL
+ {
+ mTransportInfo->type = "smtp";
+ mTransportInfo->auth = false;
+ mTransportInfo->encryption = "NONE";
+ QString serverport = transport.mid(7);
+ int colon = serverport.find(':');
+ if (colon != -1) {
+ mTransportInfo->host = serverport.left(colon);
+ mTransportInfo->port = serverport.mid(colon + 1);
+ } else {
+ mTransportInfo->host = serverport;
+ mTransportInfo->port = "25";
+ }
+ } else
+ if (transport.startsWith("smtps://")) // should probably use KURL and SMTPS_PROTOCOL
+ {
+ mTransportInfo->type = "smtps";
+ mTransportInfo->auth = false;
+ mTransportInfo->encryption = "ssl";
+ QString serverport = transport.mid(7);
+ int colon = serverport.find(':');
+ if (colon != -1) {
+ mTransportInfo->host = serverport.left(colon);
+ mTransportInfo->port = serverport.mid(colon + 1);
+ } else {
+ mTransportInfo->host = serverport;
+ mTransportInfo->port = "465";
+ }
+ }
+ else if (transport.startsWith("file://"))
+ {
+ mTransportInfo->type = "sendmail";
+ mTransportInfo->host = transport.mid(7);
+ }
+ }
+ // strip off a trailing "/"
+ while (mTransportInfo->host.endsWith("/")) {
+ mTransportInfo->host.truncate(mTransportInfo->host.length()-1);
+ }
+
+
+ if (mTransportInfo->type == "sendmail")
+ return new KMSendSendmail(this);
+ if (mTransportInfo->type == "smtp" || mTransportInfo->type == "smtps")
+ return new KMSendSMTP(this);
+
+ return 0L;
+}
+
+//-----------------------------------------------------------------------------
+void KMSender::setStatusByLink(const KMMessage *aMsg)
+{
+ int n = 0;
+ while (1) {
+ ulong msn;
+ KMMsgStatus status;
+ aMsg->getLink(n, &msn, &status);
+ if (!msn || !status)
+ break;
+ n++;
+
+ KMFolder *folder = 0;
+ int index = -1;
+ KMMsgDict::instance()->getLocation(msn, &folder, &index);
+ if (folder && index != -1) {
+ KMFolderOpener openFolder(folder, "setstatus");
+ if ( status == KMMsgStatusDeleted ) {
+ // Move the message to the trash folder
+ KMDeleteMsgCommand *cmd =
+ new KMDeleteMsgCommand( folder, folder->getMsg( index ) );
+ cmd->start();
+ } else {
+ folder->setStatus(index, status);
+ }
+ } else {
+ kdWarning(5006) << k_funcinfo << "Cannot update linked message, it could not be found!" << endl;
+ }
+ }
+}
+
+//=============================================================================
+//=============================================================================
+KMSendProc::KMSendProc( KMSender * sender )
+ : QObject( 0 ),
+ mSender( sender ),
+ mLastErrorMessage(),
+ mSendOk( false ),
+ mSending( false )
+{
+}
+
+//-----------------------------------------------------------------------------
+void KMSendProc::reset()
+{
+ mSending = false;
+ mSendOk = false;
+ mLastErrorMessage = QString::null;
+}
+
+//-----------------------------------------------------------------------------
+void KMSendProc::failed(const QString &aMsg)
+{
+ mSending = false;
+ mSendOk = false;
+ mLastErrorMessage = aMsg;
+}
+
+//-----------------------------------------------------------------------------
+void KMSendProc::statusMsg(const QString& aMsg)
+{
+ if (mSender) mSender->setStatusMsg(aMsg);
+}
+
+//=============================================================================
+//=============================================================================
+KMSendSendmail::KMSendSendmail( KMSender * sender )
+ : KMSendProc( sender ),
+ mMsgStr(),
+ mMsgPos( 0 ),
+ mMsgRest( 0 ),
+ mMailerProc( 0 )
+{
+
+}
+
+KMSendSendmail::~KMSendSendmail() {
+ delete mMailerProc; mMailerProc = 0;
+}
+
+bool KMSendSendmail::doStart() {
+
+ if (mSender->transportInfo()->host.isEmpty())
+ {
+ const QString str = i18n("Please specify a mailer program in the settings.");
+ const QString msg = i18n("Sending failed:\n%1\n"
+ "The message will stay in the 'outbox' folder and will be resent.\n"
+ "Please remove it from there if you do not want the message to "
+ "be resent.\n"
+ "The following transport protocol was used:\n %2")
+ .arg(str + "\n")
+ .arg("sendmail://");
+ KMessageBox::information(0,msg);
+ return false;
+ }
+
+ if (!mMailerProc)
+ {
+ mMailerProc = new KProcess;
+ assert(mMailerProc != 0);
+ connect(mMailerProc,SIGNAL(processExited(KProcess*)),
+ this, SLOT(sendmailExited(KProcess*)));
+ connect(mMailerProc,SIGNAL(wroteStdin(KProcess*)),
+ this, SLOT(wroteStdin(KProcess*)));
+ connect(mMailerProc,SIGNAL(receivedStderr(KProcess*,char*,int)),
+ this, SLOT(receivedStderr(KProcess*, char*, int)));
+ }
+ return true;
+}
+
+void KMSendSendmail::doFinish() {
+ delete mMailerProc;
+ mMailerProc = 0;
+}
+
+void KMSendSendmail::abort()
+{
+ delete mMailerProc;
+ mMailerProc = 0;
+ mSendOk = false;
+ mMsgStr = 0;
+ idle();
+}
+
+bool KMSendSendmail::doSend( const QString & sender, const QStringList & to, const QStringList & cc, const QStringList & bcc, const QByteArray & message ) {
+ mMailerProc->clearArguments();
+ *mMailerProc << mSender->transportInfo()->host
+ << "-i" << "-f" << sender
+ << to << cc << bcc ;
+
+ mMsgStr = message;
+
+ if ( !mMailerProc->start( KProcess::NotifyOnExit, KProcess::All ) ) {
+ KMessageBox::information( 0, i18n("Failed to execute mailer program %1")
+ .arg( mSender->transportInfo()->host ) );
+ return false;
+ }
+ mMsgPos = mMsgStr.data();
+ mMsgRest = mMsgStr.size();
+ wroteStdin( mMailerProc );
+
+ return true;
+}
+
+
+void KMSendSendmail::wroteStdin(KProcess *proc)
+{
+ char* str;
+ int len;
+
+ assert(proc!=0);
+ Q_UNUSED( proc );
+
+ str = mMsgPos;
+ len = (mMsgRest>1024 ? 1024 : mMsgRest);
+
+ if (len <= 0)
+ {
+ mMailerProc->closeStdin();
+ }
+ else
+ {
+ mMsgRest -= len;
+ mMsgPos += len;
+ mMailerProc->writeStdin(str,len);
+ // if code is added after writeStdin() KProcess probably initiates
+ // a race condition.
+ }
+}
+
+
+void KMSendSendmail::receivedStderr(KProcess *proc, char *buffer, int buflen)
+{
+ assert(proc!=0);
+ Q_UNUSED( proc );
+ mLastErrorMessage.replace(mLastErrorMessage.length(), buflen, buffer);
+}
+
+
+void KMSendSendmail::sendmailExited(KProcess *proc)
+{
+ assert(proc!=0);
+ mSendOk = (proc->normalExit() && proc->exitStatus()==0);
+ if (!mSendOk) failed(i18n("Sendmail exited abnormally."));
+ mMsgStr = 0;
+ emit idle();
+}
+
+
+
+//-----------------------------------------------------------------------------
+//=============================================================================
+//=============================================================================
+KMSendSMTP::KMSendSMTP(KMSender *sender)
+ : KMSendProc(sender),
+ mInProcess(false),
+ mJob(0),
+ mSlave(0)
+{
+ KIO::Scheduler::connect(SIGNAL(slaveError(KIO::Slave *, int,
+ const QString &)), this, SLOT(slaveError(KIO::Slave *, int,
+ const QString &)));
+}
+
+KMSendSMTP::~KMSendSMTP()
+{
+ if (mJob) mJob->kill();
+}
+
+bool KMSendSMTP::doSend( const QString & sender, const QStringList & to, const QStringList & cc, const QStringList & bcc, const QByteArray & message ) {
+ QString query = "headers=0&from=";
+ query += KURL::encode_string( sender );
+
+ QStringList::ConstIterator it;
+
+ for ( it = to.begin(); it != to.end(); ++it )
+ query += "&to=" + KURL::encode_string(*it);
+ for ( it = cc.begin(); it != cc.end(); ++it )
+ query += "&cc=" + KURL::encode_string(*it);
+ for ( it = bcc.begin(); it != bcc.end(); ++it )
+ query += "&bcc=" + KURL::encode_string(*it);
+
+ KMTransportInfo * ti = mSender->transportInfo();
+
+ if ( ti->specifyHostname )
+ query += "&hostname=" + KURL::encode_string( ti->localHostname );
+
+ if ( !kmkernel->msgSender()->sendQuotedPrintable() )
+ query += "&body=8bit";
+
+ KURL destination;
+
+ destination.setProtocol((ti->encryption == "SSL") ? SMTPS_PROTOCOL : SMTP_PROTOCOL);
+ destination.setHost(ti->host);
+ destination.setPort(ti->port.toUShort());
+
+ if (ti->auth)
+ {
+ QMapIterator<QString,QString> tpc = mSender->mPasswdCache.find( ti->name );
+ QString tpwd = ( tpc != mSender->mPasswdCache.end() )?(*tpc):QString::null;
+
+ if ( ti->passwd().isEmpty() )
+ ti->setPasswd( tpwd );
+
+ if( (ti->user.isEmpty() || ti->passwd().isEmpty()) &&
+ ti->authType != "GSSAPI" )
+ {
+ bool b = false;
+ int result;
+
+ KCursorSaver idle(KBusyPtr::idle());
+ QString passwd = ti->passwd();
+ result = KIO::PasswordDialog::getNameAndPassword(ti->user, passwd,
+ &b, i18n("You need to supply a username and a password to use this "
+ "SMTP server."), false, QString::null, ti->name, QString::null);
+
+ if ( result != QDialog::Accepted )
+ {
+ abort();
+ return false;
+ }
+ if (int id = KMTransportInfo::findTransport(ti->name)) {
+ ti->setPasswd( passwd );
+ ti->writeConfig(id);
+
+ // save the password into the cache
+ mSender->mPasswdCache[ti->name] = passwd;
+ }
+ }
+ destination.setUser(ti->user);
+ destination.setPass(ti->passwd());
+ }
+
+ if (!mSlave || !mInProcess)
+ {
+ KIO::MetaData slaveConfig;
+ slaveConfig.insert("tls", (ti->encryption == "TLS") ? "on" : "off");
+ if (ti->auth) slaveConfig.insert("sasl", ti->authType);
+ mSlave = KIO::Scheduler::getConnectedSlave(destination, slaveConfig);
+ }
+
+ if (!mSlave)
+ {
+ abort();
+ return false;
+ }
+
+ // dotstuffing is now done by the slave (see setting of metadata)
+ mMessage = message;
+ mMessageLength = mMessage.size();
+ mMessageOffset = 0;
+
+ if ( mMessageLength )
+ // allow +5% for subsequent LF->CRLF and dotstuffing (an average
+ // over 2G-lines gives an average line length of 42-43):
+ query += "&size=" + QString::number( qRound( mMessageLength * 1.05 ) );
+
+ destination.setPath("/send");
+ destination.setQuery( query );
+
+ mJob = KIO::put( destination, -1, false, false, false );
+ if ( !mJob ) {
+ abort();
+ return false;
+ }
+ mJob->addMetaData( "lf2crlf+dotstuff", "slave" );
+ KIO::Scheduler::assignJobToSlave(mSlave, mJob);
+ connect(mJob, SIGNAL(result(KIO::Job *)), this, SLOT(result(KIO::Job *)));
+ connect(mJob, SIGNAL(dataReq(KIO::Job *, QByteArray &)),
+ this, SLOT(dataReq(KIO::Job *, QByteArray &)));
+ mSendOk = true;
+ mInProcess = true;
+ return true;
+}
+
+void KMSendSMTP::cleanup() {
+ if(mJob)
+ {
+ mJob->kill(true);
+ mJob = 0;
+ mSlave = 0;
+ }
+
+ if (mSlave)
+ {
+ KIO::Scheduler::disconnectSlave(mSlave);
+ mSlave = 0;
+ }
+
+ mInProcess = false;
+}
+
+void KMSendSMTP::abort() {
+ cleanup();
+ emit idle();
+}
+
+void KMSendSMTP::doFinish() {
+ cleanup();
+}
+
+void KMSendSMTP::dataReq(KIO::Job *, QByteArray &array)
+{
+ // Send it by 32K chuncks
+ const int chunkSize = QMIN( mMessageLength - mMessageOffset, 32*1024 );
+ if ( chunkSize > 0 ) {
+ array.duplicate(mMessage.data() + mMessageOffset, chunkSize);
+ mMessageOffset += chunkSize;
+ } else
+ {
+ array.resize(0);
+ mMessage.resize(0);
+ }
+ mSender->emitProgressInfo( mMessageOffset );
+}
+
+void KMSendSMTP::result(KIO::Job *_job)
+{
+ if (!mJob) return;
+ mJob = 0;
+
+ if(_job->error())
+ {
+ mSendOk = false;
+ if (_job->error() == KIO::ERR_SLAVE_DIED) mSlave = 0;
+ failed(_job->errorString());
+ abort();
+ } else {
+ emit idle();
+ }
+}
+
+void KMSendSMTP::slaveError(KIO::Slave *aSlave, int error, const QString &errorMsg)
+{
+ if (aSlave == mSlave)
+ {
+ if (error == KIO::ERR_SLAVE_DIED) mSlave = 0;
+ mSendOk = false;
+ mJob = 0;
+ failed(KIO::buildErrorString(error, errorMsg));
+ abort();
+ }
+}
+
+#include "kmsender.moc"
+#include "kmsender_p.moc"
diff --git a/kmail/kmsender.h b/kmail/kmsender.h
new file mode 100644
index 00000000..bf986462
--- /dev/null
+++ b/kmail/kmsender.h
@@ -0,0 +1,174 @@
+/*
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ *
+ * 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 kmsender_h
+#define kmsender_h
+#include "messagesender.h"
+
+#ifndef KDE_USE_FINAL
+# ifndef REALLY_WANT_KMSENDER
+# error Do not include kmsender.h, but messagesender.h.
+# endif
+#endif
+
+#include <qcstring.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qmap.h>
+#include <qobject.h>
+#include <kdeversion.h>
+
+class KMMessage;
+class KMFolder;
+class KMFolderMgr;
+class KConfig;
+class KProcess;
+class KMSendProc;
+class KMSendSendmail;
+class KMSendSMTP;
+class QStrList;
+class KMTransportInfo;
+class KMPrecommand;
+
+namespace KPIM {
+ class ProgressItem;
+}
+
+class KMSender: public QObject, public KMail::MessageSender
+{
+ Q_OBJECT
+ friend class ::KMSendProc;
+ friend class ::KMSendSendmail;
+ friend class ::KMSendSMTP;
+
+public:
+ KMSender();
+ ~KMSender();
+
+protected:
+ /** Send given message. The message is either queued or sent
+ immediately. The default behaviour, as selected with
+ setSendImmediate(), can be overwritten with the parameter
+ sendNow (by specifying TRUE or FALSE).
+ The sender takes ownership of the given message on success,
+ so DO NOT DELETE OR MODIFY the message further.
+ Returns TRUE on success. */
+ bool doSend(KMMessage* msg, short sendNow);
+
+ /** Send queued messages, using the specified transport or the
+ * default, if none is given.
+ */
+ bool doSendQueued( const QString& transport );
+
+private:
+ /** Returns TRUE if sending is in progress. */
+ bool sending() const { return mSendInProgress; }
+
+public:
+ /** Shall messages be sent immediately (TRUE), or shall they be
+ queued and sent later upon call of sendQueued() ? */
+ bool sendImmediate() const { return mSendImmediate; }
+ void setSendImmediate(bool);
+
+ /** Shall messages be sent quoted-printable encoded. No encoding
+ happens otherwise. */
+ bool sendQuotedPrintable() const { return mSendQuotedPrintable; }
+ void setSendQuotedPrintable(bool);
+
+private:
+ /** Get the transport information */
+ KMTransportInfo * transportInfo() { return mTransportInfo; }
+
+public:
+ /** Read configuration from global config. */
+ void readConfig();
+
+ /** Write configuration to global config with optional sync() */
+ void writeConfig(bool withSync=true);
+
+private:
+ /** sets a status msg and emits statusMsg() */
+ void setStatusMsg(const QString&);
+
+ /** sets replied/forwarded status in the linked message for @p aMsg. */
+ void setStatusByLink(const KMMessage *aMsg);
+
+ /** Emit progress info - calculates a percent value based on the amount of bytes sent */
+ void emitProgressInfo( int currentFileProgress );
+
+private slots:
+ /** Start sending */
+ void slotPrecommandFinished(bool);
+
+ void slotIdle();
+
+ /** abort sending of the current message */
+ void slotAbortSend();
+
+ /** initialization sequence has finised */
+ void sendProcStarted(bool success);
+
+ /** note when a msg gets added to outbox during sending */
+ void outboxMsgAdded(int idx);
+
+private:
+ /** handle sending of messages */
+ void doSendMsg();
+
+ /** second part of handling sending of messages */
+ void doSendMsgAux();
+
+ /** cleanup after sending */
+ void cleanup();
+
+ /** Test if all required settings are set.
+ Reports problems to user via dialogs and returns FALSE.
+ Returns TRUE if everything is Ok. */
+ bool settingsOk() const;
+
+ /** Parse protocol '://' (host port? | mailer) string and
+ set transport settings */
+ KMSendProc* createSendProcFromString( const QString & transport );
+
+ bool runPrecommand( const QString & cmd );
+
+private:
+ bool mSendImmediate;
+ bool mSendQuotedPrintable;
+ KMTransportInfo *mTransportInfo;
+ KMPrecommand *mPrecommand;
+
+ QString mCustomTransport;
+ bool mSentOk, mSendAborted;
+ QString mErrorMsg;
+ KMSendProc *mSendProc;
+ QString mMethodStr;
+ bool mSendProcStarted;
+ bool mSendInProgress;
+ KMFolder *mOutboxFolder;
+ KMFolder *mSentFolder;
+ KMMessage * mCurrentMsg;
+ KPIM::ProgressItem* mProgressItem;
+ int mSentMessages, mTotalMessages;
+ int mSentBytes, mTotalBytes;
+ int mFailedMessages;
+ QMap<QString,QString> mPasswdCache;
+};
+
+#endif /*kmsender_h*/
diff --git a/kmail/kmsender_p.h b/kmail/kmsender_p.h
new file mode 100644
index 00000000..e9517974
--- /dev/null
+++ b/kmail/kmsender_p.h
@@ -0,0 +1,151 @@
+/* KMail Mail Sender
+ *
+ * Author: Stefan Taferner <taferner@alpin.or.at>
+ */
+#ifndef __KMAIL_SENDER_P_H__
+#define __KMAIL_SENDER_P_H__
+#include "kmsender.h"
+
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qobject.h>
+#include <kio/global.h>
+#include <kdeversion.h>
+
+class KProcess;
+
+namespace KIO {
+ class Job;
+ class TransferJob;
+ class Slave;
+}
+
+class KMSendProc: public QObject
+{
+ Q_OBJECT
+
+public:
+ KMSendProc(KMSender*);
+ virtual ~KMSendProc() {}
+
+ /** Initialize sending of one or more messages. */
+ void start() { emit started( doStart() ); }
+
+ /** Send given message. May return before message is sent. */
+ bool send( const QString & sender, const QStringList & to, const QStringList & cc, const QStringList & bcc, const QByteArray & message ) {
+ reset(); return doSend( sender, to, cc, bcc, message );
+ }
+
+ /** Cleanup after sending messages. */
+ void finish() { doFinish(); deleteLater(); }
+
+ /** Abort sending the current message. Sets mSending to false */
+ virtual void abort() = 0;
+
+ /** Returns TRUE if send was successful, and FALSE otherwise.
+ Returns FALSE if sending is still in progress. */
+ bool sendOk() const { return mSendOk; }
+
+ /** Returns error message of last call of failed(). */
+ QString lastErrorMessage() const { return mLastErrorMessage; }
+
+signals:
+ /** Emitted when the current message is sent or an error occurred. */
+ void idle();
+
+ /** Emitted when the initialization sequence has finished */
+ void started(bool);
+
+
+protected:
+ /** Called to signal a transmission error. The sender then
+ calls finish() and terminates sending of messages.
+ Sets mSending to FALSE. */
+ void failed(const QString &msg);
+
+ /** Informs the user about what is going on. */
+ void statusMsg(const QString&);
+
+private:
+ void reset();
+
+private:
+ virtual void doFinish() = 0;
+ virtual bool doSend( const QString & sender, const QStringList & to, const QStringList & cc, const QStringList & bcc, const QByteArray & message ) = 0;
+ virtual bool doStart() = 0;
+
+protected:
+ KMSender* mSender;
+ QString mLastErrorMessage;
+ bool mSendOk : 1;
+ bool mSending : 1;
+};
+
+
+//-----------------------------------------------------------------------------
+class KMSendSendmail: public KMSendProc
+{
+ Q_OBJECT
+public:
+ KMSendSendmail(KMSender*);
+ ~KMSendSendmail();
+ void start();
+ void abort();
+
+protected slots:
+ void receivedStderr(KProcess*,char*,int);
+ void wroteStdin(KProcess*);
+ void sendmailExited(KProcess*);
+
+private:
+ /** implemented from KMSendProc */
+ void doFinish();
+ /** implemented from KMSendProc */
+ bool doSend( const QString & sender, const QStringList & to, const QStringList & cc, const QStringList & bcc, const QByteArray & message );
+ /** implemented from KMSendProc */
+ bool doStart();
+
+private:
+ QByteArray mMsgStr;
+ char* mMsgPos;
+ int mMsgRest;
+ KProcess* mMailerProc;
+};
+
+//-----------------------------------------------------------------------------
+class KMSendSMTP : public KMSendProc
+{
+Q_OBJECT
+public:
+ KMSendSMTP(KMSender *sender);
+ ~KMSendSMTP();
+
+ void abort();
+
+private slots:
+ void dataReq(KIO::Job *, QByteArray &);
+ void result(KIO::Job *);
+ void slaveError(KIO::Slave *, int, const QString &);
+
+private:
+ /** implemented from KMSendProc */
+ void doFinish();
+ /** implemented from KMSendProc */
+ bool doSend( const QString & sender, const QStringList & to, const QStringList & cc, const QStringList & bcc, const QByteArray & message );
+ /** implemented from KMSendProc */
+ bool doStart() { return true; }
+
+ void cleanup();
+
+private:
+ QByteArray mMessage;
+ uint mMessageLength;
+ uint mMessageOffset;
+
+ bool mInProcess;
+
+ KIO::TransferJob *mJob;
+ KIO::Slave *mSlave;
+};
+
+#endif /* __KMAIL_SENDER_P_H__ */
diff --git a/kmail/kmservertest.cpp b/kmail/kmservertest.cpp
new file mode 100644
index 00000000..49fb0ad8
--- /dev/null
+++ b/kmail/kmservertest.cpp
@@ -0,0 +1,189 @@
+/* -*- c++ -*-
+ kmservertest.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2001-2002 Michael Haeckel <haeckel@kde.org>
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include <config.h>
+
+#include "kmservertest.h"
+
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+#include <kurl.h>
+#include <kapplication.h>
+#include <kio/scheduler.h>
+#include <kio/slave.h>
+#include <kio/job.h>
+#include <kio/global.h>
+
+//-----------------------------------------------------------------------------
+KMServerTest::KMServerTest( const QString & protocol, const QString & host, int port )
+ : QObject(),
+ mProtocol( protocol ), mHost( host ),
+ mSSL( false ), mJob( 0 ), mSlave( 0 ), mConnectionErrorCount( 0 )
+{
+ KIO::Scheduler::connect(
+ SIGNAL(slaveError(KIO::Slave *, int, const QString &)),
+ this, SLOT(slotSlaveResult(KIO::Slave *, int, const QString &)));
+
+ if ( port == 993 || port == 995 || port == 465 )
+ port = 0;
+
+ startOffSlave( port );
+}
+
+//-----------------------------------------------------------------------------
+KMServerTest::~KMServerTest()
+{
+ if (mJob) mJob->kill(TRUE);
+}
+
+
+KIO::MetaData KMServerTest::slaveConfig() const {
+ KIO::MetaData md;
+ md.insert( "nologin", "on" );
+ return md;
+}
+
+void KMServerTest::startOffSlave( int port ) {
+ KURL url;
+ url.setProtocol( mSSL ? mProtocol + 's' : mProtocol );
+ url.setHost( mHost );
+ if ( port )
+ url.setPort( port );
+
+ mSlave = KIO::Scheduler::getConnectedSlave( url, slaveConfig() );
+ if ( !mSlave ) {
+ slotSlaveResult( 0, 1 );
+ return;
+ }
+ connect( mSlave, SIGNAL(metaData(const KIO::MetaData&)),
+ SLOT(slotMetaData(const KIO::MetaData&)) );
+
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+
+ stream << (int) 'c';
+
+ mJob = KIO::special( url, packedArgs, false );
+ KIO::Scheduler::assignJobToSlave( mSlave, mJob );
+ connect( mJob, SIGNAL(result(KIO::Job*)), SLOT(slotResult(KIO::Job*)) );
+ connect( mJob, SIGNAL(infoMessage(KIO::Job*,const QString&)),
+ SLOT(slotData(KIO::Job*,const QString&)) );
+}
+
+
+//-----------------------------------------------------------------------------
+void KMServerTest::slotData(KIO::Job *, const QString &data)
+{
+ if ( mSSL )
+ mListSSL = QStringList::split(' ', data);
+ else
+ mListNormal = QStringList::split(' ', data);
+}
+
+
+void KMServerTest::slotMetaData( const KIO::MetaData & md ) {
+ KIO::MetaData::const_iterator it = md.find( "PLAIN AUTH METHODS" );
+ if ( it != md.end() ) {
+ mAuthNone = it.data();
+ kdDebug(5006) << "mAuthNone: " << mAuthNone << endl;
+ }
+ it = md.find( "TLS AUTH METHODS" );
+ if ( it != md.end() ) {
+ mAuthTLS = it.data();
+ kdDebug(5006) << "mAuthTLS: " << mAuthTLS << endl;
+ }
+ it = md.find( "SSL AUTH METHODS" );
+ if ( it != md.end() ) {
+ mAuthSSL = it.data();
+ kdDebug(5006) << "mAuthSSL: " << mAuthSSL << endl;
+ }
+}
+
+//-----------------------------------------------------------------------------
+void KMServerTest::slotResult(KIO::Job *job)
+{
+ slotSlaveResult(mSlave, job->error());
+}
+
+//-----------------------------------------------------------------------------
+void KMServerTest::slotSlaveResult(KIO::Slave *aSlave, int error,
+ const QString &errorText)
+{
+ if (aSlave != mSlave) return;
+ if ( mSSL && error == 0 ) {
+ // add a dummy entry to the list of SSL capabilities so that the receiver
+ // of the capabilities signal can use mListSSL.isEmpty() in order to find
+ // out whether SSL is supported
+ mListSSL.append("SSL");
+ }
+
+ if (error != KIO::ERR_SLAVE_DIED && mSlave)
+ {
+ // disconnect slave after every connect
+ KIO::Scheduler::disconnectSlave(mSlave);
+ mSlave = 0;
+ }
+ if ( error == KIO::ERR_COULD_NOT_CONNECT )
+ {
+ // if one of the two connection tests fails we ignore the error
+ // if both fail the host is probably not correct so we display the error
+ if ( mConnectionErrorCount == 0 )
+ {
+ error = 0;
+ }
+ ++mConnectionErrorCount;
+ }
+ if ( error )
+ {
+ mJob = 0;
+ KMessageBox::error( kapp->activeWindow(),
+ KIO::buildErrorString( error, errorText ),
+ i18n("Error") );
+ emit capabilities( mListNormal, mListSSL );
+ emit capabilities( mListNormal, mListSSL, mAuthNone, mAuthSSL, mAuthTLS );
+ return;
+ }
+ if (!mSSL) {
+ mSSL = true;
+ mListNormal.append("NORMAL-CONNECTION");
+ startOffSlave();
+ } else {
+ mJob = 0;
+
+ emit capabilities( mListNormal, mListSSL );
+ emit capabilities( mListNormal, mListSSL, mAuthNone, mAuthSSL, mAuthTLS );
+ }
+}
+
+
+#include "kmservertest.moc"
diff --git a/kmail/kmservertest.h b/kmail/kmservertest.h
new file mode 100644
index 00000000..d6ce9c2d
--- /dev/null
+++ b/kmail/kmservertest.h
@@ -0,0 +1,88 @@
+/* -*- c++ -*-
+ kmservertest.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2001-2002 Michael Haeckel <haeckel@kde.org>
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef kmservertest_h
+#define kmservertest_h
+
+#include <qobject.h>
+#include <qstring.h>
+#include <qstringlist.h>
+
+namespace KIO {
+ class Job;
+ class Slave;
+ class SimpleJob;
+ class MetaData;
+}
+
+class KMServerTest : public QObject
+{
+ Q_OBJECT
+
+public:
+ KMServerTest( const QString & protocol, const QString & host, int port );
+ virtual ~KMServerTest();
+
+signals:
+ void capabilities( const QStringList & capaNormal,
+ const QStringList & capaSSL );
+ void capabilities( const QStringList & capaNormal,
+ const QStringList & capaSSL,
+ const QString & authNone, const QString & authSSL,
+ const QString & authTLS );
+
+protected slots:
+ void slotData(KIO::Job *job, const QString &data);
+ void slotResult(KIO::Job *job);
+ void slotMetaData( const KIO::MetaData & );
+ void slotSlaveResult(KIO::Slave *aSlave, int error,
+ const QString &errorText = QString::null);
+
+protected:
+ KIO::MetaData slaveConfig() const;
+ void startOffSlave( int port=0 );
+
+protected:
+ const QString mProtocol;
+ const QString mHost;
+ bool mSSL;
+ QStringList mListNormal;
+ QStringList mListSSL;
+ QString mAuthNone;
+ QString mAuthSSL;
+ QString mAuthTLS;
+ KIO::SimpleJob *mJob;
+ KIO::Slave *mSlave;
+ int mConnectionErrorCount;
+};
+
+#endif
diff --git a/kmail/kmstartup.cpp b/kmail/kmstartup.cpp
new file mode 100644
index 00000000..d1967a3e
--- /dev/null
+++ b/kmail/kmstartup.cpp
@@ -0,0 +1,268 @@
+/*
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2000 Don Sanders <sanders@kde.org>
+
+ 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
+*/
+
+#include <config.h>
+
+#include "kmstartup.h"
+
+#include "kmkernel.h" //control center
+#include "kcursorsaver.h"
+
+#include <klocale.h>
+#include <ksimpleconfig.h>
+#include <kstandarddirs.h>
+#include <kmessagebox.h>
+#include <dcopclient.h>
+#include <kcrash.h>
+#include <kglobal.h>
+#include <kapplication.h>
+#include <kaboutdata.h>
+#include <kiconloader.h>
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <qfile.h>
+
+#undef Status // stupid X headers
+
+extern "C" {
+
+// Crash recovery signal handler
+void kmsignalHandler(int sigId)
+{
+ kmsetSignalHandler(SIG_DFL);
+ fprintf(stderr, "*** KMail got signal %d (Exiting)\n", sigId);
+ // try to cleanup all windows
+ if (kmkernel) kmkernel->dumpDeadLetters();
+ ::exit(-1); //
+}
+
+// Crash recovery signal handler
+void kmcrashHandler(int sigId)
+{
+ kmsetSignalHandler(SIG_DFL);
+ fprintf(stderr, "*** KMail got signal %d (Crashing)\n", sigId);
+ // try to cleanup all windows
+ if (kmkernel) kmkernel->dumpDeadLetters();
+ // Return to DrKonqi.
+}
+//-----------------------------------------------------------------------------
+
+
+void kmsetSignalHandler(void (*handler)(int))
+{
+ signal(SIGKILL, handler);
+ signal(SIGTERM, handler);
+ signal(SIGHUP, handler);
+ KCrash::setEmergencySaveFunction(kmcrashHandler);
+}
+
+}
+//-----------------------------------------------------------------------------
+
+namespace {
+ QString getMyHostName() {
+ char hostNameC[256];
+ // null terminate this C string
+ hostNameC[255] = 0;
+ // set the string to 0 length if gethostname fails
+ if(gethostname(hostNameC, 255))
+ hostNameC[0] = 0;
+ return QString::fromLocal8Bit(hostNameC);
+ }
+} // anon namespace
+
+namespace KMail {
+
+void checkConfigUpdates() {
+ static const char * const updates[] = {
+ "9",
+ "3.1-update-identities",
+ "3.1-use-identity-uoids",
+ "3.1-new-mail-notification",
+ "3.2-update-loop-on-goto-unread-settings",
+ "3.1.4-dont-use-UOID-0-for-any-identity",
+ "3.2-misc",
+ "3.2-moves",
+ "3.3-use-ID-for-accounts",
+ "3.3-update-filter-rules",
+ "3.3-move-identities-to-own-file",
+ "3.3-aegypten-kpgprc-to-kmailrc",
+ "3.3-aegypten-kpgprc-to-libkleopatrarc",
+ "3.3-aegypten-emailidentities-split-sign-encr-keys",
+ "3.3-misc",
+ "3.3b1-misc",
+ "3.4-misc",
+ "3.4a",
+ "3.4b",
+ "3.4.1",
+ "3.5.4",
+ "3.5.7-imap-flag-migration"
+ };
+ static const int numUpdates = sizeof updates / sizeof *updates;
+ // Warning: do not remove entries in the above array, or the update-level check below will break
+
+ KConfig * config = KMKernel::config();
+ KConfigGroup startup( config, "Startup" );
+ const int configUpdateLevel = startup.readNumEntry( "update-level", 0 );
+ if ( configUpdateLevel == numUpdates ) // Optimize for the common case that everything is OK
+ return;
+
+ for ( int i = configUpdateLevel ; i < numUpdates ; ++i ) {
+ config->checkUpdate( updates[i], "kmail.upd" );
+ }
+ startup.writeEntry( "update-level", numUpdates );
+}
+
+void lockOrDie() {
+// Check and create a lock file to prevent concurrent access to kmail files
+ QString appName = kapp->instanceName();
+ if ( appName.isEmpty() )
+ appName = "kmail";
+
+ QString programName;
+ const KAboutData *about = kapp->aboutData();
+ if ( about )
+ programName = about->programName();
+ if ( programName.isEmpty() )
+ programName = i18n("KMail");
+
+ QString lockLocation = locateLocal("data", "kmail/lock");
+ KSimpleConfig config(lockLocation);
+ int oldPid = config.readNumEntry("pid", -1);
+ const QString oldHostName = config.readEntry("hostname");
+ const QString oldAppName = config.readEntry( "appName", appName );
+ const QString oldProgramName = config.readEntry( "programName", programName );
+ const QString hostName = getMyHostName();
+ bool first_instance = false;
+ if ( oldPid == -1 )
+ first_instance = true;
+ else if (hostName == oldHostName && oldPid != getpid()) {
+ // check if the lock file is stale
+#ifdef Q_OS_LINUX
+ if ( ::access("/proc", X_OK ) == 0 ) {
+ // On linux with /proc we can even check that it's really kmail and not something else
+ char path_buffer[MAXPATHLEN + 1];
+ path_buffer[MAXPATHLEN] = 0;
+ const QString procPath = QString("/proc/%1/exe").arg(oldPid);
+ const int length = readlink (procPath.latin1(), path_buffer, MAXPATHLEN);
+ if ( length == -1 ) // not such pid
+ first_instance = true;
+ else {
+ path_buffer[length] = '\0';
+ const QString path = QFile::decodeName(path_buffer);
+ kdDebug() << k_funcinfo << path << endl;
+ const int pos = path.findRev('/');
+ const QString fileName = path.mid(pos+1);
+ kdDebug() << "Found process " << oldPid << " running. It's: " << fileName << endl;
+ first_instance = fileName != "kmail" && fileName != "kontact";
+ }
+ } else
+#endif
+ {
+ // Otherwise we just check if the other pid is currently running.
+ // Not 100% correct but better safe than sorry.
+ if ( kill(oldPid, 0) == -1 )
+ first_instance = ( errno == ESRCH );
+ }
+ }
+
+ if ( !first_instance ) {
+ QString msg;
+ if ( oldHostName == hostName ) {
+ // this can only happen if the user is running this application on
+ // different displays on the same machine. All other cases will be
+ // taken care of by KUniqueApplication()
+ if ( oldAppName == appName )
+ msg = i18n("%1 already seems to be running on another display on "
+ "this machine. Running %2 more than once "
+ "can cause the loss of mail. You should not start %1 "
+ "unless you are sure that it is not already running.")
+ .arg( programName, programName );
+ // QString::arg( st ) only replaces the first occurrence of %1
+ // with st while QString::arg( s1, s2 ) replacess all occurrences
+ // of %1 with s1 and all occurrences of %2 with s2. So don't
+ // even think about changing the above to .arg( programName ).
+ else
+ msg = i18n("%1 seems to be running on another display on this "
+ "machine. Running %1 and %2 at the same "
+ "time can cause the loss of mail. You should not start %2 "
+ "unless you are sure that %1 is not running.")
+ .arg( oldProgramName, programName );
+ }
+ else {
+ if ( oldAppName == appName )
+ msg = i18n("%1 already seems to be running on %2. Running %1 more "
+ "than once can cause the loss of mail. You should not "
+ "start %1 on this computer unless you are sure that it is "
+ "not already running on %2.")
+ .arg( programName, oldHostName );
+ else
+ msg = i18n("%1 seems to be running on %3. Running %1 and %2 at the "
+ "same time can cause the loss of mail. You should not "
+ "start %2 on this computer unless you are sure that %1 is "
+ "not running on %3.")
+ .arg( oldProgramName, programName, oldHostName );
+ }
+
+ KCursorSaver idle( KBusyPtr::idle() );
+ if ( KMessageBox::No ==
+ KMessageBox::warningYesNo( 0, msg, QString::null,
+ i18n("Start %1").arg( programName ),
+ i18n("Exit") ) ) {
+ exit(1);
+ }
+ }
+
+ config.writeEntry("pid", getpid());
+ config.writeEntry("hostname", hostName);
+ config.writeEntry( "appName", appName );
+ config.writeEntry( "programName", programName );
+ config.sync();
+}
+
+void insertLibraryCataloguesAndIcons() {
+ static const char * const catalogues[] = {
+ "libkdepim",
+ "libksieve",
+ "libkleopatra",
+ "libkmime"
+ };
+
+ KLocale * l = KGlobal::locale();
+ KIconLoader * il = KGlobal::iconLoader();
+ for ( unsigned int i = 0 ; i < sizeof catalogues / sizeof *catalogues ; ++i ) {
+ l->insertCatalogue( catalogues[i] );
+ il->addAppDir( catalogues[i] );
+ }
+
+}
+
+void cleanup()
+{
+ const QString lockLocation = locateLocal("data", "kmail/lock");
+ KSimpleConfig config(lockLocation);
+ config.writeEntry("pid", -1);
+ config.sync();
+}
+}
diff --git a/kmail/kmstartup.h b/kmail/kmstartup.h
new file mode 100644
index 00000000..45c63be9
--- /dev/null
+++ b/kmail/kmstartup.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2000 Don Sanders <sanders@kde.org>
+
+ 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
+*/
+#ifndef KMSTARTUP
+#define KMSTARTUP
+
+#include <kdepimmacros.h>
+
+extern "C" {
+
+KDE_EXPORT void kmsetSignalHandler(void (*handler)(int));
+KDE_EXPORT void kmsignalHandler(int sigId);
+KDE_EXPORT void kmcrashHandler(int sigId);
+
+}
+
+namespace KMail
+{
+ KDE_EXPORT void checkConfigUpdates();
+ KDE_EXPORT void lockOrDie();
+ KDE_EXPORT void insertLibraryCataloguesAndIcons();
+ KDE_EXPORT void cleanup();
+}
+
+#endif
+
+
+
+
+
diff --git a/kmail/kmsystemtray.cpp b/kmail/kmsystemtray.cpp
new file mode 100644
index 00000000..c8595a88
--- /dev/null
+++ b/kmail/kmsystemtray.cpp
@@ -0,0 +1,585 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+/***************************************************************************
+ kmsystemtray.cpp - description
+ -------------------
+ begin : Fri Aug 31 22:38:44 EDT 2001
+ copyright : (C) 2001 by Ryan Breen
+ email : ryan@porivo.com
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+#include <config.h>
+
+#include "kmsystemtray.h"
+#include "kmfolder.h"
+#include "kmfoldertree.h"
+#include "kmfoldermgr.h"
+#include "kmfolderimap.h"
+#include "kmmainwidget.h"
+#include "accountmanager.h"
+using KMail::AccountManager;
+#include "globalsettings.h"
+
+#include <kapplication.h>
+#include <kmainwindow.h>
+#include <kglobalsettings.h>
+#include <kiconloader.h>
+#include <kiconeffect.h>
+#include <kwin.h>
+#include <kdebug.h>
+#include <kpopupmenu.h>
+
+#include <qpainter.h>
+#include <qbitmap.h>
+#include <qtooltip.h>
+#include <qwidgetlist.h>
+#include <qobjectlist.h>
+
+#include <math.h>
+#include <assert.h>
+
+/**
+ * Construct a KSystemTray icon to be displayed when new mail
+ * has arrived in a non-system folder. The KMSystemTray listens
+ * for updateNewMessageNotification events from each non-system
+ * KMFolder and maintains a store of all folders with unread
+ * messages.
+ *
+ * The KMSystemTray also provides a popup menu listing each folder
+ * with its count of unread messages, allowing the user to jump
+ * to the first unread message in each folder.
+ */
+KMSystemTray::KMSystemTray(QWidget *parent, const char *name)
+ : KSystemTray( parent, name ),
+ mParentVisible( true ),
+ mPosOfMainWin( 0, 0 ),
+ mDesktopOfMainWin( 0 ),
+ mMode( GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ),
+ mCount( 0 ),
+ mNewMessagePopupId(-1),
+ mPopupMenu(0)
+{
+ setAlignment( AlignCenter );
+ kdDebug(5006) << "Initting systray" << endl;
+
+ mLastUpdate = time( 0 );
+ mUpdateTimer = new QTimer( this, "systraytimer" );
+ connect( mUpdateTimer, SIGNAL( timeout() ), SLOT( updateNewMessages() ) );
+
+ mDefaultIcon = loadIcon( "kmail" );
+ mLightIconImage = loadIcon( "kmaillight" ).convertToImage();
+
+ setPixmap(mDefaultIcon);
+
+ KMMainWidget * mainWidget = kmkernel->getKMMainWidget();
+ if ( mainWidget ) {
+ QWidget * mainWin = mainWidget->topLevelWidget();
+ if ( mainWin ) {
+ mDesktopOfMainWin = KWin::windowInfo( mainWin->winId(),
+ NET::WMDesktop ).desktop();
+ mPosOfMainWin = mainWin->pos();
+ }
+ }
+
+ // register the applet with the kernel
+ kmkernel->registerSystemTrayApplet( this );
+
+ /** Initiate connections between folders and this object */
+ foldersChanged();
+
+ connect( kmkernel->folderMgr(), SIGNAL(changed()), SLOT(foldersChanged()));
+ connect( kmkernel->imapFolderMgr(), SIGNAL(changed()), SLOT(foldersChanged()));
+ connect( kmkernel->dimapFolderMgr(), SIGNAL(changed()), SLOT(foldersChanged()));
+ connect( kmkernel->searchFolderMgr(), SIGNAL(changed()), SLOT(foldersChanged()));
+
+ connect( kmkernel->acctMgr(), SIGNAL( checkedMail( bool, bool, const QMap<QString, int> & ) ),
+ SLOT( updateNewMessages() ) );
+}
+
+void KMSystemTray::buildPopupMenu()
+{
+ // Delete any previously created popup menu
+ delete mPopupMenu;
+ mPopupMenu = 0;
+
+ mPopupMenu = new KPopupMenu();
+ KMMainWidget * mainWidget = kmkernel->getKMMainWidget();
+ if ( !mainWidget )
+ return;
+
+ mPopupMenu->insertTitle(*(this->pixmap()), "KMail");
+ KAction * action;
+ if ( ( action = mainWidget->action("check_mail") ) )
+ action->plug( mPopupMenu );
+ if ( ( action = mainWidget->action("check_mail_in") ) )
+ action->plug( mPopupMenu );
+ if ( ( action = mainWidget->action("send_queued") ) )
+ action->plug( mPopupMenu );
+ if ( ( action = mainWidget->action("send_queued_via") ) )
+ action->plug( mPopupMenu );
+ mPopupMenu->insertSeparator();
+ if ( ( action = mainWidget->action("new_message") ) )
+ action->plug( mPopupMenu );
+ if ( ( action = mainWidget->action("kmail_configure_kmail") ) )
+ action->plug( mPopupMenu );
+ mPopupMenu->insertSeparator();
+
+ KMainWindow *mainWin = ::qt_cast<KMainWindow*>(kmkernel->getKMMainWidget()->topLevelWidget());
+ if(mainWin)
+ if ( ( action=mainWin->actionCollection()->action("file_quit") ) )
+ action->plug( mPopupMenu );
+}
+
+KMSystemTray::~KMSystemTray()
+{
+ // unregister the applet
+ kmkernel->unregisterSystemTrayApplet( this );
+
+ delete mPopupMenu;
+ mPopupMenu = 0;
+}
+
+void KMSystemTray::setMode(int newMode)
+{
+ if(newMode == mMode) return;
+
+ kdDebug(5006) << "Setting systray mMode to " << newMode << endl;
+ mMode = newMode;
+
+ switch ( mMode ) {
+ case GlobalSettings::EnumSystemTrayPolicy::ShowAlways:
+ if ( isHidden() )
+ show();
+ break;
+ case GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread:
+ if ( mCount == 0 && !isHidden() )
+ hide();
+ else if ( mCount > 0 && isHidden() )
+ show();
+ break;
+ default:
+ kdDebug(5006) << k_funcinfo << " Unknown systray mode " << mMode << endl;
+ }
+}
+
+int KMSystemTray::mode() const
+{
+ return mMode;
+}
+
+/**
+ * Update the count of unread messages. If there are unread messages,
+ * overlay the count on top of a transparent version of the KMail icon.
+ * If there is no unread mail, restore the normal KMail icon.
+ */
+void KMSystemTray::updateCount()
+{
+ if(mCount != 0)
+ {
+ int oldPixmapWidth = pixmap()->size().width();
+ int oldPixmapHeight = pixmap()->size().height();
+
+ QString countString = QString::number( mCount );
+ QFont countFont = KGlobalSettings::generalFont();
+ countFont.setBold(true);
+
+ // decrease the size of the font for the number of unread messages if the
+ // number doesn't fit into the available space
+ float countFontSize = countFont.pointSizeFloat();
+ QFontMetrics qfm( countFont );
+ int width = qfm.width( countString );
+ if( width > oldPixmapWidth )
+ {
+ countFontSize *= float( oldPixmapWidth ) / float( width );
+ countFont.setPointSizeFloat( countFontSize );
+ }
+
+ // Create an image which represents the number of unread messages
+ // and which has a transparent background.
+ // Unfortunately this required the following twisted code because for some
+ // reason text that is drawn on a transparent pixmap is invisible
+ // (apparently the alpha channel isn't changed when the text is drawn).
+ // Therefore I have to draw the text on a solid background and then remove
+ // the background by making it transparent with QPixmap::setMask. This
+ // involves the slow createHeuristicMask() function (from the API docs:
+ // "This function is slow because it involves transformation to a QImage,
+ // non-trivial computations and a transformation back to a QBitmap."). Then
+ // I have to convert the resulting QPixmap to a QImage in order to overlay
+ // the light KMail icon with the number (because KIconEffect::overlay only
+ // works with QImage). Finally the resulting QImage has to be converted
+ // back to a QPixmap.
+ // That's a lot of work for overlaying the KMail icon with the number of
+ // unread messages, but every other approach I tried failed miserably.
+ // IK, 2003-09-22
+ QPixmap numberPixmap( oldPixmapWidth, oldPixmapHeight );
+ numberPixmap.fill( Qt::white );
+ QPainter p( &numberPixmap );
+ p.setFont( countFont );
+ p.setPen( Qt::blue );
+ p.drawText( numberPixmap.rect(), Qt::AlignCenter, countString );
+ numberPixmap.setMask( numberPixmap.createHeuristicMask() );
+ QImage numberImage = numberPixmap.convertToImage();
+
+ // Overlay the light KMail icon with the number image
+ QImage iconWithNumberImage = mLightIconImage.copy();
+ KIconEffect::overlay( iconWithNumberImage, numberImage );
+
+ QPixmap iconWithNumber;
+ iconWithNumber.convertFromImage( iconWithNumberImage );
+ setPixmap( iconWithNumber );
+ } else
+ {
+ setPixmap( mDefaultIcon );
+ }
+}
+
+/**
+ * Refreshes the list of folders we are monitoring. This is called on
+ * startup and is also connected to the 'changed' signal on the KMFolderMgr.
+ */
+void KMSystemTray::foldersChanged()
+{
+ /**
+ * Hide and remove all unread mappings to cover the case where the only
+ * unread message was in a folder that was just removed.
+ */
+ mFoldersWithUnread.clear();
+ mCount = 0;
+
+ if ( mMode == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ) {
+ hide();
+ }
+
+ /** Disconnect all previous connections */
+ disconnect(this, SLOT(updateNewMessageNotification(KMFolder *)));
+
+ QStringList folderNames;
+ QValueList<QGuardedPtr<KMFolder> > folderList;
+ kmkernel->folderMgr()->createFolderList(&folderNames, &folderList);
+ kmkernel->imapFolderMgr()->createFolderList(&folderNames, &folderList);
+ kmkernel->dimapFolderMgr()->createFolderList(&folderNames, &folderList);
+ kmkernel->searchFolderMgr()->createFolderList(&folderNames, &folderList);
+
+ QStringList::iterator strIt = folderNames.begin();
+
+ for(QValueList<QGuardedPtr<KMFolder> >::iterator it = folderList.begin();
+ it != folderList.end() && strIt != folderNames.end(); ++it, ++strIt)
+ {
+ KMFolder * currentFolder = *it;
+ QString currentName = *strIt;
+
+ if ( ((!currentFolder->isSystemFolder() || (currentFolder->name().lower() == "inbox")) ||
+ (currentFolder->folderType() == KMFolderTypeImap)) &&
+ !currentFolder->ignoreNewMail() )
+ {
+ /** If this is a new folder, start listening for messages */
+ connect(currentFolder, SIGNAL(numUnreadMsgsChanged(KMFolder *)),
+ this, SLOT(updateNewMessageNotification(KMFolder *)));
+
+ /** Check all new folders to see if we started with any new messages */
+ updateNewMessageNotification(currentFolder);
+ }
+ }
+}
+
+/**
+ * On left mouse click, switch focus to the first KMMainWidget. On right
+ * click, bring up a list of all folders with a count of unread messages.
+ */
+void KMSystemTray::mousePressEvent(QMouseEvent *e)
+{
+ // switch to kmail on left mouse button
+ if( e->button() == LeftButton )
+ {
+ if( mParentVisible && mainWindowIsOnCurrentDesktop() )
+ hideKMail();
+ else
+ showKMail();
+ }
+
+ // open popup menu on right mouse button
+ if( e->button() == RightButton )
+ {
+ mPopupFolders.clear();
+ mPopupFolders.reserve( mFoldersWithUnread.count() );
+
+ // Rebuild popup menu at click time to minimize race condition if
+ // the base KMainWidget is closed.
+ buildPopupMenu();
+
+ if(mNewMessagePopupId != -1)
+ {
+ mPopupMenu->removeItem(mNewMessagePopupId);
+ }
+
+ if(mFoldersWithUnread.count() > 0)
+ {
+ KPopupMenu *newMessagesPopup = new KPopupMenu();
+
+ QMap<QGuardedPtr<KMFolder>, int>::Iterator it = mFoldersWithUnread.begin();
+ for(uint i=0; it != mFoldersWithUnread.end(); ++i)
+ {
+ kdDebug(5006) << "Adding folder" << endl;
+ mPopupFolders.append( it.key() );
+ QString item = prettyName(it.key()) + " (" + QString::number(it.data()) + ")";
+ newMessagesPopup->insertItem(item, this, SLOT(selectedAccount(int)), 0, i);
+ ++it;
+ }
+
+ mNewMessagePopupId = mPopupMenu->insertItem(i18n("New Messages In"),
+ newMessagesPopup, mNewMessagePopupId, 3);
+
+ kdDebug(5006) << "Folders added" << endl;
+ }
+
+ mPopupMenu->popup(e->globalPos());
+ }
+
+}
+
+/**
+ * Return the name of the folder in which the mail is deposited, prepended
+ * with the account name if the folder is IMAP.
+ */
+QString KMSystemTray::prettyName(KMFolder * fldr)
+{
+ QString rvalue = fldr->label();
+ if(fldr->folderType() == KMFolderTypeImap)
+ {
+ KMFolderImap * imap = dynamic_cast<KMFolderImap*> (fldr->storage());
+ assert(imap);
+
+ if((imap->account() != 0) &&
+ (imap->account()->name() != 0) )
+ {
+ kdDebug(5006) << "IMAP folder, prepend label with type" << endl;
+ rvalue = imap->account()->name() + "->" + rvalue;
+ }
+ }
+
+ kdDebug(5006) << "Got label " << rvalue << endl;
+
+ return rvalue;
+}
+
+
+bool KMSystemTray::mainWindowIsOnCurrentDesktop()
+{
+ KMMainWidget * mainWidget = kmkernel->getKMMainWidget();
+ if ( !mainWidget )
+ return false;
+
+ QWidget *mainWin = kmkernel->getKMMainWidget()->topLevelWidget();
+ if ( !mainWin )
+ return false;
+
+ return KWin::windowInfo( mainWin->winId(),
+ NET::WMDesktop ).isOnCurrentDesktop();
+}
+
+/**
+ * Shows and raises the first KMMainWidget and
+ * switches to the appropriate virtual desktop.
+ */
+void KMSystemTray::showKMail()
+{
+ if (!kmkernel->getKMMainWidget())
+ return;
+ QWidget *mainWin = kmkernel->getKMMainWidget()->topLevelWidget();
+ assert(mainWin);
+ if(mainWin)
+ {
+ KWin::WindowInfo cur = KWin::windowInfo( mainWin->winId(), NET::WMDesktop );
+ if ( cur.valid() ) mDesktopOfMainWin = cur.desktop();
+ // switch to appropriate desktop
+ if ( mDesktopOfMainWin != NET::OnAllDesktops )
+ KWin::setCurrentDesktop( mDesktopOfMainWin );
+ if ( !mParentVisible ) {
+ if ( mDesktopOfMainWin == NET::OnAllDesktops )
+ KWin::setOnAllDesktops( mainWin->winId(), true );
+ mainWin->move( mPosOfMainWin );
+ mainWin->show();
+ }
+ KWin::activateWindow( mainWin->winId() );
+ mParentVisible = true;
+ }
+ kmkernel->raise();
+
+ //Fake that the folders have changed so that the icon status is correct
+ foldersChanged();
+}
+
+void KMSystemTray::hideKMail()
+{
+ if (!kmkernel->getKMMainWidget())
+ return;
+ QWidget *mainWin = kmkernel->getKMMainWidget()->topLevelWidget();
+ assert(mainWin);
+ if(mainWin)
+ {
+ mDesktopOfMainWin = KWin::windowInfo( mainWin->winId(),
+ NET::WMDesktop ).desktop();
+ mPosOfMainWin = mainWin->pos();
+ // iconifying is unnecessary, but it looks cooler
+ KWin::iconifyWindow( mainWin->winId() );
+ mainWin->hide();
+ mParentVisible = false;
+ }
+}
+
+/**
+ * Called on startup of the KMSystemTray and when the numUnreadMsgsChanged signal
+ * is emitted for one of the watched folders. Shows the system tray icon if there
+ * are new messages and the icon was hidden, or hides the system tray icon if there
+ * are no more new messages.
+ */
+void KMSystemTray::updateNewMessageNotification(KMFolder * fldr)
+{
+ //We don't want to count messages from search folders as they
+ // already counted as part of their original folders
+ if( !fldr ||
+ fldr->folderType() == KMFolderTypeSearch )
+ {
+ // kdDebug(5006) << "Null or a search folder, can't mess with that" << endl;
+ return;
+ }
+
+ mPendingUpdates[ fldr ] = true;
+ if ( time( 0 ) - mLastUpdate > 2 ) {
+ mUpdateTimer->stop();
+ updateNewMessages();
+ }
+ else {
+ mUpdateTimer->start(150, true);
+ }
+}
+
+void KMSystemTray::updateNewMessages()
+{
+ for ( QMap<QGuardedPtr<KMFolder>, bool>::Iterator it = mPendingUpdates.begin();
+ it != mPendingUpdates.end(); ++it)
+ {
+ KMFolder *fldr = it.key();
+ if ( !fldr ) // deleted folder
+ continue;
+
+ /** The number of unread messages in that folder */
+ int unread = fldr->countUnread();
+
+ QMap<QGuardedPtr<KMFolder>, int>::Iterator it =
+ mFoldersWithUnread.find(fldr);
+ bool unmapped = (it == mFoldersWithUnread.end());
+
+ /** If the folder is not mapped yet, increment count by numUnread
+ in folder */
+ if(unmapped) mCount += unread;
+ /* Otherwise, get the difference between the numUnread in the folder and
+ * our last known version, and adjust mCount with that difference */
+ else
+ {
+ int diff = unread - it.data();
+ mCount += diff;
+ }
+
+ if(unread > 0)
+ {
+ /** Add folder to our internal store, or update unread count if already mapped */
+ mFoldersWithUnread.insert(fldr, unread);
+ //kdDebug(5006) << "There are now " << mFoldersWithUnread.count() << " folders with unread" << endl;
+ }
+
+ /**
+ * Look for folder in the list of folders already represented. If there are
+ * unread messages and the system tray icon is hidden, show it. If there are
+ * no unread messages, remove the folder from the mapping.
+ */
+ if(unmapped)
+ {
+ /** Spurious notification, ignore */
+ if(unread == 0) continue;
+
+ /** Make sure the icon will be displayed */
+ if ( ( mMode == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread )
+ && isHidden() ) {
+ show();
+ }
+
+ } else
+ {
+
+ if(unread == 0)
+ {
+ kdDebug(5006) << "Removing folder from internal store " << fldr->name() << endl;
+
+ /** Remove the folder from the internal store */
+ mFoldersWithUnread.remove(fldr);
+
+ /** if this was the last folder in the dictionary, hide the systemtray icon */
+ if(mFoldersWithUnread.count() == 0)
+ {
+ mPopupFolders.clear();
+ disconnect(this, SLOT(selectedAccount(int)));
+
+ mCount = 0;
+
+ if ( mMode == GlobalSettings::EnumSystemTrayPolicy::ShowOnUnread ) {
+ hide();
+ }
+ }
+ }
+ }
+
+ }
+ mPendingUpdates.clear();
+ updateCount();
+
+ /** Update tooltip to reflect count of unread messages */
+ QToolTip::remove(this);
+ QToolTip::add(this, mCount == 0 ?
+ i18n("There are no unread messages")
+ : i18n("There is 1 unread message.",
+ "There are %n unread messages.",
+ mCount));
+
+ mLastUpdate = time( 0 );
+}
+
+/**
+ * Called when user selects a folder name from the popup menu. Shows
+ * the first KMMainWin in the memberlist and jumps to the first unread
+ * message in the selected folder.
+ */
+void KMSystemTray::selectedAccount(int id)
+{
+ showKMail();
+
+ KMMainWidget * mainWidget = kmkernel->getKMMainWidget();
+ if (!mainWidget)
+ {
+ kmkernel->openReader();
+ mainWidget = kmkernel->getKMMainWidget();
+ }
+
+ assert(mainWidget);
+
+ /** Select folder */
+ KMFolder * fldr = mPopupFolders.at(id);
+ if(!fldr) return;
+ KMFolderTree * ft = mainWidget->folderTree();
+ if(!ft) return;
+ QListViewItem * fldrIdx = ft->indexOfFolder(fldr);
+ if(!fldrIdx) return;
+
+ ft->setCurrentItem(fldrIdx);
+ ft->selectCurrentFolder();
+}
+
+#include "kmsystemtray.moc"
diff --git a/kmail/kmsystemtray.h b/kmail/kmsystemtray.h
new file mode 100644
index 00000000..2d2e2bf6
--- /dev/null
+++ b/kmail/kmsystemtray.h
@@ -0,0 +1,91 @@
+/***************************************************************************
+ kmsystemtray.h - description
+ -------------------
+ begin : Fri Aug 31 22:38:44 EDT 2001
+ copyright : (C) 2001 by Ryan Breen
+ email : ryan@porivo.com
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+#ifndef KMSYSTEMTRAY_H
+#define KMSYSTEMTRAY_H
+
+#include <ksystemtray.h>
+
+#include <qmap.h>
+#include <qguardedptr.h>
+#include <qvaluevector.h>
+#include <qpixmap.h>
+#include <qimage.h>
+
+#include <time.h>
+
+class KMFolder;
+class KMMainWidget;
+class QMouseEvent;
+class KPopupMenu;
+class QPoint;
+
+/**
+ * KMSystemTray extends KSystemTray and handles system
+ * tray notification for KMail
+ */
+class KMSystemTray : public KSystemTray
+{
+ Q_OBJECT
+public:
+ /** construtor */
+ KMSystemTray(QWidget* parent=0, const char *name=0);
+ /** destructor */
+ ~KMSystemTray();
+
+ void setMode(int mode);
+ int mode() const;
+
+ void hideKMail();
+
+private slots:
+ void updateNewMessageNotification(KMFolder * folder);
+ void foldersChanged();
+ void selectedAccount(int);
+ void updateNewMessages();
+
+protected:
+ void mousePressEvent(QMouseEvent *);
+ bool mainWindowIsOnCurrentDesktop();
+ void showKMail();
+ void buildPopupMenu();
+ void updateCount();
+
+ QString prettyName(KMFolder *);
+
+private:
+
+ bool mParentVisible;
+ QPoint mPosOfMainWin;
+ int mDesktopOfMainWin;
+
+ int mMode;
+ int mCount;
+ int mNewMessagePopupId;
+
+ KPopupMenu * mPopupMenu;
+ QPixmap mDefaultIcon;
+ QImage mLightIconImage;
+
+ QValueVector<KMFolder*> mPopupFolders;
+ QMap<QGuardedPtr<KMFolder>, int> mFoldersWithUnread;
+ QMap<QGuardedPtr<KMFolder>, bool> mPendingUpdates;
+ QTimer *mUpdateTimer;
+ time_t mLastUpdate;
+};
+
+#endif
diff --git a/kmail/kmtransport.cpp b/kmail/kmtransport.cpp
new file mode 100644
index 00000000..60e56d19
--- /dev/null
+++ b/kmail/kmtransport.cpp
@@ -0,0 +1,807 @@
+/**
+ * kmtransport.cpp
+ *
+ * Copyright (c) 2001-2002 Michael Haeckel <haeckel@kde.org>
+ *
+ * This program 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
+ *
+ * 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 <config.h>
+#include <assert.h>
+
+#include "kmtransport.h"
+
+#include <qbuttongroup.h>
+#include <qcheckbox.h>
+#include <qlayout.h>
+#include <klineedit.h>
+#include <qradiobutton.h>
+#include <qtabwidget.h>
+#include <qvalidator.h>
+#include <qlabel.h>
+#include <qpushbutton.h>
+#include <qwhatsthis.h>
+
+#include <kfiledialog.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kseparator.h>
+#include <kdebug.h>
+#include <kwallet.h>
+using KWallet::Wallet;
+#include <kprotocolinfo.h>
+
+#include "kmkernel.h"
+#include "kmservertest.h"
+#include "kmaccount.h"
+#include "protocols.h"
+#include "transportmanager.h"
+using namespace KMail;
+
+KMTransportInfo::KMTransportInfo() : mPasswdDirty( false ),
+ mStorePasswd( false ), mStorePasswdInConfig( false ), mId( 0 )
+{
+ name = i18n("Unnamed");
+ port = "25";
+ auth = false;
+ specifyHostname = false;
+}
+
+
+KMTransportInfo::~KMTransportInfo()
+{
+}
+
+
+void KMTransportInfo::readConfig(int id)
+{
+ KConfig *config = KMKernel::config();
+ KConfigGroupSaver saver(config, "Transport " + QString::number(id));
+ mId = config->readUnsignedNumEntry( "id", 0 );
+ type = config->readEntry("type", "smtp");
+ name = config->readEntry("name", i18n("Unnamed"));
+ host = config->readEntry("host", "localhost");
+ port = config->readEntry("port", "25");
+ user = config->readEntry("user");
+ mPasswd = KMAccount::decryptStr(config->readEntry("pass"));
+ precommand = config->readPathEntry("precommand");
+ encryption = config->readEntry("encryption");
+ authType = config->readEntry("authtype");
+ auth = config->readBoolEntry("auth");
+ mStorePasswd = config->readBoolEntry("storepass");
+ specifyHostname = config->readBoolEntry("specifyHostname", false);
+ localHostname = config->readEntry("localHostname");
+
+ if ( !storePasswd() )
+ return;
+
+ if ( !mPasswd.isEmpty() ) {
+ // migration to kwallet if available
+ if ( Wallet::isEnabled() ) {
+ config->deleteEntry( "pass" );
+ mPasswdDirty = true;
+ mStorePasswdInConfig = false;
+ writeConfig( id );
+ } else
+ mStorePasswdInConfig = true;
+ } else {
+ // read password if wallet is open, defer otherwise
+ if ( Wallet::isOpen( Wallet::NetworkWallet() ) )
+ readPassword();
+ }
+}
+
+
+void KMTransportInfo::writeConfig(int id)
+{
+ KConfig *config = KMKernel::config();
+ KConfigGroupSaver saver(config, "Transport " + QString::number(id));
+ if (!mId)
+ mId = TransportManager::createId();
+ config->writeEntry("id", mId);
+ config->writeEntry("type", type);
+ config->writeEntry("name", name);
+ config->writeEntry("host", host);
+ config->writeEntry("port", port);
+ config->writeEntry("user", user);
+ config->writePathEntry("precommand", precommand);
+ config->writeEntry("encryption", encryption);
+ config->writeEntry("authtype", authType);
+ config->writeEntry("auth", auth);
+ config->writeEntry("storepass", storePasswd());
+ config->writeEntry("specifyHostname", specifyHostname);
+ config->writeEntry("localHostname", localHostname);
+
+ if ( storePasswd() ) {
+ // write password into the wallet if possible and necessary
+ bool passwdStored = false;
+ Wallet *wallet = kmkernel->wallet();
+ if ( mPasswdDirty ) {
+ if ( wallet && wallet->writePassword( "transport-" + QString::number(mId), passwd() ) == 0 ) {
+ passwdStored = true;
+ mPasswdDirty = false;
+ mStorePasswdInConfig = false;
+ }
+ } else {
+ passwdStored = wallet ? !mStorePasswdInConfig /*already in the wallet*/ : config->hasKey("pass");
+ }
+ // wallet not available, ask the user if we should use the config file instead
+ if ( !passwdStored && ( mStorePasswdInConfig || KMessageBox::warningYesNo( 0,
+ i18n("KWallet is not available. It is strongly recommended to use "
+ "KWallet for managing your passwords.\n"
+ "However, KMail can store the password in its configuration "
+ "file instead. The password is stored in an obfuscated format, "
+ "but should not be considered secure from decryption efforts "
+ "if access to the configuration file is obtained.\n"
+ "Do you want to store the password for account '%1' in the "
+ "configuration file?").arg( name ),
+ i18n("KWallet Not Available"),
+ KGuiItem( i18n("Store Password") ),
+ KGuiItem( i18n("Do Not Store Password") ) )
+ == KMessageBox::Yes ) ) {
+ config->writeEntry( "pass", KMAccount::encryptStr( passwd() ) );
+ mStorePasswdInConfig = true;
+ }
+ }
+
+ // delete already stored password if password storage is disabled
+ if ( !storePasswd() ) {
+ if ( !Wallet::keyDoesNotExist(
+ Wallet::NetworkWallet(), "kmail", "transport-" + QString::number(mId) ) ) {
+ Wallet *wallet = kmkernel->wallet();
+ if ( wallet )
+ wallet->removeEntry( "transport-" + QString::number(mId) );
+ }
+ config->deleteEntry( "pass" );
+ }
+}
+
+
+int KMTransportInfo::findTransport(const QString &name)
+{
+ KConfig *config = KMKernel::config();
+ KConfigGroupSaver saver(config, "General");
+ int numTransports = config->readNumEntry("transports", 0);
+ for (int i = 1; i <= numTransports; i++)
+ {
+ KConfigGroupSaver saver(config, "Transport " + QString::number(i));
+ if (config->readEntry("name") == name) return i;
+ }
+ return 0;
+}
+
+
+QStringList KMTransportInfo::availableTransports()
+{
+ QStringList result;
+ KConfig *config = KMKernel::config();
+ KConfigGroupSaver saver(config, "General");
+ int numTransports = config->readNumEntry("transports", 0);
+ for (int i = 1; i <= numTransports; i++)
+ {
+ KConfigGroupSaver saver(config, "Transport " + QString::number(i));
+ result.append(config->readEntry("name"));
+ }
+ return result;
+}
+
+
+QString KMTransportInfo::passwd() const
+{
+ if ( auth && storePasswd() && mPasswd.isEmpty() )
+ readPassword();
+ return mPasswd;
+}
+
+
+void KMTransportInfo::setPasswd( const QString &passwd )
+{
+ if ( passwd != mPasswd ) {
+ mPasswd = passwd;
+ mPasswdDirty = true;
+ }
+}
+
+
+void KMTransportInfo::setStorePasswd( bool store )
+{
+ if ( mStorePasswd != store && store )
+ mPasswdDirty = true;
+ mStorePasswd = store;
+}
+
+
+void KMTransportInfo::readPassword() const
+{
+ if ( !storePasswd() || !auth )
+ return;
+
+ // ### workaround for broken Wallet::keyDoesNotExist() which returns wrong
+ // results for new entries without closing and reopening the wallet
+ if ( Wallet::isOpen( Wallet::NetworkWallet() ) ) {
+ Wallet* wallet = kmkernel->wallet();
+ if ( !wallet || !wallet->hasEntry( "transport-" + QString::number(mId) ) )
+ return;
+ } else {
+ if ( Wallet::keyDoesNotExist( Wallet::NetworkWallet(), "kmail", "transport-" + QString::number(mId) ) )
+ return;
+ }
+
+ if ( kmkernel->wallet() )
+ kmkernel->wallet()->readPassword( "transport-" + QString::number(mId), mPasswd );
+}
+
+
+KMTransportSelDlg::KMTransportSelDlg( QWidget *parent, const char *name,
+ bool modal )
+ : KDialogBase( parent, name, modal, i18n("Add Transport"), Ok|Cancel, Ok )
+{
+ QFrame *page = makeMainWidget();
+ QVBoxLayout *topLayout = new QVBoxLayout( page, 0, spacingHint() );
+
+ QButtonGroup *group = new QButtonGroup( i18n("Transport"), page );
+ connect(group, SIGNAL(clicked(int)), SLOT(buttonClicked(int)) );
+
+ topLayout->addWidget( group, 10 );
+ QVBoxLayout *vlay = new QVBoxLayout( group, spacingHint()*2, spacingHint() );
+ vlay->addSpacing( fontMetrics().lineSpacing() );
+
+ QRadioButton *radioButton1 = new QRadioButton( i18n("SM&TP"), group );
+ vlay->addWidget( radioButton1 );
+ QRadioButton *radioButton2 = new QRadioButton( i18n("&Sendmail"), group );
+ vlay->addWidget( radioButton2 );
+
+ vlay->addStretch( 10 );
+
+ radioButton1->setChecked(true); // Pop is most common ?
+ buttonClicked(0);
+}
+
+void KMTransportSelDlg::buttonClicked( int id )
+{
+ mSelectedButton = id;
+}
+
+
+int KMTransportSelDlg::selected( void ) const
+{
+ return mSelectedButton;
+}
+
+
+KMTransportDialog::KMTransportDialog( const QString & caption,
+ KMTransportInfo *transportInfo,
+ QWidget *parent, const char *name,
+ bool modal )
+ : KDialogBase( parent, name, modal, caption, Ok|Cancel, Ok, true ),
+ mServerTest( 0 ),
+ mTransportInfo( transportInfo ),
+ mAuthNone( AllAuth ), mAuthSSL( AllAuth ), mAuthTLS( AllAuth )
+{
+ assert(transportInfo != 0);
+
+ if( transportInfo->type == QString::fromLatin1("sendmail") )
+ {
+ makeSendmailPage();
+ } else {
+ makeSmtpPage();
+ }
+
+ setupSettings();
+}
+
+
+KMTransportDialog::~KMTransportDialog()
+{
+}
+
+
+void KMTransportDialog::makeSendmailPage()
+{
+ QFrame *page = makeMainWidget();
+ QVBoxLayout *topLayout = new QVBoxLayout( page, 0, spacingHint() );
+
+ mSendmail.titleLabel = new QLabel( page );
+ mSendmail.titleLabel->setText( i18n("Transport: Sendmail") );
+ QFont titleFont( mSendmail.titleLabel->font() );
+ titleFont.setBold( true );
+ mSendmail.titleLabel->setFont( titleFont );
+ topLayout->addWidget( mSendmail.titleLabel );
+ KSeparator *hline = new KSeparator( KSeparator::HLine, page);
+ topLayout->addWidget( hline );
+
+ QGridLayout *grid = new QGridLayout( topLayout, 3, 3, spacingHint() );
+ grid->addColSpacing( 1, fontMetrics().maxWidth()*15 );
+ grid->setRowStretch( 2, 10 );
+ grid->setColStretch( 1, 10 );
+
+ QLabel *label = new QLabel( i18n("&Name:"), page );
+ grid->addWidget( label, 0, 0 );
+ mSendmail.nameEdit = new KLineEdit( page );
+ label->setBuddy( mSendmail.nameEdit );
+ grid->addWidget( mSendmail.nameEdit, 0, 1 );
+
+ label = new QLabel( i18n("&Location:"), page );
+ grid->addWidget( label, 1, 0 );
+ mSendmail.locationEdit = new KLineEdit( page );
+ label->setBuddy(mSendmail.locationEdit);
+ grid->addWidget( mSendmail.locationEdit, 1, 1 );
+ mSendmail.chooseButton =
+ new QPushButton( i18n("Choos&e..."), page );
+ connect( mSendmail.chooseButton, SIGNAL(clicked()),
+ this, SLOT(slotSendmailChooser()) );
+
+ connect( mSendmail.locationEdit, SIGNAL(textChanged ( const QString & )),
+ this, SLOT(slotSendmailEditPath(const QString &)) );
+
+ mSendmail.chooseButton->setAutoDefault( false );
+ grid->addWidget( mSendmail.chooseButton, 1, 2 );
+ slotSendmailEditPath(mSendmail.locationEdit->text());
+}
+
+void KMTransportDialog::slotSendmailEditPath(const QString & _text)
+{
+ enableButtonOK( !_text.isEmpty() );
+}
+
+void KMTransportDialog::makeSmtpPage()
+{
+ QFrame *page = makeMainWidget();
+ QVBoxLayout *topLayout = new QVBoxLayout( page, 0, spacingHint() );
+
+ mSmtp.titleLabel = new QLabel( page );
+ mSmtp.titleLabel->setText( i18n("Transport: SMTP") );
+ QFont titleFont( mSmtp.titleLabel->font() );
+ titleFont.setBold( true );
+ mSmtp.titleLabel->setFont( titleFont );
+ topLayout->addWidget( mSmtp.titleLabel );
+ KSeparator *hline = new KSeparator( KSeparator::HLine, page);
+ topLayout->addWidget( hline );
+
+ QTabWidget *tabWidget = new QTabWidget(page);
+ topLayout->addWidget( tabWidget );
+
+ QWidget *page1 = new QWidget( tabWidget );
+ tabWidget->addTab( page1, i18n("&General") );
+
+ QGridLayout *grid = new QGridLayout( page1, 14, 2, spacingHint() );
+ grid->addColSpacing( 1, fontMetrics().maxWidth()*15 );
+ grid->setRowStretch( 13, 10 );
+ grid->setColStretch( 1, 10 );
+
+ QLabel *label = new QLabel( i18n("&Name:"), page1 );
+ grid->addWidget( label, 0, 0 );
+ mSmtp.nameEdit = new KLineEdit( page1 );
+ QWhatsThis::add(mSmtp.nameEdit,
+ i18n("The name that KMail will use when "
+ "referring to this server."));
+ label->setBuddy( mSmtp.nameEdit );
+ grid->addWidget( mSmtp.nameEdit, 0, 1 );
+
+ label = new QLabel( i18n("&Host:"), page1 );
+ grid->addWidget( label, 3, 0 );
+ mSmtp.hostEdit = new KLineEdit( page1 );
+ QWhatsThis::add(mSmtp.hostEdit,
+ i18n("The domain name or numerical address "
+ "of the SMTP server."));
+ label->setBuddy( mSmtp.hostEdit );
+ grid->addWidget( mSmtp.hostEdit, 3, 1 );
+
+ label = new QLabel( i18n("&Port:"), page1 );
+ grid->addWidget( label, 4, 0 );
+ mSmtp.portEdit = new KLineEdit( page1 );
+ mSmtp.portEdit->setValidator( new QIntValidator(this) );
+ QWhatsThis::add(mSmtp.portEdit,
+ i18n("The port number that the SMTP server "
+ "is listening on. The default port is 25."));
+ label->setBuddy( mSmtp.portEdit );
+ grid->addWidget( mSmtp.portEdit, 4, 1 );
+
+ label = new QLabel( i18n("Preco&mmand:"), page1 );
+ grid->addWidget( label, 5, 0 );
+ mSmtp.precommand = new KLineEdit( page1 );
+ QWhatsThis::add(mSmtp.precommand,
+ i18n("A command to run locally, prior "
+ "to sending email. This can be used "
+ "to set up ssh tunnels, for example. "
+ "Leave it empty if no command should be run."));
+ label->setBuddy(mSmtp.precommand);
+ grid->addWidget( mSmtp.precommand, 5, 1 );
+
+ QFrame* line = new QFrame( page1 );
+ line->setFrameStyle( QFrame::HLine | QFrame::Plain );
+ grid->addMultiCellWidget( line, 6, 6, 0, 1 );
+
+ mSmtp.authCheck =
+ new QCheckBox( i18n("Server &requires authentication"), page1 );
+ QWhatsThis::add(mSmtp.authCheck,
+ i18n("Check this option if your SMTP server "
+ "requires authentication before accepting "
+ "mail. This is known as "
+ "'Authenticated SMTP' or simply ASMTP."));
+ connect(mSmtp.authCheck, SIGNAL(clicked()),
+ SLOT(slotRequiresAuthClicked()));
+ grid->addMultiCellWidget( mSmtp.authCheck, 7, 7, 0, 1 );
+
+ mSmtp.loginLabel = new QLabel( i18n("&Login:"), page1 );
+ grid->addWidget( mSmtp.loginLabel, 8, 0 );
+ mSmtp.loginEdit = new KLineEdit( page1 );
+ mSmtp.loginLabel->setBuddy( mSmtp.loginEdit );
+ QWhatsThis::add(mSmtp.loginEdit,
+ i18n("The user name to send to the server "
+ "for authorization"));
+ grid->addWidget( mSmtp.loginEdit, 8, 1 );
+
+ mSmtp.passwordLabel = new QLabel( i18n("P&assword:"), page1 );
+ grid->addWidget( mSmtp.passwordLabel, 9, 0 );
+ mSmtp.passwordEdit = new KLineEdit( page1 );
+ mSmtp.passwordEdit->setEchoMode( QLineEdit::Password );
+ mSmtp.passwordLabel->setBuddy( mSmtp.passwordEdit );
+ QWhatsThis::add(mSmtp.passwordEdit,
+ i18n("The password to send to the server "
+ "for authorization"));
+ grid->addWidget( mSmtp.passwordEdit, 9, 1 );
+
+ mSmtp.storePasswordCheck =
+ new QCheckBox( i18n("&Store SMTP password"), page1 );
+ QWhatsThis::add(mSmtp.storePasswordCheck,
+ i18n("Check this option to have KMail store "
+ "the password.\nIf KWallet is available "
+ "the password will be stored there which is considered "
+ "safe.\nHowever, if KWallet is not available, "
+ "the password will be stored in KMail's configuration "
+ "file. The password is stored in an "
+ "obfuscated format, but should not be "
+ "considered secure from decryption efforts "
+ "if access to the configuration file is obtained."));
+ grid->addMultiCellWidget( mSmtp.storePasswordCheck, 10, 10, 0, 1 );
+
+ line = new QFrame( page1 );
+ line->setFrameStyle( QFrame::HLine | QFrame::Plain );
+ grid->addMultiCellWidget( line, 11, 11, 0, 1 );
+
+ mSmtp.specifyHostnameCheck =
+ new QCheckBox( i18n("Sen&d custom hostname to server"), page1 );
+ grid->addMultiCellWidget( mSmtp.specifyHostnameCheck, 12, 12, 0, 1 );
+ QWhatsThis::add(mSmtp.specifyHostnameCheck,
+ i18n("Check this option to have KMail use "
+ "a custom hostname when identifying itself "
+ "to the mail server."
+ "<p>This is useful when your system's hostname "
+ "may not be set correctly or to mask your "
+ "system's true hostname."));
+
+ mSmtp.localHostnameLabel = new QLabel( i18n("Hos&tname:"), page1 );
+ grid->addWidget( mSmtp.localHostnameLabel, 13, 0);
+ mSmtp.localHostnameEdit = new KLineEdit( page1 );
+ QWhatsThis::add(mSmtp.localHostnameEdit,
+ i18n("Enter the hostname KMail should use when "
+ "identifying itself to the server."));
+ mSmtp.localHostnameLabel->setBuddy( mSmtp.localHostnameEdit );
+ grid->addWidget( mSmtp.localHostnameEdit, 13, 1 );
+ connect( mSmtp.specifyHostnameCheck, SIGNAL(toggled(bool)),
+ mSmtp.localHostnameEdit, SLOT(setEnabled(bool)));
+ connect( mSmtp.specifyHostnameCheck, SIGNAL(toggled(bool)),
+ mSmtp.localHostnameLabel, SLOT(setEnabled(bool)));
+
+ QWidget *page2 = new QWidget( tabWidget );
+ tabWidget->addTab( page2, i18n("S&ecurity") );
+ QVBoxLayout *vlay = new QVBoxLayout( page2, spacingHint() );
+ mSmtp.encryptionGroup = new QButtonGroup( 1, Qt::Horizontal,
+ i18n("Encryption"), page2 );
+ mSmtp.encryptionNone =
+ new QRadioButton( i18n("&None"), mSmtp.encryptionGroup );
+ mSmtp.encryptionSSL =
+ new QRadioButton( i18n("&SSL"), mSmtp.encryptionGroup );
+ mSmtp.encryptionTLS =
+ new QRadioButton( i18n("&TLS"), mSmtp.encryptionGroup );
+ connect(mSmtp.encryptionGroup, SIGNAL(clicked(int)),
+ SLOT(slotSmtpEncryptionChanged(int)));
+ vlay->addWidget( mSmtp.encryptionGroup );
+
+ mSmtp.authGroup = new QButtonGroup( 1, Qt::Horizontal,
+ i18n("Authentication Method"), page2 );
+ mSmtp.authLogin = new QRadioButton( i18n("Please translate this "
+ "authentication method only if you have a good reason", "&LOGIN"),
+ mSmtp.authGroup );
+ mSmtp.authPlain = new QRadioButton( i18n("Please translate this "
+ "authentication method only if you have a good reason", "&PLAIN"),
+ mSmtp.authGroup );
+ mSmtp.authCramMd5 = new QRadioButton( i18n("CRAM-MD&5"), mSmtp.authGroup );
+ mSmtp.authDigestMd5 = new QRadioButton( i18n("&DIGEST-MD5"), mSmtp.authGroup );
+ mSmtp.authNTLM = new QRadioButton( i18n("&NTLM"), mSmtp.authGroup );
+ mSmtp.authGSSAPI = new QRadioButton( i18n("&GSSAPI"), mSmtp.authGroup );
+ if ( KProtocolInfo::capabilities("smtp").contains("SASL") == 0 ) {
+ mSmtp.authNTLM->hide();
+ mSmtp.authGSSAPI->hide();
+ }
+ vlay->addWidget( mSmtp.authGroup );
+
+ vlay->addStretch();
+
+ QHBoxLayout *buttonLay = new QHBoxLayout( vlay );
+ mSmtp.checkCapabilities =
+ new QPushButton( i18n("Check &What the Server Supports"), page2 );
+ connect(mSmtp.checkCapabilities, SIGNAL(clicked()),
+ SLOT(slotCheckSmtpCapabilities()));
+ buttonLay->addStretch();
+ buttonLay->addWidget( mSmtp.checkCapabilities );
+}
+
+
+void KMTransportDialog::setupSettings()
+{
+ if (mTransportInfo->type == "sendmail")
+ {
+ mSendmail.nameEdit->setText(mTransportInfo->name);
+ mSendmail.locationEdit->setText(mTransportInfo->host);
+ } else {
+ mSmtp.nameEdit->setText(mTransportInfo->name);
+ mSmtp.hostEdit->setText(mTransportInfo->host);
+ mSmtp.portEdit->setText(mTransportInfo->port);
+ mSmtp.authCheck->setChecked(mTransportInfo->auth);
+ mSmtp.loginEdit->setText(mTransportInfo->user);
+ mSmtp.passwordEdit->setText(mTransportInfo->passwd());
+ mSmtp.storePasswordCheck->setChecked(mTransportInfo->storePasswd());
+ mSmtp.precommand->setText(mTransportInfo->precommand);
+ mSmtp.specifyHostnameCheck->setChecked(mTransportInfo->specifyHostname);
+ mSmtp.localHostnameEdit->setText(mTransportInfo->localHostname);
+
+ if (mTransportInfo->encryption == "TLS")
+ mSmtp.encryptionTLS->setChecked(true);
+ else if (mTransportInfo->encryption == "SSL")
+ mSmtp.encryptionSSL->setChecked(true);
+ else mSmtp.encryptionNone->setChecked(true);
+
+ if (mTransportInfo->authType == "LOGIN")
+ mSmtp.authLogin->setChecked(true);
+ else if (mTransportInfo->authType == "CRAM-MD5")
+ mSmtp.authCramMd5->setChecked(true);
+ else if (mTransportInfo->authType == "DIGEST-MD5")
+ mSmtp.authDigestMd5->setChecked(true);
+ else if (mTransportInfo->authType == "NTLM")
+ mSmtp.authNTLM->setChecked(true);
+ else if (mTransportInfo->authType == "GSSAPI")
+ mSmtp.authGSSAPI->setChecked(true);
+ else mSmtp.authPlain->setChecked(true);
+
+ slotRequiresAuthClicked();
+ mSmtp.localHostnameEdit->setEnabled(mTransportInfo->specifyHostname);
+ mSmtp.localHostnameLabel->setEnabled(mTransportInfo->specifyHostname);
+ }
+}
+
+
+void KMTransportDialog::saveSettings()
+{
+ if (mTransportInfo->type == "sendmail")
+ {
+ mTransportInfo->name = mSendmail.nameEdit->text().stripWhiteSpace();
+ mTransportInfo->host = mSendmail.locationEdit->text().stripWhiteSpace();
+ } else {
+ mTransportInfo->name = mSmtp.nameEdit->text();
+ mTransportInfo->host = mSmtp.hostEdit->text().stripWhiteSpace();
+ mTransportInfo->port = mSmtp.portEdit->text().stripWhiteSpace();
+ mTransportInfo->auth = mSmtp.authCheck->isChecked();
+ mTransportInfo->user = mSmtp.loginEdit->text().stripWhiteSpace();
+ mTransportInfo->setPasswd( mSmtp.passwordEdit->text() );
+ mTransportInfo->setStorePasswd( mSmtp.storePasswordCheck->isChecked() );
+ mTransportInfo->precommand = mSmtp.precommand->text().stripWhiteSpace();
+ mTransportInfo->specifyHostname = mSmtp.specifyHostnameCheck->isChecked();
+ mTransportInfo->localHostname = mSmtp.localHostnameEdit->text().stripWhiteSpace();
+
+ mTransportInfo->encryption = (mSmtp.encryptionTLS->isChecked()) ? "TLS" :
+ (mSmtp.encryptionSSL->isChecked()) ? "SSL" : "NONE";
+
+ mTransportInfo->authType = (mSmtp.authLogin->isChecked()) ? "LOGIN" :
+ (mSmtp.authCramMd5->isChecked()) ? "CRAM-MD5" :
+ (mSmtp.authDigestMd5->isChecked()) ? "DIGEST-MD5" :
+ (mSmtp.authNTLM->isChecked()) ? "NTLM" :
+ (mSmtp.authGSSAPI->isChecked()) ? "GSSAPI" : "PLAIN";
+ }
+}
+
+
+void KMTransportDialog::slotSendmailChooser()
+{
+ KFileDialog dialog("/", QString::null, this, 0, true );
+ dialog.setCaption(i18n("Choose sendmail Location") );
+
+ if( dialog.exec() == QDialog::Accepted )
+ {
+ KURL url = dialog.selectedURL();
+ if( url.isEmpty() == true )
+ {
+ return;
+ }
+
+ if( url.isLocalFile() == false )
+ {
+ KMessageBox::sorry( 0, i18n( "Only local files allowed." ) );
+ return;
+ }
+
+ mSendmail.locationEdit->setText( url.path() );
+ }
+}
+
+
+void KMTransportDialog::slotRequiresAuthClicked()
+{
+ bool b = mSmtp.authCheck->isChecked();
+ mSmtp.loginLabel->setEnabled(b);
+ mSmtp.loginEdit->setEnabled(b);
+ mSmtp.passwordLabel->setEnabled(b);
+ mSmtp.passwordEdit->setEnabled(b);
+ mSmtp.storePasswordCheck->setEnabled(b);
+ mSmtp.authGroup->setEnabled(b);
+}
+
+
+void KMTransportDialog::slotSmtpEncryptionChanged(int id)
+{
+ kdDebug(5006) << "KMTransportDialog::slotSmtpEncryptionChanged( " << id << " )" << endl;
+ // adjust SSL port:
+ if (id == SSL || mSmtp.portEdit->text() == "465")
+ mSmtp.portEdit->setText((id == SSL) ? "465" : "25");
+
+ // switch supported auth methods:
+ QButton * old = mSmtp.authGroup->selected();
+ int authMethods = id == TLS ? mAuthTLS : id == SSL ? mAuthSSL : mAuthNone ;
+ enableAuthMethods( authMethods );
+ if ( !old->isEnabled() )
+ checkHighest( mSmtp.authGroup );
+}
+
+void KMTransportDialog::enableAuthMethods( unsigned int auth ) {
+ kdDebug(5006) << "KMTransportDialog::enableAuthMethods( " << auth << " )" << endl;
+ mSmtp.authPlain->setEnabled( auth & PLAIN );
+ // LOGIN doesn't offer anything over PLAIN, requires more server
+ // roundtrips and is not an official SASL mechanism, but a MS-ism,
+ // so only enable it if PLAIN isn't available:
+ mSmtp.authLogin->setEnabled( auth & LOGIN && !(auth & PLAIN));
+ mSmtp.authCramMd5->setEnabled( auth & CRAM_MD5 );
+ mSmtp.authDigestMd5->setEnabled( auth & DIGEST_MD5 );
+ mSmtp.authNTLM->setEnabled( auth & NTLM );
+ mSmtp.authGSSAPI->setEnabled( auth & GSSAPI );
+}
+
+unsigned int KMTransportDialog::authMethodsFromString( const QString & s ) {
+ unsigned int result = 0;
+ QStringList sl = QStringList::split( '\n', s.upper() );
+ for ( QStringList::const_iterator it = sl.begin() ; it != sl.end() ; ++it )
+ if ( *it == "SASL/LOGIN" )
+ result |= LOGIN;
+ else if ( *it == "SASL/PLAIN" )
+ result |= PLAIN;
+ else if ( *it == "SASL/CRAM-MD5" )
+ result |= CRAM_MD5;
+ else if ( *it == "SASL/DIGEST-MD5" )
+ result |= DIGEST_MD5;
+ else if ( *it == "SASL/NTLM" )
+ result |= NTLM;
+ else if ( *it == "SASL/GSSAPI" )
+ result |= GSSAPI;
+ return result;
+}
+
+unsigned int KMTransportDialog::authMethodsFromStringList( const QStringList & sl ) {
+ unsigned int result = 0;
+ for ( QStringList::const_iterator it = sl.begin() ; it != sl.end() ; ++it )
+ if ( *it == "LOGIN" )
+ result |= LOGIN;
+ else if ( *it == "PLAIN" )
+ result |= PLAIN;
+ else if ( *it == "CRAM-MD5" )
+ result |= CRAM_MD5;
+ else if ( *it == "DIGEST-MD5" )
+ result |= DIGEST_MD5;
+ else if ( *it == "NTLM" )
+ result |= NTLM;
+ else if ( *it == "GSSAPI" )
+ result |= GSSAPI;
+ return result;
+}
+
+void KMTransportDialog::slotCheckSmtpCapabilities()
+{
+ delete mServerTest;
+ mServerTest = new KMServerTest(SMTP_PROTOCOL, mSmtp.hostEdit->text(),
+ mSmtp.portEdit->text().toInt());
+ connect( mServerTest,
+ SIGNAL( capabilities( const QStringList &, const QStringList &,
+ const QString &, const QString &,
+ const QString & )),
+ this,
+ SLOT( slotSmtpCapabilities( const QStringList &,
+ const QStringList &, const QString &,
+ const QString &, const QString & ) ) );
+ mSmtp.checkCapabilities->setEnabled(false);
+}
+
+
+void KMTransportDialog::checkHighest(QButtonGroup *btnGroup)
+{
+ for ( int i = btnGroup->count() - 1; i >= 0 ; --i )
+ {
+ QButton * btn = btnGroup->find(i);
+ if (btn && btn->isEnabled())
+ {
+ btn->animateClick();
+ return;
+ }
+ }
+}
+
+
+void KMTransportDialog::slotSmtpCapabilities( const QStringList & capaNormal,
+ const QStringList & capaSSL,
+ const QString & authNone,
+ const QString & authSSL,
+ const QString & authTLS )
+{
+ mSmtp.checkCapabilities->setEnabled( true );
+ kdDebug(5006) << "KMTransportDialog::slotSmtpCapabilities( ..., "
+ << authNone << ", " << authSSL << ", " << authTLS << " )" << endl;
+ mSmtp.encryptionNone->setEnabled( !capaNormal.isEmpty() );
+ mSmtp.encryptionSSL->setEnabled( !capaSSL.isEmpty() );
+ mSmtp.encryptionTLS->setEnabled( capaNormal.findIndex("STARTTLS") != -1 );
+ if ( authNone.isEmpty() && authSSL.isEmpty() && authTLS.isEmpty() ) {
+ // slave doesn't seem to support "* AUTH METHODS" metadata (or server can't do AUTH)
+ mAuthNone = authMethodsFromStringList( capaNormal );
+ if ( mSmtp.encryptionTLS->isEnabled() )
+ mAuthTLS = mAuthNone;
+ else
+ mAuthTLS = 0;
+ mAuthSSL = authMethodsFromStringList( capaSSL );
+ }
+ else {
+ mAuthNone = authMethodsFromString( authNone );
+ mAuthSSL = authMethodsFromString( authSSL );
+ mAuthTLS = authMethodsFromString( authTLS );
+ }
+ kdDebug(5006) << "mAuthNone = " << mAuthNone
+ << "; mAuthSSL = " << mAuthSSL
+ << "; mAuthTLS = " << mAuthTLS << endl;
+ checkHighest( mSmtp.encryptionGroup );
+ delete mServerTest;
+ mServerTest = 0;
+}
+bool KMTransportDialog::sanityCheckSmtpInput()
+{
+ // FIXME: add additional checks for all fields that needs it
+ // this is only the beginning
+ if ( mSmtp.hostEdit->text().isEmpty() ) {
+ QString errorMsg = i18n("The Host field cannot be empty. Please "
+ "enter the name or the IP address of the SMTP server.");
+ KMessageBox::sorry( this, errorMsg, i18n("Invalid Hostname or Address") );
+ return false;
+ }
+ return true;
+}
+
+void KMTransportDialog::slotOk()
+{
+ if (mTransportInfo->type != "sendmail") {
+ if( !sanityCheckSmtpInput() ) {
+ return;
+ }
+ }
+
+ saveSettings();
+ accept();
+}
+
+
+#include "kmtransport.moc"
diff --git a/kmail/kmtransport.h b/kmail/kmtransport.h
new file mode 100644
index 00000000..3f4c57a3
--- /dev/null
+++ b/kmail/kmtransport.h
@@ -0,0 +1,169 @@
+/*
+ * kmtransport.h
+ *
+ * Copyright (c) 2001-2002 Michael Haeckel <haeckel@kde.org>
+ *
+ * This program 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
+ *
+ * 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 _KMTRANSPORT_H_
+#define _KMTRANSPORT_H_
+
+#include <kdialogbase.h>
+
+class QCheckBox;
+class QLabel;
+class QLineEdit;
+class QRadioButton;
+class KMServerTest;
+class QButtonGroup;
+
+class KMTransportInfo : public QObject
+{
+public:
+ KMTransportInfo();
+ virtual ~KMTransportInfo();
+ void readConfig(int id);
+ void writeConfig(int id);
+ static int findTransport(const QString &name);
+ static QStringList availableTransports();
+ uint id() const { return mId; }
+
+ /** Get/set password for this account */
+ QString passwd() const;
+ void setPasswd( const QString& passwd );
+
+ /** Get/set password storage flag */
+ bool storePasswd() const { return mStorePasswd; }
+ void setStorePasswd( bool store );
+
+ /** Read password from wallet */
+ void readPassword() const;
+
+ QString type, name, host, port, user, precommand, encryption, authType;
+ QString localHostname;
+ bool auth, specifyHostname;
+
+ private:
+ mutable QString mPasswd;
+ bool mPasswdDirty, mStorePasswd, mStorePasswdInConfig;
+ uint mId;
+};
+
+class KMTransportSelDlg : public KDialogBase
+{
+ Q_OBJECT
+
+public:
+ KMTransportSelDlg( QWidget *parent=0, const char *name=0, bool modal=TRUE );
+ int selected() const;
+
+private slots:
+ void buttonClicked( int id );
+
+private:
+ int mSelectedButton;
+};
+
+class KMTransportDialog : public KDialogBase
+{
+ Q_OBJECT
+
+public:
+ KMTransportDialog( const QString & caption, KMTransportInfo *transportInfo,
+ QWidget *parent=0, const char *name=0, bool modal=TRUE );
+ virtual ~KMTransportDialog();
+
+private slots:
+ virtual void slotOk();
+ void slotSendmailChooser();
+ void slotRequiresAuthClicked();
+ void slotSmtpEncryptionChanged(int);
+ void slotCheckSmtpCapabilities();
+ void slotSmtpCapabilities( const QStringList &, const QStringList &,
+ const QString &, const QString &,
+ const QString & );
+ void slotSendmailEditPath(const QString &);
+private:
+ struct SendmailWidgets
+ {
+ QLabel *titleLabel;
+ QLineEdit *nameEdit;
+ QLineEdit *locationEdit;
+ QPushButton *chooseButton;
+ };
+ struct SmtpWidgets
+ {
+ QLabel *titleLabel;
+ QLineEdit *nameEdit;
+ QLineEdit *hostEdit;
+ QLineEdit *portEdit;
+ QCheckBox *authCheck;
+ QLabel *loginLabel;
+ QLineEdit *loginEdit;
+ QLabel *passwordLabel;
+ QLineEdit *passwordEdit;
+ QLineEdit *precommand;
+ QButtonGroup *encryptionGroup;
+ QRadioButton *encryptionNone;
+ QRadioButton *encryptionSSL;
+ QRadioButton *encryptionTLS;
+ QButtonGroup *authGroup;
+ QRadioButton *authPlain;
+ QRadioButton *authLogin;
+ QRadioButton *authCramMd5;
+ QRadioButton *authDigestMd5;
+ QRadioButton *authNTLM;
+ QRadioButton *authGSSAPI;
+ QPushButton *checkCapabilities;
+ QCheckBox *storePasswordCheck;
+ QCheckBox *specifyHostnameCheck;
+ QLineEdit *localHostnameEdit;
+ QLabel *localHostnameLabel;
+ };
+
+ void makeSendmailPage();
+ void makeSmtpPage();
+ void setupSettings();
+ void saveSettings();
+ void checkHighest( QButtonGroup * );
+ void enableAuthMethods( unsigned int which );
+ bool sanityCheckSmtpInput();
+ static unsigned int authMethodsFromString( const QString & s );
+ static unsigned int authMethodsFromStringList( const QStringList & sl );
+
+ KMServerTest *mServerTest;
+ SmtpWidgets mSmtp;
+ SendmailWidgets mSendmail;
+ KMTransportInfo *mTransportInfo;
+ enum EncryptionMethods {
+ NoEncryption = 0,
+ SSL = 1,
+ TLS = 2
+ };
+ enum AuthMethods {
+ NoAuth = 0,
+ LOGIN = 1,
+ PLAIN = 2,
+ CRAM_MD5 = 4,
+ DIGEST_MD5 = 8,
+ NTLM = 16,
+ GSSAPI = 32,
+ AllAuth = 0xffffffff
+ };
+ unsigned int mAuthNone, mAuthSSL, mAuthTLS;
+};
+
+
+#endif
diff --git a/kmail/kmversion.h b/kmail/kmversion.h
new file mode 100644
index 00000000..8a7e6858
--- /dev/null
+++ b/kmail/kmversion.h
@@ -0,0 +1,8 @@
+// KMail Version Information
+//
+#ifndef kmversion_h
+#define kmversion_h
+
+#define KMAIL_VERSION "1.9.10"
+
+#endif /*kmversion_h*/
diff --git a/kmail/korghelper.cpp b/kmail/korghelper.cpp
new file mode 100644
index 00000000..57968bd9
--- /dev/null
+++ b/kmail/korghelper.cpp
@@ -0,0 +1,53 @@
+/*
+ Copyright (c) 2007 Volker Krause <vkrause@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "korghelper.h"
+
+#include <dcopclient.h>
+#include <dcopref.h>
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kdcopservicestarter.h>
+
+void KMail::KorgHelper::ensureRunning()
+{
+ QString error;
+ QCString dcopService;
+ int result = KDCOPServiceStarter::self()->findServiceFor( "DCOP/Organizer", QString::null, QString::null, &error, &dcopService );
+ if ( result == 0 ) {
+ // OK, so korganizer (or kontact) is running. Now ensure the object we want is available
+ // [that's not the case when kontact was already running, but korganizer not loaded into it...]
+ static const char* const dcopObjectId = "KOrganizerIface";
+ QCString dummy;
+ if ( !kapp->dcopClient()->findObject( dcopService, dcopObjectId, "", QByteArray(), dummy, dummy ) ) {
+ DCOPRef ref( dcopService, dcopService ); // talk to the KUniqueApplication or its kontact wrapper
+ DCOPReply reply = ref.call( "load()" );
+ if ( reply.isValid() && (bool)reply ) {
+ kdDebug() << "Loaded " << dcopService << " successfully" << endl;
+ Q_ASSERT( kapp->dcopClient()->findObject( dcopService, dcopObjectId, "", QByteArray(), dummy, dummy ) );
+ } else
+ kdWarning() << "Error loading " << dcopService << endl;
+ }
+
+ // We don't do anything with it, we just need it to be running so that it handles
+ // the incoming directory.
+ }
+ else
+ kdWarning() << "Couldn't start DCOP/Organizer: " << dcopService << " " << error << endl;
+}
diff --git a/kmail/korghelper.h b/kmail/korghelper.h
new file mode 100644
index 00000000..eeb3f181
--- /dev/null
+++ b/kmail/korghelper.h
@@ -0,0 +1,32 @@
+/*
+ Copyright (c) 2007 Volker Krause <vkrause@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KMAIL_KORGHELPER_H
+#define KMAIL_KORGHELPER_H
+
+namespace KMail {
+
+namespace KorgHelper
+{
+ /** ensure that korganizer is running standalone or in Kontact */
+ void ensureRunning();
+}
+}
+
+#endif
diff --git a/kmail/kwindowpositioner.cpp b/kmail/kwindowpositioner.cpp
new file mode 100644
index 00000000..c99ab7c0
--- /dev/null
+++ b/kmail/kwindowpositioner.cpp
@@ -0,0 +1,60 @@
+/*
+ This file is part of KDE.
+
+ Copyright (c) 2005 Cornelius Schumacher <schumacher@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "kwindowpositioner.h"
+
+#include <kdebug.h>
+
+#include <qwidget.h>
+
+KWindowPositioner::KWindowPositioner( QWidget *master, QWidget *slave,
+ Mode mode )
+ : QObject( master ), mMaster( master ), mSlave( slave ), mMode( mode )
+{
+ master->topLevelWidget()->installEventFilter( this );
+}
+
+bool KWindowPositioner::eventFilter( QObject *, QEvent *e )
+{
+ if ( e->type() == QEvent::Move ) {
+ reposition();
+ }
+
+ return false;
+}
+
+void KWindowPositioner::reposition()
+{
+ QPoint relativePos;
+ if ( mMode == Right ) {
+ relativePos = QPoint( mMaster->width(), -100 );
+ } else if ( mMode == Bottom ) {
+ relativePos = QPoint( 100 - mSlave->width() + mMaster->width(),
+ mMaster->height() );
+ } else {
+ kdError() << "KWindowPositioner: Illegal mode" << endl;
+ }
+ QPoint pos = mMaster->mapToGlobal( relativePos );
+ mSlave->move( pos );
+ mSlave->raise();
+}
+
+#include "kwindowpositioner.moc"
diff --git a/kmail/kwindowpositioner.h b/kmail/kwindowpositioner.h
new file mode 100644
index 00000000..1b5ad97d
--- /dev/null
+++ b/kmail/kwindowpositioner.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of KDE.
+
+ Copyright (c) 2005 Cornelius Schumacher <schumacher@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef KWINDOWPOSITIONER_H
+#define KWINDOWPOSITIONER_H
+
+#include <qobject.h>
+
+class QWindow;
+
+class KWindowPositioner : public QObject
+{
+ Q_OBJECT
+ public:
+ enum Mode { Right, Bottom };
+
+ KWindowPositioner( QWidget *master, QWidget *slave, Mode mode = Bottom );
+
+ bool eventFilter( QObject *watched, QEvent *e );
+
+ void reposition();
+
+ private:
+ QWidget *mMaster;
+ QWidget *mSlave;
+
+ Mode mMode;
+};
+
+#endif
diff --git a/kmail/listjob.cpp b/kmail/listjob.cpp
new file mode 100644
index 00000000..341e934a
--- /dev/null
+++ b/kmail/listjob.cpp
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2004 Carsten Burghardt <burghardt@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#include "listjob.h"
+#include "kmessagebox.h"
+#include "kmfolderimap.h"
+#include "kmfoldercachedimap.h"
+#include "kmacctimap.h"
+#include "kmacctcachedimap.h"
+#include "folderstorage.h"
+#include "kmfolder.h"
+#include "progressmanager.h"
+using KPIM::ProgressManager;
+
+#include <kdebug.h>
+#include <kurl.h>
+#include <kio/scheduler.h>
+#include <kio/job.h>
+#include <kio/global.h>
+#include <klocale.h>
+
+#include <qstylesheet.h>
+
+#include <stdlib.h>
+
+using namespace KMail;
+
+ListJob::ListJob( ImapAccountBase* account, ImapAccountBase::ListType type,
+ FolderStorage* storage, const QString& path, bool complete,
+ KPIM::ProgressItem* item )
+ : FolderJob( 0, tOther, (storage ? storage->folder() : 0) ),
+ mStorage( storage ), mAccount( account ), mType( type ),
+ mComplete( complete ),
+ mHonorLocalSubscription( false ), mPath( path ),
+ mParentProgressItem( item )
+{
+}
+
+ListJob::~ListJob()
+{
+}
+
+void ListJob::execute()
+{
+ if ( mAccount->makeConnection() == ImapAccountBase::Error )
+ {
+ kdWarning(5006) << "ListJob - got no connection" << endl;
+ delete this;
+ return;
+ } else if ( mAccount->makeConnection() == ImapAccountBase::Connecting )
+ {
+ // We'll wait for the connectionResult signal from the account.
+ kdDebug(5006) << "ListJob - waiting for connection" << endl;
+ connect( mAccount, SIGNAL( connectionResult(int, const QString&) ),
+ this, SLOT( slotConnectionResult(int, const QString&) ) );
+ return;
+ }
+ // this is needed until we have a common base class for d(imap)
+ if ( mPath.isEmpty() )
+ {
+ if ( mStorage && mStorage->folderType() == KMFolderTypeImap ) {
+ mPath = static_cast<KMFolderImap*>(mStorage)->imapPath();
+ } else if ( mStorage && mStorage->folderType() == KMFolderTypeCachedImap ) {
+ mPath = static_cast<KMFolderCachedImap*>(mStorage)->imapPath();
+ } else {
+ kdError(5006) << "ListJob - no valid path and no folder given" << endl;
+ delete this;
+ return;
+ }
+ }
+ if ( mNamespace.isEmpty() && mStorage )
+ {
+ mNamespace = mAccount->namespaceForFolder( mStorage );
+ }
+ // create jobData
+ ImapAccountBase::jobData jd;
+ jd.total = 1; jd.done = 0;
+ jd.cancellable = true;
+ jd.parent = mDestFolder;
+ jd.onlySubscribed = ( mType == ImapAccountBase::ListSubscribed ||
+ mType == ImapAccountBase::ListSubscribedNoCheck ||
+ mType == ImapAccountBase::ListFolderOnlySubscribed );
+ jd.path = mPath;
+ jd.curNamespace = mNamespace;
+ if ( mParentProgressItem )
+ {
+ QString escapedStatus = mDestFolder ? QStyleSheet::escape( mDestFolder->prettyURL() )
+ : QString::null;
+ jd.progressItem = ProgressManager::createProgressItem(
+ mParentProgressItem,
+ "ListDir" + ProgressManager::getUniqueID(),
+ escapedStatus,
+ i18n("retrieving folders"),
+ false,
+ mAccount->useSSL() || mAccount->useTLS() );
+ mParentProgressItem->setStatus( escapedStatus );
+ }
+
+ // make the URL
+ QString ltype = "LIST";
+ if ( mType == ImapAccountBase::ListSubscribed ||
+ mType == ImapAccountBase::ListFolderOnlySubscribed )
+ ltype = "LSUB";
+ else if ( mType == ImapAccountBase::ListSubscribedNoCheck )
+ ltype = "LSUBNOCHECK";
+
+ QString section;
+ if ( mComplete )
+ section = ";SECTION=COMPLETE";
+ else if ( mType == ImapAccountBase::ListFolderOnly ||
+ mType == ImapAccountBase::ListFolderOnlySubscribed )
+ section = ";SECTION=FOLDERONLY";
+
+ KURL url = mAccount->getUrl();
+ url.setPath( mPath
+ + ";TYPE=" + ltype
+ + section );
+ // go
+ //kdDebug(5006) << "start listjob for " << url.path() << endl;
+ KIO::SimpleJob *job = KIO::listDir( url, false );
+ KIO::Scheduler::assignJobToSlave( mAccount->slave(), job );
+ mAccount->insertJob( job, jd );
+ connect( job, SIGNAL(result(KIO::Job *)),
+ this, SLOT(slotListResult(KIO::Job *)) );
+ connect( job, SIGNAL(entries(KIO::Job *, const KIO::UDSEntryList &)),
+ this, SLOT(slotListEntries(KIO::Job *, const KIO::UDSEntryList &)) );
+}
+
+void ListJob::slotConnectionResult( int errorCode, const QString& errorMsg )
+{
+ Q_UNUSED( errorMsg );
+ if ( !errorCode )
+ execute();
+ else {
+ if ( mParentProgressItem )
+ mParentProgressItem->setComplete();
+ delete this;
+ }
+}
+
+void ListJob::slotListResult( KIO::Job* job )
+{
+ ImapAccountBase::JobIterator it = mAccount->findJob( job );
+ if ( it == mAccount->jobsEnd() )
+ {
+ delete this;
+ return;
+ }
+ if ( job->error() )
+ {
+ mAccount->handleJobError( job,
+ i18n( "Error while listing folder %1: " ).arg((*it).path),
+ true );
+ } else
+ {
+ // transport the information, include the jobData
+ emit receivedFolders( mSubfolderNames, mSubfolderPaths,
+ mSubfolderMimeTypes, mSubfolderAttributes, *it );
+ mAccount->removeJob( it );
+ }
+ delete this;
+}
+
+void ListJob::slotListEntries( KIO::Job* job, const KIO::UDSEntryList& uds )
+{
+ ImapAccountBase::JobIterator it = mAccount->findJob( job );
+ if ( it == mAccount->jobsEnd() )
+ {
+ delete this;
+ return;
+ }
+ if( (*it).progressItem )
+ (*it).progressItem->setProgress( 50 );
+ QString name;
+ KURL url;
+ QString mimeType;
+ QString attributes;
+ for ( KIO::UDSEntryList::ConstIterator udsIt = uds.begin();
+ udsIt != uds.end(); udsIt++ )
+ {
+ mimeType = QString::null;
+ attributes = QString::null;
+ for ( KIO::UDSEntry::ConstIterator eIt = (*udsIt).begin();
+ eIt != (*udsIt).end(); eIt++ )
+ {
+ // get the needed information
+ if ( (*eIt).m_uds == KIO::UDS_NAME )
+ name = (*eIt).m_str;
+ else if ( (*eIt).m_uds == KIO::UDS_URL )
+ url = KURL((*eIt).m_str, 106); // utf-8
+ else if ( (*eIt).m_uds == KIO::UDS_MIME_TYPE )
+ mimeType = (*eIt).m_str;
+ else if ( (*eIt).m_uds == KIO::UDS_EXTRA )
+ attributes = (*eIt).m_str;
+ }
+ if ( (mimeType == "inode/directory" || mimeType == "message/digest"
+ || mimeType == "message/directory")
+ && name != ".." && (mAccount->hiddenFolders() || name.at(0) != '.') )
+ {
+ if ( mHonorLocalSubscription && mAccount->onlyLocallySubscribedFolders()
+ && !mAccount->locallySubscribedTo( url.path() ) ) {
+ continue;
+ }
+
+ // Some servers send _lots_ of duplicates
+ // This check is too slow for huge lists
+ if ( mSubfolderPaths.count() > 100 ||
+ mSubfolderPaths.findIndex(url.path()) == -1 )
+ {
+ mSubfolderNames.append( name );
+ mSubfolderPaths.append( url.path() );
+ mSubfolderMimeTypes.append( mimeType );
+ mSubfolderAttributes.append( attributes );
+ }
+ }
+ }
+}
+
+
+void KMail::ListJob::setHonorLocalSubscription( bool value )
+{
+ mHonorLocalSubscription = value;
+}
+
+bool KMail::ListJob::honorLocalSubscription() const
+{
+ return mHonorLocalSubscription;
+}
+
+#include "listjob.moc"
diff --git a/kmail/listjob.h b/kmail/listjob.h
new file mode 100644
index 00000000..75213f5c
--- /dev/null
+++ b/kmail/listjob.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2004 Carsten Burghardt <burghardt@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+#ifndef LISTJOB_H
+#define LISTJOB_H
+
+#include "folderjob.h"
+#include "imapaccountbase.h"
+
+class KMFolderImap;
+class KMFolderCachedImap;
+class KMAcctImap;
+class KMAcctCachedImap;
+class FolderStorage;
+class KURL;
+
+namespace KIO {
+ class Job;
+}
+
+namespace KPIM {
+ class ProgressItem;
+}
+
+namespace KMail {
+
+/**
+ * Generic folder list job for (d)imap accounts
+ */
+class ListJob : public FolderJob
+{
+ Q_OBJECT
+public:
+ /**
+ * Create a new job
+ * @param storage the parent folder, either provide this or a path
+ * @param account the ImapAccountBase
+ * @param type Type of subscription
+ * @param complete list all folders or only next level
+ * @param path the listing path;
+ * if empty the path of the folder will be taken
+ * @param item a parent ProgressItem
+ */
+ ListJob( ImapAccountBase* account, ImapAccountBase::ListType type,
+ FolderStorage* storage = 0, const QString& path = QString::null,
+ bool complete = false, KPIM::ProgressItem* item = 0 );
+
+ virtual ~ListJob();
+
+ /**
+ * Set whether the listing should include only folders that the
+ * account is subscribed to locally. This is different from the server
+ * side subscription managed by the ctor parameter.
+ */
+ void setHonorLocalSubscription( bool value );
+
+ /**
+ * Return whether the listing includes only folders that the
+ * account is subscribed to locally. This is different from the server
+ * side subscription managed by the ctor parameter.
+ */
+ bool honorLocalSubscription() const;
+
+ virtual void execute();
+
+ /** Path */
+ void setPath( const QString& path ) { mPath = path; }
+
+ /** Storage */
+ void setStorage( FolderStorage* st ) { mStorage = st; }
+
+ /** Set this to true for a complete listing */
+ void setComplete( bool complete ) { mComplete = complete; }
+
+ /** Set parent progress item */
+ void setParentProgressItem( KPIM::ProgressItem* it ) {
+ mParentProgressItem = it; }
+
+ /** Set the namespace for this listing */
+ void setNamespace( const QString& ns ) { mNamespace = ns; }
+
+protected slots:
+ /**
+ * Is called when the listing is done
+ * Passes the folders and the jobData to the responding folder
+ */
+ void slotListResult( KIO::Job* job );
+
+ /**
+ * Collects the folder information
+ */
+ void slotListEntries( KIO::Job* job, const KIO::UDSEntryList& uds );
+
+ /**
+ * Called from the account when a connection was established
+ */
+ void slotConnectionResult( int errorCode, const QString& errorMsg );
+
+signals:
+ /**
+ * Emitted when new folders have been received
+ */
+ void receivedFolders( const QStringList&, const QStringList&,
+ const QStringList&, const QStringList&, const ImapAccountBase::jobData& );
+
+protected:
+ FolderStorage* mStorage;
+ ImapAccountBase* mAccount;
+ ImapAccountBase::ListType mType;
+ bool mComplete;
+ bool mHonorLocalSubscription;
+ QString mPath;
+ QStringList mSubfolderNames, mSubfolderPaths,
+ mSubfolderMimeTypes, mSubfolderAttributes;
+ KPIM::ProgressItem* mParentProgressItem;
+ QString mNamespace;
+};
+
+} // namespace
+
+#endif /* LISTJOB_H */
+
diff --git a/kmail/localsubscriptiondialog.cpp b/kmail/localsubscriptiondialog.cpp
new file mode 100644
index 00000000..ad01c220
--- /dev/null
+++ b/kmail/localsubscriptiondialog.cpp
@@ -0,0 +1,148 @@
+/* -*- c++ -*-
+ localsubscriptiondialog.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (C) 2006 Till Adam <adam@kde.org>
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+
+#include "localsubscriptiondialog.h"
+#include "kmkernel.h"
+#include "accountmanager.h"
+#include "kmmessage.h"
+#include "imapaccountbase.h"
+#include "listjob.h"
+
+namespace KMail {
+
+LocalSubscriptionDialog::LocalSubscriptionDialog( QWidget *parent, const QString &caption,
+ ImapAccountBase *acct, QString startPath )
+ : SubscriptionDialog( parent, caption, acct, startPath ),
+ mAccount( acct )
+{
+}
+
+/* virtual */
+LocalSubscriptionDialog::~LocalSubscriptionDialog()
+{
+
+}
+
+void LocalSubscriptionDialog::listAllAvailableAndCreateItems()
+{
+ if ( mAccount->onlySubscribedFolders() )
+ mSubscribed = true;
+ SubscriptionDialog::listAllAvailableAndCreateItems();
+}
+
+/* virtual */
+void LocalSubscriptionDialog::processFolderListing()
+{
+ uint done = 0;
+ for (uint i = mCount; i < mFolderNames.count(); ++i)
+ {
+ // give the dialog a chance to repaint
+ if (done == 1000)
+ {
+ emit listChanged();
+ QTimer::singleShot(0, this, SLOT(processItems()));
+ return;
+ }
+ ++mCount;
+ ++done;
+ createListViewItem( i );
+ }
+
+ if ( mPrefixList.isEmpty() && !mSubscribed )
+ loadingComplete(); // no need to load subscribed folders
+ else
+ processNext();
+}
+
+void LocalSubscriptionDialog::setCheckedStateOfAllItems()
+{
+ // iterate over all items and check them, unless they are
+ // in the account's local subscription blacklist
+ QDictIterator<GroupItem> it( mItemDict );
+ for ( ; it.current(); ++it ) {
+ GroupItem *item = it.current();
+ QString path = it.currentKey();
+ item->setOn( mAccount->locallySubscribedTo( path ) );
+ }
+}
+
+/*virtual*/
+void LocalSubscriptionDialog::doSave()
+{
+ bool somethingHappened = false;
+ // subscribe
+ QListViewItemIterator it(subView);
+ for ( ; it.current(); ++it) {
+ static_cast<ImapAccountBase*>(account())->changeLocalSubscription(
+ static_cast<GroupItem*>(it.current())->info().path, true );
+ somethingHappened = true;
+ }
+
+ // unsubscribe
+ QListViewItemIterator it2(unsubView);
+ if ( unsubView->childCount() > 0 ) {
+ const QString message = i18n("Locally unsubscribing from folders will remove all "
+ "information that is present locally about those folders. The folders will "
+ "not be changed on the server. Press cancel now if you want to make sure "
+ "all local changes have been written to the server by checking mail first.");
+ const QString caption = i18n("Local changes will be lost when unsubscribing");
+ if ( KMessageBox::warningContinueCancel( this, message, caption )
+ != KMessageBox::Cancel ) {
+ somethingHappened = true;
+ for ( ; it2.current(); ++it2) {
+ static_cast<ImapAccountBase*>(account())->changeLocalSubscription(
+ static_cast<GroupItem*>(it2.current())->info().path, false );
+ }
+
+ }
+ }
+ if ( somethingHappened ) {
+ kmkernel->acctMgr()->singleCheckMail( mAccount, true);
+ }
+}
+
+void LocalSubscriptionDialog::loadingComplete()
+{
+ setCheckedStateOfAllItems();
+ SubscriptionDialog::loadingComplete();
+}
+
+} // namespace
+
+#include "localsubscriptiondialog.moc"
diff --git a/kmail/localsubscriptiondialog.h b/kmail/localsubscriptiondialog.h
new file mode 100644
index 00000000..e38b06e9
--- /dev/null
+++ b/kmail/localsubscriptiondialog.h
@@ -0,0 +1,67 @@
+/* -*- c++ -*-
+ localsubscriptiondialog.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (C) 2006 Till Adam <adam@kde.org>
+
+ 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __LOCALSUBSCRIPTIONDIALOG
+#define __LOCALSUBSCRIPTIONDIALOG
+
+#include "subscriptiondialog.h"
+
+namespace KMail {
+ class ImapAccountBase;
+
+ class LocalSubscriptionDialog: public SubscriptionDialog
+ {
+ Q_OBJECT
+
+ public:
+ LocalSubscriptionDialog( QWidget *parent, const QString &caption,
+ ImapAccountBase* acct,
+ QString startPath = QString::null );
+ virtual ~LocalSubscriptionDialog();
+
+ protected:
+ /** reimpl */
+ virtual void listAllAvailableAndCreateItems();
+ /** reimpl */
+ virtual void processFolderListing();
+ /** reimpl */
+ virtual void doSave();
+ virtual void loadingComplete();
+
+ private:
+ void setCheckedStateOfAllItems();
+
+ ImapAccountBase* mAccount;
+ };
+
+} // namespace KMail
+
+#endif
diff --git a/kmail/mailcomposerIface.h b/kmail/mailcomposerIface.h
new file mode 100644
index 00000000..28586578
--- /dev/null
+++ b/kmail/mailcomposerIface.h
@@ -0,0 +1,53 @@
+#ifndef MAILCOMPOSERIFACE_H
+#define MAILCOMPOSERIFACE_H
+
+#include <dcopobject.h>
+#include <kurl.h>
+
+/**
+ DCOP interface for mail composer window. The address header fields are set,
+ when the composer is constructed. KMailIface::openComposer() returns a
+ reference to the DCOP interface of the new composer window, which provides the
+ functions defined in the MailComposerIface.
+*/
+class MailComposerIface : virtual public DCOPObject
+{
+ K_DCOP
+ k_dcop:
+ /**
+ Send message.
+
+ @param how 0 for deafult method, 1 for sending now, 2 for sending later.
+ */
+ virtual void send(int how) = 0;
+ /**
+ Add url as attachment with a user-defined comment.
+ */
+ virtual void addAttachment(KURL url,QString comment) = 0;
+ /**
+ Set message body.
+ */
+ virtual void setBody (QString body) = 0;
+ /**
+ Add attachment.
+
+ @param name Name of Attachment
+ @param cte Content Transfer Encoding
+ @param data Data to be attached
+ @param type MIME content type
+ @param subType MIME content sub type
+ @param paramAttr Attribute name of parameter of content type
+ @param paramValue Value of parameter of content type
+ @param contDisp Content disposition
+ */
+ virtual void addAttachment(const QString &name,
+ const QCString &cte,
+ const QByteArray &data,
+ const QCString &type,
+ const QCString &subType,
+ const QCString &paramAttr,
+ const QString &paramValue,
+ const QCString &contDisp) = 0;
+};
+
+#endif
diff --git a/kmail/maildirjob.cpp b/kmail/maildirjob.cpp
new file mode 100644
index 00000000..424ea434
--- /dev/null
+++ b/kmail/maildirjob.cpp
@@ -0,0 +1,120 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ *
+ * This file is part of KMail, the KDE mail client.
+ * Copyright (c) 2003 Zack Rusin <zack@kde.org>
+ *
+ * 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 Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "maildirjob.h"
+
+#include "kmfoldermaildir.h"
+#include "kmfolder.h"
+
+#include <kapplication.h>
+#include <kdebug.h>
+
+#include <qtimer.h>
+#include <qdatetime.h>
+
+namespace KMail {
+
+
+//-----------------------------------------------------------------------------
+MaildirJob::MaildirJob( KMMessage *msg, JobType jt , KMFolder *folder )
+ : FolderJob( msg, jt, folder ), mParentFolder( 0 )
+{
+}
+
+//-----------------------------------------------------------------------------
+MaildirJob::MaildirJob( QPtrList<KMMessage>& msgList, const QString& sets,
+ JobType jt , KMFolder *folder )
+ : FolderJob( msgList, sets, jt, folder ), mParentFolder( 0 )
+{
+}
+
+//-----------------------------------------------------------------------------
+MaildirJob::~MaildirJob()
+{
+}
+
+//-----------------------------------------------------------------------------
+void MaildirJob::setParentFolder( const KMFolderMaildir* parent )
+{
+ mParentFolder = const_cast<KMFolderMaildir*>( parent );
+}
+
+
+
+//-----------------------------------------------------------------------------
+void MaildirJob::execute()
+{
+ QTimer::singleShot( 0, this, SLOT(startJob()) );
+}
+
+//-----------------------------------------------------------------------------
+void MaildirJob::startJob()
+{
+ switch( mType ) {
+ case tGetMessage:
+ {
+ KMMessage* msg = mMsgList.first();
+ if ( msg ) {
+ msg->setComplete( true );
+ emit messageRetrieved( msg );
+ }
+ }
+ break;
+ case tDeleteMessage:
+ {
+ static_cast<KMFolder*>(mParentFolder->folder())->removeMsg( mMsgList );
+ }
+ break;
+ case tPutMessage:
+ {
+ mParentFolder->addMsg( mMsgList.first() );
+ emit messageStored( mMsgList.first() );
+ }
+ break;
+ case tCopyMessage:
+ case tCreateFolder:
+ case tGetFolder:
+ case tListMessages:
+ kdDebug(5006)<<k_funcinfo<<"### Serious problem! "<<endl;
+ break;
+ default:
+ break;
+ }
+ //OK, we're done
+ //delete this;
+ deleteLater();
+}
+
+}
+
+#include "maildirjob.moc"
diff --git a/kmail/maildirjob.h b/kmail/maildirjob.h
new file mode 100644
index 00000000..e94558cb
--- /dev/null
+++ b/kmail/maildirjob.h
@@ -0,0 +1,61 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ *
+ * This file is part of KMail, the KDE mail client.
+ * Copyright (c) 2003 Zack Rusin <zack@kde.org>
+ *
+ * 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 Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#ifndef MAILDIRJOB_H
+#define MAILDIRJOB_H
+
+#include "folderjob.h"
+
+class KMFolderMaildir;
+class KMMessage;
+
+namespace KMail {
+
+class MaildirJob : public FolderJob
+{
+ Q_OBJECT
+public:
+ MaildirJob( KMMessage *msg, JobType jt = tGetMessage, KMFolder *folder = 0 );
+ MaildirJob( QPtrList<KMMessage>& msgList, const QString& sets,
+ JobType jt = tGetMessage, KMFolder *folder = 0 );
+ virtual ~MaildirJob();
+
+ void setParentFolder( const KMFolderMaildir* parent );
+protected:
+ void execute();
+protected slots:
+ void startJob();
+private:
+ KMFolderMaildir* mParentFolder;
+};
+
+}
+
+#endif
diff --git a/kmail/mailinglist-magic.cpp b/kmail/mailinglist-magic.cpp
new file mode 100644
index 00000000..25b2abd0
--- /dev/null
+++ b/kmail/mailinglist-magic.cpp
@@ -0,0 +1,414 @@
+// -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "mailinglist-magic.h"
+
+#include "kmmessage.h"
+
+#include <kconfig.h>
+#include <kurl.h>
+#include <kdebug.h>
+
+#include <qstringlist.h>
+
+using namespace KMail;
+
+typedef QString (*MagicDetectorFunc) (const KMMessage *, QCString &, QString &);
+
+/* Sender: (owner-([^@]+)|([^@+]-owner)@ */
+static QString check_sender(const KMMessage *message,
+ QCString &header_name,
+ QString &header_value )
+{
+ QString header = message->headerField( "Sender" );
+
+ if ( header.isEmpty() )
+ return QString::null;
+
+ if ( header.left( 6 ) == "owner-" )
+ {
+ header_name = "Sender";
+ header_value = header;
+ header = header.mid( 6, header.find( '@' ) - 6 );
+
+ } else {
+ int index = header.find( "-owner@ " );
+ if ( index == -1 )
+ return QString::null;
+
+ header.truncate( index );
+ header_name = "Sender";
+ header_value = header;
+ }
+
+ return header;
+}
+
+/* X-BeenThere: ([^@]+) */
+static QString check_x_beenthere(const KMMessage *message,
+ QCString &header_name,
+ QString &header_value )
+{
+ QString header = message->headerField( "X-BeenThere" );
+ if ( header.isNull() || header.find( '@' ) == -1 )
+ return QString::null;
+
+ header_name = "X-BeenThere";
+ header_value = header;
+ header.truncate( header.find( '@' ) );
+ return header;
+}
+
+/* Delivered-To:: <([^@]+) */
+static QString check_delivered_to(const KMMessage *message,
+ QCString &header_name,
+ QString &header_value )
+{
+ QString header = message->headerField( "Delivered-To" );
+ if ( header.isNull() || header.left(13 ) != "mailing list"
+ || header.find( '@' ) == -1 )
+ return QString::null;
+
+ header_name = "Delivered-To";
+ header_value = header;
+
+ return header.mid( 13, header.find( '@' ) - 13 );
+}
+
+/* X-Mailing-List: <?([^@]+) */
+static QString check_x_mailing_list(const KMMessage *message,
+ QCString &header_name,
+ QString &header_value )
+{
+ QString header = message->headerField( "X-Mailing-List");
+ if ( header.isEmpty() )
+ return QString::null;
+
+ if ( header.find( '@' ) < 1 )
+ return QString::null;
+
+ header_name = "X-Mailing-List";
+ header_value = header;
+ if ( header[0] == '<' )
+ header = header.mid(1, header.find( '@' ) - 1);
+ else
+ header.truncate( header.find( '@' ) );
+ return header;
+}
+
+/* List-Id: [^<]* <([^.]+) */
+static QString check_list_id(const KMMessage *message,
+ QCString &header_name,
+ QString &header_value )
+{
+ int lAnglePos, firstDotPos;
+ QString header = message->headerField( "List-Id" );
+ if ( header.isEmpty() )
+ return QString::null;
+
+ lAnglePos = header.find( '<' );
+ if ( lAnglePos < 0 )
+ return QString::null;
+
+ firstDotPos = header.find( '.', lAnglePos );
+ if ( firstDotPos < 0 )
+ return QString::null;
+
+ header_name = "List-Id";
+ header_value = header.mid( lAnglePos );
+ header = header.mid( lAnglePos + 1, firstDotPos - lAnglePos - 1 );
+ return header;
+}
+
+
+/* List-Post: <mailto:[^< ]*>) */
+static QString check_list_post(const KMMessage *message,
+ QCString &header_name,
+ QString &header_value )
+{
+ QString header = message->headerField( "List-Post" );
+ if ( header.isEmpty() )
+ return QString::null;
+
+ int lAnglePos = header.find( "<mailto:" );
+ if ( lAnglePos < 0 )
+ return QString::null;
+
+ header_name = "List-Post";
+ header_value = header;
+ header = header.mid( lAnglePos + 8, header.length());
+ header.truncate( header.find('@') );
+ return header;
+}
+
+/* Mailing-List: list ([^@]+) */
+static QString check_mailing_list(const KMMessage *message,
+ QCString &header_name,
+ QString &header_value )
+{
+ QString header = message->headerField( "Mailing-List");
+ if ( header.isEmpty() )
+ return QString::null;
+
+ if (header.left( 5 ) != "list " || header.find( '@' ) < 5 )
+ return QString::null;
+
+ header_name = "Mailing-List";
+ header_value = header;
+ header = header.mid(5, header.find( '@' ) - 5);
+ return header;
+}
+
+
+/* X-Loop: ([^@]+) */
+static QString check_x_loop(const KMMessage *message,
+ QCString &header_name,
+ QString &header_value ){
+ QString header = message->headerField( "X-Loop");
+ if ( header.isEmpty() )
+ return QString::null;
+
+ if (header.find( '@' ) < 2 )
+ return QString::null;
+
+ header_name = "X-Loop";
+ header_value = header;
+ header.truncate(header.find( '@' ));
+ return header;
+}
+
+/* X-ML-Name: (.+) */
+static QString check_x_ml_name(const KMMessage *message,
+ QCString &header_name,
+ QString &header_value ){
+ QString header = message->headerField( "X-ML-Name");
+ if ( header.isEmpty() )
+ return QString::null;
+
+ header_name = "X-ML-Name";
+ header_value = header;
+ header.truncate(header.find( '@' ));
+ return header;
+}
+
+MagicDetectorFunc magic_detector[] =
+{
+ check_list_id,
+ check_list_post,
+ check_sender,
+ check_x_mailing_list,
+ check_mailing_list,
+ check_delivered_to,
+ check_x_beenthere,
+ check_x_loop,
+ check_x_ml_name
+};
+
+static const int num_detectors = sizeof (magic_detector) / sizeof (magic_detector[0]);
+
+static QStringList
+headerToAddress( const QString& header )
+{
+ QStringList addr;
+ int start = 0;
+ int end = 0;
+
+ if ( header.isEmpty() )
+ return addr;
+
+ while ( (start = header.find( "<", start )) != -1 ) {
+ if ( (end = header.find( ">", ++start ) ) == -1 ) {
+ kdDebug(5006)<<k_funcinfo<<"Serious mailing list header parsing error !"<<endl;
+ return addr;
+ }
+ kdDebug(5006)<<"Mailing list = "<<header.mid( start, end - start )<<endl;
+ addr.append( header.mid( start, end - start ) );
+ }
+ return addr;
+}
+
+MailingList
+MailingList::detect( const KMMessage *message )
+{
+ MailingList mlist;
+
+ mlist.setPostURLS( headerToAddress(
+ message->headerField( "List-Post" ) ) );
+ mlist.setHelpURLS( headerToAddress(
+ message->headerField( "List-Help" ) ) );
+ mlist.setSubscribeURLS( headerToAddress(
+ message->headerField( "List-Subscribe" ) ) );
+ mlist.setUnsubscribeURLS( headerToAddress(
+ message->headerField( "List-Unsubscribe" ) ) );
+ mlist.setArchiveURLS( headerToAddress(
+ message->headerField( "List-Archive" ) ) );
+ mlist.setId( message->headerField( "List-Id" ) );
+
+ return mlist;
+}
+
+QString
+MailingList::name( const KMMessage *message, QCString &header_name,
+ QString &header_value )
+{
+ QString mlist;
+ header_name = QCString();
+ header_value = QString::null;
+
+ if ( !message )
+ return QString::null;
+
+ for (int i = 0; i < num_detectors; i++) {
+ mlist = magic_detector[i] (message, header_name, header_value);
+ if ( !mlist.isNull() )
+ return mlist;
+ }
+
+ return QString::null;
+}
+
+MailingList::MailingList()
+ : mFeatures( None ), mHandler( KMail )
+{
+}
+
+int
+MailingList::features() const
+{
+ return mFeatures;
+}
+
+void
+MailingList::setHandler( MailingList::Handler han )
+{
+ mHandler = han;
+}
+MailingList::Handler
+MailingList::handler() const
+{
+ return mHandler;
+}
+
+void
+MailingList::setPostURLS ( const KURL::List& lst )
+{
+ mFeatures |= Post;
+ if ( lst.empty() ) {
+ mFeatures ^= Post;
+ }
+ mPostURLS = lst;
+}
+KURL::List
+MailingList::postURLS() const
+{
+ return mPostURLS;
+}
+
+void
+MailingList::setSubscribeURLS( const KURL::List& lst )
+{
+ mFeatures |= Subscribe;
+ if ( lst.empty() ) {
+ mFeatures ^= Subscribe;
+ }
+
+ mSubscribeURLS = lst;
+}
+KURL::List
+MailingList::subscribeURLS() const
+{
+ return mSubscribeURLS;
+}
+
+void
+MailingList::setUnsubscribeURLS( const KURL::List& lst )
+{
+ mFeatures |= Unsubscribe;
+ if ( lst.empty() ) {
+ mFeatures ^= Unsubscribe;
+ }
+
+ mUnsubscribeURLS = lst;
+}
+KURL::List MailingList::unsubscribeURLS() const
+{
+ return mUnsubscribeURLS;
+}
+
+void
+MailingList::setHelpURLS( const KURL::List& lst )
+{
+ mFeatures |= Help;
+ if ( lst.empty() ) {
+ mFeatures ^= Help;
+ }
+
+ mHelpURLS = lst;
+}
+KURL::List
+MailingList::helpURLS() const
+{
+ return mHelpURLS;
+}
+
+void
+MailingList::setArchiveURLS( const KURL::List& lst )
+{
+ mFeatures |= Archive;
+ if ( lst.empty() ) {
+ mFeatures ^= Archive;
+ }
+
+ mArchiveURLS = lst;
+}
+KURL::List
+MailingList::archiveURLS() const
+{
+ return mArchiveURLS;
+}
+
+void
+MailingList::setId( const QString& str )
+{
+ mFeatures |= Id;
+ if ( str.isEmpty() ) {
+ mFeatures ^= Id;
+ }
+
+ mId = str;
+}
+QString
+MailingList::id() const
+{
+ return mId;
+}
+
+void
+MailingList::writeConfig( KConfig* config ) const
+{
+ config->writeEntry( "MailingListFeatures", mFeatures );
+ config->writeEntry( "MailingListHandler", mHandler );
+ config->writeEntry( "MailingListId", mId );
+ config->writeEntry( "MailingListPostingAddress", mPostURLS.toStringList() );
+ config->writeEntry( "MailingListSubscribeAddress", mSubscribeURLS.toStringList() );
+ config->writeEntry( "MailingListUnsubscribeAddress", mUnsubscribeURLS.toStringList() );
+ config->writeEntry( "MailingListArchiveAddress", mArchiveURLS.toStringList() );
+ config->writeEntry( "MailingListHelpAddress", mHelpURLS.toStringList() );
+}
+
+void
+MailingList::readConfig( KConfig* config )
+{
+ mFeatures = config->readNumEntry( "MailingListFeatures", 0 );
+ mHandler = static_cast<MailingList::Handler>(
+ config->readNumEntry( "MailingListHandler", MailingList::KMail ) );
+
+ mId = config->readEntry("MailingListId");
+ mPostURLS = config->readListEntry( "MailingListPostingAddress" );
+ mSubscribeURLS = config->readListEntry( "MailingListSubscribeAddress" );
+ mUnsubscribeURLS = config->readListEntry( "MailingListUnsubscribeAddress" );
+ mArchiveURLS = config->readListEntry( "MailingListArchiveAddress" );
+ mHelpURLS = config->readListEntry( "MailingListHelpAddress" );
+}
diff --git a/kmail/mailinglist-magic.h b/kmail/mailinglist-magic.h
new file mode 100644
index 00000000..8c30c20e
--- /dev/null
+++ b/kmail/mailinglist-magic.h
@@ -0,0 +1,85 @@
+// -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+#ifndef MAILINGLIST_MAGIC_H
+#define MAILINGLIST_MAGIC_H
+
+#include <kurl.h>
+#include <qstring.h>
+
+class KMMessage;
+class KConfig;
+
+namespace KMail
+{
+
+/**
+ * Class is used for all Mailing List handling inside
+ * KMail. The static detect method is used to detect
+ * a full set of ml informations from a message. The
+ * features() method defines which addresses the mailing
+ * has defined.
+ *
+ * @author Zack Rusin <zack@kde.org>
+ */
+class MailingList
+{
+public:
+ enum Handler {
+ KMail,
+ Browser
+ };
+
+ enum Supports {
+ None = 0 << 0,
+ Post = 1 << 0,
+ Subscribe = 1 << 1,
+ Unsubscribe = 1 << 2,
+ Help = 1 << 3,
+ Archive = 1 << 4,
+ Id = 1 << 5
+ };
+public:
+ static MailingList detect( const KMMessage* msg );
+ static QString name( const KMMessage *message, QCString &header_name,
+ QString &header_value );
+public:
+ MailingList();
+
+ int features() const;
+
+ void setHandler( Handler han );
+ Handler handler() const;
+
+ void setPostURLS ( const KURL::List& );
+ KURL::List postURLS() const;
+
+ void setSubscribeURLS( const KURL::List& );
+ KURL::List subscribeURLS() const;
+
+ void setUnsubscribeURLS ( const KURL::List& );
+ KURL::List unsubscribeURLS() const;
+
+ void setHelpURLS( const KURL::List& );
+ KURL::List helpURLS() const;
+
+ void setArchiveURLS( const KURL::List& );
+ KURL::List archiveURLS() const;
+
+ void setId( const QString& );
+ QString id() const;
+
+ void writeConfig( KConfig* config ) const;
+ void readConfig( KConfig* config );
+private:
+ int mFeatures;
+ Handler mHandler;
+ KURL::List mPostURLS;
+ KURL::List mSubscribeURLS;
+ KURL::List mUnsubscribeURLS;
+ KURL::List mHelpURLS;
+ KURL::List mArchiveURLS;
+ QString mId;
+};
+
+}
+
+#endif
diff --git a/kmail/mailinglistpropertiesdialog.cpp b/kmail/mailinglistpropertiesdialog.cpp
new file mode 100644
index 00000000..3ea3beaa
--- /dev/null
+++ b/kmail/mailinglistpropertiesdialog.cpp
@@ -0,0 +1,344 @@
+/*******************************************************************************
+**
+** Filename : mailinglistpropertiesdialog.cpp
+** Created on : 30 January, 2005
+** Copyright : (c) 2005 Till Adam
+** Email : adam@kde.org
+**
+*******************************************************************************/
+
+/*******************************************************************************
+**
+** 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.
+**
+** In addition, as a special exception, the copyright holders give
+** permission to link the code of this program with any edition of
+** the Qt library by Trolltech AS, Norway (or with modified versions
+** of Qt that use the same license as Qt), and distribute linked
+** combinations including the two. You must obey the GNU General
+** Public License in all respects for all of the code used other than
+** Qt. If you modify this file, you may extend this exception to
+** your version of the file, but you are not obligated to do so. If
+** you do not wish to do so, delete this exception statement from
+** your version.
+*******************************************************************************/
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qcombobox.h>
+#include <qgroupbox.h>
+#include <qcheckbox.h>
+#include <qpushbutton.h>
+
+#include <klocale.h>
+#include <keditlistbox.h>
+#include <kdialogbase.h>
+#include <kdebug.h>
+#include <kmessagebox.h>
+#include <kmcommands.h>
+
+#include "kmfolder.h"
+#include "mailinglist-magic.h"
+#include "mailinglistpropertiesdialog.h"
+
+using namespace KMail;
+
+MailingListFolderPropertiesDialog::MailingListFolderPropertiesDialog( QWidget* parent, KMFolder *folder )
+ : KDialogBase( parent, "mailinglist_properties", false, i18n( "Mailinglist Folder Properties" ),
+ KDialogBase::Ok|KDialogBase::Cancel,
+ KDialogBase::Ok, true ),
+ mFolder( folder )
+{
+ setWFlags( getWFlags() | WDestructiveClose );
+ QLabel* label;
+ mLastItem = 0;
+
+ QVBoxLayout *topLayout = new QVBoxLayout( layout(), spacingHint(),
+ "topLayout" );
+
+ QGroupBox *mlGroup = new QGroupBox( i18n("Associated Mailing List" ), this );
+ mlGroup->setColumnLayout( 0, Qt::Vertical );
+ QGridLayout *groupLayout = new QGridLayout( mlGroup->layout(), 6, 3, spacingHint() );
+ topLayout->addWidget( mlGroup );
+ setMainWidget( mlGroup );
+
+ mHoldsMailingList = new QCheckBox( i18n("&Folder holds a mailing list"), mlGroup );
+ QObject::connect( mHoldsMailingList, SIGNAL(toggled(bool)),
+ SLOT(slotHoldsML(bool)) );
+ groupLayout->addMultiCellWidget( mHoldsMailingList, 0, 0, 0, 2 );
+
+ groupLayout->addItem( new QSpacerItem( 0, 10 ), 1, 0 );
+
+ mDetectButton = new QPushButton( i18n("Detect Automatically"), mlGroup );
+ mDetectButton->setEnabled( false );
+ QObject::connect( mDetectButton, SIGNAL(pressed()), SLOT(slotDetectMailingList()) );
+ groupLayout->addWidget( mDetectButton, 2, 1 );
+
+ groupLayout->addItem( new QSpacerItem( 0, 10 ), 3, 0 );
+
+ label = new QLabel( i18n("Mailing list description:"), mlGroup );
+ label->setEnabled( false );
+ QObject::connect( mHoldsMailingList, SIGNAL(toggled(bool)),
+ label, SLOT(setEnabled(bool)) );
+ groupLayout->addWidget( label, 4, 0 );
+ mMLId = new QLabel( label, "", mlGroup );
+ groupLayout->addMultiCellWidget( mMLId, 4, 4, 1, 2 );
+ mMLId->setEnabled( false );
+
+ //FIXME: add QWhatsThis
+ label = new QLabel( i18n("Preferred handler:"), mlGroup );
+ label->setEnabled(false);
+ QObject::connect( mHoldsMailingList, SIGNAL(toggled(bool)),
+ label, SLOT(setEnabled(bool)) );
+ groupLayout->addWidget( label, 5, 0 );
+ mMLHandlerCombo = new QComboBox( mlGroup );
+ mMLHandlerCombo->insertItem( i18n("KMail"), MailingList::KMail );
+ mMLHandlerCombo->insertItem( i18n("Browser"), MailingList::Browser );
+ mMLHandlerCombo->setEnabled( false );
+ groupLayout->addMultiCellWidget( mMLHandlerCombo, 5, 5, 1, 2 );
+ QObject::connect( mMLHandlerCombo, SIGNAL(activated(int)),
+ SLOT(slotMLHandling(int)) );
+ label->setBuddy( mMLHandlerCombo );
+
+ label = new QLabel( i18n("&Address type:"), mlGroup );
+ label->setEnabled(false);
+ QObject::connect( mHoldsMailingList, SIGNAL(toggled(bool)),
+ label, SLOT(setEnabled(bool)) );
+ groupLayout->addWidget( label, 6, 0 );
+ mAddressCombo = new QComboBox( mlGroup );
+ label->setBuddy( mAddressCombo );
+ groupLayout->addWidget( mAddressCombo, 6, 1 );
+ mAddressCombo->setEnabled( false );
+
+ //FIXME: if the mailing list actions have either KAction's or toolbar buttons
+ // associated with them - remove this button since it's really silly
+ // here
+ QPushButton *handleButton = new QPushButton( i18n( "Invoke Handler" ), mlGroup );
+ handleButton->setEnabled( false );
+ if( mFolder)
+ {
+ QObject::connect( mHoldsMailingList, SIGNAL(toggled(bool)),
+ handleButton, SLOT(setEnabled(bool)) );
+ QObject::connect( handleButton, SIGNAL(clicked()),
+ SLOT(slotInvokeHandler()) );
+ }
+ groupLayout->addWidget( handleButton, 6, 2 );
+
+ mEditList = new KEditListBox( mlGroup );
+ mEditList->setEnabled( false );
+ groupLayout->addMultiCellWidget( mEditList, 7, 7, 0, 3 );
+
+ QStringList el;
+
+ //Order is important because the activate handler and fillMLFromWidgets
+ //depend on it
+ el << i18n( "Post to List" )
+ << i18n( "Subscribe to List" )
+ << i18n( "Unsubscribe from List" )
+ << i18n( "List Archives" )
+ << i18n( "List Help" );
+ mAddressCombo->insertStringList( el );
+ QObject::connect( mAddressCombo, SIGNAL(activated(int)),
+ SLOT(slotAddressChanged(int)) );
+
+ load();
+ resize( QSize(295, 204).expandedTo(minimumSizeHint()) );
+ clearWState( WState_Polished );
+}
+
+void MailingListFolderPropertiesDialog::slotOk()
+{
+ save();
+ KDialogBase::slotOk();
+}
+
+void MailingListFolderPropertiesDialog::load()
+{
+ if (mFolder) mMailingList = mFolder->mailingList();
+ mMLId->setText( (mMailingList.id().isEmpty() ? i18n("Not available") : mMailingList.id()) );
+ mMLHandlerCombo->setCurrentItem( mMailingList.handler() );
+ mEditList->insertStringList( mMailingList.postURLS().toStringList() );
+
+ mAddressCombo->setCurrentItem( mLastItem );
+ mHoldsMailingList->setChecked( mFolder && mFolder->isMailingListEnabled() );
+}
+
+//-----------------------------------------------------------------------------
+bool MailingListFolderPropertiesDialog::save()
+{
+ if( mFolder )
+ {
+ // settings for mailingList
+ mFolder->setMailingListEnabled( mHoldsMailingList && mHoldsMailingList->isChecked() );
+ fillMLFromWidgets();
+ mFolder->setMailingList( mMailingList );
+ }
+ return true;
+}
+
+//----------------------------------------------------------------------------
+void MailingListFolderPropertiesDialog::slotHoldsML( bool holdsML )
+{
+ mMLHandlerCombo->setEnabled( holdsML );
+ if ( mFolder && mFolder->count() )
+ mDetectButton->setEnabled( holdsML );
+ mAddressCombo->setEnabled( holdsML );
+ mEditList->setEnabled( holdsML );
+ mMLId->setEnabled( holdsML );
+}
+
+//----------------------------------------------------------------------------
+void MailingListFolderPropertiesDialog::slotDetectMailingList()
+{
+ if ( !mFolder ) return; // in case the folder was just created
+ int num = mFolder->count();
+
+ kdDebug(5006)<<k_funcinfo<<" Detecting mailing list"<<endl;
+
+ /* FIXME Till - make work without the folder tree
+ // first try the currently selected message
+ KMFolderTree *folderTree = static_cast<KMFolderTree *>( mDlg->parent() );
+ int curMsgIdx = folderTree->mainWidget()->headers()->currentItemIndex();
+ if ( curMsgIdx > 0 ) {
+ KMMessage *mes = mFolder->getMsg( curMsgIdx );
+ if ( mes )
+ mMailingList = MailingList::detect( mes );
+ }
+ */
+
+ // next try the 5 most recently added messages
+ if ( !( mMailingList.features() & MailingList::Post ) ) {
+ const int maxchecks = 5;
+ for( int i = --num; i > num-maxchecks; --i ) {
+ KMMessage *mes = mFolder->getMsg( i );
+ if ( !mes )
+ continue;
+ mMailingList = MailingList::detect( mes );
+ if ( mMailingList.features() & MailingList::Post )
+ break;
+ }
+ }
+ if ( !(mMailingList.features() & MailingList::Post) ) {
+ KMessageBox::error( this,
+ i18n("KMail was unable to detect a mailing list in this folder. "
+ "Please fill the addresses by hand.") );
+ } else {
+ mMLId->setText( (mMailingList.id().isEmpty() ? i18n("Not available.") : mMailingList.id() ) );
+ fillEditBox();
+ }
+}
+
+//----------------------------------------------------------------------------
+void MailingListFolderPropertiesDialog::slotMLHandling( int element )
+{
+ mMailingList.setHandler( static_cast<MailingList::Handler>( element ) );
+}
+
+//----------------------------------------------------------------------------
+void MailingListFolderPropertiesDialog::slotAddressChanged( int i )
+{
+ fillMLFromWidgets();
+ fillEditBox();
+ mLastItem = i;
+}
+
+//----------------------------------------------------------------------------
+void MailingListFolderPropertiesDialog::fillMLFromWidgets()
+{
+ if ( !mHoldsMailingList->isChecked() )
+ return;
+
+ // make sure that email addresses are prepended by "mailto:"
+ bool changed = false;
+ QStringList oldList = mEditList->items();
+ QStringList newList; // the correct string list
+ for ( QStringList::ConstIterator it = oldList.begin();
+ it != oldList.end(); ++it ) {
+ if ( !(*it).startsWith("http:") && !(*it).startsWith("https:") &&
+ !(*it).startsWith("mailto:") && ( (*it).find('@') != -1 ) ) {
+ changed = true;
+ newList << "mailto:" + *it;
+ }
+ else {
+ newList << *it;
+ }
+ }
+ if ( changed ) {
+ mEditList->clear();
+ mEditList->insertStringList( newList );
+ }
+
+ //mMailingList.setHandler( static_cast<MailingList::Handler>( mMLHandlerCombo->currentItem() ) );
+ switch ( mLastItem ) {
+ case 0:
+ mMailingList.setPostURLS( mEditList->items() );
+ break;
+ case 1:
+ mMailingList.setSubscribeURLS( mEditList->items() );
+ break;
+ case 2:
+ mMailingList.setUnsubscribeURLS( mEditList->items() );
+ break;
+ case 3:
+ mMailingList.setArchiveURLS( mEditList->items() );
+ break;
+ case 4:
+ mMailingList.setHelpURLS( mEditList->items() );
+ break;
+ default:
+ kdWarning( 5006 )<<"Wrong entry in the mailing list entry combo!"<<endl;
+ }
+}
+
+void MailingListFolderPropertiesDialog::fillEditBox()
+{
+ mEditList->clear();
+ switch ( mAddressCombo->currentItem() ) {
+ case 0:
+ mEditList->insertStringList( mMailingList.postURLS().toStringList() );
+ break;
+ case 1:
+ mEditList->insertStringList( mMailingList.subscribeURLS().toStringList() );
+ break;
+ case 2:
+ mEditList->insertStringList( mMailingList.unsubscribeURLS().toStringList() );
+ break;
+ case 3:
+ mEditList->insertStringList( mMailingList.archiveURLS().toStringList() );
+ break;
+ case 4:
+ mEditList->insertStringList( mMailingList.helpURLS().toStringList() );
+ break;
+ default:
+ kdWarning( 5006 )<<"Wrong entry in the mailing list entry combo!"<<endl;
+ }
+}
+
+void MailingListFolderPropertiesDialog::slotInvokeHandler()
+{
+ KMCommand *command =0;
+ switch ( mAddressCombo->currentItem() ) {
+ case 0:
+ command = new KMMailingListPostCommand( this, mFolder );
+ break;
+ case 1:
+ command = new KMMailingListSubscribeCommand( this, mFolder );
+ break;
+ case 2:
+ command = new KMMailingListUnsubscribeCommand( this, mFolder );
+ break;
+ case 3:
+ command = new KMMailingListArchivesCommand( this, mFolder );
+ break;
+ case 4:
+ command = new KMMailingListHelpCommand( this, mFolder );
+ break;
+ default:
+ kdWarning( 5006 )<<"Wrong entry in the mailing list entry combo!"<<endl;
+ }
+ if ( command ) command->start();
+}
+
+#include "mailinglistpropertiesdialog.moc"
diff --git a/kmail/mailinglistpropertiesdialog.h b/kmail/mailinglistpropertiesdialog.h
new file mode 100644
index 00000000..5996d607
--- /dev/null
+++ b/kmail/mailinglistpropertiesdialog.h
@@ -0,0 +1,87 @@
+/*******************************************************************************
+**
+** Filename : mailinglistpropertiesdialog.h
+** Created on : 30 January, 2005
+** Copyright : (c) 2005 Till Adam
+** Email : adam@kde.org
+**
+*******************************************************************************/
+
+/*******************************************************************************
+**
+** 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.
+**
+** In addition, as a special exception, the copyright holders give
+** permission to link the code of this program with any edition of
+** the Qt library by Trolltech AS, Norway (or with modified versions
+** of Qt that use the same license as Qt), and distribute linked
+** combinations including the two. You must obey the GNU General
+** Public License in all respects for all of the code used other than
+** Qt. If you modify this file, you may extend this exception to
+** your version of the file, but you are not obligated to do so. If
+** you do not wish to do so, delete this exception statement from
+** your version.
+*******************************************************************************/
+
+#ifndef MAILINGLISTFOLDERPROPERTIESDIALOG_H
+#define MAILINGLISTFOLDERPROPERTIESDIALOG_H
+
+#include "mailinglist-magic.h"
+#include <kdialogbase.h> // include for the base class
+
+class KMFolder;
+class QCheckBox;
+class QComboBox;
+class QPushButton;
+class QLabel;
+class KEditListBox;
+
+namespace KMail
+{
+
+class MailingListFolderPropertiesDialog : public KDialogBase
+{
+ Q_OBJECT
+public:
+ MailingListFolderPropertiesDialog( QWidget *parent, KMFolder *folder );
+ ~MailingListFolderPropertiesDialog() {};
+protected:
+ void load();
+ bool save();
+
+protected slots:
+ void slotOk();
+
+private slots:
+ /*
+ * Detects mailing-list related stuff
+ */
+ void slotDetectMailingList();
+ void slotInvokeHandler();
+ void slotMLHandling( int element );
+ void slotHoldsML( bool holdsML );
+ void slotAddressChanged( int addr );
+
+private:
+ KMFolder *mFolder;
+ void fillMLFromWidgets();
+ void fillEditBox();
+
+ bool mMLInfoChanged;
+ QCheckBox *mHoldsMailingList;
+ QComboBox *mMLHandlerCombo;
+ QPushButton *mDetectButton;
+ QComboBox *mAddressCombo;
+ int mLastItem;
+ KEditListBox *mEditList;
+ QLabel *mMLId;
+ MailingList mMailingList;
+}; // End of class MailingListFolderProperties
+
+} // End of namespace KMail
+
+
+#endif // MAILINGLISTFOLDERPROPERTIESDIALOG_H
diff --git a/kmail/mailserviceimpl.cpp b/kmail/mailserviceimpl.cpp
new file mode 100644
index 00000000..6d82f5a4
--- /dev/null
+++ b/kmail/mailserviceimpl.cpp
@@ -0,0 +1,135 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ *
+ * This file is part of KMail, the KDE mail client.
+ * Copyright (c) 2003 Zack Rusin <zack@kde.org>
+ *
+ * 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 Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "mailserviceimpl.h"
+
+#include "composer.h"
+
+#include "kmmessage.h"
+#include "kmmsgpart.h"
+
+#include <dcopobject.h>
+#include <kurl.h>
+#include <kdebug.h>
+#include <qstring.h>
+
+namespace KMail {
+
+
+MailServiceImpl::MailServiceImpl()
+ : DCOPObject( "MailTransportServiceIface" )
+{
+}
+
+bool MailServiceImpl::sendMessage( const QString& from, const QString& to,
+ const QString& cc, const QString& bcc,
+ const QString& subject, const QString& body,
+ const KURL::List& attachments )
+{
+ if ( to.isEmpty() && cc.isEmpty() && bcc.isEmpty() )
+ return false;
+
+ KMMessage *msg = new KMMessage;
+ msg->initHeader();
+
+ msg->setCharset( "utf-8" );
+
+ if ( !from.isEmpty() ) msg->setFrom( from );
+ if ( !to.isEmpty() ) msg->setTo( to );
+ if ( !cc.isEmpty() ) msg->setCc( cc );
+ if ( !bcc.isEmpty() ) msg->setBcc( bcc );
+ if ( !subject.isEmpty() ) msg->setSubject( subject );
+ if ( !body.isEmpty() ) msg->setBody( body.utf8() );
+
+ KMail::Composer * cWin = KMail::makeComposer( msg );
+ cWin->setCharset("", TRUE);
+
+ cWin->addAttachmentsAndSend(attachments, "", 1);//send now
+ return true;
+}
+
+bool MailServiceImpl::sendMessage( const QString& to,
+ const QString& cc, const QString& bcc,
+ const QString& subject, const QString& body,
+ const KURL::List& attachments )
+{
+ kdDebug(5006) << "DCOP call MailTransportServiceIface bool sendMessage(QString to,QString cc,QString bcc,QString subject,QString body,KURL::List attachments)" << endl;
+ kdDebug(5006) << "This DCOP call is deprecated. Use the corresponding DCOP call with the additional parameter QString from instead." << endl;
+ return sendMessage( QString::null, to, cc, bcc, subject, body, attachments );
+}
+
+
+bool MailServiceImpl::sendMessage( const QString& from, const QString& to,
+ const QString& cc, const QString& bcc,
+ const QString& subject, const QString& body,
+ const QByteArray& attachment )
+{
+ if ( to.isEmpty() && cc.isEmpty() && bcc.isEmpty() )
+ return false;
+
+ KMMessage *msg = new KMMessage;
+ msg->initHeader();
+
+ msg->setCharset( "utf-8" );
+
+ if ( !from.isEmpty() ) msg->setFrom( from );
+ if ( !to.isEmpty() ) msg->setTo( to );
+ if ( !cc.isEmpty() ) msg->setCc( cc );
+ if ( !bcc.isEmpty() ) msg->setBcc( bcc );
+ if ( !subject.isEmpty() ) msg->setSubject( subject );
+ if ( !body.isEmpty() ) msg->setBody( body.utf8() );
+
+ KMMessagePart *part = new KMMessagePart;
+ part->setCteStr( "base64" );
+ part->setBodyEncodedBinary( attachment );
+ msg->addBodyPart( part );
+
+ KMail::Composer * cWin = KMail::makeComposer( msg );
+ cWin->setCharset("", TRUE);
+ return true;
+}
+
+
+bool MailServiceImpl::sendMessage( const QString& to,
+ const QString& cc, const QString& bcc,
+ const QString& subject, const QString& body,
+ const QByteArray& attachment )
+{
+ kdDebug(5006) << "DCOP call MailTransportServiceIface bool sendMessage(QString to,QString cc,QString bcc,QString subject,QString body,QByteArray attachment)" << endl;
+ kdDebug(5006) << "This DCOP call is deprecated. Use the corresponding DCOP call with the additional parameter QString from instead." << endl;
+ return sendMessage( QString::null, to, cc, bcc, subject, body, attachment );
+}
+
+}//end namespace KMail
+
diff --git a/kmail/mailserviceimpl.h b/kmail/mailserviceimpl.h
new file mode 100644
index 00000000..0bb92557
--- /dev/null
+++ b/kmail/mailserviceimpl.h
@@ -0,0 +1,74 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ *
+ * This file is part of KMail, the KDE mail client.
+ * Copyright (c) 2003 Zack Rusin <zack@kde.org>
+ *
+ * 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 Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+#ifndef MAILSERVICEIMPL_H
+#define MAILSERVICEIMPL_H
+
+#include "interfaces/MailTransportServiceIface.h"
+
+class QString;
+class KURL;
+template <typename T> class QMemArray;
+typedef QMemArray<char> QByteArray;
+
+
+namespace KMail {
+
+ class MailServiceImpl : virtual public KPim::MailTransportServiceIface
+ {
+ public:
+ MailServiceImpl();
+ bool sendMessage( const QString& from, const QString& to,
+ const QString& cc, const QString& bcc,
+ const QString& subject, const QString& body,
+ const KURL::List& attachments );
+
+ // FIXME KDE 4.0: Remove this.
+ // (cf. libkdepim/interfaces/MailTransportServiceIface.h)
+ bool sendMessage( const QString& to,
+ const QString& cc, const QString& bcc,
+ const QString& subject, const QString& body,
+ const KURL::List& attachments );
+
+ bool sendMessage( const QString& from, const QString& to,
+ const QString& cc, const QString& bcc,
+ const QString& subject, const QString& body,
+ const QByteArray& attachment );
+
+ // FIXME KDE 4.0: Remove this.
+ // (cf. libkdepim/interfaces/MailTransportServiceIface.h)
+ bool sendMessage( const QString& to,
+ const QString& cc, const QString& bcc,
+ const QString& subject, const QString& body,
+ const QByteArray& attachment );
+
+ };
+}
+
+#endif
diff --git a/kmail/mailsourceviewer.cpp b/kmail/mailsourceviewer.cpp
new file mode 100644
index 00000000..ac77b7dd
--- /dev/null
+++ b/kmail/mailsourceviewer.cpp
@@ -0,0 +1,84 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ *
+ * This file is part of KMail, the KDE mail client.
+ *
+ * Copyright (c) 2002-2003 Carsten Pfeiffer <pfeiffer@kde.org>
+ * Copyright (c) 2003 Zack Rusin <zack@kde.org>
+ *
+ * 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 Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "mailsourceviewer.h"
+
+#include <kapplication.h>
+#include <kwin.h>
+
+#include <qregexp.h>
+#include <qaccel.h>
+
+namespace KMail {
+
+int MailSourceHighlighter::highlightParagraph( const QString& text, int ) {
+ QRegExp regexp( "^([\\w-]+:\\s)" );
+ if( regexp.search( text ) != -1 ) {
+ QFont font = textEdit()->currentFont();
+ font.setBold( true );
+ setFormat( 0, regexp.matchedLength(), font );
+ }
+ return 0;
+}
+
+MailSourceViewer::MailSourceViewer( QWidget *parent, const char *name )
+ : KTextBrowser( parent, name ), mSourceHighLighter( 0 )
+{
+ setWFlags( WDestructiveClose );
+ QAccel *accel = new QAccel( this, "browser close-accel" );
+ accel->connectItem( accel->insertItem( Qt::Key_Escape ), this , SLOT( close() ));
+ accel->connectItem( accel->insertItem( Qt::Key_W+CTRL ), this , SLOT( close() ));
+ setWordWrap( KTextBrowser::NoWrap );
+ KWin::setIcons(winId(), kapp->icon(), kapp->miniIcon());
+}
+
+MailSourceViewer::~MailSourceViewer()
+{
+ delete mSourceHighLighter; mSourceHighLighter = 0;
+}
+
+void MailSourceViewer::setText( const QString& text )
+{
+ delete mSourceHighLighter; mSourceHighLighter = 0;
+ if ( text.length() > 500000 ) {
+ setTextFormat( Qt::LogText );
+ } else {
+ setTextFormat( Qt::PlainText );
+ mSourceHighLighter = new MailSourceHighlighter( this );
+ }
+ KTextBrowser::setText( text );
+}
+
+}
diff --git a/kmail/mailsourceviewer.h b/kmail/mailsourceviewer.h
new file mode 100644
index 00000000..0bed8b41
--- /dev/null
+++ b/kmail/mailsourceviewer.h
@@ -0,0 +1,71 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ *
+ * This file is part of KMail, the KDE mail client.
+ *
+ * Copyright (c) 2002-2003 Carsten Pfeiffer <pfeiffer@kde.org>
+ * Copyright (c) 2003 Zack Rusin <zack@kde.org>
+ *
+ * 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 Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+#ifndef MAILSOURCEVIEWER_H
+#define MAILSOURCEVIEWER_H
+
+#include <ktextbrowser.h>
+#include <qsyntaxhighlighter.h>
+
+
+/**
+ * A tiny little class to use for displaying raw messages, textual
+ * attachments etc.
+ *
+ * Auto-deletes itself when closed.
+ *
+ * @author Carsten Pfeiffer <pfeiffer@kde.org>
+ */
+namespace KMail {
+
+class MailSourceHighlighter : public QSyntaxHighlighter
+{
+public:
+ MailSourceHighlighter( QTextEdit* edit )
+ : QSyntaxHighlighter( edit )
+ {}
+ int highlightParagraph( const QString& text, int );
+};
+
+
+class MailSourceViewer : public KTextBrowser
+{
+public:
+ MailSourceViewer( QWidget *parent = 0, const char *name = 0 );
+ ~MailSourceViewer();
+ void setText( const QString& text );
+private:
+ MailSourceHighlighter *mSourceHighLighter;
+};
+
+}
+
+#endif // MAILSOURCEVIEWER_H
diff --git a/kmail/main.cpp b/kmail/main.cpp
new file mode 100644
index 00000000..640fdc6f
--- /dev/null
+++ b/kmail/main.cpp
@@ -0,0 +1,116 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ *
+ * 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 <config.h>
+
+#include <kuniqueapplication.h>
+#include <kglobal.h>
+#include <knotifyclient.h>
+#include <dcopclient.h>
+#include "kmkernel.h" //control center
+#include "kmail_options.h"
+
+#include <kdebug.h>
+
+#undef Status // stupid X headers
+
+#include "aboutdata.h"
+
+#include "kmstartup.h"
+
+//-----------------------------------------------------------------------------
+
+class KMailApplication : public KUniqueApplication
+{
+public:
+ KMailApplication() : KUniqueApplication() { };
+ virtual int newInstance();
+ void commitData(QSessionManager& sm);
+
+};
+
+void KMailApplication::commitData(QSessionManager& sm) {
+ kmkernel->dumpDeadLetters();
+ kmkernel->setShuttingDown( true ); // Prevent further dumpDeadLetters calls
+ KApplication::commitData( sm );
+}
+
+
+int KMailApplication::newInstance()
+{
+ kdDebug(5006) << "KMailApplication::newInstance()" << endl;
+ if (!kmkernel)
+ return 0;
+
+ if (!kmkernel->firstInstance() || !kapp->isRestored())
+ kmkernel->handleCommandLine( true );
+ kmkernel->setFirstInstance(FALSE);
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ // WABA: KMail is a KUniqueApplication. Unfortunately this makes debugging
+ // a bit harder: You should pass --nofork as commandline argument when using
+ // a debugger. In gdb you can do this by typing "set args --nofork" before
+ // typing "run".
+
+ KMail::AboutData about;
+
+ KCmdLineArgs::init(argc, argv, &about);
+ KCmdLineArgs::addCmdLineOptions( kmail_options ); // Add kmail options
+ if (!KMailApplication::start())
+ return 0;
+
+ KMailApplication app;
+
+ // import i18n data and icons from libraries:
+ KMail::insertLibraryCataloguesAndIcons();
+
+ // Make sure that the KNotify Daemon is running (this is necessary for people
+ // using KMail without KDE)
+ KNotifyClient::startDaemon();
+
+ kapp->dcopClient()->suspend(); // Don't handle DCOP requests yet
+
+ KMail::lockOrDie();
+
+ //local, do the init
+ KMKernel kmailKernel;
+ kmailKernel.init();
+ kapp->dcopClient()->setDefaultObject( kmailKernel.objId() );
+
+ // and session management
+ kmailKernel.doSessionManagement();
+
+ // any dead letters?
+ kmailKernel.recoverDeadLetters();
+
+ kmsetSignalHandler(kmsignalHandler);
+
+ kapp->dcopClient()->resume(); // Ok. We are ready for DCOP requests.
+ kmkernel->setStartingUp( false ); // Starting up is finished
+ // Go!
+ int ret = kapp->exec();
+ // clean up
+ kmailKernel.cleanup();
+
+ KMail::cleanup(); // pid file (see kmstartup.cpp)
+ return ret;
+}
diff --git a/kmail/managesievescriptsdialog.cpp b/kmail/managesievescriptsdialog.cpp
new file mode 100644
index 00000000..594a467d
--- /dev/null
+++ b/kmail/managesievescriptsdialog.cpp
@@ -0,0 +1,347 @@
+#include "managesievescriptsdialog.h"
+#include "managesievescriptsdialog_p.h"
+
+#include "sieveconfig.h"
+#include "accountmanager.h"
+#include "imapaccountbase.h"
+#include "sievejob.h"
+#include "kmkernel.h"
+
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kwin.h>
+#include <kapplication.h>
+#include <kinputdialog.h>
+#include <kglobalsettings.h>
+#include <kmessagebox.h>
+
+#include <qlayout.h>
+#include <qlistview.h>
+#include <qtextedit.h>
+#include <qpopupmenu.h>
+
+#include <cassert>
+
+inline QCheckListItem * qcli_cast( QListViewItem * lvi ) {
+ return lvi && lvi->rtti() == 1 ? static_cast<QCheckListItem*>( lvi ) : 0 ;
+}
+inline const QCheckListItem * qcli_cast( const QListViewItem * lvi ) {
+ return lvi && lvi->rtti() == 1 ? static_cast<const QCheckListItem*>( lvi ) : 0 ;
+}
+
+KMail::ManageSieveScriptsDialog::ManageSieveScriptsDialog( QWidget * parent, const char * name )
+ : KDialogBase( Plain, i18n( "Manage Sieve Scripts" ), Close, Close,
+ parent, name, false ),
+ mSieveEditor( 0 ),
+ mContextMenuItem( 0 ),
+ mWasActive( false )
+{
+ setWFlags( WGroupLeader|WDestructiveClose );
+ KWin::setIcons( winId(), kapp->icon(), kapp->miniIcon() );
+
+ QVBoxLayout * vlay = new QVBoxLayout( plainPage(), 0, 0 );
+
+ mListView = new QListView( plainPage() );
+ mListView->addColumn( i18n( "Available Scripts" ) );
+ mListView->setResizeMode( QListView::LastColumn );
+ mListView->setRootIsDecorated( true );
+ mListView->setSelectionMode( QListView::Single );
+ connect( mListView, SIGNAL(contextMenuRequested(QListViewItem*,const QPoint&,int)),
+ this, SLOT(slotContextMenuRequested(QListViewItem*, const QPoint&)) );
+ connect( mListView, SIGNAL(doubleClicked(QListViewItem*,const QPoint&,int)),
+ this, SLOT(slotDoubleClicked(QListViewItem*)) );
+ connect( mListView, SIGNAL(selectionChanged(QListViewItem*)),
+ this, SLOT(slotSelectionChanged(QListViewItem*)) );
+ vlay->addWidget( mListView );
+
+ resize( 2 * sizeHint().width(), sizeHint().height() );
+
+ slotRefresh();
+}
+
+KMail::ManageSieveScriptsDialog::~ManageSieveScriptsDialog() {
+ killAllJobs();
+}
+
+void KMail::ManageSieveScriptsDialog::killAllJobs() {
+ for ( QMap<SieveJob*,QCheckListItem*>::const_iterator it = mJobs.constBegin(), end = mJobs.constEnd() ; it != end ; ++it )
+ it.key()->kill();
+ mJobs.clear();
+}
+
+static KURL findUrlForAccount( const KMail::ImapAccountBase * a ) {
+ assert( a );
+ const KMail::SieveConfig sieve = a->sieveConfig();
+ if ( !sieve.managesieveSupported() )
+ return KURL();
+ if ( sieve.reuseConfig() ) {
+ // assemble Sieve url from the settings of the account:
+ KURL u;
+ 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.setQuery( "x-mech=" + ( a->auth() == "*" ? "PLAIN" : a->auth() ) );
+ return u;
+ } else {
+ return sieve.alternateURL();
+ }
+}
+
+void KMail::ManageSieveScriptsDialog::slotRefresh() {
+ killAllJobs();
+ mUrls.clear();
+ mListView->clear();
+
+ KMail::AccountManager * am = kmkernel->acctMgr();
+ assert( am );
+ QCheckListItem * last = 0;
+ for ( KMAccount * a = am->first() ; a ; a = am->next() ) {
+ last = new QCheckListItem( mListView, last, a->name(), QCheckListItem::Controller );
+ last->setPixmap( 0, SmallIcon( "server" ) );
+ if ( ImapAccountBase * iab = dynamic_cast<ImapAccountBase*>( a ) ) {
+ const KURL u = ::findUrlForAccount( iab );
+ if ( u.isEmpty() )
+ continue;
+ SieveJob * job = SieveJob::list( u );
+ connect( job, SIGNAL(item(KMail::SieveJob*,const QString&,bool)),
+ this, SLOT(slotItem(KMail::SieveJob*,const QString&,bool)) );
+ connect( job, SIGNAL(result(KMail::SieveJob*,bool,const QString&,bool)),
+ this, SLOT(slotResult(KMail::SieveJob*,bool,const QString&,bool)) );
+ mJobs.insert( job, last );
+ mUrls.insert( last, u );
+ } else {
+ QListViewItem * item = new QListViewItem( last, i18n( "No Sieve URL configured" ) );
+ item->setEnabled( false );
+ last->setOpen( true );
+ }
+ }
+}
+
+void KMail::ManageSieveScriptsDialog::slotResult( KMail::SieveJob * job, bool success, const QString &, bool ) {
+ QCheckListItem * parent = mJobs[job];
+ if ( !parent )
+ return;
+
+ mJobs.remove( job );
+
+ parent->setOpen( true );
+
+ if ( success )
+ return;
+
+ QListViewItem * item = new QListViewItem( parent, i18n( "Failed to fetch the list of scripts" ) );
+ item->setEnabled( false );
+}
+
+void KMail::ManageSieveScriptsDialog::slotItem( KMail::SieveJob * job, const QString & filename, bool isActive ) {
+ QCheckListItem * parent = mJobs[job];
+ if ( !parent )
+ return;
+ QCheckListItem * item = new QCheckListItem( parent, filename, QCheckListItem::RadioButton );
+ if ( isActive ) {
+ item->setOn( true );
+ mSelectedItems[parent] = item;
+ }
+}
+
+void KMail::ManageSieveScriptsDialog::slotContextMenuRequested( QListViewItem * i, const QPoint & p ) {
+ QCheckListItem * item = qcli_cast( i );
+ if ( !item )
+ return;
+ if ( !item->depth() && !mUrls.count( item ) )
+ return;
+ QPopupMenu menu;
+ mContextMenuItem = item;
+ if ( item->depth() ) {
+ // script items:
+ menu.insertItem( i18n( "Delete Script" ), this, SLOT(slotDeleteScript()) );
+ menu.insertItem( i18n( "Edit Script..." ), this, SLOT(slotEditScript()) );
+ } else {
+ // top-levels:
+ menu.insertItem( i18n( "New Script..." ), this, SLOT(slotNewScript()) );
+ }
+ menu.exec( p );
+ mContextMenuItem = 0;
+}
+
+void KMail::ManageSieveScriptsDialog::slotSelectionChanged( QListViewItem * i ) {
+ QCheckListItem * item = qcli_cast( i );
+ if ( !item )
+ return;
+ QCheckListItem * parent = qcli_cast( item->parent() );
+ if ( !parent )
+ return;
+ if ( item->isOn() && mSelectedItems[parent] != item ) {
+ mSelectedItems[parent] = item;
+ changeActiveScript( parent );
+ }
+}
+
+void KMail::ManageSieveScriptsDialog::changeActiveScript( QCheckListItem * item ) {
+ if ( !item )
+ return;
+ if ( !mUrls.count( item ) )
+ return;
+ if ( !mSelectedItems.count( item ) )
+ return;
+ KURL u = mUrls[item];
+ if ( u.isEmpty() )
+ return;
+ QCheckListItem * selected = mSelectedItems[item];
+ if ( !selected )
+ return;
+ u.setFileName( selected->text( 0 ) );
+
+ SieveJob * job = SieveJob::activate( u );
+ connect( job, SIGNAL(result(KMail::SieveJob*,bool,const QString&,bool)),
+ this, SLOT(slotRefresh()) );
+}
+
+void KMail::ManageSieveScriptsDialog::slotDoubleClicked( QListViewItem * i ) {
+ QCheckListItem * item = qcli_cast( i );
+ if ( !item )
+ return;
+ if ( !item->depth() )
+ return;
+ mContextMenuItem = item;
+ slotEditScript();
+ mContextMenuItem = 0;
+}
+
+void KMail::ManageSieveScriptsDialog::slotDeleteScript() {
+ if ( !mContextMenuItem )
+ return;
+ if ( !mContextMenuItem->depth() )
+ return;
+
+ QCheckListItem * parent = qcli_cast( mContextMenuItem->parent() );
+ if ( !parent )
+ return;
+
+ if ( !mUrls.count( parent ) )
+ return;
+
+ KURL u = mUrls[parent];
+ if ( u.isEmpty() )
+ return;
+
+ u.setFileName( mContextMenuItem->text( 0 ) );
+
+ if ( KMessageBox::warningContinueCancel( this, i18n( "Really delete script \"%1\" from the server?" ).arg( u.fileName() ),
+ i18n( "Delete Sieve Script Confirmation" ),
+ KStdGuiItem::del() )
+ != KMessageBox::Continue )
+ return;
+
+ SieveJob * job = SieveJob::del( u );
+ connect( job, SIGNAL(result(KMail::SieveJob*,bool,const QString&,bool)),
+ this, SLOT(slotRefresh()) );
+}
+
+void KMail::ManageSieveScriptsDialog::slotEditScript() {
+ if ( !mContextMenuItem )
+ return;
+ if ( !mContextMenuItem->depth() )
+ return;
+ QCheckListItem * parent = qcli_cast( mContextMenuItem->parent() );
+ if ( !mUrls.count( parent ) )
+ return;
+ KURL url = mUrls[parent];
+ if ( url.isEmpty() )
+ return;
+ url.setFileName( mContextMenuItem->text( 0 ) );
+ mCurrentURL = url;
+ SieveJob * job = SieveJob::get( url );
+ connect( job, SIGNAL(result(KMail::SieveJob*,bool,const QString&,bool)),
+ this, SLOT(slotGetResult(KMail::SieveJob*,bool,const QString&,bool)) );
+}
+
+void KMail::ManageSieveScriptsDialog::slotNewScript() {
+ if ( !mContextMenuItem )
+ return;
+ if ( mContextMenuItem->depth() )
+ mContextMenuItem = qcli_cast( mContextMenuItem->parent() );
+ if ( !mContextMenuItem )
+ return;
+
+ if ( !mUrls.count( mContextMenuItem ) )
+ return;
+
+ KURL u = mUrls[mContextMenuItem];
+ if ( u.isEmpty() )
+ return;
+
+ bool ok = false;
+ const QString name = KInputDialog::getText( i18n( "New Sieve Script" ),
+ i18n( "Please enter a name for the new Sieve script:" ),
+ i18n( "unnamed" ), &ok, this );
+ if ( !ok || name.isEmpty() )
+ return;
+
+ u.setFileName( name );
+
+ (void) new QCheckListItem( mContextMenuItem, name, QCheckListItem::RadioButton );
+
+ mCurrentURL = u;
+ slotGetResult( 0, true, QString::null, false );
+}
+
+KMail::SieveEditor::SieveEditor( QWidget * parent, const char * name )
+ : KDialogBase( Plain, i18n( "Edit Sieve Script" ), Ok|Cancel, Ok, parent, name )
+{
+ QVBoxLayout * vlay = new QVBoxLayout( plainPage(), 0, spacingHint() );
+ mTextEdit = new QTextEdit( plainPage() );
+ vlay->addWidget( mTextEdit );
+ mTextEdit->setTextFormat( QTextEdit::PlainText );
+ mTextEdit->setWordWrap( QTextEdit::NoWrap );
+ mTextEdit->setFont( KGlobalSettings::fixedFont() );
+
+ resize( 3 * sizeHint() );
+}
+
+KMail::SieveEditor::~SieveEditor() {}
+
+void KMail::ManageSieveScriptsDialog::slotGetResult( KMail::SieveJob *, bool success, const QString & script, bool isActive ) {
+ if ( !success )
+ return;
+
+ if ( mSieveEditor )
+ return;
+
+ mSieveEditor = new SieveEditor( this );
+ mSieveEditor->setScript( script );
+ connect( mSieveEditor, SIGNAL(okClicked()), this, SLOT(slotSieveEditorOkClicked()) );
+ connect( mSieveEditor, SIGNAL(cancelClicked()), this, SLOT(slotSieveEditorCancelClicked()) );
+ mSieveEditor->show();
+ mWasActive = isActive;
+}
+
+void KMail::ManageSieveScriptsDialog::slotSieveEditorOkClicked() {
+ if ( !mSieveEditor )
+ return;
+ SieveJob * job = SieveJob::put( mCurrentURL,mSieveEditor->script(), mWasActive, mWasActive );
+ connect( job, SIGNAL(result(KMail::SieveJob*,bool,const QString&,bool)),
+ this, SLOT(slotPutResult(KMail::SieveJob*,bool)) );
+}
+
+void KMail::ManageSieveScriptsDialog::slotSieveEditorCancelClicked() {
+ mSieveEditor->deleteLater(); mSieveEditor = 0;
+ mCurrentURL = KURL();
+}
+
+void KMail::ManageSieveScriptsDialog::slotPutResult( KMail::SieveJob *, bool success ) {
+ if ( success ) {
+ KMessageBox::information( this, i18n( "The Sieve script was successfully uploaded." ),
+ i18n( "Sieve Script Upload" ) );
+ mSieveEditor->deleteLater(); mSieveEditor = 0;
+ mCurrentURL = KURL();
+ } else {
+ mSieveEditor->show();
+ }
+}
+
+#include "managesievescriptsdialog.moc"
+#include "managesievescriptsdialog_p.moc"
diff --git a/kmail/managesievescriptsdialog.h b/kmail/managesievescriptsdialog.h
new file mode 100644
index 00000000..f0b99ebc
--- /dev/null
+++ b/kmail/managesievescriptsdialog.h
@@ -0,0 +1,55 @@
+#ifndef __KMAIL__MANAGESIEVESCRIPTSDIALOG_H__
+#define __KMAIL__MANAGESIEVESCRIPTSDIALOG_H__
+
+#include <kdialogbase.h>
+#include <kurl.h>
+#include <qmap.h>
+
+class QListView;
+class QCheckListItem;
+
+namespace KMail {
+
+class SieveJob;
+class SieveEditor;
+
+class ManageSieveScriptsDialog : public KDialogBase {
+ Q_OBJECT
+public:
+ ManageSieveScriptsDialog( QWidget * parent=0, const char * name=0 );
+ ~ManageSieveScriptsDialog();
+
+private slots:
+ void slotRefresh();
+ void slotItem( KMail::SieveJob *, const QString &, bool );
+ void slotResult( KMail::SieveJob *, bool, const QString &, bool );
+ void slotContextMenuRequested( QListViewItem *, const QPoint & );
+ void slotDoubleClicked( QListViewItem * );
+ void slotSelectionChanged( QListViewItem * );
+ void slotNewScript();
+ void slotEditScript();
+ void slotDeleteScript();
+ void slotGetResult( KMail::SieveJob *, bool, const QString &, bool );
+ void slotPutResult( KMail::SieveJob *, bool );
+ void slotSieveEditorOkClicked();
+ void slotSieveEditorCancelClicked();
+
+private:
+ void killAllJobs();
+ void changeActiveScript( QCheckListItem * );
+
+private:
+ QListView * mListView;
+ SieveEditor * mSieveEditor;
+ QMap<KMail::SieveJob*,QCheckListItem*> mJobs;
+ QMap<QCheckListItem*,KURL> mUrls;
+ QMap<QCheckListItem*,QCheckListItem*> mSelectedItems;
+ QCheckListItem * mContextMenuItem;
+ KURL mCurrentURL;
+ bool mWasActive : 1;
+};
+
+}
+
+#endif /* __KMAIL__MANAGESIEVESCRIPTSDIALOG_H__ */
+
diff --git a/kmail/managesievescriptsdialog_p.h b/kmail/managesievescriptsdialog_p.h
new file mode 100644
index 00000000..d46b2d11
--- /dev/null
+++ b/kmail/managesievescriptsdialog_p.h
@@ -0,0 +1,27 @@
+#ifndef __KMAIL__MANAGESIEVESCRIPTSDIALOG_P_H__
+#define __KMAIL__MANAGESIEVESCRIPTSDIALOG_P_H__
+
+#include <kdialogbase.h>
+
+#include <qtextedit.h>
+
+namespace KMail {
+
+class SieveEditor : public KDialogBase {
+ Q_OBJECT
+ Q_PROPERTY( QString script READ script WRITE setScript )
+public:
+ SieveEditor( QWidget * parent=0, const char * name=0 );
+ ~SieveEditor();
+
+ QString script() const { return mTextEdit->text(); }
+ void setScript( const QString & script ) { mTextEdit->setText( script ); }
+
+private:
+ QTextEdit * mTextEdit;
+};
+
+}
+
+#endif /* __KMAIL__MANAGESIEVESCRIPTSDIALOG_P_H__ */
+
diff --git a/kmail/mboxjob.cpp b/kmail/mboxjob.cpp
new file mode 100644
index 00000000..ee2cc7a3
--- /dev/null
+++ b/kmail/mboxjob.cpp
@@ -0,0 +1,124 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ *
+ * This file is part of KMail, the KDE mail client.
+ * Copyright (c) 2003 Zack Rusin <zack@kde.org>
+ *
+ * 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 Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "mboxjob.h"
+
+#include "kmfoldermbox.h"
+#include "kmfolder.h"
+
+#include <kapplication.h>
+#include <kdebug.h>
+#include <qtimer.h>
+#include <qdatetime.h>
+
+namespace KMail {
+
+
+//-----------------------------------------------------------------------------
+MboxJob::MboxJob( KMMessage *msg, JobType jt , KMFolder *folder )
+ : FolderJob( msg, jt, folder )
+{
+}
+
+//-----------------------------------------------------------------------------
+MboxJob::MboxJob( QPtrList<KMMessage>& msgList, const QString& sets,
+ JobType jt, KMFolder *folder )
+ : FolderJob( msgList, sets, jt, folder )
+{
+}
+
+//-----------------------------------------------------------------------------
+MboxJob::~MboxJob()
+{
+}
+
+//-----------------------------------------------------------------------------
+void
+MboxJob::execute()
+{
+ QTimer::singleShot( 0, this, SLOT(startJob()) );
+}
+
+//-----------------------------------------------------------------------------
+void
+MboxJob::setParent( const KMFolderMbox *parent )
+{
+ mParent = const_cast<KMFolderMbox*>( parent );
+}
+
+//-----------------------------------------------------------------------------
+void
+MboxJob::startJob()
+{
+ KMMessage *msg = mMsgList.first();
+ assert( (msg && ( mParent || msg->parent() )) );
+ switch( mType ) {
+ case tGetMessage:
+ {
+ kdDebug(5006)<<msg<<endl;
+ kdDebug(5006)<<this<<endl;
+ kdDebug(5006)<<"Done"<<endl;
+ //KMMessage* msg = mParent->getMsg( mParent->find( mMsgList.first() ) );
+ msg->setComplete( true );
+ emit messageRetrieved( msg );
+ }
+ break;
+ case tDeleteMessage:
+ {
+ mParent->removeMsg( mMsgList );
+ }
+ break;
+ case tPutMessage:
+ {
+ mParent->addMsg( mMsgList.first() );
+ emit messageStored( mMsgList.first() );
+ }
+ break;
+ case tCopyMessage:
+ case tCreateFolder:
+ case tGetFolder:
+ case tListMessages:
+ kdDebug(5006)<<k_funcinfo<<"### Serious problem! "<<endl;
+ break;
+ default:
+ break;
+ }
+ //OK, we're done
+ //delete this;
+ deleteLater();
+}
+
+}
+
+#include "mboxjob.moc"
diff --git a/kmail/mboxjob.h b/kmail/mboxjob.h
new file mode 100644
index 00000000..a9318a5e
--- /dev/null
+++ b/kmail/mboxjob.h
@@ -0,0 +1,61 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ *
+ * This file is part of KMail, the KDE mail client.
+ * Copyright (c) 2003 Zack Rusin <zack@kde.org>
+ *
+ * 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 Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#ifndef MBOXJOB_H
+#define MBOXJOB_H
+
+#include "folderjob.h"
+
+class KMFolderMbox;
+class KMMessage;
+
+namespace KMail {
+
+class MboxJob : public FolderJob
+{
+ Q_OBJECT
+ friend class ::KMFolderMbox;
+public:
+ MboxJob( KMMessage *msg, JobType jt = tGetMessage, KMFolder *folder = 0 );
+ MboxJob( QPtrList<KMMessage>& msgList, const QString& sets,
+ JobType jt = tGetMessage, KMFolder *folder = 0 );
+ virtual ~MboxJob();
+protected:
+ void execute();
+ void setParent( const KMFolderMbox *parent );
+protected slots:
+ void startJob();
+private:
+ KMFolderMbox *mParent;
+};
+
+
+}
+#endif
diff --git a/kmail/messageactions.cpp b/kmail/messageactions.cpp
new file mode 100644
index 00000000..5c655cc3
--- /dev/null
+++ b/kmail/messageactions.cpp
@@ -0,0 +1,260 @@
+/*
+ Copyright (c) 2007 Volker Krause <vkrause@kde.org>
+
+ 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 "messageactions.h"
+
+#include "globalsettings.h"
+#include "kmfolder.h"
+#include "kmmessage.h"
+#include "kmreaderwin.h"
+
+#include <kaction.h>
+#include <kactioncollection.h>
+#include <kdebug.h>
+#include <klocale.h>
+
+#include <qwidget.h>
+
+using namespace KMail;
+
+MessageActions::MessageActions( KActionCollection *ac, QWidget * parent ) :
+ QObject( parent ),
+ mParent( parent ),
+ mActionCollection( ac ),
+ mCurrentMessage( 0 ),
+ mMessageView( 0 )
+{
+ mReplyActionMenu = new KActionMenu( i18n("Message->","&Reply"),
+ "mail_reply", mActionCollection,
+ "message_reply_menu" );
+ connect( mReplyActionMenu, SIGNAL(activated()), this,
+ SLOT(slotReplyToMsg()) );
+
+ mReplyAction = new KAction( i18n("&Reply..."), "mail_reply", Key_R, this,
+ SLOT(slotReplyToMsg()), mActionCollection, "reply" );
+ mReplyActionMenu->insert( mReplyAction );
+
+ mReplyAuthorAction = new KAction( i18n("Reply to A&uthor..."), "mail_reply",
+ SHIFT+Key_A, this,
+ SLOT(slotReplyAuthorToMsg()),
+ mActionCollection, "reply_author" );
+ mReplyActionMenu->insert( mReplyAuthorAction );
+
+ mReplyAllAction = new KAction( i18n("Reply to &All..."), "mail_replyall",
+ Key_A, this, SLOT(slotReplyAllToMsg()),
+ mActionCollection, "reply_all" );
+ mReplyActionMenu->insert( mReplyAllAction );
+
+ mReplyListAction = new KAction( i18n("Reply to Mailing-&List..."),
+ "mail_replylist", Key_L, this,
+ SLOT(slotReplyListToMsg()), mActionCollection,
+ "reply_list" );
+ mReplyActionMenu->insert( mReplyListAction );
+
+ mNoQuoteReplyAction = new KAction( i18n("Reply Without &Quote..."), SHIFT+Key_R,
+ this, SLOT(slotNoQuoteReplyToMsg()), mActionCollection, "noquotereply" );
+
+
+ mCreateTodoAction = new KAction( i18n("Create Task/Reminder..."), "mail_todo",
+ 0, this, SLOT(slotCreateTodo()), mActionCollection,
+ "create_todo" );
+
+
+ mStatusMenu = new KActionMenu ( i18n( "Mar&k Message" ),
+ mActionCollection, "set_status" );
+
+ mStatusMenu->insert(new KAction(KGuiItem(i18n("Mark Message as &Read"), "kmmsgread",
+ i18n("Mark selected messages as read")),
+ 0, this, SLOT(slotSetMsgStatusRead()),
+ mActionCollection, "status_read"));
+
+ mStatusMenu->insert(new KAction(KGuiItem(i18n("Mark Message as &New"), "kmmsgnew",
+ i18n("Mark selected messages as new")),
+ 0, this, SLOT(slotSetMsgStatusNew()),
+ mActionCollection, "status_new" ));
+
+ mStatusMenu->insert(new KAction(KGuiItem(i18n("Mark Message as &Unread"), "kmmsgunseen",
+ i18n("Mark selected messages as unread")),
+ 0, this, SLOT(slotSetMsgStatusUnread()),
+ mActionCollection, "status_unread"));
+
+ mStatusMenu->insert( new KActionSeparator( this ) );
+
+ mToggleFlagAction = new KToggleAction(i18n("Mark Message as &Important"), "mail_flag",
+ 0, this, SLOT(slotSetMsgStatusFlag()),
+ mActionCollection, "status_flag");
+ mToggleFlagAction->setCheckedState( i18n("Remove &Important Message Mark") );
+ mStatusMenu->insert( mToggleFlagAction );
+
+ mToggleTodoAction = new KToggleAction(i18n("Mark Message as &Action Item"), "mail_todo",
+ 0, this, SLOT(slotSetMsgStatusTodo()),
+ mActionCollection, "status_todo");
+ mToggleTodoAction->setCheckedState( i18n("Remove &Action Item Message Mark") );
+ mStatusMenu->insert( mToggleTodoAction );
+
+ mEditAction = new KAction( i18n("&Edit Message"), "edit", Key_T, this,
+ SLOT(editCurrentMessage()), mActionCollection, "edit" );
+ mEditAction->plugAccel( mActionCollection->kaccel() );
+
+ updateActions();
+}
+
+void MessageActions::setCurrentMessage(KMMessage * msg)
+{
+ mCurrentMessage = msg;
+ if ( !msg ) {
+ mSelectedSernums.clear();
+ mVisibleSernums.clear();
+ }
+ updateActions();
+}
+
+void MessageActions::setSelectedSernums(const QValueList< Q_UINT32 > & sernums)
+{
+ mSelectedSernums = sernums;
+ updateActions();
+}
+
+void MessageActions::setSelectedVisibleSernums(const QValueList< Q_UINT32 > & sernums)
+{
+ mVisibleSernums = sernums;
+ updateActions();
+}
+
+void MessageActions::updateActions()
+{
+ const bool singleMsg = (mCurrentMessage != 0);
+ const bool multiVisible = mVisibleSernums.count() > 0 || mCurrentMessage;
+ const bool flagsAvailable = GlobalSettings::self()->allowLocalFlags() ||
+ !((mCurrentMessage && mCurrentMessage->parent()) ? mCurrentMessage->parent()->isReadOnly() : true);
+
+ mCreateTodoAction->setEnabled( singleMsg );
+ mReplyActionMenu->setEnabled( singleMsg );
+ mReplyAction->setEnabled( singleMsg );
+ mNoQuoteReplyAction->setEnabled( singleMsg );
+ mReplyAuthorAction->setEnabled( singleMsg );
+ mReplyAllAction->setEnabled( singleMsg );
+ mReplyListAction->setEnabled( singleMsg );
+ mNoQuoteReplyAction->setEnabled( singleMsg );
+
+ mStatusMenu->setEnabled( multiVisible );
+ mToggleFlagAction->setEnabled( flagsAvailable );
+ mToggleTodoAction->setEnabled( flagsAvailable );
+
+ if ( mCurrentMessage ) {
+ mToggleTodoAction->setChecked( mCurrentMessage->isTodo() );
+ mToggleFlagAction->setChecked( mCurrentMessage->isImportant() );
+ }
+
+ mEditAction->setEnabled( singleMsg );
+}
+
+void MessageActions::slotCreateTodo()
+{
+ if ( !mCurrentMessage )
+ return;
+ KMCommand *command = new CreateTodoCommand( mParent, mCurrentMessage );
+ command->start();
+}
+
+void MessageActions::setMessageView(KMReaderWin * msgView)
+{
+ mMessageView = msgView;
+}
+
+void MessageActions::slotReplyToMsg()
+{
+ replyCommand<KMReplyToCommand>();
+}
+
+void MessageActions::slotReplyAuthorToMsg()
+{
+ replyCommand<KMReplyAuthorCommand>();
+}
+
+void MessageActions::slotReplyListToMsg()
+{
+ replyCommand<KMReplyListCommand>();
+}
+
+void MessageActions::slotReplyAllToMsg()
+{
+ replyCommand<KMReplyToAllCommand>();
+}
+
+void MessageActions::slotNoQuoteReplyToMsg()
+{
+ if ( !mCurrentMessage )
+ return;
+ KMCommand *command = new KMNoQuoteReplyToCommand( mParent, mCurrentMessage );
+ command->start();
+}
+
+void MessageActions::slotSetMsgStatusNew()
+{
+ setMessageStatus( KMMsgStatusNew );
+}
+
+void MessageActions::slotSetMsgStatusUnread()
+{
+ setMessageStatus( KMMsgStatusUnread );
+}
+
+void MessageActions::slotSetMsgStatusRead()
+{
+ setMessageStatus( KMMsgStatusRead );
+}
+
+void MessageActions::slotSetMsgStatusFlag()
+{
+ setMessageStatus( KMMsgStatusFlag, true );
+}
+
+void MessageActions::slotSetMsgStatusTodo()
+{
+ setMessageStatus( KMMsgStatusTodo, true );
+}
+
+void MessageActions::setMessageStatus( KMMsgStatus status, bool toggle )
+{
+ QValueList<Q_UINT32> serNums = mVisibleSernums;
+ if ( serNums.isEmpty() && mCurrentMessage )
+ serNums.append( mCurrentMessage->getMsgSerNum() );
+ if ( serNums.empty() )
+ return;
+ KMCommand *command = new KMSetStatusCommand( status, serNums, toggle );
+ command->start();
+}
+
+void MessageActions::editCurrentMessage()
+{
+ if ( !mCurrentMessage )
+ return;
+ KMCommand *command = 0;
+ KMFolder *folder = mCurrentMessage->parent();
+ // edit, unlike send again, removes the message from the folder
+ // we only want that for templates and drafts folders
+ if ( folder && ( kmkernel->folderIsDraftOrOutbox( folder ) ||
+ kmkernel->folderIsTemplates( folder ) ) )
+ command = new KMEditMsgCommand( mParent, mCurrentMessage );
+ else
+ command = new KMResendMessageCommand( mParent, mCurrentMessage );
+ command->start();
+}
+
+#include "messageactions.moc"
diff --git a/kmail/messageactions.h b/kmail/messageactions.h
new file mode 100644
index 00000000..6761a066
--- /dev/null
+++ b/kmail/messageactions.h
@@ -0,0 +1,106 @@
+/*
+ Copyright (c) 2007 Volker Krause <vkrause@kde.org>
+
+ 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_MESSAGEACTIONS_H
+#define KMAIL_MESSAGEACTIONS_H
+
+#include "kmcommands.h"
+#include "kmreaderwin.h"
+
+#include <qobject.h>
+#include <qvaluelist.h>
+
+class QWidget;
+class KAction;
+class KActionMenu;
+class KActionCollection;
+class KMMessage;
+
+namespace KMail {
+
+/**
+ Manages common actions that can be performed on one or more messages.
+*/
+class MessageActions : public QObject
+{
+ Q_OBJECT
+ public:
+ MessageActions( KActionCollection* ac, QWidget *parent );
+ void setMessageView( KMReaderWin *msgView );
+
+ void setCurrentMessage( KMMessage *msg );
+ void setSelectedSernums( const QValueList<Q_UINT32> &sernums );
+ void setSelectedVisibleSernums( const QValueList<Q_UINT32> &sernums );
+
+ KActionMenu* replyMenu() const { return mReplyActionMenu; }
+ KAction* replyListAction() const { return mReplyListAction; }
+ KAction* createTodoAction() const { return mCreateTodoAction; }
+
+ KActionMenu* messageStatusMenu() const { return mStatusMenu; }
+
+ KAction* editAction() const { return mEditAction; }
+
+ public slots:
+ void editCurrentMessage();
+
+ private:
+ void updateActions();
+ template<typename T> void replyCommand()
+ {
+ if ( !mCurrentMessage )
+ return;
+ const QString text = mMessageView ? mMessageView->copyText() : "";
+ KMCommand *command = new T( mParent, mCurrentMessage, text );
+ command->start();
+ }
+ void setMessageStatus( KMMsgStatus status, bool toggle = false );
+
+ private slots:
+ void slotReplyToMsg();
+ void slotReplyAuthorToMsg();
+ void slotReplyListToMsg();
+ void slotReplyAllToMsg();
+ void slotNoQuoteReplyToMsg();
+ void slotCreateTodo();
+ void slotSetMsgStatusNew();
+ void slotSetMsgStatusUnread();
+ void slotSetMsgStatusRead();
+ void slotSetMsgStatusTodo();
+ void slotSetMsgStatusFlag();
+
+ private:
+ QWidget *mParent;
+ KActionCollection *mActionCollection;
+ KMMessage* mCurrentMessage;
+ QValueList<Q_UINT32> mSelectedSernums;
+ QValueList<Q_UINT32> mVisibleSernums;
+ KMReaderWin *mMessageView;
+
+ KActionMenu *mReplyActionMenu;
+ KAction *mReplyAction, *mReplyAllAction, *mReplyAuthorAction,
+ *mReplyListAction, *mNoQuoteReplyAction;
+ KAction *mCreateTodoAction;
+ KActionMenu *mStatusMenu;
+ KToggleAction *mToggleFlagAction, *mToggleTodoAction;
+ KAction *mEditAction;
+};
+
+}
+
+#endif
+
diff --git a/kmail/messagecomposer.cpp b/kmail/messagecomposer.cpp
new file mode 100644
index 00000000..2cf5b813
--- /dev/null
+++ b/kmail/messagecomposer.cpp
@@ -0,0 +1,2284 @@
+/**
+ * messagecomposer.cpp
+ *
+ * Copyright (c) 2004 Bo Thorsen <bo@sonofthor.dk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "messagecomposer.h"
+#include "kmmsgpart.h"
+#define REALLY_WANT_KMCOMPOSEWIN_H
+#include "kmcomposewin.h"
+#undef REALLY_WANT_KMCOMPOSEWIN_H
+#include "klistboxdialog.h"
+#include "kcursorsaver.h"
+#include "messagesender.h"
+#include "kmfolder.h"
+#include "kmfoldercombobox.h"
+#include "keyresolver.h"
+#include "kleo_util.h"
+#include "globalsettings.h"
+#include "custommimeheader.h"
+#include "kmedit.h"
+#include "util.h"
+
+#include <libkpimidentities/identity.h>
+#include <libkpimidentities/identitymanager.h>
+#include <libemailfunctions/email.h>
+
+#include <ui/keyselectiondialog.h>
+#include <ui/keyapprovaldialog.h>
+#include <ui/messagebox.h>
+#include <kleo/cryptobackendfactory.h>
+#include <kleo/keylistjob.h>
+#include <kleo/encryptjob.h>
+#include <kleo/signencryptjob.h>
+#include <kleo/signjob.h>
+#include <kleo/specialjob.h>
+
+#include <kmime_util.h>
+#include <kmime_codecs.h>
+#include <kpgpblock.h>
+
+#include <mimelib/mimepp.h>
+
+#include <kmessagebox.h>
+#include <klocale.h>
+#include <kinputdialog.h>
+#include <kdebug.h>
+#include <kaction.h>
+#include <qfile.h>
+#include <qtextcodec.h>
+#include <qtextedit.h>
+#include <qtimer.h>
+
+#include <gpgmepp/key.h>
+#include <gpgmepp/keylistresult.h>
+#include <gpgmepp/encryptionresult.h>
+#include <gpgmepp/signingresult.h>
+#include <gpgmepp/context.h>
+
+#include <algorithm>
+#include <memory>
+
+// ## keep default values in sync with configuredialog.cpp, Security::CryptoTab::setup()
+// This should be ported to a .kcfg one day I suppose (dfaure).
+
+static inline bool warnSendUnsigned() {
+ KConfigGroup group( KMKernel::config(), "Composer" );
+ return group.readBoolEntry( "crypto-warning-unsigned", false );
+}
+static inline bool warnSendUnencrypted() {
+ KConfigGroup group( KMKernel::config(), "Composer" );
+ return group.readBoolEntry( "crypto-warning-unencrypted", false );
+}
+static inline bool saveMessagesEncrypted() {
+ KConfigGroup group( KMKernel::config(), "Composer" );
+ return group.readBoolEntry( "crypto-store-encrypted", true );
+}
+static inline bool encryptToSelf() {
+ // return !Kpgp::Module::getKpgp() || Kpgp::Module::getKpgp()->encryptToSelf();
+ KConfigGroup group( KMKernel::config(), "Composer" );
+ return group.readBoolEntry( "crypto-encrypt-to-self", true );
+}
+static inline bool showKeyApprovalDialog() {
+ KConfigGroup group( KMKernel::config(), "Composer" );
+ return group.readBoolEntry( "crypto-show-keys-for-approval", true );
+}
+
+static inline int encryptKeyNearExpiryWarningThresholdInDays() {
+ const KConfigGroup composer( KMKernel::config(), "Composer" );
+ if ( ! composer.readBoolEntry( "crypto-warn-when-near-expire", true ) )
+ return -1;
+ const int num = composer.readNumEntry( "crypto-warn-encr-key-near-expire-int", 14 );
+ return kMax( 1, num );
+}
+
+static inline int signingKeyNearExpiryWarningThresholdInDays() {
+ const KConfigGroup composer( KMKernel::config(), "Composer" );
+ if ( ! composer.readBoolEntry( "crypto-warn-when-near-expire", true ) )
+ return -1;
+ const int num = composer.readNumEntry( "crypto-warn-sign-key-near-expire-int", 14 );
+ return kMax( 1, num );
+}
+
+static inline int encryptRootCertNearExpiryWarningThresholdInDays() {
+ const KConfigGroup composer( KMKernel::config(), "Composer" );
+ if ( ! composer.readBoolEntry( "crypto-warn-when-near-expire", true ) )
+ return -1;
+ const int num = composer.readNumEntry( "crypto-warn-encr-root-near-expire-int", 14 );
+ return kMax( 1, num );
+}
+
+static inline int signingRootCertNearExpiryWarningThresholdInDays() {
+ const KConfigGroup composer( KMKernel::config(), "Composer" );
+ if ( ! composer.readBoolEntry( "crypto-warn-when-near-expire", true ) )
+ return -1;
+ const int num = composer.readNumEntry( "crypto-warn-sign-root-near-expire-int", 14 );
+ return kMax( 1, num );
+}
+
+static inline int encryptChainCertNearExpiryWarningThresholdInDays() {
+ const KConfigGroup composer( KMKernel::config(), "Composer" );
+ if ( ! composer.readBoolEntry( "crypto-warn-when-near-expire", true ) )
+ return -1;
+ const int num = composer.readNumEntry( "crypto-warn-encr-chaincert-near-expire-int", 14 );
+ return kMax( 1, num );
+}
+
+static inline int signingChainCertNearExpiryWarningThresholdInDays() {
+ const KConfigGroup composer( KMKernel::config(), "Composer" );
+ if ( ! composer.readBoolEntry( "crypto-warn-when-near-expire", true ) )
+ return -1;
+ const int num = composer.readNumEntry( "crypto-warn-sign-chaincert-near-expire-int", 14 );
+ return kMax( 1, num );
+}
+
+/*
+ Design of this:
+
+ The idea is that the main run of applyChanges here makes two jobs:
+ the first sets the flags for encryption/signing or not, and the other
+ starts the encryption process.
+
+ When a job is run, it has already been removed from the job queue. This
+ means if one of the current jobs needs to add new jobs, it can add them
+ to the front and that way control when new jobs are added.
+
+ For example, the compose message job will add jobs that will do the
+ actual encryption and signing.
+
+ There are two types of jobs: synchronous and asynchronous:
+
+ A synchronous job simply implments the execute() method and performs
+ it's operation there and sets mComposer->mRc to false if the compose
+ queue should be canceled.
+
+ An asynchronous job only sets up and starts it's operation. Before
+ returning, it connects to the result signals of the operation
+ (e.g. Kleo::Job's result(...) signal) and sets mComposer->mHoldJobs
+ to true. This makes the scheduler return to the event loop. The job
+ is now responsible for giving control back to the scheduler by
+ calling mComposer->doNextJob().
+*/
+
+/*
+ Test plan:
+
+ For each message format (e.g. openPGP/MIME)
+ 1. Body signed
+ 2. Body encrypted
+ 3. Body signed and encrypted
+ 4. Body encrypted, attachments encrypted (they must be encrypted together, mEarlyAddAttachments)
+ 5. Body encrypted, attachments not encrypted
+ 6. Body encrypted, attachment encrypted and signed (separately)
+ 7. Body not encrypted, one attachment encrypted+signed, one attachment encrypted only, one attachment signed only
+ (https://intevation.de/roundup/aegypten/issue295)
+ (this is the reason attachments can't be encrypted together)
+ 8. Body and attachments encrypted+signed (they must be encrypted+signed together, mEarlyAddAttachments)
+ 9. Body encrypted+signed, attachments encrypted
+ 10. Body encrypted+signed, one attachment signed, one attachment not encrypted nor signed
+ ...
+
+ I recorded a KDExecutor script sending all of the above (David)
+
+ Further tests (which test opportunistic encryption):
+ 1. Send a message to a person with valid key but without encryption preference
+ and answer the question whether the message should be encrypted with Yes.
+ 2. Send a message to a person with valid key but without encryption preference
+ and answer the question whether the message should be encrypted with No.
+ 3. Send a message to a person with valid key and with encryption preference
+ "Encrypt whenever possible" (aka opportunistic encryption).
+*/
+
+static QString mErrorProcessingStructuringInfo =
+i18n("<qt><p>Structuring information returned by the Crypto plug-in "
+ "could not be processed correctly; the plug-in might be damaged.</p>"
+ "<p>Please contact your system administrator.</p></qt>");
+static QString mErrorNoCryptPlugAndNoBuildIn =
+i18n("<p>No active Crypto Plug-In was found and the built-in OpenPGP code "
+ "did not run successfully.</p>"
+ "<p>You can do two things to change this:</p>"
+ "<ul><li><em>either</em> activate a Plug-In using the "
+ "Settings->Configure KMail->Plug-In dialog.</li>"
+ "<li><em>or</em> specify traditional OpenPGP settings on the same dialog's "
+ "Identity->Advanced tab.</li></ul>");
+
+
+class MessageComposerJob {
+public:
+ MessageComposerJob( MessageComposer* composer ) : mComposer( composer ) {}
+ virtual ~MessageComposerJob() {}
+
+ virtual void execute() = 0;
+
+protected:
+ // These are the methods that call the private MessageComposer methods
+ // Workaround for friend not being inherited
+ void adjustCryptFlags() { mComposer->adjustCryptFlags(); }
+ void composeMessage() { mComposer->composeMessage(); }
+ void continueComposeMessage( KMMessage& msg, bool doSign, bool doEncrypt,
+ Kleo::CryptoMessageFormat format )
+ {
+ mComposer->continueComposeMessage( msg, doSign, doEncrypt, format );
+ }
+ void chiasmusEncryptAllAttachments() {
+ mComposer->chiasmusEncryptAllAttachments();
+ }
+
+ MessageComposer* mComposer;
+};
+
+class ChiasmusBodyPartEncryptJob : public MessageComposerJob {
+public:
+ ChiasmusBodyPartEncryptJob( MessageComposer * composer )
+ : MessageComposerJob( composer ) {}
+
+ void execute() {
+ chiasmusEncryptAllAttachments();
+ }
+};
+
+class AdjustCryptFlagsJob : public MessageComposerJob {
+public:
+ AdjustCryptFlagsJob( MessageComposer* composer )
+ : MessageComposerJob( composer ) {}
+
+ void execute() {
+ adjustCryptFlags();
+ }
+};
+
+class ComposeMessageJob : public MessageComposerJob {
+public:
+ ComposeMessageJob( MessageComposer* composer )
+ : MessageComposerJob( composer ) {}
+
+ void execute() {
+ composeMessage();
+ }
+};
+
+MessageComposer::MessageComposer( KMComposeWin* win, const char* name )
+ : QObject( win, name ), mComposeWin( win ), mCurrentJob( 0 ),
+ mReferenceMessage( 0 ), mKeyResolver( 0 ),
+ mUseOpportunisticEncryption( false ),
+ mSignBody( false ), mEncryptBody( false ),
+ mSigningRequested( false ), mEncryptionRequested( false ),
+ mDoSign( false ), mDoEncrypt( false ),
+ mAllowedCryptoMessageFormats( 0 ),
+ mDisableCrypto( false ),
+ mDisableBreaking( false ),
+ mDebugComposerCrypto( false ),
+ mAutoCharset( true ),
+ mIsRichText( false ),
+ mIdentityUid( 0 ), mRc( true ),
+ mHoldJobs( false ),
+ mNewBodyPart( 0 ),
+ mEarlyAddAttachments( false ), mAllAttachmentsAreInBody( false ),
+ mPreviousBoundaryLevel( 0 ),
+ mEncryptWithChiasmus( false ),
+ mPerformingSignOperation( false )
+{
+}
+
+MessageComposer::~MessageComposer()
+{
+ delete mKeyResolver; mKeyResolver = 0;
+ delete mNewBodyPart; mNewBodyPart = 0;
+}
+
+void MessageComposer::applyChanges( bool disableCrypto )
+{
+ // Do the initial setup
+ if( getenv("KMAIL_DEBUG_COMPOSER_CRYPTO") != 0 ) {
+ QCString cE = getenv("KMAIL_DEBUG_COMPOSER_CRYPTO");
+ mDebugComposerCrypto = cE == "1" || cE.upper() == "ON" || cE.upper() == "TRUE";
+ kdDebug(5006) << "KMAIL_DEBUG_COMPOSER_CRYPTO = TRUE" << endl;
+ } else {
+ mDebugComposerCrypto = false;
+ kdDebug(5006) << "KMAIL_DEBUG_COMPOSER_CRYPTO = FALSE" << endl;
+ }
+
+ mHoldJobs = false;
+ mRc = true;
+
+ mDisableCrypto = disableCrypto;
+
+ // 1: Read everything from KMComposeWin and set all
+ // trivial parts of the message
+ readFromComposeWin();
+
+ // From now on, we're not supposed to read from the composer win
+ // TODO: Make it so ;-)
+ // 1.5: Replace all body parts with their chiasmus-encrypted equivalent
+ mJobs.push_back( new ChiasmusBodyPartEncryptJob( this ) );
+
+ // 2: Set encryption/signing options and resolve keys
+ mJobs.push_back( new AdjustCryptFlagsJob( this ) );
+
+ // 3: Build the message (makes the crypto jobs also)
+ mJobs.push_back( new ComposeMessageJob( this ) );
+
+ // Finally: Run the jobs
+ doNextJob();
+}
+
+void MessageComposer::doNextJob()
+{
+ delete mCurrentJob; mCurrentJob = 0;
+
+ if( mJobs.isEmpty() ) {
+ // No more jobs. Signal that we're done
+ emitDone( mRc );
+ return;
+ }
+
+ if( !mRc ) {
+ // Something has gone wrong - stop the process and bail out
+ while( !mJobs.isEmpty() ) {
+ delete mJobs.front();
+ mJobs.pop_front();
+ }
+ emitDone( false );
+ return;
+ }
+
+ // We have more jobs to do, but allow others to come first
+ QTimer::singleShot( 0, this, SLOT( slotDoNextJob() ) );
+}
+
+void MessageComposer::emitDone( bool b )
+{
+ // Save memory - before sending the mail
+ mEncodedBody = QByteArray();
+ delete mNewBodyPart; mNewBodyPart = 0;
+ mOldBodyPart.clear();
+ emit done( b );
+}
+
+void MessageComposer::slotDoNextJob()
+{
+ assert( !mCurrentJob );
+ if( mHoldJobs )
+ // Always make it run from now. If more than one job should be held,
+ // The individual jobs must do this.
+ mHoldJobs = false;
+ else {
+ assert( !mJobs.empty() );
+ // Get the next job
+ mCurrentJob = mJobs.front();
+ assert( mCurrentJob );
+ mJobs.pop_front();
+
+ // Execute it
+ mCurrentJob->execute();
+ }
+
+ // Finally run the next job if necessary
+ if( !mHoldJobs )
+ doNextJob();
+}
+
+void MessageComposer::readFromComposeWin()
+{
+ // Copy necessary attributes over
+ mDisableBreaking = false;
+
+ mSignBody = mComposeWin->mSignAction->isChecked();
+ mSigningRequested = mSignBody; // for now; will be adjusted depending on attachments
+ mEncryptBody = mComposeWin->mEncryptAction->isChecked();
+ mEncryptionRequested = mEncryptBody; // for now; will be adjusted depending on attachments
+
+ mAutoCharset = mComposeWin->mAutoCharset;
+ mCharset = mComposeWin->mCharset;
+ mReferenceMessage = mComposeWin->mMsg;
+ // if the user made any modifications to the message then the Content-Type
+ // of the message is no longer reliable (e. g. if he editted a draft/resent a
+ // message and then removed all attachments or changed from PGP/MIME signed
+ // to clearsigned);
+ // even if the user didn't make any modifications to the message the
+ // Content-Type of the message might be wrong, e.g. when inline-forwarding
+ // an mp/alt message then the Content-Type is set to mp/alt although it should
+ // be text/plain (cf. bug 127526);
+ // OTOH we must not reset the Content-Type of inline invitations;
+ // therefore we reset the Content-Type to text/plain whenever the current
+ // Content-Type is multipart/*.
+ if ( mReferenceMessage->type() == DwMime::kTypeMultipart )
+ mReferenceMessage->setHeaderField( "Content-Type", "text/plain" );
+ mUseOpportunisticEncryption = GlobalSettings::self()->pgpAutoEncrypt();
+ mAllowedCryptoMessageFormats = mComposeWin->cryptoMessageFormat();
+
+ if( mAutoCharset ) {
+ QCString charset = KMMsgBase::autoDetectCharset( mCharset, KMMessage::preferredCharsets(), mComposeWin->mEditor->text() );
+ if( charset.isEmpty() )
+ {
+ KMessageBox::sorry( mComposeWin,
+ i18n( "No suitable encoding could be found for "
+ "your message.\nPlease set an encoding "
+ "using the 'Options' menu." ) );
+ mRc = false;
+ return;
+ }
+ mCharset = charset;
+ // Also apply this to the composer window
+ mComposeWin->mCharset = charset;
+ }
+ mReferenceMessage->setCharset(mCharset);
+
+ mReferenceMessage->setTo(mComposeWin->to());
+ mReferenceMessage->setFrom(mComposeWin->from());
+ mReferenceMessage->setCc(mComposeWin->cc());
+ mReferenceMessage->setSubject(mComposeWin->subject());
+ mReferenceMessage->setReplyTo(mComposeWin->replyTo());
+ mReferenceMessage->setBcc(mComposeWin->bcc());
+
+ const KPIM::Identity & id = mComposeWin->identity();
+
+ KMFolder *f = mComposeWin->mFcc->getFolder();
+ assert( f != 0 );
+ if ( f->idString() == id.fcc() )
+ mReferenceMessage->removeHeaderField("X-KMail-Fcc");
+ else
+ mReferenceMessage->setFcc( f->idString() );
+
+ // set the correct drafts folder
+ mReferenceMessage->setDrafts( id.drafts() );
+
+ if (id.isDefault())
+ mReferenceMessage->removeHeaderField("X-KMail-Identity");
+ else mReferenceMessage->setHeaderField("X-KMail-Identity", QString::number( id.uoid() ));
+
+ QString replyAddr;
+ if (!mComposeWin->replyTo().isEmpty()) replyAddr = mComposeWin->replyTo();
+ else replyAddr = mComposeWin->from();
+
+ if (mComposeWin->mRequestMDNAction->isChecked())
+ mReferenceMessage->setHeaderField("Disposition-Notification-To", replyAddr);
+ else
+ mReferenceMessage->removeHeaderField("Disposition-Notification-To");
+
+ if (mComposeWin->mUrgentAction->isChecked()) {
+ mReferenceMessage->setHeaderField("X-PRIORITY", "2 (High)");
+ mReferenceMessage->setHeaderField("Priority", "urgent");
+ } else {
+ mReferenceMessage->removeHeaderField("X-PRIORITY");
+ mReferenceMessage->removeHeaderField("Priority");
+ }
+
+ int num = GlobalSettings::self()->custHeaderCount();
+ for(int ix=0; ix<num; ix++) {
+ CustomMimeHeader customMimeHeader( QString::number(ix) );
+ customMimeHeader.readConfig();
+ mReferenceMessage->setHeaderField(
+ KMMsgBase::toUsAscii( customMimeHeader.custHeaderName() ),
+ customMimeHeader.custHeaderValue() );
+ }
+
+
+ // we have to remember the Bcc because it might have been overwritten
+ // by a custom header (therefore we can't use bcc() later) and because
+ // mimelib removes addresses without domain part (therefore we can't use
+ // mReferenceMessage->bcc() later and also not now. So get the Bcc from
+ // the composer window.)
+ mBcc = mComposeWin->bcc();
+ mTo = KPIM::splitEmailAddrList( mComposeWin->to().stripWhiteSpace() );
+ mCc = KPIM::splitEmailAddrList( mComposeWin->cc().stripWhiteSpace() );
+ mBccList = KPIM::splitEmailAddrList( mBcc.stripWhiteSpace() );
+
+ for ( unsigned int i = 0 ; i < mComposeWin->mAtmList.count() ; ++i )
+ mAttachments.push_back( Attachment( mComposeWin->mAtmList.at(i),
+ mComposeWin->signFlagOfAttachment( i ),
+ mComposeWin->encryptFlagOfAttachment( i ) ) );
+
+ mEncryptWithChiasmus = mComposeWin->mEncryptWithChiasmus;
+
+ mIsRichText = mComposeWin->mEditor->textFormat() == Qt::RichText;
+ mIdentityUid = mComposeWin->identityUid();
+ mText = breakLinesAndApplyCodec();
+ assert( mText.isEmpty() || mText[mText.size()-1] == '\n' );
+ // Hopefully we can get rid of this eventually, it's needed to be able
+ // to break the plain/text version of a multipart/alternative (html) mail
+ // according to the line breaks of the richtext version.
+ mLineBreakColumn = mComposeWin->mEditor->lineBreakColumn();
+}
+
+static QCString escape_quoted_string( const QCString & str ) {
+ QCString result;
+ const unsigned int str_len = str.length();
+ result.resize( 2*str_len + 1 );
+ char * d = result.data();
+ for ( unsigned int i = 0 ; i < str_len ; ++i )
+ switch ( const char ch = str[i] ) {
+ case '\\':
+ case '"':
+ *d++ = '\\';
+ default: // fall through:
+ *d++ = ch;
+ }
+ result.truncate( d - result.begin() );
+ return result;
+}
+
+bool MessageComposer::encryptWithChiasmus( const Kleo::CryptoBackend::Protocol * chiasmus,
+ const QByteArray& body,
+ QByteArray& resultData )
+{
+ std::auto_ptr<Kleo::SpecialJob> job( chiasmus->specialJob( "x-encrypt", QMap<QString,QVariant>() ) );
+ if ( !job.get() ) {
+ const QString msg = i18n( "Chiasmus backend does not offer the "
+ "\"x-encrypt\" function. Please report this bug." );
+ KMessageBox::error( mComposeWin, msg, i18n( "Chiasmus Backend Error" ) );
+ return false;
+ }
+ if ( !job->setProperty( "key", GlobalSettings::chiasmusKey() ) ||
+ !job->setProperty( "options", GlobalSettings::chiasmusOptions() ) ||
+ !job->setProperty( "input", body ) ) {
+ const QString msg = i18n( "The \"x-encrypt\" function does not accept "
+ "the expected parameters. Please report this bug." );
+ KMessageBox::error( mComposeWin, msg, i18n( "Chiasmus Backend Error" ) );
+ return false;
+ }
+ const GpgME::Error err = job->exec();
+ if ( err.isCanceled() || err ) {
+ if ( err )
+ job->showErrorDialog( mComposeWin, i18n( "Chiasmus Encryption Error" ) );
+ return false;
+ }
+ const QVariant result = job->property( "result" );
+ if ( result.type() != QVariant::ByteArray ) {
+ const QString msg = i18n( "Unexpected return value from Chiasmus backend: "
+ "The \"x-encrypt\" function did not return a "
+ "byte array. Please report this bug." );
+ KMessageBox::error( mComposeWin, msg, i18n( "Chiasmus Backend Error" ) );
+ return false;
+ }
+ resultData = result.toByteArray();
+ return true;
+}
+
+void MessageComposer::chiasmusEncryptAllAttachments() {
+ if ( !mEncryptWithChiasmus )
+ return;
+ assert( !GlobalSettings::chiasmusKey().isEmpty() ); // kmcomposewin code should have made sure
+ if ( mAttachments.empty() )
+ return;
+ const Kleo::CryptoBackend::Protocol * chiasmus
+ = Kleo::CryptoBackendFactory::instance()->protocol( "Chiasmus" );
+ assert( chiasmus ); // kmcomposewin code should have made sure
+
+
+ for ( QValueVector<Attachment>::iterator it = mAttachments.begin(), end = mAttachments.end() ; it != end ; ++it ) {
+ KMMessagePart * part = it->part;
+ const QString filename = part->fileName();
+ if ( filename.endsWith( ".xia", false ) )
+ continue; // already encrypted
+ const QByteArray body = part->bodyDecodedBinary();
+ QByteArray resultData;
+ if ( !encryptWithChiasmus( chiasmus, body, resultData ) ) {
+ mRc = false;
+ return;
+ }
+ // everything ok, so let's fill in the part again:
+ QValueList<int> dummy;
+ part->setBodyAndGuessCte( resultData, dummy );
+ part->setTypeStr( "application" );
+ part->setSubtypeStr( "vnd.de.bund.bsi.chiasmus" );
+ part->setName( filename + ".xia" );
+ // this is taken from kmmsgpartdlg.cpp:
+ QCString encoding = KMMsgBase::autoDetectCharset( part->charset(), KMMessage::preferredCharsets(), filename );
+ if ( encoding.isEmpty() )
+ encoding = "utf-8";
+ const QCString enc_name = KMMsgBase::encodeRFC2231String( filename + ".xia", encoding );
+ const QCString cDisp = "attachment;\n\tfilename"
+ + ( QString( enc_name ) != filename + ".xia"
+ ? "*=" + enc_name
+ : "=\"" + escape_quoted_string( enc_name ) + '\"' );
+ part->setContentDisposition( cDisp );
+ }
+}
+
+void MessageComposer::adjustCryptFlags()
+{
+ if ( !mDisableCrypto &&
+ mAllowedCryptoMessageFormats & Kleo::InlineOpenPGPFormat &&
+ !mAttachments.empty() &&
+ ( mSigningRequested || mEncryptionRequested ) )
+ {
+ int ret;
+ if ( mAllowedCryptoMessageFormats == Kleo::InlineOpenPGPFormat ) {
+ ret = KMessageBox::warningYesNoCancel( mComposeWin,
+ i18n("The inline OpenPGP crypto message format "
+ "does not support encryption or signing "
+ "of attachments.\n"
+ "Really use deprecated inline OpenPGP?"),
+ i18n("Insecure Message Format"),
+ i18n("Use Inline OpenPGP"),
+ i18n("Use OpenPGP/MIME") );
+ }
+ else {
+ // if other crypto message formats are allowed then simply don't use
+ // inline OpenPGP
+ ret = KMessageBox::No;
+ }
+
+ if ( ret == KMessageBox::Cancel ) {
+ mRc = false;
+ return;
+ } else if ( ret == KMessageBox::No ) {
+ mAllowedCryptoMessageFormats &= ~Kleo::InlineOpenPGPFormat;
+ mAllowedCryptoMessageFormats |= Kleo::OpenPGPMIMEFormat;
+ if ( mSigningRequested ) {
+ // The composer window disabled signing on the attachments, re-enable it
+ for ( unsigned int idx = 0 ; idx < mAttachments.size() ; ++idx )
+ mAttachments[idx].sign = true;
+ }
+ if ( mEncryptionRequested ) {
+ // The composer window disabled encrypting on the attachments, re-enable it
+ // We assume this is what the user wants - after all he chose OpenPGP/MIME for this.
+ for ( unsigned int idx = 0 ; idx < mAttachments.size() ; ++idx )
+ mAttachments[idx].encrypt = true;
+ }
+ }
+ }
+
+ mKeyResolver =
+ new Kleo::KeyResolver( encryptToSelf(), showKeyApprovalDialog(),
+ mUseOpportunisticEncryption, mAllowedCryptoMessageFormats,
+ encryptKeyNearExpiryWarningThresholdInDays(),
+ signingKeyNearExpiryWarningThresholdInDays(),
+ encryptRootCertNearExpiryWarningThresholdInDays(),
+ signingRootCertNearExpiryWarningThresholdInDays(),
+ encryptChainCertNearExpiryWarningThresholdInDays(),
+ signingChainCertNearExpiryWarningThresholdInDays() );
+
+ if ( !mDisableCrypto ) {
+ const KPIM::Identity & id =
+ kmkernel->identityManager()->identityForUoidOrDefault( mIdentityUid );
+
+ QStringList encryptToSelfKeys;
+ if ( !id.pgpEncryptionKey().isEmpty() )
+ encryptToSelfKeys.push_back( id.pgpEncryptionKey() );
+ if ( !id.smimeEncryptionKey().isEmpty() )
+ encryptToSelfKeys.push_back( id.smimeEncryptionKey() );
+ if ( mKeyResolver->setEncryptToSelfKeys( encryptToSelfKeys ) != Kpgp::Ok ) {
+ mRc = false;
+ return;
+ }
+
+ QStringList signKeys;
+ if ( !id.pgpSigningKey().isEmpty() )
+ signKeys.push_back( mPGPSigningKey = id.pgpSigningKey() );
+ if ( !id.smimeSigningKey().isEmpty() )
+ signKeys.push_back( mSMIMESigningKey = id.smimeSigningKey() );
+ if ( mKeyResolver->setSigningKeys( signKeys ) != Kpgp::Ok ) {
+ mRc = false;
+ return;
+ }
+ }
+
+ mKeyResolver->setPrimaryRecipients( mTo + mCc );
+ mKeyResolver->setSecondaryRecipients( mBccList );
+
+ // check settings of composer buttons *and* attachment check boxes
+ bool doSignCompletely = mSigningRequested;
+ bool doEncryptCompletely = mEncryptionRequested;
+ for ( unsigned int idx = 0 ; idx < mAttachments.size() ; ++idx ) {
+ if ( mAttachments[idx].encrypt )
+ mEncryptionRequested = true;
+ else
+ doEncryptCompletely = false;
+ if ( mAttachments[idx].sign )
+ mSigningRequested = true;
+ else
+ doSignCompletely = false;
+ }
+
+ mDoSign = !mDisableCrypto && determineWhetherToSign( doSignCompletely );
+
+ if ( !mRc )
+ return;
+
+ mDoEncrypt = !mDisableCrypto && determineWhetherToEncrypt( doEncryptCompletely );
+
+ if ( !mRc )
+ return;
+
+ // resolveAllKeys needs to run even if mDisableCrypto == true, since
+ // we depend on it collecting all recipients into one dummy
+ // SplitInfo to avoid special-casing all over the place:
+ if ( mKeyResolver->resolveAllKeys( mDoSign, mDoEncrypt ) != Kpgp::Ok )
+ mRc = false;
+}
+
+bool MessageComposer::determineWhetherToSign( bool doSignCompletely ) {
+ bool sign = false;
+ switch ( mKeyResolver->checkSigningPreferences( mSigningRequested ) ) {
+ case Kleo::DoIt:
+ if ( !mSigningRequested ) {
+ markAllAttachmentsForSigning( true );
+ return true;
+ }
+ sign = true;
+ break;
+ case Kleo::DontDoIt:
+ sign = false;
+ break;
+ case Kleo::AskOpportunistic:
+ assert( 0 );
+ case Kleo::Ask:
+ {
+ // the user wants to be asked or has to be asked
+ const KCursorSaver idle( KBusyPtr::idle() );
+ const QString msg = i18n("Examination of the recipient's signing preferences "
+ "yielded that you be asked whether or not to sign "
+ "this message.\n"
+ "Sign this message?");
+ switch ( KMessageBox::questionYesNoCancel( mComposeWin, msg,
+ i18n("Sign Message?"),
+ i18n("to sign","&Sign"),
+ i18n("Do &Not Sign") ) ) {
+ case KMessageBox::Cancel:
+ mRc = false;
+ return false;
+ case KMessageBox::Yes:
+ markAllAttachmentsForSigning( true );
+ return true;
+ case KMessageBox::No:
+ markAllAttachmentsForSigning( false );
+ return false;
+ }
+ }
+ break;
+ case Kleo::Conflict:
+ {
+ // warn the user that there are conflicting signing preferences
+ const KCursorSaver idle( KBusyPtr::idle() );
+ const QString msg = i18n("There are conflicting signing preferences "
+ "for these recipients.\n"
+ "Sign this message?");
+ switch ( KMessageBox::warningYesNoCancel( mComposeWin, msg,
+ i18n("Sign Message?"),
+ i18n("to sign","&Sign"),
+ i18n("Do &Not Sign") ) ) {
+ case KMessageBox::Cancel:
+ mRc = false;
+ return false;
+ case KMessageBox::Yes:
+ markAllAttachmentsForSigning( true );
+ return true;
+ case KMessageBox::No:
+ markAllAttachmentsForSigning( false );
+ return false;
+ }
+ }
+ break;
+ case Kleo::Impossible:
+ {
+ const KCursorSaver idle( KBusyPtr::idle() );
+ const QString msg = i18n("You have requested to sign this message, "
+ "but no valid signing keys have been configured "
+ "for this identity.");
+ if ( KMessageBox::warningContinueCancel( mComposeWin, msg,
+ i18n("Send Unsigned?"),
+ i18n("Send &Unsigned") )
+ == KMessageBox::Cancel ) {
+ mRc = false;
+ return false;
+ } else {
+ markAllAttachmentsForSigning( false );
+ return false;
+ }
+ }
+ }
+
+ if ( !sign || !doSignCompletely ) {
+ if ( warnSendUnsigned() ) {
+ const KCursorSaver idle( KBusyPtr::idle() );
+ const QString msg = sign && !doSignCompletely
+ ? i18n("Some parts of this message will not be signed.\n"
+ "Sending only partially signed messages might violate site policy.\n"
+ "Sign all parts instead?") // oh, I hate this...
+ : i18n("This message will not be signed.\n"
+ "Sending unsigned message might violate site policy.\n"
+ "Sign message instead?") ; // oh, I hate this...
+ const QString buttonText = sign && !doSignCompletely
+ ? i18n("&Sign All Parts") : i18n("&Sign") ;
+ switch ( KMessageBox::warningYesNoCancel( mComposeWin, msg,
+ i18n("Unsigned-Message Warning"),
+ buttonText,
+ i18n("Send &As Is") ) ) {
+ case KMessageBox::Cancel:
+ mRc = false;
+ return false;
+ case KMessageBox::Yes:
+ markAllAttachmentsForSigning( true );
+ return true;
+ case KMessageBox::No:
+ return sign || doSignCompletely;
+ }
+ }
+ }
+
+ return sign || doSignCompletely ;
+}
+
+bool MessageComposer::determineWhetherToEncrypt( bool doEncryptCompletely ) {
+ bool encrypt = false;
+ bool opportunistic = false;
+ switch ( mKeyResolver->checkEncryptionPreferences( mEncryptionRequested ) ) {
+ case Kleo::DoIt:
+ if ( !mEncryptionRequested ) {
+ markAllAttachmentsForEncryption( true );
+ return true;
+ }
+ encrypt = true;
+ break;
+ case Kleo::DontDoIt:
+ encrypt = false;
+ break;
+ case Kleo::AskOpportunistic:
+ opportunistic = true;
+ // fall through...
+ case Kleo::Ask:
+ {
+ // the user wants to be asked or has to be asked
+ const KCursorSaver idle( KBusyPtr::idle() );
+ const QString msg = opportunistic
+ ? i18n("Valid trusted encryption keys were found for all recipients.\n"
+ "Encrypt this message?")
+ : i18n("Examination of the recipient's encryption preferences "
+ "yielded that you be asked whether or not to encrypt "
+ "this message.\n"
+ "Encrypt this message?");
+ switch ( KMessageBox::questionYesNoCancel( mComposeWin, msg,
+ i18n("Encrypt Message?"),
+ mDoSign
+ ? i18n("Sign && &Encrypt")
+ : i18n("&Encrypt"),
+ mDoSign
+ ? i18n("&Sign Only")
+ : i18n("&Send As-Is") ) ) {
+ case KMessageBox::Cancel:
+ mRc = false;
+ return false;
+ case KMessageBox::Yes:
+ markAllAttachmentsForEncryption( true );
+ return true;
+ case KMessageBox::No:
+ markAllAttachmentsForEncryption( false );
+ return false;
+ }
+ }
+ break;
+ case Kleo::Conflict:
+ {
+ // warn the user that there are conflicting encryption preferences
+ const KCursorSaver idle( KBusyPtr::idle() );
+ const QString msg = i18n("There are conflicting encryption preferences "
+ "for these recipients.\n"
+ "Encrypt this message?");
+ switch ( KMessageBox::warningYesNoCancel( mComposeWin, msg,
+ i18n("Encrypt Message?"),
+ i18n("&Encrypt"),
+ i18n("Do &Not Encrypt") ) ) {
+ case KMessageBox::Cancel:
+ mRc = false;
+ return false;
+ case KMessageBox::Yes:
+ markAllAttachmentsForEncryption( true );
+ return true;
+ case KMessageBox::No:
+ markAllAttachmentsForEncryption( false );
+ return false;
+ }
+ }
+ break;
+ case Kleo::Impossible:
+ {
+ const KCursorSaver idle( KBusyPtr::idle() );
+ const QString msg = i18n("You have requested to encrypt this message, "
+ "and to encrypt a copy to yourself, "
+ "but no valid trusted encryption keys have been "
+ "configured for this identity.");
+ if ( KMessageBox::warningContinueCancel( mComposeWin, msg,
+ i18n("Send Unencrypted?"),
+ i18n("Send &Unencrypted") )
+ == KMessageBox::Cancel ) {
+ mRc = false;
+ return false;
+ } else {
+ markAllAttachmentsForEncryption( false );
+ return false;
+ }
+ }
+ }
+
+ if ( !encrypt || !doEncryptCompletely ) {
+ if ( warnSendUnencrypted() ) {
+ const KCursorSaver idle( KBusyPtr::idle() );
+ const QString msg = !doEncryptCompletely
+ ? i18n("Some parts of this message will not be encrypted.\n"
+ "Sending only partially encrypted messages might violate site policy "
+ "and/or leak sensitive information.\n"
+ "Encrypt all parts instead?") // oh, I hate this...
+ : i18n("This message will not be encrypted.\n"
+ "Sending unencrypted messages might violate site policy and/or "
+ "leak sensitive information.\n"
+ "Encrypt messages instead?") ; // oh, I hate this...
+ const QString buttonText = !doEncryptCompletely
+ ? i18n("&Encrypt All Parts") : i18n("&Encrypt") ;
+ switch ( KMessageBox::warningYesNoCancel( mComposeWin, msg,
+ i18n("Unencrypted Message Warning"),
+ buttonText,
+ mDoSign
+ ? i18n("&Sign Only")
+ : i18n("&Send As-Is") ) ) {
+ case KMessageBox::Cancel:
+ mRc = false;
+ return false;
+ case KMessageBox::Yes:
+ markAllAttachmentsForEncryption( true );
+ return true;
+ case KMessageBox::No:
+ return encrypt || doEncryptCompletely;
+ }
+ }
+ }
+
+ return encrypt || doEncryptCompletely ;
+}
+
+void MessageComposer::markAllAttachmentsForSigning( bool sign ) {
+ mSignBody = sign;
+ for ( QValueVector<Attachment>::iterator it = mAttachments.begin() ; it != mAttachments.end() ; ++it )
+ it->sign = sign;
+}
+
+void MessageComposer::markAllAttachmentsForEncryption( bool enc ) {
+ mEncryptBody = enc;
+ for ( QValueVector<Attachment>::iterator it = mAttachments.begin() ; it != mAttachments.end() ; ++it )
+ it->encrypt = enc;
+}
+
+
+void MessageComposer::composeMessage()
+{
+ for ( unsigned int i = 0 ; i < numConcreteCryptoMessageFormats ; ++i ) {
+ if ( mKeyResolver->encryptionItems( concreteCryptoMessageFormats[i] ).empty() )
+ continue;
+ KMMessage * msg = new KMMessage( *mReferenceMessage );
+ composeMessage( *msg, mDoSign, mDoEncrypt, concreteCryptoMessageFormats[i] );
+ if ( !mRc )
+ return;
+ }
+}
+
+//
+// These are replacements for StructuringInfo(Wrapper):
+//
+
+// check whether to use multipart/{signed,encrypted}
+static inline bool makeMultiMime( Kleo::CryptoMessageFormat f, bool sign ) {
+ switch ( f ) {
+ default:
+ case Kleo::InlineOpenPGPFormat:
+ case Kleo::SMIMEOpaqueFormat: return false;
+ case Kleo::OpenPGPMIMEFormat: return true;
+ case Kleo::SMIMEFormat: return sign; // only on sign - there's no mp/encrypted for S/MIME
+ }
+}
+static inline bool makeMultiPartSigned( Kleo::CryptoMessageFormat f ) {
+ return makeMultiMime( f, true );
+}
+static inline bool makeMultiPartEncrypted( Kleo::CryptoMessageFormat f ) {
+ return makeMultiMime( f, false );
+}
+
+static inline bool makeMimeObject( Kleo::CryptoMessageFormat f, bool /*signing*/ ) {
+ return f != Kleo::InlineOpenPGPFormat;
+}
+
+static inline const char * toplevelContentType( Kleo::CryptoMessageFormat f, bool signing ) {
+ switch ( f ) {
+ default:
+ case Kleo::InlineOpenPGPFormat: return 0;
+ case Kleo::OpenPGPMIMEFormat:
+ return signing ?
+ "multipart/signed;\n\t"
+ "boundary=\"%boundary\";\n\t"
+ "protocol=\"application/pgp-signature\";\n\t"
+ "micalg=pgp-sha1" // FIXME: obtain this parameter from gpgme!
+ :
+ "multipart/encrypted;\n\t"
+ "boundary=\"%boundary\";\n\t"
+ "protocol=\"application/pgp-encrypted\""
+ ;
+ case Kleo::SMIMEFormat:
+ if ( signing )
+ return
+ "multipart/signed;\n\t"
+ "boundary=\"%boundary\";\n\t"
+ "protocol=\"application/pkcs7-signature\";\n\t"
+ "micalg=sha1"; // FIXME: obtain this parameter from gpgme!
+ // fall through (for encryption, there's no difference between
+ // SMIME and SMIMEOpaque, since there is no mp/encrypted for
+ // S/MIME):
+ case Kleo::SMIMEOpaqueFormat:
+ return signing ?
+ "application/pkcs7-mime;\n\t"
+ "smime-type=signed-data;\n\t"
+ "name=\"smime.p7m\";\n\t"
+ :
+ "application/pkcs7-mime;\n\t"
+ "smime-type=enveloped-data;\n\t"
+ "name=\"smime.p7m\";\n\t"
+ ;
+ }
+}
+
+static inline const char * toplevelContentDisposition( Kleo::CryptoMessageFormat f, bool signing ) {
+ switch ( f ) {
+ default:
+ case Kleo::InlineOpenPGPFormat:
+ case Kleo::OpenPGPMIMEFormat:
+ return 0;
+ case Kleo::SMIMEFormat:
+ if ( signing )
+ return 0;
+ case Kleo::SMIMEOpaqueFormat:
+ return "attachment; filename=\"smime.p7m\"";
+ }
+}
+
+static inline bool includeCleartextWhenSigning( Kleo::CryptoMessageFormat f ) {
+ return makeMultiPartSigned( f );
+}
+
+static inline const char * nestedContentType( Kleo::CryptoMessageFormat f, bool signing ) {
+ switch ( f ) {
+ case Kleo::OpenPGPMIMEFormat:
+ return signing ? "application/pgp-signature; name=signature.asc \nContent-Description: This is a digitally signed message part." : "application/octet-stream" ;
+ case Kleo::SMIMEFormat:
+ if ( signing )
+ return "application/pkcs7-signature; name=\"smime.p7s\"";
+ // fall through:
+ default:
+ case Kleo::InlineOpenPGPFormat:
+ case Kleo::SMIMEOpaqueFormat:
+ return 0;
+ }
+}
+
+static inline const char * nestedContentDisposition( Kleo::CryptoMessageFormat f, bool signing ) {
+ if ( !signing && f == Kleo::OpenPGPMIMEFormat )
+ return "inline; filename=\"msg.asc\"";
+ if ( signing && f == Kleo::SMIMEFormat )
+ return "attachment; filename=\"smime.p7s\"";
+ return 0;
+}
+
+static inline bool binaryHint( Kleo::CryptoMessageFormat f ) {
+ switch ( f ) {
+ case Kleo::SMIMEFormat:
+ case Kleo::SMIMEOpaqueFormat:
+ return true;
+ default:
+ case Kleo::OpenPGPMIMEFormat:
+ case Kleo::InlineOpenPGPFormat:
+ return false;
+ }
+}
+
+static inline bool armor( Kleo::CryptoMessageFormat f ) {
+ return !binaryHint( f );
+}
+
+static inline bool textMode( Kleo::CryptoMessageFormat f ) {
+ return f == Kleo::InlineOpenPGPFormat;
+}
+
+static inline GpgME::Context::SignatureMode signingMode( Kleo::CryptoMessageFormat f ) {
+ switch ( f ) {
+ case Kleo::SMIMEOpaqueFormat:
+ return GpgME::Context::Normal;
+ case Kleo::InlineOpenPGPFormat:
+ return GpgME::Context::Clearsigned;
+ default:
+ case Kleo::SMIMEFormat:
+ case Kleo::OpenPGPMIMEFormat:
+ return GpgME::Context::Detached;
+ }
+}
+
+//
+// END replacements for StructuringInfo(Wrapper)
+//
+
+class EncryptMessageJob : public MessageComposerJob {
+public:
+ EncryptMessageJob( KMMessage* msg, const Kleo::KeyResolver::SplitInfo & si,
+ bool doSign, bool doEncrypt, const QByteArray& encodedBody,
+ int boundaryLevel, /*const KMMessagePart& oldBodyPart,*/
+ KMMessagePart* newBodyPart, Kleo::CryptoMessageFormat format,
+ MessageComposer* composer )
+ : MessageComposerJob( composer ), mMsg( msg ), mSplitInfo( si ),
+ mDoSign( doSign ), mDoEncrypt( doEncrypt ), mEncodedBody( encodedBody ),
+ mBoundaryLevel( boundaryLevel ), /*mOldBodyPart( oldBodyPart ),*/
+ mNewBodyPart( newBodyPart ), mFormat( format ) {}
+
+ void execute() {
+ KMMessagePart tmpNewBodyPart;
+ tmpNewBodyPart.duplicate( *mNewBodyPart ); // slow - we duplicate everything again
+
+ // TODO: Async call
+
+ mComposer->encryptMessage( mMsg, mSplitInfo, mDoSign, mDoEncrypt,
+ tmpNewBodyPart, mFormat );
+ if ( !mComposer->mRc ) {
+ delete mMsg; mMsg = 0;
+ return;
+ }
+ mComposer->mMessageList.push_back( mMsg );
+ }
+
+private:
+ KMMessage* mMsg;
+ Kleo::KeyResolver::SplitInfo mSplitInfo;
+ bool mDoSign, mDoEncrypt;
+ QByteArray mEncodedBody;
+ int mBoundaryLevel;
+ //KMMessagePart mOldBodyPart;
+ KMMessagePart* mNewBodyPart;
+ Kleo::CryptoMessageFormat mFormat;
+};
+
+class SetLastMessageAsUnencryptedVersionOfLastButOne : public MessageComposerJob {
+public:
+ SetLastMessageAsUnencryptedVersionOfLastButOne( MessageComposer * composer )
+ : MessageComposerJob( composer ) {}
+
+ void execute() {
+ KMMessage * last = mComposer->mMessageList.back();
+ mComposer->mMessageList.pop_back();
+ mComposer->mMessageList.back()->setUnencryptedMsg( last );
+ }
+};
+
+void MessageComposer::composeInlineOpenPGPMessage( KMMessage& theMessage,
+ bool doSign, bool doEncrypt )
+{
+ // preprocess the body text
+ const QByteArray bodyData = mText;
+ if (bodyData.isNull()) {
+ mRc = false;
+ return;
+ }
+
+ mNewBodyPart = 0; // unused
+ mEarlyAddAttachments = false;
+ mAllAttachmentsAreInBody = false;
+
+ // set the main headers
+ theMessage.deleteBodyParts();
+ QString oldContentType = theMessage.headerField( "Content-Type" );
+ theMessage.removeHeaderField("Content-Type");
+ theMessage.removeHeaderField("Content-Transfer-Encoding");
+
+ const std::vector<Kleo::KeyResolver::SplitInfo> splitInfos
+ = mKeyResolver->encryptionItems( Kleo::InlineOpenPGPFormat );
+ kdWarning( splitInfos.empty() )
+ << "MessageComposer::continueComposeMessage(): splitInfos.empty() for InlineOpenPGPFormat"
+ << endl;
+ std::vector<Kleo::KeyResolver::SplitInfo>::const_iterator it;
+ for ( it = splitInfos.begin() ; it != splitInfos.end() ; ++it ) {
+ const Kleo::KeyResolver::SplitInfo& splitInfo = *it;
+ KMMessage* msg = new KMMessage( theMessage );
+ if ( doEncrypt ) {
+ Kpgp::Result result;
+ QByteArray encryptedBody;
+ if ( doSign ) { // Sign and encrypt
+ const std::vector<GpgME::Key> signingKeys = mKeyResolver->signingKeys( Kleo::InlineOpenPGPFormat );
+ result = pgpSignedAndEncryptedMsg( encryptedBody, bodyData, signingKeys,
+ splitInfo.keys, Kleo::InlineOpenPGPFormat );
+ } else { // Encrypt but don't sign
+ result = pgpEncryptedMsg( encryptedBody, bodyData,
+ splitInfo.keys, Kleo::InlineOpenPGPFormat );
+ }
+ if ( result != Kpgp::Ok ) {
+ mRc = false;
+ return;
+ }
+ assert( !encryptedBody.isNull() ); // if you hit this, check gpg-agent is running, then blame gpgme.
+ mOldBodyPart.setBodyEncodedBinary( encryptedBody );
+ } else {
+ if ( doSign ) { // Sign but don't encrypt
+ pgpSignedMsg( bodyData, Kleo::InlineOpenPGPFormat );
+ if ( mSignature.isNull() ) {
+ mRc = false;
+ return;
+ }
+ mOldBodyPart.setBodyEncodedBinary( mSignature );
+ } else { // don't sign nor encrypt -> nothing to do
+ assert( !bodyData.isNull() );
+ mOldBodyPart.setBodyEncodedBinary( bodyData );
+ }
+ }
+ mOldBodyPart.setContentDisposition( "inline" );
+ mOldBodyPart.setOriginalContentTypeStr( oldContentType.utf8() );
+ mOldBodyPart.setCharset(mCharset);
+ addBodyAndAttachments( msg, splitInfo, false, false, mOldBodyPart, Kleo::InlineOpenPGPFormat );
+ mMessageList.push_back( msg );
+ if ( it == splitInfos.begin() ) {
+ if ( doEncrypt && !saveMessagesEncrypted() ) {
+ mOldBodyPart.setBodyEncodedBinary( bodyData );
+ KMMessage* msgUnenc = new KMMessage( theMessage );
+ addBodyAndAttachments( msgUnenc, splitInfo, false, false, mOldBodyPart, Kleo::InlineOpenPGPFormat );
+ msg->setUnencryptedMsg( msgUnenc );
+ }
+ }
+ } // end for
+}
+
+// very much inspired by composeInlineOpenPGPMessage
+void MessageComposer::composeChiasmusMessage( KMMessage& theMessage, Kleo::CryptoMessageFormat format )
+{
+ assert( !GlobalSettings::chiasmusKey().isEmpty() ); // kmcomposewin code should have made sure
+ const Kleo::CryptoBackendFactory * cpf = Kleo::CryptoBackendFactory::instance();
+ assert( cpf );
+ const Kleo::CryptoBackend::Protocol * chiasmus
+ = cpf->protocol( "Chiasmus" );
+ assert( chiasmus ); // kmcomposewin code should have made sure
+
+ // preprocess the body text
+ const QByteArray bodyData = mText;
+ if (bodyData.isNull()) {
+ mRc = false;
+ return;
+ }
+
+ mNewBodyPart = 0; // unused
+ mEarlyAddAttachments = false;
+ mAllAttachmentsAreInBody = false;
+
+ // set the main headers
+ theMessage.deleteBodyParts();
+ QString oldContentType = theMessage.headerField( "Content-Type" );
+ theMessage.removeHeaderField("Content-Type");
+ theMessage.removeHeaderField("Content-Transfer-Encoding");
+
+ // This reads strange, but we know that AdjustCryptFlagsJob created a single splitinfo,
+ // under the given "format" (usually openpgp/mime; doesn't matter)
+ const std::vector<Kleo::KeyResolver::SplitInfo> splitInfos
+ = mKeyResolver->encryptionItems( format );
+ assert( splitInfos.size() == 1 );
+ for ( std::vector<Kleo::KeyResolver::SplitInfo>::const_iterator it = splitInfos.begin() ; it != splitInfos.end() ; ++it )
+ {
+ const Kleo::KeyResolver::SplitInfo& splitInfo = *it;
+ KMMessage* msg = new KMMessage( theMessage );
+ QByteArray encryptedBody;
+
+ if ( !encryptWithChiasmus( chiasmus, bodyData, encryptedBody ) ) {
+ mRc = false;
+ return;
+ }
+ assert( !encryptedBody.isNull() );
+ // This leaves CTE==7-bit, no good
+ //mOldBodyPart.setBodyEncodedBinary( encryptedBody );
+
+ bool doSign = false;
+ QValueList<int> allowedCTEs;
+ mOldBodyPart.setBodyAndGuessCte( encryptedBody, allowedCTEs,
+ !kmkernel->msgSender()->sendQuotedPrintable() && !doSign,
+ doSign );
+
+
+ mOldBodyPart.setContentDisposition( "inline" );
+ // Used in case of no attachments
+ mOldBodyPart.setOriginalContentTypeStr( "application/vnd.de.bund.bsi.chiasmus-text;chiasmus-charset=" + mCharset );
+ // Used in case of attachments
+ mOldBodyPart.setTypeStr( "application" );
+ mOldBodyPart.setSubtypeStr( "vnd.de.bund.bsi.chiasmus-text" );
+ mOldBodyPart.setAdditionalCTypeParamStr( QCString( "chiasmus-charset=" + mCharset ) );
+ addBodyAndAttachments( msg, splitInfo, false, false, mOldBodyPart, Kleo::InlineOpenPGPFormat );
+ mMessageList.push_back( msg );
+
+ if ( it == splitInfos.begin() && !saveMessagesEncrypted() ) {
+ mOldBodyPart.setBodyEncodedBinary( bodyData );
+ KMMessage* msgUnenc = new KMMessage( theMessage );
+ addBodyAndAttachments( msgUnenc, splitInfo, false, false, mOldBodyPart, Kleo::InlineOpenPGPFormat );
+ msg->setUnencryptedMsg( msgUnenc );
+ }
+ }
+}
+
+void MessageComposer::composeMessage( KMMessage& theMessage,
+ bool doSign, bool doEncrypt,
+ Kleo::CryptoMessageFormat format )
+{
+#ifdef DEBUG
+ kdDebug(5006) << "entering KMComposeWin::composeMessage" << endl;
+#endif
+ if ( format == Kleo::InlineOpenPGPFormat ) {
+ composeInlineOpenPGPMessage( theMessage, doSign, doEncrypt );
+ return;
+ }
+
+ if ( mEncryptWithChiasmus )
+ {
+ composeChiasmusMessage( theMessage, format );
+ return;
+ }
+
+ // create informative header for those that have no mime-capable
+ // email client
+ theMessage.setBody( "This message is in MIME format." );
+
+ // preprocess the body text
+ QByteArray bodyData = mText;
+ if (bodyData.isNull()) {
+ mRc = false;
+ return;
+ }
+
+ // set the main headers
+ QString oldContentType = theMessage.headerField( "Content-Type" );
+ theMessage.deleteBodyParts();
+ theMessage.removeHeaderField("Content-Type");
+ theMessage.removeHeaderField("Content-Transfer-Encoding");
+ theMessage.setAutomaticFields(true); // == multipart/mixed
+
+ // this is our *final* body part
+ mNewBodyPart = new KMMessagePart;
+
+ // this is the boundary depth of the surrounding MIME part
+ mPreviousBoundaryLevel = 0;
+
+ // whether the body must be signed/encrypted
+ const bool doEncryptBody = doEncrypt && mEncryptBody;
+ const bool doSignBody = doSign && mSignBody;
+
+ // create temporary bodyPart for editor text
+ // (and for all attachments, if mail is to be signed and/or encrypted)
+ mEarlyAddAttachments = !mAttachments.empty() && ( doSignBody || doEncryptBody );
+
+ mAllAttachmentsAreInBody = mEarlyAddAttachments;
+
+ // test whether there ARE attachments that can be included into the body
+ if( mEarlyAddAttachments ) {
+ bool someOk = false;
+ for ( QValueVector<Attachment>::const_iterator it = mAttachments.begin() ; it != mAttachments.end() ; ++it ) {
+ if ( it->encrypt == doEncryptBody && it->sign == doSignBody )
+ someOk = true;
+ else
+ mAllAttachmentsAreInBody = false;
+ }
+ if( !mAllAttachmentsAreInBody && !someOk )
+ mEarlyAddAttachments = false;
+ }
+
+ kdDebug(5006) << "mEarlyAddAttachments=" << mEarlyAddAttachments << " mAllAttachmentsAreInBody=" << mAllAttachmentsAreInBody << endl;
+
+ // if an html message is to be generated, make a text/plain and text/html part
+ mMultipartMixedBoundary = "";
+ if ( mEarlyAddAttachments ) {
+ mOldBodyPart.setTypeStr( "multipart" );
+ mOldBodyPart.setSubtypeStr( "mixed" );
+ // calculate a boundary string
+ DwMediaType tmpCT;
+ tmpCT.CreateBoundary( ++mPreviousBoundaryLevel );
+ mMultipartMixedBoundary = tmpCT.Boundary().c_str();
+ }
+ else if ( mIsRichText ) {
+ mOldBodyPart.setTypeStr( "multipart" );
+ mOldBodyPart.setSubtypeStr( "alternative" );
+ }
+ else
+ mOldBodyPart.setOriginalContentTypeStr( oldContentType.utf8() );
+
+ mOldBodyPart.setContentDisposition( "inline" );
+
+ if ( mIsRichText ) { // create a multipart body
+ // calculate a boundary string
+ QCString boundaryCStr; // storing boundary string data
+ QCString newbody;
+ DwMediaType tmpCT;
+ tmpCT.CreateBoundary( ++mPreviousBoundaryLevel );
+ boundaryCStr = KMail::Util::CString( tmpCT.Boundary() );
+ QValueList<int> allowedCTEs;
+
+ KMMessagePart textBodyPart;
+ textBodyPart.setTypeStr("text");
+ textBodyPart.setSubtypeStr("plain");
+
+ QCString textbody = plainTextFromMarkup( mText /* converted to QString */ );
+
+ // the signed body must not be 8bit encoded
+ textBodyPart.setBodyAndGuessCte( textbody, allowedCTEs,
+ !kmkernel->msgSender()->sendQuotedPrintable() && !doSign,
+ doSign );
+ textBodyPart.setCharset( mCharset );
+ textBodyPart.setBodyEncoded( textbody );
+ DwBodyPart* textDwPart = theMessage.createDWBodyPart( &textBodyPart );
+ textDwPart->Assemble();
+ newbody += "--";
+ newbody += boundaryCStr;
+ newbody += "\n";
+ newbody += textDwPart->AsString().c_str();
+ delete textDwPart;
+ textDwPart = 0;
+
+ KMMessagePart htmlBodyPart;
+ htmlBodyPart.setTypeStr("text");
+ htmlBodyPart.setSubtypeStr("html");
+ QByteArray htmlbody = mText;
+ // the signed body must not be 8bit encoded
+ htmlBodyPart.setBodyAndGuessCte( htmlbody, allowedCTEs,
+ !kmkernel->msgSender()->sendQuotedPrintable() && !doSign,
+ doSign );
+ htmlBodyPart.setCharset( mCharset );
+ htmlBodyPart.setBodyEncodedBinary( htmlbody );
+ DwBodyPart* htmlDwPart = theMessage.createDWBodyPart( &htmlBodyPart );
+ htmlDwPart->Assemble();
+ newbody += "\n--";
+ newbody += boundaryCStr;
+ newbody += "\n";
+ newbody += htmlDwPart->AsString().c_str();
+ delete htmlDwPart;
+ htmlDwPart = 0;
+
+ newbody += "--";
+ newbody += boundaryCStr;
+ newbody += "--\n";
+ bodyData = KMail::Util::byteArrayFromQCStringNoDetach( newbody );
+ mOldBodyPart.setBodyEncodedBinary( bodyData );
+
+ mSaveBoundary = tmpCT.Boundary();
+ }
+
+ // Prepare attachments that will be signed/encrypted
+ for ( QValueVector<Attachment>::const_iterator it = mAttachments.begin() ; it != mAttachments.end() ; ++it ) {
+ // signed/encrypted body parts must be either QP or base64 encoded
+ // Why not 7 bit? Because the LF->CRLF canonicalization would render
+ // e.g. 7 bit encoded shell scripts unusable because of the CRs.
+ //
+ // (marc) this is a workaround for the KMail bug that doesn't
+ // respect the CRLF->LF de-canonicalisation. We should
+ // eventually get rid of this:
+ if( it->sign || it->encrypt ) {
+ QCString cte = it->part->cteStr().lower();
+ if( ( "8bit" == cte && it->part->type() != DwMime::kTypeMessage )
+ || ( ( it->part->type() == DwMime::kTypeText )
+ && ( "7bit" == cte ) ) ) {
+ const QByteArray body = it->part->bodyDecodedBinary();
+ QValueList<int> dummy;
+ it->part->setBodyAndGuessCte(body, dummy, false, it->sign);
+ kdDebug(5006) << "Changed encoding of message part from "
+ << cte << " to " << it->part->cteStr() << endl;
+ }
+ }
+ }
+
+ if( mEarlyAddAttachments ) {
+ // add the normal body text
+ KMMessagePart innerBodyPart;
+ if ( mIsRichText ) {
+ innerBodyPart.setTypeStr( "multipart");//text" );
+ innerBodyPart.setSubtypeStr("alternative");//html");
+ }
+ else {
+ innerBodyPart.setOriginalContentTypeStr( oldContentType.utf8() );
+ }
+ innerBodyPart.setContentDisposition( "inline" );
+ QValueList<int> allowedCTEs;
+ // the signed body must not be 8bit encoded
+ innerBodyPart.setBodyAndGuessCte( bodyData, allowedCTEs,
+ !kmkernel->msgSender()->sendQuotedPrintable() && !doSign,
+ doSign );
+ if ( !mIsRichText )
+ innerBodyPart.setCharset( mCharset );
+ innerBodyPart.setBodyEncodedBinary( bodyData ); // do we need this, since setBodyAndGuessCte does this already?
+ DwBodyPart* innerDwPart = theMessage.createDWBodyPart( &innerBodyPart );
+ innerDwPart->Assemble();
+ QByteArray tmpbody = KMail::Util::ByteArray( innerDwPart->AsString() );
+ if ( mIsRichText ) { // and add our mp/a boundary
+ int boundPos = tmpbody.find( '\n' );
+ if( -1 < boundPos ) {
+ QCString bStr( ";\n boundary=\"" );
+ bStr += mSaveBoundary.c_str();
+ bStr += "\"";
+ bodyData = tmpbody;
+ KMail::Util::insert( bodyData, boundPos, bStr );
+ KMail::Util::insert( bodyData, 0, "--" + mMultipartMixedBoundary + "\n" ); // prepend
+ }
+ }
+ else {
+ bodyData = tmpbody;
+ KMail::Util::insert( bodyData, 0, "--" + mMultipartMixedBoundary + "\n" ); // prepend
+ }
+ delete innerDwPart;
+ innerDwPart = 0;
+ // add all matching Attachments
+ // NOTE: This code will be changed when KMime is complete.
+ for ( QValueVector<Attachment>::iterator it = mAttachments.begin() ; it != mAttachments.end() ; ++it ) {
+ if ( it->encrypt == doEncryptBody && it->sign == doSignBody ) {
+ innerDwPart = theMessage.createDWBodyPart( it->part );
+ innerDwPart->Assemble();
+ KMail::Util::append( bodyData, QCString( "\n--" + mMultipartMixedBoundary + "\n" ) );
+ KMail::Util::append( bodyData, innerDwPart->AsString().c_str() );
+ delete innerDwPart;
+ innerDwPart = 0;
+ }
+ }
+ KMail::Util::append( bodyData, QCString( "\n--" + mMultipartMixedBoundary + "--\n" ) );
+ } else { // !earlyAddAttachments
+ QValueList<int> allowedCTEs;
+ // the signed body must not be 8bit encoded
+ mOldBodyPart.setBodyAndGuessCte(bodyData, allowedCTEs, !kmkernel->msgSender()->sendQuotedPrintable() && !doSign,
+ doSign);
+ if ( !mIsRichText )
+ mOldBodyPart.setCharset(mCharset);
+ }
+ // create S/MIME body part for signing and/or encrypting
+ mOldBodyPart.setBodyEncodedBinary( bodyData );
+
+ if( doSignBody || doEncryptBody ) {
+ // get string representation of body part (including the attachments)
+
+ DwBodyPart* dwPart;
+ if ( mIsRichText && !mEarlyAddAttachments ) {
+ // if we are using richtext and not already have a mp/a body
+ // make the body a mp/a body
+ dwPart = theMessage.createDWBodyPart( &mOldBodyPart );
+ DwHeaders& headers = dwPart->Headers();
+ DwMediaType& ct = headers.ContentType();
+ ct.SetBoundary(mSaveBoundary);
+ dwPart->Assemble();
+ }
+ else {
+ dwPart = theMessage.createDWBodyPart( &mOldBodyPart );
+ dwPart->Assemble();
+ }
+ mEncodedBody = KMail::Util::ByteArray( dwPart->AsString() );
+ delete dwPart;
+ dwPart = 0;
+
+ // manually add a boundary definition to the Content-Type header
+ if( !mMultipartMixedBoundary.isEmpty() ) {
+ int boundPos = mEncodedBody.find( '\n' );
+ if( -1 < boundPos ) {
+ // insert new "boundary" parameter
+ QCString bStr( ";\n boundary=\"" );
+ bStr += mMultipartMixedBoundary;
+ bStr += "\"";
+ KMail::Util::insert( mEncodedBody, boundPos, bStr.data() );
+ }
+ }
+
+ // replace simple LFs by CRLFs for all MIME supporting CryptPlugs
+ // according to RfC 2633, 3.1.1 Canonicalization
+ //kdDebug(5006) << "Converting LF to CRLF (see RfC 2633, 3.1.1 Canonicalization)" << endl;
+ mEncodedBody = KMail::Util::lf2crlf( mEncodedBody );
+ }
+
+ if ( doSignBody ) {
+ mPerformingSignOperation = true; // this lets the KMComposeWin know if it is safe to close the window.
+ pgpSignedMsg( mEncodedBody, format );
+ mPerformingSignOperation = false;
+
+ if ( mSignature.isEmpty() ) {
+ kdDebug() << "signature was empty" << endl;
+ mRc = false;
+ return;
+ }
+ mRc = processStructuringInfo( QString::null,
+ mOldBodyPart.contentDescription(),
+ mOldBodyPart.typeStr(),
+ mOldBodyPart.subtypeStr(),
+ mOldBodyPart.contentDisposition(),
+ mOldBodyPart.contentTransferEncodingStr(),
+ mEncodedBody, "signature",
+ mSignature,
+ *mNewBodyPart, true, format );
+ if ( mRc ) {
+ if ( !makeMultiPartSigned( format ) ) {
+ mNewBodyPart->setCharset( mCharset );
+ }
+ } else
+ KMessageBox::sorry( mComposeWin,
+ mErrorProcessingStructuringInfo );
+ }
+
+ if ( !mRc )
+ return;
+
+ continueComposeMessage( theMessage, doSign, doEncrypt, format );
+}
+
+// Do the encryption stuff
+void MessageComposer::continueComposeMessage( KMMessage& theMessage,
+ bool doSign, bool doEncrypt,
+ Kleo::CryptoMessageFormat format )
+{
+
+ const std::vector<Kleo::KeyResolver::SplitInfo> splitInfos
+ = mKeyResolver->encryptionItems( format );
+ kdWarning( splitInfos.empty() )
+ << "MessageComposer::continueComposeMessage(): splitInfos.empty() for "
+ << Kleo::cryptoMessageFormatToString( format ) << endl;
+
+ if ( !splitInfos.empty() && doEncrypt && !saveMessagesEncrypted() ) {
+ mJobs.push_front( new SetLastMessageAsUnencryptedVersionOfLastButOne( this ) );
+ mJobs.push_front( new EncryptMessageJob( new KMMessage( theMessage ),
+ Kleo::KeyResolver::SplitInfo( splitInfos.front().recipients ), doSign,
+ false, mEncodedBody,
+ mPreviousBoundaryLevel,
+ /*mOldBodyPart,*/ mNewBodyPart,
+ format, this ) );
+ }
+
+ for ( std::vector<Kleo::KeyResolver::SplitInfo>::const_iterator it = splitInfos.begin() ; it != splitInfos.end() ; ++it )
+ mJobs.push_front( new EncryptMessageJob( new KMMessage( theMessage ), *it, doSign,
+ doEncrypt, mEncodedBody,
+ mPreviousBoundaryLevel,
+ /*mOldBodyPart,*/ mNewBodyPart,
+ format, this ) );
+}
+
+void MessageComposer::encryptMessage( KMMessage* msg,
+ const Kleo::KeyResolver::SplitInfo & splitInfo,
+ bool doSign, bool doEncrypt,
+ KMMessagePart newBodyPart,
+ Kleo::CryptoMessageFormat format )
+{
+ if ( doEncrypt && splitInfo.keys.empty() ) {
+ // the user wants to send the message unencrypted
+ //mComposeWin->setEncryption( false, false );
+ //FIXME why is this talkback needed? Till
+ doEncrypt = false;
+ }
+
+ const bool doEncryptBody = doEncrypt && mEncryptBody;
+ const bool doSignBody = doSign && mSignBody;
+
+ if ( doEncryptBody ) {
+ QByteArray innerContent;
+ if ( doSignBody ) {
+ // extract signed body from newBodyPart
+ DwBodyPart* dwPart = msg->createDWBodyPart( &newBodyPart );
+ dwPart->Assemble();
+ innerContent = KMail::Util::ByteArray( dwPart->AsString() );
+ delete dwPart;
+ dwPart = 0;
+ } else {
+ innerContent = mEncodedBody;
+ }
+
+ // now do the encrypting:
+ // replace simple LFs by CRLFs for all MIME supporting CryptPlugs
+ // according to RfC 2633, 3.1.1 Canonicalization
+ //kdDebug(5006) << "Converting LF to CRLF (see RfC 2633, 3.1.1 Canonicalization)" << endl;
+ innerContent = KMail::Util::lf2crlf( innerContent );
+ //kdDebug(5006) << " done." << endl;
+
+ QByteArray encryptedBody;
+ Kpgp::Result result = pgpEncryptedMsg( encryptedBody, innerContent,
+ splitInfo.keys, format );
+ if ( result != Kpgp::Ok ) {
+ mRc = false;
+ return;
+ }
+ mRc = processStructuringInfo( "http://www.gnupg.org/aegypten/",
+ newBodyPart.contentDescription(),
+ newBodyPart.typeStr(),
+ newBodyPart.subtypeStr(),
+ newBodyPart.contentDisposition(),
+ newBodyPart.contentTransferEncodingStr(),
+ innerContent,
+ "encrypted data",
+ encryptedBody,
+ newBodyPart, false, format );
+ if ( !mRc )
+ KMessageBox::sorry(mComposeWin, mErrorProcessingStructuringInfo);
+ }
+
+ // process the attachments that are not included into the body
+ if( mRc ) {
+ const bool useNewBodyPart = doSignBody || doEncryptBody;
+ addBodyAndAttachments( msg, splitInfo, doSign, doEncrypt,
+ useNewBodyPart ? newBodyPart : mOldBodyPart, format );
+ }
+}
+
+void MessageComposer::addBodyAndAttachments( KMMessage* msg,
+ const Kleo::KeyResolver::SplitInfo & splitInfo,
+ bool doSign, bool doEncrypt,
+ const KMMessagePart& ourFineBodyPart,
+ Kleo::CryptoMessageFormat format )
+{
+ const bool doEncryptBody = doEncrypt && mEncryptBody;
+ const bool doSignBody = doSign && mSignBody;
+
+ if( !mAttachments.empty()
+ && ( !mEarlyAddAttachments || !mAllAttachmentsAreInBody ) ) {
+ // set the content type header
+ msg->headers().ContentType().SetType( DwMime::kTypeMultipart );
+ msg->headers().ContentType().SetSubtype( DwMime::kSubtypeMixed );
+ msg->headers().ContentType().CreateBoundary( 0 );
+ kdDebug(5006) << "MessageComposer::addBodyAndAttachments() : set top level Content-Type to Multipart/Mixed" << endl;
+
+ // add our Body Part
+ DwBodyPart* tmpDwPart = msg->createDWBodyPart( &ourFineBodyPart );
+ DwHeaders& headers = tmpDwPart->Headers();
+ DwMediaType& ct = headers.ContentType();
+ if ( !mSaveBoundary.empty() )
+ ct.SetBoundary(mSaveBoundary);
+ tmpDwPart->Assemble();
+
+ //KMMessagePart newPart;
+ //newPart.setBody(tmpDwPart->AsString().c_str());
+ msg->addDwBodyPart(tmpDwPart); // only this method doesn't add it as text/plain
+
+ // add Attachments
+ // create additional bodyparts for the attachments (if any)
+ KMMessagePart newAttachPart;
+ for ( QValueVector<Attachment>::iterator it = mAttachments.begin() ; it != mAttachments.end() ; ++it ) {
+
+ const bool cryptFlagsDifferent = ( it->encrypt != doEncryptBody || it->sign != doSignBody ) ;
+
+ if ( !cryptFlagsDifferent && mEarlyAddAttachments )
+ continue;
+
+ const bool encryptThisNow = doEncrypt && cryptFlagsDifferent && it->encrypt ;
+ const bool signThisNow = doSign && cryptFlagsDifferent && it->sign ;
+
+ if ( !encryptThisNow && !signThisNow ) {
+ msg->addBodyPart( it->part );
+ // Assemble the message. Not sure why, but this fixes the vanishing boundary parameter
+ (void)msg->asDwMessage();
+ continue;
+ }
+
+ KMMessagePart& rEncryptMessagePart( *it->part );
+
+ DwBodyPart* innerDwPart = msg->createDWBodyPart( it->part );
+ innerDwPart->Assemble();
+ QByteArray encodedAttachment = KMail::Util::ByteArray( innerDwPart->AsString() );
+ delete innerDwPart;
+ innerDwPart = 0;
+
+ // replace simple LFs by CRLFs for all MIME supporting CryptPlugs
+ // according to RfC 2633, 3.1.1 Canonicalization
+ //kdDebug(5006) << "Converting LF to CRLF (see RfC 2633, 3.1.1 Canonicalization)" << endl;
+ encodedAttachment = KMail::Util::lf2crlf( encodedAttachment );
+
+ // sign this attachment
+ if( signThisNow ) {
+ pgpSignedMsg( encodedAttachment, format );
+ mRc = !mSignature.isEmpty();
+ if( mRc ) {
+ mRc = processStructuringInfo( "http://www.gnupg.org/aegypten/",
+ it->part->contentDescription(),
+ it->part->typeStr(),
+ it->part->subtypeStr(),
+ it->part->contentDisposition(),
+ it->part->contentTransferEncodingStr(),
+ encodedAttachment,
+ "signature",
+ mSignature,
+ newAttachPart, true, format );
+ if( mRc ) {
+ if( encryptThisNow ) {
+ rEncryptMessagePart = newAttachPart;
+ DwBodyPart* dwPart = msg->createDWBodyPart( &newAttachPart );
+ dwPart->Assemble();
+ encodedAttachment = KMail::Util::ByteArray( dwPart->AsString() );
+ delete dwPart;
+ dwPart = 0;
+ }
+ } else
+ KMessageBox::sorry( mComposeWin, mErrorProcessingStructuringInfo );
+ } else {
+ // quit the attachments' loop
+ break;
+ }
+ }
+ if( encryptThisNow ) {
+ QByteArray encryptedBody;
+ Kpgp::Result result = pgpEncryptedMsg( encryptedBody,
+ encodedAttachment,
+ splitInfo.keys,
+ format );
+
+ if( Kpgp::Ok == result ) {
+ mRc = processStructuringInfo( "http://www.gnupg.org/aegypten/",
+ rEncryptMessagePart.contentDescription(),
+ rEncryptMessagePart.typeStr(),
+ rEncryptMessagePart.subtypeStr(),
+ rEncryptMessagePart.contentDisposition(),
+ rEncryptMessagePart.contentTransferEncodingStr(),
+ encodedAttachment,
+ "encrypted data",
+ encryptedBody,
+ newAttachPart, false, format );
+ if ( !mRc )
+ KMessageBox::sorry( mComposeWin, mErrorProcessingStructuringInfo );
+ } else
+ mRc = false;
+ }
+ msg->addBodyPart( &newAttachPart );
+ (void)msg->asDwMessage(); // Assemble the message. One gets a completely empty message otherwise :/
+ }
+ } else { // no attachments in the final message
+ if( ourFineBodyPart.originalContentTypeStr() ) {
+ msg->headers().ContentType().FromString( ourFineBodyPart.originalContentTypeStr() );
+ msg->headers().ContentType().Parse();
+ kdDebug(5006) << "MessageComposer::addBodyAndAttachments() : set top level Content-Type from originalContentTypeStr()=" << ourFineBodyPart.originalContentTypeStr() << endl;
+ } else {
+ QCString ct = ourFineBodyPart.typeStr() + "/" + ourFineBodyPart.subtypeStr();
+ if ( ct == "multipart/mixed" )
+ ct += ";\n\tboundary=\"" + mMultipartMixedBoundary + '"';
+ else if ( ct == "multipart/alternative" )
+ ct += ";\n\tboundary=\"" + QCString(mSaveBoundary.c_str()) + '"';
+ msg->headers().ContentType().FromString( ct );
+ msg->headers().ContentType().Parse();
+ kdDebug(5006) << "MessageComposer::addBodyAndAttachments() : set top level Content-Type to " << ct << endl;
+ }
+ if ( !ourFineBodyPart.charset().isEmpty() )
+ msg->setCharset( ourFineBodyPart.charset() );
+ msg->setHeaderField( "Content-Transfer-Encoding",
+ ourFineBodyPart.contentTransferEncodingStr() );
+ msg->setHeaderField( "Content-Description",
+ ourFineBodyPart.contentDescription() );
+ msg->setHeaderField( "Content-Disposition",
+ ourFineBodyPart.contentDisposition() );
+
+ if ( mDebugComposerCrypto )
+ kdDebug(5006) << "MessageComposer::addBodyAndAttachments() : top level headers and body adjusted" << endl;
+
+ // set body content
+ msg->setBody( ourFineBodyPart.dwBody() );
+
+ }
+
+ msg->setHeaderField( "X-KMail-Recipients",
+ splitInfo.recipients.join(", "), KMMessage::Address );
+
+ if ( mDebugComposerCrypto ) {
+ kdDebug(5006) << "MessageComposer::addBodyAndAttachments():\n Final message:\n|||" << msg->asString() << "|||\n\n" << endl;
+ msg->headers().Assemble();
+ kdDebug(5006) << "\n\n\nMessageComposer::addBodyAndAttachments():\n Final headers:\n\n" << msg->headerAsString() << "|||\n\n\n\n\n" << endl;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// This method does not call any crypto ops, so it does not need to be async
+bool MessageComposer::processStructuringInfo( const QString bugURL,
+ const QString contentDescClear,
+ const QCString contentTypeClear,
+ const QCString contentSubtypeClear,
+ const QCString contentDispClear,
+ const QCString contentTEncClear,
+ const QByteArray& clearCStr,
+ const QString /*contentDescCiph*/,
+ const QByteArray& ciphertext,
+ KMMessagePart& resultingPart,
+ bool signing, Kleo::CryptoMessageFormat format )
+{
+ assert( clearCStr.isEmpty() || clearCStr[clearCStr.size()-1] != '\0' ); // I was called with a QCString !?
+ bool bOk = true;
+
+ if ( makeMimeObject( format, signing ) ) {
+ QCString mainHeader = "Content-Type: ";
+ const char * toplevelCT = toplevelContentType( format, signing );
+ if ( toplevelCT )
+ mainHeader += toplevelCT;
+ else {
+ if( makeMultiMime( format, signing ) )
+ mainHeader += "text/plain";
+ else
+ mainHeader += contentTypeClear + '/' + contentSubtypeClear;
+ }
+
+ const QCString boundaryCStr = KMime::multiPartBoundary();
+ // add "boundary" parameter
+ if ( makeMultiMime( format, signing ) )
+ mainHeader.replace( "%boundary", boundaryCStr );
+
+ if ( toplevelCT ) {
+ if ( const char * str = toplevelContentDisposition( format, signing ) ) {
+ mainHeader += "\nContent-Disposition: ";
+ mainHeader += str;
+ }
+ if ( !makeMultiMime( format, signing ) &&
+ binaryHint( format ) )
+ mainHeader += "\nContent-Transfer-Encoding: base64";
+ } else {
+ if( 0 < contentDispClear.length() ) {
+ mainHeader += "\nContent-Disposition: ";
+ mainHeader += contentDispClear;
+ }
+ if( 0 < contentTEncClear.length() ) {
+ mainHeader += "\nContent-Transfer-Encoding: ";
+ mainHeader += contentTEncClear;
+ }
+ }
+
+ //kdDebug(5006) << "processStructuringInfo: mainHeader=" << mainHeader << endl;
+
+ DwString mainDwStr;
+ mainDwStr = mainHeader + "\n\n";
+ DwBodyPart mainDwPa( mainDwStr, 0 );
+ mainDwPa.Parse();
+ KMMessage::bodyPart( &mainDwPa, &resultingPart );
+ if( !makeMultiMime( format, signing ) ) {
+ if ( signing && includeCleartextWhenSigning( format ) ) {
+ QByteArray bodyText( clearCStr );
+ KMail::Util::append( bodyText, "\n" );
+ KMail::Util::append( bodyText, ciphertext );
+ resultingPart.setBodyEncodedBinary( bodyText );
+ } else {
+ resultingPart.setBodyEncodedBinary( ciphertext );
+ }
+ } else {
+ // Build the encapsulated MIME parts.
+ // Build a MIME part holding the version information
+ // taking the body contents returned in
+ // structuring.data.bodyTextVersion.
+ QCString versCStr, codeCStr;
+ if ( !signing && format == Kleo::OpenPGPMIMEFormat )
+ versCStr =
+ "Content-Type: application/pgp-encrypted\n"
+ "Content-Disposition: attachment\n"
+ "\n"
+ "Version: 1";
+
+ // Build a MIME part holding the code information
+ // taking the body contents returned in ciphertext.
+ const char * nestedCT = nestedContentType( format, signing );
+ assert( nestedCT );
+ codeCStr = "Content-Type: ";
+ codeCStr += nestedCT;
+ codeCStr += '\n';
+ if ( const char * str = nestedContentDisposition( format, signing ) ) {
+ codeCStr += "Content-Disposition: ";
+ codeCStr += str;
+ codeCStr += '\n';
+ }
+ if ( binaryHint( format ) ) {
+ codeCStr += "Content-Transfer-Encoding: base64\n\n";
+ codeCStr += KMime::Codec::codecForName( "base64" )->encodeToQCString( ciphertext );
+ } else
+ codeCStr += '\n' + QCString( ciphertext.data(), ciphertext.size() + 1 );
+
+
+ QByteArray mainStr;
+ KMail::Util::append( mainStr, "--" );
+ KMail::Util::append( mainStr, boundaryCStr );
+ if ( signing && includeCleartextWhenSigning( format ) &&
+ !clearCStr.isEmpty() ) {
+ KMail::Util::append( mainStr, "\n" );
+ // clearCStr is the one that can be very big for large attachments, don't merge with the other lines
+ KMail::Util::append( mainStr, clearCStr );
+ KMail::Util::append( mainStr, "\n--" + boundaryCStr );
+ }
+ if ( !versCStr.isEmpty() )
+ KMail::Util::append( mainStr, "\n" + versCStr + "\n--" + boundaryCStr );
+ if( !codeCStr.isEmpty() )
+ KMail::Util::append( mainStr, "\n" + codeCStr + "\n--" + boundaryCStr );
+ KMail::Util::append( mainStr, "--\n" );
+
+ //kdDebug(5006) << "processStructuringInfo: mainStr=" << mainStr << endl;
+ resultingPart.setBodyEncodedBinary( mainStr );
+ }
+
+ } else { // not making a mime object, build a plain message body.
+
+ resultingPart.setContentDescription( contentDescClear );
+ resultingPart.setTypeStr( contentTypeClear );
+ resultingPart.setSubtypeStr( contentSubtypeClear );
+ resultingPart.setContentDisposition( contentDispClear );
+ resultingPart.setContentTransferEncodingStr( contentTEncClear );
+ QByteArray resultingBody;
+
+ if ( signing && includeCleartextWhenSigning( format ) ) {
+ if( !clearCStr.isEmpty() )
+ KMail::Util::append( resultingBody, clearCStr );
+ }
+ if ( !ciphertext.isEmpty() )
+ KMail::Util::append( resultingBody, ciphertext );
+ else {
+ // Plugin error!
+ KMessageBox::sorry( mComposeWin,
+ i18n( "<qt><p>Error: The backend did not return "
+ "any encoded data.</p>"
+ "<p>Please report this bug:<br>%2</p></qt>" )
+ .arg( bugURL ) );
+ bOk = false;
+ }
+ resultingPart.setBodyEncodedBinary( resultingBody );
+ }
+
+ return bOk;
+}
+
+//-----------------------------------------------------------------------------
+QCString MessageComposer::plainTextFromMarkup( const QString& markupText )
+{
+ QTextEdit *hackConspiratorTextEdit = new QTextEdit( markupText );
+ hackConspiratorTextEdit->setTextFormat(Qt::PlainText);
+ if ( !mDisableBreaking ) {
+ hackConspiratorTextEdit->setWordWrap( QTextEdit::FixedColumnWidth );
+ hackConspiratorTextEdit->setWrapColumnOrWidth( mLineBreakColumn );
+ }
+ QString text = hackConspiratorTextEdit->text();
+ QCString textbody;
+
+ const QTextCodec *codec = KMMsgBase::codecForName( mCharset );
+ if( mCharset == "us-ascii" ) {
+ textbody = KMMsgBase::toUsAscii( text );
+ } else if( codec == 0 ) {
+ kdDebug(5006) << "Something is wrong and I can not get a codec." << endl;
+ textbody = text.local8Bit();
+ } else {
+ text = codec->toUnicode( text.latin1(), text.length() );
+ textbody = codec->fromUnicode( text );
+ }
+ if (textbody.isNull()) textbody = "";
+
+ delete hackConspiratorTextEdit;
+ return textbody;
+}
+
+//-----------------------------------------------------------------------------
+QByteArray MessageComposer::breakLinesAndApplyCodec()
+{
+ QString text;
+ QCString cText;
+
+ if( mDisableBreaking || mIsRichText || !GlobalSettings::self()->wordWrap() )
+ text = mComposeWin->mEditor->text();
+ else
+ text = mComposeWin->mEditor->brokenText();
+ text.truncate( text.length() ); // to ensure text.size()==text.length()+1
+
+ QString newText;
+ const QTextCodec *codec = KMMsgBase::codecForName( mCharset );
+
+ if( mCharset == "us-ascii" ) {
+ cText = KMMsgBase::toUsAscii( text );
+ newText = QString::fromLatin1( cText );
+ } else if( codec == 0 ) {
+ kdDebug(5006) << "Something is wrong and I can not get a codec." << endl;
+ cText = text.local8Bit();
+ newText = QString::fromLocal8Bit( cText );
+ } else {
+ cText = codec->fromUnicode( text );
+ newText = codec->toUnicode( cText );
+ }
+ if (cText.isNull()) cText = "";
+
+ if( !text.isEmpty() && (newText != text) ) {
+ QString oldText = mComposeWin->mEditor->text();
+ mComposeWin->mEditor->setText( newText );
+ KCursorSaver idle( KBusyPtr::idle() );
+ bool anyway = ( KMessageBox::warningYesNo( mComposeWin,
+ i18n("<qt>Not all characters fit into the chosen"
+ " encoding.<br><br>Send the message anyway?</qt>"),
+ i18n("Some Characters Will Be Lost"),
+ i18n("Lose Characters"), i18n("Change Encoding") ) == KMessageBox::Yes );
+ if( !anyway ) {
+ mComposeWin->mEditor->setText(oldText);
+ return QByteArray();
+ }
+ }
+
+ // From RFC 3156:
+ // Note: The accepted OpenPGP convention is for signed data to end
+ // with a <CR><LF> sequence. Note that the <CR><LF> sequence
+ // immediately preceding a MIME boundary delimiter line is considered
+ // to be part of the delimiter in [3], 5.1. Thus, it is not part of
+ // the signed data preceding the delimiter line. An implementation
+ // which elects to adhere to the OpenPGP convention has to make sure
+ // it inserts a <CR><LF> pair on the last line of the data to be
+ // signed and transmitted (signed message and transmitted message
+ // MUST be identical).
+ // So make sure that the body ends with a <LF>.
+ if( cText.isEmpty() || cText[cText.length()-1] != '\n' ) {
+ kdDebug(5006) << "Added an <LF> on the last line" << endl;
+ cText += "\n";
+ }
+ return KMail::Util::byteArrayFromQCStringNoDetach( cText );
+}
+
+
+//-----------------------------------------------------------------------------
+void MessageComposer::pgpSignedMsg( const QByteArray& cText, Kleo::CryptoMessageFormat format ) {
+
+ assert( cText.isEmpty() || cText[cText.size()-1] != '\0' ); // I was called with a QCString !?
+ mSignature = QByteArray();
+
+ const std::vector<GpgME::Key> signingKeys = mKeyResolver->signingKeys( format );
+
+ assert( !signingKeys.empty() );
+
+ // TODO: ASync call? Likely, yes :-)
+ const Kleo::CryptoBackendFactory * cpf = Kleo::CryptoBackendFactory::instance();
+ assert( cpf );
+ const Kleo::CryptoBackend::Protocol * proto
+ = isSMIME( format ) ? cpf->smime() : cpf->openpgp() ;
+ assert( proto ); /// hmmm.....?
+
+ std::auto_ptr<Kleo::SignJob> job( proto->signJob( armor( format ),
+ textMode( format ) ) );
+
+ if ( !job.get() ) {
+ KMessageBox::sorry( mComposeWin,
+ i18n("This message could not be signed, "
+ "since the chosen backend does not seem to support "
+ "signing; this should actually never happen, "
+ "please report this bug.") );
+ return;
+ }
+
+ QByteArray signature;
+ const GpgME::SigningResult res =
+ job->exec( signingKeys, cText, signingMode( format ), signature );
+ if ( res.error().isCanceled() ) {
+ kdDebug() << "signing was canceled by user" << endl;
+ return;
+ }
+ if ( res.error() ) {
+ kdDebug() << "signing failed: " << res.error().asString() << endl;
+ job->showErrorDialog( mComposeWin );
+ return;
+ }
+
+ if ( GlobalSettings::showGnuPGAuditLogAfterSuccessfulSignEncrypt() )
+ Kleo::MessageBox::auditLog( 0, job.get(), i18n("GnuPG Audit Log for Signing Operation") );
+
+ mSignature = signature;
+ if ( mSignature.isEmpty() ) {
+ KMessageBox::sorry( mComposeWin,
+ i18n( "The signing operation failed. "
+ "Please make sure that the gpg-agent program "
+ "is running." ) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+Kpgp::Result MessageComposer::pgpEncryptedMsg( QByteArray & encryptedBody,
+ const QByteArray& cText,
+ const std::vector<GpgME::Key> & encryptionKeys,
+ Kleo::CryptoMessageFormat format )
+{
+ // TODO: ASync call? Likely, yes :-)
+ const Kleo::CryptoBackendFactory * cpf = Kleo::CryptoBackendFactory::instance();
+ assert( cpf );
+ const Kleo::CryptoBackend::Protocol * proto
+ = isSMIME( format ) ? cpf->smime() : cpf->openpgp() ;
+ assert( proto ); // hmmmm....?
+
+ std::auto_ptr<Kleo::EncryptJob> job( proto->encryptJob( armor( format ),
+ textMode( format ) ) );
+ if ( !job.get() ) {
+ KMessageBox::sorry( mComposeWin,
+ i18n("This message could not be encrypted, "
+ "since the chosen backend does not seem to support "
+ "encryption; this should actually never happen, "
+ "please report this bug.") );
+ return Kpgp::Failure;
+ }
+
+ const GpgME::EncryptionResult res =
+ job->exec( encryptionKeys, cText, true /* we do ownertrust ourselves */, encryptedBody );
+ if ( res.error().isCanceled() ) {
+ kdDebug() << "encryption was canceled by user" << endl;
+ return Kpgp::Canceled;
+ }
+ if ( res.error() ) {
+ kdDebug() << "encryption failed: " << res.error().asString() << endl;
+ job->showErrorDialog( mComposeWin );
+ return Kpgp::Failure;
+ }
+
+ if ( GlobalSettings::showGnuPGAuditLogAfterSuccessfulSignEncrypt() )
+ Kleo::MessageBox::auditLog( 0, job.get(), i18n("GnuPG Audit Log for Encryption Operation") );
+
+ return Kpgp::Ok;
+}
+
+Kpgp::Result MessageComposer::pgpSignedAndEncryptedMsg( QByteArray & encryptedBody,
+ const QByteArray& cText,
+ const std::vector<GpgME::Key> & signingKeys,
+ const std::vector<GpgME::Key> & encryptionKeys,
+ Kleo::CryptoMessageFormat format )
+{
+ // TODO: ASync call? Likely, yes :-)
+ const Kleo::CryptoBackendFactory * cpf = Kleo::CryptoBackendFactory::instance();
+ assert( cpf );
+ const Kleo::CryptoBackend::Protocol * proto
+ = isSMIME( format ) ? cpf->smime() : cpf->openpgp() ;
+ assert( proto ); // hmmmm....?
+
+ std::auto_ptr<Kleo::SignEncryptJob> job( proto->signEncryptJob( armor( format ),
+ textMode( format ) ) );
+ if ( !job.get() ) {
+ KMessageBox::sorry( mComposeWin,
+ i18n("This message could not be signed and encrypted, "
+ "since the chosen backend does not seem to support "
+ "combined signing and encryption; this should actually never happen, "
+ "please report this bug.") );
+ return Kpgp::Failure;
+ }
+
+ const std::pair<GpgME::SigningResult,GpgME::EncryptionResult> res =
+ job->exec( signingKeys, encryptionKeys, cText, false, encryptedBody );
+ if ( res.first.error().isCanceled() || res.second.error().isCanceled() ) {
+ kdDebug() << "encrypt/sign was canceled by user" << endl;
+ return Kpgp::Canceled;
+ }
+ if ( res.first.error() || res.second.error() ) {
+ if ( res.first.error() )
+ kdDebug() << "signing failed: " << res.first.error().asString() << endl;
+ else
+ kdDebug() << "encryption failed: " << res.second.error().asString() << endl;
+ job->showErrorDialog( mComposeWin );
+ return Kpgp::Failure;
+ }
+
+ if ( GlobalSettings::showGnuPGAuditLogAfterSuccessfulSignEncrypt() )
+ Kleo::MessageBox::auditLog( 0, job.get(), i18n("GnuPG Audit Log for Encryption Operation") );
+
+ return Kpgp::Ok;
+}
+
+
+#include "messagecomposer.moc"
diff --git a/kmail/messagecomposer.h b/kmail/messagecomposer.h
new file mode 100644
index 00000000..7d908bfa
--- /dev/null
+++ b/kmail/messagecomposer.h
@@ -0,0 +1,279 @@
+/*
+ * messagecomposer.cpp
+ *
+ * Copyright (c) 2004 Bo Thorsen <bo@sonofthor.dk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#ifndef MESSAGECOMPOSER_H
+#define MESSAGECOMPOSER_H
+
+#include "kmmsgpart.h"
+#include "keyresolver.h"
+
+#include <qobject.h>
+#include <qvaluevector.h>
+
+#include <mimelib/mediatyp.h>
+#include <kleo/cryptobackend.h>
+#include <kpgp.h>
+
+#include <vector>
+
+class KMMessage;
+class KMComposeWin;
+
+class MessageComposerJob;
+class EncryptMessageJob;
+class SetLastMessageAsUnencryptedVersionOfLastButOne;
+
+namespace Kleo {
+ class KeyResolver;
+}
+
+namespace GpgME {
+ class Key;
+}
+
+namespace KPIM {
+ class Identity;
+}
+
+class MessageComposer : public QObject {
+ Q_OBJECT
+ friend class ::MessageComposerJob;
+ friend class ::EncryptMessageJob;
+ friend class ::SetLastMessageAsUnencryptedVersionOfLastButOne;
+
+public:
+ class KeyResolver;
+
+ MessageComposer( KMComposeWin* win, const char* name=0 );
+ ~MessageComposer();
+
+ /**
+ * Applies the user changes to the message object of the composer
+ * and signs/encrypts the message if activated. Returns FALSE in
+ * case of an error (e.g. if PGP encryption fails).
+ * If backgroundMode is true then no functions which might require
+ * user interaction (like signing/encrypting) are performed
+ */
+ void applyChanges( bool disableCrypto );
+
+ QString originalBCC() const { return mBcc; }
+
+ void setDisableBreaking( bool b ) { mDisableBreaking = b; }
+
+ const QValueVector<KMMessage*> & composedMessageList() const {
+ return mMessageList;
+ }
+
+ bool isPerformingSignOperation() const { return mPerformingSignOperation; }
+signals:
+ void done( bool );
+
+private:
+ void readFromComposeWin();
+
+ void adjustCryptFlags();
+
+ bool encryptWithChiasmus( const Kleo::CryptoBackend::Protocol * chiasmus,
+ const QByteArray& body,
+ QByteArray& resultData );
+ void chiasmusEncryptAllAttachments();
+ void composeChiasmusMessage( KMMessage& theMessage, Kleo::CryptoMessageFormat format );
+
+ // This is the composeMessage method
+ void composeMessage();
+ // And these two are the parts that should be run after job completions
+ void createUnencryptedMessageVersion();
+
+ /**
+ * Internal helper function called from applyChanges(void) to allow
+ * processing several messages (encrypted or unencrypted) based on
+ * the same composer content.
+ * That's useful for storing decrypted versions of messages which
+ * were sent in encrypted form. (khz, 2002/06/24)
+ */
+ void composeMessage( KMMessage& theMessage,
+ bool doSign, bool doEncrypt,
+ Kleo::CryptoMessageFormat format );
+ void continueComposeMessage( KMMessage& theMessage, bool doSign,
+ bool doEncrypt,
+ Kleo::CryptoMessageFormat format );
+
+ /**
+ * Called by composeMessage for inline-openpgp messages
+ */
+ void composeInlineOpenPGPMessage( KMMessage& theMessage,
+ bool doSign, bool doEncrypt );
+
+ /**
+ * Get message ready for sending or saving.
+ * This must be done _before_ signing and/or encrypting it.
+ */
+ QByteArray breakLinesAndApplyCodec();
+
+ /**
+ * Create a plain text version of a marked up mail for use as the plain
+ * part in a multipart/alternative mail.
+ */
+ QCString plainTextFromMarkup( const QString& markupText );
+
+ /**
+ * Get signature for a message (into mMessage).
+ * To build nice S/MIME objects signing and encoding must be separated.
+ */
+ void pgpSignedMsg( const QByteArray& cText, Kleo::CryptoMessageFormat f );
+ /**
+ * Get encrypted message.
+ * To build nice S/MIME objects signing and encrypting must be separate.
+ */
+ Kpgp::Result pgpEncryptedMsg( QByteArray& rEncryptedBody,
+ const QByteArray& cText,
+ const std::vector<GpgME::Key> & encryptionKeys,
+ Kleo::CryptoMessageFormat f );
+
+ /**
+ * Get signed & encrypted message.
+ * To build nice S/MIME objects signing and encrypting must be separate.
+ */
+ Kpgp::Result pgpSignedAndEncryptedMsg( QByteArray& rEncryptedBody,
+ const QByteArray& cText,
+ const std::vector<GpgME::Key> & signingKeys,
+ const std::vector<GpgME::Key> & encryptionKeys,
+ Kleo::CryptoMessageFormat f );
+
+ /**
+ * Check for expiry of various certificates.
+ */
+ bool checkForEncryptCertificateExpiry( const QString& recipient,
+ const QCString& certFingerprint );
+
+ /**
+ * Build a MIME object (or a flat text resp.) based upon
+ * structuring information returned by a crypto plugin that was
+ * called via pgpSignedMsg() (or pgpEncryptedMsg(), resp.).
+ *
+ * NOTE: The c string representation of the MIME object (or the
+ * flat text, resp.) is returned in resultingPart, so just
+ * use this string as body text of the surrounding MIME object.
+ * This string *is* encoded according to contentTEncClear
+ * and thus should be ready for being sent via SMTP.
+ */
+ bool processStructuringInfo( const QString bugURL,
+ const QString contentDescriptionClear,
+ const QCString contentTypeClear,
+ const QCString contentSubtypeClear,
+ const QCString contentDispClear,
+ const QCString contentTEncClear,
+ const QByteArray& bodytext,
+ const QString contentDescriptionCiph,
+ const QByteArray& ciphertext,
+ KMMessagePart& resultingPart,
+ bool signing, Kleo::CryptoMessageFormat format );
+
+ void encryptMessage( KMMessage* msg, const Kleo::KeyResolver::SplitInfo & si,
+ bool doSign, bool doEncrypt,
+ KMMessagePart newBodyPart,
+ Kleo::CryptoMessageFormat format );
+
+ void addBodyAndAttachments( KMMessage* msg, const Kleo::KeyResolver::SplitInfo & si,
+ bool doSign, bool doEncrypt,
+ const KMMessagePart& ourFineBodyPart,
+ Kleo::CryptoMessageFormat format );
+
+private slots:
+ void slotDoNextJob();
+
+private:
+ void doNextJob();
+ void emitDone( bool ok );
+
+ int encryptionPossible( const QStringList & recipients, bool openPGP );
+ bool determineWhetherToSign( bool doSignCompletely );
+ bool determineWhetherToEncrypt( bool doEncryptCompletely );
+ void markAllAttachmentsForSigning( bool sign );
+ void markAllAttachmentsForEncryption( bool enc );
+
+ KMComposeWin* mComposeWin;
+ MessageComposerJob * mCurrentJob;
+ KMMessage* mReferenceMessage;
+ QValueVector<KMMessage*> mMessageList;
+
+ Kleo::KeyResolver * mKeyResolver;
+
+ QCString mSignCertFingerprint;
+
+ struct Attachment {
+ Attachment( KMMessagePart * p=0, bool s=false, bool e=false )
+ : part( p ), sign( s ), encrypt( e ) {}
+ KMMessagePart * part;
+ bool sign;
+ bool encrypt;
+ };
+ QValueVector<Attachment> mAttachments;
+
+ QString mPGPSigningKey, mSMIMESigningKey;
+ bool mUseOpportunisticEncryption;
+ bool mSignBody, mEncryptBody;
+ bool mSigningRequested, mEncryptionRequested;
+ bool mDoSign, mDoEncrypt;
+ unsigned int mAllowedCryptoMessageFormats;
+ bool mDisableCrypto;
+ bool mDisableBreaking;
+ QString mBcc;
+ QStringList mTo, mCc, mBccList;
+ bool mDebugComposerCrypto;
+ bool mAutoCharset;
+ QCString mCharset;
+ bool mIsRichText;
+ uint mIdentityUid;
+ bool mRc; // Set this to false, if something fails during the processes
+ bool mHoldJobs; // Don't run the next job yet
+
+ QByteArray mText; // textual representation of the message text, encoded
+ unsigned int mLineBreakColumn; // used for line breaking
+
+ // These are the variables of the big composeMessage(X,Y,Z) message
+ KMMessagePart* mNewBodyPart;
+ QByteArray mSignature;
+
+ QByteArray mEncodedBody; // Only needed if signing and/or encrypting
+ bool mEarlyAddAttachments, mAllAttachmentsAreInBody;
+ KMMessagePart mOldBodyPart;
+ int mPreviousBoundaryLevel;
+
+ // The boundary is saved for later addition into mp/a body
+ DwString mSaveBoundary;
+ QCString mMultipartMixedBoundary;
+
+ QValueList<MessageComposerJob*> mJobs;
+ bool mEncryptWithChiasmus;
+ bool mPerformingSignOperation;
+};
+
+#endif /* MESSAGECOMPOSER_H */
diff --git a/kmail/messagecopyhelper.cpp b/kmail/messagecopyhelper.cpp
new file mode 100644
index 00000000..2a36c53a
--- /dev/null
+++ b/kmail/messagecopyhelper.cpp
@@ -0,0 +1,115 @@
+/*
+ Copyright (c) 2007 Volker Krause <vkrause@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#include "messagecopyhelper.h"
+
+#include "kmcommands.h"
+#include "kmfolder.h"
+#include "kmmsgdict.h"
+
+using namespace KMail;
+using namespace KPIM;
+
+MessageCopyHelper::MessageCopyHelper( const QValueList< Q_UINT32 > & msgs,
+ KMFolder * dest, bool move, QObject * parent ) :
+ QObject( parent )
+{
+ if ( msgs.isEmpty() || !dest )
+ return;
+
+ KMFolder *f = 0;
+ int index;
+ QPtrList<KMMsgBase> list;
+
+ for ( QValueList<Q_UINT32>::ConstIterator it = msgs.constBegin(); it != msgs.constEnd(); ++it ) {
+ KMMsgDict::instance()->getLocation( *it, &f, &index );
+ if ( !f ) // not found
+ continue;
+ if ( f == dest )
+ continue; // already there
+ if ( !mOpenFolders.contains( f ) ) {// not yet opened
+ f->open( "messagecopyhelper" );
+ mOpenFolders.insert( f, 0 );
+ }
+ KMMsgBase *msgBase = f->getMsgBase( index );
+ if ( msgBase )
+ list.append( msgBase );
+ }
+
+ if ( list.isEmpty() )
+ return; // nothing to do
+
+ KMCommand *command;
+ if ( move ) {
+ command = new KMMoveCommand( dest, list );
+ } else {
+ command = new KMCopyCommand( dest, list );
+ }
+
+ connect( command, SIGNAL(completed(KMCommand*)), SLOT(copyCompleted(KMCommand*)) );
+ command->start();
+}
+
+void MessageCopyHelper::copyCompleted(KMCommand * cmd)
+{
+ Q_UNUSED( cmd );
+
+ // close all folders we opened
+ for ( QMap<QGuardedPtr<KMFolder>, int>::ConstIterator it = mOpenFolders.constBegin();
+ it != mOpenFolders.constEnd(); ++it ) {
+ it.key()->close( "messagecopyhelper" );
+ }
+ mOpenFolders.clear();
+ deleteLater();
+}
+
+QValueList< Q_UINT32 > MessageCopyHelper::serNumListFromMailList(const KPIM::MailList & list)
+{
+ QValueList<Q_UINT32> rv;
+ for ( MailList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it )
+ rv.append( (*it).serialNumber() );
+ return rv;
+}
+
+QValueList< Q_UINT32 > MessageCopyHelper::serNumListFromMsgList(QPtrList< KMMsgBase > list)
+{
+ QValueList<Q_UINT32> rv;
+ KMMsgBase* msg = list.first();
+ while( msg ) {
+ rv.append( msg->getMsgSerNum() );
+ msg = list.next();
+ }
+ return rv;
+}
+
+bool MessageCopyHelper::inReadOnlyFolder(const QValueList< Q_UINT32 > & sernums)
+{
+ KMFolder *f = 0;
+ int index;
+ for ( QValueList<Q_UINT32>::ConstIterator it = sernums.begin(); it != sernums.end(); ++it ) {
+ KMMsgDict::instance()->getLocation( *it, &f, &index );
+ if ( !f ) // not found
+ continue;
+ if ( f->isReadOnly() )
+ return true;
+ }
+ return false;
+}
+
+#include "messagecopyhelper.moc"
diff --git a/kmail/messagecopyhelper.h b/kmail/messagecopyhelper.h
new file mode 100644
index 00000000..2291fce9
--- /dev/null
+++ b/kmail/messagecopyhelper.h
@@ -0,0 +1,79 @@
+/*
+ Copyright (c) 2007 Volker Krause <vkrause@kde.org>
+
+ This library is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Library General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This library is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
+ License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA.
+*/
+
+#ifndef KMAIL_MESSAGECOPYHELPER_H
+#define KMAIL_MESSAGECOPYHELPER_H
+
+#include <qguardedptr.h>
+#include <qobject.h>
+#include <qptrlist.h>
+
+#include <maillistdrag.h>
+
+class KMCommand;
+class KMFolder;
+class KMMsgBase;
+
+namespace KMail {
+
+/**
+ Helper class to copy/move a set of messages defined by their serial
+ numbers from arbitrary folders into a common destination folder.
+*/
+class MessageCopyHelper : public QObject
+{
+ Q_OBJECT
+
+ public:
+ /**
+ Creates new MessageCopyHelper object to copy the given messages
+ to the specified destination folder.
+ @param msgs List of serial numbers.
+ @param dest Destination folder.
+ @param move If set to true, messages will be moved instead of copied
+ @param parent The parent object.
+ */
+ MessageCopyHelper( const QValueList<Q_UINT32> &msgs, KMFolder *dest,
+ bool move, QObject *parent = 0 );
+
+ /**
+ Converts a MailList into a serial number list.
+ */
+ static QValueList<Q_UINT32> serNumListFromMailList( const KPIM::MailList &list );
+
+ /**
+ Converts a KMMsgsBase* list into a serial number list.
+ */
+ static QValueList<Q_UINT32> serNumListFromMsgList( QPtrList<KMMsgBase> list );
+
+ /**
+ Checks of any of the given messages comes from a read-only source.
+ */
+ static bool inReadOnlyFolder( const QValueList<Q_UINT32> &sernums );
+
+ private slots:
+ void copyCompleted( KMCommand *cmd );
+
+ private:
+ QMap<QGuardedPtr<KMFolder>, int> mOpenFolders;
+};
+
+}
+
+#endif
diff --git a/kmail/messageproperty.cpp b/kmail/messageproperty.cpp
new file mode 100644
index 00000000..66444ff8
--- /dev/null
+++ b/kmail/messageproperty.cpp
@@ -0,0 +1,173 @@
+/* Message Property
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) Don Sanders <sanders@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "messageproperty.h"
+using namespace KMail;
+
+QMap<Q_UINT32, QGuardedPtr<KMFolder> > MessageProperty::sFolders;
+QMap<Q_UINT32, QGuardedPtr<ActionScheduler> > MessageProperty::sHandlers;
+QMap<Q_UINT32, int > MessageProperty::sTransfers;
+QMap<const KMMsgBase*, long > MessageProperty::sSerialCache;
+
+bool MessageProperty::filtering( Q_UINT32 serNum )
+{
+ return sFolders.contains( serNum );
+}
+
+void MessageProperty::setFiltering( Q_UINT32 serNum, bool filter )
+{
+ assert(!filtering(serNum) || !filter);
+ if (filter && !filtering(serNum))
+ sFolders.replace(serNum, QGuardedPtr<KMFolder>(0) );
+ else if (!filter)
+ sFolders.remove(serNum);
+}
+
+bool MessageProperty::filtering( const KMMsgBase *msgBase )
+{
+ return filtering( msgBase->getMsgSerNum() );
+}
+
+void MessageProperty::setFiltering( const KMMsgBase *msgBase, bool filter )
+{
+ setFiltering( msgBase->getMsgSerNum(), filter );
+}
+
+KMFolder* MessageProperty::filterFolder( Q_UINT32 serNum )
+{
+ if (sFolders.contains(serNum))
+ return sFolders[serNum].operator->();
+ return 0;
+}
+
+void MessageProperty::setFilterFolder( Q_UINT32 serNum, KMFolder* folder )
+{
+ sFolders.replace(serNum, QGuardedPtr<KMFolder>(folder) );
+}
+
+KMFolder* MessageProperty::filterFolder( const KMMsgBase *msgBase )
+{
+ return filterFolder( msgBase->getMsgSerNum() );
+}
+
+void MessageProperty::setFilterFolder( const KMMsgBase *msgBase, KMFolder* folder )
+{
+ setFilterFolder( msgBase->getMsgSerNum(), folder );
+}
+
+ActionScheduler* MessageProperty::filterHandler( Q_UINT32 serNum )
+{
+ if ( sHandlers.contains( serNum ))
+ return sHandlers[serNum].operator->();
+ return 0;
+}
+
+void MessageProperty::setFilterHandler( Q_UINT32 serNum, ActionScheduler* handler )
+{
+ if (handler)
+ sHandlers.replace( serNum, QGuardedPtr<ActionScheduler>(handler) );
+ else
+ sHandlers.remove( serNum );
+}
+
+ActionScheduler* MessageProperty::filterHandler( const KMMsgBase *msgBase )
+{
+ return filterHandler( msgBase->getMsgSerNum() );
+}
+
+void MessageProperty::setFilterHandler( const KMMsgBase *msgBase, ActionScheduler* handler )
+{
+ setFilterHandler( msgBase->getMsgSerNum(), handler );
+}
+
+bool MessageProperty::transferInProgress( Q_UINT32 serNum )
+{
+ if (sTransfers.contains(serNum))
+ return sTransfers[serNum];
+ return false;
+}
+
+void MessageProperty::setTransferInProgress( Q_UINT32 serNum, bool transfer, bool force )
+{
+ int transferInProgress = 0;
+ if (sTransfers.contains(serNum))
+ transferInProgress = sTransfers[serNum];
+ if ( force && !transfer )
+ transferInProgress = 0;
+ else
+ transfer ? ++transferInProgress : --transferInProgress;
+ if ( transferInProgress < 0 )
+ transferInProgress = 0;
+ if (transferInProgress)
+ sTransfers.replace( serNum, transferInProgress );
+ else
+ sTransfers.remove( serNum );
+}
+
+bool MessageProperty::transferInProgress( const KMMsgBase *msgBase )
+{
+ return transferInProgress( msgBase->getMsgSerNum() );
+}
+
+void MessageProperty::setTransferInProgress( const KMMsgBase *msgBase, bool transfer, bool force )
+{
+ setTransferInProgress( msgBase->getMsgSerNum(), transfer, force );
+}
+
+Q_UINT32 MessageProperty::serialCache( const KMMsgBase *msgBase )
+{
+ if (sSerialCache.contains( msgBase ))
+ return sSerialCache[msgBase];
+ return 0;
+}
+
+void MessageProperty::setSerialCache( const KMMsgBase *msgBase, Q_UINT32 serNum )
+{
+ if (serNum)
+ sSerialCache.replace( msgBase, serNum );
+ else
+ sSerialCache.remove( msgBase );
+}
+
+void MessageProperty::forget( const KMMsgBase *msgBase )
+{
+ Q_UINT32 serNum = serialCache( msgBase );
+ if (serNum) {
+ Q_ASSERT( !transferInProgress( serNum ) );
+ sTransfers.remove( serNum );
+ sSerialCache.remove( msgBase );
+ }
+}
+
+#include "messageproperty.moc"
diff --git a/kmail/messageproperty.h b/kmail/messageproperty.h
new file mode 100644
index 00000000..1ffbdcc7
--- /dev/null
+++ b/kmail/messageproperty.h
@@ -0,0 +1,112 @@
+/* Message Property
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) Don Sanders <sanders@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+#ifndef messageproperty_h
+#define messageproperty_h
+
+#include "kmfilteraction.h" // for KMFilterAction::ReturnCode
+#include "kmfolder.h"
+
+#include <qptrlist.h>
+#include <qguardedptr.h>
+#include <qobject.h>
+
+class KMFilter;
+class KMFilterDlg;
+
+namespace KMail {
+
+class ActionScheduler;
+
+/* A place to store properties that some but not necessarily all messages
+ have.
+
+ These properties do not need to be stored persistantly on disk but
+ rather only are required while KMail is running.
+
+ Furthermore some properties, namely complete, transferInProgress, and
+ serialCache should only exist during the lifetime of a particular
+ KMMsgBase based instance.
+ */
+class MessageProperty : public QObject
+{
+ Q_OBJECT
+
+public:
+ /** If the message is being filtered */
+ static bool filtering( Q_UINT32 );
+ static void setFiltering( Q_UINT32, bool filtering );
+ static bool filtering( const KMMsgBase* );
+ static void setFiltering( const KMMsgBase*, bool filtering );
+ /** The folder this message is to be moved into once
+ filtering is finished, or null if the message is not
+ scheduled to be moved */
+ static KMFolder* filterFolder( Q_UINT32 );
+ static void setFilterFolder( Q_UINT32, KMFolder* folder );
+ static KMFolder* filterFolder( const KMMsgBase* );
+ static void setFilterFolder( const KMMsgBase*, KMFolder* folder );
+ /* Set the filterHandler for a message */
+ static ActionScheduler* filterHandler( Q_UINT32 );
+ static void setFilterHandler( Q_UINT32, ActionScheduler* filterHandler );
+ static ActionScheduler* filterHandler( const KMMsgBase* );
+ static void setFilterHandler( const KMMsgBase*, ActionScheduler* filterHandler );
+
+ /* Caches the serial number for a message, or more correctly for a
+ KMMsgBase based instance representing a message.
+ This property becomes invalid when the message is destructed or
+ assigned a new value */
+ static void setSerialCache( const KMMsgBase*, Q_UINT32 );
+ static Q_UINT32 serialCache( const KMMsgBase* );
+
+ /* Set the transferInProgress for a message
+ This property becomes invalid when the message is destructed or
+ assigned a new value */
+ static void setTransferInProgress( const KMMsgBase*, bool, bool = false );
+ 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 */
+ static void forget( const KMMsgBase* );
+
+private:
+ // The folder a message is to be moved into once filtering is finished if any
+ static QMap<Q_UINT32, QGuardedPtr<KMFolder> > sFolders;
+ // The action scheduler currently processing a message if any
+ static QMap<Q_UINT32, QGuardedPtr<ActionScheduler> > sHandlers;
+ // The transferInProgres state of a message if any.
+ static QMap<Q_UINT32, int > sTransfers;
+ // The cached serial number of a message if any.
+ static QMap<const KMMsgBase*, long > sSerialCache;
+};
+
+}
+
+#endif /*messageproperty_h*/
diff --git a/kmail/messagesender.h b/kmail/messagesender.h
new file mode 100644
index 00000000..4f25b378
--- /dev/null
+++ b/kmail/messagesender.h
@@ -0,0 +1,97 @@
+/*
+ messagesender.h
+
+ This file is part of KMail, the KDE mail client
+ Copyright (c) 2005 Klarlvdalens Datakonsult AB
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_MESSAGESENDER_H__
+#define __KMAIL_MESSAGESENDER_H__
+#include <qstring.h>
+
+class KMMessage;
+
+namespace KMail {
+
+class MessageSender {
+protected:
+ virtual ~MessageSender() = 0;
+
+public:
+ enum SendMethod {
+ SendDefault = -1,
+ SendImmediate = true,
+ SendLater = false
+ };
+ /**
+ Send given message.
+
+ The message is either queued (@p method == SendLater) or sent
+ immediately (@p method = SendImmediate). The default behaviour,
+ as selected with setSendImmediate(), can be overwritten with the
+ parameter @p method. The sender takes ownership of the given
+ message on success, so DO NOT DELETE OR MODIFY the message
+ further.
+
+ FIXME: what about send() == false?
+
+ @return true on success.
+ */
+ bool send( KMMessage * msg, SendMethod method=SendDefault ) { return doSend( msg, method ); }
+
+ /**
+ Start sending all queued messages.
+
+ FIXME: what does success mean here, if it's only _start_ sending?
+
+ Optionally a transport can be specified that will be used as the
+ default transport.
+
+ @return true on success.
+ */
+ bool sendQueued( const QString & transport=QString::null ) { return doSendQueued( transport ); }
+
+ virtual void readConfig() = 0;
+ virtual void writeConfig( bool withSync = true ) = 0;
+
+ virtual bool sendImmediate() const = 0;
+ virtual void setSendImmediate( bool immediate ) = 0;
+
+ virtual bool sendQuotedPrintable() const = 0;
+ virtual void setSendQuotedPrintable( bool qp ) = 0;
+protected:
+ virtual bool doSend( KMMessage * msg, short sendNow ) = 0;
+ virtual bool doSendQueued( const QString& transport ) = 0;
+};
+
+inline MessageSender::~MessageSender() {}
+
+}
+
+#endif /* __KMAIL_MESSAGESENDER_H__ */
+
diff --git a/kmail/mh2kmail b/kmail/mh2kmail
new file mode 100755
index 00000000..827cac58
--- /dev/null
+++ b/kmail/mh2kmail
@@ -0,0 +1,27 @@
+#!/bin/bash
+# This script converts MH mail folders to the folders that KMail
+# prefers (all messages in one file)
+#
+# usage: mh2kmail <dirname>
+#
+# Author: Stefan Taferner <taferner@kde.org>
+# This script is under GPL
+#
+if [ "X$*" = "X" ]; then
+ echo "usage: mh2kmail <dirname>"
+ exit 0
+fi
+
+MH_DIR=$1
+FOLDER=${MH_DIR}.mbx
+
+# remove old kmail folder if any
+rm -f $FOLDER
+
+for f in `/bin/ls $MH_DIR|sed -e :a -e 's/^.\{1,5\}$/ &/;ta'|sort`; do
+ echo "From ???@??? 00:00:00 1997 +0000" >> $FOLDER
+ cat $MH_DIR/$f >> $FOLDER
+done
+
+echo "Done. Messages stored in file $FOLDER."
+
diff --git a/kmail/mh2kmailr b/kmail/mh2kmailr
new file mode 100755
index 00000000..d88905aa
--- /dev/null
+++ b/kmail/mh2kmailr
@@ -0,0 +1,29 @@
+#!/bin/bash
+# This script converts MH mail folders to the folders that KMail
+# prefers (all messages in one file)
+#
+# usage: mh2kmail <dirname>
+#
+# Author: Stefan Taferner <taferner@kde.org>
+# This script is under GPL
+#
+if [ "X$*" = "X" ]; then
+ echo "usage: mh2kmailr <dirname>"
+ exit 0
+fi
+
+MH_DIR=$1
+
+for d in `/usr/bin/find $MH_DIR -type d -print|sed -e :a -e 's/^.\{1,5\}$/ &/;ta'|sort`; do
+FOLDER=${d}.mbx
+
+# remove old kmail folder if any
+rm -f $FOLDER
+
+for f in `/usr/bin/find $d -type f -maxdepth 1 -print|sed -e :a -e 's/^.\{1,5\}$/ &/;ta'|sort`; do
+ echo "From ???@??? 00:00:00 1997 +0000" >> $FOLDER
+ cat $f >> $FOLDER
+done
+
+echo "Done. Messages stored in file $FOLDER."
+done
diff --git a/kmail/networkaccount.cpp b/kmail/networkaccount.cpp
new file mode 100644
index 00000000..3639ab7b
--- /dev/null
+++ b/kmail/networkaccount.cpp
@@ -0,0 +1,367 @@
+/** -*- c++ -*-
+ * networkaccount.cpp
+ *
+ * Copyright (c) 2000-2002 Michael Haeckel <haeckel@kde.org>
+ * Copyright (c) 2002 Marc Mutz <mutz@kde.org>
+ *
+ * This file is based on work on pop3 and imap account implementations
+ * by Don Sanders <sanders@kde.org> and Michael Haeckel <haeckel@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "networkaccount.h"
+#include "accountmanager.h"
+#include "kmkernel.h"
+#include "globalsettings.h"
+
+#include <kconfig.h>
+#include <kio/global.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+#include <kwallet.h>
+using KIO::MetaData;
+using KWallet::Wallet;
+
+#include <climits>
+
+namespace KMail {
+
+
+ // for restricting number of concurrent connections to the same server
+ static QMap<QString, int> s_serverConnections;
+
+ NetworkAccount::NetworkAccount( AccountManager * parent, const QString & name, uint id )
+ : KMAccount( parent, name, id ),
+ mSlave( 0 ),
+ mAuth( "*" ),
+ mPort( 0 ),
+ mStorePasswd( false ),
+ mUseSSL( false ),
+ mUseTLS( false ),
+ mAskAgain( false ),
+ mPasswdDirty( false ),
+ mStorePasswdInConfig( false )
+ {
+
+ }
+
+ NetworkAccount::~NetworkAccount() {
+
+ }
+
+ void NetworkAccount::init() {
+ KMAccount::init();
+
+ mSieveConfig = SieveConfig();
+ mLogin = QString::null;
+ mPasswd = QString::null;
+ mAuth = "*";
+ mHost = QString::null;
+ mPort = defaultPort();
+ mStorePasswd = false;
+ mUseSSL = false;
+ mUseTLS = false;
+ mAskAgain = false;
+ }
+
+ //
+ //
+ // Getters and Setters
+ //
+ //
+
+ void NetworkAccount::setLogin( const QString & login ) {
+ mLogin = login;
+ }
+
+ QString NetworkAccount::passwd() const {
+ if ( storePasswd() && mPasswd.isEmpty() )
+ mOwner->readPasswords();
+ return decryptStr( mPasswd );
+ }
+
+ void NetworkAccount::setPasswd( const QString & passwd, bool storeInConfig ) {
+ if ( mPasswd != encryptStr( passwd ) ) {
+ mPasswd = encryptStr( passwd );
+ mPasswdDirty = true;
+ }
+ setStorePasswd( storeInConfig );
+ }
+
+ void NetworkAccount::clearPasswd() {
+ setPasswd( "", false );
+ }
+
+ void NetworkAccount::setAuth( const QString & auth ) {
+ mAuth = auth;
+ }
+
+ void NetworkAccount::setStorePasswd( bool store ) {
+ if( mStorePasswd != store && store )
+ mPasswdDirty = true;
+ mStorePasswd = store;
+ }
+
+ void NetworkAccount::setHost( const QString & host ) {
+ mHost = host;
+ }
+
+ void NetworkAccount::setPort( unsigned short int port ) {
+ mPort = port;
+ }
+
+ void NetworkAccount::setUseSSL( bool use ) {
+ mUseSSL = use;
+ }
+
+ void NetworkAccount::setUseTLS( bool use ) {
+ mUseTLS = use;
+ }
+
+ void NetworkAccount::setSieveConfig( const SieveConfig & config ) {
+ mSieveConfig = config;
+ }
+
+ //
+ //
+ // read/write config
+ //
+ //
+
+ void NetworkAccount::readConfig( /*const*/ KConfig/*Base*/ & config ) {
+ KMAccount::readConfig( config );
+
+ setLogin( config.readEntry( "login" ) );
+
+ if ( config.readNumEntry( "store-passwd", false ) ) { // ### s/Num/Bool/
+ mStorePasswd = true;
+ QString encpasswd = config.readEntry( "pass" );
+ if ( encpasswd.isEmpty() ) {
+ encpasswd = config.readEntry( "passwd" );
+ if ( !encpasswd.isEmpty() ) encpasswd = importPassword( encpasswd );
+ }
+
+ if ( !encpasswd.isEmpty() ) {
+ setPasswd( decryptStr( encpasswd ), true );
+ // migrate to KWallet if available
+ if ( Wallet::isEnabled() ) {
+ config.deleteEntry( "pass" );
+ config.deleteEntry( "passwd" );
+ mPasswdDirty = true;
+ mStorePasswdInConfig = false;
+ } else {
+ mPasswdDirty = false; // set by setPasswd() on first read
+ mStorePasswdInConfig = true;
+ }
+ } else {
+ // read password if wallet is already open, otherwise defer to on-demand loading
+ if ( Wallet::isOpen( Wallet::NetworkWallet() ) )
+ readPassword();
+ }
+
+ } else {
+ setPasswd( "", false );
+ }
+
+ setHost( config.readEntry( "host" ) );
+
+ unsigned int port = config.readUnsignedNumEntry( "port", defaultPort() );
+ if ( port > USHRT_MAX ) port = defaultPort();
+ setPort( port );
+
+ setAuth( config.readEntry( "auth", "*" ) );
+ setUseSSL( config.readBoolEntry( "use-ssl", false ) );
+ setUseTLS( config.readBoolEntry( "use-tls", false ) );
+
+ mSieveConfig.readConfig( config );
+ }
+
+ void NetworkAccount::writeConfig( KConfig/*Base*/ & config ) /*const*/ {
+ KMAccount::writeConfig( config );
+
+ config.writeEntry( "login", login() );
+ config.writeEntry( "store-passwd", storePasswd() );
+
+ if ( storePasswd() ) {
+ // write password to the wallet if possbile and necessary
+ bool passwdStored = false;
+ if ( mPasswdDirty ) {
+ Wallet *wallet = kmkernel->wallet();
+ if ( wallet && wallet->writePassword( "account-" + QString::number(mId), passwd() ) == 0 ) {
+ passwdStored = true;
+ mPasswdDirty = false;
+ mStorePasswdInConfig = false;
+ }
+ } else {
+ passwdStored = !mStorePasswdInConfig; // already in the wallet
+ }
+ // if wallet is not available, write to config file, since the account
+ // manager deletes this group, we need to write it always
+ if ( !passwdStored && ( mStorePasswdInConfig || KMessageBox::warningYesNo( 0,
+ i18n("KWallet is not available. It is strongly recommended to use "
+ "KWallet for managing your passwords.\n"
+ "However, KMail can store the password in its configuration "
+ "file instead. The password is stored in an obfuscated format, "
+ "but should not be considered secure from decryption efforts "
+ "if access to the configuration file is obtained.\n"
+ "Do you want to store the password for account '%1' in the "
+ "configuration file?").arg( name() ),
+ i18n("KWallet Not Available"),
+ KGuiItem( i18n("Store Password") ),
+ KGuiItem( i18n("Do Not Store Password") ) )
+ == KMessageBox::Yes ) ) {
+ config.writeEntry( "pass", encryptStr( passwd() ) );
+ mStorePasswdInConfig = true;
+ }
+ }
+
+ // delete password from the wallet if password storage is disabled
+ if (!storePasswd() && !Wallet::keyDoesNotExist(
+ Wallet::NetworkWallet(), "kmail", "account-" + QString::number(mId))) {
+ Wallet *wallet = kmkernel->wallet();
+ if (wallet)
+ wallet->removeEntry( "account-" + QString::number(mId) );
+ }
+
+ config.writeEntry( "host", host() );
+ config.writeEntry( "port", static_cast<unsigned int>( port() ) );
+ config.writeEntry( "auth", auth() );
+ config.writeEntry( "use-ssl", useSSL() );
+ config.writeEntry( "use-tls", useTLS() );
+
+ mSieveConfig.writeConfig( config );
+ }
+
+ //
+ //
+ // Network processing
+ //
+ //
+
+ KURL NetworkAccount::getUrl() const {
+ KURL url;
+ url.setProtocol( protocol() );
+ url.setUser( login() );
+ url.setPass( passwd() );
+ url.setHost( host() );
+ url.setPort( port() );
+ return url;
+ }
+
+ MetaData NetworkAccount::slaveConfig() const {
+ MetaData m;
+ m.insert( "tls", useTLS() ? "on" : "off" );
+ return m;
+ }
+
+ void NetworkAccount::pseudoAssign( const KMAccount * a ) {
+ KMAccount::pseudoAssign( a );
+
+ const NetworkAccount * n = dynamic_cast<const NetworkAccount*>( a );
+ if ( !n ) return;
+
+ setLogin( n->login() );
+ setPasswd( n->passwd(), n->storePasswd() );
+ setHost( n->host() );
+ setPort( n->port() );
+ setAuth( n->auth() );
+ setUseSSL( n->useSSL() );
+ setUseTLS( n->useTLS() );
+ setSieveConfig( n->sieveConfig() );
+ }
+
+ void NetworkAccount::readPassword() {
+ if ( !storePasswd() )
+ return;
+
+ // ### workaround for broken Wallet::keyDoesNotExist() which returns wrong
+ // results for new entries without closing and reopening the wallet
+ if ( Wallet::isOpen( Wallet::NetworkWallet() ) )
+ {
+ Wallet *wallet = kmkernel->wallet();
+ if (!wallet || !wallet->hasEntry( "account-" + QString::number(mId) ) )
+ return;
+ }
+ else
+ {
+ if (Wallet::keyDoesNotExist( Wallet::NetworkWallet(), "kmail", "account-" + QString::number(mId) ) )
+ return;
+ }
+
+ if ( kmkernel->wallet() ) {
+ QString passwd;
+ kmkernel->wallet()->readPassword( "account-" + QString::number(mId), passwd );
+ setPasswd( passwd, true );
+ mPasswdDirty = false;
+ }
+ }
+
+ void NetworkAccount::setCheckingMail( bool checking )
+ {
+ mCheckingMail = checking;
+ if ( host().isEmpty() )
+ return;
+ if ( checking ) {
+ if ( s_serverConnections.find( host() ) != s_serverConnections.end() )
+ s_serverConnections[host()] += 1;
+ else
+ s_serverConnections[host()] = 1;
+ kdDebug(5006) << "check mail started - connections for host "
+ << host() << " now is "
+ << s_serverConnections[host()] << endl;
+ } else {
+ if ( s_serverConnections.find( host() ) != s_serverConnections.end() &&
+ s_serverConnections[host()] > 0 ) {
+ s_serverConnections[host()] -= 1;
+ kdDebug(5006) << "connections to server " << host()
+ << " now " << s_serverConnections[host()] << endl;
+ }
+ }
+}
+
+ bool NetworkAccount::mailCheckCanProceed() const
+ {
+ bool offlineMode = KMKernel::isOffline();
+
+ kdDebug(5006) << "for host " << host()
+ << " current connections="
+ << (s_serverConnections.find(host())==s_serverConnections.end() ? 0 : s_serverConnections[host()])
+ << " and limit is " << GlobalSettings::self()->maxConnectionsPerHost()
+ << endl;
+ bool connectionLimitForHostReached = !host().isEmpty()
+ && GlobalSettings::self()->maxConnectionsPerHost() > 0
+ && s_serverConnections.find( host() ) != s_serverConnections.end()
+ && s_serverConnections[host()] >= GlobalSettings::self()->maxConnectionsPerHost();
+ kdDebug(5006) << "connection limit reached: "
+ << connectionLimitForHostReached << endl;
+
+ return ( !connectionLimitForHostReached && !offlineMode );
+ }
+
+ void NetworkAccount::resetConnectionList( NetworkAccount* acct )
+ {
+ s_serverConnections[ acct->host() ] = 0;
+ }
+
+} // namespace KMail
+
+#include "networkaccount.moc"
diff --git a/kmail/networkaccount.h b/kmail/networkaccount.h
new file mode 100644
index 00000000..616de671
--- /dev/null
+++ b/kmail/networkaccount.h
@@ -0,0 +1,142 @@
+/* -*- c++ -*-
+ * networkaccount.h
+ *
+ * Copyright (c) 2000-2002 Michael Haeckel <haeckel@kde.org>
+ * Copyright (c) 2002 Marc Mutz <mutz@kde.org>
+ *
+ * This file is based on work on pop3 and imap account implementations
+ * by Don Sanders <sanders@kde.org> and Michael Haeckel <haeckel@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __KMAIL_NETWORKACCOUNT_H__
+#define __KMAIL_NETWORKACCOUNT_H__
+
+#include <qguardedptr.h>
+
+#include "kmaccount.h"
+
+#include "sieveconfig.h"
+
+#include <qstring.h>
+
+class AccountManager;
+class KConfig/*Base*/;
+class KURL;
+namespace KIO {
+ class Slave;
+ class MetaData;
+}
+
+namespace KMail {
+
+ class NetworkAccount : public KMAccount {
+ Q_OBJECT
+ protected:
+ NetworkAccount( AccountManager * parent, const QString & name, uint id );
+ public:
+ virtual ~NetworkAccount();
+
+ /** Set the config options to a decent state */
+ virtual void init();
+
+ /** A weak assignment operator */
+ virtual void pseudoAssign( const KMAccount * a );
+
+ /** User login name */
+ QString login() const { return mLogin; }
+ virtual void setLogin( const QString & login );
+
+ /** User password */
+ QString passwd() const;
+ virtual void setPasswd( const QString & passwd, bool storeInConfig=false );
+
+ /**
+ * Set the password to "" (empty string)
+ */
+ virtual void clearPasswd();
+
+ /** authentication method */
+ QString auth() const { return mAuth; }
+ virtual void setAuth( const QString & auth );
+
+ /** @return whether to store the password in the config file */
+ bool storePasswd() const { return mStorePasswd; }
+ virtual void setStorePasswd( bool store );
+
+ /** Server hostname */
+ QString host() const { return mHost; }
+ virtual void setHost( const QString & host );
+
+ /** Server port number */
+ unsigned short int port() const { return mPort; }
+ virtual void setPort( unsigned short int port );
+
+ /** @return whether to use SSL */
+ bool useSSL() const { return mUseSSL; }
+ virtual void setUseSSL( bool use );
+
+ /** @return whether to use TLS */
+ bool useTLS() const { return mUseTLS; }
+ virtual void setUseTLS( bool use );
+
+ /** @return the SieveConfig for this account */
+ KMail::SieveConfig sieveConfig() const { return mSieveConfig; }
+ virtual void setSieveConfig( const KMail::SieveConfig & config );
+
+ /** Configure the slave by adding to the meta data map */
+ virtual KIO::MetaData slaveConfig() const;
+
+ virtual void readConfig( /*const*/ KConfig/*Base*/ & config );
+ virtual void writeConfig( KConfig/*Base*/ & config ) /*const*/;
+
+ /** @return an URL for this account */
+ virtual KURL getUrl() const;
+
+ /** @return the KIO slave for this account */
+ KIO::Slave * slave() const { return mSlave; }
+
+ /** Kill all jobs that are currently in progress */
+ virtual void killAllJobs( bool disconnectSlave = false ) = 0;
+
+ /** Read password from wallet, used for on-demand wallet opening */
+ void readPassword();
+
+ virtual bool mailCheckCanProceed() const;
+
+ virtual void setCheckingMail( bool checking );
+
+ /** Reset connection list for the account */
+ static void resetConnectionList( NetworkAccount* acct );
+ protected:
+ virtual QString protocol() const = 0;
+ virtual unsigned short int defaultPort() const = 0;
+
+ protected:
+ KMail::SieveConfig mSieveConfig;
+ QGuardedPtr<KIO::Slave> mSlave;
+ QString mLogin, mPasswd, mAuth, mHost;
+ unsigned short int mPort;
+ bool mStorePasswd : 1;
+ bool mUseSSL : 1;
+ bool mUseTLS : 1;
+ bool mAskAgain : 1;
+ bool mPasswdDirty, mStorePasswdInConfig;
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_NETWORKACCOUNT_H__
diff --git a/kmail/newfolderdialog.cpp b/kmail/newfolderdialog.cpp
new file mode 100644
index 00000000..35bb461b
--- /dev/null
+++ b/kmail/newfolderdialog.cpp
@@ -0,0 +1,311 @@
+/*******************************************************************************
+**
+** Filename : newfolderdialog.cpp
+** Created on : 30 January, 2005
+** Copyright : (c) 2005 Till Adam
+** Email : adam@kde.org
+**
+*******************************************************************************/
+
+/*******************************************************************************
+**
+** 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.
+**
+** In addition, as a special exception, the copyright holders give
+** permission to link the code of this program with any edition of
+** the Qt library by Trolltech AS, Norway (or with modified versions
+** of Qt that use the same license as Qt), and distribute linked
+** combinations including the two. You must obey the GNU General
+** Public License in all respects for all of the code used other than
+** Qt. If you modify this file, you may extend this exception to
+** your version of the file, but you are not obligated to do so. If
+** you do not wish to do so, delete this exception statement from
+** your version.
+*******************************************************************************/
+
+#include <qvariant.h>
+#include <qpushbutton.h>
+#include <qlabel.h>
+#include <qlineedit.h>
+#include <qcombobox.h>
+#include <qlayout.h>
+#include <qtooltip.h>
+#include <qwhatsthis.h>
+#include <qregexp.h>
+
+#include <klocale.h>
+#include <kdialogbase.h>
+#include <kmessagebox.h>
+
+#include "newfolderdialog.h"
+#include "kmfolder.h"
+#include "folderstorage.h"
+#include "kmfolderimap.h"
+#include "kmfoldercachedimap.h"
+#include "kmfoldermgr.h"
+#include "kmfolderdir.h"
+#include "folderstorage.h"
+#include "kmailicalifaceimpl.h"
+#include "kmacctimap.h"
+#include "kmacctcachedimap.h"
+
+using namespace KMail;
+
+NewFolderDialog::NewFolderDialog( QWidget* parent, KMFolder *folder )
+ : KDialogBase( parent, "new_folder_dialog", false, i18n( "New Folder" ),
+ KDialogBase::Ok|KDialogBase::Cancel,
+ KDialogBase::Ok, true ),
+ mFolder( folder )
+{
+ setWFlags( getWFlags() | WDestructiveClose );
+ if ( mFolder ) {
+ setCaption( i18n("New Subfolder of %1").arg( mFolder->prettyURL() ) );
+ }
+ QWidget* privateLayoutWidget = new QWidget( this, "mTopLevelLayout" );
+ privateLayoutWidget->setGeometry( QRect( 10, 10, 260, 80 ) );
+ setMainWidget( privateLayoutWidget );
+ mTopLevelLayout = new QVBoxLayout( privateLayoutWidget, 0, spacingHint(),
+ "mTopLevelLayout");
+
+ mNameHBox = new QHBoxLayout( 0, 0, 6, "mNameHBox");
+
+ mNameLabel = new QLabel( privateLayoutWidget, "mNameLabel" );
+ mNameLabel->setText( i18n( "&Name:" ) );
+ mNameHBox->addWidget( mNameLabel );
+
+ mNameLineEdit = new QLineEdit( privateLayoutWidget, "mNameLineEdit" );
+ mNameLabel->setBuddy( mNameLineEdit );
+ QWhatsThis::add( mNameLineEdit, i18n( "Enter a name for the new folder." ) );
+ mNameLineEdit->setFocus();
+ mNameHBox->addWidget( mNameLineEdit );
+ mTopLevelLayout->addLayout( mNameHBox );
+ connect( mNameLineEdit, SIGNAL( textChanged ( const QString & ) ), this, SLOT( slotFolderNameChanged( const QString & ) ) );
+
+ if ( !mFolder ||
+ ( mFolder->folderType() != KMFolderTypeImap &&
+ mFolder->folderType() != KMFolderTypeCachedImap ) ) {
+ mFormatHBox = new QHBoxLayout( 0, 0, 6, "mFormatHBox");
+ mMailboxFormatLabel = new QLabel( privateLayoutWidget, "mMailboxFormatLabel" );
+ mMailboxFormatLabel->setText( i18n( "Mailbox &format:" ) );
+ mFormatHBox->addWidget( mMailboxFormatLabel );
+
+ mFormatComboBox = new QComboBox( false, privateLayoutWidget, "mFormatComboBox" );
+ mMailboxFormatLabel->setBuddy( mFormatComboBox );
+ QWhatsThis::add( mFormatComboBox, i18n( "Select whether you want to store the messages in this folder as one file per message (maildir) or as one big file (mbox). KMail uses maildir by default and this only needs to be changed in rare circumstances. If you are unsure, leave this option as-is." ) );
+
+ mFormatComboBox->insertItem("mbox", 0);
+ mFormatComboBox->insertItem("maildir", 1);
+ // does the below make any sense?
+ // mFormatComboBox->insertItem("search", 2);
+ {
+ KConfig *config = KMKernel::config();
+ KConfigGroupSaver saver(config, "General");
+ int type = config->readNumEntry("default-mailbox-format", 1);
+ if ( type < 0 || type > 1 ) type = 1;
+ mFormatComboBox->setCurrentItem( type );
+ }
+ mFormatHBox->addWidget( mFormatComboBox );
+ mTopLevelLayout->addLayout( mFormatHBox );
+ }
+
+ // --- contents -----
+ if ( kmkernel->iCalIface().isEnabled() ) {
+ mContentsHBox = new QHBoxLayout( 0, 0, 6, "mContentsHBox");
+
+ mContentsLabel = new QLabel( privateLayoutWidget, "mContentsLabel" );
+ mContentsLabel->setText( i18n( "Folder &contains:" ) );
+ mContentsHBox->addWidget( mContentsLabel );
+
+ mContentsComboBox = new QComboBox( false, privateLayoutWidget, "mContentsComboBox" );
+ mContentsLabel->setBuddy( mContentsComboBox );
+ QWhatsThis::add( mContentsComboBox, i18n( "Select whether you want the new folder to be used for mail storage of for storage of groupware items such as tasks or notes. The default is mail. If you are unsure, leave this option as-is." ) );
+ mContentsComboBox->insertItem( i18n( "Mail" ) );
+ mContentsComboBox->insertItem( i18n( "Calendar" ) );
+ mContentsComboBox->insertItem( i18n( "Contacts" ) );
+ mContentsComboBox->insertItem( i18n( "Notes" ) );
+ mContentsComboBox->insertItem( i18n( "Tasks" ) );
+ mContentsComboBox->insertItem( i18n( "Journal" ) );
+ if ( mFolder ) // inherit contents type from papa
+ mContentsComboBox->setCurrentItem( mFolder->storage()->contentsType() );
+ mContentsHBox->addWidget( mContentsComboBox );
+ mTopLevelLayout->addLayout( mContentsHBox );
+ }
+
+ if ( mFolder &&
+ ( mFolder->folderType() == KMFolderTypeImap ||
+ mFolder->folderType() == KMFolderTypeCachedImap ) ) {
+ bool rootFolder = false;
+ QStringList namespaces;
+ if ( mFolder->folderType() == KMFolderTypeImap ) {
+ ImapAccountBase* ai = static_cast<KMFolderImap*>(mFolder->storage())->account();
+ if ( mFolder->storage() == ai->rootFolder() ) {
+ rootFolder = true;
+ namespaces = ai->namespaces()[ImapAccountBase::PersonalNS];
+ }
+ }
+ if ( mFolder->folderType() == KMFolderTypeCachedImap ) {
+ ImapAccountBase* ai = static_cast<KMFolderCachedImap*>(mFolder->storage())->account();
+ if ( ai && mFolder->storage() == ai->rootFolder() ) {
+ rootFolder = true;
+ namespaces = ai->namespaces()[ImapAccountBase::PersonalNS];
+ }
+ }
+ if ( rootFolder && namespaces.count() > 1 ) {
+ mNamespacesHBox = new QHBoxLayout( 0, 0, 6, "mNamespaceHBox");
+
+ mNamespacesLabel = new QLabel( privateLayoutWidget, "mNamespacesLabel" );
+ mNamespacesLabel->setText( i18n( "Namespace for &folder:" ) );
+ mNamespacesHBox->addWidget( mNamespacesLabel );
+
+ mNamespacesComboBox = new QComboBox( false, privateLayoutWidget, "mNamespacesComboBox" );
+ mNamespacesLabel->setBuddy( mNamespacesComboBox );
+ QWhatsThis::add( mNamespacesComboBox, i18n( "Select the personal namespace the folder should be created in." ) );
+ mNamespacesComboBox->insertStringList( namespaces );
+ mNamespacesHBox->addWidget( mNamespacesComboBox );
+ mTopLevelLayout->addLayout( mNamespacesHBox );
+ } else {
+ mNamespacesComboBox = 0;
+ }
+ }
+
+ resize( QSize(282, 108).expandedTo(minimumSizeHint()) );
+ clearWState( WState_Polished );
+ slotFolderNameChanged( mNameLineEdit->text());
+}
+
+void NewFolderDialog::slotFolderNameChanged( const QString & _text)
+{
+ enableButtonOK( !_text.isEmpty() );
+}
+
+void NewFolderDialog::slotOk()
+{
+ const QString fldName = mNameLineEdit->text();
+ if ( fldName.isEmpty() ) {
+ KMessageBox::error( this, i18n("Please specify a name for the new folder."),
+ i18n( "No Name Specified" ) );
+ 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." ) );
+ 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 ) ) {
+ QString delimiter;
+ if ( mFolder->folderType() == KMFolderTypeImap ) {
+ KMAcctImap* ai = static_cast<KMFolderImap*>( mFolder->storage() )->account();
+ if ( ai )
+ delimiter = ai->delimiterForFolder( mFolder->storage() );
+ } else {
+ KMAcctCachedImap* ai = static_cast<KMFolderCachedImap*>( 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
+ if ( mFolder )
+ selectedFolderDir = mFolder->createChildFolder();
+
+ // check if the folder already exists
+ if( selectedFolderDir->hasNamedFolder( fldName )
+ && ( !( mFolder
+ && ( selectedFolderDir == mFolder->parent() )
+ && ( mFolder->storage()->name() == fldName ) ) ) )
+ {
+ const QString message = i18n( "<qt>Failed to create folder <b>%1</b>, folder already exists.</qt>" ).arg(fldName);
+ KMessageBox::error( this, message );
+ return;
+ }
+
+ /* Ok, obvious errors caught, let's try creating it for real. */
+ const QString message = i18n( "<qt>Failed to create folder <b>%1</b>."
+ "</qt> " ).arg(fldName);
+ bool success = false;
+ KMFolder *newFolder = 0;
+
+ if ( mFolder && mFolder->folderType() == KMFolderTypeImap ) {
+ KMFolderImap* selectedStorage = static_cast<KMFolderImap*>( 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 ) {
+ QString 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<KMFolderImap*>( newFolder->storage() );
+ selectedStorage->createFolder(fldName, parent); // create it on the server
+ newStorage->initializeFrom( selectedStorage, imapPath, QString::null );
+ static_cast<KMFolderImap*>(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<KMFolderCachedImap*>( mFolder->storage() );
+ KMFolderCachedImap* newStorage = static_cast<KMFolderCachedImap*>( newFolder->storage() );
+ newStorage->initializeFrom( selectedStorage );
+ if ( mNamespacesComboBox ) {
+ // create folder with namespace
+ QString 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;
+ }
+ if ( !success ) {
+ KMessageBox::error( this, message );
+ return;
+ }
+
+ // Set type field
+ if ( kmkernel->iCalIface().isEnabled() && mContentsComboBox ) {
+ KMail::FolderContentsType type =
+ static_cast<KMail::FolderContentsType>( mContentsComboBox->currentItem() );
+ newFolder->storage()->setContentsType( type );
+ newFolder->storage()->writeConfig(); // connected slots will read it
+ }
+ KDialogBase::slotOk();
+}
+
+#include "newfolderdialog.moc"
diff --git a/kmail/newfolderdialog.h b/kmail/newfolderdialog.h
new file mode 100644
index 00000000..f5065bcd
--- /dev/null
+++ b/kmail/newfolderdialog.h
@@ -0,0 +1,79 @@
+/*******************************************************************************
+**
+** Filename : newfolderdialog.h
+** Created on : 30 January, 2005
+** Copyright : (c) 2005 Till Adam
+** Email : adam@kde.org
+**
+*******************************************************************************/
+
+/*******************************************************************************
+**
+** 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.
+**
+** In addition, as a special exception, the copyright holders give
+** permission to link the code of this program with any edition of
+** the Qt library by Trolltech AS, Norway (or with modified versions
+** of Qt that use the same license as Qt), and distribute linked
+** combinations including the two. You must obey the GNU General
+** Public License in all respects for all of the code used other than
+** Qt. If you modify this file, you may extend this exception to
+** your version of the file, but you are not obligated to do so. If
+** you do not wish to do so, delete this exception statement from
+** your version.
+*******************************************************************************/
+
+#ifndef NEW_FOLDER_DIALOG_H
+#define NEW_FOLDER_DIALOG_H
+
+#include <qvariant.h>
+#include <qdialog.h>
+#include <kdialogbase.h>
+
+class QVBoxLayout;
+class QHBoxLayout;
+class QGridLayout;
+class QSpacerItem;
+class QLabel;
+class QLineEdit;
+class QComboBox;
+class KMFolder;
+
+namespace KMail {
+
+class NewFolderDialog : public KDialogBase
+{
+ Q_OBJECT
+
+ public:
+ NewFolderDialog( QWidget* parent = 0, KMFolder *folder = 0 );
+ ~NewFolderDialog() {};
+
+ QLabel* mNameLabel;
+ QLineEdit* mNameLineEdit;
+ QLabel* mMailboxFormatLabel;
+ QComboBox* mFormatComboBox;
+ QLabel* mContentsLabel;
+ QComboBox* mContentsComboBox;
+ QLabel* mNamespacesLabel;
+ QComboBox* mNamespacesComboBox;
+
+ protected:
+ QVBoxLayout* mTopLevelLayout;
+ QHBoxLayout* mNameHBox;
+ QHBoxLayout* mFormatHBox;
+ QHBoxLayout* mContentsHBox;
+ QHBoxLayout* mNamespacesHBox;
+ protected slots:
+ void slotOk();
+ void slotFolderNameChanged( const QString & _text);
+
+ private:
+ KMFolder* mFolder;
+};
+
+} // namespace
+#endif // NEW_FOLDER_DIALOG_H
diff --git a/kmail/objecttreeparser.cpp b/kmail/objecttreeparser.cpp
new file mode 100644
index 00000000..6f4bd465
--- /dev/null
+++ b/kmail/objecttreeparser.cpp
@@ -0,0 +1,2872 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ objecttreeparser.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2002-2004 Klarälvdalens Datakonsult AB
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include <config.h>
+
+// my header file
+#include "objecttreeparser.h"
+
+// other KMail headers
+#include "kmkernel.h"
+#include "kmreaderwin.h"
+#include "partNode.h"
+#include <libkdepim/kfileio.h>
+#include <libemailfunctions/email.h>
+#include "partmetadata.h"
+#include "attachmentstrategy.h"
+#include "interfaces/htmlwriter.h"
+#include "htmlstatusbar.h"
+#include "csshelper.h"
+#include "bodypartformatter.h"
+#include "bodypartformatterfactory.h"
+#include "partnodebodypart.h"
+#include "interfaces/bodypartformatter.h"
+#include "globalsettings.h"
+#include "util.h"
+
+// other module headers
+#include <mimelib/enum.h>
+#include <mimelib/bodypart.h>
+#include <mimelib/string.h>
+#include <mimelib/text.h>
+
+#include <kleo/specialjob.h>
+#include <kleo/cryptobackendfactory.h>
+#include <kleo/decryptverifyjob.h>
+#include <kleo/verifydetachedjob.h>
+#include <kleo/verifyopaquejob.h>
+#include <kleo/keylistjob.h>
+#include <kleo/importjob.h>
+#include <kleo/dn.h>
+
+#include <gpgmepp/importresult.h>
+#include <gpgmepp/decryptionresult.h>
+#include <gpgmepp/key.h>
+#include <gpgmepp/keylistresult.h>
+#include <gpgme.h>
+
+#include <kpgpblock.h>
+#include <kpgp.h>
+#include <linklocator.h>
+
+#include <ktnef/ktnefparser.h>
+#include <ktnef/ktnefmessage.h>
+#include <ktnef/ktnefattach.h>
+
+// other KDE headers
+#include <kdebug.h>
+#include <klocale.h>
+#include <kmimetype.h>
+#include <kglobal.h>
+#include <khtml_part.h>
+#include <ktempfile.h>
+#include <kstandarddirs.h>
+#include <kapplication.h>
+#include <kmessagebox.h>
+#include <kiconloader.h>
+#include <kmdcodec.h>
+
+// other Qt headers
+#include <qtextcodec.h>
+#include <qdir.h>
+#include <qfile.h>
+#include <qapplication.h>
+#include <kstyle.h>
+#include <qbuffer.h>
+#include <qpixmap.h>
+#include <qpainter.h>
+#include <qregexp.h>
+
+// other headers
+#include <memory>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include "chiasmuskeyselector.h"
+
+namespace KMail {
+
+ // A small class that eases temporary CryptPlugWrapper changes:
+ class ObjectTreeParser::CryptoProtocolSaver {
+ ObjectTreeParser * otp;
+ const Kleo::CryptoBackend::Protocol * protocol;
+ public:
+ CryptoProtocolSaver( ObjectTreeParser * _otp, const Kleo::CryptoBackend::Protocol* _w )
+ : otp( _otp ), protocol( _otp ? _otp->cryptoProtocol() : 0 )
+ {
+ if ( otp )
+ otp->setCryptoProtocol( _w );
+ }
+
+ ~CryptoProtocolSaver() {
+ if ( otp )
+ otp->setCryptoProtocol( protocol );
+ }
+ };
+
+
+ ObjectTreeParser::ObjectTreeParser( KMReaderWin * reader, const Kleo::CryptoBackend::Protocol * protocol,
+ bool showOnlyOneMimePart, bool keepEncryptions,
+ bool includeSignatures,
+ const AttachmentStrategy * strategy,
+ HtmlWriter * htmlWriter,
+ CSSHelper * cssHelper )
+ : mReader( reader ),
+ mCryptoProtocol( protocol ),
+ mShowOnlyOneMimePart( showOnlyOneMimePart ),
+ mKeepEncryptions( keepEncryptions ),
+ mIncludeSignatures( includeSignatures ),
+ mAttachmentStrategy( strategy ),
+ mHtmlWriter( htmlWriter ),
+ mCSSHelper( cssHelper )
+ {
+ if ( !attachmentStrategy() )
+ mAttachmentStrategy = reader ? reader->attachmentStrategy()
+ : AttachmentStrategy::smart();
+ if ( reader && !this->htmlWriter() )
+ mHtmlWriter = reader->htmlWriter();
+ if ( reader && !this->cssHelper() )
+ mCSSHelper = reader->mCSSHelper;
+ }
+
+ ObjectTreeParser::ObjectTreeParser( const ObjectTreeParser & other )
+ : mReader( other.mReader ),
+ mCryptoProtocol( other.cryptoProtocol() ),
+ mShowOnlyOneMimePart( other.showOnlyOneMimePart() ),
+ mKeepEncryptions( other.keepEncryptions() ),
+ mIncludeSignatures( other.includeSignatures() ),
+ mAttachmentStrategy( other.attachmentStrategy() ),
+ mHtmlWriter( other.htmlWriter() ),
+ mCSSHelper( other.cssHelper() )
+ {
+
+ }
+
+ ObjectTreeParser::~ObjectTreeParser() {}
+
+ void ObjectTreeParser::insertAndParseNewChildNode( partNode& startNode,
+ const char* content,
+ const char* cntDesc,
+ bool append )
+ {
+ DwBodyPart* myBody = new DwBodyPart( DwString( content ), 0 );
+ myBody->Parse();
+
+ if ( ( !myBody->Body().FirstBodyPart() ||
+ myBody->Body().AsString().length() == 0 ) &&
+ startNode.dwPart() &&
+ startNode.dwPart()->Body().Message() &&
+ startNode.dwPart()->Body().Message()->Body().FirstBodyPart() )
+ {
+ // if encapsulated imap messages are loaded the content-string is not complete
+ // so we need to keep the child dwparts
+ myBody = new DwBodyPart( *(startNode.dwPart()->Body().Message()) );
+ }
+
+ if ( myBody->hasHeaders() ) {
+ DwText& desc = myBody->Headers().ContentDescription();
+ desc.FromString( cntDesc );
+ desc.SetModified();
+ myBody->Headers().Parse();
+ }
+
+ partNode* parentNode = &startNode;
+ partNode* newNode = new partNode(false, myBody);
+ if ( append && parentNode->firstChild() ) {
+ parentNode = parentNode->firstChild();
+ while( parentNode->nextSibling() )
+ parentNode = parentNode->nextSibling();
+ parentNode->setNext( newNode );
+ } else
+ parentNode->setFirstChild( newNode );
+
+ newNode->buildObjectTree( false );
+
+ if ( startNode.mimePartTreeItem() ) {
+ kdDebug(5006) << "\n -----> Inserting items into MimePartTree\n" << endl;
+ newNode->fillMimePartTree( startNode.mimePartTreeItem(), 0,
+ QString::null, QString::null, QString::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;
+ }
+
+
+//-----------------------------------------------------------------------------
+
+ void ObjectTreeParser::parseObjectTree( partNode * node ) {
+ kdDebug(5006) << "ObjectTreeParser::parseObjectTree( "
+ << (node ? "node OK, " : "no node, ")
+ << "showOnlyOneMimePart: " << (showOnlyOneMimePart() ? "TRUE" : "FALSE")
+ << " )" << endl;
+
+ if ( !node )
+ return;
+
+ // reset "processed" flags for...
+ if ( showOnlyOneMimePart() ) {
+ // ... this node and all descendants
+ node->setProcessed( false, false );
+ if ( partNode * child = node->firstChild() )
+ child->setProcessed( false, true );
+ } else if ( mReader && !node->parentNode() ) {
+ // ...this node and all it's siblings and descendants
+ node->setProcessed( false, true );
+ }
+
+ for ( ; node ; node = node->nextSibling() ) {
+ if ( node->processed() )
+ continue;
+
+ ProcessResult processResult;
+
+ if ( mReader )
+ htmlWriter()->queue( QString::fromLatin1("<a name=\"att%1\"/>").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
+ ;
+ }
+ } else {
+ const BodyPartFormatter * bpf
+ = BodyPartFormatter::createFor( node->type(), node->subType() );
+ kdFatal( !bpf, 5006 ) << "THIS SHOULD NO LONGER HAPPEN ("
+ << node->typeString() << '/' << node->subTypeString()
+ << ')' << endl;
+
+ if ( bpf && !bpf->process( this, node, processResult ) )
+ defaultHandling( node, processResult );
+ }
+ node->setProcessed( true, false );
+
+ // adjust signed/encrypted flags if inline PGP was found
+ processResult.adjustCryptoStatesOfNode( node );
+
+ if ( showOnlyOneMimePart() )
+ break;
+ }
+ }
+
+ void ObjectTreeParser::defaultHandling( partNode * node, ProcessResult & result ) {
+ // ### (mmutz) default handling should go into the respective
+ // ### bodypartformatters.
+ if ( !mReader )
+ return;
+ if ( attachmentStrategy() == AttachmentStrategy::hidden() &&
+ !showOnlyOneMimePart() &&
+ node->parentNode() /* message is not an attachment */ )
+ return;
+
+ bool asIcon = true;
+ if ( showOnlyOneMimePart() )
+ // ### (mmutz) this is wrong! If I click on an image part, I
+ // want the equivalent of "view...", except for the extra
+ // window!
+ asIcon = !node->hasContentDispositionInline();
+ else if ( !result.neverDisplayInline() )
+ if ( const AttachmentStrategy * as = attachmentStrategy() )
+ asIcon = as->defaultDisplay( node ) == AttachmentStrategy::AsIcon;
+ // neither image nor text -> show as icon
+ 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() )
+ writePartIcon( &node->msgPart(), node->nodeId() );
+ } else if ( result.isImage() )
+ writePartIcon( &node->msgPart(), node->nodeId(), true );
+ else
+ writeBodyString( node->msgPart().bodyDecoded(),
+ node->trueFromAddress(),
+ codecFor( node ), result, false );
+ // end of ###
+ }
+
+ void ProcessResult::adjustCryptoStatesOfNode( partNode * node ) const {
+ if ( ( inlineSignatureState() != KMMsgNotSigned ) ||
+ ( inlineEncryptionState() != KMMsgNotEncrypted ) ) {
+ node->setSignatureState( inlineSignatureState() );
+ node->setEncryptionState( inlineEncryptionState() );
+ }
+ }
+
+ //////////////////
+ //////////////////
+ //////////////////
+
+ static int signatureToStatus( const GpgME::Signature &sig )
+ {
+ switch ( sig.status().code() ) {
+ case GPG_ERR_NO_ERROR:
+ return GPGME_SIG_STAT_GOOD;
+ case GPG_ERR_BAD_SIGNATURE:
+ return GPGME_SIG_STAT_BAD;
+ case GPG_ERR_NO_PUBKEY:
+ return GPGME_SIG_STAT_NOKEY;
+ case GPG_ERR_NO_DATA:
+ return GPGME_SIG_STAT_NOSIG;
+ case GPG_ERR_SIG_EXPIRED:
+ return GPGME_SIG_STAT_GOOD_EXP;
+ case GPG_ERR_KEY_EXPIRED:
+ return GPGME_SIG_STAT_GOOD_EXPKEY;
+ default:
+ return GPGME_SIG_STAT_ERROR;
+ }
+ }
+
+ bool ObjectTreeParser::writeOpaqueOrMultipartSignedData( partNode* data,
+ partNode& sign,
+ const QString& fromAddress,
+ bool doCheck,
+ QCString* cleartextData,
+ std::vector<GpgME::Signature> paramSignatures,
+ bool hideErrors )
+ {
+ bool bIsOpaqueSigned = false;
+ enum { NO_PLUGIN, NOT_INITIALIZED, CANT_VERIFY_SIGNATURES }
+ cryptPlugError = NO_PLUGIN;
+
+ const Kleo::CryptoBackend::Protocol* cryptProto = cryptoProtocol();
+
+ QString cryptPlugLibName;
+ QString cryptPlugDisplayName;
+ if ( cryptProto ) {
+ cryptPlugLibName = cryptProto->name();
+ cryptPlugDisplayName = cryptProto->displayName();
+ }
+
+#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;
+#endif
+
+ if ( doCheck && cryptProto ) {
+ kdDebug(5006) << "ObjectTreeParser::writeOpaqueOrMultipartSignedData: going to call CRYPTPLUG "
+ << cryptPlugLibName << endl;
+ }
+
+ QCString cleartext;
+ QByteArray signaturetext;
+
+ if ( doCheck && cryptProto ) {
+ if ( data ) {
+ cleartext = KMail::Util::CString( data->dwPart()->AsString() );
+
+ dumpToFile( "dat_01_reader_signedtext_before_canonicalization",
+ cleartext.data(), cleartext.length() );
+
+ // 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;
+ cleartext = Util::lf2crlf( cleartext );
+ kdDebug(5006) << " done." << endl;
+ }
+
+ dumpToFile( "dat_02_reader_signedtext_after_canonicalization",
+ cleartext.data(), cleartext.length() );
+
+ signaturetext = sign.msgPart().bodyDecodedBinary();
+ dumpToFile( "dat_03_reader.sig", signaturetext.data(),
+ signaturetext.size() );
+ }
+
+ std::vector<GpgME::Signature> signatures;
+ if ( doCheck )
+ signatures = paramSignatures;
+
+ PartMetaData messagePart;
+ messagePart.isSigned = true;
+ messagePart.technicalProblem = ( cryptProto == 0 );
+ messagePart.isGoodSignature = false;
+ messagePart.isEncrypted = false;
+ messagePart.isDecryptable = false;
+ messagePart.keyTrust = Kpgp::KPGP_VALIDITY_UNKNOWN;
+ messagePart.status = i18n("Wrong Crypto Plug-In.");
+ messagePart.status_code = GPGME_SIG_STAT_NONE;
+
+ if ( doCheck && cryptProto ) {
+ GpgME::VerificationResult result;
+ if ( data ) { // detached
+ if ( Kleo::VerifyDetachedJob * const job = cryptProto->verifyDetachedJob() ) {
+ QByteArray plainData = cleartext;
+ plainData.resize( cleartext.size() - 1 );
+ result = job->exec( signaturetext, plainData );
+ messagePart.auditLog = job->auditLogAsHtml();
+ } else {
+ cryptPlugError = CANT_VERIFY_SIGNATURES;
+ }
+ } else { // opaque
+ if ( Kleo::VerifyOpaqueJob * const job = cryptProto->verifyOpaqueJob() ) {
+ QByteArray plainData;
+ result = job->exec( signaturetext, plainData );
+ cleartext = QCString( plainData.data(), plainData.size() + 1 );
+ messagePart.auditLog = job->auditLogAsHtml();
+ } else {
+ cryptPlugError = CANT_VERIFY_SIGNATURES;
+ }
+ }
+ signatures = result.signatures();
+ }
+
+ 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;
+ GpgME::Signature signature = signatures[0];
+
+ messagePart.status_code = signatureToStatus( signature );
+ messagePart.status = QString::fromUtf8( signature.status().asString() );
+ for ( uint i = 1; i < signatures.size(); ++i ) {
+ if ( signatureToStatus( signatures[i] ) != messagePart.status_code ) {
+ messagePart.status_code = GPGME_SIG_STAT_DIFF;
+ messagePart.status = i18n("Different results for signatures");
+ }
+ }
+ if ( messagePart.status_code & GPGME_SIG_STAT_GOOD )
+ messagePart.isGoodSignature = true;
+
+ // get key for this signature
+ Kleo::KeyListJob *job = cryptProto->keyListJob();
+ std::vector<GpgME::Key> keys;
+ GpgME::KeyListResult keyListRes = job->exec( QString::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();
+
+ if ( key.keyID() )
+ messagePart.keyId = key.keyID();
+ if ( messagePart.keyId.isEmpty() )
+ messagePart.keyId = signature.fingerprint();
+ // ### Ugh. We depend on two enums being in sync:
+ messagePart.keyTrust = (Kpgp::Validity)signature.validity();
+ if ( key.numUserIDs() > 0 && key.userID( 0 ).id() )
+ messagePart.signer = Kleo::DN( key.userID( 0 ).id() ).prettyDN();
+ for ( uint iMail = 0; iMail < key.numUserIDs(); ++iMail ) {
+ // The following if /should/ always result in TRUE but we
+ // won't trust implicitely the plugin that gave us these data.
+ if ( key.userID( iMail ).email() ) {
+ QString email = QString::fromUtf8( key.userID( iMail ).email() );
+ // ### work around gpgme 0.3.x / cryptplug bug where the
+ // ### email addresses are specified as angle-addr, not addr-spec:
+ if ( email.startsWith( "<" ) && email.endsWith( ">" ) )
+ email = email.mid( 1, email.length() - 2 );
+ if ( !email.isEmpty() )
+ messagePart.signerMailAddresses.append( email );
+ }
+ }
+
+ if ( signature.creationTime() )
+ messagePart.creationTime.setTime_t( signature.creationTime() );
+ else
+ messagePart.creationTime = QDateTime();
+ if ( messagePart.signer.isEmpty() ) {
+ if ( key.numUserIDs() > 0 && key.userID( 0 ).name() )
+ messagePart.signer = Kleo::DN( key.userID( 0 ).name() ).prettyDN();
+ if ( !messagePart.signerMailAddresses.empty() ) {
+ if ( messagePart.signer.isEmpty() )
+ messagePart.signer = messagePart.signerMailAddresses.front();
+ else
+ messagePart.signer += " <" + messagePart.signerMailAddresses.front() + '>';
+ }
+ }
+
+ kdDebug(5006) << "\n key id: " << messagePart.keyId
+ << "\n key trust: " << messagePart.keyTrust
+ << "\n signer: " << messagePart.signer << endl;
+
+ } else {
+ messagePart.creationTime = QDateTime();
+ }
+
+ if ( !doCheck || !data ){
+ if ( cleartextData || !cleartext.isEmpty() ) {
+ if ( mReader )
+ htmlWriter()->queue( writeSigstatHeader( messagePart,
+ cryptProto,
+ fromAddress ) );
+ bIsOpaqueSigned = true;
+
+ CryptoProtocolSaver cpws( this, cryptProto );
+ insertAndParseNewChildNode( sign, doCheck ? cleartext.data() : cleartextData->data(),
+ "opaqued signed data" );
+
+ if ( mReader )
+ htmlWriter()->queue( writeSigstatFooter( messagePart ) );
+
+ }
+ else if ( !hideErrors ) {
+ QString txt;
+ txt = "<hr><b><h2>";
+ txt.append( i18n( "The crypto engine returned no cleartext data." ) );
+ txt.append( "</h2></b>" );
+ txt.append( "<br>&nbsp;<br>" );
+ txt.append( i18n( "Status: " ) );
+ if ( !messagePart.status.isEmpty() ) {
+ txt.append( "<i>" );
+ txt.append( messagePart.status );
+ txt.append( "</i>" );
+ }
+ else
+ txt.append( i18n("(unknown)") );
+ if ( mReader )
+ htmlWriter()->queue(txt);
+ }
+ }
+ else {
+ if ( mReader ) {
+ if ( !cryptProto ) {
+ QString errorMsg;
+ switch ( cryptPlugError ) {
+ case NOT_INITIALIZED:
+ errorMsg = i18n( "Crypto plug-in \"%1\" is not initialized." )
+ .arg( cryptPlugLibName );
+ break;
+ case CANT_VERIFY_SIGNATURES:
+ errorMsg = i18n( "Crypto plug-in \"%1\" cannot verify signatures." )
+ .arg( cryptPlugLibName );
+ break;
+ case NO_PLUGIN:
+ if ( cryptPlugDisplayName.isEmpty() )
+ errorMsg = i18n( "No appropriate crypto plug-in was found." );
+ else
+ errorMsg = i18n( "%1 is either 'OpenPGP' or 'S/MIME'",
+ "No %1 plug-in was found." )
+ .arg( cryptPlugDisplayName );
+ break;
+ }
+ messagePart.errorText = i18n( "The message is signed, but the "
+ "validity of the signature cannot be "
+ "verified.<br />"
+ "Reason: %1" )
+ .arg( errorMsg );
+ }
+
+ if ( mReader )
+ htmlWriter()->queue( writeSigstatHeader( messagePart,
+ cryptProto,
+ fromAddress ) );
+ }
+
+ ObjectTreeParser otp( mReader, cryptProto, true );
+ otp.parseObjectTree( data );
+ mRawReplyString += otp.rawReplyString();
+ mTextualContent += otp.textualContent();
+ if ( !otp.textualContentCharset().isEmpty() )
+ mTextualContentCharset = otp.textualContentCharset();
+
+ if ( mReader )
+ htmlWriter()->queue( writeSigstatFooter( messagePart ) );
+ }
+
+ kdDebug(5006) << "\nObjectTreeParser::writeOpaqueOrMultipartSignedData: done, returning "
+ << ( bIsOpaqueSigned ? "TRUE" : "FALSE" ) << endl;
+ return bIsOpaqueSigned;
+ }
+
+
+bool ObjectTreeParser::okDecryptMIME( partNode& data,
+ QCString& decryptedData,
+ bool& signatureFound,
+ std::vector<GpgME::Signature> &signatures,
+ bool showWarning,
+ bool& passphraseError,
+ bool& actuallyEncrypted,
+ QString& aErrorText,
+ QString& auditLog )
+{
+ passphraseError = false;
+ aErrorText = QString::null;
+ auditLog = QString::null;
+ bool bDecryptionOk = false;
+ enum { NO_PLUGIN, NOT_INITIALIZED, CANT_DECRYPT }
+ cryptPlugError = NO_PLUGIN;
+
+ const Kleo::CryptoBackend::Protocol* cryptProto = cryptoProtocol();
+
+ QString cryptPlugLibName;
+ if ( cryptProto )
+ cryptPlugLibName = cryptProto->name();
+
+ if ( mReader && !mReader->decryptMessage() ) {
+ QString iconName = KGlobal::instance()->iconLoader()->iconPath( "decrypted", KIcon::Small );
+ decryptedData = "<div style=\"font-size:large; text-align:center;"
+ "padding-top:20pt;\">"
+ + i18n("This message is encrypted.").utf8()
+ + "</div>"
+ "<div style=\"text-align:center; padding-bottom:20pt;\">"
+ "<a href=\"kmail:decryptMessage\">"
+ "<img src=\"" + iconName.utf8() + "\"/>"
+ + i18n("Decrypt Message").utf8()
+ + "</a></div>";
+ return false;
+ }
+
+ if ( cryptProto && !kmkernel->contextMenuShown() ) {
+ QByteArray ciphertext( data.msgPart().bodyDecodedBinary() );
+#ifdef MARCS_DEBUG
+ QCString cipherStr( ciphertext.data(), ciphertext.size() + 1 );
+ bool cipherIsBinary = (-1 == cipherStr.find("BEGIN ENCRYPTED MESSAGE", 0, false) ) &&
+ (-1 == cipherStr.find("BEGIN PGP ENCRYPTED MESSAGE", 0, false) ) &&
+ (-1 == cipherStr.find("BEGIN PGP MESSAGE", 0, false) );
+
+ dumpToFile( "dat_04_reader.encrypted", ciphertext.data(), ciphertext.size() );
+
+ QCString deb;
+ deb = "\n\nE N C R Y P T E D D A T A = ";
+ if ( cipherIsBinary )
+ deb += "[binary data]";
+ else {
+ deb += "\"";
+ deb += cipherStr;
+ deb += "\"";
+ }
+ deb += "\n\n";
+ kdDebug(5006) << deb << endl;
+#endif
+
+
+ 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 {
+ QByteArray plainText;
+ const std::pair<GpgME::DecryptionResult,GpgME::VerificationResult> res = job->exec( ciphertext, plainText );
+ const GpgME::DecryptionResult & decryptResult = res.first;
+ const GpgME::VerificationResult & verifyResult = res.second;
+ signatureFound = verifyResult.signatures().size() > 0;
+ signatures = verifyResult.signatures();
+ bDecryptionOk = !decryptResult.error();
+ passphraseError = decryptResult.error().isCanceled()
+ || decryptResult.error().code() == GPG_ERR_NO_SECKEY;
+ actuallyEncrypted = decryptResult.error().code() != GPG_ERR_NO_DATA;
+ aErrorText = QString::fromLocal8Bit( decryptResult.error().asString() );
+ auditLog = job->auditLogAsHtml();
+
+ kdDebug(5006) << "ObjectTreeParser::decryptMIME: returned from CRYPTPLUG"
+ << endl;
+ if ( bDecryptionOk )
+ decryptedData = QCString( plainText.data(), plainText.size() + 1 );
+ else if ( mReader && showWarning ) {
+ decryptedData = "<div style=\"font-size:x-large; text-align:center;"
+ "padding:20pt;\">"
+ + i18n("Encrypted data not shown.").utf8()
+ + "</div>";
+ if ( !passphraseError )
+ aErrorText = i18n("Crypto plug-in \"%1\" could not decrypt the data.")
+ .arg( cryptPlugLibName )
+ + "<br />"
+ + i18n("Error: %1").arg( aErrorText );
+ }
+ }
+ }
+
+ if ( !cryptProto ) {
+ decryptedData = "<div style=\"text-align:center; padding:20pt;\">"
+ + i18n("Encrypted data not shown.").utf8()
+ + "</div>";
+ switch ( cryptPlugError ) {
+ case NOT_INITIALIZED:
+ aErrorText = i18n( "Crypto plug-in \"%1\" is not initialized." )
+ .arg( cryptPlugLibName );
+ break;
+ case CANT_DECRYPT:
+ aErrorText = i18n( "Crypto plug-in \"%1\" cannot decrypt messages." )
+ .arg( cryptPlugLibName );
+ break;
+ case NO_PLUGIN:
+ aErrorText = i18n( "No appropriate crypto plug-in was found." );
+ break;
+ }
+ } else if ( kmkernel->contextMenuShown() ) {
+ // ### Workaround for bug 56693 (kmail freeze with the complete desktop
+ // ### while pinentry-qt appears)
+ QByteArray ciphertext( data.msgPart().bodyDecodedBinary() );
+ QCString cipherStr( ciphertext.data(), ciphertext.size() + 1 );
+ bool cipherIsBinary = (-1 == cipherStr.find("BEGIN ENCRYPTED MESSAGE", 0, false) ) &&
+ (-1 == cipherStr.find("BEGIN PGP ENCRYPTED MESSAGE", 0, false) ) &&
+ (-1 == cipherStr.find("BEGIN PGP MESSAGE", 0, false) );
+ if ( !cipherIsBinary ) {
+ decryptedData = cipherStr;
+ }
+ else {
+ decryptedData = "<div style=\"font-size:x-large; text-align:center;"
+ "padding:20pt;\">"
+ + i18n("Encrypted data not shown.").utf8()
+ + "</div>";
+ }
+ }
+
+ dumpToFile( "dat_05_reader.decrypted", decryptedData.data(), decryptedData.size() );
+
+ return bDecryptionOk;
+}
+
+ //static
+ bool ObjectTreeParser::containsExternalReferences( const QCString & str )
+ {
+ QRegExp httpRegExp("(\\\"|\\\'|url\\s*\\(\\s*)http[s]?:");
+ int httpPos = str.find( httpRegExp, 0 );
+
+ while ( httpPos >= 0 ) {
+ // look backwards for "href"
+ if ( httpPos > 5 ) {
+ int hrefPos = str.findRev( "href", httpPos - 5, true );
+ // if no 'href' is found or the distance between 'href' and '"http[s]:'
+ // is larger than 7 (7 is the distance in 'href = "http[s]:') then
+ // we assume that we have found an external reference
+ if ( ( hrefPos == -1 ) || ( httpPos - hrefPos > 7 ) )
+ return true;
+ }
+ // find next occurrence of "http: or "https:
+ httpPos = str.find( httpRegExp, httpPos + 6 );
+ }
+ return false;
+ }
+
+ bool ObjectTreeParser::processTextHtmlSubtype( partNode * curNode, ProcessResult & ) {
+ QCString cstr( curNode->msgPart().bodyDecoded() );
+
+ mRawReplyString = cstr;
+ if ( curNode->isFirstTextPart() ) {
+ mTextualContent += curNode->msgPart().bodyToUnicode();
+ mTextualContentCharset = curNode->msgPart().charset();
+ }
+
+ if ( !mReader )
+ return true;
+
+ if ( curNode->isFirstTextPart() ||
+ attachmentStrategy()->defaultDisplay( curNode ) == AttachmentStrategy::Inline ||
+ showOnlyOneMimePart() )
+ {
+ if ( mReader->htmlMail() ) {
+ // ---Sven's strip </BODY> and </HTML> 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 </BODY> and
+ // put \0 there.
+ int i = cstr.findRev("</body>", -1, false); //case insensitive
+ if ( 0 <= i )
+ cstr.truncate(i);
+ else // just in case - search for </html>
+ {
+ i = cstr.findRev("</html>", -1, false); //case insensitive
+ if ( 0 <= i ) cstr.truncate(i);
+ }
+ // ---Sven's strip </BODY> and </HTML> from end of attachment end-
+ // Show the "external references" warning (with possibility to load
+ // external references only if loading external references is disabled
+ // and the HTML code contains obvious external references). For
+ // messages where the external references are obfuscated the user won't
+ // have an easy way to load them but that shouldn't be a problem
+ // because only spam contains obfuscated external references.
+ if ( !mReader->htmlLoadExternal() &&
+ containsExternalReferences( cstr ) ) {
+ htmlWriter()->queue( "<div class=\"htmlWarn\">\n" );
+ htmlWriter()->queue( i18n("<b>Note:</b> This HTML message may contain external "
+ "references to images etc. For security/privacy reasons "
+ "external references are not loaded. If you trust the "
+ "sender of this message then you can load the external "
+ "references for this message "
+ "<a href=\"kmail:loadExternal\">by clicking here</a>.") );
+ htmlWriter()->queue( "</div><br><br>" );
+ }
+ } else {
+ htmlWriter()->queue( "<div class=\"htmlWarn\">\n" );
+ htmlWriter()->queue( i18n("<b>Note:</b> This is an HTML message. For "
+ "security reasons, only the raw HTML code "
+ "is shown. If you trust the sender of this "
+ "message then you can activate formatted "
+ "HTML display for this message "
+ "<a href=\"kmail:showHTML\">by clicking here</a>.") );
+ htmlWriter()->queue( "</div><br><br>" );
+ }
+ htmlWriter()->queue( codecFor( curNode )->toUnicode( mReader->htmlMail() ? cstr : KMMessage::html2source( cstr )));
+ mReader->mColorBar->setHtmlMode();
+ return true;
+ }
+ return false;
+ }
+} // namespace KMail
+
+static bool isMailmanMessage( partNode * curNode ) {
+ if ( !curNode->dwPart() || !curNode->dwPart()->hasHeaders() )
+ return false;
+ DwHeaders & headers = curNode->dwPart()->Headers();
+ if ( headers.HasField("X-Mailman-Version") )
+ return true;
+ if ( headers.HasField("X-Mailer") &&
+ 0 == QCString( headers.FieldBody("X-Mailer").AsString().c_str() )
+ .find("MAILMAN", 0, false) )
+ return true;
+ return false;
+}
+
+namespace KMail {
+
+ bool ObjectTreeParser::processMailmanMessage( partNode * curNode ) {
+ const QCString cstr = curNode->msgPart().bodyDecoded();
+
+ //###
+ const QCString delim1( "--__--__--\n\nMessage:");
+ const QCString delim2( "--__--__--\r\n\r\nMessage:");
+ const QCString delimZ2("--__--__--\n\n_____________");
+ const QCString delimZ1("--__--__--\r\n\r\n_____________");
+ QCString partStr, digestHeaderStr;
+ int thisDelim = cstr.find(delim1, 0, false);
+ if ( thisDelim == -1 )
+ thisDelim = cstr.find(delim2, 0, false);
+ if ( thisDelim == -1 ) {
+ kdDebug(5006) << " Sorry: Old style Mailman message but no delimiter found." << endl;
+ return false;
+ }
+
+ int nextDelim = cstr.find(delim1, thisDelim+1, false);
+ if ( -1 == nextDelim )
+ nextDelim = cstr.find(delim2, thisDelim+1, false);
+ if ( -1 == nextDelim )
+ nextDelim = cstr.find(delimZ1, thisDelim+1, false);
+ if ( -1 == nextDelim )
+ nextDelim = cstr.find(delimZ2, thisDelim+1, false);
+ if ( nextDelim < 0)
+ return false;
+
+ kdDebug(5006) << " processing old style Mailman digest" << endl;
+ //if ( curNode->mRoot )
+ // curNode = curNode->mRoot;
+
+ // at least one message found: build a mime tree
+ digestHeaderStr = "Content-Type=text/plain\nContent-Description=digest header\n\n";
+ digestHeaderStr += cstr.mid( 0, thisDelim );
+ insertAndParseNewChildNode( *curNode,
+ &*digestHeaderStr,
+ "Digest Header", true );
+ //mReader->queueHtml("<br><hr><br>");
+ // temporarily change curent node's Content-Type
+ // to get our embedded RfC822 messages properly inserted
+ curNode->setType( DwMime::kTypeMultipart );
+ curNode->setSubType( DwMime::kSubtypeDigest );
+ while( -1 < nextDelim ){
+ int thisEoL = cstr.find("\nMessage:", thisDelim, false);
+ if ( -1 < thisEoL )
+ thisDelim = thisEoL+1;
+ else{
+ thisEoL = cstr.find("\n_____________", thisDelim, false);
+ if ( -1 < thisEoL )
+ thisDelim = thisEoL+1;
+ }
+ thisEoL = cstr.find('\n', thisDelim);
+ if ( -1 < thisEoL )
+ thisDelim = thisEoL+1;
+ else
+ thisDelim = thisDelim+1;
+ //while( thisDelim < cstr.size() && '\n' == cstr[thisDelim] )
+ // ++thisDelim;
+
+ partStr = "Content-Type=message/rfc822\nContent-Description=embedded message\n";
+ partStr += cstr.mid( thisDelim, nextDelim-thisDelim );
+ QCString subject("embedded message");
+ QCString subSearch("\nSubject:");
+ int subPos = partStr.find(subSearch, 0, false);
+ if ( -1 < subPos ){
+ subject = partStr.mid(subPos+subSearch.length());
+ thisEoL = subject.find('\n');
+ if ( -1 < thisEoL )
+ subject.truncate( thisEoL );
+ }
+ kdDebug(5006) << " embedded message found: \"" << subject << "\"" << endl;
+ insertAndParseNewChildNode( *curNode,
+ &*partStr,
+ subject, true );
+ //mReader->queueHtml("<br><hr><br>");
+ thisDelim = nextDelim+1;
+ nextDelim = cstr.find(delim1, thisDelim, false);
+ if ( -1 == nextDelim )
+ nextDelim = cstr.find(delim2, thisDelim, false);
+ if ( -1 == nextDelim )
+ nextDelim = cstr.find(delimZ1, thisDelim, false);
+ if ( -1 == nextDelim )
+ nextDelim = cstr.find(delimZ2, thisDelim, false);
+ }
+ // reset curent node's Content-Type
+ curNode->setType( DwMime::kTypeText );
+ curNode->setSubType( DwMime::kSubtypePlain );
+ int thisEoL = cstr.find("_____________", thisDelim);
+ if ( -1 < thisEoL ){
+ thisDelim = thisEoL;
+ thisEoL = cstr.find('\n', thisDelim);
+ if ( -1 < thisEoL )
+ thisDelim = thisEoL+1;
+ }
+ else
+ thisDelim = thisDelim+1;
+ partStr = "Content-Type=text/plain\nContent-Description=digest footer\n\n";
+ partStr += cstr.mid( thisDelim );
+ insertAndParseNewChildNode( *curNode,
+ &*partStr,
+ "Digest Footer", true );
+ return true;
+ }
+
+ bool ObjectTreeParser::processTextPlainSubtype( partNode * curNode, ProcessResult & result ) {
+ if ( !mReader ) {
+ mRawReplyString = curNode->msgPart().bodyDecoded();
+ if ( curNode->isFirstTextPart() ) {
+ mTextualContent += curNode->msgPart().bodyToUnicode();
+ mTextualContentCharset = curNode->msgPart().charset();
+ }
+ return true;
+ }
+
+ if ( !curNode->isFirstTextPart() &&
+ attachmentStrategy()->defaultDisplay( curNode ) != AttachmentStrategy::Inline &&
+ !showOnlyOneMimePart() )
+ return false;
+
+ mRawReplyString = curNode->msgPart().bodyDecoded();
+ if ( curNode->isFirstTextPart() ) {
+ mTextualContent += curNode->msgPart().bodyToUnicode();
+ mTextualContentCharset = curNode->msgPart().charset();
+ }
+
+ QString label = curNode->msgPart().fileName().stripWhiteSpace();
+ if ( label.isEmpty() )
+ label = curNode->msgPart().name().stripWhiteSpace();
+
+ const bool bDrawFrame = !curNode->isFirstTextPart()
+ && !showOnlyOneMimePart()
+ && !label.isEmpty();
+ if ( bDrawFrame ) {
+ label = KMMessage::quoteHtmlChars( label, true );
+
+ const QString comment =
+ KMMessage::quoteHtmlChars( curNode->msgPart().contentDescription(), true );
+
+ const QString fileName =
+ mReader->writeMessagePartToTempFile( &curNode->msgPart(),
+ curNode->nodeId() );
+
+ const QString dir = QApplication::reverseLayout() ? "rtl" : "ltr" ;
+
+ QString htmlStr = "<table cellspacing=\"1\" class=\"textAtm\">"
+ "<tr class=\"textAtmH\"><td dir=\"" + dir + "\">";
+ if ( !fileName.isEmpty() )
+ htmlStr += "<a href=\"" + QString("file:")
+ + KURL::encode_string( fileName ) + "\">"
+ + label + "</a>";
+ else
+ htmlStr += label;
+ if ( !comment.isEmpty() )
+ htmlStr += "<br>" + comment;
+ htmlStr += "</td></tr><tr class=\"textAtmB\"><td>";
+
+ htmlWriter()->queue( htmlStr );
+ }
+ // process old style not-multipart Mailman messages to
+ // enable verification of the embedded messages' signatures
+ if ( !isMailmanMessage( curNode ) ||
+ !processMailmanMessage( curNode ) )
+ writeBodyString( mRawReplyString, curNode->trueFromAddress(),
+ codecFor( curNode ), result, !bDrawFrame );
+ if ( bDrawFrame )
+ htmlWriter()->queue( "</td></tr></table>" );
+
+ return true;
+ }
+
+ void ObjectTreeParser::stdChildHandling( partNode * child ) {
+ if ( !child )
+ return;
+
+ ObjectTreeParser otp( *this );
+ otp.setShowOnlyOneMimePart( false );
+ otp.parseObjectTree( child );
+ mRawReplyString += otp.rawReplyString();
+ mTextualContent += otp.textualContent();
+ if ( !otp.textualContentCharset().isEmpty() )
+ mTextualContentCharset = otp.textualContentCharset();
+ }
+
+ bool ObjectTreeParser::processMultiPartMixedSubtype( partNode * node, ProcessResult & ) {
+ partNode * child = node->firstChild();
+ if ( !child )
+ return false;
+
+ // normal treatment of the parts in the mp/mixed container
+ stdChildHandling( child );
+ return true;
+ }
+
+ bool ObjectTreeParser::processMultiPartAlternativeSubtype( partNode * node, ProcessResult & ) {
+ partNode * child = node->firstChild();
+ if ( !child )
+ return false;
+
+ partNode * dataHtml = child->findType( DwMime::kTypeText,
+ DwMime::kSubtypeHtml, false, true );
+ partNode * dataPlain = child->findType( DwMime::kTypeText,
+ DwMime::kSubtypePlain, false, true );
+
+ if ( (mReader && mReader->htmlMail() && dataHtml) ||
+ (dataHtml && dataPlain && dataPlain->msgPart().body().isEmpty()) ) {
+ if ( dataPlain )
+ dataPlain->setProcessed( true, false );
+ stdChildHandling( dataHtml );
+ return true;
+ }
+
+ if ( !mReader || (!mReader->htmlMail() && dataPlain) ) {
+ if ( dataHtml )
+ dataHtml->setProcessed( true, false );
+ stdChildHandling( dataPlain );
+ return true;
+ }
+
+ stdChildHandling( child );
+ return true;
+ }
+
+ bool ObjectTreeParser::processMultiPartDigestSubtype( partNode * node, ProcessResult & result ) {
+ return processMultiPartMixedSubtype( node, result );
+ }
+
+ bool ObjectTreeParser::processMultiPartParallelSubtype( partNode * node, ProcessResult & result ) {
+ return processMultiPartMixedSubtype( node, result );
+ }
+
+ bool ObjectTreeParser::processMultiPartSignedSubtype( partNode * node, ProcessResult & ) {
+ if ( node->childCount() != 2 ) {
+ kdDebug(5006) << "mulitpart/signed must have exactly two child parts!" << endl
+ << "processing as multipart/mixed" << endl;
+ if ( node->firstChild() )
+ stdChildHandling( node->firstChild() );
+ return node->firstChild();
+ }
+
+ partNode * signedData = node->firstChild();
+ assert( signedData );
+
+ partNode * signature = signedData->nextSibling();
+ assert( signature );
+
+ signature->setProcessed( true, true );
+
+ if ( !includeSignatures() ) {
+ stdChildHandling( signedData );
+ return true;
+ }
+
+ // FIXME(marc) check here that the protocol parameter matches the
+ // mimetype of "signature" (not required by the RFC, but practised
+ // by all implementaions of security multiparts
+
+ const QString contentType = node->contentTypeParameter( "protocol" ).lower();
+ const Kleo::CryptoBackend::Protocol *protocol = 0;
+ if ( contentType == "application/pkcs7-signature" || contentType == "application/x-pkcs7-signature" )
+ protocol = Kleo::CryptoBackendFactory::instance()->smime();
+ else if ( contentType == "application/pgp-signature" || contentType == "application/x-pgp-signature" )
+ protocol = Kleo::CryptoBackendFactory::instance()->openpgp();
+
+ if ( !protocol ) {
+ signature->setProcessed( true, true );
+ stdChildHandling( signedData );
+ return true;
+ }
+
+ CryptoProtocolSaver saver( this, protocol );
+
+ node->setSignatureState( KMMsgFullySigned );
+ writeOpaqueOrMultipartSignedData( signedData, *signature,
+ node->trueFromAddress() );
+ return true;
+ }
+
+ bool ObjectTreeParser::processMultiPartEncryptedSubtype( partNode * node, ProcessResult & result ) {
+ partNode * child = node->firstChild();
+ if ( !child )
+ return false;
+
+ if ( keepEncryptions() ) {
+ node->setEncryptionState( KMMsgFullyEncrypted );
+ const QCString cstr = node->msgPart().bodyDecoded();
+ if ( mReader )
+ writeBodyString( cstr, node->trueFromAddress(),
+ codecFor( node ), result, false );
+ mRawReplyString += cstr;
+ return true;
+ }
+
+ const Kleo::CryptoBackend::Protocol * useThisCryptProto = 0;
+
+ /*
+ 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 ) {
+ useThisCryptProto = Kleo::CryptoBackendFactory::instance()->openpgp();
+ }
+ if ( !data ) {
+ data = child->findType( DwMime::kTypeApplication,
+ DwMime::kSubtypePkcs7Mime, false, true );
+ if ( data ) {
+ useThisCryptProto = Kleo::CryptoBackendFactory::instance()->smime();
+ }
+ }
+ /*
+ ---------------------------------------------------------------------------------------------------------------
+ */
+
+ if ( !data ) {
+ stdChildHandling( child );
+ return true;
+ }
+
+ CryptoProtocolSaver cpws( this, useThisCryptProto );
+
+ if ( partNode * dataChild = data->firstChild() ) {
+ kdDebug(5006) << "\n-----> Calling parseObjectTree( curNode->mChild )\n" << endl;
+ stdChildHandling( dataChild );
+ 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 );
+ QCString decryptedData;
+ bool signatureFound;
+ std::vector<GpgME::Signature> signatures;
+ bool passphraseError;
+ bool actuallyEncrypted = true;
+
+ bool bOkDecrypt = okDecryptMIME( *data,
+ decryptedData,
+ signatureFound,
+ signatures,
+ true,
+ passphraseError,
+ actuallyEncrypted,
+ messagePart.errorText,
+ messagePart.auditLog );
+
+ // paint the frame
+ if ( mReader ) {
+ messagePart.isDecryptable = bOkDecrypt;
+ messagePart.isEncrypted = true;
+ messagePart.isSigned = false;
+ htmlWriter()->queue( writeSigstatHeader( messagePart,
+ cryptoProtocol(),
+ node->trueFromAddress() ) );
+ }
+
+ if ( bOkDecrypt ) {
+ // Note: Multipart/Encrypted might also be signed
+ // without encapsulating a nicely formatted
+ // ~~~~~~~ Multipart/Signed part.
+ // (see RFC 3156 --> 6.2)
+ // In this case we paint a _2nd_ frame inside the
+ // encryption frame, but we do _not_ show a respective
+ // encapsulated MIME part in the Mime Tree Viewer
+ // since we do want to show the _true_ structure of the
+ // message there - not the structure that the sender's
+ // MUA 'should' have sent. :-D (khz, 12.09.2002)
+ //
+ if ( signatureFound ) {
+ writeOpaqueOrMultipartSignedData( 0,
+ *node,
+ node->trueFromAddress(),
+ false,
+ &decryptedData,
+ signatures,
+ false );
+ node->setSignatureState( KMMsgFullySigned );
+ } else {
+ insertAndParseNewChildNode( *node,
+ &*decryptedData,
+ "encrypted data" );
+ }
+ } else {
+ mRawReplyString += decryptedData;
+ if ( mReader ) {
+ // print the error message that was returned in decryptedData
+ // (utf8-encoded)
+ htmlWriter()->queue( QString::fromUtf8( decryptedData.data() ) );
+ }
+ }
+
+ if ( mReader )
+ htmlWriter()->queue( writeSigstatFooter( messagePart ) );
+ data->setProcessed( true, false ); // Set the data node to done to prevent it from being processed
+ return true;
+ }
+
+
+ bool ObjectTreeParser::processMessageRfc822Subtype( partNode * node, ProcessResult & ) {
+ if ( mReader
+ && !attachmentStrategy()->inlineNestedMessages()
+ && !showOnlyOneMimePart() )
+ return false;
+
+ if ( partNode * child = node->firstChild() ) {
+ 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;
+ return true;
+ }
+ kdDebug(5006) << "\n-----> Initially processing data of embedded RfC 822 message\n" << endl;
+ // paint the frame
+ PartMetaData messagePart;
+ if ( mReader ) {
+ messagePart.isEncrypted = false;
+ messagePart.isSigned = false;
+ messagePart.isEncapsulatedRfc822Message = true;
+ QString filename =
+ mReader->writeMessagePartToTempFile( &node->msgPart(),
+ node->nodeId() );
+ htmlWriter()->queue( writeSigstatHeader( messagePart,
+ cryptoProtocol(),
+ node->trueFromAddress(),
+ filename ) );
+ }
+ QCString rfc822messageStr( node->msgPart().bodyDecoded() );
+ // display the headers of the encapsulated message
+ DwMessage* rfc822DwMessage = new DwMessage(); // will be deleted by c'tor of rfc822headers
+ rfc822DwMessage->FromString( rfc822messageStr );
+ rfc822DwMessage->Parse();
+ KMMessage rfc822message( rfc822DwMessage );
+ node->setFromAddress( rfc822message.from() );
+ 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" );
+ if ( mReader )
+ htmlWriter()->queue( writeSigstatFooter( messagePart ) );
+ return true;
+ }
+
+
+ bool ObjectTreeParser::processApplicationOctetStreamSubtype( partNode * node, ProcessResult & result ) {
+ if ( partNode * child = node->firstChild() ) {
+ 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;
+ return true;
+ }
+
+ const Kleo::CryptoBackend::Protocol* oldUseThisCryptPlug = cryptoProtocol();
+ if ( node->parentNode()
+ && DwMime::kTypeMultipart == node->parentNode()->type()
+ && DwMime::kSubtypeEncrypted == node->parentNode()->subType() ) {
+ kdDebug(5006) << "\n-----> Initially processing encrypted data\n" << endl;
+ node->setEncryptionState( KMMsgFullyEncrypted );
+ if ( keepEncryptions() ) {
+ const QCString cstr = node->msgPart().bodyDecoded();
+ if ( mReader )
+ writeBodyString( cstr, node->trueFromAddress(),
+ codecFor( node ), result, false );
+ mRawReplyString += cstr;
+ } else {
+ /*
+ ATTENTION: This code is to be replaced by the planned 'auto-detect' feature.
+ */
+ PartMetaData messagePart;
+ setCryptoProtocol( Kleo::CryptoBackendFactory::instance()->openpgp() );
+ QCString decryptedData;
+ bool signatureFound;
+ std::vector<GpgME::Signature> signatures;
+ bool passphraseError;
+ bool actuallyEncrypted = true;
+
+ bool bOkDecrypt = okDecryptMIME( *node,
+ decryptedData,
+ signatureFound,
+ signatures,
+ true,
+ passphraseError,
+ actuallyEncrypted,
+ messagePart.errorText,
+ messagePart.auditLog );
+
+ // paint the frame
+ if ( mReader ) {
+ messagePart.isDecryptable = bOkDecrypt;
+ messagePart.isEncrypted = true;
+ messagePart.isSigned = false;
+ htmlWriter()->queue( writeSigstatHeader( messagePart,
+ cryptoProtocol(),
+ node->trueFromAddress() ) );
+ }
+
+ if ( bOkDecrypt ) {
+ // fixing the missing attachments bug #1090-b
+ insertAndParseNewChildNode( *node,
+ &*decryptedData,
+ "encrypted data" );
+ } else {
+ mRawReplyString += decryptedData;
+ if ( mReader ) {
+ // print the error message that was returned in decryptedData
+ // (utf8-encoded)
+ htmlWriter()->queue( QString::fromUtf8( decryptedData.data() ) );
+ }
+ }
+
+ if ( mReader )
+ htmlWriter()->queue( writeSigstatFooter( messagePart ) );
+ }
+ return true;
+ }
+ setCryptoProtocol( oldUseThisCryptPlug );
+ return false;
+ }
+
+ bool ObjectTreeParser::processApplicationPkcs7MimeSubtype( partNode * node, ProcessResult & result ) {
+ if ( partNode * child = node->firstChild() ) {
+ 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;
+ return true;
+ }
+
+ kdDebug(5006) << "\n-----> Initially processing signed and/or encrypted data\n" << endl;
+ if ( !node->dwPart() || !node->dwPart()->hasHeaders() )
+ return false;
+
+ const Kleo::CryptoBackend::Protocol * smimeCrypto = Kleo::CryptoBackendFactory::instance()->smime();
+
+ const QString smimeType = node->contentTypeParameter("smime-type").lower();
+
+ if ( smimeType == "certs-only" ) {
+ result.setNeverDisplayInline( true );
+ if ( !smimeCrypto || !mReader )
+ return false;
+
+ const KConfigGroup reader( KMKernel::config(), "Reader" );
+ if ( !reader.readBoolEntry( "AutoImportKeys", false ) )
+ return false;
+
+ const QByteArray certData = node->msgPart().bodyDecodedBinary();
+
+ Kleo::ImportJob *import = smimeCrypto->importJob();
+ const GpgME::ImportResult res = import->exec( certData );
+ if ( res.error() ) {
+ htmlWriter()->queue( i18n( "Sorry, certificate could not be imported.<br>"
+ "Reason: %1").arg( QString::fromLocal8Bit( res.error().asString() ) ) );
+ return true;
+ }
+
+ const int nImp = res.numImported();
+ const int nUnc = res.numUnchanged();
+ const int nSKImp = res.numSecretKeysImported();
+ const int nSKUnc = res.numSecretKeysUnchanged();
+ if ( !nImp && !nSKImp && !nUnc && !nSKUnc ) {
+ htmlWriter()->queue( i18n( "Sorry, no certificates were found in this message." ) );
+ return true;
+ }
+ QString comment = "<b>" + i18n( "Certificate import status:" ) + "</b><br>&nbsp;<br>";
+ if ( nImp )
+ comment += i18n( "1 new certificate was imported.",
+ "%n new certificates were imported.", nImp ) + "<br>";
+ if ( nUnc )
+ comment += i18n( "1 certificate was unchanged.",
+ "%n certificates were unchanged.", nUnc ) + "<br>";
+ if ( nSKImp )
+ comment += i18n( "1 new secret key was imported.",
+ "%n new secret keys were imported.", nSKImp ) + "<br>";
+ if ( nSKUnc )
+ comment += i18n( "1 secret key was unchanged.",
+ "%n secret keys were unchanged.", nSKUnc ) + "<br>";
+ comment += "&nbsp;<br>";
+ htmlWriter()->queue( comment );
+ if ( !nImp && !nSKImp ) {
+ htmlWriter()->queue( "<hr>" );
+ return true;
+ }
+ const std::vector<GpgME::Import> imports = res.imports();
+ if ( imports.empty() ) {
+ htmlWriter()->queue( i18n( "Sorry, no details on certificate import available." ) + "<hr>" );
+ return true;
+ }
+ htmlWriter()->queue( "<b>" + i18n( "Certificate import details:" ) + "</b><br>" );
+ for ( std::vector<GpgME::Import>::const_iterator it = imports.begin() ; it != imports.end() ; ++it ) {
+ if ( (*it).error() )
+ htmlWriter()->queue( i18n( "Failed: %1 (%2)" )
+ .arg( (*it).fingerprint(),
+ QString::fromLocal8Bit( (*it).error().asString() ) ) );
+ 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
+ htmlWriter()->queue( i18n( "New or changed: %1" ).arg( (*it).fingerprint() ) );
+ htmlWriter()->queue( "<br>" );
+ }
+
+ htmlWriter()->queue( "<hr>" );
+ return true;
+ }
+
+ if ( !smimeCrypto )
+ return false;
+ CryptoProtocolSaver cpws( this, smimeCrypto );
+
+ bool isSigned = smimeType == "signed-data";
+ bool isEncrypted = smimeType == "enveloped-data";
+
+ // Analyze "signTestNode" node to find/verify a signature.
+ // If zero this verification was successfully done after
+ // decrypting via recursion by insertAndParseNewChildNode().
+ partNode* signTestNode = isEncrypted ? 0 : node;
+
+
+ // We try decrypting the content
+ // 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;
+ QCString decryptedData;
+ PartMetaData messagePart;
+ messagePart.isEncrypted = true;
+ messagePart.isSigned = false;
+ bool signatureFound;
+ std::vector<GpgME::Signature> signatures;
+ bool passphraseError;
+ bool actuallyEncrypted = true;
+
+ if ( okDecryptMIME( *node,
+ decryptedData,
+ signatureFound,
+ signatures,
+ false,
+ passphraseError,
+ actuallyEncrypted,
+ 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 ) ) {
+ 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() ) );
+ if ( mReader->decryptMessage() )
+ writePartIcon( &node->msgPart(), node->nodeId() );
+ else
+ htmlWriter()->queue( QString::fromUtf8( decryptedData ) );
+ htmlWriter()->queue( writeSigstatFooter( messagePart ) );
+ }
+ } else {
+ kdDebug(5006) << "pkcs7 mime - NO encryption found" << endl;
+ }
+ }
+ if ( isEncrypted )
+ node->setEncryptionState( KMMsgFullyEncrypted );
+ }
+
+ // 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;
+
+ bool sigFound = writeOpaqueOrMultipartSignedData( 0,
+ *signTestNode,
+ node->trueFromAddress(),
+ true,
+ 0,
+ std::vector<GpgME::Signature>(),
+ isEncrypted );
+ if ( sigFound ) {
+ if ( !isSigned ) {
+ 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;
+ }
+ }
+
+ return isSigned || isEncrypted;
+}
+
+bool ObjectTreeParser::decryptChiasmus( const QByteArray& data, QByteArray& bodyDecoded, QString& errorText )
+{
+ const Kleo::CryptoBackend::Protocol * chiasmus =
+ Kleo::CryptoBackendFactory::instance()->protocol( "Chiasmus" );
+ Q_ASSERT( chiasmus );
+ if ( !chiasmus )
+ return false;
+
+ const STD_NAMESPACE_PREFIX auto_ptr<Kleo::SpecialJob> listjob( chiasmus->specialJob( "x-obtain-keys", QMap<QString,QVariant>() ) );
+ if ( !listjob.get() ) {
+ errorText = i18n( "Chiasmus backend does not offer the "
+ "\"x-obtain-keys\" function. Please report this bug." );
+ return false;
+ }
+
+ if ( listjob->exec() ) {
+ errorText = i18n( "Chiasmus Backend Error" );
+ return false;
+ }
+
+ const QVariant result = listjob->property( "result" );
+ if ( result.type() != QVariant::StringList ) {
+ errorText = i18n( "Unexpected return value from Chiasmus backend: "
+ "The \"x-obtain-keys\" function did not return a "
+ "string list. Please report this bug." );
+ return false;
+ }
+
+ const QStringList keys = result.toStringList();
+ if ( keys.empty() ) {
+ errorText = i18n( "No keys have been found. Please check that a "
+ "valid key path has been set in the Chiasmus "
+ "configuration." );
+ return false;
+ }
+
+ emit mReader->noDrag();
+ ChiasmusKeySelector selectorDlg( mReader, i18n( "Chiasmus Decryption Key Selection" ),
+ keys, GlobalSettings::chiasmusDecryptionKey(),
+ GlobalSettings::chiasmusDecryptionOptions() );
+ if ( selectorDlg.exec() != QDialog::Accepted )
+ return false;
+
+ GlobalSettings::setChiasmusDecryptionOptions( selectorDlg.options() );
+ GlobalSettings::setChiasmusDecryptionKey( selectorDlg.key() );
+ assert( !GlobalSettings::chiasmusDecryptionKey().isEmpty() );
+
+ Kleo::SpecialJob * job = chiasmus->specialJob( "x-decrypt", QMap<QString,QVariant>() );
+ if ( !job ) {
+ errorText = i18n( "Chiasmus backend does not offer the "
+ "\"x-decrypt\" function. Please report this bug." );
+ return false;
+ }
+
+ if ( !job->setProperty( "key", GlobalSettings::chiasmusDecryptionKey() ) ||
+ !job->setProperty( "options", GlobalSettings::chiasmusDecryptionOptions() ) ||
+ !job->setProperty( "input", data ) ) {
+ errorText = i18n( "The \"x-decrypt\" function does not accept "
+ "the expected parameters. Please report this bug." );
+ return false;
+ }
+
+ if ( job->exec() ) {
+ errorText = i18n( "Chiasmus Decryption Error" );
+ return false;
+ }
+
+ const QVariant resultData = job->property( "result" );
+ if ( resultData.type() != QVariant::ByteArray ) {
+ errorText = i18n( "Unexpected return value from Chiasmus backend: "
+ "The \"x-decrypt\" function did not return a "
+ "byte array. Please report this bug." );
+ return false;
+ }
+ bodyDecoded = resultData.toByteArray();
+ return true;
+}
+
+bool ObjectTreeParser::processApplicationChiasmusTextSubtype( partNode * curNode, ProcessResult & result )
+{
+ if ( !mReader ) {
+ mRawReplyString = curNode->msgPart().bodyDecoded();
+ mTextualContent += curNode->msgPart().bodyToUnicode();
+ mTextualContentCharset = curNode->msgPart().charset();
+ return true;
+ }
+
+ QByteArray decryptedBody;
+ QString errorText;
+ const QByteArray data = curNode->msgPart().bodyDecodedBinary();
+ bool bOkDecrypt = decryptChiasmus( data, decryptedBody, errorText );
+ PartMetaData messagePart;
+ messagePart.isDecryptable = bOkDecrypt;
+ messagePart.isEncrypted = true;
+ messagePart.isSigned = false;
+ messagePart.errorText = errorText;
+ if ( mReader )
+ htmlWriter()->queue( writeSigstatHeader( messagePart,
+ 0, //cryptPlugWrapper(),
+ curNode->trueFromAddress() ) );
+ const QByteArray body = bOkDecrypt ? decryptedBody : data;
+ const QString chiasmusCharset = curNode->contentTypeParameter("chiasmus-charset");
+ const QTextCodec* aCodec = chiasmusCharset.isEmpty()
+ ? codecFor( curNode )
+ : KMMsgBase::codecForName( chiasmusCharset.ascii() );
+ htmlWriter()->queue( quotedHTML( aCodec->toUnicode( body ), false /*decorate*/ ) );
+ result.setInlineEncryptionState( KMMsgFullyEncrypted );
+ if ( mReader )
+ htmlWriter()->queue( writeSigstatFooter( messagePart ) );
+ return true;
+}
+
+bool ObjectTreeParser::processApplicationMsTnefSubtype( partNode *node, ProcessResult &result )
+{
+ Q_UNUSED( result );
+ if ( !mReader )
+ return false;
+
+ const QString fileName = mReader->writeMessagePartToTempFile( &node->msgPart(), node->nodeId() );
+ KTNEFParser parser;
+ if ( !parser.openFile( fileName ) || !parser.message()) {
+ kdDebug() << k_funcinfo << "Could not parse " << fileName << endl;
+ return false;
+ }
+
+ QPtrList<KTNEFAttach> tnefatts = parser.message()->attachmentList();
+ if ( tnefatts.isEmpty() ) {
+ kdDebug() << k_funcinfo << "No attachments found in " << fileName << endl;
+ return false;
+ }
+
+ if ( !showOnlyOneMimePart() ) {
+ QString label = node->msgPart().fileName().stripWhiteSpace();
+ if ( label.isEmpty() )
+ label = node->msgPart().name().stripWhiteSpace();
+ label = KMMessage::quoteHtmlChars( label, true );
+ const QString comment = KMMessage::quoteHtmlChars( node->msgPart().contentDescription(), true );
+ const QString dir = QApplication::reverseLayout() ? "rtl" : "ltr" ;
+
+ QString htmlStr = "<table cellspacing=\"1\" class=\"textAtm\">"
+ "<tr class=\"textAtmH\"><td dir=\"" + dir + "\">";
+ if ( !fileName.isEmpty() )
+ htmlStr += "<a href=\"" + QString("file:")
+ + KURL::encode_string( fileName ) + "\">"
+ + label + "</a>";
+ else
+ htmlStr += label;
+ if ( !comment.isEmpty() )
+ htmlStr += "<br>" + comment;
+ htmlStr += "</td></tr><tr class=\"textAtmB\"><td>";
+ htmlWriter()->queue( htmlStr );
+ }
+
+ for ( uint i = 0; i < tnefatts.count(); ++i ) {
+ KTNEFAttach *att = tnefatts.at( i );
+ QString label = att->displayName();
+ if( label.isEmpty() )
+ label = att->name();
+ label = KMMessage::quoteHtmlChars( label, true );
+
+ QString dir = mReader->createTempDir( "ktnef-" + QString::number( i ) );
+ parser.extractFileTo( att->name(), dir );
+ mReader->mTempFiles.append( dir + QDir::separator() + att->name() );
+ QString href = "file:" + KURL::encode_string( dir + QDir::separator() + att->name() );
+
+ KMimeType::Ptr mimeType = KMimeType::mimeType( att->mimeTag() );
+ QString iconName = KGlobal::instance()->iconLoader()->iconPath( mimeType->icon( QString(), false ), KIcon::Desktop );
+
+ htmlWriter()->queue( "<div><a href=\"" + href + "\"><img src=\"" +
+ iconName + "\" border=\"0\" style=\"max-width: 100%\">" + label +
+ "</a></div><br>" );
+ }
+
+ if ( !showOnlyOneMimePart() )
+ htmlWriter()->queue( "</td></tr></table>" );
+
+ return true;
+}
+
+ void ObjectTreeParser::writeBodyString( const QCString & bodyString,
+ const QString & fromAddress,
+ const QTextCodec * codec,
+ ProcessResult & result,
+ bool decorate ) {
+ assert( mReader ); assert( codec );
+ KMMsgSignatureState inlineSignatureState = result.inlineSignatureState();
+ KMMsgEncryptionState inlineEncryptionState = result.inlineEncryptionState();
+ writeBodyStr( bodyString, codec, fromAddress,
+ inlineSignatureState, inlineEncryptionState, decorate );
+ result.setInlineSignatureState( inlineSignatureState );
+ result.setInlineEncryptionState( inlineEncryptionState );
+ }
+
+ void ObjectTreeParser::writePartIcon( KMMessagePart * msgPart, int partNum, bool inlineImage ) {
+ if ( !mReader || !msgPart )
+ return;
+
+ kdDebug(5006) << "writePartIcon: PartNum: " << partNum << endl;
+
+ QString label = msgPart->fileName();
+ if( label.isEmpty() )
+ label = msgPart->name();
+ if( label.isEmpty() )
+ label = "unnamed";
+ label = KMMessage::quoteHtmlChars( label, true );
+
+ QString comment = msgPart->contentDescription();
+ comment = KMMessage::quoteHtmlChars( comment, true );
+ if ( label == comment ) comment = QString::null;
+
+ QString fileName = mReader->writeMessagePartToTempFile( msgPart, partNum );
+
+ QString href = fileName.isEmpty() ?
+ "part://" + QString::number( partNum + 1 ) :
+ "file:" + KURL::encode_string( fileName ) ;
+
+ QString iconName;
+ if( inlineImage )
+ iconName = href;
+ else {
+ iconName = msgPart->iconName();
+ if( iconName.right( 14 ) == "mime_empty.png" ) {
+ msgPart->magicSetType();
+ iconName = msgPart->iconName();
+ }
+ }
+
+ QCString contentId = msgPart->contentId();
+ if ( !contentId.isEmpty() ) {
+ htmlWriter()->embedPart( contentId, href );
+ }
+
+ if( inlineImage )
+ // show the filename of the image below the embedded image
+ htmlWriter()->queue( "<div><a href=\"" + href + "\">"
+ "<img src=\"" + iconName + "\" border=\"0\" style=\"max-width: 100%\"></a>"
+ "</div>"
+ "<div><a href=\"" + href + "\">" + label + "</a>"
+ "</div>"
+ "<div>" + comment + "</div><br>" );
+ else
+ // show the filename next to the image
+ htmlWriter()->queue( "<div><a href=\"" + href + "\"><img src=\"" +
+ iconName + "\" border=\"0\" style=\"max-width: 100%\">" + label +
+ "</a></div>"
+ "<div>" + comment + "</div><br>" );
+ }
+
+#define SIG_FRAME_COL_UNDEF 99
+#define SIG_FRAME_COL_RED -1
+#define SIG_FRAME_COL_YELLOW 0
+#define SIG_FRAME_COL_GREEN 1
+QString ObjectTreeParser::sigStatusToString( const Kleo::CryptoBackend::Protocol* cryptProto,
+ int status_code,
+ GpgME::Signature::Summary summary,
+ int& frameColor,
+ bool& showKeyInfos )
+{
+ // note: At the moment frameColor and showKeyInfos are
+ // used for CMS only but not for PGP signatures
+ // pending(khz): Implement usage of these for PGP sigs as well.
+ showKeyInfos = true;
+ QString result;
+ if( cryptProto ) {
+ if( cryptProto == Kleo::CryptoBackendFactory::instance()->openpgp() ) {
+ // process enum according to it's definition to be read in
+ // GNU Privacy Guard CVS repository /gpgme/gpgme/gpgme.h
+ switch( status_code ) {
+ case 0: // GPGME_SIG_STAT_NONE
+ result = i18n("Error: Signature not verified");
+ break;
+ case 1: // GPGME_SIG_STAT_GOOD
+ result = i18n("Good signature");
+ break;
+ case 2: // GPGME_SIG_STAT_BAD
+ result = i18n("<b>Bad</b> signature");
+ break;
+ case 3: // GPGME_SIG_STAT_NOKEY
+ result = i18n("No public key to verify the signature");
+ break;
+ case 4: // GPGME_SIG_STAT_NOSIG
+ result = i18n("No signature found");
+ break;
+ case 5: // GPGME_SIG_STAT_ERROR
+ result = i18n("Error verifying the signature");
+ break;
+ case 6: // GPGME_SIG_STAT_DIFF
+ result = i18n("Different results for signatures");
+ break;
+ /* PENDING(khz) Verify exact meaning of the following values:
+ case 7: // GPGME_SIG_STAT_GOOD_EXP
+ return i18n("Signature certificate is expired");
+ break;
+ case 8: // GPGME_SIG_STAT_GOOD_EXPKEY
+ return i18n("One of the certificate's keys is expired");
+ break;
+ */
+ default:
+ result = ""; // do *not* return a default text here !
+ break;
+ }
+ }
+ else if ( cryptProto == Kleo::CryptoBackendFactory::instance()->smime() ) {
+ // process status bits according to SigStatus_...
+ // definitions in kdenetwork/libkdenetwork/cryptplug.h
+
+ if( summary == GpgME::Signature::None ) {
+ result = i18n("No status information available.");
+ frameColor = SIG_FRAME_COL_YELLOW;
+ showKeyInfos = false;
+ return result;
+ }
+
+ if( summary & GpgME::Signature::Valid ) {
+ result = i18n("Good signature.");
+ // Note:
+ // Here we are work differently than KMail did before!
+ //
+ // The GOOD case ( == sig matching and the complete
+ // certificate chain was verified and is valid today )
+ // by definition does *not* show any key
+ // information but just states that things are OK.
+ // (khz, according to LinuxTag 2002 meeting)
+ frameColor = SIG_FRAME_COL_GREEN;
+ showKeyInfos = false;
+ return result;
+ }
+
+ // we are still there? OK, let's test the different cases:
+
+ // we assume green, test for yellow or red (in this order!)
+ frameColor = SIG_FRAME_COL_GREEN;
+ QString result2;
+ if( summary & GpgME::Signature::KeyExpired ){
+ // still is green!
+ result2 += i18n("One key has expired.");
+ }
+ if( summary & GpgME::Signature::SigExpired ){
+ // and still is green!
+ result2 += i18n("The signature has expired.");
+ }
+
+ // test for yellow:
+ if( summary & GpgME::Signature::KeyMissing ) {
+ result2 += i18n("Unable to verify: key missing.");
+ // if the signature certificate is missing
+ // we cannot show infos on it
+ showKeyInfos = false;
+ frameColor = SIG_FRAME_COL_YELLOW;
+ }
+ if( summary & GpgME::Signature::CrlMissing ){
+ result2 += i18n("CRL not available.");
+ frameColor = SIG_FRAME_COL_YELLOW;
+ }
+ if( summary & GpgME::Signature::CrlTooOld ){
+ result2 += i18n("Available CRL is too old.");
+ frameColor = SIG_FRAME_COL_YELLOW;
+ }
+ if( summary & GpgME::Signature::BadPolicy ){
+ result2 += i18n("A policy was not met.");
+ frameColor = SIG_FRAME_COL_YELLOW;
+ }
+ if( summary & GpgME::Signature::SysError ){
+ result2 += i18n("A system error occurred.");
+ // if a system error occurred
+ // we cannot trust any information
+ // that was given back by the plug-in
+ showKeyInfos = false;
+ frameColor = SIG_FRAME_COL_YELLOW;
+ }
+
+ // test for red:
+ if( summary & GpgME::Signature::KeyRevoked ){
+ // this is red!
+ result2 += i18n("One key has been revoked.");
+ frameColor = SIG_FRAME_COL_RED;
+ }
+ if( summary & GpgME::Signature::Red ) {
+ if( result2.isEmpty() )
+ // Note:
+ // Here we are work differently than KMail did before!
+ //
+ // The BAD case ( == sig *not* matching )
+ // by definition does *not* show any key
+ // information but just states that things are BAD.
+ //
+ // The reason for this: In this case ALL information
+ // might be falsificated, we can NOT trust the data
+ // in the body NOT the signature - so we don't show
+ // any key/signature information at all!
+ // (khz, according to LinuxTag 2002 meeting)
+ showKeyInfos = false;
+ frameColor = SIG_FRAME_COL_RED;
+ }
+ else
+ result = "";
+
+ if( SIG_FRAME_COL_GREEN == frameColor ) {
+ result = i18n("Good signature.");
+ } else if( SIG_FRAME_COL_RED == frameColor ) {
+ result = i18n("<b>Bad</b> signature.");
+ } else
+ result = "";
+
+ if( !result2.isEmpty() ) {
+ if( !result.isEmpty() )
+ result.append("<br />");
+ result.append( result2 );
+ }
+ }
+ /*
+ // add i18n support for 3rd party plug-ins here:
+ else if (0 <= cryptPlug->libName().find( "yetanotherpluginname", 0, false )) {
+
+ }
+ */
+ }
+ return result;
+}
+
+
+static QString writeSimpleSigstatHeader( const PartMetaData &block )
+{
+ QString html;
+ html += "<table cellspacing=\"0\" cellpadding=\"0\" width=\"100%\"><tr><td>";
+
+ if ( block.signClass == "signErr" ) {
+ html += i18n( "Invalid signature." );
+ } else if ( block.signClass == "signOkKeyBad" || block.signClass == "signWarn" ) {
+ html += i18n( "Not enough information to check signature validity." );
+ } else if ( block.signClass == "signOkKeyOk" ) {
+ QString addr;
+ if ( !block.signerMailAddresses.isEmpty() )
+ addr = block.signerMailAddresses.first();
+ QString name = addr;
+ if ( name.isEmpty() )
+ name = block.signer;
+ if ( addr.isEmpty() ) {
+ html += i18n( "Signature is valid." );
+ } else {
+ html += i18n( "Signed by <a href=\"mailto:%1\">%2</a>." ).arg( addr, name );
+ }
+ } else {
+ // should not happen
+ html += i18n( "Unknown signature state" );
+ }
+ html += "</td><td align=\"right\">";
+ html += "<a href=\"kmail:showSignatureDetails\">";
+ html += i18n( "Show Details" );
+ html += "</a></td></tr></table>";
+ return html;
+}
+
+static QString beginVerboseSigstatHeader()
+{
+ return "<table cellspacing=\"0\" cellpadding=\"0\" width=\"100%\"><tr><td rowspan=\"2\">";
+}
+
+static QString makeShowAuditLogLink( const QString & auditLog ) {
+ if ( auditLog.isEmpty() )
+ return i18n("No Audit Log available");
+
+ KURL url;
+ url.setProtocol( "kmail" );
+ url.setPath( "showAuditLog" );
+ url.addQueryItem( "log", auditLog );
+
+ return "<a href=\"" + url.htmlURL() + "\">" + i18n("The Audit Log is a detailed error log from the gnupg backend", "Show Audit Log") + "</a>";
+}
+
+static QString endVerboseSigstatHeader( const PartMetaData & pmd )
+{
+ QString html;
+ html += "</td><td align=\"right\" valign=\"top\" nowrap=\"nowrap\">";
+ html += "<a href=\"kmail:hideSignatureDetails\">";
+ html += i18n( "Hide Details" );
+ html += "</a></td></tr>";
+ html += "<tr><td align=\"right\" valign=\"bottom\" nowrap=\"nowrap\">";
+ html += makeShowAuditLogLink( pmd.auditLog );
+ html += "</td></tr></table>";
+ return html;
+}
+
+QString ObjectTreeParser::writeSigstatHeader( PartMetaData & block,
+ const Kleo::CryptoBackend::Protocol * cryptProto,
+ const QString & fromAddress,
+ const QString & filename )
+{
+ const bool isSMIME = cryptProto && ( cryptProto == Kleo::CryptoBackendFactory::instance()->smime() );
+ QString signer = block.signer;
+
+ QString htmlStr, simpleHtmlStr;
+ QString dir = ( QApplication::reverseLayout() ? "rtl" : "ltr" );
+ QString cellPadding("cellpadding=\"1\"");
+
+ if( block.isEncapsulatedRfc822Message )
+ {
+ htmlStr += "<table cellspacing=\"1\" "+cellPadding+" class=\"rfc822\">"
+ "<tr class=\"rfc822H\"><td dir=\"" + dir + "\">";
+ if( !filename.isEmpty() )
+ htmlStr += "<a href=\"" + QString("file:")
+ + KURL::encode_string( filename ) + "\">"
+ + i18n("Encapsulated message") + "</a>";
+ else
+ htmlStr += i18n("Encapsulated message");
+ htmlStr += "</td></tr><tr class=\"rfc822B\"><td>";
+ }
+
+ if( block.isEncrypted )
+ {
+ htmlStr += "<table cellspacing=\"1\" "+cellPadding+" class=\"encr\">"
+ "<tr class=\"encrH\"><td dir=\"" + dir + "\">";
+ if( block.isDecryptable )
+ htmlStr += i18n("Encrypted message");
+ else {
+ htmlStr += i18n("Encrypted message (decryption not possible)");
+ if( !block.errorText.isEmpty() )
+ htmlStr += "<br />" + i18n("Reason: %1").arg( block.errorText );
+ }
+ htmlStr += "</td></tr><tr class=\"encrB\"><td>";
+ }
+ simpleHtmlStr = htmlStr;
+
+ if( block.isSigned ) {
+ QStringList& blockAddrs( block.signerMailAddresses );
+ // note: At the moment frameColor and showKeyInfos are
+ // used for CMS only but not for PGP signatures
+ // pending(khz): Implement usage of these for PGP sigs as well.
+ int frameColor = SIG_FRAME_COL_UNDEF;
+ bool showKeyInfos;
+ bool onlyShowKeyURL = false;
+ bool cannotCheckSignature = true;
+ QString statusStr = sigStatusToString( cryptProto,
+ block.status_code,
+ block.sigSummary,
+ frameColor,
+ showKeyInfos );
+ // if needed fallback to english status text
+ // that was reported by the plugin
+ if( statusStr.isEmpty() )
+ statusStr = block.status;
+ if( block.technicalProblem )
+ frameColor = SIG_FRAME_COL_YELLOW;
+
+ switch( frameColor ){
+ case SIG_FRAME_COL_RED:
+ cannotCheckSignature = false;
+ break;
+ case SIG_FRAME_COL_YELLOW:
+ cannotCheckSignature = true;
+ break;
+ case SIG_FRAME_COL_GREEN:
+ cannotCheckSignature = false;
+ break;
+ }
+
+ // compose the string for displaying the key ID
+ // either as URL or not linked (for PGP)
+ // note: Once we can start PGP key manager programs
+ // from within KMail we could change this and
+ // always show the URL. (khz, 2002/06/27)
+ QString startKeyHREF;
+ if( isSMIME )
+ startKeyHREF =
+ QString("<a href=\"kmail:showCertificate#%1 ### %2 ### %3\">")
+ .arg( cryptProto->displayName(),
+ cryptProto->name(),
+ block.keyId );
+ QString keyWithWithoutURL
+ = isSMIME
+ ? QString("%1%2</a>")
+ .arg( startKeyHREF,
+ cannotCheckSignature ? i18n("[Details]") : ("0x" + block.keyId) )
+ : "0x" + QString::fromUtf8( block.keyId );
+
+
+ // temporary hack: always show key infos!
+ showKeyInfos = true;
+
+ // Sorry for using 'black' as null color but .isValid()
+ // checking with QColor default c'tor did not work for
+ // some reason.
+ if( isSMIME && (SIG_FRAME_COL_UNDEF != frameColor) ) {
+
+ // new frame settings for CMS:
+ // beautify the status string
+ if( !statusStr.isEmpty() ) {
+ statusStr.prepend("<i>");
+ statusStr.append( "</i>");
+ }
+
+ // special color handling: S/MIME uses only green/yellow/red.
+ switch( frameColor ) {
+ case SIG_FRAME_COL_RED:
+ block.signClass = "signErr";//"signCMSRed";
+ onlyShowKeyURL = true;
+ break;
+ case SIG_FRAME_COL_YELLOW:
+ if( block.technicalProblem )
+ block.signClass = "signWarn";
+ else
+ block.signClass = "signOkKeyBad";//"signCMSYellow";
+ break;
+ case SIG_FRAME_COL_GREEN:
+ block.signClass = "signOkKeyOk";//"signCMSGreen";
+ // extra hint for green case
+ // that email addresses in DN do not match fromAddress
+ QString greenCaseWarning;
+ QString msgFrom( KPIM::getEmailAddress(fromAddress) );
+ QString certificate;
+ if( block.keyId.isEmpty() )
+ certificate = i18n("certificate");
+ else
+ certificate = startKeyHREF + i18n("certificate") + "</a>";
+ if( !blockAddrs.empty() ){
+ if( blockAddrs.grep(
+ msgFrom,
+ false ).isEmpty() ) {
+ greenCaseWarning =
+ "<u>" +
+ i18n("Warning:") +
+ "</u> " +
+ i18n("Sender's mail address is not stored "
+ "in the %1 used for signing.").arg(certificate) +
+ "<br />" +
+ i18n("sender: ") +
+ msgFrom +
+ "<br />" +
+ i18n("stored: ");
+ // We cannot use Qt's join() function here but
+ // have to join the addresses manually to
+ // extract the mail addresses (without '<''>')
+ // before including it into our string:
+ bool bStart = true;
+ for(QStringList::ConstIterator it = blockAddrs.begin();
+ it != blockAddrs.end(); ++it ){
+ if( !bStart )
+ greenCaseWarning.append(", <br />&nbsp; &nbsp;");
+ bStart = false;
+ greenCaseWarning.append( KPIM::getEmailAddress(*it) );
+ }
+ }
+ } else {
+ greenCaseWarning =
+ "<u>" +
+ i18n("Warning:") +
+ "</u> " +
+ i18n("No mail address is stored in the %1 used for signing, "
+ "so we cannot compare it to the sender's address %2.")
+ .arg(certificate,msgFrom);
+ }
+ if( !greenCaseWarning.isEmpty() ) {
+ if( !statusStr.isEmpty() )
+ statusStr.append("<br />&nbsp;<br />");
+ statusStr.append( greenCaseWarning );
+ }
+ break;
+ }
+
+ QString frame = "<table cellspacing=\"1\" "+cellPadding+" "
+ "class=\"" + block.signClass + "\">"
+ "<tr class=\"" + block.signClass + "H\"><td dir=\"" + dir + "\">";
+ htmlStr += frame + beginVerboseSigstatHeader();
+ simpleHtmlStr += frame;
+ simpleHtmlStr += writeSimpleSigstatHeader( block );
+ if( block.technicalProblem ) {
+ htmlStr += block.errorText;
+ }
+ else if( showKeyInfos ) {
+ if( cannotCheckSignature ) {
+ htmlStr += i18n( "Not enough information to check "
+ "signature. %1" )
+ .arg( keyWithWithoutURL );
+ }
+ else {
+
+ if (block.signer.isEmpty())
+ signer = "";
+ else {
+ if( !blockAddrs.empty() ){
+ QString address = KMMessage::encodeMailtoUrl( blockAddrs.first() );
+ signer = "<a href=\"mailto:" + address + "\">" + signer + "</a>";
+ }
+ }
+
+ if( block.keyId.isEmpty() ) {
+ if( signer.isEmpty() || onlyShowKeyURL )
+ htmlStr += i18n( "Message was signed with unknown key." );
+ else
+ htmlStr += i18n( "Message was signed by %1." )
+ .arg( signer );
+ } else {
+ QDateTime created = block.creationTime;
+ if( created.isValid() ) {
+ if( signer.isEmpty() ) {
+ if( onlyShowKeyURL )
+ htmlStr += i18n( "Message was signed with key %1." )
+ .arg( keyWithWithoutURL );
+ else
+ htmlStr += i18n( "Message was signed on %1 with key %2." )
+ .arg( KGlobal::locale()->formatDateTime( created ),
+ keyWithWithoutURL );
+ }
+ else {
+ if( onlyShowKeyURL )
+ htmlStr += i18n( "Message was signed with key %1." )
+ .arg( keyWithWithoutURL );
+ else
+ htmlStr += i18n( "Message was signed by %3 on %1 with key %2" )
+ .arg( KGlobal::locale()->formatDateTime( created ),
+ keyWithWithoutURL,
+ signer );
+ }
+ }
+ else {
+ if( signer.isEmpty() || onlyShowKeyURL )
+ htmlStr += i18n( "Message was signed with key %1." )
+ .arg( keyWithWithoutURL );
+ else
+ htmlStr += i18n( "Message was signed by %2 with key %1." )
+ .arg( keyWithWithoutURL,
+ signer );
+ }
+ }
+ }
+ htmlStr += "<br />";
+ if( !statusStr.isEmpty() ) {
+ htmlStr += "&nbsp;<br />";
+ htmlStr += i18n( "Status: " );
+ htmlStr += statusStr;
+ }
+ } else {
+ htmlStr += statusStr;
+ }
+ frame = "</td></tr><tr class=\"" + block.signClass + "B\"><td>";
+ htmlStr += endVerboseSigstatHeader( block ) + frame;
+ simpleHtmlStr += frame;
+
+ } else {
+
+ // old frame settings for PGP:
+
+ if( block.signer.isEmpty() || block.technicalProblem ) {
+ block.signClass = "signWarn";
+ QString frame = "<table cellspacing=\"1\" "+cellPadding+" "
+ "class=\"" + block.signClass + "\">"
+ "<tr class=\"" + block.signClass + "H\"><td dir=\"" + dir + "\">";
+ htmlStr += frame + beginVerboseSigstatHeader();
+ simpleHtmlStr += frame;
+ simpleHtmlStr += writeSimpleSigstatHeader( block );
+ if( block.technicalProblem ) {
+ htmlStr += block.errorText;
+ }
+ else {
+ if( !block.keyId.isEmpty() ) {
+ QDateTime created = block.creationTime;
+ if ( created.isValid() )
+ htmlStr += i18n( "Message was signed on %1 with unknown key %2." )
+ .arg( KGlobal::locale()->formatDateTime( created ),
+ keyWithWithoutURL );
+ else
+ htmlStr += i18n( "Message was signed with unknown key %1." )
+ .arg( keyWithWithoutURL );
+ }
+ else
+ htmlStr += i18n( "Message was signed with unknown key." );
+ htmlStr += "<br />";
+ htmlStr += i18n( "The validity of the signature cannot be "
+ "verified." );
+ if( !statusStr.isEmpty() ) {
+ htmlStr += "<br />";
+ htmlStr += i18n( "Status: " );
+ htmlStr += "<i>";
+ htmlStr += statusStr;
+ htmlStr += "</i>";
+ }
+ }
+ frame = "</td></tr><tr class=\"" + block.signClass + "B\"><td>";
+ htmlStr += endVerboseSigstatHeader( block ) + frame;
+ simpleHtmlStr += frame;
+ }
+ else
+ {
+ // HTMLize the signer's user id and create mailto: link
+ signer = KMMessage::quoteHtmlChars( signer, true );
+ signer = "<a href=\"mailto:" + signer + "\">" + signer + "</a>";
+
+ if (block.isGoodSignature) {
+ if( block.keyTrust < Kpgp::KPGP_VALIDITY_MARGINAL )
+ block.signClass = "signOkKeyBad";
+ else
+ block.signClass = "signOkKeyOk";
+ QString frame = "<table cellspacing=\"1\" "+cellPadding+" "
+ "class=\"" + block.signClass + "\">"
+ "<tr class=\"" + block.signClass + "H\"><td dir=\"" + dir + "\">";
+ htmlStr += frame + beginVerboseSigstatHeader();
+ simpleHtmlStr += frame;
+ simpleHtmlStr += writeSimpleSigstatHeader( block );
+ if( !block.keyId.isEmpty() )
+ htmlStr += i18n( "Message was signed by %2 (Key ID: %1)." )
+ .arg( keyWithWithoutURL,
+ signer );
+ else
+ htmlStr += i18n( "Message was signed by %1." ).arg( signer );
+ htmlStr += "<br />";
+
+ switch( block.keyTrust )
+ {
+ case Kpgp::KPGP_VALIDITY_UNKNOWN:
+ htmlStr += i18n( "The signature is valid, but the key's "
+ "validity is unknown." );
+ break;
+ case Kpgp::KPGP_VALIDITY_MARGINAL:
+ htmlStr += i18n( "The signature is valid and the key is "
+ "marginally trusted." );
+ break;
+ case Kpgp::KPGP_VALIDITY_FULL:
+ htmlStr += i18n( "The signature is valid and the key is "
+ "fully trusted." );
+ break;
+ case Kpgp::KPGP_VALIDITY_ULTIMATE:
+ htmlStr += i18n( "The signature is valid and the key is "
+ "ultimately trusted." );
+ break;
+ default:
+ htmlStr += i18n( "The signature is valid, but the key is "
+ "untrusted." );
+ }
+ frame = "</td></tr>"
+ "<tr class=\"" + block.signClass + "B\"><td>";
+ htmlStr += endVerboseSigstatHeader( block ) + frame;
+ simpleHtmlStr += frame;
+ }
+ else
+ {
+ block.signClass = "signErr";
+ QString frame = "<table cellspacing=\"1\" "+cellPadding+" "
+ "class=\"" + block.signClass + "\">"
+ "<tr class=\"" + block.signClass + "H\"><td dir=\"" + dir + "\">";
+ htmlStr += frame + beginVerboseSigstatHeader();
+ simpleHtmlStr += frame;
+ simpleHtmlStr += writeSimpleSigstatHeader( block );
+ if( !block.keyId.isEmpty() )
+ htmlStr += i18n( "Message was signed by %2 (Key ID: %1)." )
+ .arg( keyWithWithoutURL,
+ signer );
+ else
+ htmlStr += i18n( "Message was signed by %1." ).arg( signer );
+ htmlStr += "<br />";
+ htmlStr += i18n("Warning: The signature is bad.");
+ frame = "</td></tr>"
+ "<tr class=\"" + block.signClass + "B\"><td>";
+ htmlStr += endVerboseSigstatHeader( block ) + frame;
+ simpleHtmlStr += frame;
+ }
+ }
+ }
+ }
+
+ if ( mReader->showSignatureDetails() )
+ return htmlStr;
+ return simpleHtmlStr;
+}
+
+QString ObjectTreeParser::writeSigstatFooter( PartMetaData& block )
+{
+ QString dir = ( QApplication::reverseLayout() ? "rtl" : "ltr" );
+
+ QString htmlStr;
+
+ if (block.isSigned) {
+ htmlStr += "</td></tr><tr class=\"" + block.signClass + "H\">";
+ htmlStr += "<td dir=\"" + dir + "\">" +
+ i18n( "End of signed message" ) +
+ "</td></tr></table>";
+ }
+
+ if (block.isEncrypted) {
+ htmlStr += "</td></tr><tr class=\"encrH\"><td dir=\"" + dir + "\">" +
+ i18n( "End of encrypted message" ) +
+ "</td></tr></table>";
+ }
+
+ if( block.isEncapsulatedRfc822Message )
+ {
+ htmlStr += "</td></tr><tr class=\"rfc822H\"><td dir=\"" + dir + "\">" +
+ i18n( "End of encapsulated message" ) +
+ "</td></tr></table>";
+ }
+
+ return htmlStr;
+}
+
+//-----------------------------------------------------------------------------
+void ObjectTreeParser::writeBodyStr( const QCString& aStr, const QTextCodec *aCodec,
+ const QString& fromAddress )
+{
+ KMMsgSignatureState dummy1;
+ KMMsgEncryptionState dummy2;
+ writeBodyStr( aStr, aCodec, fromAddress, dummy1, dummy2, false );
+}
+
+//-----------------------------------------------------------------------------
+void ObjectTreeParser::writeBodyStr( const QCString& aStr, const QTextCodec *aCodec,
+ const QString& fromAddress,
+ KMMsgSignatureState& inlineSignatureState,
+ KMMsgEncryptionState& inlineEncryptionState,
+ bool decorate )
+{
+ bool goodSignature = false;
+ Kpgp::Module* pgp = Kpgp::Module::getKpgp();
+ assert(pgp != 0);
+ bool isPgpMessage = false; // true if the message contains at least one
+ // PGP MESSAGE or one PGP SIGNED MESSAGE block
+ QString dir = ( QApplication::reverseLayout() ? "rtl" : "ltr" );
+ QString headerStr = QString("<div dir=\"%1\">").arg(dir);
+
+ inlineSignatureState = KMMsgNotSigned;
+ inlineEncryptionState = KMMsgNotEncrypted;
+ QPtrList<Kpgp::Block> pgpBlocks;
+ QStrList nonPgpBlocks;
+ if( Kpgp::Module::prepareMessageForDecryption( aStr, pgpBlocks, nonPgpBlocks ) )
+ {
+ bool isEncrypted = false, isSigned = false;
+ bool fullySignedOrEncrypted = true;
+ bool firstNonPgpBlock = true;
+ bool couldDecrypt = false;
+ QString signer;
+ QCString keyId;
+ QString decryptionError;
+ Kpgp::Validity keyTrust = Kpgp::KPGP_VALIDITY_FULL;
+
+ QPtrListIterator<Kpgp::Block> pbit( pgpBlocks );
+
+ QStrListIterator npbit( nonPgpBlocks );
+
+ QString htmlStr;
+ for( ; *pbit != 0; ++pbit, ++npbit )
+ {
+ // insert the next Non-OpenPGP block
+ QCString str( *npbit );
+ if( !str.isEmpty() ) {
+ htmlStr += quotedHTML( aCodec->toUnicode( str ), decorate );
+ 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 ) {
+ // check whether str only consists of \n
+ for( QCString::ConstIterator c = str.begin(); *c; ++c ) {
+ if( *c != '\n' ) {
+ fullySignedOrEncrypted = false;
+ break;
+ }
+ }
+ }
+ else {
+ fullySignedOrEncrypted = false;
+ }
+ }
+ firstNonPgpBlock = false;
+
+ //htmlStr += "<br>";
+
+ Kpgp::Block* block = *pbit;
+ if( ( block->type() == Kpgp::PgpMessageBlock &&
+ // ### Workaround for bug 56693
+ !kmkernel->contextMenuShown() ) ||
+ ( block->type() == Kpgp::ClearsignedBlock ) )
+ {
+ isPgpMessage = true;
+ if( block->type() == Kpgp::PgpMessageBlock )
+ {
+ if ( mReader )
+ emit mReader->noDrag();
+ // try to decrypt this OpenPGP block
+ couldDecrypt = block->decrypt();
+ isEncrypted = block->isEncrypted();
+ if (!couldDecrypt) {
+ decryptionError = pgp->lastErrorMsg();
+ }
+ }
+ else
+ {
+ // try to verify this OpenPGP block
+ block->verify();
+ }
+
+ isSigned = block->isSigned();
+ if( isSigned )
+ {
+ keyId = block->signatureKeyId();
+ signer = block->signatureUserId();
+ if( !signer.isEmpty() )
+ {
+ goodSignature = block->goodSignature();
+
+ if( !keyId.isEmpty() ) {
+ keyTrust = pgp->keyTrust( keyId );
+ Kpgp::Key* key = pgp->publicKey( keyId );
+ if ( key ) {
+ // Use the user ID from the key because this one
+ // is charset safe.
+ signer = key->primaryUserID();
+ }
+ }
+ else
+ // This is needed for the PGP 6 support because PGP 6 doesn't
+ // print the key id of the signing key if the key is known.
+ keyTrust = pgp->keyTrust( signer );
+ }
+ }
+
+ if( isSigned )
+ inlineSignatureState = KMMsgPartiallySigned;
+ if( isEncrypted )
+ inlineEncryptionState = KMMsgPartiallyEncrypted;
+
+ PartMetaData messagePart;
+
+ messagePart.isSigned = isSigned;
+ messagePart.technicalProblem = false;
+ messagePart.isGoodSignature = goodSignature;
+ messagePart.isEncrypted = isEncrypted;
+ messagePart.isDecryptable = couldDecrypt;
+ messagePart.decryptionError = decryptionError;
+ messagePart.signer = signer;
+ messagePart.keyId = keyId;
+ messagePart.keyTrust = keyTrust;
+
+ htmlStr += writeSigstatHeader( messagePart, 0, fromAddress );
+
+ htmlStr += quotedHTML( aCodec->toUnicode( block->text() ), decorate );
+ htmlStr += writeSigstatFooter( messagePart );
+ }
+ else // block is neither message block nor clearsigned block
+ htmlStr += quotedHTML( aCodec->toUnicode( block->text() ),
+ decorate );
+ }
+
+ // add the last Non-OpenPGP block
+ QCString str( nonPgpBlocks.last() );
+ if( !str.isEmpty() ) {
+ htmlStr += quotedHTML( aCodec->toUnicode( str ), decorate );
+ // Even if the trailing Non-OpenPGP block isn't empty we still
+ // consider the message part fully signed/encrypted because else
+ // all inline signed mailing list messages would only be partially
+ // signed because of the footer which is often added by the mailing
+ // list software. IK, 2003-02-15
+ }
+ if( fullySignedOrEncrypted ) {
+ if( inlineSignatureState == KMMsgPartiallySigned )
+ inlineSignatureState = KMMsgFullySigned;
+ if( inlineEncryptionState == KMMsgPartiallyEncrypted )
+ inlineEncryptionState = KMMsgFullyEncrypted;
+ }
+ htmlWriter()->queue( htmlStr );
+ }
+ else
+ htmlWriter()->queue( quotedHTML( aCodec->toUnicode( aStr ), decorate ) );
+}
+
+
+QString ObjectTreeParser::quotedHTML( const QString& s, bool decorate )
+{
+ assert( mReader );
+ assert( cssHelper() );
+
+ int convertFlags = LinkLocator::PreserveSpaces;
+ if ( decorate && GlobalSettings::self()->showEmoticons() ) {
+ convertFlags |= LinkLocator::ReplaceSmileys;
+ }
+ QString htmlStr;
+ const QString normalStartTag = cssHelper()->nonQuotedFontTag();
+ QString quoteFontTag[3];
+ QString deepQuoteFontTag[3];
+ for ( int i = 0 ; i < 3 ; ++i ) {
+ quoteFontTag[i] = cssHelper()->quoteFontTag( i );
+ deepQuoteFontTag[i] = cssHelper()->quoteFontTag( i+3 );
+ }
+ const QString normalEndTag = "</div>";
+ const QString quoteEnd = "</div>";
+
+ unsigned int pos, beg;
+ const unsigned int length = s.length();
+
+ // skip leading empty lines
+ for ( pos = 0; pos < length && s[pos] <= ' '; pos++ );
+ while (pos > 0 && (s[pos-1] == ' ' || s[pos-1] == '\t')) pos--;
+ beg = pos;
+
+ int currQuoteLevel = -2; // -2 == no previous lines
+ bool curHidden = false; // no hide any block
+
+ while (beg<length)
+ {
+ QString line;
+
+ /* search next occurrence of '\n' */
+ pos = s.find('\n', beg, FALSE);
+ if (pos == (unsigned int)(-1))
+ pos = length;
+
+ line = s.mid(beg,pos-beg);
+ beg = pos+1;
+
+ /* calculate line's current quoting depth */
+ int actQuoteLevel = -1;
+
+ if ( GlobalSettings::self()->showExpandQuotesMark() )
+ {
+ // Cache Icons
+ if ( mCollapseIcon.isEmpty() ) {
+ mCollapseIcon= LinkLocator::pngToDataUrl(
+ KGlobal::instance()->iconLoader()->iconPath( "quotecollapse",0 ));
+ }
+ if ( mExpandIcon.isEmpty() )
+ mExpandIcon= LinkLocator::pngToDataUrl(
+ KGlobal::instance()->iconLoader()->iconPath( "quoteexpand",0 ));
+ }
+
+ for (unsigned int p=0; p<line.length(); p++) {
+ switch (line[p].latin1()) {
+ case '>':
+ case '|':
+ actQuoteLevel++;
+ break;
+ case ' ': // spaces and tabs are allowed between the quote markers
+ case '\t':
+ case '\r':
+ break;
+ default: // stop quoting depth calculation
+ p = line.length();
+ break;
+ }
+ } /* for() */
+
+ bool actHidden = false;
+ QString textExpand;
+
+ // This quoted line needs be hiden
+ if (GlobalSettings::self()->showExpandQuotesMark() && mReader->mLevelQuote >= 0
+ && mReader->mLevelQuote <= ( actQuoteLevel ) )
+ actHidden = true;
+
+ if ( actQuoteLevel != currQuoteLevel ) {
+ /* finish last quotelevel */
+ if (currQuoteLevel == -1)
+ htmlStr.append( normalEndTag );
+ else if ( currQuoteLevel >= 0 && !curHidden )
+ htmlStr.append( quoteEnd );
+
+ /* start new quotelevel */
+ if (actQuoteLevel == -1)
+ htmlStr += normalStartTag;
+ else
+ {
+ if ( GlobalSettings::self()->showExpandQuotesMark() )
+ {
+ if ( actHidden )
+ {
+ //only show the QuoteMark when is the first line of the level hidden
+ if ( !curHidden )
+ {
+ //Expand all quotes
+ htmlStr += "<div class=\"quotelevelmark\" >" ;
+ htmlStr += QString( "<a href=\"kmail:levelquote?%1 \">"
+ "<img src=\"%2\" alt=\"\" title=\"\"/></a>" )
+ .arg(-1)
+ .arg( mExpandIcon );
+ htmlStr += "</div><br/>";
+ htmlStr += quoteEnd;
+ }
+ }else {
+ htmlStr += "<div class=\"quotelevelmark\" >" ;
+ htmlStr += QString( "<a href=\"kmail:levelquote?%1 \">"
+ "<img src=\"%2\" alt=\"\" title=\"\"/></a>" )
+ .arg(actQuoteLevel)
+ .arg( mCollapseIcon);
+ htmlStr += "</div>";
+ if ( actQuoteLevel < 3 )
+ htmlStr += quoteFontTag[actQuoteLevel];
+ else
+ htmlStr += deepQuoteFontTag[actQuoteLevel%3];
+ }
+ } else
+ if ( actQuoteLevel < 3 )
+ htmlStr += quoteFontTag[actQuoteLevel];
+ else
+ htmlStr += deepQuoteFontTag[actQuoteLevel%3];
+ }
+ currQuoteLevel = actQuoteLevel;
+ }
+ curHidden = actHidden;
+
+
+ if ( !actHidden )
+ {
+ // don't write empty <div ...></div> blocks (they have zero height)
+ // ignore ^M DOS linebreaks
+ if( !line.replace('\015', "").isEmpty() )
+ {
+ htmlStr +=QString( "<div dir=\"%1\">" ).arg( line.isRightToLeft() ? "rtl":"ltr" );
+ htmlStr += LinkLocator::convertToHtml( line, convertFlags );
+ htmlStr += QString( "</div>" );
+ }
+ else
+ htmlStr += "<br>";
+ }
+ } /* while() */
+
+ /* really finish the last quotelevel */
+ if (currQuoteLevel == -1)
+ htmlStr.append( normalEndTag );
+ else
+ htmlStr.append( quoteEnd );
+
+ //kdDebug(5006) << "KMReaderWin::quotedHTML:\n"
+ // << "========================================\n"
+ // << htmlStr
+ // << "\n======================================\n";
+ return htmlStr;
+}
+
+
+
+ const QTextCodec * ObjectTreeParser::codecFor( partNode * node ) const {
+ assert( node );
+ if ( mReader && mReader->overrideCodec() )
+ return mReader->overrideCodec();
+ return node->msgPart().codec();
+ }
+
+#ifdef MARCS_DEBUG
+ void ObjectTreeParser::dumpToFile( const char * filename, const char * start,
+ size_t len ) {
+ assert( filename );
+
+ QFile f( filename );
+ if ( f.open( IO_WriteOnly ) ) {
+ if ( start ) {
+ QDataStream ds( &f );
+ ds.writeRawBytes( start, len );
+ }
+ f.close(); // If data is 0 we just create a zero length file.
+ }
+ }
+#endif // !NDEBUG
+
+
+} // namespace KMail
diff --git a/kmail/objecttreeparser.h b/kmail/objecttreeparser.h
new file mode 100644
index 00000000..bcc137b0
--- /dev/null
+++ b/kmail/objecttreeparser.h
@@ -0,0 +1,295 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ objecttreeparser.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2002-2003 Klarälvdalens Datakonsult AB
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef _KMAIL_OBJECTTREEPARSER_H_
+#define _KMAIL_OBJECTTREEPARSER_H_
+
+#include "kmmsgbase.h"
+
+#include <qcstring.h>
+
+#include <kleo/cryptobackend.h>
+#include <gpgmepp/verificationresult.h>
+
+class KMReaderWin;
+class KMMessagePart;
+class QString;
+class QWidget;
+class partNode;
+
+namespace KMail {
+
+ class AttachmentStrategy;
+ class HtmlWriter;
+ class PartMetaData;
+ class CSSHelper;
+
+ class ProcessResult {
+ public:
+ ProcessResult( KMMsgSignatureState inlineSignatureState = KMMsgNotSigned,
+ KMMsgEncryptionState inlineEncryptionState = KMMsgNotEncrypted,
+ bool neverDisplayInline = false,
+ bool isImage = false )
+ : mInlineSignatureState( inlineSignatureState ),
+ mInlineEncryptionState( inlineEncryptionState ),
+ mNeverDisplayInline( neverDisplayInline ),
+ mIsImage( isImage ) {}
+
+ KMMsgSignatureState inlineSignatureState() const {
+ return mInlineSignatureState;
+ }
+ void setInlineSignatureState( KMMsgSignatureState state ) {
+ mInlineSignatureState = state;
+ }
+
+ KMMsgEncryptionState inlineEncryptionState() const {
+ return mInlineEncryptionState;
+ }
+ void setInlineEncryptionState( KMMsgEncryptionState state ) {
+ mInlineEncryptionState = state;
+ }
+
+ bool neverDisplayInline() const { return mNeverDisplayInline; }
+ void setNeverDisplayInline( bool display ) {
+ mNeverDisplayInline = display;
+ }
+
+ bool isImage() const { return mIsImage; }
+ void setIsImage( bool image ) {
+ mIsImage = image;
+ }
+
+ void adjustCryptoStatesOfNode( partNode * node ) const;
+
+ private:
+ KMMsgSignatureState mInlineSignatureState;
+ KMMsgEncryptionState mInlineEncryptionState;
+ bool mNeverDisplayInline : 1;
+ bool mIsImage : 1;
+ };
+
+ class ObjectTreeParser {
+ class CryptoProtocolSaver;
+ /** Internal. Copies the context of @p other, but not it's rawReplyString() */
+ ObjectTreeParser( const ObjectTreeParser & other );
+ public:
+ ObjectTreeParser( KMReaderWin * reader=0, const Kleo::CryptoBackend::Protocol * protocol=0,
+ bool showOneMimePart=false, bool keepEncryptions=false,
+ bool includeSignatures=true,
+ const KMail::AttachmentStrategy * attachmentStrategy=0,
+ KMail::HtmlWriter * htmlWriter=0,
+ KMail::CSSHelper * cssHelper=0 );
+ virtual ~ObjectTreeParser();
+
+ QCString rawReplyString() const { return mRawReplyString; }
+
+ /*! @return the text of the message, ie. what would appear in the
+ composer's text editor if this was edited. */
+ QString textualContent() const { return mTextualContent; }
+
+ QCString textualContentCharset() const { return mTextualContentCharset; }
+
+ void setCryptoProtocol( const Kleo::CryptoBackend::Protocol * protocol ) {
+ mCryptoProtocol = protocol;
+ }
+ const Kleo::CryptoBackend::Protocol* cryptoProtocol() const {
+ return mCryptoProtocol;
+ }
+
+ bool showOnlyOneMimePart() const { return mShowOnlyOneMimePart; }
+ void setShowOnlyOneMimePart( bool show ) {
+ mShowOnlyOneMimePart = show;
+ }
+
+ bool keepEncryptions() const { return mKeepEncryptions; }
+ void setKeepEncryptions( bool keep ) {
+ mKeepEncryptions = keep;
+ }
+
+ bool includeSignatures() const { return mIncludeSignatures; }
+ void setIncludeSignatures( bool include ) {
+ mIncludeSignatures = include;
+ }
+
+ const KMail::AttachmentStrategy * attachmentStrategy() const {
+ return mAttachmentStrategy;
+ }
+
+ KMail::HtmlWriter * htmlWriter() const { return mHtmlWriter; }
+
+ KMail::CSSHelper * cssHelper() const { return mCSSHelper; }
+
+ /** Parse beginning at a given node and recursively parsing
+ the children of that node and it's next sibling. */
+ // Function is called internally by "parseMsg(KMMessage* msg)"
+ // and it will be replaced once KMime is alive.
+ void parseObjectTree( partNode * node );
+
+ private:
+ /** Standard children handling a.k.a. multipart/mixed (w/o
+ kroupware hacks) */
+ void stdChildHandling( partNode * child );
+
+ 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. */
+ // Function will be replaced once KMime is alive.
+ void insertAndParseNewChildNode( partNode & node,
+ const char * content,
+ const char * cntDesc,
+ bool append=false );
+ /** if data is 0:
+ Feeds the HTML widget with the contents of the opaque signed
+ data found in partNode 'sign'.
+ if data is set:
+ Feeds the HTML widget with the contents of the given
+ multipart/signed object.
+ Signature is tested. May contain body parts.
+
+ Returns whether a signature was found or not: use this to
+ find out if opaque data is signed or not. */
+ bool writeOpaqueOrMultipartSignedData( partNode * data,
+ partNode & sign,
+ const QString & fromAddress,
+ bool doCheck=true,
+ QCString * cleartextData=0,
+ std::vector<GpgME::Signature> paramSignatures = std::vector<GpgME::Signature>(),
+ bool hideErrors=false );
+
+ /** Returns the contents of the given multipart/encrypted
+ object. Data is decypted. May contain body parts. */
+ bool okDecryptMIME( partNode& data,
+ QCString& decryptedData,
+ bool& signatureFound,
+ std::vector<GpgME::Signature> &signatures,
+ bool showWarning,
+ bool& passphraseError,
+ bool& actuallyEncrypted,
+ QString& aErrorText,
+ QString& auditLog );
+
+ bool processMailmanMessage( 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.
+ */
+ static bool containsExternalReferences( const QCString & str );
+
+ public:// (during refactoring)
+
+ bool processTextHtmlSubtype( partNode * node, ProcessResult & result );
+ bool processTextPlainSubtype( partNode * node, ProcessResult & result );
+
+ bool processMultiPartMixedSubtype( partNode * node, ProcessResult & result );
+ bool processMultiPartAlternativeSubtype( partNode * node, ProcessResult & result );
+ bool processMultiPartDigestSubtype( partNode * node, ProcessResult & result );
+ bool processMultiPartParallelSubtype( partNode * node, ProcessResult & result );
+ bool processMultiPartSignedSubtype( partNode * node, ProcessResult & result );
+ bool processMultiPartEncryptedSubtype( partNode * node, ProcessResult & result );
+
+ bool processMessageRfc822Subtype( partNode * node, ProcessResult & result );
+
+ bool processApplicationOctetStreamSubtype( partNode * node, ProcessResult & result );
+ bool processApplicationPkcs7MimeSubtype( partNode * node, ProcessResult & result );
+ bool processApplicationChiasmusTextSubtype( partNode * node, ProcessResult & result );
+ bool processApplicationMsTnefSubtype( partNode *node, ProcessResult &result );
+
+ private:
+ bool decryptChiasmus( const QByteArray& data, QByteArray& bodyDecoded, QString& errorText );
+ void writeBodyString( const QCString & bodyString,
+ const QString & fromAddress,
+ const QTextCodec * codec,
+ ProcessResult & result, bool decorate );
+
+ void writePartIcon( KMMessagePart * msgPart, int partNumber, bool inlineImage=false );
+
+ QString sigStatusToString( const Kleo::CryptoBackend::Protocol * cryptProto,
+ int status_code,
+ GpgME::Signature::Summary summary,
+ int & frameColor,
+ bool & showKeyInfos );
+ QString writeSigstatHeader( KMail::PartMetaData & part,
+ const Kleo::CryptoBackend::Protocol * cryptProto,
+ const QString & fromAddress,
+ const QString & filename = QString::null );
+ QString writeSigstatFooter( KMail::PartMetaData & part );
+
+ void writeBodyStr( const QCString & bodyString,
+ const QTextCodec * aCodec,
+ const QString & fromAddress,
+ KMMsgSignatureState & inlineSignatureState,
+ KMMsgEncryptionState & inlineEncryptionState,
+ bool decorate );
+ public: // KMReaderWin still needs this...
+ void writeBodyStr( const QCString & bodyString,
+ const QTextCodec * aCodec,
+ const QString & fromAddress );
+
+ private:
+ /** Change the string to `quoted' html (meaning, that the quoted
+ part of the message get italized */
+ QString quotedHTML(const QString& pos, bool decorate);
+
+ const QTextCodec * codecFor( partNode * node ) const;
+
+#ifdef MARCS_DEBUG
+ void dumpToFile( const char * filename, const char * dataStart, size_t dataLen );
+#else
+ void dumpToFile( const char *, const char *, size_t ) {}
+#endif
+
+ private:
+ KMReaderWin * mReader;
+ QCString mRawReplyString;
+ QCString mTextualContentCharset;
+ QString mTextualContent;
+ const Kleo::CryptoBackend::Protocol * mCryptoProtocol;
+ bool mShowOnlyOneMimePart;
+ bool mKeepEncryptions;
+ bool mIncludeSignatures;
+ const KMail::AttachmentStrategy * mAttachmentStrategy;
+ KMail::HtmlWriter * mHtmlWriter;
+ KMail::CSSHelper * mCSSHelper;
+ // DataUrl Icons cache
+ QString mCollapseIcon;
+ QString mExpandIcon;
+ };
+
+} // namespace KMail
+
+#endif // _KMAIL_OBJECTTREEPARSER_H_
+
diff --git a/kmail/partNode.cpp b/kmail/partNode.cpp
new file mode 100644
index 00000000..4fd51c85
--- /dev/null
+++ b/kmail/partNode.cpp
@@ -0,0 +1,612 @@
+/* -*- c++ -*-
+ partNode.cpp A node in a MIME tree.
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2002 Klarlvdalens 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include <config.h>
+#include "partNode.h"
+#include <klocale.h>
+#include <kdebug.h>
+#include "kmmimeparttree.h"
+#include <mimelib/utility.h>
+#include <qregexp.h>
+#include <kasciistricmp.h>
+#include "util.h"
+
+/*
+ ===========================================================================
+
+
+ S T A R T O F T E M P O R A R Y M I M E C O D E
+
+
+ ===========================================================================
+ N O T E : The partNode structure will most likely be replaced by KMime.
+ It's purpose: Speed optimization for KDE 3. (khz, 28.11.01)
+ ===========================================================================
+*/
+
+partNode::partNode()
+ : mRoot( 0 ), mNext( 0 ), mChild( 0 ),
+ mWasProcessed( false ),
+ mDwPart( 0 ),
+ mType( DwMime::kTypeUnknown ),
+ mSubType( DwMime::kSubtypeUnknown ),
+ mEncryptionState( KMMsgNotEncrypted ),
+ mSignatureState( KMMsgNotSigned ),
+ mMsgPartOk( false ),
+ mEncodedOk( false ),
+ mDeleteDwBodyPart( false ),
+ mMimePartTreeItem( 0 ),
+ mBodyPartMemento( 0 )
+{
+ adjustDefaultType( this );
+}
+
+partNode::partNode( DwBodyPart* dwPart, int explicitType, int explicitSubType,
+ bool deleteDwBodyPart )
+ : mRoot( 0 ), mNext( 0 ), mChild( 0 ),
+ mWasProcessed( false ),
+ mDwPart( dwPart ),
+ mEncryptionState( KMMsgNotEncrypted ),
+ mSignatureState( KMMsgNotSigned ),
+ mMsgPartOk( false ),
+ mEncodedOk( false ),
+ mDeleteDwBodyPart( deleteDwBodyPart ),
+ mMimePartTreeItem( 0 ),
+ mBodyPartMemento( 0 )
+{
+ 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();
+ } else {
+ mType = DwMime::kTypeUnknown;
+ 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 ) {
+ if ( !msg )
+ return 0;
+
+ int mainType = msg->type();
+ int mainSubType = msg->subtype();
+ if( (DwMime::kTypeNull == mainType)
+ || (DwMime::kTypeUnknown == mainType) ){
+ mainType = DwMime::kTypeText;
+ mainSubType = DwMime::kSubtypePlain;
+ }
+
+ // we don't want to treat the top-level part special. mimelib does
+ // (Message vs. BodyPart, with common base class Entity). But we
+ // used DwBodyPart, not DwEntiy everywhere. *shrug*. DwStrings are
+ // subscrib-shared, so we just force mimelib to parse the whole mail
+ // as just another DwBodyPart...
+ DwBodyPart * mainBody = new DwBodyPart( *msg->getTopLevelPart() );
+
+ partNode * root = new partNode( mainBody, mainType, mainSubType, true );
+ root->buildObjectTree();
+
+ root->setFromAddress( msg->from() );
+ root->dump();
+ return root;
+}
+
+partNode::partNode( bool deleteDwBodyPart, DwBodyPart* dwPart )
+ : mRoot( 0 ), mNext( 0 ), mChild( 0 ),
+ mWasProcessed( false ),
+ mDwPart( dwPart ),
+ mEncryptionState( KMMsgNotEncrypted ),
+ mSignatureState( KMMsgNotSigned ),
+ mMsgPartOk( false ),
+ mEncodedOk( false ),
+ mDeleteDwBodyPart( deleteDwBodyPart ),
+ mMimePartTreeItem( 0 ),
+ mBodyPartMemento( 0 )
+{
+ if ( dwPart && dwPart->hasHeaders() && dwPart->Headers().HasContentType() ) {
+ mType = (!dwPart->Headers().ContentType().Type())?DwMime::kTypeUnknown:dwPart->Headers().ContentType().Type();
+ mSubType = dwPart->Headers().ContentType().Subtype();
+ } else {
+ mType = DwMime::kTypeUnknown;
+ mSubType = DwMime::kSubtypeUnknown;
+ }
+}
+
+partNode::~partNode() {
+ if( mDeleteDwBodyPart )
+ delete mDwPart;
+ mDwPart = 0;
+ delete mChild; mChild = 0;
+ delete mNext; mNext = 0;
+ delete mBodyPartMemento; mBodyPartMemento = 0;
+}
+
+#ifndef NDEBUG
+void partNode::dump( int chars ) const {
+ kdDebug(5006) << QString().fill( ' ', chars ) << "+ "
+ << typeString() << '/' << subTypeString() << endl;
+ if ( mChild )
+ mChild->dump( chars + 1 );
+ if ( mNext )
+ mNext->dump( chars );
+}
+#else
+void partNode::dump( int ) const {}
+#endif
+
+const QCString & partNode::encodedBody() {
+ if ( mEncodedOk )
+ return mEncodedBody;
+
+ if ( mDwPart )
+ mEncodedBody = KMail::Util::CString( mDwPart->Body().AsString() );
+ else
+ mEncodedBody = 0;
+ mEncodedOk = true;
+ return mEncodedBody;
+}
+
+
+void partNode::buildObjectTree( bool processSiblings )
+{
+ partNode* curNode = this;
+ while( curNode && curNode->dwPart() ) {
+ //dive into multipart messages
+ while( DwMime::kTypeMultipart == curNode->type() ) {
+ partNode * newNode = new partNode( curNode->dwPart()->Body().FirstBodyPart() );
+ curNode->setFirstChild( newNode );
+ curNode = newNode;
+ }
+ // go up in the tree until reaching a node with next
+ // (or the last top-level node)
+ while( curNode
+ && !( curNode->dwPart()
+ && curNode->dwPart()->Next() ) ) {
+ curNode = curNode->mRoot;
+ }
+ // we might have to leave when all children have been processed
+ if( this == curNode && !processSiblings )
+ return;
+ // store next node
+ if( curNode && curNode->dwPart() && curNode->dwPart()->Next() ) {
+ partNode* nextNode = new partNode( curNode->dwPart()->Next() );
+ curNode->setNext( nextNode );
+ curNode = nextNode;
+ } else
+ curNode = 0;
+ }
+}
+
+QCString partNode::typeString() const {
+ DwString s;
+ DwTypeEnumToStr( type(), s );
+ return s.c_str();
+}
+
+QCString partNode::subTypeString() const {
+ DwString s;
+ DwSubtypeEnumToStr( subType(), s );
+ return s.c_str();
+}
+
+int partNode::childCount() const {
+ int count = 0;
+ for ( partNode * child = firstChild() ; child ; child = child->nextSibling() )
+ ++ count;
+ return count;
+}
+
+QString partNode::contentTypeParameter( const char * name ) const {
+ if ( !mDwPart || !mDwPart->hasHeaders() )
+ return QString::null;
+ DwHeaders & headers = mDwPart->Headers();
+ if ( !headers.HasContentType() )
+ return QString::null;
+ DwString attr = name;
+ attr.ConvertToLowerCase();
+ for ( DwParameter * param = headers.ContentType().FirstParameter() ; param ; param = param->Next() ) {
+ DwString this_attr = param->Attribute();
+ this_attr.ConvertToLowerCase(); // what a braindead design!
+ if ( this_attr == attr )
+ return QString::fromLatin1( param->Value().data(), param->Value().size() );
+ // warning: misses rfc2231 handling!
+ }
+ return QString::null;
+}
+
+KMMsgEncryptionState partNode::overallEncryptionState() const
+{
+ KMMsgEncryptionState myState = KMMsgEncryptionStateUnknown;
+ if( mEncryptionState == KMMsgNotEncrypted ) {
+ // NOTE: children are tested ONLY when parent is not encrypted
+ if( mChild )
+ myState = mChild->overallEncryptionState();
+ else
+ myState = KMMsgNotEncrypted;
+ }
+ else { // part is partially or fully encrypted
+ myState = mEncryptionState;
+ }
+ // siblings are tested always
+ if( mNext ) {
+ KMMsgEncryptionState otherState = mNext->overallEncryptionState();
+ switch( otherState ) {
+ case KMMsgEncryptionStateUnknown:
+ break;
+ case KMMsgNotEncrypted:
+ if( myState == KMMsgFullyEncrypted )
+ myState = KMMsgPartiallyEncrypted;
+ else if( myState != KMMsgPartiallyEncrypted )
+ myState = KMMsgNotEncrypted;
+ break;
+ case KMMsgPartiallyEncrypted:
+ myState = KMMsgPartiallyEncrypted;
+ break;
+ case KMMsgFullyEncrypted:
+ if( myState != KMMsgFullyEncrypted )
+ myState = KMMsgPartiallyEncrypted;
+ break;
+ case KMMsgEncryptionProblematic:
+ break;
+ }
+ }
+
+//kdDebug(5006) << "\n\n KMMsgEncryptionState: " << myState << endl;
+
+ return myState;
+}
+
+
+KMMsgSignatureState partNode::overallSignatureState() const
+{
+ KMMsgSignatureState myState = KMMsgSignatureStateUnknown;
+ if( mSignatureState == KMMsgNotSigned ) {
+ // children are tested ONLY when parent is not signed
+ if( mChild )
+ myState = mChild->overallSignatureState();
+ else
+ myState = KMMsgNotSigned;
+ }
+ else { // part is partially or fully signed
+ myState = mSignatureState;
+ }
+ // siblings are tested always
+ if( mNext ) {
+ KMMsgSignatureState otherState = mNext->overallSignatureState();
+ switch( otherState ) {
+ case KMMsgSignatureStateUnknown:
+ break;
+ case KMMsgNotSigned:
+ if( myState == KMMsgFullySigned )
+ myState = KMMsgPartiallySigned;
+ else if( myState != KMMsgPartiallySigned )
+ myState = KMMsgNotSigned;
+ break;
+ case KMMsgPartiallySigned:
+ myState = KMMsgPartiallySigned;
+ break;
+ case KMMsgFullySigned:
+ if( myState != KMMsgFullySigned )
+ myState = KMMsgPartiallySigned;
+ break;
+ case KMMsgEncryptionProblematic:
+ break;
+ }
+ }
+
+//kdDebug(5006) << "\n\n KMMsgSignatureState: " << myState << endl;
+
+ return myState;
+}
+
+
+int partNode::nodeId() const
+{
+ int curId = 0;
+ partNode* rootNode = const_cast<partNode*>( this );
+ while( rootNode->mRoot )
+ rootNode = rootNode->mRoot;
+ return rootNode->calcNodeIdOrFindNode( curId, this, 0, 0 );
+}
+
+
+partNode* partNode::findId( int id )
+{
+ int curId = 0;
+ partNode* rootNode = this;
+ while( rootNode->mRoot )
+ rootNode = rootNode->mRoot;
+ partNode* foundNode;
+ rootNode->calcNodeIdOrFindNode( curId, 0, id, &foundNode );
+ return foundNode;
+}
+
+
+int partNode::calcNodeIdOrFindNode( int &curId, const partNode* findNode, int findId, partNode** foundNode )
+{
+ // We use the same algorithm to determine the id of a node and
+ // to find the node when id is known.
+ curId++;
+ // check for node ?
+ if( findNode && this == findNode )
+ return curId;
+ // check for id ?
+ if( foundNode && curId == findId ) {
+ *foundNode = this;
+ return curId;
+ }
+ if( mChild )
+ {
+ int res = mChild->calcNodeIdOrFindNode( curId, findNode, findId, foundNode );
+ if (res != -1) return res;
+ }
+ if( mNext )
+ return mNext->calcNodeIdOrFindNode( curId, findNode, findId, foundNode );
+
+ if( foundNode )
+ *foundNode = 0;
+ return -1;
+}
+
+
+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) )
+ && ( (subType == DwMime::kSubtypeUnknown)
+ || (subType == mSubType) ) )
+ return this;
+ if ( mChild && deep )
+ return mChild->findType( type, subType, deep, wide );
+ if ( mNext && wide )
+ return mNext->findType( type, subType, deep, wide );
+ return 0;
+}
+
+partNode* partNode::findNodeForDwPart( DwBodyPart* part )
+{
+ partNode* found = 0;
+ if( kasciistricmp( dwPart()->partId(), part->partId() ) == 0 )
+ return this;
+ if( mChild )
+ found = mChild->findNodeForDwPart( part );
+ if( mNext && !found )
+ found = mNext->findNodeForDwPart( part );
+ return found;
+}
+
+partNode* partNode::findTypeNot( int type, int subType, bool deep, bool wide )
+{
+ if( (mType != DwMime::kTypeUnknown)
+ && ( (type == DwMime::kTypeUnknown)
+ || (type != mType) )
+ && ( (subType == DwMime::kSubtypeUnknown)
+ || (subType != mSubType) ) )
+ return this;
+ if ( mChild && deep )
+ return mChild->findTypeNot( type, subType, deep, wide );
+ if ( mNext && wide )
+ return mNext->findTypeNot( type, subType, deep, wide );
+ return 0;
+}
+
+void partNode::fillMimePartTree( KMMimePartTreeItem* parentItem,
+ KMMimePartTree* mimePartTree,
+ QString labelDescr,
+ QString labelCntType,
+ QString labelEncoding,
+ KIO::filesize_t size,
+ bool revertOrder )
+{
+ if( parentItem || mimePartTree ) {
+
+ if( mNext )
+ mNext->fillMimePartTree( parentItem, mimePartTree,
+ QString::null, QString::null, QString::null, 0,
+ revertOrder );
+
+ QString cntDesc, cntType, cntEnc;
+ KIO::filesize_t cntSize = 0;
+
+ if( labelDescr.isEmpty() ) {
+ DwHeaders* headers = 0;
+ if( mDwPart && mDwPart->hasHeaders() )
+ headers = &mDwPart->Headers();
+ if( headers && headers->HasSubject() )
+ cntDesc = KMMsgBase::decodeRFC2047String( headers->Subject().AsString().c_str() );
+ if( headers && headers->HasContentType()) {
+ cntType = headers->ContentType().TypeStr().c_str();
+ cntType += '/';
+ cntType += headers->ContentType().SubtypeStr().c_str();
+ }
+ 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() ) {
+ if( mRoot && mRoot->mRoot )
+ cntDesc = i18n("internal part");
+ else
+ cntDesc = i18n("body part");
+ }
+ cntEnc = msgPart().contentTransferEncodingStr();
+ if( mDwPart )
+ cntSize = mDwPart->BodySize();
+ } else {
+ cntDesc = labelDescr;
+ cntType = labelCntType;
+ cntEnc = labelEncoding;
+ cntSize = size;
+ }
+ // remove linebreak+whitespace from folded Content-Description
+ cntDesc.replace( QRegExp("\\n\\s*"), " " );
+
+kdDebug(5006) << " Inserting one item into MimePartTree" << endl;
+kdDebug(5006) << " Content-Type: " << cntType << endl;
+ if( parentItem )
+ mMimePartTreeItem = new KMMimePartTreeItem( parentItem,
+ this,
+ cntDesc,
+ cntType,
+ cntEnc,
+ cntSize,
+ revertOrder );
+ else if( mimePartTree )
+ mMimePartTreeItem = new KMMimePartTreeItem( mimePartTree,
+ this,
+ cntDesc,
+ cntType,
+ cntEnc,
+ cntSize );
+ mMimePartTreeItem->setOpen( true );
+ if( mChild )
+ mChild->fillMimePartTree( mMimePartTreeItem, 0,
+ QString::null, QString::null, QString::null, 0,
+ revertOrder );
+
+ }
+}
+
+void partNode::adjustDefaultType( partNode* node )
+{
+ // Only bodies of 'Multipart/Digest' objects have
+ // default type 'Message/RfC822'. All other bodies
+ // have default type 'Text/Plain' (khz, 5.12.2001)
+ if( node && DwMime::kTypeUnknown == node->type() ) {
+ if( node->mRoot
+ && DwMime::kTypeMultipart == node->mRoot->type()
+ && DwMime::kSubtypeDigest == node->mRoot->subType() ) {
+ node->setType( DwMime::kTypeMessage );
+ node->setSubType( DwMime::kSubtypeRfc822 );
+ }
+ else
+ {
+ node->setType( DwMime::kTypeText );
+ node->setSubType( DwMime::kSubtypePlain );
+ }
+ }
+}
+
+bool partNode::isAttachment() const
+{
+ if( !dwPart() )
+ return false;
+ if ( !dwPart()->hasHeaders() )
+ return false;
+ DwHeaders& headers = dwPart()->Headers();
+ if( !headers.HasContentDisposition() )
+ return false;
+ return ( headers.ContentDisposition().DispositionType()
+ == DwMime::kDispTypeAttachment );
+}
+
+bool partNode::isHeuristicalAttachment() const {
+ if ( isAttachment() )
+ return true;
+ const KMMessagePart & p = msgPart();
+ return !p.fileName().isEmpty() || !p.name().isEmpty() ;
+}
+
+partNode * partNode::next( bool allowChildren ) const {
+ if ( allowChildren )
+ if ( partNode * c = firstChild() )
+ return c;
+ if ( partNode * s = nextSibling() )
+ return s;
+ for ( partNode * p = parentNode() ; p ; p = p->parentNode() )
+ if ( partNode * s = p->nextSibling() )
+ return s;
+ return 0;
+}
+
+bool partNode::isFirstTextPart() const {
+ if ( type() != DwMime::kTypeText )
+ return false;
+ const partNode * root = this;
+ // go up until we reach the root node of a message (of the actual message or
+ // of an attached message)
+ while ( const partNode * p = root->parentNode() ) {
+ if ( p->type() == DwMime::kTypeMessage )
+ break;
+ else
+ root = p;
+ }
+ for ( const partNode * n = root ; n ; n = n->next() )
+ if ( n->type() == DwMime::kTypeText )
+ return n == this;
+ kdFatal() << "partNode::isFirstTextPart(): Didn't expect to end up here..." << endl;
+ return false; // make comiler happy
+}
+
+bool partNode::hasContentDispositionInline() const
+{
+ if( !dwPart() )
+ return false;
+ DwHeaders& headers = dwPart()->Headers();
+ if( headers.HasContentDisposition() )
+ return ( headers.ContentDisposition().DispositionType()
+ == DwMime::kDispTypeInline );
+ else
+ return false;
+}
+
+const QString& partNode::trueFromAddress() const
+{
+ const partNode* node = this;
+ while( node->mFromAddress.isEmpty() && node->mRoot )
+ node = node->mRoot;
+ return node->mFromAddress;
+}
diff --git a/kmail/partNode.h b/kmail/partNode.h
new file mode 100644
index 00000000..9ca76ebf
--- /dev/null
+++ b/kmail/partNode.h
@@ -0,0 +1,259 @@
+/* -*- c++ -*-
+ partNode.h A node in a MIME tree.
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2002,2004 Klarlvdalens 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef PARTNODE_H
+#define PARTNODE_H
+
+#include "kmmsgpart.h"
+#include "kmmsgbase.h"
+#include "kmmessage.h"
+
+#include "interfaces/bodypart.h"
+
+#include <mimelib/mimepp.h>
+#include <mimelib/body.h>
+#include <mimelib/utility.h>
+
+#include <kio/global.h>
+#include <kdebug.h>
+
+class KMMimePartTreeItem;
+class KMMimePartTree;
+
+/*
+ ===========================================================================
+
+
+ S T A R T O F T E M P O R A R Y M I M E C O D E
+
+
+ ===========================================================================
+ N O T E : The partNode structure will most likely be replaced by KMime.
+ It's purpose: Speed optimization for KDE 3. (khz, 28.11.01)
+ ===========================================================================
+*/
+class partNode
+{
+ partNode();
+
+ int calcNodeIdOrFindNode( int& curId, const partNode* calcNode,
+ int findId, partNode** findNode );
+
+public:
+ static partNode * fromMessage( const KMMessage * msg );
+
+ partNode( DwBodyPart* dwPart,
+ int explicitType = DwMime::kTypeUnknown,
+ int explicitSubType = DwMime::kSubtypeUnknown,
+ bool deleteDwBodyPart = false );
+
+ partNode( bool deleteDwBodyPart,
+ DwBodyPart* dwPart );
+
+ ~partNode();
+
+ void dump( int chars=0 ) const;
+
+ void buildObjectTree( bool processSiblings=true );
+
+ DwBodyPart* dwPart() const {
+ return mDwPart;
+ }
+
+ void setDwPart( DwBodyPart* part ) {
+ mDwPart = part;
+ mMsgPartOk = false;
+ }
+
+ KMMessagePart& msgPart() const {
+ if( !mMsgPartOk ) {
+ KMMessage::bodyPart(mDwPart, &mMsgPart);
+ mMsgPartOk = true;
+ }
+ return mMsgPart;
+ }
+
+ const QCString & encodedBody();
+
+ void setType( int type ) {
+ mType = type;
+ }
+
+ void setSubType( int subType ) {
+ mSubType = subType;
+ }
+
+ int type() const {
+ return mType;
+ }
+
+ QCString typeString() const;
+
+ int subType() const {
+ return mSubType;
+ }
+
+ QCString subTypeString() const;
+
+ bool hasType( int type ) {
+ return mType == type;
+ }
+
+ bool hasSubType( int subType ) {
+ return mSubType == subType;
+ }
+
+ void setEncryptionState( KMMsgEncryptionState state ) {
+ mEncryptionState = state;
+ }
+ KMMsgEncryptionState encryptionState() const {
+ return mEncryptionState;
+ }
+
+ // look at the encryption states of all children and return result
+ KMMsgEncryptionState overallEncryptionState() const ;
+
+ // look at the signature states of all children and return result
+ KMMsgSignatureState overallSignatureState() const ;
+
+ void setSignatureState( KMMsgSignatureState state ) {
+ mSignatureState = state;
+ }
+ KMMsgSignatureState signatureState() const {
+ return mSignatureState;
+ }
+
+ 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.)
+
+ partNode* findType( int type, int subType, bool deep=true, bool wide=true );
+
+ partNode* findTypeNot( int type, int subType, bool deep=true,
+ bool wide=true );
+
+ partNode* findNodeForDwPart( DwBodyPart* part );
+
+ void fillMimePartTree( KMMimePartTreeItem* parentItem,
+ KMMimePartTree* mimePartTree,
+ QString labelDescr = QString::null,
+ QString labelCntType = QString::null,
+ QString labelEncoding = QString::null,
+ KIO::filesize_t size=0,
+ bool revertOrder = false );
+
+ void adjustDefaultType( partNode* node );
+
+ void setNext( partNode* next ) {
+ mNext = next;
+ if( mNext ){
+ mNext->mRoot = mRoot;
+ adjustDefaultType( mNext );
+ }
+ }
+
+ void setFirstChild( partNode* child ) {
+ mChild = child;
+ if( mChild ) {
+ mChild->mRoot = this;
+ adjustDefaultType( mChild );
+ }
+ }
+
+ void setProcessed( bool processed, bool recurse ) {
+ mWasProcessed = processed;
+ if ( recurse ) {
+ if( mChild )
+ mChild->setProcessed( processed, true );
+ if( mNext )
+ mNext->setProcessed( processed, true );
+ }
+ }
+
+ void setMimePartTreeItem( KMMimePartTreeItem* item ) {
+ mMimePartTreeItem = item;
+ }
+
+ KMMimePartTreeItem* mimePartTreeItem() {
+ return mMimePartTreeItem;
+ }
+
+ void setFromAddress( const QString& address ) {
+ mFromAddress = address;
+ }
+
+ bool isAttachment() const;
+ bool isHeuristicalAttachment() const;
+ /** returns true if this is the first text part of the message this node
+ is a body part of
+ */
+ bool isFirstTextPart() const;
+
+ bool hasContentDispositionInline() const;
+
+ QString contentTypeParameter( const char * name ) const;
+
+ const QString& trueFromAddress() 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;
+ bool processed() const { return mWasProcessed; }
+
+ KMail::Interface::BodyPartMemento * bodyPartMemento() const { return mBodyPartMemento; };
+ void setBodyPartMemento( KMail::Interface::BodyPartMemento * memento ) {
+ mBodyPartMemento = memento;
+ };
+
+private:
+ partNode* mRoot;
+ partNode* mNext;
+ partNode* mChild;
+ bool mWasProcessed; // to be used by parseObjectTree()
+private:
+ DwBodyPart* mDwPart; // may be zero
+ mutable KMMessagePart mMsgPart; // is valid - even if mDwPart is zero
+ QCString mEncodedBody;
+ QString mFromAddress;
+ int mType;
+ int mSubType;
+ KMMsgEncryptionState mEncryptionState;
+ KMMsgSignatureState mSignatureState;
+ mutable bool mMsgPartOk;
+ bool mEncodedOk;
+ bool mDeleteDwBodyPart;
+ KMMimePartTreeItem* mMimePartTreeItem;
+ KMail::Interface::BodyPartMemento * mBodyPartMemento;
+};
+
+#endif
diff --git a/kmail/partmetadata.h b/kmail/partmetadata.h
new file mode 100644
index 00000000..84e7d1f1
--- /dev/null
+++ b/kmail/partmetadata.h
@@ -0,0 +1,64 @@
+/* -*- c++ -*-
+ partmetadata.h
+
+ KMail, the KDE mail client.
+ Copyright (c) 2002-2003 Karl-Heinz Zimmer <khz@kde.org>
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License,
+ version 2.0, as published by the Free Software Foundation.
+ 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, US
+*/
+
+
+#ifndef _KMAIL_PARTMETADATA_H_
+#define _KMAIL_PARTMETADATA_H_
+
+#include <gpgmepp/verificationresult.h>
+
+#include <kpgp.h>
+#include <qstring.h>
+#include <qcstring.h>
+#include <qdatetime.h>
+
+namespace KMail {
+
+ class PartMetaData {
+ public:
+ PartMetaData()
+ : sigSummary( GpgME::Signature::None ),
+ isSigned( false ),
+ isGoodSignature( false ),
+ isEncrypted( false ),
+ isDecryptable( false ),
+ technicalProblem( false ),
+ isEncapsulatedRfc822Message( false )
+ {
+ }
+ GpgME::Signature::Summary sigSummary;
+ QString signClass;
+ QString signer;
+ QStringList signerMailAddresses;
+ QCString keyId;
+ Kpgp::Validity keyTrust;
+ QString status; // to be used for unknown plug-ins
+ int status_code; // to be used for i18n of OpenPGP and S/MIME CryptPlugs
+ QString errorText;
+ QDateTime creationTime;
+ QString decryptionError;
+ QString auditLog;
+ bool isSigned : 1;
+ bool isGoodSignature : 1;
+ bool isEncrypted : 1;
+ bool isDecryptable : 1;
+ bool technicalProblem : 1;
+ bool isEncapsulatedRfc822Message : 1;
+ };
+
+} // namespace KMail
+
+#endif // _KMAIL_PARTMETADATA_H_
+
diff --git a/kmail/partnodebodypart.cpp b/kmail/partnodebodypart.cpp
new file mode 100644
index 00000000..50ec3cda
--- /dev/null
+++ b/kmail/partnodebodypart.cpp
@@ -0,0 +1,98 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ partnodebodypart.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2004 Marc Mutz <mutz@kde.org>,
+ Ingo Kloecker <kloecker@kde.org>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "partnodebodypart.h"
+
+#include "partNode.h"
+
+#include <qstring.h>
+#include <qcstring.h>
+
+static int serial = 0;
+
+KMail::PartNodeBodyPart::PartNodeBodyPart( partNode & n, const QTextCodec * codec )
+ : KMail::Interface::BodyPart(), mPartNode( n ), mCodec( codec ),
+ mDefaultDisplay( KMail::Interface::BodyPart::None )
+{}
+
+QString KMail::PartNodeBodyPart::makeLink( const QString & path ) const {
+ static const int utf8 = 106;
+ // FIXME: use a PRNG for the first arg, instead of a serial number
+ return QString( "x-kmail:/bodypart/%1/%2/%3" )
+ .arg( serial++ ).arg( mPartNode.nodeId() )
+ .arg( KURL::encode_string_no_slash( path, utf8 ) );
+}
+
+QString KMail::PartNodeBodyPart::asText() const {
+ if ( mPartNode.type() != DwMime::kTypeText )
+ return QString::null;
+ return mPartNode.msgPart().bodyToUnicode( mCodec );
+}
+
+QByteArray KMail::PartNodeBodyPart::asBinary() const {
+ return mPartNode.msgPart().bodyDecodedBinary();
+}
+
+QString KMail::PartNodeBodyPart::contentTypeParameter( const char * param ) const {
+ return mPartNode.contentTypeParameter( param );
+}
+
+QString KMail::PartNodeBodyPart::contentDescription() const {
+ return mPartNode.msgPart().contentDescription();
+}
+
+QString KMail::PartNodeBodyPart::contentDispositionParameter( const char * ) const {
+ kdWarning( 5006 ) << "Sorry, not yet implemented: PartNodeBodyPart::contentDispositionParameter()" << endl;
+ return QString::null;
+}
+
+bool KMail::PartNodeBodyPart::hasCompleteBody() const {
+ kdWarning( 5006 ) << "Sorry, not yet implemented: PartNodeBodyPart::contentDispositionParameter()" << endl;
+ return true;
+}
+
+KMail::Interface::BodyPartMemento * KMail::PartNodeBodyPart::memento() const {
+ return mPartNode.bodyPartMemento();
+}
+
+void KMail::PartNodeBodyPart::setBodyPartMemento( Interface::BodyPartMemento * memento ) {
+ mPartNode.setBodyPartMemento( memento );
+}
+
+KMail::Interface::BodyPart::Display KMail::PartNodeBodyPart::defaultDisplay() const {
+ return mDefaultDisplay;
+}
+
+void KMail::PartNodeBodyPart::setDefaultDisplay( KMail::Interface::BodyPart::Display d ){
+ mDefaultDisplay = d;
+}
diff --git a/kmail/partnodebodypart.h b/kmail/partnodebodypart.h
new file mode 100644
index 00000000..8421650e
--- /dev/null
+++ b/kmail/partnodebodypart.h
@@ -0,0 +1,74 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ partnodebodypart.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2004 Marc Mutz <mutz@kde.org>,
+ Ingo Kloecker <kloecker@kde.org>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_BODYPART_H_
+#define __KMAIL_BODYPART_H_
+
+#include "interfaces/bodypart.h"
+
+class partNode;
+
+class QTextCodec;
+
+namespace KMail {
+
+ /**
+ @short an implemenation of the BodyPart interface using partNodes
+ */
+ class PartNodeBodyPart : public Interface::BodyPart {
+ public:
+ PartNodeBodyPart( partNode & n, const QTextCodec * codec=0 );
+
+ QString makeLink( const QString & path ) const;
+ QString asText() const;
+ QByteArray asBinary() const;
+ QString contentTypeParameter( const char * param ) const;
+ QString contentDescription() const;
+ //int contentDisposition() const;
+ QString contentDispositionParameter( const char * param ) const;
+ bool hasCompleteBody() const;
+
+ Interface::BodyPartMemento * memento() const;
+ void setBodyPartMemento( Interface::BodyPartMemento * memento );
+ BodyPart::Display defaultDisplay() const;
+ void setDefaultDisplay( BodyPart::Display );
+
+ private:
+ partNode & mPartNode;
+ const QTextCodec * mCodec;
+ BodyPart::Display mDefaultDisplay;
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_BODYPART_H_
diff --git a/kmail/pics/Makefile.am b/kmail/pics/Makefile.am
new file mode 100644
index 00000000..b1e1c368
--- /dev/null
+++ b/kmail/pics/Makefile.am
@@ -0,0 +1,37 @@
+SUBDIRS = icons
+
+pics_DATA = kmmsgdel.png kmmsgnew.png kmmsgunseen.png kmmsgread.png \
+ kmmsgreplied.png kmmsgforwarded.png kmmsgqueued.png kmmsgflag.png \
+ kmmsgsent.png kmmsgwatched.png kmmsgignored.png kmmsgtodo.png \
+ kmmsgread_fwd.png kmmsgread_replied.png kmmsgread_fwd_replied.png \
+ pgp-keys.png \
+ kmmsgpartiallyencrypted.png \
+ kmmsgpartiallysigned.png kmmsgfullyencrypted.png \
+ kmmsgfullysigned.png kmmsgundefinedencrypted.png \
+ kmmsgundefinedsigned.png \
+ kmmsgspam.png kmmsgham.png kmmsgattachment.png \
+ kmwizard.png \
+ quotecollapse.png quoteexpand.png \
+ enterprise_bottom_left.png \
+ enterprise_bottom.png \
+ enterprise_bottom_right.png \
+ enterprise_icon.png \
+ enterprise_left.png \
+ enterprise_right.png \
+ enterprise_s_left.png \
+ enterprise_sbar.png \
+ enterprise_s_right.png \
+ enterprise_sp_right.png \
+ enterprise_top_left.png \
+ enterprise_top.png \
+ enterprise_top_right.png \
+ enterprise_sw.png \
+ enterprise_w.png \
+ attachmentQuicklistClosed.png \
+ attachmentQuicklistOpened.png
+
+
+picsdir = $(kde_datadir)/kmail/pics
+
+EXTRA_DIST = $(pics_DATA)
+
diff --git a/kmail/pics/attachmentQuicklistClosed.png b/kmail/pics/attachmentQuicklistClosed.png
new file mode 100644
index 00000000..b3599ba3
--- /dev/null
+++ b/kmail/pics/attachmentQuicklistClosed.png
Binary files differ
diff --git a/kmail/pics/attachmentQuicklistOpened.png b/kmail/pics/attachmentQuicklistOpened.png
new file mode 100644
index 00000000..aaf56156
--- /dev/null
+++ b/kmail/pics/attachmentQuicklistOpened.png
Binary files differ
diff --git a/kmail/pics/enterprise_bottom.png b/kmail/pics/enterprise_bottom.png
new file mode 100644
index 00000000..9982b8ec
--- /dev/null
+++ b/kmail/pics/enterprise_bottom.png
Binary files differ
diff --git a/kmail/pics/enterprise_bottom_left.png b/kmail/pics/enterprise_bottom_left.png
new file mode 100644
index 00000000..499f7b07
--- /dev/null
+++ b/kmail/pics/enterprise_bottom_left.png
Binary files differ
diff --git a/kmail/pics/enterprise_bottom_right.png b/kmail/pics/enterprise_bottom_right.png
new file mode 100644
index 00000000..7646ed87
--- /dev/null
+++ b/kmail/pics/enterprise_bottom_right.png
Binary files differ
diff --git a/kmail/pics/enterprise_icon.png b/kmail/pics/enterprise_icon.png
new file mode 100644
index 00000000..6dabf375
--- /dev/null
+++ b/kmail/pics/enterprise_icon.png
Binary files differ
diff --git a/kmail/pics/enterprise_left.png b/kmail/pics/enterprise_left.png
new file mode 100644
index 00000000..3712d7bc
--- /dev/null
+++ b/kmail/pics/enterprise_left.png
Binary files differ
diff --git a/kmail/pics/enterprise_right.png b/kmail/pics/enterprise_right.png
new file mode 100644
index 00000000..31acd371
--- /dev/null
+++ b/kmail/pics/enterprise_right.png
Binary files differ
diff --git a/kmail/pics/enterprise_s_left.png b/kmail/pics/enterprise_s_left.png
new file mode 100644
index 00000000..c672b238
--- /dev/null
+++ b/kmail/pics/enterprise_s_left.png
Binary files differ
diff --git a/kmail/pics/enterprise_s_right.png b/kmail/pics/enterprise_s_right.png
new file mode 100644
index 00000000..1bff2b7a
--- /dev/null
+++ b/kmail/pics/enterprise_s_right.png
Binary files differ
diff --git a/kmail/pics/enterprise_sbar.png b/kmail/pics/enterprise_sbar.png
new file mode 100644
index 00000000..1e2329e8
--- /dev/null
+++ b/kmail/pics/enterprise_sbar.png
Binary files differ
diff --git a/kmail/pics/enterprise_sp_right.png b/kmail/pics/enterprise_sp_right.png
new file mode 100644
index 00000000..1fde5980
--- /dev/null
+++ b/kmail/pics/enterprise_sp_right.png
Binary files differ
diff --git a/kmail/pics/enterprise_sw.png b/kmail/pics/enterprise_sw.png
new file mode 100644
index 00000000..428eae16
--- /dev/null
+++ b/kmail/pics/enterprise_sw.png
Binary files differ
diff --git a/kmail/pics/enterprise_top.png b/kmail/pics/enterprise_top.png
new file mode 100644
index 00000000..e08ebef7
--- /dev/null
+++ b/kmail/pics/enterprise_top.png
Binary files differ
diff --git a/kmail/pics/enterprise_top_left.png b/kmail/pics/enterprise_top_left.png
new file mode 100644
index 00000000..e851bdda
--- /dev/null
+++ b/kmail/pics/enterprise_top_left.png
Binary files differ
diff --git a/kmail/pics/enterprise_top_right.png b/kmail/pics/enterprise_top_right.png
new file mode 100644
index 00000000..b2175c9f
--- /dev/null
+++ b/kmail/pics/enterprise_top_right.png
Binary files differ
diff --git a/kmail/pics/enterprise_w.png b/kmail/pics/enterprise_w.png
new file mode 100644
index 00000000..bf5aa3bd
--- /dev/null
+++ b/kmail/pics/enterprise_w.png
Binary files differ
diff --git a/kmail/pics/icons/Makefile.am b/kmail/pics/icons/Makefile.am
new file mode 100644
index 00000000..b606ede9
--- /dev/null
+++ b/kmail/pics/icons/Makefile.am
@@ -0,0 +1,2 @@
+kmailicondir = $(kde_datadir)/kmail/icons
+kmailicon_ICON = AUTO
diff --git a/kmail/pics/icons/cr128-action-online_status.png b/kmail/pics/icons/cr128-action-online_status.png
new file mode 100644
index 00000000..9fb0d1fb
--- /dev/null
+++ b/kmail/pics/icons/cr128-action-online_status.png
Binary files differ
diff --git a/kmail/pics/icons/cr16-action-kmgroupware_folder_calendar.png b/kmail/pics/icons/cr16-action-kmgroupware_folder_calendar.png
new file mode 100644
index 00000000..ca55f907
--- /dev/null
+++ b/kmail/pics/icons/cr16-action-kmgroupware_folder_calendar.png
Binary files differ
diff --git a/kmail/pics/icons/cr16-action-kmgroupware_folder_contacts.png b/kmail/pics/icons/cr16-action-kmgroupware_folder_contacts.png
new file mode 100644
index 00000000..31b6f339
--- /dev/null
+++ b/kmail/pics/icons/cr16-action-kmgroupware_folder_contacts.png
Binary files differ
diff --git a/kmail/pics/icons/cr16-action-kmgroupware_folder_journals.png b/kmail/pics/icons/cr16-action-kmgroupware_folder_journals.png
new file mode 100644
index 00000000..326deb2c
--- /dev/null
+++ b/kmail/pics/icons/cr16-action-kmgroupware_folder_journals.png
Binary files differ
diff --git a/kmail/pics/icons/cr16-action-kmgroupware_folder_notes.png b/kmail/pics/icons/cr16-action-kmgroupware_folder_notes.png
new file mode 100644
index 00000000..0ac606c9
--- /dev/null
+++ b/kmail/pics/icons/cr16-action-kmgroupware_folder_notes.png
Binary files differ
diff --git a/kmail/pics/icons/cr16-action-kmgroupware_folder_tasks.png b/kmail/pics/icons/cr16-action-kmgroupware_folder_tasks.png
new file mode 100644
index 00000000..3fec7b21
--- /dev/null
+++ b/kmail/pics/icons/cr16-action-kmgroupware_folder_tasks.png
Binary files differ
diff --git a/kmail/pics/icons/cr16-action-mail_flag.png b/kmail/pics/icons/cr16-action-mail_flag.png
new file mode 100644
index 00000000..3240b29d
--- /dev/null
+++ b/kmail/pics/icons/cr16-action-mail_flag.png
Binary files differ
diff --git a/kmail/pics/icons/cr16-action-mail_ham.png b/kmail/pics/icons/cr16-action-mail_ham.png
new file mode 100644
index 00000000..543710fb
--- /dev/null
+++ b/kmail/pics/icons/cr16-action-mail_ham.png
Binary files differ
diff --git a/kmail/pics/icons/cr16-action-mail_ignore.png b/kmail/pics/icons/cr16-action-mail_ignore.png
new file mode 100644
index 00000000..6f75c09a
--- /dev/null
+++ b/kmail/pics/icons/cr16-action-mail_ignore.png
Binary files differ
diff --git a/kmail/pics/icons/cr16-action-mail_spam.png b/kmail/pics/icons/cr16-action-mail_spam.png
new file mode 100644
index 00000000..785f62e9
--- /dev/null
+++ b/kmail/pics/icons/cr16-action-mail_spam.png
Binary files differ
diff --git a/kmail/pics/icons/cr16-action-mail_todo.png b/kmail/pics/icons/cr16-action-mail_todo.png
new file mode 100644
index 00000000..db0dae30
--- /dev/null
+++ b/kmail/pics/icons/cr16-action-mail_todo.png
Binary files differ
diff --git a/kmail/pics/icons/cr16-action-online_status.png b/kmail/pics/icons/cr16-action-online_status.png
new file mode 100644
index 00000000..3c5dbdf0
--- /dev/null
+++ b/kmail/pics/icons/cr16-action-online_status.png
Binary files differ
diff --git a/kmail/pics/icons/cr22-action-kmgroupware_folder_calendar.png b/kmail/pics/icons/cr22-action-kmgroupware_folder_calendar.png
new file mode 100644
index 00000000..146be66f
--- /dev/null
+++ b/kmail/pics/icons/cr22-action-kmgroupware_folder_calendar.png
Binary files differ
diff --git a/kmail/pics/icons/cr22-action-kmgroupware_folder_journals.png b/kmail/pics/icons/cr22-action-kmgroupware_folder_journals.png
new file mode 100644
index 00000000..52335816
--- /dev/null
+++ b/kmail/pics/icons/cr22-action-kmgroupware_folder_journals.png
Binary files differ
diff --git a/kmail/pics/icons/cr22-action-kmgroupware_folder_tasks.png b/kmail/pics/icons/cr22-action-kmgroupware_folder_tasks.png
new file mode 100644
index 00000000..4c07468d
--- /dev/null
+++ b/kmail/pics/icons/cr22-action-kmgroupware_folder_tasks.png
Binary files differ
diff --git a/kmail/pics/icons/cr22-action-mail_ham.png b/kmail/pics/icons/cr22-action-mail_ham.png
new file mode 100644
index 00000000..31c064ba
--- /dev/null
+++ b/kmail/pics/icons/cr22-action-mail_ham.png
Binary files differ
diff --git a/kmail/pics/icons/cr22-action-mail_ignore.png b/kmail/pics/icons/cr22-action-mail_ignore.png
new file mode 100644
index 00000000..4376a621
--- /dev/null
+++ b/kmail/pics/icons/cr22-action-mail_ignore.png
Binary files differ
diff --git a/kmail/pics/icons/cr22-action-mail_spam.png b/kmail/pics/icons/cr22-action-mail_spam.png
new file mode 100644
index 00000000..a629fe48
--- /dev/null
+++ b/kmail/pics/icons/cr22-action-mail_spam.png
Binary files differ
diff --git a/kmail/pics/icons/cr22-action-online_status.png b/kmail/pics/icons/cr22-action-online_status.png
new file mode 100644
index 00000000..eb61b0f3
--- /dev/null
+++ b/kmail/pics/icons/cr22-action-online_status.png
Binary files differ
diff --git a/kmail/pics/icons/cr32-action-kmgroupware_folder_calendar.png b/kmail/pics/icons/cr32-action-kmgroupware_folder_calendar.png
new file mode 100644
index 00000000..97bc5cdf
--- /dev/null
+++ b/kmail/pics/icons/cr32-action-kmgroupware_folder_calendar.png
Binary files differ
diff --git a/kmail/pics/icons/cr32-action-kmgroupware_folder_tasks.png b/kmail/pics/icons/cr32-action-kmgroupware_folder_tasks.png
new file mode 100644
index 00000000..cec78315
--- /dev/null
+++ b/kmail/pics/icons/cr32-action-kmgroupware_folder_tasks.png
Binary files differ
diff --git a/kmail/pics/icons/cr32-action-mail_ham.png b/kmail/pics/icons/cr32-action-mail_ham.png
new file mode 100644
index 00000000..230de530
--- /dev/null
+++ b/kmail/pics/icons/cr32-action-mail_ham.png
Binary files differ
diff --git a/kmail/pics/icons/cr32-action-mail_ignore.png b/kmail/pics/icons/cr32-action-mail_ignore.png
new file mode 100644
index 00000000..6ef61fb5
--- /dev/null
+++ b/kmail/pics/icons/cr32-action-mail_ignore.png
Binary files differ
diff --git a/kmail/pics/icons/cr32-action-mail_spam.png b/kmail/pics/icons/cr32-action-mail_spam.png
new file mode 100644
index 00000000..6e49330a
--- /dev/null
+++ b/kmail/pics/icons/cr32-action-mail_spam.png
Binary files differ
diff --git a/kmail/pics/icons/cr32-action-online_status.png b/kmail/pics/icons/cr32-action-online_status.png
new file mode 100644
index 00000000..a3ee15c4
--- /dev/null
+++ b/kmail/pics/icons/cr32-action-online_status.png
Binary files differ
diff --git a/kmail/pics/icons/cr48-action-online_status.png b/kmail/pics/icons/cr48-action-online_status.png
new file mode 100644
index 00000000..76e8f92f
--- /dev/null
+++ b/kmail/pics/icons/cr48-action-online_status.png
Binary files differ
diff --git a/kmail/pics/icons/crsc-action-mail_ignore.svgz b/kmail/pics/icons/crsc-action-mail_ignore.svgz
new file mode 100644
index 00000000..9dde0429
--- /dev/null
+++ b/kmail/pics/icons/crsc-action-mail_ignore.svgz
Binary files differ
diff --git a/kmail/pics/icons/crsc-action-online_status.svgz b/kmail/pics/icons/crsc-action-online_status.svgz
new file mode 100644
index 00000000..b640e95b
--- /dev/null
+++ b/kmail/pics/icons/crsc-action-online_status.svgz
Binary files differ
diff --git a/kmail/pics/kmmsgattachment.png b/kmail/pics/kmmsgattachment.png
new file mode 100644
index 00000000..cfe40b03
--- /dev/null
+++ b/kmail/pics/kmmsgattachment.png
Binary files differ
diff --git a/kmail/pics/kmmsgdel.png b/kmail/pics/kmmsgdel.png
new file mode 100644
index 00000000..2de77754
--- /dev/null
+++ b/kmail/pics/kmmsgdel.png
Binary files differ
diff --git a/kmail/pics/kmmsgencryptionproblematic.png b/kmail/pics/kmmsgencryptionproblematic.png
new file mode 100644
index 00000000..be38cda7
--- /dev/null
+++ b/kmail/pics/kmmsgencryptionproblematic.png
Binary files differ
diff --git a/kmail/pics/kmmsgflag.png b/kmail/pics/kmmsgflag.png
new file mode 100644
index 00000000..c3c3da37
--- /dev/null
+++ b/kmail/pics/kmmsgflag.png
Binary files differ
diff --git a/kmail/pics/kmmsgforwarded.png b/kmail/pics/kmmsgforwarded.png
new file mode 100644
index 00000000..bcc52441
--- /dev/null
+++ b/kmail/pics/kmmsgforwarded.png
Binary files differ
diff --git a/kmail/pics/kmmsgfullyencrypted.png b/kmail/pics/kmmsgfullyencrypted.png
new file mode 100644
index 00000000..258433d6
--- /dev/null
+++ b/kmail/pics/kmmsgfullyencrypted.png
Binary files differ
diff --git a/kmail/pics/kmmsgfullysigned.png b/kmail/pics/kmmsgfullysigned.png
new file mode 100644
index 00000000..bf74bc94
--- /dev/null
+++ b/kmail/pics/kmmsgfullysigned.png
Binary files differ
diff --git a/kmail/pics/kmmsgham.png b/kmail/pics/kmmsgham.png
new file mode 100644
index 00000000..3a1d1219
--- /dev/null
+++ b/kmail/pics/kmmsgham.png
Binary files differ
diff --git a/kmail/pics/kmmsgignored.png b/kmail/pics/kmmsgignored.png
new file mode 100644
index 00000000..8b965854
--- /dev/null
+++ b/kmail/pics/kmmsgignored.png
Binary files differ
diff --git a/kmail/pics/kmmsgnew.png b/kmail/pics/kmmsgnew.png
new file mode 100644
index 00000000..245d1569
--- /dev/null
+++ b/kmail/pics/kmmsgnew.png
Binary files differ
diff --git a/kmail/pics/kmmsgpartiallyencrypted.png b/kmail/pics/kmmsgpartiallyencrypted.png
new file mode 100644
index 00000000..a6425294
--- /dev/null
+++ b/kmail/pics/kmmsgpartiallyencrypted.png
Binary files differ
diff --git a/kmail/pics/kmmsgpartiallysigned.png b/kmail/pics/kmmsgpartiallysigned.png
new file mode 100644
index 00000000..7af30986
--- /dev/null
+++ b/kmail/pics/kmmsgpartiallysigned.png
Binary files differ
diff --git a/kmail/pics/kmmsgqueued.png b/kmail/pics/kmmsgqueued.png
new file mode 100644
index 00000000..78d6161f
--- /dev/null
+++ b/kmail/pics/kmmsgqueued.png
Binary files differ
diff --git a/kmail/pics/kmmsgread.png b/kmail/pics/kmmsgread.png
new file mode 100644
index 00000000..fabb0888
--- /dev/null
+++ b/kmail/pics/kmmsgread.png
Binary files differ
diff --git a/kmail/pics/kmmsgread_fwd.png b/kmail/pics/kmmsgread_fwd.png
new file mode 100644
index 00000000..c6bf74e1
--- /dev/null
+++ b/kmail/pics/kmmsgread_fwd.png
Binary files differ
diff --git a/kmail/pics/kmmsgread_fwd_replied.png b/kmail/pics/kmmsgread_fwd_replied.png
new file mode 100644
index 00000000..1b70af92
--- /dev/null
+++ b/kmail/pics/kmmsgread_fwd_replied.png
Binary files differ
diff --git a/kmail/pics/kmmsgread_replied.png b/kmail/pics/kmmsgread_replied.png
new file mode 100644
index 00000000..6013f908
--- /dev/null
+++ b/kmail/pics/kmmsgread_replied.png
Binary files differ
diff --git a/kmail/pics/kmmsgreplied.png b/kmail/pics/kmmsgreplied.png
new file mode 100644
index 00000000..685ab730
--- /dev/null
+++ b/kmail/pics/kmmsgreplied.png
Binary files differ
diff --git a/kmail/pics/kmmsgsent.png b/kmail/pics/kmmsgsent.png
new file mode 100644
index 00000000..c7e2f16a
--- /dev/null
+++ b/kmail/pics/kmmsgsent.png
Binary files differ
diff --git a/kmail/pics/kmmsgsignatureproblematic.png b/kmail/pics/kmmsgsignatureproblematic.png
new file mode 100644
index 00000000..45ec8777
--- /dev/null
+++ b/kmail/pics/kmmsgsignatureproblematic.png
Binary files differ
diff --git a/kmail/pics/kmmsgspam.png b/kmail/pics/kmmsgspam.png
new file mode 100644
index 00000000..5f3e20f1
--- /dev/null
+++ b/kmail/pics/kmmsgspam.png
Binary files differ
diff --git a/kmail/pics/kmmsgtodo.png b/kmail/pics/kmmsgtodo.png
new file mode 100644
index 00000000..db0dae30
--- /dev/null
+++ b/kmail/pics/kmmsgtodo.png
Binary files differ
diff --git a/kmail/pics/kmmsgundefinedencrypted.png b/kmail/pics/kmmsgundefinedencrypted.png
new file mode 100644
index 00000000..e7e555a0
--- /dev/null
+++ b/kmail/pics/kmmsgundefinedencrypted.png
Binary files differ
diff --git a/kmail/pics/kmmsgundefinedsigned.png b/kmail/pics/kmmsgundefinedsigned.png
new file mode 100644
index 00000000..2699cd0f
--- /dev/null
+++ b/kmail/pics/kmmsgundefinedsigned.png
Binary files differ
diff --git a/kmail/pics/kmmsgunseen.png b/kmail/pics/kmmsgunseen.png
new file mode 100644
index 00000000..8e3de202
--- /dev/null
+++ b/kmail/pics/kmmsgunseen.png
Binary files differ
diff --git a/kmail/pics/kmmsgwatched.png b/kmail/pics/kmmsgwatched.png
new file mode 100644
index 00000000..0b8f61f8
--- /dev/null
+++ b/kmail/pics/kmmsgwatched.png
Binary files differ
diff --git a/kmail/pics/kmwizard.png b/kmail/pics/kmwizard.png
new file mode 100644
index 00000000..134873b7
--- /dev/null
+++ b/kmail/pics/kmwizard.png
Binary files differ
diff --git a/kmail/pics/kmwizard.svg b/kmail/pics/kmwizard.svg
new file mode 100644
index 00000000..0fd2829d
--- /dev/null
+++ b/kmail/pics/kmwizard.svg
@@ -0,0 +1,1875 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:xml="http://www.w3.org/XML/1998/namespace"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="210mm"
+ height="297mm"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.41"
+ sodipodi:docbase="/home/tina/Desktop"
+ sodipodi:docname="umschlag.svg"
+ inkscape:export-filename="/home/tina/Desktop/spamfilter.png"
+ inkscape:export-xdpi="30.240000"
+ inkscape:export-ydpi="30.240000">
+ <defs
+ id="defs3">
+ <linearGradient
+ id="linearGradient6682">
+ <stop
+ style="stop-color:#818181;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop6684" />
+ <stop
+ style="stop-color:#a4a4d6;stop-opacity:1.0000000;"
+ offset="0.39092737"
+ id="stop6686" />
+ <stop
+ style="stop-color:#6d6dd2;stop-opacity:1.0000000;"
+ offset="0.64374995"
+ id="stop6688" />
+ <stop
+ style="stop-color:#1c1e66;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop6690" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5898">
+ <stop
+ style="stop-color:#fffe3c;stop-opacity:0.81443298;"
+ offset="0.0000000"
+ id="stop5900" />
+ <stop
+ style="stop-color:#ffff54;stop-opacity:0.50515461;"
+ offset="1.0000000"
+ id="stop5906" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3087">
+ <stop
+ style="stop-color:#4a5799;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop3089" />
+ <stop
+ style="stop-color:#242736;stop-opacity:0;"
+ offset="1"
+ id="stop3091" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2182">
+ <stop
+ style="stop-color:#54de3d;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop2184" />
+ <stop
+ style="stop-color:#48de34;stop-opacity:1.0000000;"
+ offset="0.29429376"
+ id="stop2194" />
+ <stop
+ style="stop-color:#9eeea9;stop-opacity:1.0000000;"
+ offset="0.77987844"
+ id="stop2210" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop2186" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2096">
+ <stop
+ style="stop-color:#3f7cc0;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop2098" />
+ <stop
+ style="stop-color:#9fbddf;stop-opacity:1.0000000;"
+ offset="0.39622641"
+ id="stop6692" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop2102" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2060">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop2062" />
+ <stop
+ style="stop-color:#d4d3ff;stop-opacity:0.67450980;"
+ offset="0.0000000"
+ id="stop3358" />
+ <stop
+ style="stop-color:#c9c0ff;stop-opacity:0.35051546;"
+ offset="0.29089755"
+ id="stop2080" />
+ <stop
+ style="stop-color:#9da1ef;stop-opacity:0.52549020;"
+ offset="0.62727022"
+ id="stop3354" />
+ <stop
+ style="stop-color:#8792e7;stop-opacity:1.0000000;"
+ offset="0.86753637"
+ id="stop3356" />
+ <stop
+ style="stop-color:#7183e0;stop-opacity:0.70196080;"
+ offset="1.0000000"
+ id="stop2064" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2052">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop2054" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop2056" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2182"
+ id="linearGradient2258"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="pad"
+ x1="247.92247"
+ y1="176.20723"
+ x2="245.86125"
+ y2="120.85145" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2060"
+ id="linearGradient3034"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.126199,0.000000,2.401959e-16,0.887942,727.7790,559.0186)"
+ spreadMethod="pad"
+ x1="254.73114"
+ y1="196.68800"
+ x2="259.43448"
+ y2="408.85126" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2096"
+ id="linearGradient3036"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.489235,0.000000,2.179591e-16,0.805739,727.7790,538.0115)"
+ x1="178.78366"
+ y1="143.67641"
+ x2="178.78354"
+ y2="345.06073" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2060"
+ id="linearGradient3038"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.422751,0.000000,2.006954e-16,0.741919,737.4919,549.2401)"
+ x1="178.27229"
+ y1="166.46916"
+ x2="178.27234"
+ y2="306.36243" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2060"
+ id="linearGradient3042"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.126199,0.000000,5.764703e-16,0.887942,727.7790,559.0186)"
+ spreadMethod="pad"
+ x1="254.73114"
+ y1="196.68800"
+ x2="259.43448"
+ y2="408.85126" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2096"
+ id="linearGradient3044"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.489235,0.000000,5.231017e-16,0.805739,727.7790,538.0115)"
+ x1="178.78366"
+ y1="143.67641"
+ x2="178.78354"
+ y2="345.06073" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2060"
+ id="linearGradient3046"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.422751,0.000000,4.816691e-16,0.741919,737.4919,549.2401)"
+ x1="178.27229"
+ y1="166.46916"
+ x2="178.27234"
+ y2="306.36243" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2060"
+ id="linearGradient3050"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.126199,0.000000,8.166663e-16,0.887942,727.7790,559.0186)"
+ spreadMethod="pad"
+ x1="254.73114"
+ y1="196.68800"
+ x2="259.43448"
+ y2="408.85126" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2096"
+ id="linearGradient3052"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.489235,0.000000,7.410607e-16,0.805739,727.7790,538.0115)"
+ x1="178.78366"
+ y1="143.67641"
+ x2="178.78354"
+ y2="345.06073" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2060"
+ id="linearGradient3054"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.422751,0.000000,6.823646e-16,0.741919,737.4919,549.2401)"
+ x1="178.27229"
+ y1="166.46916"
+ x2="178.27234"
+ y2="306.36243" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2060"
+ id="linearGradient3058"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.126199,0.000000,1.056862e-15,0.887942,727.7790,559.0186)"
+ spreadMethod="pad"
+ x1="254.73114"
+ y1="196.68800"
+ x2="259.43448"
+ y2="408.85126" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2096"
+ id="linearGradient3060"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.489235,0.000000,9.590197e-16,0.805739,727.7790,538.0115)"
+ x1="178.78366"
+ y1="143.67641"
+ x2="178.78354"
+ y2="345.06073" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2060"
+ id="linearGradient3062"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.422751,0.000000,8.830601e-16,0.741919,737.4919,549.2401)"
+ x1="178.27229"
+ y1="166.46916"
+ x2="178.27234"
+ y2="306.36243" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2060"
+ id="linearGradient3085"
+ gradientTransform="matrix(0.221676,0.000000,0.000000,0.282397,1711.212,935.0857)"
+ x1="-1424.7039"
+ y1="598.11609"
+ x2="-355.81067"
+ y2="593.29181"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3087"
+ id="radialGradient3093"
+ gradientTransform="scale(1.885255,0.530432)"
+ cx="-281.49677"
+ cy="793.81860"
+ fx="-282.59509"
+ fy="785.91730"
+ r="231.19614"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2182"
+ id="linearGradient4912"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="pad"
+ x1="247.92247"
+ y1="176.20723"
+ x2="245.86125"
+ y2="120.85145" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2060"
+ id="linearGradient4914"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.126199,0.000000,0.000000,0.887942,220.0355,607.2400)"
+ spreadMethod="pad"
+ x1="254.73114"
+ y1="196.68800"
+ x2="259.43448"
+ y2="408.85126" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2096"
+ id="linearGradient4916"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.489235,0.000000,0.000000,0.805739,220.0355,586.2329)"
+ x1="178.78366"
+ y1="143.67641"
+ x2="178.78354"
+ y2="345.06073" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2060"
+ id="linearGradient4918"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.422751,0.000000,0.000000,0.741919,229.7484,597.4615)"
+ x1="178.27229"
+ y1="166.46916"
+ x2="178.27234"
+ y2="306.36243" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2060"
+ id="linearGradient4940"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.126199,0.000000,0.000000,0.887942,220.0355,607.2400)"
+ spreadMethod="pad"
+ x1="254.73114"
+ y1="196.68800"
+ x2="259.43448"
+ y2="408.85126" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2096"
+ id="linearGradient4942"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.489235,0.000000,0.000000,0.805739,220.0355,586.2329)"
+ x1="178.78366"
+ y1="143.67641"
+ x2="178.78354"
+ y2="345.06073" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2060"
+ id="linearGradient4944"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.422751,0.000000,0.000000,0.741919,229.7484,597.4615)"
+ x1="178.27229"
+ y1="166.46916"
+ x2="178.27234"
+ y2="306.36243" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2182"
+ id="linearGradient4990"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="pad"
+ x1="247.92247"
+ y1="176.20723"
+ x2="245.86125"
+ y2="120.85145" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2182"
+ id="linearGradient4992"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="pad"
+ x1="247.92247"
+ y1="176.20723"
+ x2="245.86125"
+ y2="120.85145" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2182"
+ id="linearGradient4994"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="pad"
+ x1="247.92247"
+ y1="176.20723"
+ x2="245.86125"
+ y2="120.85145" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2182"
+ id="linearGradient4996"
+ gradientUnits="userSpaceOnUse"
+ spreadMethod="pad"
+ x1="247.92247"
+ y1="176.20723"
+ x2="245.86125"
+ y2="120.85145" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2060"
+ id="linearGradient4998"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.126199,0.000000,9.315399e-16,0.740741,424.7254,215.8207)"
+ spreadMethod="pad"
+ x1="254.73114"
+ y1="196.68800"
+ x2="259.43448"
+ y2="408.85126" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2096"
+ id="linearGradient5000"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.489235,0.000000,-0.522636,0.422679,270.5420,124.6950)"
+ x1="178.78366"
+ y1="143.67641"
+ x2="178.78354"
+ y2="345.06073" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2060"
+ id="linearGradient5002"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.422751,0.000000,-0.481240,0.389200,272.9716,130.5854)"
+ x1="178.27229"
+ y1="166.46916"
+ x2="178.27234"
+ y2="306.36243" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2060"
+ id="linearGradient5004"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.126199,0.000000,0.000000,0.887942,943.3572,-158.6300)"
+ spreadMethod="pad"
+ x1="254.73114"
+ y1="196.68800"
+ x2="259.43448"
+ y2="408.85126" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2096"
+ id="linearGradient5006"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.489235,0.000000,0.000000,0.805739,943.3572,-179.6371)"
+ x1="178.78366"
+ y1="143.67641"
+ x2="178.78354"
+ y2="345.06073" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2060"
+ id="linearGradient5008"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.422751,0.000000,0.000000,0.741919,953.0701,-168.4085)"
+ x1="178.27229"
+ y1="166.46916"
+ x2="178.27234"
+ y2="306.36243" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5898"
+ id="linearGradient5896"
+ gradientTransform="matrix(-0.865144,0.000000,0.000000,0.993160,675.3779,4.000000)"
+ x1="353.05484"
+ y1="401.20505"
+ x2="350.91586"
+ y2="375.05551"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ y2="306.36243"
+ x2="178.27234"
+ y1="166.46916"
+ x1="178.27229"
+ gradientTransform="matrix(0.378049,0.000000,0.000000,0.197140,277.9878,487.5007)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3343"
+ xlink:href="#linearGradient2060"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="345.06073"
+ x2="178.78354"
+ y1="143.67641"
+ x1="178.78366"
+ gradientTransform="matrix(0.395715,0.000000,0.000000,0.214099,275.4069,484.5171)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3348"
+ xlink:href="#linearGradient2096"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="660.58789"
+ x2="313.48145"
+ y1="598.38739"
+ x1="313.11353"
+ spreadMethod="pad"
+ gradientTransform="scale(1.126200,0.887942)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3351"
+ xlink:href="#linearGradient2060"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="408.85126"
+ x2="259.43448"
+ y1="196.68800"
+ x1="254.73114"
+ spreadMethod="pad"
+ gradientTransform="matrix(1.126199,0.000000,0.000000,0.887942,943.3572,-158.6300)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3404"
+ xlink:href="#linearGradient2060"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="345.06073"
+ x2="178.78354"
+ y1="143.67641"
+ x1="178.78366"
+ gradientTransform="matrix(1.489235,0.000000,0.000000,0.805739,943.3572,-179.6371)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3406"
+ xlink:href="#linearGradient2096"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="306.36243"
+ x2="178.27234"
+ y1="166.46916"
+ x1="178.27229"
+ gradientTransform="matrix(1.422751,0.000000,0.000000,0.741919,953.0701,-168.4085)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3408"
+ xlink:href="#linearGradient2060"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="408.85126"
+ x2="259.43448"
+ y1="196.68800"
+ x1="254.73114"
+ spreadMethod="pad"
+ gradientTransform="matrix(1.126199,0.000000,0.000000,0.887942,943.3572,-158.6300)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3410"
+ xlink:href="#linearGradient2060"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="345.06073"
+ x2="178.78354"
+ y1="143.67641"
+ x1="178.78366"
+ gradientTransform="matrix(1.489235,0.000000,0.000000,0.805739,943.3572,-179.6371)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3412"
+ xlink:href="#linearGradient2096"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="306.36243"
+ x2="178.27234"
+ y1="166.46916"
+ x1="178.27229"
+ gradientTransform="matrix(1.422751,0.000000,0.000000,0.741919,953.0701,-168.4085)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3414"
+ xlink:href="#linearGradient2060"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="408.85126"
+ x2="259.43448"
+ y1="196.68800"
+ x1="254.73114"
+ spreadMethod="pad"
+ gradientTransform="matrix(1.126199,0.000000,0.000000,0.887942,943.3572,-158.6300)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3416"
+ xlink:href="#linearGradient2060"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="345.06073"
+ x2="178.78354"
+ y1="143.67641"
+ x1="178.78366"
+ gradientTransform="matrix(1.489235,0.000000,0.000000,0.805739,943.3572,-179.6371)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3418"
+ xlink:href="#linearGradient2096"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="306.36243"
+ x2="178.27234"
+ y1="166.46916"
+ x1="178.27229"
+ gradientTransform="matrix(1.422751,0.000000,0.000000,0.741919,953.0701,-168.4085)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3420"
+ xlink:href="#linearGradient2060"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="458.81534"
+ x2="271.98376"
+ y1="384.87134"
+ x1="270.30557"
+ gradientTransform="scale(1.384798,0.722127)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4219"
+ xlink:href="#linearGradient2060"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="345.06073"
+ x2="178.78354"
+ y1="143.67641"
+ x1="178.78366"
+ gradientTransform="matrix(0.504973,0.000000,0.000000,0.273212,285.4510,239.6270)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4224"
+ xlink:href="#linearGradient2096"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="413.40652"
+ x2="319.92978"
+ y1="327.43060"
+ x1="319.92975"
+ spreadMethod="pad"
+ gradientTransform="matrix(1.126200,0.000000,0.000000,0.887942,11.12619,0.000000)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4227"
+ xlink:href="#linearGradient2060"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="413.40652"
+ x2="319.92978"
+ y1="327.43060"
+ x1="319.92975"
+ spreadMethod="pad"
+ gradientTransform="matrix(1.126200,0.000000,0.000000,0.887942,11.12619,0.000000)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient5754"
+ xlink:href="#linearGradient2060"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="345.06073"
+ x2="178.78354"
+ y1="143.67641"
+ x1="178.78366"
+ gradientTransform="matrix(0.504973,0.000000,0.000000,0.273212,285.4510,239.6270)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient5756"
+ xlink:href="#linearGradient2096"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="458.81534"
+ x2="271.98376"
+ y1="384.87134"
+ x1="270.30557"
+ gradientTransform="scale(1.384798,0.722127)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient5758"
+ xlink:href="#linearGradient2060"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="413.40652"
+ x2="319.92978"
+ y1="327.43060"
+ x1="319.92975"
+ spreadMethod="pad"
+ gradientTransform="matrix(1.126200,0.000000,0.000000,0.887942,11.12619,0.000000)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient5774"
+ xlink:href="#linearGradient2060"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="345.06073"
+ x2="178.78354"
+ y1="143.67641"
+ x1="178.78366"
+ gradientTransform="matrix(0.504973,0.000000,0.000000,0.273212,285.4510,239.6270)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient5776"
+ xlink:href="#linearGradient2096"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="458.81534"
+ x2="271.98376"
+ y1="384.87134"
+ x1="270.30557"
+ gradientTransform="scale(1.384798,0.722127)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient5778"
+ xlink:href="#linearGradient2060"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="660.58789"
+ x2="313.48145"
+ y1="598.38739"
+ x1="313.11353"
+ spreadMethod="pad"
+ gradientTransform="scale(1.126200,0.887942)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient6573"
+ xlink:href="#linearGradient2060"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="345.06073"
+ x2="178.78354"
+ y1="143.67641"
+ x1="178.78366"
+ gradientTransform="matrix(0.395715,0.000000,0.000000,0.214099,275.4069,484.5171)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient6575"
+ xlink:href="#linearGradient2096"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="306.36243"
+ x2="178.27234"
+ y1="166.46916"
+ x1="178.27229"
+ gradientTransform="matrix(0.378049,0.000000,0.000000,0.197140,277.9878,487.5007)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient6577"
+ xlink:href="#linearGradient2060"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="120.85145"
+ x2="245.86125"
+ y1="176.20723"
+ x1="247.92247"
+ spreadMethod="pad"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient6579"
+ xlink:href="#linearGradient2182"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="408.85126"
+ x2="259.43448"
+ y1="196.68800"
+ x1="254.73114"
+ spreadMethod="pad"
+ gradientTransform="matrix(1.126199,0.000000,0.000000,0.887942,943.3572,-158.6300)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient6599"
+ xlink:href="#linearGradient2060"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="345.06073"
+ x2="178.78354"
+ y1="143.67641"
+ x1="178.78366"
+ gradientTransform="matrix(1.489235,0.000000,0.000000,0.805739,943.3572,-179.6371)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient6601"
+ xlink:href="#linearGradient2096"
+ inkscape:collect="always" />
+ <linearGradient
+ y2="306.36243"
+ x2="178.27234"
+ y1="166.46916"
+ x1="178.27229"
+ gradientTransform="matrix(1.422751,0.000000,0.000000,0.741919,953.0701,-168.4085)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient6603"
+ xlink:href="#linearGradient2060"
+ inkscape:collect="always" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.73598669"
+ inkscape:cx="587.45546"
+ inkscape:cy="552.04627"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ inkscape:window-width="1024"
+ inkscape:window-height="676"
+ inkscape:window-x="0"
+ inkscape:window-y="25"
+ showguides="false"
+ inkscape:guide-bbox="true">
+ <sodipodi:guide
+ id="guide3428"
+ position="297.71968"
+ orientation="vertical" />
+ <sodipodi:guide
+ id="guide3430"
+ position="478.45862"
+ orientation="vertical" />
+ <sodipodi:guide
+ id="guide6583"
+ position="321.85470"
+ orientation="vertical" />
+ <sodipodi:guide
+ id="guide6605"
+ position="452.99847"
+ orientation="vertical" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata4">
+ <rdf:RDF
+ id="RDF5">
+ <cc:Work
+ rdf:about=""
+ id="Work6">
+ <dc:format
+ id="format7">image/svg+xml</dc:format>
+ <dc:type
+ id="type9"
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <rect
+ style="fill:#0069c5;fill-opacity:0.69767439;fill-rule:nonzero;stroke:none;stroke-width:3.7977202;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect6666"
+ width="181.32584"
+ height="337.54471"
+ x="297.71967"
+ y="265.11880"
+ rx="0.0000000"
+ ry="0.0000000"
+ inkscape:export-filename="/home/tina/Desktop/spamfilter.png"
+ inkscape:export-xdpi="90.000000"
+ inkscape:export-ydpi="90.000000" />
+ <g
+ id="g2427"
+ transform="matrix(1.000000,0.000000,-0.648642,0.524586,1194.952,-310.8931)">
+ <rect
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:8.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect2429"
+ width="309.83340"
+ height="242.59299"
+ x="833.25421"
+ y="659.46698"
+ ry="17.384861"
+ rx="14.502842" />
+ <rect
+ style="fill:url(#linearGradient3034);fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:8.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect2431"
+ width="309.83340"
+ height="242.59299"
+ x="833.25421"
+ y="659.46698"
+ ry="17.384861"
+ rx="14.502842" />
+ <path
+ style="opacity:0.51999998;fill:url(#linearGradient3036);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 841.16486,666.50679 L 1136.4955,664.92476 L 987.51172,824.71110 L 841.16486,666.50679 z "
+ id="path2433" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#3c3f5e;stroke-width:5.4770780;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ d="M 841.16486,668.85306 L 1136.4955,667.27102 L 987.51172,827.05736 L 841.16486,668.85306 z "
+ id="path2435" />
+ <path
+ style="fill:#ffffff;fill-opacity:0.99447513;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 841.16486,666.12248 L 1136.4955,664.60308 L 987.51172,818.06126 L 841.16486,666.12248 z "
+ id="path2437" />
+ <path
+ style="fill:url(#linearGradient3038);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 847.07550,667.36015 L 1129.2217,665.90342 L 986.88902,813.03342 L 847.07550,667.36015 z "
+ id="path2439" />
+ </g>
+ <g
+ id="g2495"
+ transform="matrix(1.000000,0.000000,-0.648642,0.524586,1192.952,-318.8931)">
+ <rect
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:8.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect2497"
+ width="309.83340"
+ height="242.59299"
+ x="833.25421"
+ y="659.46698"
+ ry="17.384861"
+ rx="14.502842" />
+ <rect
+ style="fill:url(#linearGradient3042);fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:8.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect2499"
+ width="309.83340"
+ height="242.59299"
+ x="833.25421"
+ y="659.46698"
+ ry="17.384861"
+ rx="14.502842" />
+ <path
+ style="opacity:0.51999998;fill:url(#linearGradient3044);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 841.16486,666.50679 L 1136.4955,664.92476 L 987.51172,824.71110 L 841.16486,666.50679 z "
+ id="path2501" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#3c3f5e;stroke-width:5.4770780;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ d="M 841.16486,668.85306 L 1136.4955,667.27102 L 987.51172,827.05736 L 841.16486,668.85306 z "
+ id="path2503" />
+ <path
+ style="fill:#ffffff;fill-opacity:0.99447513;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 841.16486,666.12248 L 1136.4955,664.60308 L 987.51172,818.06126 L 841.16486,666.12248 z "
+ id="path2505" />
+ <path
+ style="fill:url(#linearGradient3046);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 847.07550,667.36015 L 1129.2217,665.90342 L 986.88902,813.03342 L 847.07550,667.36015 z "
+ id="path2507" />
+ </g>
+ <g
+ id="g2537"
+ transform="matrix(1.000000,0.000000,-0.648642,0.524586,1192.952,-328.8931)">
+ <rect
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:8.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect2539"
+ width="309.83340"
+ height="242.59299"
+ x="833.25421"
+ y="659.46698"
+ ry="17.384861"
+ rx="14.502842" />
+ <rect
+ style="fill:url(#linearGradient3050);fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:8.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect2541"
+ width="309.83340"
+ height="242.59299"
+ x="833.25421"
+ y="659.46698"
+ ry="17.384861"
+ rx="14.502842" />
+ <path
+ style="opacity:0.51999998;fill:url(#linearGradient3052);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 841.16486,666.50679 L 1136.4955,664.92476 L 987.51172,824.71110 L 841.16486,666.50679 z "
+ id="path2543" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#3c3f5e;stroke-width:5.4770780;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ d="M 841.16486,668.85306 L 1136.4955,667.27102 L 987.51172,827.05736 L 841.16486,668.85306 z "
+ id="path2545" />
+ <path
+ style="fill:#ffffff;fill-opacity:0.99447513;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 841.16486,666.12248 L 1136.4955,664.60308 L 987.51172,818.06126 L 841.16486,666.12248 z "
+ id="path2547" />
+ <path
+ style="fill:url(#linearGradient3054);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 847.07550,667.36015 L 1129.2217,665.90342 L 986.88902,813.03342 L 847.07550,667.36015 z "
+ id="path2549" />
+ </g>
+ <g
+ id="g2579"
+ transform="matrix(1.000000,0.000000,-0.648642,0.524586,1192.952,-338.8931)">
+ <rect
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:8.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect2581"
+ width="309.83340"
+ height="242.59299"
+ x="833.25421"
+ y="659.46698"
+ ry="17.384861"
+ rx="14.502842" />
+ <rect
+ style="fill:url(#linearGradient3058);fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:8.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect2583"
+ width="309.83340"
+ height="242.59299"
+ x="833.25421"
+ y="659.46698"
+ ry="17.384861"
+ rx="14.502842" />
+ <path
+ style="opacity:0.51999998;fill:url(#linearGradient3060);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 841.16486,666.50679 L 1136.4955,664.92476 L 987.51172,824.71110 L 841.16486,666.50679 z "
+ id="path2585" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#3c3f5e;stroke-width:5.4770780;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ d="M 841.16486,668.85306 L 1136.4955,667.27102 L 987.51172,827.05736 L 841.16486,668.85306 z "
+ id="path2587" />
+ <path
+ style="fill:#ffffff;fill-opacity:0.99447513;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 841.16486,666.12248 L 1136.4955,664.60308 L 987.51172,818.06126 L 841.16486,666.12248 z "
+ id="path2589" />
+ <path
+ style="fill:url(#linearGradient3062);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 847.07550,667.36015 L 1129.2217,665.90342 L 986.88902,813.03342 L 847.07550,667.36015 z "
+ id="path2591" />
+ </g>
+ <g
+ id="g4946"
+ transform="translate(1307.591,-196.1386)">
+ <g
+ id="g2447"
+ transform="matrix(1.000000,0.000000,-0.648642,0.524586,307.6119,134.1687)">
+ <path
+ sodipodi:type="arc"
+ style="fill:#4c7f0d;fill-opacity:1.0000000;fill-rule:nonzero;stroke:none;stroke-width:5.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ id="path2449"
+ sodipodi:cx="246.54832"
+ sodipodi:cy="170.32568"
+ sodipodi:rx="48.782288"
+ sodipodi:ry="48.782288"
+ d="M 295.33061 170.32568 A 48.782288 48.782288 0 1 1 197.76604,170.32568 A 48.782288 48.782288 0 1 1 295.33061 170.32568 z"
+ transform="matrix(1.057719,0.000000,0.000000,1.057719,-1.627570,63.86943)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:url(#linearGradient4990);fill-opacity:1.0000000;fill-rule:nonzero;stroke:none;stroke-width:5.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ id="path2451"
+ sodipodi:cx="246.54832"
+ sodipodi:cy="170.32568"
+ sodipodi:rx="48.782288"
+ sodipodi:ry="48.782288"
+ d="M 295.33061 170.32568 A 48.782288 48.782288 0 1 1 197.76604,170.32568 A 48.782288 48.782288 0 1 1 295.33061 170.32568 z"
+ transform="translate(12.60297,73.70043)" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:2.3750000;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 239.64598,265.97724 C 252.59911,275.08621 268.56070,275.16978 281.76453,265.72654"
+ id="path2453"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#538b0f;stroke-width:2.8750000;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 239.64598,265.02544 C 252.59911,274.13441 268.56069,273.71657 281.76452,264.27333"
+ id="path2455"
+ sodipodi:nodetypes="cc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#ffffff;stroke-width:2.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path2457"
+ sodipodi:cx="204.70119"
+ sodipodi:cy="475.73911"
+ sodipodi:rx="1.3788812"
+ sodipodi:ry="7.2704649"
+ d="M 206.08007 475.73911 A 1.3788812 7.2704649 0 1 1 203.32231,475.73911 A 1.3788812 7.2704649 0 1 1 206.08007 475.73911 z"
+ transform="matrix(1.700789,0.000000,0.000000,1.418821,-74.95054,-438.9680)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#4c7f0d;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#4c7f0d;stroke-width:2.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path2459"
+ sodipodi:cx="204.70119"
+ sodipodi:cy="475.73911"
+ sodipodi:rx="1.3788812"
+ sodipodi:ry="7.2704649"
+ d="M 206.08007 475.73911 A 1.3788812 7.2704649 0 1 1 203.32231,475.73911 A 1.3788812 7.2704649 0 1 1 206.08007 475.73911 z"
+ transform="matrix(1.700789,0.000000,0.000000,1.418821,-75.37694,-440.0271)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#ffffff;stroke-width:2.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path2461"
+ sodipodi:cx="204.70119"
+ sodipodi:cy="475.73911"
+ sodipodi:rx="1.3788812"
+ sodipodi:ry="7.2704649"
+ d="M 206.08007 475.73911 A 1.3788812 7.2704649 0 1 1 203.32231,475.73911 A 1.3788812 7.2704649 0 1 1 206.08007 475.73911 z"
+ transform="matrix(1.700789,0.000000,0.000000,1.418821,-98.95050,-438.9680)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#4c7f0d;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#4c7f0d;stroke-width:2.0036430;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path2463"
+ sodipodi:cx="204.70119"
+ sodipodi:cy="475.73911"
+ sodipodi:rx="1.3788812"
+ sodipodi:ry="7.2704649"
+ d="M 206.08007 475.73911 A 1.3788812 7.2704649 0 1 1 203.32231,475.73911 A 1.3788812 7.2704649 0 1 1 206.08007 475.73911 z"
+ transform="matrix(1.700789,0.000000,0.000000,1.418821,-99.37690,-440.0271)" />
+ </g>
+ <g
+ id="g2509"
+ transform="matrix(1.000000,0.000000,-0.648642,0.524586,305.6119,126.1687)">
+ <path
+ sodipodi:type="arc"
+ style="fill:#4c7f0d;fill-opacity:1.0000000;fill-rule:nonzero;stroke:none;stroke-width:5.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ id="path2511"
+ sodipodi:cx="246.54832"
+ sodipodi:cy="170.32568"
+ sodipodi:rx="48.782288"
+ sodipodi:ry="48.782288"
+ d="M 295.33061 170.32568 A 48.782288 48.782288 0 1 1 197.76604,170.32568 A 48.782288 48.782288 0 1 1 295.33061 170.32568 z"
+ transform="matrix(1.057719,0.000000,0.000000,1.057719,-1.627570,63.86943)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:url(#linearGradient4992);fill-opacity:1.0000000;fill-rule:nonzero;stroke:none;stroke-width:5.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ id="path2513"
+ sodipodi:cx="246.54832"
+ sodipodi:cy="170.32568"
+ sodipodi:rx="48.782288"
+ sodipodi:ry="48.782288"
+ d="M 295.33061 170.32568 A 48.782288 48.782288 0 1 1 197.76604,170.32568 A 48.782288 48.782288 0 1 1 295.33061 170.32568 z"
+ transform="translate(12.60297,73.70043)" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:2.3750000;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 239.64598,265.97724 C 252.59911,275.08621 268.56070,275.16978 281.76453,265.72654"
+ id="path2515"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#538b0f;stroke-width:2.8750000;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 239.64598,265.02544 C 252.59911,274.13441 268.56069,273.71657 281.76452,264.27333"
+ id="path2517"
+ sodipodi:nodetypes="cc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#ffffff;stroke-width:2.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path2519"
+ sodipodi:cx="204.70119"
+ sodipodi:cy="475.73911"
+ sodipodi:rx="1.3788812"
+ sodipodi:ry="7.2704649"
+ d="M 206.08007 475.73911 A 1.3788812 7.2704649 0 1 1 203.32231,475.73911 A 1.3788812 7.2704649 0 1 1 206.08007 475.73911 z"
+ transform="matrix(1.700789,0.000000,0.000000,1.418821,-74.95054,-438.9680)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#4c7f0d;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#4c7f0d;stroke-width:2.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path2521"
+ sodipodi:cx="204.70119"
+ sodipodi:cy="475.73911"
+ sodipodi:rx="1.3788812"
+ sodipodi:ry="7.2704649"
+ d="M 206.08007 475.73911 A 1.3788812 7.2704649 0 1 1 203.32231,475.73911 A 1.3788812 7.2704649 0 1 1 206.08007 475.73911 z"
+ transform="matrix(1.700789,0.000000,0.000000,1.418821,-75.37694,-440.0271)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#ffffff;stroke-width:2.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path2523"
+ sodipodi:cx="204.70119"
+ sodipodi:cy="475.73911"
+ sodipodi:rx="1.3788812"
+ sodipodi:ry="7.2704649"
+ d="M 206.08007 475.73911 A 1.3788812 7.2704649 0 1 1 203.32231,475.73911 A 1.3788812 7.2704649 0 1 1 206.08007 475.73911 z"
+ transform="matrix(1.700789,0.000000,0.000000,1.418821,-98.95050,-438.9680)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#4c7f0d;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#4c7f0d;stroke-width:2.0036430;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path2525"
+ sodipodi:cx="204.70119"
+ sodipodi:cy="475.73911"
+ sodipodi:rx="1.3788812"
+ sodipodi:ry="7.2704649"
+ d="M 206.08007 475.73911 A 1.3788812 7.2704649 0 1 1 203.32231,475.73911 A 1.3788812 7.2704649 0 1 1 206.08007 475.73911 z"
+ transform="matrix(1.700789,0.000000,0.000000,1.418821,-99.37690,-440.0271)" />
+ </g>
+ <g
+ id="g2551"
+ transform="matrix(1.000000,0.000000,-0.648642,0.524586,305.6119,116.1687)">
+ <path
+ sodipodi:type="arc"
+ style="fill:#4c7f0d;fill-opacity:1.0000000;fill-rule:nonzero;stroke:none;stroke-width:5.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ id="path2553"
+ sodipodi:cx="246.54832"
+ sodipodi:cy="170.32568"
+ sodipodi:rx="48.782288"
+ sodipodi:ry="48.782288"
+ d="M 295.33061 170.32568 A 48.782288 48.782288 0 1 1 197.76604,170.32568 A 48.782288 48.782288 0 1 1 295.33061 170.32568 z"
+ transform="matrix(1.057719,0.000000,0.000000,1.057719,-1.627570,63.86943)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:url(#linearGradient4994);fill-opacity:1.0000000;fill-rule:nonzero;stroke:none;stroke-width:5.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ id="path2555"
+ sodipodi:cx="246.54832"
+ sodipodi:cy="170.32568"
+ sodipodi:rx="48.782288"
+ sodipodi:ry="48.782288"
+ d="M 295.33061 170.32568 A 48.782288 48.782288 0 1 1 197.76604,170.32568 A 48.782288 48.782288 0 1 1 295.33061 170.32568 z"
+ transform="translate(12.60297,73.70043)" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:2.3750000;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 239.64598,265.97724 C 252.59911,275.08621 268.56070,275.16978 281.76453,265.72654"
+ id="path2557"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#538b0f;stroke-width:2.8750000;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 239.64598,265.02544 C 252.59911,274.13441 268.56069,273.71657 281.76452,264.27333"
+ id="path2559"
+ sodipodi:nodetypes="cc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#ffffff;stroke-width:2.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path2561"
+ sodipodi:cx="204.70119"
+ sodipodi:cy="475.73911"
+ sodipodi:rx="1.3788812"
+ sodipodi:ry="7.2704649"
+ d="M 206.08007 475.73911 A 1.3788812 7.2704649 0 1 1 203.32231,475.73911 A 1.3788812 7.2704649 0 1 1 206.08007 475.73911 z"
+ transform="matrix(1.700789,0.000000,0.000000,1.418821,-74.95054,-438.9680)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#4c7f0d;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#4c7f0d;stroke-width:2.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path2563"
+ sodipodi:cx="204.70119"
+ sodipodi:cy="475.73911"
+ sodipodi:rx="1.3788812"
+ sodipodi:ry="7.2704649"
+ d="M 206.08007 475.73911 A 1.3788812 7.2704649 0 1 1 203.32231,475.73911 A 1.3788812 7.2704649 0 1 1 206.08007 475.73911 z"
+ transform="matrix(1.700789,0.000000,0.000000,1.418821,-75.37694,-440.0271)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#ffffff;stroke-width:2.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path2565"
+ sodipodi:cx="204.70119"
+ sodipodi:cy="475.73911"
+ sodipodi:rx="1.3788812"
+ sodipodi:ry="7.2704649"
+ d="M 206.08007 475.73911 A 1.3788812 7.2704649 0 1 1 203.32231,475.73911 A 1.3788812 7.2704649 0 1 1 206.08007 475.73911 z"
+ transform="matrix(1.700789,0.000000,0.000000,1.418821,-98.95050,-438.9680)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#4c7f0d;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#4c7f0d;stroke-width:2.0036430;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path2567"
+ sodipodi:cx="204.70119"
+ sodipodi:cy="475.73911"
+ sodipodi:rx="1.3788812"
+ sodipodi:ry="7.2704649"
+ d="M 206.08007 475.73911 A 1.3788812 7.2704649 0 1 1 203.32231,475.73911 A 1.3788812 7.2704649 0 1 1 206.08007 475.73911 z"
+ transform="matrix(1.700789,0.000000,0.000000,1.418821,-99.37690,-440.0271)" />
+ </g>
+ <g
+ id="g2593"
+ transform="matrix(1.000000,0.000000,-0.648642,0.524586,305.6119,106.1687)">
+ <path
+ sodipodi:type="arc"
+ style="fill:#4c7f0d;fill-opacity:1.0000000;fill-rule:nonzero;stroke:none;stroke-width:5.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ id="path2595"
+ sodipodi:cx="246.54832"
+ sodipodi:cy="170.32568"
+ sodipodi:rx="48.782288"
+ sodipodi:ry="48.782288"
+ d="M 295.33061 170.32568 A 48.782288 48.782288 0 1 1 197.76604,170.32568 A 48.782288 48.782288 0 1 1 295.33061 170.32568 z"
+ transform="matrix(1.057719,0.000000,0.000000,1.057719,-1.627570,63.86943)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:url(#linearGradient4996);fill-opacity:1.0000000;fill-rule:nonzero;stroke:none;stroke-width:5.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ id="path2597"
+ sodipodi:cx="246.54832"
+ sodipodi:cy="170.32568"
+ sodipodi:rx="48.782288"
+ sodipodi:ry="48.782288"
+ d="M 295.33061 170.32568 A 48.782288 48.782288 0 1 1 197.76604,170.32568 A 48.782288 48.782288 0 1 1 295.33061 170.32568 z"
+ transform="translate(12.60297,73.70043)" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:2.3750000;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 239.64598,265.97724 C 252.59911,275.08621 268.56070,275.16978 281.76453,265.72654"
+ id="path2599"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#538b0f;stroke-width:2.8750000;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 239.64598,265.02544 C 252.59911,274.13441 268.56069,273.71657 281.76452,264.27333"
+ id="path2601"
+ sodipodi:nodetypes="cc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#ffffff;stroke-width:2.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path2603"
+ sodipodi:cx="204.70119"
+ sodipodi:cy="475.73911"
+ sodipodi:rx="1.3788812"
+ sodipodi:ry="7.2704649"
+ d="M 206.08007 475.73911 A 1.3788812 7.2704649 0 1 1 203.32231,475.73911 A 1.3788812 7.2704649 0 1 1 206.08007 475.73911 z"
+ transform="matrix(1.700789,0.000000,0.000000,1.418821,-74.95054,-438.9680)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#4c7f0d;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#4c7f0d;stroke-width:2.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path2605"
+ sodipodi:cx="204.70119"
+ sodipodi:cy="475.73911"
+ sodipodi:rx="1.3788812"
+ sodipodi:ry="7.2704649"
+ d="M 206.08007 475.73911 A 1.3788812 7.2704649 0 1 1 203.32231,475.73911 A 1.3788812 7.2704649 0 1 1 206.08007 475.73911 z"
+ transform="matrix(1.700789,0.000000,0.000000,1.418821,-75.37694,-440.0271)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#ffffff;stroke-width:2.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path2607"
+ sodipodi:cx="204.70119"
+ sodipodi:cy="475.73911"
+ sodipodi:rx="1.3788812"
+ sodipodi:ry="7.2704649"
+ d="M 206.08007 475.73911 A 1.3788812 7.2704649 0 1 1 203.32231,475.73911 A 1.3788812 7.2704649 0 1 1 206.08007 475.73911 z"
+ transform="matrix(1.700789,0.000000,0.000000,1.418821,-98.95050,-438.9680)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#4c7f0d;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#4c7f0d;stroke-width:2.0036430;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path2609"
+ sodipodi:cx="204.70119"
+ sodipodi:cy="475.73911"
+ sodipodi:rx="1.3788812"
+ sodipodi:ry="7.2704649"
+ d="M 206.08007 475.73911 A 1.3788812 7.2704649 0 1 1 203.32231,475.73911 A 1.3788812 7.2704649 0 1 1 206.08007 475.73911 z"
+ transform="matrix(1.700789,0.000000,0.000000,1.418821,-99.37690,-440.0271)" />
+ </g>
+ <rect
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:7.3068619;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect2623"
+ width="309.83340"
+ height="202.37650"
+ x="530.20056"
+ y="299.61697"
+ ry="14.502839"
+ rx="14.502842"
+ transform="matrix(1.000000,0.000000,-0.777541,0.628832,0.000000,0.000000)" />
+ <rect
+ style="fill:url(#linearGradient4998);fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:7.3068619;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect2625"
+ width="309.83340"
+ height="202.37650"
+ x="530.20056"
+ y="299.61697"
+ ry="14.502839"
+ rx="14.502842"
+ transform="matrix(1.000000,0.000000,-0.777541,0.628832,0.000000,0.000000)" />
+ <path
+ style="opacity:0.51999998;fill:url(#linearGradient5000);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 300.58046,192.10183 L 596.93727,191.27192 L 344.30936,275.09360 L 300.58046,192.10183 z "
+ id="path2627" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#3c3f5e;stroke-width:3.9669549;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ d="M 299.05857,193.33265 L 595.41539,192.50274 L 342.78748,276.32441 L 299.05857,193.33265 z "
+ id="path2629" />
+ <path
+ style="fill:#ffffff;fill-opacity:0.99447513;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 300.82974,191.90023 L 597.14593,191.10317 L 348.62273,271.60518 L 300.82974,191.90023 z "
+ id="path2631" />
+ <path
+ style="opacity:0.46150228;fill:url(#linearGradient5002);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 305.93758,192.54949 L 589.02867,191.78531 L 351.26130,268.96765 L 305.93758,192.54949 z "
+ id="path2633" />
+ </g>
+ <g
+ id="g2240"
+ transform="translate(1513.148,60.96344)">
+ <path
+ sodipodi:type="arc"
+ style="fill:#4c7f0d;fill-opacity:1.0000000;fill-rule:nonzero;stroke:none;stroke-width:5.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ id="path2242"
+ sodipodi:cx="246.54832"
+ sodipodi:cy="170.32568"
+ sodipodi:rx="48.782288"
+ sodipodi:ry="48.782288"
+ d="M 295.33061 170.32568 A 48.782288 48.782288 0 1 1 197.76604,170.32568 A 48.782288 48.782288 0 1 1 295.33061 170.32568 z"
+ transform="matrix(1.057719,0.000000,0.000000,1.057719,-1.627570,63.86943)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:url(#linearGradient2258);fill-opacity:1.0000000;fill-rule:nonzero;stroke:none;stroke-width:5.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ id="path2244"
+ sodipodi:cx="246.54832"
+ sodipodi:cy="170.32568"
+ sodipodi:rx="48.782288"
+ sodipodi:ry="48.782288"
+ d="M 295.33061 170.32568 A 48.782288 48.782288 0 1 1 197.76604,170.32568 A 48.782288 48.782288 0 1 1 295.33061 170.32568 z"
+ transform="translate(12.60297,73.70043)" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:2.3750000;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 239.64598,265.97724 C 252.59911,275.08621 268.56070,275.16978 281.76453,265.72654"
+ id="path2246"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#538b0f;stroke-width:2.8750000;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 239.64598,265.02544 C 252.59911,274.13441 268.56069,273.71657 281.76452,264.27333"
+ id="path2248"
+ sodipodi:nodetypes="cc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#ffffff;stroke-width:2.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path2250"
+ sodipodi:cx="204.70119"
+ sodipodi:cy="475.73911"
+ sodipodi:rx="1.3788812"
+ sodipodi:ry="7.2704649"
+ d="M 206.08007 475.73911 A 1.3788812 7.2704649 0 1 1 203.32231,475.73911 A 1.3788812 7.2704649 0 1 1 206.08007 475.73911 z"
+ transform="matrix(1.700789,0.000000,0.000000,1.418821,-74.95054,-438.9680)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#4c7f0d;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#4c7f0d;stroke-width:2.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path2252"
+ sodipodi:cx="204.70119"
+ sodipodi:cy="475.73911"
+ sodipodi:rx="1.3788812"
+ sodipodi:ry="7.2704649"
+ d="M 206.08007 475.73911 A 1.3788812 7.2704649 0 1 1 203.32231,475.73911 A 1.3788812 7.2704649 0 1 1 206.08007 475.73911 z"
+ transform="matrix(1.700789,0.000000,0.000000,1.418821,-75.37694,-440.0271)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#ffffff;stroke-width:2.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path2254"
+ sodipodi:cx="204.70119"
+ sodipodi:cy="475.73911"
+ sodipodi:rx="1.3788812"
+ sodipodi:ry="7.2704649"
+ d="M 206.08007 475.73911 A 1.3788812 7.2704649 0 1 1 203.32231,475.73911 A 1.3788812 7.2704649 0 1 1 206.08007 475.73911 z"
+ transform="matrix(1.700789,0.000000,0.000000,1.418821,-98.95050,-438.9680)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#4c7f0d;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#4c7f0d;stroke-width:2.0036430;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path2256"
+ sodipodi:cx="204.70119"
+ sodipodi:cy="475.73911"
+ sodipodi:rx="1.3788812"
+ sodipodi:ry="7.2704649"
+ d="M 206.08007 475.73911 A 1.3788812 7.2704649 0 1 1 203.32231,475.73911 A 1.3788812 7.2704649 0 1 1 206.08007 475.73911 z"
+ transform="matrix(1.700789,0.000000,0.000000,1.418821,-99.37690,-440.0271)" />
+ </g>
+ <g
+ id="g2835"
+ transform="translate(572.2447,-929.7866)">
+ <rect
+ style="fill:#ffffff;fill-opacity:0.44505492;fill-rule:nonzero;stroke:#e50000;stroke-width:5.1249957;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect2661"
+ width="238.27049"
+ height="102.11591"
+ x="425.32895"
+ y="1425.8314"
+ rx="14.502831"
+ ry="14.502831"
+ transform="matrix(0.952074,-0.305867,0.305867,0.952074,0.000000,0.000000)" />
+ <text
+ xml:space="preserve"
+ style="font-size:80.367645;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;fill:#e71717;fill-opacity:1.0000000;stroke:none;stroke-width:1.0000000pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000;font-family:Bitstream Charter;text-anchor:start;writing-mode:lr-tb"
+ x="435.71271"
+ y="1502.7532"
+ id="text2663"
+ sodipodi:linespacing="100%"
+ transform="matrix(0.952074,-0.305867,0.305867,0.952074,0.000000,0.000000)"><tspan
+ sodipodi:role="line"
+ id="tspan2665"
+ x="435.71271"
+ y="1502.7532">SPAM</tspan></text>
+ </g>
+ <g
+ id="g4844"
+ transform="translate(409.8180,503.9007)">
+ <rect
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:8.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect2677"
+ width="309.83340"
+ height="242.59299"
+ x="1048.8324"
+ y="-58.181622"
+ ry="14.502842"
+ rx="14.502842" />
+ <rect
+ style="fill:url(#linearGradient5004);fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:8.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect2679"
+ width="309.83340"
+ height="242.59299"
+ x="1048.8324"
+ y="-58.181622"
+ ry="14.502842"
+ rx="14.502842" />
+ <path
+ style="opacity:0.51999998;fill:url(#linearGradient5006);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 1056.7431,-51.141810 L 1352.0737,-52.723840 L 1203.0899,107.06250 L 1056.7431,-51.141810 z "
+ id="path2681" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#3c3f5e;stroke-width:5.4770780;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ d="M 1056.7431,-48.795540 L 1352.0737,-50.377580 L 1203.0899,109.40876 L 1056.7431,-48.795540 z "
+ id="path2683" />
+ <path
+ style="fill:#ffffff;fill-opacity:0.99447513;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 1056.7431,-51.526120 L 1352.0737,-53.045520 L 1203.0899,100.41266 L 1056.7431,-51.526120 z "
+ id="path2685" />
+ <path
+ style="opacity:0.46009389;fill:url(#linearGradient5008);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 1062.6537,-50.288450 L 1344.7999,-51.745180 L 1202.4672,95.384820 L 1062.6537,-50.288450 z "
+ id="path2687" />
+ </g>
+ <g
+ id="g4878"
+ transform="matrix(0.224176,0.000000,0.000000,0.224176,250.0883,294.8714)"
+ inkscape:export-filename="/home/tina/Desktop/spamfilter.png"
+ inkscape:export-xdpi="90.000000"
+ inkscape:export-ydpi="90.000000">
+ <rect
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:8.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect2409"
+ width="309.83340"
+ height="242.59299"
+ x="325.51071"
+ y="707.68842"
+ ry="22.927092"
+ rx="22.927092" />
+ <rect
+ style="fill:url(#linearGradient4914);fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:8.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect2143"
+ width="309.83340"
+ height="242.59299"
+ x="325.51071"
+ y="707.68842"
+ ry="22.927092"
+ rx="22.927092" />
+ <path
+ style="opacity:0.51999998;fill:url(#linearGradient4916);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 333.42136,714.72823 L 628.75200,713.14620 L 479.76822,872.93254 L 333.42136,714.72823 z "
+ id="path2145" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#3c3f5e;stroke-width:5.4770780;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ d="M 333.42136,717.07450 L 628.75200,715.49246 L 479.76822,875.27880 L 333.42136,717.07450 z "
+ id="path2147" />
+ <path
+ style="fill:#ffffff;fill-opacity:0.99447513;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 333.42136,714.34392 L 628.75200,712.82452 L 479.76822,866.28270 L 333.42136,714.34392 z "
+ id="path2149" />
+ <path
+ style="opacity:0.46009389;fill:url(#linearGradient4918);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 339.33200,715.58159 L 621.47820,714.12486 L 479.14552,861.25486 L 339.33200,715.58159 z "
+ id="path2151" />
+ <rect
+ style="fill:#ffffff;fill-opacity:0.44505492;fill-rule:nonzero;stroke:#e50000;stroke-width:5.1249943;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect2842"
+ width="238.27043"
+ height="102.11589"
+ x="86.626945"
+ y="885.01361"
+ rx="22.927069"
+ ry="22.927069"
+ transform="matrix(0.952074,-0.305867,0.305867,0.952074,0.000000,0.000000)" />
+ <text
+ xml:space="preserve"
+ style="font-size:80.367622;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;fill:#e71717;fill-opacity:1.0000000;stroke:none;stroke-width:1.0000000pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000;font-family:Bitstream Charter;text-anchor:start;writing-mode:lr-tb"
+ x="97.010704"
+ y="961.93536"
+ id="text2844"
+ sodipodi:linespacing="100%"
+ transform="matrix(0.952074,-0.305867,0.305867,0.952074,0.000000,0.000000)"><tspan
+ sodipodi:role="line"
+ id="tspan2846"
+ x="97.010704"
+ y="961.93536">SPAM</tspan></text>
+ </g>
+ <g
+ transform="translate(66.76019,6.021400)"
+ id="g6557">
+ <rect
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:2.1257360;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect2894"
+ width="82.328003"
+ height="64.461082"
+ x="303.43344"
+ y="516.78992"
+ ry="5.1397133"
+ rx="5.1397133" />
+ <rect
+ style="fill:url(#linearGradient6573);fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:2.1257360;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect2896"
+ width="82.328003"
+ height="64.461082"
+ x="303.43344"
+ y="516.78992"
+ ry="5.1397133"
+ rx="5.1397133" />
+ <path
+ style="opacity:0.51999998;fill:url(#linearGradient6575);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 305.53545,518.66049 L 384.00982,518.24011 L 344.42230,560.69806 L 305.53545,518.66049 z "
+ id="path2898" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#3c3f5e;stroke-width:1.4553528;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ d="M 305.53545,519.28393 L 384.00982,518.86356 L 344.42230,561.32150 L 305.53545,519.28393 z "
+ id="path2900" />
+ <path
+ style="fill:#ffffff;fill-opacity:0.99447513;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 305.53545,518.55837 L 384.00982,518.15464 L 344.42230,558.93109 L 305.53545,518.55837 z "
+ id="path2902" />
+ <path
+ style="opacity:0.46009389;fill:url(#linearGradient6577);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 307.10601,518.88724 L 382.07705,518.50016 L 344.25683,557.59510 L 307.10601,518.88724 z "
+ id="path2904" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#185600;fill-opacity:1.0000000;fill-rule:nonzero;stroke:none;stroke-width:5.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ id="path2874"
+ sodipodi:cx="246.54832"
+ sodipodi:cy="170.32568"
+ sodipodi:rx="48.782288"
+ sodipodi:ry="48.782288"
+ d="M 295.33061 170.32568 A 48.782288 48.782288 0 1 1 197.76604,170.32568 A 48.782288 48.782288 0 1 1 295.33061 170.32568 z"
+ transform="matrix(0.281054,0.000000,0.000000,0.281054,274.9206,487.0389)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:url(#linearGradient6579);fill-opacity:1.0000000;fill-rule:nonzero;stroke:none;stroke-width:5.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ id="path2876"
+ sodipodi:cx="246.54832"
+ sodipodi:cy="170.32568"
+ sodipodi:rx="48.782288"
+ sodipodi:ry="48.782288"
+ d="M 295.33061 170.32568 A 48.782288 48.782288 0 1 1 197.76604,170.32568 A 48.782288 48.782288 0 1 1 295.33061 170.32568 z"
+ transform="matrix(0.265717,0.000000,0.000000,0.265717,278.7019,489.6512)" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.63107789;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 339.03111,540.74237 C 342.47298,543.16278 346.71424,543.18499 350.22273,540.67576"
+ id="path2878"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#185600;stroke-width:0.76393640;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 339.03111,540.48946 C 342.47298,542.90987 346.71424,542.79885 350.22272,540.28962"
+ id="path2880"
+ sodipodi:nodetypes="cc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#ffffff;stroke-width:2.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path2882"
+ sodipodi:cx="204.70119"
+ sodipodi:cy="475.73911"
+ sodipodi:rx="1.3788812"
+ sodipodi:ry="7.2704649"
+ d="M 206.08007 475.73911 A 1.3788812 7.2704649 0 1 1 203.32231,475.73911 A 1.3788812 7.2704649 0 1 1 206.08007 475.73911 z"
+ transform="matrix(0.451929,0.000000,0.000000,0.377005,255.4375,353.4264)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#185600;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#185600;stroke-width:2.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path2884"
+ sodipodi:cx="204.70119"
+ sodipodi:cy="475.73911"
+ sodipodi:rx="1.3788812"
+ sodipodi:ry="7.2704649"
+ d="M 206.08007 475.73911 A 1.3788812 7.2704649 0 1 1 203.32231,475.73911 A 1.3788812 7.2704649 0 1 1 206.08007 475.73911 z"
+ transform="matrix(0.451929,0.000000,0.000000,0.377005,255.3242,353.1450)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#ffffff;stroke-width:2.0000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path2886"
+ sodipodi:cx="204.70119"
+ sodipodi:cy="475.73911"
+ sodipodi:rx="1.3788812"
+ sodipodi:ry="7.2704649"
+ d="M 206.08007 475.73911 A 1.3788812 7.2704649 0 1 1 203.32231,475.73911 A 1.3788812 7.2704649 0 1 1 206.08007 475.73911 z"
+ transform="matrix(0.451929,0.000000,0.000000,0.377005,249.0603,353.4264)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#185600;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#185600;stroke-width:2.0036430;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path2888"
+ sodipodi:cx="204.70119"
+ sodipodi:cy="475.73911"
+ sodipodi:rx="1.3788812"
+ sodipodi:ry="7.2704649"
+ d="M 206.08007 475.73911 A 1.3788812 7.2704649 0 1 1 203.32231,475.73911 A 1.3788812 7.2704649 0 1 1 206.08007 475.73911 z"
+ transform="matrix(0.451929,0.000000,0.000000,0.377005,248.9470,353.1450)" />
+ </g>
+ <path
+ style="fill:url(#linearGradient3085);fill-opacity:1.0000000;fill-rule:nonzero;stroke:#2a00a7;stroke-width:1.6575843;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 1497.1904,1040.4610 C 1509.3057,1014.2280 1639.9904,1016.6487 1646.3180,1040.7716 L 1619.4600,1196.9688 C 1592.8334,1216.1310 1540.6573,1218.2601 1520.4181,1196.9688 L 1497.1904,1040.4610 z "
+ id="rect3072"
+ sodipodi:nodetypes="ccccc" />
+ <path
+ sodipodi:type="arc"
+ style="fill:url(#radialGradient3093);fill-opacity:1.0000000;fill-rule:nonzero;stroke:#2a00a7;stroke-width:6.6250000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="path3077"
+ sodipodi:cx="-565.89288"
+ sodipodi:cy="340.38669"
+ sodipodi:rx="290.74698"
+ sodipodi:ry="79.423561"
+ d="M -275.14590 340.38669 A 290.74698 79.423561 0 1 1 -856.63986,340.38669 A 290.74698 79.423561 0 1 1 -275.14590 340.38669 z"
+ transform="matrix(0.256235,0.000000,0.000000,0.250201,1716.983,956.3770)" />
+ <g
+ id="g4920"
+ transform="translate(1137.304,11.32366)">
+ <rect
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:8.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect4922"
+ width="309.83340"
+ height="242.59299"
+ x="325.51071"
+ y="707.68842"
+ ry="14.502842"
+ rx="14.502842" />
+ <rect
+ style="fill:url(#linearGradient4940);fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:8.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect4924"
+ width="309.83340"
+ height="242.59299"
+ x="325.51071"
+ y="707.68842"
+ ry="14.502842"
+ rx="14.502842" />
+ <path
+ style="opacity:0.51999998;fill:url(#linearGradient4942);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 333.42136,714.72823 L 628.75200,713.14620 L 479.76822,872.93254 L 333.42136,714.72823 z "
+ id="path4926" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#3c3f5e;stroke-width:5.4770780;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ d="M 333.42136,717.07450 L 628.75200,715.49246 L 479.76822,875.27880 L 333.42136,717.07450 z "
+ id="path4928" />
+ <path
+ style="fill:#ffffff;fill-opacity:0.99447513;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 333.42136,714.34392 L 628.75200,712.82452 L 479.76822,866.28270 L 333.42136,714.34392 z "
+ id="path4930" />
+ <path
+ style="opacity:0.46009389;fill:url(#linearGradient4944);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 339.33200,715.58159 L 621.47820,714.12486 L 479.14552,861.25486 L 339.33200,715.58159 z "
+ id="path4932" />
+ <rect
+ style="fill:#ffffff;fill-opacity:0.44505492;fill-rule:nonzero;stroke:#e50000;stroke-width:5.1249943;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect4934"
+ width="238.27043"
+ height="102.11589"
+ x="86.626945"
+ y="885.01361"
+ rx="14.502828"
+ ry="14.502828"
+ transform="matrix(0.952074,-0.305867,0.305867,0.952074,0.000000,0.000000)" />
+ <text
+ xml:space="preserve"
+ style="font-size:80.367622;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;fill:#e71717;fill-opacity:1.0000000;stroke:none;stroke-width:1.0000000pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000;font-family:Bitstream Charter;text-anchor:start;writing-mode:lr-tb"
+ x="97.010704"
+ y="961.93536"
+ id="text4936"
+ sodipodi:linespacing="100%"
+ transform="matrix(0.952074,-0.305867,0.305867,0.952074,0.000000,0.000000)"><tspan
+ sodipodi:role="line"
+ id="tspan4938"
+ x="97.010704"
+ y="961.93536">SPAM</tspan></text>
+ </g>
+ <path
+ style="fill:url(#linearGradient5896);fill-opacity:1.0000000;fill-rule:evenodd;stroke:#edc815;stroke-width:1.4182249;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 427.76540,385.20353 C 376.55040,420.90606 466.77468,446.01202 434.21611,496.56099 L 441.84056,499.23204 L 425.60512,514.41636 L 419.66043,492.13987 L 426.86269,493.97977 C 435.49662,454.31558 377.57624,448.33522 370.84614,394.13912 C 354.43693,403.56919 332.68344,410.52436 344.99942,434.20743 L 351.71877,432.37777 L 347.02522,447.73731 L 332.35927,439.23870 L 338.35617,436.80490 C 327.12517,426.76221 319.75596,409.57921 326.20861,383.68072"
+ id="path5130"
+ sodipodi:nodetypes="ccccccccccccc"
+ inkscape:export-xdpi="90.000000"
+ inkscape:export-ydpi="90.000000"
+ inkscape:export-filename="/home/tina/Desktop/spamfilter.png" />
+ <g
+ id="g5010"
+ transform="matrix(0.300454,0.000000,0.000000,0.300454,43.64869,308.8726)"
+ inkscape:export-filename="/home/tina/Desktop/spamfilter.png"
+ inkscape:export-xdpi="90.000000"
+ inkscape:export-ydpi="90.000000">
+ <rect
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:8.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect5012"
+ width="309.83340"
+ height="242.59299"
+ x="1048.8324"
+ y="-58.181622"
+ ry="17.106504"
+ rx="17.106504" />
+ <rect
+ style="fill:url(#linearGradient3404);fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:8.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect5014"
+ width="309.83340"
+ height="242.59299"
+ x="1048.8324"
+ y="-58.181622"
+ ry="17.106504"
+ rx="17.106504" />
+ <path
+ style="opacity:0.51999998;fill:url(#linearGradient3406);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 1056.7431,-51.141810 L 1352.0737,-52.723840 L 1203.0899,107.06250 L 1056.7431,-51.141810 z "
+ id="path5016" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#3c3f5e;stroke-width:5.4770780;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ d="M 1056.7431,-48.795540 L 1352.0737,-50.377580 L 1203.0899,109.40876 L 1056.7431,-48.795540 z "
+ id="path5018" />
+ <path
+ style="fill:#ffffff;fill-opacity:0.99447513;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 1056.7431,-51.526120 L 1352.0737,-53.045520 L 1203.0899,100.41266 L 1056.7431,-51.526120 z "
+ id="path5020" />
+ <path
+ style="opacity:0.46009389;fill:url(#linearGradient3408);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 1062.6537,-50.288450 L 1344.7999,-51.745180 L 1202.4672,95.384820 L 1062.6537,-50.288450 z "
+ id="path5022" />
+ </g>
+ <g
+ id="g6585"
+ transform="matrix(0.321914,0.000000,0.000000,0.321914,8.217869,313.8258)"
+ inkscape:export-filename="/home/tina/Desktop/spamfilter.png"
+ inkscape:export-xdpi="90.000000"
+ inkscape:export-ydpi="90.000000">
+ <rect
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:8.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect6587"
+ width="309.83340"
+ height="242.59299"
+ x="1048.8324"
+ y="-58.181622"
+ ry="15.966108"
+ rx="15.966108" />
+ <rect
+ style="fill:url(#linearGradient6599);fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:8.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect6589"
+ width="309.83340"
+ height="242.59299"
+ x="1048.8324"
+ y="-58.181622"
+ ry="15.966108"
+ rx="15.966108" />
+ <path
+ style="opacity:0.51999998;fill:url(#linearGradient6601);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 1056.7431,-51.141810 L 1352.0737,-52.723840 L 1203.0899,107.06250 L 1056.7431,-51.141810 z "
+ id="path6591" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#3c3f5e;stroke-width:5.4770780;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ d="M 1056.7431,-48.795540 L 1352.0737,-50.377580 L 1203.0899,109.40876 L 1056.7431,-48.795540 z "
+ id="path6593" />
+ <path
+ style="fill:#ffffff;fill-opacity:0.99447513;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 1056.7431,-51.526120 L 1352.0737,-53.045520 L 1203.0899,100.41266 L 1056.7431,-51.526120 z "
+ id="path6595" />
+ <path
+ style="opacity:0.46009389;fill:url(#linearGradient6603);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 1062.6537,-50.288450 L 1344.7999,-51.745180 L 1202.4672,95.384820 L 1062.6537,-50.288450 z "
+ id="path6597" />
+ </g>
+ <g
+ id="g5050"
+ transform="matrix(0.332644,0.000000,0.000000,0.332644,-14.25084,318.6381)"
+ inkscape:export-filename="/home/tina/Desktop/spamfilter.png"
+ inkscape:export-xdpi="90.000000"
+ inkscape:export-ydpi="90.000000">
+ <rect
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:8.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect5052"
+ width="309.83340"
+ height="242.59299"
+ x="1048.8324"
+ y="-58.181622"
+ ry="15.451089"
+ rx="15.451089" />
+ <rect
+ style="fill:url(#linearGradient3416);fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:8.0000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect5054"
+ width="309.83340"
+ height="242.59299"
+ x="1048.8324"
+ y="-58.181622"
+ ry="15.451089"
+ rx="15.451089" />
+ <path
+ style="opacity:0.51999998;fill:url(#linearGradient3418);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 1056.7431,-51.141810 L 1352.0737,-52.723840 L 1203.0899,107.06250 L 1056.7431,-51.141810 z "
+ id="path5056" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#3c3f5e;stroke-width:5.4770780;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ d="M 1056.7431,-48.795540 L 1352.0737,-50.377580 L 1203.0899,109.40876 L 1056.7431,-48.795540 z "
+ id="path5058" />
+ <path
+ style="fill:#ffffff;fill-opacity:0.99447513;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 1056.7431,-51.526120 L 1352.0737,-53.045520 L 1203.0899,100.41266 L 1056.7431,-51.526120 z "
+ id="path5060" />
+ <path
+ style="opacity:0.46009389;fill:url(#linearGradient3420);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 1062.6537,-50.288450 L 1344.7999,-51.745180 L 1202.4672,95.384820 L 1062.6537,-50.288450 z "
+ id="path5062" />
+ </g>
+ <g
+ transform="translate(1.528258,23.96449)"
+ id="g5746">
+ <rect
+ style="fill:#ffffff;fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:2.7126560;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect5072"
+ width="105.05893"
+ height="82.258919"
+ x="321.21570"
+ y="280.81036"
+ ry="5.1397109"
+ rx="5.1397109" />
+ <rect
+ style="fill:url(#linearGradient5754);fill-opacity:1.0000000;fill-rule:nonzero;stroke:#361f7b;stroke-width:2.7126560;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect5074"
+ width="105.05893"
+ height="82.258919"
+ x="321.21570"
+ y="280.81036"
+ ry="5.1397109"
+ rx="5.1397109" />
+ <path
+ style="opacity:0.51999998;fill:url(#linearGradient5756);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 323.89807,283.19743 L 424.03936,282.66099 L 373.52164,336.84167 L 323.89807,283.19743 z "
+ id="path5076" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#3c3f5e;stroke-width:1.8571786;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:0.32417580"
+ d="M 323.89807,283.99301 L 424.03936,283.45657 L 373.52164,337.63724 L 323.89807,283.99301 z "
+ id="path5078" />
+ <path
+ style="fill:#ffffff;fill-opacity:0.99447513;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 323.89807,283.06712 L 424.03936,282.55192 L 373.52164,334.58683 L 323.89807,283.06712 z "
+ id="path5080" />
+ <path
+ style="opacity:0.46009389;fill:url(#linearGradient5758);fill-opacity:1.0000000;fill-rule:evenodd;stroke:none;stroke-width:1.2500000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 325.90225,283.48679 L 421.57295,282.99284 L 373.31049,332.88198 L 325.90225,283.48679 z "
+ id="path5082" />
+ </g>
+ </g>
+</svg>
diff --git a/kmail/pics/pgp-keys.png b/kmail/pics/pgp-keys.png
new file mode 100644
index 00000000..d6732ee4
--- /dev/null
+++ b/kmail/pics/pgp-keys.png
Binary files differ
diff --git a/kmail/pics/quotecollapse.png b/kmail/pics/quotecollapse.png
new file mode 100644
index 00000000..cb7ab9b1
--- /dev/null
+++ b/kmail/pics/quotecollapse.png
Binary files differ
diff --git a/kmail/pics/quoteexpand.png b/kmail/pics/quoteexpand.png
new file mode 100644
index 00000000..77a2d983
--- /dev/null
+++ b/kmail/pics/quoteexpand.png
Binary files differ
diff --git a/kmail/popaccount.cpp b/kmail/popaccount.cpp
new file mode 100644
index 00000000..ebe8bf48
--- /dev/null
+++ b/kmail/popaccount.cpp
@@ -0,0 +1,1086 @@
+/*
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2000 Don Sanders <sanders@kde.org>
+
+ Based on popaccount by:
+ Stefan Taferner <taferner@kde.org>
+ Markus Wuebben <markus.wuebben@kde.org>
+
+ 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
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "popaccount.h"
+
+#include "broadcaststatus.h"
+using KPIM::BroadcastStatus;
+#include "progressmanager.h"
+#include "kmfoldermgr.h"
+#include "kmfiltermgr.h"
+#include "kmpopfiltercnfrmdlg.h"
+#include "protocols.h"
+#include "kmglobal.h"
+#include "util.h"
+#include "accountmanager.h"
+
+#include <kdebug.h>
+#include <kstandarddirs.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kmainwindow.h>
+#include <kio/scheduler.h>
+#include <kio/passdlg.h>
+#include <kconfig.h>
+using KIO::MetaData;
+
+#include <qstylesheet.h>
+
+static const unsigned short int pop3DefaultPort = 110;
+
+namespace KMail {
+//-----------------------------------------------------------------------------
+PopAccount::PopAccount(AccountManager* aOwner, const QString& aAccountName, uint id)
+ : NetworkAccount(aOwner, aAccountName, id),
+ headerIt(headersOnServer),
+ processMsgsTimer( 0, "processMsgsTimer" )
+{
+ init();
+ job = 0;
+ mSlave = 0;
+ mPort = defaultPort();
+ stage = Idle;
+ indexOfCurrentMsg = -1;
+ curMsgStrm = 0;
+ processingDelay = 2*100;
+ mProcessing = false;
+ dataCounter = 0;
+ mUidsOfSeenMsgsDict.setAutoDelete( false );
+ mUidsOfNextSeenMsgsDict.setAutoDelete( false );
+
+ headersOnServer.setAutoDelete(true);
+ connect(&processMsgsTimer,SIGNAL(timeout()),SLOT(slotProcessPendingMsgs()));
+ KIO::Scheduler::connect(
+ SIGNAL(slaveError(KIO::Slave *, int, const QString &)),
+ this, SLOT(slotSlaveError(KIO::Slave *, int, const QString &)));
+
+ mHeaderDeleteUids.clear();
+ mHeaderDownUids.clear();
+ mHeaderLaterUids.clear();
+}
+
+
+//-----------------------------------------------------------------------------
+PopAccount::~PopAccount()
+{
+ if (job) {
+ job->kill();
+ mMsgsPendingDownload.clear();
+ processRemainingQueuedMessages();
+ saveUidList();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+QString PopAccount::type(void) const
+{
+ return "pop";
+}
+
+QString PopAccount::protocol() const {
+ return useSSL() ? POP_SSL_PROTOCOL : POP_PROTOCOL;
+}
+
+unsigned short int PopAccount::defaultPort() const {
+ return pop3DefaultPort;
+}
+
+//-----------------------------------------------------------------------------
+void PopAccount::init(void)
+{
+ NetworkAccount::init();
+
+ mUsePipelining = false;
+ mLeaveOnServer = false;
+ mLeaveOnServerDays = -1;
+ mLeaveOnServerCount = -1;
+ mLeaveOnServerSize = -1;
+ mFilterOnServer = false;
+ //tz todo
+ mFilterOnServerCheckSize = 50000;
+}
+
+//-----------------------------------------------------------------------------
+void PopAccount::pseudoAssign( const KMAccount * a ) {
+ slotAbortRequested();
+ NetworkAccount::pseudoAssign( a );
+
+ const PopAccount * p = dynamic_cast<const PopAccount*>( a );
+ if ( !p ) return;
+
+ setUsePipelining( p->usePipelining() );
+ setLeaveOnServer( p->leaveOnServer() );
+ setLeaveOnServerDays( p->leaveOnServerDays() );
+ setLeaveOnServerCount( p->leaveOnServerCount() );
+ setLeaveOnServerSize( p->leaveOnServerSize() );
+ setFilterOnServer( p->filterOnServer() );
+ setFilterOnServerCheckSize( p->filterOnServerCheckSize() );
+}
+
+//-----------------------------------------------------------------------------
+void PopAccount::processNewMail(bool _interactive)
+{
+ if (stage == Idle) {
+
+ if ( (mAskAgain || passwd().isEmpty() || mLogin.isEmpty()) &&
+ mAuth != "GSSAPI" ) {
+ QString passwd = NetworkAccount::passwd();
+ bool b = storePasswd();
+ if (KIO::PasswordDialog::getNameAndPassword(mLogin, passwd, &b,
+ i18n("You need to supply a username and a password to access this "
+ "mailbox."), false, QString::null, mName, i18n("Account:"))
+ != QDialog::Accepted)
+ {
+ checkDone( false, CheckAborted );
+ return;
+ } else {
+ setPasswd( passwd, b );
+ if ( b ) {
+ kmkernel->acctMgr()->writeConfig( true );
+ }
+ mAskAgain = false;
+ }
+ }
+
+ QString seenUidList = locateLocal( "data", "kmail/" + mLogin + ":" + "@" +
+ mHost + ":" + QString("%1").arg(mPort) );
+ KConfig config( seenUidList );
+ QStringList uidsOfSeenMsgs = config.readListEntry( "seenUidList" );
+ QValueList<int> timeOfSeenMsgs = config.readIntListEntry( "seenUidTimeList" );
+ mUidsOfSeenMsgsDict.clear();
+ mUidsOfSeenMsgsDict.resize( KMail::nextPrime( ( uidsOfSeenMsgs.count() * 11 ) / 10 ) );
+ int idx = 1;
+ for ( QStringList::ConstIterator it = uidsOfSeenMsgs.begin();
+ it != uidsOfSeenMsgs.end(); ++it, idx++ ) {
+ // we use mUidsOfSeenMsgsDict to just provide fast random access to the
+ // keys, so we can store the index(+1) that corresponds to the index of
+ // mTimeOfSeenMsgsVector for use in PopAccount::slotData()
+ mUidsOfSeenMsgsDict.insert( *it, (const int *)idx );
+ }
+ mTimeOfSeenMsgsVector.clear();
+ mTimeOfSeenMsgsVector.reserve( timeOfSeenMsgs.size() );
+ for ( QValueList<int>::ConstIterator it = timeOfSeenMsgs.begin();
+ it != timeOfSeenMsgs.end(); ++it) {
+ mTimeOfSeenMsgsVector.append( *it );
+ }
+ // If the counts differ then the config file has presumably been tampered
+ // with and so to avoid possible unwanted message deletion we'll treat
+ // them all as newly seen by clearing the seen times vector
+ if ( mTimeOfSeenMsgsVector.count() != mUidsOfSeenMsgsDict.count() )
+ mTimeOfSeenMsgsVector.clear();
+ QStringList downloadLater = config.readListEntry( "downloadLater" );
+ for ( QStringList::Iterator it = downloadLater.begin(); it != downloadLater.end(); ++it ) {
+ mHeaderLaterUids.insert( *it, true );
+ }
+ mUidsOfNextSeenMsgsDict.clear();
+ mTimeOfNextSeenMsgsMap.clear();
+ mSizeOfNextSeenMsgsDict.clear();
+
+ interactive = _interactive;
+ mUidlFinished = false;
+ startJob();
+ }
+ else {
+ checkDone( false, CheckIgnored );
+ return;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void PopAccount::readConfig(KConfig& config)
+{
+ NetworkAccount::readConfig(config);
+
+ mUsePipelining = config.readNumEntry("pipelining", false);
+ mLeaveOnServer = config.readNumEntry("leave-on-server", false);
+ mLeaveOnServerDays = config.readNumEntry("leave-on-server-days", -1);
+ mLeaveOnServerCount = config.readNumEntry("leave-on-server-count", -1);
+ mLeaveOnServerSize = config.readNumEntry("leave-on-server-size", -1);
+ mFilterOnServer = config.readNumEntry("filter-on-server", false);
+ mFilterOnServerCheckSize = config.readUnsignedNumEntry("filter-os-check-size", 50000);
+}
+
+
+//-----------------------------------------------------------------------------
+void PopAccount::writeConfig(KConfig& config)
+{
+ NetworkAccount::writeConfig(config);
+
+ config.writeEntry("pipelining", mUsePipelining);
+ config.writeEntry("leave-on-server", mLeaveOnServer);
+ config.writeEntry("leave-on-server-days", mLeaveOnServerDays);
+ config.writeEntry("leave-on-server-count", mLeaveOnServerCount);
+ config.writeEntry("leave-on-server-size", mLeaveOnServerSize);
+ config.writeEntry("filter-on-server", mFilterOnServer);
+ config.writeEntry("filter-os-check-size", mFilterOnServerCheckSize);
+}
+
+
+//-----------------------------------------------------------------------------
+void PopAccount::setUsePipelining(bool b)
+{
+ mUsePipelining = b;
+}
+
+//-----------------------------------------------------------------------------
+void PopAccount::setLeaveOnServer(bool b)
+{
+ mLeaveOnServer = b;
+}
+
+//-----------------------------------------------------------------------------
+void PopAccount::setLeaveOnServerDays(int days)
+{
+ mLeaveOnServerDays = days;
+}
+
+//-----------------------------------------------------------------------------
+void PopAccount::setLeaveOnServerCount(int count)
+{
+ mLeaveOnServerCount = count;
+}
+
+//-----------------------------------------------------------------------------
+void PopAccount::setLeaveOnServerSize(int size)
+{
+ mLeaveOnServerSize = size;
+}
+
+//---------------------------------------------------------------------------
+void PopAccount::setFilterOnServer(bool b)
+{
+ mFilterOnServer = b;
+}
+
+//---------------------------------------------------------------------------
+void PopAccount::setFilterOnServerCheckSize(unsigned int aSize)
+{
+ mFilterOnServerCheckSize = aSize;
+}
+
+//-----------------------------------------------------------------------------
+void PopAccount::connectJob() {
+ KIO::Scheduler::assignJobToSlave(mSlave, job);
+ connect(job, SIGNAL( data( KIO::Job*, const QByteArray &)),
+ SLOT( slotData( KIO::Job*, const QByteArray &)));
+ connect(job, SIGNAL( result( KIO::Job * ) ),
+ SLOT( slotResult( KIO::Job * ) ) );
+ connect(job, SIGNAL(infoMessage( KIO::Job*, const QString & )),
+ SLOT( slotMsgRetrieved(KIO::Job*, const QString &)));
+}
+
+
+//-----------------------------------------------------------------------------
+void PopAccount::slotCancel()
+{
+ mMsgsPendingDownload.clear();
+ processRemainingQueuedMessages();
+ saveUidList();
+ slotJobFinished();
+}
+
+
+//-----------------------------------------------------------------------------
+void PopAccount::slotProcessPendingMsgs()
+{
+ if (mProcessing) // not reentrant
+ return;
+ mProcessing = true;
+
+ bool addedOk;
+ QValueList<KMMessage*>::Iterator cur = msgsAwaitingProcessing.begin();
+ QStringList::Iterator curId = msgIdsAwaitingProcessing.begin();
+ QStringList::Iterator curUid = msgUidsAwaitingProcessing.begin();
+
+ while (cur != msgsAwaitingProcessing.end()) {
+ // note we can actually end up processing events in processNewMsg
+ // this happens when send receipts is turned on
+ // hence the check for re-entry at the start of this method.
+ // -sanders Update processNewMsg should no longer process events
+
+ addedOk = processNewMsg(*cur); //added ok? Error displayed if not.
+
+ if (!addedOk) {
+ mMsgsPendingDownload.clear();
+ msgIdsAwaitingProcessing.clear();
+ msgUidsAwaitingProcessing.clear();
+ break;
+ }
+ else {
+ idsOfMsgsToDelete.append( *curId );
+ mUidsOfNextSeenMsgsDict.insert( *curUid, (const int *)1 );
+ mTimeOfNextSeenMsgsMap.insert( *curUid, time(0) );
+ }
+ ++cur;
+ ++curId;
+ ++curUid;
+ }
+
+ msgsAwaitingProcessing.clear();
+ msgIdsAwaitingProcessing.clear();
+ msgUidsAwaitingProcessing.clear();
+ mProcessing = false;
+}
+
+
+//-----------------------------------------------------------------------------
+void PopAccount::slotAbortRequested()
+{
+ if (stage == Idle) return;
+ if ( mMailCheckProgressItem )
+ disconnect( mMailCheckProgressItem, SIGNAL( progressItemCanceled( KPIM::ProgressItem* ) ),
+ this, SLOT( slotAbortRequested() ) );
+ stage = Quit;
+ if (job) job->kill();
+ job = 0;
+ mSlave = 0;
+ slotCancel();
+}
+
+
+//-----------------------------------------------------------------------------
+void PopAccount::startJob()
+{
+ // Run the precommand
+ if (!runPrecommand(precommand()))
+ {
+ KMessageBox::sorry(0,
+ i18n("Could not execute precommand: %1").arg(precommand()),
+ i18n("KMail Error Message"));
+ checkDone( false, CheckError );
+ return;
+ }
+ // end precommand code
+
+ KURL url = getUrl();
+
+ if ( !url.isValid() ) {
+ KMessageBox::error(0, i18n("Source URL is malformed"),
+ i18n("Kioslave Error Message") );
+ return;
+ }
+
+ mMsgsPendingDownload.clear();
+ idsOfMsgs.clear();
+ mUidForIdMap.clear();
+ idsOfMsgsToDelete.clear();
+ idsOfForcedDeletes.clear();
+
+ //delete any headers if there are some this have to be done because of check again
+ headersOnServer.clear();
+ headers = false;
+ indexOfCurrentMsg = -1;
+
+ Q_ASSERT( !mMailCheckProgressItem );
+ QString escapedName = QStyleSheet::escape( mName );
+ mMailCheckProgressItem = KPIM::ProgressManager::createProgressItem(
+ "MailCheck" + mName,
+ escapedName,
+ i18n("Preparing transmission from \"%1\"...").arg( escapedName ),
+ true, // can be canceled
+ useSSL() || useTLS() );
+ connect( mMailCheckProgressItem, SIGNAL( progressItemCanceled( KPIM::ProgressItem* ) ),
+ this, SLOT( slotAbortRequested() ) );
+
+ numBytes = 0;
+ numBytesRead = 0;
+ stage = List;
+ mSlave = KIO::Scheduler::getConnectedSlave( url, slaveConfig() );
+ if (!mSlave)
+ {
+ slotSlaveError(0, KIO::ERR_CANNOT_LAUNCH_PROCESS, url.protocol());
+ return;
+ }
+ url.setPath(QString("/index"));
+ job = KIO::get( url, false, false );
+ connectJob();
+}
+
+MetaData PopAccount::slaveConfig() const {
+ MetaData m = NetworkAccount::slaveConfig();
+
+ m.insert("progress", "off");
+ m.insert("pipelining", (mUsePipelining) ? "on" : "off");
+ if (mAuth == "PLAIN" || mAuth == "LOGIN" || mAuth == "CRAM-MD5" ||
+ mAuth == "DIGEST-MD5" || mAuth == "NTLM" || mAuth == "GSSAPI") {
+ m.insert("auth", "SASL");
+ m.insert("sasl", mAuth);
+ } else if ( mAuth == "*" )
+ m.insert("auth", "USER");
+ else
+ m.insert("auth", mAuth);
+
+ return m;
+}
+
+//-----------------------------------------------------------------------------
+// one message is finished
+// add data to a KMMessage
+void PopAccount::slotMsgRetrieved(KIO::Job*, const QString & infoMsg)
+{
+ if (infoMsg != "message complete") return;
+ KMMessage *msg = new KMMessage;
+ msg->setComplete(true);
+ // Make sure to use LF as line ending to make the processing easier
+ // when piping through external programs
+ uint newSize = Util::crlf2lf( curMsgData.data(), curMsgData.size() );
+ curMsgData.resize( newSize );
+ msg->fromByteArray( curMsgData , true );
+ if (stage == Head)
+ {
+ int size = mMsgsPendingDownload[ headerIt.current()->id() ];
+ kdDebug(5006) << "Size of Message: " << size << endl;
+ msg->setMsgLength( size );
+ headerIt.current()->setHeader(msg);
+ ++headerIt;
+ slotGetNextHdr();
+ } else {
+ //kdDebug(5006) << kfuncinfo << "stage == Retr" << endl;
+ //kdDebug(5006) << "curMsgData.size() = " << curMsgData.size() << endl;
+ msg->setMsgLength( curMsgData.size() );
+ msgsAwaitingProcessing.append(msg);
+ msgIdsAwaitingProcessing.append(idsOfMsgs[indexOfCurrentMsg]);
+ msgUidsAwaitingProcessing.append( mUidForIdMap[idsOfMsgs[indexOfCurrentMsg]] );
+ slotGetNextMsg();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// finit state machine to cycle trow the stages
+void PopAccount::slotJobFinished() {
+ QStringList emptyList;
+ if (stage == List) {
+ kdDebug(5006) << k_funcinfo << "stage == List" << endl;
+ // set the initial size of mUidsOfNextSeenMsgsDict to the number of
+ // messages on the server + 10%
+ mUidsOfNextSeenMsgsDict.resize( KMail::nextPrime( ( idsOfMsgs.count() * 11 ) / 10 ) );
+ KURL url = getUrl();
+ url.setPath(QString("/uidl"));
+ job = KIO::get( url, false, false );
+ connectJob();
+ stage = Uidl;
+ }
+ else if (stage == Uidl) {
+ kdDebug(5006) << k_funcinfo << "stage == Uidl" << endl;
+ mUidlFinished = true;
+
+ if ( mLeaveOnServer && mUidForIdMap.isEmpty() &&
+ mUidsOfNextSeenMsgsDict.isEmpty() && !idsOfMsgs.isEmpty() ) {
+ KMessageBox::sorry(0, i18n("Your POP3 server (Account: %1) does not support "
+ "the UIDL command: this command is required to determine, in a reliable way, "
+ "which of the mails on the server KMail has already seen before;\n"
+ "the feature to leave the mails on the server will therefore not "
+ "work properly.").arg(NetworkAccount::name()) );
+ // An attempt to work around buggy pop servers, these seem to be popular.
+ mUidsOfNextSeenMsgsDict = mUidsOfSeenMsgsDict;
+ }
+
+ //check if filter on server
+ if (mFilterOnServer == true) {
+ QMap<QString, int>::Iterator hids;
+ for ( hids = mMsgsPendingDownload.begin();
+ hids != mMsgsPendingDownload.end(); hids++ ) {
+ kdDebug(5006) << "Length: " << hids.data() << endl;
+ //check for mails bigger mFilterOnServerCheckSize
+ if ( (unsigned int)hids.data() >= mFilterOnServerCheckSize ) {
+ kdDebug(5006) << "bigger than " << mFilterOnServerCheckSize << endl;
+ headersOnServer.append(new KMPopHeaders( hids.key(),
+ mUidForIdMap[hids.key()],
+ Later));//TODO
+ //set Action if already known
+ if( mHeaderDeleteUids.contains( headersOnServer.current()->uid() ) ) {
+ headersOnServer.current()->setAction(Delete);
+ }
+ else if( mHeaderDownUids.contains( headersOnServer.current()->uid() ) ) {
+ headersOnServer.current()->setAction(Down);
+ }
+ else if( mHeaderLaterUids.contains( headersOnServer.current()->uid() ) ) {
+ headersOnServer.current()->setAction(Later);
+ }
+ }
+ }
+ // delete the uids so that you don't get them twice in the list
+ mHeaderDeleteUids.clear();
+ mHeaderDownUids.clear();
+ mHeaderLaterUids.clear();
+ }
+ // kdDebug(5006) << "Num of Msgs to Filter: " << headersOnServer.count() << endl;
+ // if there are mails which should be checkedc download the headers
+ if ((headersOnServer.count() > 0) && (mFilterOnServer == true)) {
+ headerIt.toFirst();
+ KURL url = getUrl();
+ QString headerIds;
+ while (headerIt.current())
+ {
+ headerIds += headerIt.current()->id();
+ if (!headerIt.atLast()) headerIds += ",";
+ ++headerIt;
+ }
+ headerIt.toFirst();
+ url.setPath(QString("/headers/") + headerIds);
+ job = KIO::get( url, false, false );
+ connectJob();
+ slotGetNextHdr();
+ stage = Head;
+ }
+ else {
+ stage = Retr;
+ numMsgs = mMsgsPendingDownload.count();
+ numBytesToRead = 0;
+ QMap<QString, int>::Iterator len;
+ for ( len = mMsgsPendingDownload.begin();
+ len != mMsgsPendingDownload.end(); len++ )
+ numBytesToRead += len.data();
+ idsOfMsgs = QStringList( mMsgsPendingDownload.keys() );
+ KURL url = getUrl();
+ url.setPath( "/download/" + idsOfMsgs.join(",") );
+ job = KIO::get( url, false, false );
+ connectJob();
+ slotGetNextMsg();
+ processMsgsTimer.start(processingDelay);
+ }
+ }
+ else if (stage == Head) {
+ kdDebug(5006) << k_funcinfo << "stage == Head" << endl;
+
+ // All headers have been downloaded, check which mail you want to get
+ // data is in list headersOnServer
+
+ // check if headers apply to a filter
+ // if set the action of the filter
+ KMPopFilterAction action;
+ bool dlgPopup = false;
+ for (headersOnServer.first(); headersOnServer.current(); headersOnServer.next()) {
+ action = (KMPopFilterAction)kmkernel->popFilterMgr()->process(headersOnServer.current()->header());
+ //debug todo
+ switch ( action ) {
+ case NoAction:
+ kdDebug(5006) << "PopFilterAction = NoAction" << endl;
+ break;
+ case Later:
+ kdDebug(5006) << "PopFilterAction = Later" << endl;
+ break;
+ case Delete:
+ kdDebug(5006) << "PopFilterAction = Delete" << endl;
+ break;
+ case Down:
+ kdDebug(5006) << "PopFilterAction = Down" << endl;
+ break;
+ default:
+ kdDebug(5006) << "PopFilterAction = default oops!" << endl;
+ break;
+ }
+ switch ( action ) {
+ case NoAction:
+ //kdDebug(5006) << "PopFilterAction = NoAction" << endl;
+ dlgPopup = true;
+ break;
+ case Later:
+ if (kmkernel->popFilterMgr()->showLaterMsgs())
+ dlgPopup = true;
+ // fall through
+ default:
+ headersOnServer.current()->setAction(action);
+ headersOnServer.current()->setRuleMatched(true);
+ break;
+ }
+ }
+
+ // if there are some messages which are not coverd by a filter
+ // show the dialog
+ headers = true;
+ if (dlgPopup) {
+ KMPopFilterCnfrmDlg dlg(&headersOnServer, this->name(), kmkernel->popFilterMgr()->showLaterMsgs());
+ dlg.exec();
+ }
+
+ for (headersOnServer.first(); headersOnServer.current(); headersOnServer.next()) {
+ if (headersOnServer.current()->action() == Delete ||
+ headersOnServer.current()->action() == Later) {
+ //remove entries from the lists when the mails should not be downloaded
+ //(deleted or downloaded later)
+ if ( mMsgsPendingDownload.contains( headersOnServer.current()->id() ) ) {
+ mMsgsPendingDownload.remove( headersOnServer.current()->id() );
+ }
+ if (headersOnServer.current()->action() == Delete) {
+ mHeaderDeleteUids.insert(headersOnServer.current()->uid(), true);
+ mUidsOfNextSeenMsgsDict.insert( headersOnServer.current()->uid(),
+ (const int *)1 );
+ idsOfMsgsToDelete.append(headersOnServer.current()->id());
+ mTimeOfNextSeenMsgsMap.insert( headersOnServer.current()->uid(),
+ time(0) );
+ }
+ else {
+ mHeaderLaterUids.insert(headersOnServer.current()->uid(), true);
+ }
+ }
+ else if (headersOnServer.current()->action() == Down) {
+ mHeaderDownUids.insert(headersOnServer.current()->uid(), true);
+ }
+ }
+
+ headersOnServer.clear();
+ stage = Retr;
+ numMsgs = mMsgsPendingDownload.count();
+ numBytesToRead = 0;
+ QMap<QString, int>::Iterator len;
+ for (len = mMsgsPendingDownload.begin();
+ len != mMsgsPendingDownload.end(); len++)
+ numBytesToRead += len.data();
+ idsOfMsgs = QStringList( mMsgsPendingDownload.keys() );
+ KURL url = getUrl();
+ url.setPath( "/download/" + idsOfMsgs.join(",") );
+ job = KIO::get( url, false, false );
+ connectJob();
+ slotGetNextMsg();
+ processMsgsTimer.start(processingDelay);
+ }
+ else if (stage == Retr) {
+ if ( mMailCheckProgressItem )
+ mMailCheckProgressItem->setProgress( 100 );
+ processRemainingQueuedMessages();
+
+ mHeaderDeleteUids.clear();
+ mHeaderDownUids.clear();
+ mHeaderLaterUids.clear();
+
+ kmkernel->folderMgr()->syncAllFolders();
+
+ KURL url = getUrl();
+ QMap< QPair<time_t, QString>, int > idsToSave;
+ idsToSave.clear();
+ // Check if we want to keep any messages
+ if ( mLeaveOnServer && !idsOfMsgsToDelete.isEmpty() ) {
+ // Keep all messages on server
+ if ( mLeaveOnServerDays == -1 && mLeaveOnServerCount <= 0 &&
+ mLeaveOnServerSize <= 0)
+ idsOfMsgsToDelete.clear();
+ // Delete old messages
+ else if ( mLeaveOnServerDays > 0 && !mTimeOfNextSeenMsgsMap.isEmpty() ) {
+ time_t timeLimit = time(0) - (86400 * mLeaveOnServerDays);
+ kdDebug() << "timeLimit is " << timeLimit << endl;
+ QStringList::Iterator cur = idsOfMsgsToDelete.begin();
+ for ( ; cur != idsOfMsgsToDelete.end(); ++cur) {
+ time_t msgTime = mTimeOfNextSeenMsgsMap[mUidForIdMap[*cur]];
+ kdDebug() << "id: " << *cur << " msgTime: " << msgTime << endl;
+ if (msgTime >= timeLimit ||
+ !mTimeOfNextSeenMsgsMap[mUidForIdMap[*cur]]) {
+ kdDebug() << "Saving msg id " << *cur << endl;
+ QPair<time_t, QString> msg(msgTime, *cur);
+ idsToSave.insert( msg, 1 );
+ }
+ }
+ }
+ // Delete more old messages if there are more than mLeaveOnServerCount
+ if ( mLeaveOnServerCount > 0 ) {
+ int numToDelete = idsToSave.count() - mLeaveOnServerCount;
+ kdDebug() << "numToDelete is " << numToDelete << endl;
+ if ( numToDelete > 0 && (unsigned)numToDelete < idsToSave.count() ) {
+ QMap< QPair<time_t, QString>, int >::Iterator cur = idsToSave.begin();
+ for ( int deleted = 0; deleted < numToDelete && cur != idsToSave.end()
+ ; deleted++, cur++ ) {
+ kdDebug() << "deleting msg id " << cur.key().second << endl;
+ idsToSave.remove( cur );
+ }
+ }
+ else if ( numToDelete > 0 && (unsigned)numToDelete >= idsToSave.count() )
+ idsToSave.clear();
+ }
+ // Delete more old messages until we're under mLeaveOnServerSize MBs
+ if ( mLeaveOnServerSize > 0 ) {
+ double sizeOnServer = 0;
+ QMap< QPair<time_t, QString>, int >::Iterator cur = idsToSave.begin();
+ for ( ; cur != idsToSave.end(); cur++ ) {
+ sizeOnServer +=
+ *mSizeOfNextSeenMsgsDict[ mUidForIdMap[ cur.key().second ] ];
+ }
+ kdDebug() << "sizeOnServer is " << sizeOnServer/(1024*1024) << "MB" << endl;
+ long limitInBytes = mLeaveOnServerSize * ( 1024 * 1024 );
+ for ( cur = idsToSave.begin(); cur != idsToSave.end()
+ && sizeOnServer > limitInBytes; cur++ ) {
+ sizeOnServer -=
+ *mSizeOfNextSeenMsgsDict[ mUidForIdMap[ cur.key().second ] ];
+ idsToSave.remove( cur );
+ }
+ }
+ // Save msgs from deletion
+ QMap< QPair<time_t, QString>, int >::Iterator it = idsToSave.begin();
+ kdDebug() << "Going to save " << idsToSave.count() << endl;
+ for ( ; it != idsToSave.end(); ++it ) {
+ kdDebug() << "saving msg id " << it.key().second << endl;
+ idsOfMsgsToDelete.remove( it.key().second );
+ }
+ }
+
+ if ( !idsOfForcedDeletes.isEmpty() ) {
+ idsOfMsgsToDelete += idsOfForcedDeletes;
+ idsOfForcedDeletes.clear();
+ }
+
+ // If there are messages to delete then delete them
+ if ( !idsOfMsgsToDelete.isEmpty() ) {
+ stage = Dele;
+ if ( mMailCheckProgressItem )
+ mMailCheckProgressItem->setStatus(
+ i18n( "Fetched 1 message from %1. Deleting messages from server...",
+ "Fetched %n messages from %1. Deleting messages from server...",
+ numMsgs )
+ .arg( mHost ) );
+ url.setPath("/remove/" + idsOfMsgsToDelete.join(","));
+ kdDebug(5006) << "url: " << url.prettyURL() << endl;
+ } else {
+ stage = Quit;
+ if ( mMailCheckProgressItem )
+ mMailCheckProgressItem->setStatus(
+ i18n( "Fetched 1 message from %1. Terminating transmission...",
+ "Fetched %n messages from %1. Terminating transmission...",
+ numMsgs )
+ .arg( mHost ) );
+ url.setPath(QString("/commit"));
+ kdDebug(5006) << "url: " << url.prettyURL() << endl;
+ }
+ job = KIO::get( url, false, false );
+ connectJob();
+ }
+ else if (stage == Dele) {
+ kdDebug(5006) << k_funcinfo << "stage == Dele" << endl;
+ // remove the uids of all messages which have been deleted
+ for ( QStringList::ConstIterator it = idsOfMsgsToDelete.begin();
+ it != idsOfMsgsToDelete.end(); ++it ) {
+ mUidsOfNextSeenMsgsDict.remove( mUidForIdMap[*it] );
+ }
+ idsOfMsgsToDelete.clear();
+ if ( mMailCheckProgressItem )
+ mMailCheckProgressItem->setStatus(
+ i18n( "Fetched 1 message from %1. Terminating transmission...",
+ "Fetched %n messages from %1. Terminating transmission...",
+ numMsgs )
+ .arg( mHost ) );
+ KURL url = getUrl();
+ url.setPath(QString("/commit"));
+ job = KIO::get( url, false, false );
+ stage = Quit;
+ connectJob();
+ }
+ else if (stage == Quit) {
+ kdDebug(5006) << k_funcinfo << "stage == Quit" << endl;
+ saveUidList();
+ job = 0;
+ if (mSlave) KIO::Scheduler::disconnectSlave(mSlave);
+ mSlave = 0;
+ stage = Idle;
+ if( mMailCheckProgressItem ) { // do this only once...
+ bool canceled = !kmkernel || kmkernel->mailCheckAborted() || mMailCheckProgressItem->canceled();
+ int numMessages = canceled ? indexOfCurrentMsg : idsOfMsgs.count();
+ BroadcastStatus::instance()->setStatusMsgTransmissionCompleted(
+ this->name(), numMessages, numBytes, numBytesRead, numBytesToRead, mLeaveOnServer, mMailCheckProgressItem );
+ // set mMailCheckProgressItem = 0 before calling setComplete() to prevent
+ // a race condition
+ ProgressItem *savedMailCheckProgressItem = mMailCheckProgressItem;
+ mMailCheckProgressItem = 0;
+ savedMailCheckProgressItem->setComplete(); // that will delete it
+ checkDone( ( numMessages > 0 ), canceled ? CheckAborted : CheckOK );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void PopAccount::processRemainingQueuedMessages()
+{
+ kdDebug(5006) << k_funcinfo << endl;
+ slotProcessPendingMsgs(); // Force processing of any messages still in the queue
+ processMsgsTimer.stop();
+
+ stage = Quit;
+ if ( kmkernel && kmkernel->folderMgr() ) {
+ kmkernel->folderMgr()->syncAllFolders();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void PopAccount::saveUidList()
+{
+ kdDebug(5006) << k_funcinfo << endl;
+ // Don't update the seen uid list unless we successfully got
+ // a new list from the server
+ if (!mUidlFinished) return;
+
+ QStringList uidsOfNextSeenMsgs;
+ QValueList<int> seenUidTimeList;
+ QDictIterator<int> it( mUidsOfNextSeenMsgsDict );
+ for( ; it.current(); ++it ) {
+ uidsOfNextSeenMsgs.append( it.currentKey() );
+ seenUidTimeList.append( mTimeOfNextSeenMsgsMap[it.currentKey()] );
+ }
+ QString seenUidList = locateLocal( "data", "kmail/" + mLogin + ":" + "@" +
+ mHost + ":" + QString("%1").arg(mPort) );
+ KConfig config( seenUidList );
+ config.writeEntry( "seenUidList", uidsOfNextSeenMsgs );
+ config.writeEntry( "seenUidTimeList", seenUidTimeList );
+ config.writeEntry( "downloadLater", QStringList( mHeaderLaterUids.keys() ) );
+ config.sync();
+}
+
+
+//-----------------------------------------------------------------------------
+void PopAccount::slotGetNextMsg()
+{
+ QMap<QString, int>::Iterator next = mMsgsPendingDownload.begin();
+
+ curMsgData.resize(0);
+ numMsgBytesRead = 0;
+ curMsgLen = 0;
+ delete curMsgStrm;
+ curMsgStrm = 0;
+
+ if ( next != mMsgsPendingDownload.end() ) {
+ // get the next message
+ int nextLen = next.data();
+ curMsgStrm = new QDataStream( curMsgData, IO_WriteOnly );
+ curMsgLen = nextLen;
+ ++indexOfCurrentMsg;
+ kdDebug(5006) << QString("Length of message about to get %1").arg( nextLen ) << endl;
+ mMsgsPendingDownload.remove( next.key() );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void PopAccount::slotData( KIO::Job* job, const QByteArray &data)
+{
+ if (data.size() == 0) {
+ kdDebug(5006) << "Data: <End>" << endl;
+ if ((stage == Retr) && (numMsgBytesRead < curMsgLen))
+ numBytesRead += curMsgLen - numMsgBytesRead;
+ else if (stage == Head){
+ kdDebug(5006) << "Head: <End>" << endl;
+ }
+ return;
+ }
+
+ int oldNumMsgBytesRead = numMsgBytesRead;
+ if (stage == Retr) {
+ headers = false;
+ curMsgStrm->writeRawBytes( data.data(), data.size() );
+ numMsgBytesRead += data.size();
+ if (numMsgBytesRead > curMsgLen)
+ numMsgBytesRead = curMsgLen;
+ numBytesRead += numMsgBytesRead - oldNumMsgBytesRead;
+ dataCounter++;
+ if ( mMailCheckProgressItem &&
+ ( dataCounter % 5 == 0 ||
+ ( indexOfCurrentMsg + 1 == numMsgs && numMsgBytesRead == curMsgLen ) ) )
+ {
+ QString msg;
+ if (numBytes != numBytesToRead && mLeaveOnServer)
+ {
+ msg = i18n("Fetching message %1 of %2 (%3 of %4 KB) for %5@%6 "
+ "(%7 KB remain on the server).")
+ .arg(indexOfCurrentMsg+1).arg(numMsgs).arg(numBytesRead/1024)
+ .arg(numBytesToRead/1024).arg(mLogin).arg(mHost).arg(numBytes/1024);
+ }
+ else
+ {
+ msg = i18n("Fetching message %1 of %2 (%3 of %4 KB) for %5@%6.")
+ .arg(indexOfCurrentMsg+1).arg(numMsgs).arg(numBytesRead/1024)
+ .arg(numBytesToRead/1024).arg(mLogin).arg(mHost);
+ }
+ mMailCheckProgressItem->setStatus( msg );
+ mMailCheckProgressItem->setProgress(
+ (numBytesToRead <= 100) ? 50 // We never know what the server tells us
+ // This way of dividing is required for > 21MB of mail
+ : (numBytesRead / (numBytesToRead / 100)) );
+ }
+ return;
+ }
+
+ if (stage == Head) {
+ curMsgStrm->writeRawBytes( data.data(), data.size() );
+ return;
+ }
+
+ // otherwise stage is List Or Uidl
+ QString qdata = data;
+ qdata = qdata.simplifyWhiteSpace(); // Workaround for Maillennium POP3/UNIBOX
+ int spc = qdata.find( ' ' );
+ if ( stage == List ) {
+ if ( spc > 0 ) {
+ QString length = qdata.mid(spc+1);
+ if (length.find(' ') != -1) length.truncate(length.find(' '));
+ int len = length.toInt();
+ numBytes += len;
+ QString id = qdata.left(spc);
+ idsOfMsgs.append( id );
+ mMsgsPendingDownload.insert( id, len );
+ }
+ else {
+ stage = Idle;
+ if ( job ) job->kill();
+ job = 0;
+ mSlave = 0;
+ KMessageBox::error( 0, i18n( "Unable to complete LIST operation." ),
+ i18n( "Invalid Response From Server") );
+ return;
+ }
+ }
+ else { // stage == Uidl
+ Q_ASSERT ( stage == Uidl);
+
+ QString id;
+ QString uid;
+
+ if ( spc <= 0 ) {
+ // an invalid uidl line. we might just need to skip it, but
+ // some servers generate invalid uids with valid ids. in that
+ // case we will just make up a uid - which will cause us to
+ // not cache the document, but we will be able to interoperate
+
+ int testid = atoi ( qdata.ascii() );
+ if ( testid < 1 ) {
+ // we'll just have to skip this
+ kdDebug(5006) << "PopAccount::slotData skipping UIDL entry due to parse error "
+ << endl << qdata.ascii() << endl;
+ return;
+ }
+ id.setNum (testid, 10);
+
+ QString datestring, serialstring;
+
+ serialstring.setNum ( ++dataCounter, 10 );
+ datestring.setNum ( time(NULL),10 );
+ uid = QString( "uidlgen" ) + datestring + QString( "." ) + serialstring;
+ kdDebug(5006) << "PopAccount::slotData message " << id.ascii()
+ << "%d has bad UIDL, cannot keep a copy on server" << endl;
+ idsOfForcedDeletes.append( id );
+ }
+ else {
+ id = qdata.left( spc );
+ uid = qdata.mid( spc + 1 );
+ }
+
+ int *size = new int; //malloc(size_of(int));
+ *size = mMsgsPendingDownload[id];
+ mSizeOfNextSeenMsgsDict.insert( uid, size );
+ if ( mUidsOfSeenMsgsDict.find( uid ) != 0 ) {
+ if ( mMsgsPendingDownload.contains( id ) ) {
+ mMsgsPendingDownload.remove( id );
+ }
+ else
+ kdDebug(5006) << "PopAccount::slotData synchronization failure." << endl;
+ idsOfMsgsToDelete.append( id );
+ mUidsOfNextSeenMsgsDict.insert( uid, (const int *)1 );
+ if ( mTimeOfSeenMsgsVector.empty() ) {
+ mTimeOfNextSeenMsgsMap.insert( uid, time(0) );
+ }
+ else {
+ // cast the int* with a long to can convert it to a int, BTW
+ // works with g++-4.0 and amd64
+ mTimeOfNextSeenMsgsMap.insert( uid, mTimeOfSeenMsgsVector[(int)( long )
+ mUidsOfSeenMsgsDict[uid] - 1] );
+ }
+ }
+ mUidForIdMap.insert( id, uid );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void PopAccount::slotResult( KIO::Job* )
+{
+ if (!job) return;
+ if ( job->error() )
+ {
+ if (interactive) {
+ if (headers) { // nothing to be done for headers
+ idsOfMsgs.clear();
+ }
+ if (stage == Head && job->error() == KIO::ERR_COULD_NOT_READ)
+ {
+ KMessageBox::error(0, i18n("Your server does not support the "
+ "TOP command. Therefore it is not possible to fetch the headers "
+ "of large emails first, before downloading them."));
+ slotCancel();
+ return;
+ }
+ // force the dialog to be shown next time the account is checked
+ if (!mStorePasswd) mPasswd = "";
+ job->showErrorDialog();
+ }
+ slotCancel();
+ }
+ else
+ slotJobFinished();
+}
+
+
+//-----------------------------------------------------------------------------
+void PopAccount::slotSlaveError(KIO::Slave *aSlave, int error,
+ const QString &errorMsg)
+{
+ if (aSlave != mSlave) return;
+ if (error == KIO::ERR_SLAVE_DIED) mSlave = 0;
+
+ // explicitely disconnect the slave if the connection went down
+ if ( error == KIO::ERR_CONNECTION_BROKEN && mSlave ) {
+ KIO::Scheduler::disconnectSlave( mSlave );
+ mSlave = 0;
+ }
+
+ if (interactive && kmkernel) {
+ KMessageBox::error(kmkernel->mainWin(), KIO::buildErrorString(error, errorMsg));
+ }
+
+
+ stage = Quit;
+ if (error == KIO::ERR_COULD_NOT_LOGIN && !mStorePasswd)
+ mAskAgain = true;
+ /* We need a timer, otherwise slotSlaveError of the next account is also
+ executed, if it reuses the slave, because the slave member variable
+ is changed too early */
+ QTimer::singleShot(0, this, SLOT(slotCancel()));
+}
+
+//-----------------------------------------------------------------------------
+void PopAccount::slotGetNextHdr(){
+ kdDebug(5006) << "slotGetNextHeader" << endl;
+
+ curMsgData.resize(0);
+ delete curMsgStrm;
+ curMsgStrm = 0;
+
+ curMsgStrm = new QDataStream( curMsgData, IO_WriteOnly );
+}
+
+void PopAccount::killAllJobs( bool ) {
+ // must reimpl., but we don't use it yet
+}
+
+} // namespace KMail
+#include "popaccount.moc"
diff --git a/kmail/popaccount.h b/kmail/popaccount.h
new file mode 100644
index 00000000..718ac316
--- /dev/null
+++ b/kmail/popaccount.h
@@ -0,0 +1,242 @@
+// -*- c++ -*-
+#ifndef KMAcctExpPop_h
+#define KMAcctExpPop_h
+
+#include "networkaccount.h"
+
+#include <qvaluelist.h>
+#include <qstringlist.h>
+#include <qvaluevector.h>
+#include <qtimer.h>
+#include <qdict.h>
+
+class KMPopHeaders;
+class KMMessage;
+class QDataStream;
+namespace KIO {
+ class MetaData;
+ class Slave;
+ class SimpleJob;
+ class Job;
+}
+
+/** The namespace where all classes of KMail can be found in. */
+namespace KMail {
+/**
+ * KMail account for pop mail account
+ */
+class PopAccount: public NetworkAccount {
+ Q_OBJECT
+
+public:
+ virtual ~PopAccount();
+ virtual void init(void);
+
+ virtual KIO::MetaData slaveConfig() const;
+
+ /** A weak assignment operator */
+ virtual void pseudoAssign( const KMAccount * a );
+
+ virtual QString protocol() const;
+ virtual unsigned short int defaultPort() const;
+
+ /**
+ * Sending of several commands at once
+ */
+ bool usePipelining(void) const { return mUsePipelining; }
+ virtual void setUsePipelining(bool);
+
+ /**
+ * Shall messages be left on the server upon retreival (TRUE)
+ * or deleted (FALSE).
+ */
+ bool leaveOnServer(void) const { return mLeaveOnServer; }
+ virtual void setLeaveOnServer(bool);
+
+ /**
+ * If value is positive, leave mail on the server for so many days.
+ */
+ int leaveOnServerDays(void) const { return mLeaveOnServerDays; }
+ virtual void setLeaveOnServerDays(int);
+
+ /**
+ * If value is positive, leave so many messages on the server.
+ */
+ int leaveOnServerCount(void) const { return mLeaveOnServerCount; }
+ virtual void setLeaveOnServerCount(int);
+
+ /**
+ * If value is positive, leave so many MBs on the server.
+ */
+ int leaveOnServerSize(void) const { return mLeaveOnServerSize; }
+ virtual void setLeaveOnServerSize(int);
+
+ /**
+ * Shall messages be filter on the server (TRUE)
+ * or not (FALSE).
+ */
+ bool filterOnServer(void) const { return mFilterOnServer; }
+ virtual void setFilterOnServer(bool);
+
+ /**
+ * Size of messages which should be check on the
+ * pop server before download
+ */
+ unsigned int filterOnServerCheckSize(void) const { return mFilterOnServerCheckSize; }
+ virtual void setFilterOnServerCheckSize(unsigned int);
+
+ /**
+ * Inherited methods.
+ */
+ virtual QString type(void) const;
+ virtual void readConfig(KConfig&);
+ virtual void writeConfig(KConfig&);
+ virtual void processNewMail(bool _interactive);
+
+ virtual void killAllJobs( bool disconnectSlave=false ); // NOOP currently
+
+protected:
+ enum Stage { Idle, List, Uidl, Head, Retr, Dele, Quit };
+ friend class ::AccountManager;
+ PopAccount(AccountManager* owner, const QString& accountName, uint id);
+
+ /**
+ * Start a KIO Job to get a list of messages on the pop server
+ */
+ void startJob();
+
+ /**
+ * Connect up the standard signals/slots for the KIO Jobs
+ */
+ void connectJob();
+
+ /**
+ * Process any queued messages
+ */
+ void processRemainingQueuedMessages();
+
+ /**
+ * Save the list of seen uids for this user/server
+ */
+ void saveUidList();
+
+ bool mUsePipelining;
+ bool mLeaveOnServer;
+ int mLeaveOnServerDays;
+ int mLeaveOnServerCount;
+ int mLeaveOnServerSize;
+ bool gotMsgs;
+ bool mFilterOnServer;
+ unsigned int mFilterOnServerCheckSize;
+
+ KIO::SimpleJob *job;
+ //Map of ID's vs. sizes of messages which should be downloaded
+ QMap<QString, int> mMsgsPendingDownload;
+
+ QPtrList<KMPopHeaders> headersOnServer;
+ QPtrListIterator<KMPopHeaders> headerIt;
+ bool headers;
+
+ QMap<QString, bool> mHeaderDeleteUids;
+ QMap<QString, bool> mHeaderDownUids;
+ QMap<QString, bool> mHeaderLaterUids;
+
+ QStringList idsOfMsgs; //used for ids and for count
+ QValueList<int> lensOfMsgs;
+ QMap<QString, QString> mUidForIdMap; // maps message ID (i.e. index on the server) to UID
+ QDict<int> mUidsOfSeenMsgsDict; // set of UIDs of previously seen messages (for fast lookup)
+ QDict<int> mUidsOfNextSeenMsgsDict; // set of UIDs of seen messages (for the next check)
+ QValueVector<int> mTimeOfSeenMsgsVector; // list of times of previously seen messages
+ QMap<QString, int> mTimeOfNextSeenMsgsMap; // map of uid to times of seen messages
+ QDict<int> mSizeOfNextSeenMsgsDict;
+ QStringList idsOfMsgsToDelete;
+ QStringList idsOfForcedDeletes;
+ int indexOfCurrentMsg;
+
+ QValueList<KMMessage*> msgsAwaitingProcessing;
+ QStringList msgIdsAwaitingProcessing;
+ QStringList msgUidsAwaitingProcessing;
+
+ QByteArray curMsgData;
+ QDataStream *curMsgStrm;
+
+ int curMsgLen;
+ Stage stage;
+ QTimer processMsgsTimer;
+ int processingDelay;
+ int numMsgs, numBytes, numBytesToRead, numBytesRead, numMsgBytesRead;
+ bool interactive;
+ bool mProcessing;
+ bool mUidlFinished;
+ int dataCounter;
+
+protected slots:
+ /**
+ * Messages are downloaded in the background and then once every x seconds
+ * a batch of messages are processed. Messages are processed in batches to
+ * reduce flicker (multiple refreshes of the qlistview of messages headers
+ * in a single second causes flicker) when using a fast pop server such as
+ * one on a lan.
+ *
+ * Processing a message means applying KMAccount::processNewMsg to it and
+ * adding its UID to the list of seen UIDs
+ */
+ void slotProcessPendingMsgs();
+
+ /**
+ * If there are more messages to be downloaded then start a new kio job
+ * to get the message whose id is at the head of the queue
+ */
+ void slotGetNextMsg();
+
+ /**
+ * A messages has been retrieved successfully. The next data belongs to the
+ * next message.
+ */
+ void slotMsgRetrieved(KIO::Job*, const QString &);
+
+ /**
+ * New data has arrived append it to the end of the current message
+ */
+ void slotData( KIO::Job*, const QByteArray &);
+
+ /**
+ * Finished downloading the current kio job, either due to an error
+ * or because the job has been canceled or because the complete message
+ * has been downloaded
+ */
+ void slotResult( KIO::Job* );
+
+ /**
+ * Cleans up after a user cancels the current job
+ */
+ void slotCancel();
+
+ /**
+ * Kills the job if still stage == List
+ */
+ void slotAbortRequested();
+
+ /**
+ * Called when a job is finished. Basically a finite state machine for
+ * cycling through the Idle, List, Uidl, Retr, Quit stages
+ */
+ void slotJobFinished();
+
+ /**
+ * Slave error handling
+ */
+ void slotSlaveError(KIO::Slave *, int, const QString &);
+
+ /**
+ * If there are more headers to be downloaded then start a new kio job
+ * to get the next header
+ */
+ void slotGetNextHdr();
+};
+
+} // namespace KMail
+
+
+
+#endif /*KMAcctExpPop_h*/
diff --git a/kmail/profiles/Makefile.am b/kmail/profiles/Makefile.am
new file mode 100644
index 00000000..4b61af44
--- /dev/null
+++ b/kmail/profiles/Makefile.am
@@ -0,0 +1,14 @@
+profiles = default html high-contrast purist secure
+
+# remove the .desktop suffix on install (which is needed for translation)
+install-data-local:
+ $(mkinstalldirs) $(DESTDIR)$(kde_datadir)/kmail
+ for profile in $(profiles); do \
+ $(INSTALL_DATA) $(srcdir)/profile-$$profile-rc.desktop \
+ $(DESTDIR)$(kde_datadir)/kmail/profile-$$profile-rc; \
+ done
+
+uninstall-local:
+ -for profile in $(profiles); do \
+ rm -f $(DESTDIR)$(kde_datadir)/kmail/profile-$$profile-rc; \
+ done
diff --git a/kmail/profiles/profile-default-rc.desktop b/kmail/profiles/profile-default-rc.desktop
new file mode 100644
index 00000000..7a75c8bb
--- /dev/null
+++ b/kmail/profiles/profile-default-rc.desktop
@@ -0,0 +1,180 @@
+[KMail Profile]
+Name=Default
+Name[af]=Standaard
+Name[ar]=الإفتراضي
+Name[az]=Əsas
+Name[be]=Па ўмаўчанні
+Name[bg]=Стандартен
+Name[br]=Dre ziouer
+Name[bs]=Podrazumijevano
+Name[ca]=Omissió
+Name[cs]=Implicitní
+Name[cy]=Rhagosod
+Name[da]=Standard
+Name[de]=Standard
+Name[el]=Προκαθορισμένο
+Name[eo]=Apriora
+Name[es]=Predeterminado
+Name[et]=Vaikimisi
+Name[eu]=Lehenetsia
+Name[fa]=پیش‌فرض
+Name[fi]=Oletus
+Name[fr]=Défaut
+Name[fy]=Standert
+Name[ga]=Réamhshocrú
+Name[gl]=Por Omisión
+Name[he]=ברירת מחדל
+Name[hi]=डिफ़ॉल्ट
+Name[hr]=Uobičajeno
+Name[hu]=Alapértelmezés
+Name[id]=Standar
+Name[is]=Sjálfgefið
+Name[it]=Predefinito
+Name[ja]=標準
+Name[ka]=ნაგულისხმევი
+Name[kk]=Әдетті
+Name[km]=លំនាំដើម
+Name[lt]=Numatytasis
+Name[lv]=Noklusētais
+Name[mk]=Стандарден
+Name[ms]=Piawai
+Name[mt]=Normali
+Name[nb]=Standard
+Name[nds]=Standard
+Name[ne]=पूर्वनिर्धारित
+Name[nl]=Standaard
+Name[nn]=Standard
+Name[pa]=ਮੂਲ
+Name[pl]=Domyślny
+Name[pt]=Por Omissão
+Name[pt_BR]=Padrão
+Name[ro]=Implicit
+Name[ru]=По умолчанию
+Name[rw]=Mburabuzi
+Name[se]=Standárda
+Name[sk]=Štandarné
+Name[sl]=Privzeto
+Name[sr]=Подразумевано
+Name[sr@Latn]=Podrazumevano
+Name[sv]=Standard
+Name[ta]=முன்னிருப்பு
+Name[tg]=Пешфарзӣ
+Name[th]=ค่าปริยาย
+Name[tr]=Öntanımlı
+Name[uk]=Типовий
+Name[uz]=Andoza
+Name[uz@cyrillic]=Андоза
+Name[ven]=Zwi si zwavhudi
+Name[vi]=Mặc định
+Name[xh]=Engagqibekanga
+Name[zh_CN]=默认
+Name[zh_TW]=預設
+Name[zu]=Engaqedekanga
+Comment=Standard profile
+Comment[af]=Standaard profiel
+Comment[az]=Standard profil
+Comment[be]=Стандартны профіль
+Comment[bg]=Стандартен профил
+Comment[bs]=Standardni profil
+Comment[ca]=Perfil estàndard
+Comment[cs]=Standardní profil
+Comment[cy]=Proffil safonol
+Comment[da]=Standard-profil
+Comment[de]=Standardprofil
+Comment[el]=Τυπικό προφίλ
+Comment[eo]=Apriora agordo
+Comment[es]=Perfil estándar
+Comment[et]=Standardprofiil
+Comment[eu]=Profil estandarra
+Comment[fa]=profile استاندارد
+Comment[fi]=Normaali profiili
+Comment[fr]=Profil standard
+Comment[fy]=Standert profyl
+Comment[ga]=Próifíl chaighdeánach
+Comment[gl]=Perfil Estándar
+Comment[he]=פרופיל ברירת מחדל
+Comment[hi]=मानक प्रोफ़ाइल
+Comment[hr]=Standardni profil
+Comment[hu]=Standard profil
+Comment[is]=Venjulegt snið
+Comment[it]=Profilo standard
+Comment[ja]=標準プロファイル
+Comment[ka]=სტანდარტული პროფილი
+Comment[kk]=Стандартты профилі
+Comment[km]=ទម្រង់​ខ្នាត​គំរូ
+Comment[lt]=Standartinis profilis
+Comment[lv]=Standarta profils
+Comment[mk]=Стандарден профил
+Comment[ms]=Profail Standard
+Comment[nb]=Standardprofil
+Comment[nds]=Standardprofil
+Comment[ne]=मानक प्रोफाइल
+Comment[nl]=Standaard profiel
+Comment[nn]=Standardprofil
+Comment[pl]=Profil standardowy
+Comment[pt]=O perfil-padrão
+Comment[pt_BR]=Perfil padrão
+Comment[ro]=Profil standard
+Comment[ru]=Обычный профиль
+Comment[se]=Standárdprofiila
+Comment[sk]=Štandardný profil
+Comment[sl]=Običajen profil
+Comment[sr]=Стандардни профил
+Comment[sr@Latn]=Standardni profil
+Comment[sv]=Standardprofil
+Comment[ta]=நிலையான சுருக்கக்குறிப்பு
+Comment[tg]=Профили оддӣ
+Comment[th]=โปรไฟล์มาตรฐาน
+Comment[tr]=Standart profil
+Comment[uk]=Типовий профіль
+Comment[uz]=Andoza profili
+Comment[uz@cyrillic]=Андоза профили
+Comment[ven]=Zwithu zwo doweleaho
+Comment[vi]=Hồ sơ chuẩn
+Comment[xh]=Imboniselo yabucala esezantsi
+Comment[zh_CN]=标准配置文件
+Comment[zh_TW]=標準設定
+Comment[zu]=Iprofayela Elingeneyo
+
+[Reader]
+BackgroundColor=255,255,255
+ColorbarHTML=255,64,64
+ColorbarPGP=128,255,128
+ColorbarPlain=255,255,128
+FlagMessage=0,127,0
+FollowedColor=255,0,255
+ForegroundColor=0,0,0
+LinkColor=84,112,152
+NewMessage=255,0,0
+PGPMessageEncr=0,128,255
+PGPMessageErr=255,0,0
+PGPMessageOkKeyBad=255,255,64
+PGPMessageOkKeyOk=64,255,64
+PGPMessageWarn=255,255,64
+QuotedText1=0,127,0
+QuotedText2=0,111,0
+QuotedText3=0,95,0
+FlagMessage=0,127,0
+UnreadMessage=0,0,255
+defaultColors=false
+showColorbar=false
+htmlMail=false
+
+[Fonts]
+defaultFonts=true
+
+[Geometry]
+nestedMessages=true
+longFolderList=true
+
+[General]
+dateDisplay=fancyDate
+showMessageSize=true
+showCryptoIcons=false
+
+[Behaviour]
+LoopOnGotoUnread=true
+
+[MDN]
+quote-message=0
+default-policy=0
diff --git a/kmail/profiles/profile-high-contrast-rc.desktop b/kmail/profiles/profile-high-contrast-rc.desktop
new file mode 100644
index 00000000..8c5b7cee
--- /dev/null
+++ b/kmail/profiles/profile-high-contrast-rc.desktop
@@ -0,0 +1,164 @@
+[KMail Profile]
+Name=High Contrast
+Name[af]=Hoë kontras
+Name[bg]=Висок контраст
+Name[br]=Dargemm huel
+Name[bs]=Visoki kontrast
+Name[ca]=Alt contrast
+Name[cs]=Vysoký kontrast
+Name[cy]=Cyferbyniad Gref
+Name[da]=Høj kontrast
+Name[de]=Starker Kontrast
+Name[el]=Μεγάλη αντίθεση
+Name[eo]=Forta kontrasto
+Name[es]=Alto contraste
+Name[et]=Suur kontrast
+Name[eu]=Kontraste altua
+Name[fa]=سایه روشن زیاد
+Name[fi]=Korkea kontrasti
+Name[fr]=Contraste élevé
+Name[fy]=Heech kontrast
+Name[ga]=Ardchodarsnacht
+Name[gl]=Alto contraste
+Name[he]=ניגודיות גבוהה
+Name[hi]=उच्च कंट्रास्ट
+Name[hr]=Visoki kontrast
+Name[hu]=Kontrasztos
+Name[is]=Mikil birtuskil
+Name[it]=Alto contrasto
+Name[ja]=ハイコントラスト
+Name[ka]=მაღალი კონტრასტი
+Name[kk]=Контрастығы жоғары
+Name[km]=កម្រិត​ពណ៌​ខ្ពស់
+Name[lt]=Didelis kontrastas
+Name[mk]=Висок контраст
+Name[ms]=Kontras Tinggi
+Name[nb]=Høy kontrast
+Name[nds]=Hoge Kontrast
+Name[ne]=उच्च ब्यतिरेक
+Name[nl]=Hoog contrast
+Name[nn]=Høg kontrast
+Name[pl]=Wysoki kontrast
+Name[pt]=Alto Contraste
+Name[pt_BR]=Alto Contraste
+Name[ro]=Contrast mare
+Name[ru]=Высокий контраст
+Name[sk]=Vysoký kontrast
+Name[sl]=Velik kontrast
+Name[sr]=Високи контраст
+Name[sr@Latn]=Visoki kontrast
+Name[sv]=Hög kontrast
+Name[ta]=அதிக அடர்த்தி
+Name[tg]=Тазоди баланд
+Name[tr]=Yüksek Kontrast
+Name[uk]=Висока контрастність
+Name[zh_CN]=高对比
+Name[zh_TW]=高反差
+Comment=Increased font sizes for visually impaired users
+Comment[af]=Vergroot die skrif tipe vir visueel gestremde gebruikers
+Comment[ar]=محارف بقياسات أكبر للمستخدمين ذوي البصر الضعيف
+Comment[az]=Görmə pozuqluğu olanlar üçün böyüdülmüş yazı növləri
+Comment[bg]=Увеличаване на размера на шрифта за потребители със зрителни увреждания
+Comment[bs]=Povećane dimenzije fontova za korisnike sa vizuelnim nedostacima
+Comment[ca]=Augmenta la mida de la lletra per als usuaris amb deficiències visuals
+Comment[cs]=Zvětší velikost písma pro zrakově postižené uživatele
+Comment[cy]=Meintiau wynebfathau wedi'u cynyddu ar gyfer defnyddwyr â nam gweledol
+Comment[da]=Forøgede skrifttypestørrelser for brugere med svagt syn
+Comment[de]=Größere Schriften für sehbehinderte Benutzer
+Comment[el]=Αυξημένα μεγέθη γραμματοσειρών για χρήστες με προβλήματα όρασης
+Comment[eo]=Pligrandigitaj tiparoj por vidhandikapitoj
+Comment[es]=Tamaños grandes de tipografía para usuarios con deficiencias visuales
+Comment[et]=Suured fondid halva nägemisega kasutajatele
+Comment[eu]=Letra-tipo handiagoak ikusmen arazoak dituzten erabiltzaileentzat
+Comment[fa]=اندازۀ قلمهای افزایش یافته برای کاربرانی که از نظر تصویری زیان دیده‌اند
+Comment[fi]=Suurennettu kirjasinkoko heikkonäköisille käyttäjille
+Comment[fr]=Des polices de caractères plus grandes pour les malvoyants
+Comment[fy]=Grutter lettertype foar brûker mei minne eagen
+Comment[gl]=Tamaños de fonte aumentados para usuarios con discapacidade visual
+Comment[he]=גופנים גדולים עבור אנשים בעלי ליקויי ראייה
+Comment[hi]=कम दिखाई देने वाले उपयोक्ताओं के लिए फ़ॉन्ट आकार बढ़ाएँ
+Comment[hr]=Povećana pisma za korisnike sa slabijim vidom
+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
+Comment[lv]=Palielināti fontu izmēri lietotājiem ar redzes traucējumiem
+Comment[ms]=Saiz fon ditambah untuk pengguna cacat penglihatan
+Comment[nb]=Økte skriftstørrelser for svaksynte brukere
+Comment[nds]=Grötter Tekens för kiekbehinnert Brukers
+Comment[ne]=दृश्यात्मक विकृत प्रयोगकर्ताका लागि बढाइएको फन्ट साइज
+Comment[nl]=Groter lettertype voor slechtziende gebruikers
+Comment[nn]=Auka skriftstorleik for synshemma brukarar
+Comment[pl]=Zwiększone rozmiary czcionek dla osób mających problemy ze wzrokiem
+Comment[pt]=Tamanhos de letra aumentados para utilizadores com problemas visuais
+Comment[pt_BR]=Fontes de tamanho maior para usuários com visão reduzida
+Comment[ro]=Mărimi de font mari pentru utilizatori cu handicap
+Comment[ru]=Увеличенные размеры шрифтов для людей с ослабленным зрением
+Comment[sk]=Zväčšená veľkosť písma pre zrakovo postihnutých užívateľov
+Comment[sl]=Povečane velikosti pisav za uporabnike s slabšim vidom
+Comment[sr]=Повећани фонтови за кориснике са лошијим видом
+Comment[sr@Latn]=Povećani fontovi za korisnike sa lošijim vidom
+Comment[sv]=Större teckenstorlek för användare med synproblem
+Comment[ta]=பார்வையற்ற பயனர்களுக்கான பெரிதுப்படுத்தப்பட்ட எழுத்துரு அளவுகள்
+Comment[tg]=Ҳуруфҳои андозаашон калон барои одамҳои бо чашми хира
+Comment[th]=เพิ่มขนาดตัวอักษรสำหรับผู้ที่สายตาไม่ดี
+Comment[tr]=Görsel engelli kullanıcılar için arttırılmış yazı tipi boyutu
+Comment[uk]=Збільшений розмір шрифтів для людей з поганим зором
+Comment[ven]=Saizi ya fontu yo engedzhwaho uitela vhashumisi vha zwauvhona
+Comment[vi]=Tăng cỡ font cho người dùng tàn tật
+Comment[xh]=Ubungakanani bobukhulu begama bunyusiwe kubasebenzisi ababonayo
+Comment[zh_CN]=对有视觉障碍的用户增加字体大小
+Comment[zh_TW]=遞增字型大小以適合視覺障礙者
+Comment[zu]=Abakhushululiweyo osayizi befont kwabantu abayizimpumputhe
+
+[Reader]
+BackgroundColor=250,235,215
+ColorbarHTML=255,64,64
+ColorbarPGP=128,255,128
+ColorbarPlain=255,255,128
+FlagMessage=0,127,0
+FollowedColor=255,0,255
+ForegroundColor=0,0,0
+LinkColor=84,112,152
+NewMessage=255,0,0
+PGPMessageEncr=0,128,255
+PGPMessageErr=255,0,0
+PGPMessageOkKeyBad=255,255,64
+PGPMessageOkKeyOk=64,255,64
+PGPMessageWarn=255,255,64
+QuotedText1=0,127,0
+QuotedText2=0,111,0
+QuotedText3=0,95,0
+FlagMessage=0,127,0
+UnreadMessage=0,0,255
+defaultColors=false
+showColorbar=false
+htmlMail=false
+
+[Fonts]
+body-font=Sans Serif,12,-1,5,75,0,0,0,0,0
+defaultFonts=false
+folder-font=Sans Serif,12,-1,5,75,0,0,0,0,0
+list-date-font=Sans Serif,12,-1,5,75,0,0,0,0,0
+list-font=Sans Serif,12,-1,5,75,0,0,0,0,0
+quote1-font=Sans Serif,12,-1,5,75,0,0,0,0,0
+quote2-font=Sans Serif,12,-1,5,75,0,0,0,0,0
+quote3-font=Sans Serif,12,-1,5,75,0,0,0,0,0
+
+# should this profile be restricted to font and color changes?
+[Geometry]
+nestedMessages=true
+longFolderList=true
+
+[General]
+dateDisplay=fancyDate
+showMessageSize=true
+showCryptoIcons=false
+
+[MDN]
+quote-message=0
+default-policy=0
diff --git a/kmail/profiles/profile-html-rc.desktop b/kmail/profiles/profile-html-rc.desktop
new file mode 100644
index 00000000..e609c50d
--- /dev/null
+++ b/kmail/profiles/profile-html-rc.desktop
@@ -0,0 +1,109 @@
+[KMail Profile]
+Name=HTML
+Name[ar]=لغة علامات النص الفائق
+Name[fa]=زنگام
+Name[ne]=एचटीएमएल
+Comment=Standard profile with HTML preview enabled - less secure!
+Comment[af]=Standaard profiel, met HTML voorskou geaktiveer. Dis minder veilig!
+Comment[ar]=الشاكلة المعيارية مع تمكين معاينة لغة علامة النص الفائق HTML - أقل أمناً !
+Comment[az]=HTML nümayişli standard - ən az etibarlı!
+Comment[bg]=Стандартен профил с поддръжка на HTML в писмата (по-малко сигурен)
+Comment[bs]=Standardni profil sa omogućenim HTML pregledom - manje siguran!
+Comment[ca]=Perfil estàndard amb la vista prèvia de l'HTML habilitada - menys segur!
+Comment[cs]=Standardní profil s povoleným náhledem HTML souborů - méně bezpečné!
+Comment[cy]=Proffil safonol efo rhagolwg HTML yn alluog - llai ddiogel!
+Comment[da]=Standardprofil med HTML-visning aktiveret - mindre sikkert!
+Comment[de]=Standardprofil mit aktivierter HTML-Vorschau - weniger sicher
+Comment[el]=Τυπικό προφίλ με την προεπισκόπηση HTML ενεργοποιημένη - λιγότερο ασφαλές!
+Comment[eo]=Apriora agordo, kun ŝaltita HTML-antaŭrigardo - malpli sekura!
+Comment[es]=Perfil estándar con las vista HTML activada - menos seguro
+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[fy]=Standertprofyl mei HTML-foarbyld aktivearre - minder feilich!
+Comment[gl]=Perfil estándar con previsualización HTML activada - menos seguro!
+Comment[hi]=एचटीएमएल पूर्वावलोकन के साथ मानक प्रोफ़ाइल सक्षम है - कम सुरक्षित!
+Comment[hr]=Standardni profil sa uključenim pregledom HTML sadržaja - manje sigurno!
+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!
+Comment[lv]=Standarta profils ar atļautu HTML apskati - nedrošāks!
+Comment[mk]=Стандарден профил со овозможен преглед на HTML - помалку безбедно!
+Comment[ms]=Profil piawai dengan paparan HTML diaktifkan - kurang selamat!
+Comment[nb]=Standardprofil med forhåndsvisning av HTML – mindre sikkert!
+Comment[nds]=Standardprofil mit aktiveert HTML-Vöransicht - weniger seker
+Comment[ne]=एचटीएमएल पूर्वावलोकनको मानक प्रोफाइल सक्षम पारियो - कम सुरक्षित!
+Comment[nl]=Standaardprofiel met HTML-voorbeeld geactiveerd - minder veilig!
+Comment[nn]=Standard profil med HTML-vising slått på – mindre trygt!
+Comment[pl]=Standardowy profil z włączonym podglądem HTML - mniej bezpieczny!
+Comment[pt]=O perfil predefinido com a antevisão de HTML activada - menos seguro!
+Comment[pt_BR]=Perfil padrão com previsão de HTML habilitada - menos seguro!
+Comment[ro]=Profil standard cu previzualizare HTML activată (securitate scăzută!)
+Comment[ru]=Обычный профиль с просмотром HTML - менее безопасный!
+Comment[sk]=Štandardný profil s HTML náhľadom - menej bezpečné!
+Comment[sl]=Običajen profil z omogočenim ogledom v HTML - manj varno!
+Comment[sr]=Стандардни профил са омогућеним прегледом HTML-а — мање безбедно!
+Comment[sr@Latn]=Standardni profil sa omogućenim pregledom HTML-a — manje bezbedno!
+Comment[sv]=Standardprofil med HTML-förhandsgranskning aktiverad - mindre säker!
+Comment[ta]= சரியான பயனர் விவரங்களுடன் HTML முன்காட்சி செயலாக்கப்பட்டது - குறைந்த பாதுகாப்பு!
+Comment[tg]=Профили оддӣ бо намоиши HTML - каме бехатарнок аст!
+Comment[th]=โปรไฟล์มาตรฐานพร้อมกับแสดงตัวอย่าง HTML - ความปลอดภัยน้อย !
+Comment[tr]=HTML ön izleme aktif standart profil - daha az güvenli!
+Comment[uk]=Окремий профіль з ввімкненим переглядом HTML - менш безпечний!
+Comment[ven]=Profile yo doweleaho irena HTML ya mbonelaphanda yo itwaho - a i ngo tsireledzea!
+Comment[vi]=Hồ sơ chuẩn cho phép xem trước HTML - I't bảo mật hơn !
+Comment[xh]=Imboniselo yabucala esezantsi nge HTML imboniso yenziwe - ukhuseleko oluncinane!
+Comment[zh_CN]=启用 HTML 预览的标准配置文件 - 更不安全!
+Comment[zh_TW]=標準設定,HTML 預覽開啟 - 較不安全!
+Comment[zu]=Yenza ngokulingeneyo iprofayela nge HTML okokukhombisa ngaphambili kwenzeliwe - okuncane ukuvikeleka!
+
+[Reader]
+BackgroundColor=255,255,255
+ColorbarHTML=255,64,64
+ColorbarPGP=128,255,128
+ColorbarPlain=255,255,128
+FlagMessage=0,127,0
+FollowedColor=255,0,255
+ForegroundColor=0,0,0
+LinkColor=84,112,152
+NewMessage=255,0,0
+PGPMessageEncr=0,128,255
+PGPMessageErr=255,0,0
+PGPMessageOkKeyBad=255,255,64
+PGPMessageOkKeyOk=64,255,64
+PGPMessageWarn=255,255,64
+QuotedText1=0,127,0
+QuotedText2=0,111,0
+QuotedText3=0,95,0
+FlagMessage=0,127,0
+UnreadMessage=0,0,255
+defaultColors=false
+showColorbar=false
+htmlMail=true
+htmlLoadExternal=false
+
+[Fonts]
+defaultFonts=true
+
+[Geometry]
+nestedMessages=true
+longFolderList=true
+
+[General]
+dateDisplay=fancyDate
+showMessageSize=true
+showCryptoIcons=false
+
+[Behaviour]
+LoopOnGotoUnread=true
+
+[MDN]
+quote-message=0
+default-policy=0
diff --git a/kmail/profiles/profile-purist-rc.desktop b/kmail/profiles/profile-purist-rc.desktop
new file mode 100644
index 00000000..c77f2808
--- /dev/null
+++ b/kmail/profiles/profile-purist-rc.desktop
@@ -0,0 +1,129 @@
+[KMail Profile]
+Name=Purist
+Name[af]=Suiwer
+Name[bg]=Пуритан
+Name[bs]=Za čistunce
+Name[cs]=Puristický
+Name[cy]=Puryddol
+Name[eo]=Puristo
+Name[es]=Purista
+Name[eu]=Purista
+Name[fa]=سَره‌گرا
+Name[fi]=Puristi
+Name[fr]=Puriste
+Name[gl]=Purista
+Name[he]=נקי
+Name[hi]=प्यूरिस्ट
+Name[hr]=Čistunski
+Name[hu]=Egyszerűsített
+Name[it]=Purista
+Name[ja]=純正
+Name[ka]=პურისტი
+Name[kk]=Пурист
+Name[km]=បរិសុទ្ធ
+Name[lt]=Itin paprastas
+Name[lv]=Puritānis
+Name[ms]=Mementingkan Istilah Yang Betul
+Name[nds]=Reen
+Name[ne]=संस्कारक
+Name[nn]=Enkel
+Name[pl]=Czysty
+Name[pt]=Purista
+Name[pt_BR]=Purista
+Name[ro]=Puritan
+Name[ru]=Аскет
+Name[se]=Oktageardán
+Name[sr]=Чисто
+Name[sr@Latn]=Čisto
+Name[sv]=Ren
+Name[ta]=சுத்தமானது
+Name[tg]=Пурист
+Name[tr]=Pürist
+Name[uk]=Пуристичний
+Name[ven]=Zwo kunaho
+Name[xh]=isiPurist
+Name[zh_CN]=严格
+Name[zh_TW]=較安全
+Name[zu]=Okuhlanzekileyo
+Comment=Most features turned off, KDE global settings are used
+Comment[af]=Meeste funksies af geskakel. KDE globale waardes word gebruik.
+Comment[ar]=أغلب الميزات غبر عاملة, سيتم استخدام اعدادات KDE العامة
+Comment[az]=Bir çox xüsusiyyət açıq deyil, KDE qlobal qurğuları işlədilir
+Comment[bg]=Пуритански режим, при който повечето възможности за изключени
+Comment[bs]=Većina mogućnosti je isključena, koriste se KDE globalne postavke
+Comment[ca]=La majoria de característiques estan desactivades, s'usarà l'arranjament global de KDE
+Comment[cs]=Většina funkcí je vypnuta a použije se globální nastavení KDE
+Comment[cy]=Y mwyafrif o nodweddion wedi'u diffodd, defnyddir gosodiadau eang KDE
+Comment[da]=De fleste egenskaber slået fra, KDE's globale opsætning bruges
+Comment[de]=Die meisten Funktionen werden zugunsten von KDE-Voreinstellungen deaktiviert
+Comment[el]=Τα περισσότερα χαρακτηριστικά απενεργοποιημένα, ενώ χρησιμοποιούνται οι καθολικές ρυθμίσεις του KDE
+Comment[eo]=Plej multaj funkcioj malŝaltitaj, malloka agordo de KDE estas uzata
+Comment[es]=La mayoría de las características desactivadas; se usan las opciones globales de KDE
+Comment[et]=Enamik võimalusi välja lülitatud, kasutatakse KDE seadistusi
+Comment[eu]=Eginbide gehienak ezgaituta, KDE-ren ezarpen globalak erabiliko dira
+Comment[fa]=اغلب ویژگیها خاموش شد، تنظیمات سراسری KDE استفاده می‌شوند
+Comment[fi]=Suurin osa ominaisuuksia on pois päältä, käytetään KDE:n järjestelmänlaajuisia asetuksia.
+Comment[fr]=Presque toutes les fonctionnalités désactivées, utilisation de la configuration globale de KDE
+Comment[fy]=Measte mooglikheden útset, de globale KDE-ynstellings wurde brûkt
+Comment[gl]=A maioría das características desactivadas, empréganse as opcións globais de KDE
+Comment[hi]=लगभग सभी विशेषताएँ बन्द की हुई हैं, केडीई वैश्विक विन्यास उपयोग में लिए जाएंगे
+Comment[hr]=Većina mogućnosti isključena, KDE globalne postavke bivaju korištene
+Comment[hu]=A legtöbb extra kikapcsolva, a KDE alapértelmezéseinek felhasználásával
+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
+Comment[lv]=Vairums īpašību izslēgtas, tiek izmantoti KDE globālie uzstādījumi
+Comment[mk]=Повеќето карактеристики се исклучени, се користат глобалните поставувања на KDE
+Comment[ms]=Kebanyakan cirian ditutup, seting global KDE digunakan
+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[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
+Comment[ro]=Multe caracteristici dezactivate; sînt utilizate setările KDE globale
+Comment[ru]=Большинство дополнительных возможностей отключены, используются глобальные настройки KDE
+Comment[sk]=Najviac vlastností je vypnutých, sú použité globálne nastavenia KDE
+Comment[sl]=Večina zmožnosti je izklopljenih, uporabljene so globalne nastavitve KDE
+Comment[sr]=Већина могућности је искључена, користе се глобална подешавања KDE-а
+Comment[sr@Latn]=Većina mogućnosti je isključena, koriste se globalna podešavanja KDE-a
+Comment[sv]=De flesta funktioner avstängda, KDE:s globala inställningar används
+Comment[ta]=நிறைய தன்மைகள் நிறுத்தப்பட்டது. கேடிஇ உலகளாவிய அமைப்புகள் பயன்படுத்தப்பட்டது.
+Comment[tg]=Бисёри имкониятҳои иловагӣ ғайрифаъол шудаанд, танзимотҳои глобалии KDE истифода шуда истодаанд
+Comment[th]=คุณสมบัติส่วนมากถูกปิดการใช้ และใช้ค่าทั่วไปของ KDE แทน
+Comment[tr]=Bir çok özellik kapatılmış, KDE'nin küresel ayarları kullanılır
+Comment[uk]=Більшість функцій вимкнено, вживаються глобальні параметри KDE
+Comment[ven]=Zwithu zwinzhi zwo tsimiwa, mavhekanyelo a KDE a hothe na hothe a khu shumiswa
+Comment[vi]=Hầu hết các chức năng tắt, Thiết lập toàn cục của KDE được dùng
+Comment[xh]=Imisebenzi emininzi icinyiwe, izicwangciso ezingqukuva ze KDE ziyasetyenziswa
+Comment[zh_CN]=关闭大多数特性,使用 KDE 全局设置
+Comment[zh_TW]=大部份功能都關閉,使用 KDE 全域設定
+Comment[zu]=Eziningi izinto azikhanyiswanga, ezezizwe izilungiselo ze KDE ziyasetshenziswa
+
+[Reader]
+defaultColors=true
+showColorbar=false
+htmlMail=false
+
+[Fonts]
+defaultFonts=true
+
+# should this profile be restricted to font and color changes?
+[Geometry]
+nestedMessages=false
+longFolderList=true
+
+[General]
+dateDisplay=ctime
+showMessageSize=false
+showCryptoIcons=false
+
+[MDN]
+quote-message=0
+default-policy=0
diff --git a/kmail/profiles/profile-secure-rc.desktop b/kmail/profiles/profile-secure-rc.desktop
new file mode 100644
index 00000000..fbc14b91
--- /dev/null
+++ b/kmail/profiles/profile-secure-rc.desktop
@@ -0,0 +1,136 @@
+[KMail Profile]
+Name=Most Secure
+Name[af]=Veiligste
+Name[ar]=الأكثر آمن
+Name[bg]=Сигурност
+Name[bs]=Najsigurniji
+Name[ca]=Més segur
+Name[cs]=Nejbezpečnější
+Name[cy]=Diogelaf
+Name[da]=Mest sikker
+Name[de]=Sicherste Variante
+Name[el]=Πιο ασφαλές
+Name[eo]=Plej sekura
+Name[es]=Más seguro
+Name[et]=Kõige turvalisem
+Name[eu]=Seguruena
+Name[fa]=بیشترین امنیت
+Name[fi]=Turvallisin
+Name[fr]=Le plus sécurisé
+Name[fy]=Meast feilich
+Name[gl]=O máis seguro
+Name[he]=הכי בטוח
+Name[hi]=अत्यंत सुरक्षित
+Name[hr]=Najsigurnije
+Name[hu]=Maximális biztonság
+Name[is]=Öruggast
+Name[it]=Massima sicurezza
+Name[ja]=最も安全
+Name[ka]=ყველაზე უსაფრთხო
+Name[kk]=Ең қауіпсіз
+Name[km]=សុវត្ថិភាព​បំផុត
+Name[lt]=Saugiausias
+Name[mk]=Најбезбедно
+Name[ms]=Paling Selamat
+Name[nb]=Sikrest
+Name[nds]=Sekerst
+Name[ne]=सबैभन्दा सुरक्षित
+Name[nl]=Meest veilig
+Name[nn]=Tryggast
+Name[pa]=ਜਿਆਦਾ ਸੁਰੱਖਿਅਤ
+Name[pl]=Najbezpieczniejszy
+Name[pt]=Mais Seguro
+Name[pt_BR]=Segurança Máxima
+Name[ru]=Самый безопасный
+Name[se]=Sihkkareamus
+Name[sk]=Najviac bezpečné
+Name[sl]=Najbolj varen
+Name[sr]=Најбезбедније
+Name[sr@Latn]=Najbezbednije
+Name[sv]=Säkrast
+Name[ta]=மிகவும் பாதுகாப்பான
+Name[tg]=Аз ҳама бехавфнокаш
+Name[tr]=En Güvenli
+Name[uk]=Найбільш безпечний
+Name[uz]=Juda xavfsiz
+Name[uz@cyrillic]=Жуда хавфсиз
+Name[zh_CN]=最安全
+Name[zh_TW]=最安全
+Comment=Sets all necessary options to achieve maximum security
+Comment[af]=Stel al die nodige opsies om die maksimum sekuriteit te hê
+Comment[bg]=Режим, в който всички настройки за сигурност за включени
+Comment[bs]=Postavlja sve potrebne opcije radi maksimalne sigurnosti
+Comment[ca]=Estableix totes les opcions necessàries per aconseguir una seguretat màxima
+Comment[cs]=Zapne všechny volby nutné k dosažení nejvyšší bezpečnosti
+Comment[cy]=Gosod pob nodwedd gofynnol i gyrchu diogelwch mwyaf
+Comment[da]=Sætter alle nødvendige tilvalg til maksimal sikkerhed
+Comment[de]=Aktivierung aller nötigen Einstellungen für die höchste Sicherheitsstufe
+Comment[el]=Ενεργοποιεί όλες τις απαραίτητες επιλογές για την επίτευξη της μέγιστης ασφάλειας
+Comment[eo]=Agordas la necesajn opciojn por atingi maksimuman sekurecon
+Comment[es]=Pone todas las opciones necesarias para lograr la máxima seguridad
+Comment[et]=Kõik võimalused maksimaalse turvalisuse tagamiseks
+Comment[eu]=Sekuritate maximoa lortzeko aukera guztiak ezartzen ditu
+Comment[fa]=همۀ گزینه‌های لازم را تنظیم می‌کند تا به امنیت بیشینه دست یابد
+Comment[fi]=Asettaa kaikki tarvittavat asetukset suurimman turvallisuuden saavuttamiseksi
+Comment[fr]=Règle toutes les options nécessaires pour atteindre une sécurité maximale
+Comment[fy]=Stelt alle nedige opsjes yn foar maksimale feiligens
+Comment[gl]=Estabelece tódalas opcións precisas para acada-la máxima seguridade
+Comment[hi]=अधिकतम सुरक्षा के लिए सभी आवश्यक विकल्पों को नियत करे
+Comment[hr]=Podešava sve opcije da postigne najbolju sigurnost
+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
+Comment[mk]=Ги поставува сите потребни опции за да се постигне максимална безбедност
+Comment[ms]=Mengeset semua opsyen yang perlu untuk mencapai keselamatan maksimum
+Comment[nb]=Setter opp alle nødvendige innstillinger for å oppnå høyeste sikkerhet
+Comment[nds]=All Optschonen op maximale Sekerheit
+Comment[ne]=अधिकतम सुरक्षा प्राप्त गर्न सबै आवश्यक विकल्प सेट गर्छ
+Comment[nl]=Stelt alle benodigde opties in voor maximale veiligheid
+Comment[nn]=Set alle nødvendige innstillingar for maksimal tryggleik
+Comment[pl]=Włącza wszystkie opcje zapewniające najwięcej bezpieczeństwa
+Comment[pt]=Define todas as opções necessárias para atingir a máxima segurança
+Comment[pt_BR]=Define todas as opções necessárias para alcançar segurança máxima
+Comment[ro]=Setează toate opţiunile necesare pentru a atinge securitatea maximă
+Comment[ru]=Устанавливает все параметры, необходимые для достижения максимальной безопасности
+Comment[sk]=Nastaví všetky potrebné voľby pre dosiahnutie maximálnej bezpečnosti
+Comment[sl]=Nastavi vse potrebne možnosti da doseže najvišjo stopnjo varnosti
+Comment[sr]=Све неопходне опције поставају се тако да се постигне највећа безбедност
+Comment[sr@Latn]=Sve neophodne opcije postavaju se tako da se postigne najveća bezbednost
+Comment[sv]=Ställer in alla nödvändiga alternativ för att uppnå maximal säkerhet
+Comment[ta]=எல்லா தேர்வுகளையும் மிகுந்த அளவில் பாதுகாப்பில் இருக்கும்படி அமைக்கிறது
+Comment[tg]=Тамоми параметрҳоро, ки барои ноилшавии амнияти максималӣ лозим аст, сабт мекунад
+Comment[tr]=En yüksek güvenliği yakalamak için gerekli bütün takım seçenekleri
+Comment[uk]=Встановлює всі необхідні параметри для забезпечення максимальної безпеки
+Comment[zh_CN]=设置所有必要的选项来达到最大的安全性
+Comment[zh_TW]=將所有設定設為最高安全狀態
+
+[Composer]
+pgp-auto-sign=true
+pgp-auto-encrypt=true
+
+[Reader]
+ColorbarHTML=255,64,64
+ColorbarPGP=128,255,128
+ColorbarPlain=255,255,128
+PGPMessageEncr=0,128,255
+PGPMessageErr=255,0,0
+PGPMessageOkKeyBad=255,255,64
+PGPMessageOkKeyOk=64,255,64
+PGPMessageWarn=255,255,64
+defaultColors=false
+showColorbar=true
+htmlMail=false
+htmlLoadExternal=false
+
+[General]
+send-receipts=false
+showCryptoIcons=true
+
+[MDN]
+quote-message=0
+default-policy=0
diff --git a/kmail/protocols.h b/kmail/protocols.h
new file mode 100644
index 00000000..b1815538
--- /dev/null
+++ b/kmail/protocols.h
@@ -0,0 +1,45 @@
+// -*- mode: C++; c-file-style: "gnu" -*-
+/**
+ * protocols.h
+ *
+ * Copyright (c) 2004 David Faure <faure@kde.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#ifndef PROTOCOLS_H
+#define PROTOCOLS_H
+
+#define IMAP_PROTOCOL "imap"
+#define IMAP_SSL_PROTOCOL "imaps"
+
+#define POP_PROTOCOL "pop3"
+#define POP_SSL_PROTOCOL "pop3s"
+
+#define SMTP_PROTOCOL "smtp"
+#define SMTPS_PROTOCOL "smtps"
+
+#endif /* PROTOCOLS_H */
diff --git a/kmail/quotajobs.cpp b/kmail/quotajobs.cpp
new file mode 100644
index 00000000..f44bc52f
--- /dev/null
+++ b/kmail/quotajobs.cpp
@@ -0,0 +1,147 @@
+/**
+ * quotajobs.cpp
+ *
+ * Copyright (c) 2006 Till Adam <adam@kde.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+#include "quotajobs.h"
+#include <kio/scheduler.h>
+#include <kdebug.h>
+
+using namespace KMail;
+
+QuotaJobs::GetQuotarootJob* QuotaJobs::getQuotaroot(
+ KIO::Slave* slave, const KURL& url )
+{
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+ stream << (int)'Q' << (int)'R' << url;
+
+ GetQuotarootJob* job = new GetQuotarootJob( url, packedArgs, false );
+ KIO::Scheduler::assignJobToSlave( slave, job );
+ return job;
+}
+
+QuotaJobs::GetQuotarootJob::GetQuotarootJob( const KURL& url,
+ const QByteArray &packedArgs,
+ bool showProgressInfo )
+ : KIO::SimpleJob( url, KIO::CMD_SPECIAL, packedArgs, showProgressInfo )
+{
+ connect( this, SIGNAL(infoMessage(KIO::Job*,const QString&)),
+ SLOT(slotInfoMessage(KIO::Job*,const QString&)) );
+}
+
+void QuotaJobs::GetQuotarootJob::slotInfoMessage( KIO::Job*, const QString& str )
+{
+ // Parse the result
+ QStringList results = QStringList::split("\r", str);
+ QStringList roots;
+ QuotaInfoList quotas;
+ if ( results.size() > 0 ) {
+ // the first line is the available roots
+ roots = QStringList::split(" ", results.front() );
+ results.pop_front();
+ // the rest are pairs of root -> list of triplets
+ while ( results.size() > 0 ) {
+ QString root = results.front(); results.pop_front();
+ // and the quotas
+ if ( results.size() > 0 ) {
+ QStringList triplets = QStringList::split(" ", results.front() );
+ results.pop_front();
+ while ( triplets.size() > 0 ) {
+ // there's always three, the label, current and max values
+ QString name = triplets.front(); triplets.pop_front();
+ QString current = triplets.front(); triplets.pop_front();
+ QString max = triplets.front(); triplets.pop_front();
+ QuotaInfo info( name, root, current, max );
+ quotas.append( info );
+ }
+ }
+ }
+ }
+ if ( !quotas.isEmpty() ) {
+ emit quotaInfoReceived( quotas );
+ }
+ emit quotaRootResult( roots );
+}
+
+QuotaJobs::GetStorageQuotaJob* QuotaJobs::getStorageQuota(
+ KIO::Slave* slave, const KURL& url )
+{
+ GetStorageQuotaJob* job = new GetStorageQuotaJob( slave, url );
+ return job;
+}
+
+
+QuotaJobs::GetStorageQuotaJob::GetStorageQuotaJob( KIO::Slave* slave, const KURL& url )
+ : KIO::Job( false )
+{
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+ stream << (int)'Q' << (int)'R' << url;
+
+ QuotaJobs::GetQuotarootJob *job =
+ new QuotaJobs::GetQuotarootJob( url, packedArgs, false );
+ connect(job, SIGNAL(quotaInfoReceived(const QuotaInfoList&)),
+ SLOT(slotQuotaInfoReceived(const QuotaInfoList&)));
+ connect(job, SIGNAL(quotaRootResult(const QStringList&)),
+ SLOT(slotQuotarootResult(const QStringList&)));
+ KIO::Scheduler::assignJobToSlave( slave, job );
+ addSubjob( job );
+}
+
+void QuotaJobs::GetStorageQuotaJob::slotQuotarootResult( const QStringList& roots )
+{
+ Q_UNUSED(roots); // we only support one for now
+ if ( !mStorageQuotaInfo.isValid() && !error() ) {
+ // No error, so the account supports quota, but no usable info
+ // was transmitted => no quota set on the folder. Make the info
+ // valid, bit leave it empty.
+ mStorageQuotaInfo.setName( "STORAGE" );
+ }
+ if ( mStorageQuotaInfo.isValid() )
+ emit storageQuotaResult( mStorageQuotaInfo );
+}
+
+void QuotaJobs::GetStorageQuotaJob::slotQuotaInfoReceived( const QuotaInfoList& infos )
+{
+ QuotaInfoList::ConstIterator it( infos.begin() );
+ while ( it != infos.end() ) {
+ // FIXME we only use the first storage quota, for now
+ if ( it->name() == "STORAGE" && !mStorageQuotaInfo.isValid() ) {
+ mStorageQuotaInfo = *it;
+ }
+ ++it;
+ }
+}
+
+QuotaInfo QuotaJobs::GetStorageQuotaJob::storageQuotaInfo() const
+{
+ return mStorageQuotaInfo;
+}
+
+#include "quotajobs.moc"
diff --git a/kmail/quotajobs.h b/kmail/quotajobs.h
new file mode 100644
index 00000000..a73e3084
--- /dev/null
+++ b/kmail/quotajobs.h
@@ -0,0 +1,194 @@
+/**
+ * quotajobs.h
+ *
+ * Copyright (c) 2006 Till Adam <adam@kde.org>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#ifndef QUOTAJOBS_H
+#define QUOTAJOBS_H
+
+#include <qvariant.h>
+
+#include <kio/job.h>
+#include <klocale.h>
+#include <qvaluevector.h>
+
+#include <math.h>
+
+#include "globalsettings.h"
+
+namespace KMail {
+
+// One quota entry consisting of a name, the quota root,
+// the current value and the maximal value
+class QuotaInfo {
+ public :
+ QuotaInfo() {} // for QValueVector
+ QuotaInfo( const QString& _name, const QString& _root, const QVariant& _current, const QVariant& _max )
+ : mName( _name ), mRoot( _root ), mCurrent( _current ),mMax( _max ) {}
+ bool operator==( const QuotaInfo & other ) const {
+ return mName == other.mName && mRoot == other.mRoot && mMax == other.mMax && mCurrent == other.mCurrent;
+ }
+ bool operator!=( const QuotaInfo & other ) const {
+ return !(operator==(other) );
+ }
+ bool isValid() const { return !mName.isEmpty(); }
+ bool isEmpty() const { return mName.isEmpty() || ( mRoot.isEmpty() && !mCurrent.isValid() && !mMax.isValid() ); }
+
+ QString name() const { return mName; }
+ void setName( const QString& n ) { mName = n; }
+ QString root() const { return mRoot; }
+ void setRoot( const QString& r ) { mRoot = r; }
+ QVariant max() const { return mMax; }
+ void setMax( const QVariant& m ) { mMax = m; }
+ QVariant current() const { return mCurrent; }
+ void setCurrent( const QVariant& c ) { mCurrent = c; }
+
+ QString toString() const {
+ if ( isValid() && !isEmpty() ) {
+ readConfig();
+ int factor = static_cast<int> ( pow( 1000, mFactor ) );
+ return i18n("%1 of %2 %3 used").arg( mCurrent.toInt() / factor )
+ .arg( mMax.toInt() / factor ).arg( mUnits );
+ }
+ return QString();
+ }
+
+ private:
+ void readConfig() const {
+ if( GlobalSettings::self()->quotaUnit() == GlobalSettings::EnumQuotaUnit::KB )
+ {
+ mUnits = i18n("KB");
+ mFactor = 0;
+ }
+ else if( GlobalSettings::self()->quotaUnit() == GlobalSettings::EnumQuotaUnit::MB )
+ {
+ mUnits = i18n("MB");
+ mFactor = 1;
+ }
+ else if( GlobalSettings::self()->quotaUnit() == GlobalSettings::EnumQuotaUnit::GB )
+ {
+ mUnits = i18n("GB");
+ mFactor = 2;
+ }
+ }
+
+ QString mName; // e.g. STORAGE
+ QString mRoot; /// e.g. INBOX
+ QVariant mCurrent;
+ QVariant mMax;
+ mutable QString mUnits; //used by readConfig (const) privately and is modified
+ mutable int mFactor;
+};
+
+typedef QValueVector<QuotaInfo> QuotaInfoList;
+
+/**
+ * This namespace contains functions that return jobs for quota operations.
+ *
+ * The current implementation is tied to IMAP.
+ * If someone wants to extend this to other protocols, turn the namespace into a class
+ * and use virtual methods.
+ */
+namespace QuotaJobs {
+
+class GetQuotarootJob;
+/**
+ * Get the quotaroots for a mailbox
+ * @param slave Slave object the job should be assigned to
+ * @param url URL for which to get the quotaroot
+ */
+GetQuotarootJob* getQuotaroot( KIO::Slave* slave, const KURL& url );
+
+class GetStorageQuotaJob;
+/**
+ * Get the storage quota for a mailbox, if there is one.
+ * @param slave Slave object the job should be assigned to
+ * @param url URL for which to get the storage quota
+ */
+GetStorageQuotaJob* getStorageQuota( KIO::Slave* slave, const KURL& url );
+
+/// for getQuotaroot()
+class GetQuotarootJob : public KIO::SimpleJob
+{
+ Q_OBJECT
+public:
+ GetQuotarootJob( const KURL& url, const QByteArray &packedArgs, bool showProgressInfo );
+
+signals:
+ /** Emitted when the server returns a (potentially empty) list of
+ * quota roots for the specified mailbox.
+ * @param roots List of quota roots for the mailbox
+ */
+ void quotaRootResult( const QStringList& roots );
+
+ /**
+ * Emitted when the server returns a list of quota infos for the specified
+ * mailbox. This is an aggregate of all quotas for all applicable roots for
+ * the mailbox. It happens as a side effect of root listing.
+ * @param info List of quota infos for the mailbox
+ */
+ void quotaInfoReceived( const QuotaInfoList& info );
+
+protected slots:
+ void slotInfoMessage( KIO::Job*, const QString& );
+};
+
+/// for getStorageQuota()
+class GetStorageQuotaJob : public KIO::Job
+{
+ Q_OBJECT
+public:
+ GetStorageQuotaJob( KIO::Slave* slave, const KURL& url );
+
+ /** Returns the storage quota info, if any, can be queried on result(). */
+ QuotaInfo storageQuotaInfo() const;
+
+signals:
+ /** Emitted to indicate that storage quota information has
+ * been received. Is not emitted if there is no such info
+ * on the server, so users need to rely on the normal
+ * result() signal to be informed when the job is done.
+ */
+ void storageQuotaResult( const QuotaInfo& info );
+
+
+protected slots:
+ void slotQuotarootResult( const QStringList& roots );
+ void slotQuotaInfoReceived( const QuotaInfoList& roots );
+private:
+ QuotaInfo mStorageQuotaInfo;
+};
+
+} // QuotaJobs namespace
+
+} // KMail namespace
+
+
+#endif /* QUOTAJOBS_H */
+
diff --git a/kmail/recipientseditor.cpp b/kmail/recipientseditor.cpp
new file mode 100644
index 00000000..62e11b90
--- /dev/null
+++ b/kmail/recipientseditor.cpp
@@ -0,0 +1,998 @@
+/*
+ This file is part of KMail.
+
+ Copyright (c) 2004 Cornelius Schumacher <schumacher@kde.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ As a special exception, permission is given to link this program
+ with any edition of Qt, and distribute the resulting executable,
+ without including the source code for Qt in the source distribution.
+*/
+
+#include "recipientseditor.h"
+
+#include "recipientspicker.h"
+#include "kwindowpositioner.h"
+#include "distributionlistdialog.h"
+#include "globalsettings.h"
+
+#include <libemailfunctions/email.h>
+
+#include <kapplication.h>
+#include <kcompletionbox.h>
+#include <kdebug.h>
+#include <kinputdialog.h>
+#include <klocale.h>
+#include <kiconloader.h>
+#include <kmessagebox.h>
+
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qscrollview.h>
+#include <qcombobox.h>
+#include <qhbox.h>
+#include <qtimer.h>
+#include <qpushbutton.h>
+#include <qstylesheet.h>
+
+Recipient::Recipient( const QString &email, Recipient::Type type )
+ : mEmail( email ), mType( type )
+{
+}
+
+void Recipient::setType( Type type )
+{
+ mType = type;
+}
+
+Recipient::Type Recipient::type() const
+{
+ return mType;
+}
+
+void Recipient::setEmail( const QString &email )
+{
+ mEmail = email;
+}
+
+QString Recipient::email() const
+{
+ return mEmail;
+}
+
+bool Recipient::isEmpty() const
+{
+ return mEmail.isEmpty();
+}
+
+int Recipient::typeToId( Recipient::Type type )
+{
+ return static_cast<int>( type );
+}
+
+Recipient::Type Recipient::idToType( int id )
+{
+ return static_cast<Type>( id );
+}
+
+QString Recipient::typeLabel() const
+{
+ return typeLabel( mType );
+}
+
+QString Recipient::typeLabel( Recipient::Type type )
+{
+ switch( type ) {
+ case To:
+ return i18n("To");
+ case Cc:
+ return i18n("CC");
+ case Bcc:
+ return i18n("BCC");
+ case Undefined:
+ break;
+ }
+
+ return i18n("<Undefined RecipientType>");
+}
+
+QStringList Recipient::allTypeLabels()
+{
+ QStringList types;
+ types.append( typeLabel( To ) );
+ types.append( typeLabel( Cc ) );
+ types.append( typeLabel( Bcc ) );
+ return types;
+}
+
+
+RecipientComboBox::RecipientComboBox( QWidget *parent )
+ : QComboBox( parent )
+{
+}
+
+void RecipientComboBox::keyPressEvent( QKeyEvent *ev )
+{
+ if ( ev->key() == Key_Right ) emit rightPressed();
+ else QComboBox::keyPressEvent( ev );
+}
+
+
+void RecipientLineEdit::keyPressEvent( QKeyEvent *ev )
+{
+ if ( ev->key() == Key_Backspace && text().isEmpty() ) {
+ ev->accept();
+ emit deleteMe();
+ } else if ( ev->key() == Key_Left && cursorPosition() == 0 ) {
+ emit leftPressed();
+ } else if ( ev->key() == Key_Right && cursorPosition() == (int)text().length() ) {
+ emit rightPressed();
+ } else {
+ KMLineEdit::keyPressEvent( ev );
+ }
+}
+
+RecipientLine::RecipientLine( QWidget *parent )
+ : QWidget( parent ), mRecipientsCount( 0 ), mModified( false )
+{
+ QBoxLayout *topLayout = new QHBoxLayout( this );
+ topLayout->setSpacing( KDialog::spacingHint() );
+
+ QStringList recipientTypes = Recipient::allTypeLabels();
+
+ mCombo = new RecipientComboBox( this );
+ mCombo->insertStringList( recipientTypes );
+ topLayout->addWidget( mCombo );
+ QToolTip::add( mCombo, i18n("Select type of recipient") );
+
+ mEdit = new RecipientLineEdit( this );
+ topLayout->addWidget( mEdit );
+ connect( mEdit, SIGNAL( returnPressed() ), SLOT( slotReturnPressed() ) );
+ connect( mEdit, SIGNAL( deleteMe() ), SLOT( slotPropagateDeletion() ) );
+ connect( mEdit, SIGNAL( textChanged( const QString & ) ),
+ SLOT( analyzeLine( const QString & ) ) );
+ connect( mEdit, SIGNAL( focusUp() ), SLOT( slotFocusUp() ) );
+ connect( mEdit, SIGNAL( focusDown() ), SLOT( slotFocusDown() ) );
+ connect( mEdit, SIGNAL( rightPressed() ), SIGNAL( rightPressed() ) );
+
+ connect( mEdit, SIGNAL( leftPressed() ), mCombo, SLOT( setFocus() ) );
+ connect( mCombo, SIGNAL( rightPressed() ), mEdit, SLOT( setFocus() ) );
+
+ connect( mCombo, SIGNAL( activated ( int ) ),
+ this, SLOT( slotTypeModified() ) );
+
+ mRemoveButton = new QPushButton( this );
+ mRemoveButton->setIconSet( KApplication::reverseLayout() ? SmallIconSet("locationbar_erase") : SmallIconSet( "clear_left" ) );
+ topLayout->addWidget( mRemoveButton );
+ connect( mRemoveButton, SIGNAL( clicked() ), SLOT( slotPropagateDeletion() ) );
+ QToolTip::add( mRemoveButton, i18n("Remove recipient line") );
+}
+
+void RecipientLine::slotFocusUp()
+{
+ emit upPressed( this );
+}
+
+void RecipientLine::slotFocusDown()
+{
+ emit downPressed( this );
+}
+
+void RecipientLine::slotTypeModified()
+{
+ mModified = true;
+
+ emit typeModified( this );
+}
+
+void RecipientLine::analyzeLine( const QString &text )
+{
+ QStringList r = KPIM::splitEmailAddrList( text );
+ if ( int( r.count() ) != mRecipientsCount ) {
+ mRecipientsCount = r.count();
+ emit countChanged();
+ }
+}
+
+int RecipientLine::recipientsCount()
+{
+ return mRecipientsCount;
+}
+
+void RecipientLine::setRecipient( const Recipient &rec )
+{
+ mEdit->setText( rec.email() );
+ mCombo->setCurrentItem( Recipient::typeToId( rec.type() ) );
+}
+
+void RecipientLine::setRecipient( const QString &email )
+{
+ setRecipient( Recipient( email ) );
+}
+
+Recipient RecipientLine::recipient() const
+{
+ return Recipient( mEdit->text(),
+ Recipient::idToType( mCombo->currentItem() ) );
+}
+
+void RecipientLine::setRecipientType( Recipient::Type type )
+{
+ mCombo->setCurrentItem( Recipient::typeToId( type ) );
+}
+
+Recipient::Type RecipientLine::recipientType() const
+{
+ return Recipient::idToType( mCombo->currentItem() );
+}
+
+void RecipientLine::activate()
+{
+ mEdit->setFocus();
+}
+
+bool RecipientLine::isActive()
+{
+ return mEdit->hasFocus();
+}
+
+bool RecipientLine::isEmpty()
+{
+ return mEdit->text().isEmpty();
+}
+
+bool RecipientLine::isModified()
+{
+ return mModified || mEdit->isModified();
+}
+
+void RecipientLine::clearModified()
+{
+ mModified = false;
+ mEdit->clearModified();
+}
+
+void RecipientLine::slotReturnPressed()
+{
+ emit returnPressed( this );
+}
+
+void RecipientLine::slotPropagateDeletion()
+{
+ emit deleteLine( this );
+}
+
+void RecipientLine::keyPressEvent( QKeyEvent *ev )
+{
+ if ( ev->key() == Key_Up ) {
+ emit upPressed( this );
+ } else if ( ev->key() == Key_Down ) {
+ emit downPressed( this );
+ }
+}
+
+int RecipientLine::setComboWidth( int w )
+{
+ w = QMAX( w, mCombo->sizeHint().width() );
+ mCombo->setFixedWidth( w );
+ mCombo->updateGeometry();
+ parentWidget()->updateGeometry();
+ return w;
+}
+
+void RecipientLine::fixTabOrder( QWidget *previous )
+{
+ setTabOrder( previous, mCombo );
+ setTabOrder( mCombo, mEdit );
+ setTabOrder( mEdit, mRemoveButton );
+}
+
+QWidget *RecipientLine::tabOut() const
+{
+ return mRemoveButton;
+}
+
+void RecipientLine::clear()
+{
+ mEdit->clear();
+}
+
+void RecipientLine::setRemoveLineButtonEnabled( bool b )
+{
+ mRemoveButton->setEnabled( b );
+}
+
+
+// ------------ RecipientsView ---------------------
+
+RecipientsView::RecipientsView( QWidget *parent )
+ : QScrollView( parent ), mCurDelLine( 0 ),
+ mLineHeight( 0 ), mFirstColumnWidth( 0 ),
+ mModified( false )
+{
+ mCompletionMode = KGlobalSettings::completionMode();
+ setHScrollBarMode( AlwaysOff );
+ setLineWidth( 0 );
+
+ addLine();
+ setResizePolicy( QScrollView::Manual );
+ setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
+
+ viewport()->setPaletteBackgroundColor( paletteBackgroundColor() );
+}
+
+RecipientLine *RecipientsView::activeLine()
+{
+ return mLines.last();
+}
+
+RecipientLine *RecipientsView::emptyLine()
+{
+ RecipientLine *line;
+ for( line = mLines.first(); line; line = mLines.next() ) {
+ if ( line->isEmpty() ) return line;
+ }
+
+ return 0;
+}
+
+RecipientLine *RecipientsView::addLine()
+{
+ RecipientLine *line = new RecipientLine( viewport() );
+ addChild( line, 0, mLines.count() * mLineHeight );
+ line->mEdit->setCompletionMode( mCompletionMode );
+ line->show();
+ connect( line, SIGNAL( returnPressed( RecipientLine * ) ),
+ SLOT( slotReturnPressed( RecipientLine * ) ) );
+ connect( line, SIGNAL( upPressed( RecipientLine * ) ),
+ SLOT( slotUpPressed( RecipientLine * ) ) );
+ connect( line, SIGNAL( downPressed( RecipientLine * ) ),
+ SLOT( slotDownPressed( RecipientLine * ) ) );
+ connect( line, SIGNAL( rightPressed() ), SIGNAL( focusRight() ) );
+ connect( line, SIGNAL( deleteLine( RecipientLine * ) ),
+ SLOT( slotDecideLineDeletion( RecipientLine * ) ) );
+ connect( line, SIGNAL( countChanged() ), SLOT( calculateTotal() ) );
+ connect( line, SIGNAL( typeModified( RecipientLine * ) ),
+ SLOT( slotTypeModified( RecipientLine * ) ) );
+ connect( line->mEdit, SIGNAL( completionModeChanged( KGlobalSettings::Completion ) ),
+ SLOT( setCompletionMode( KGlobalSettings::Completion ) ) );
+
+ if ( mLines.last() ) {
+ if ( mLines.count() == 1 ) {
+ if ( GlobalSettings::self()->secondRecipientTypeDefault() ==
+ GlobalSettings::EnumSecondRecipientTypeDefault::To ) {
+ line->setRecipientType( Recipient::To );
+ } else {
+ if ( mLines.last()->recipientType() == Recipient::Bcc ) {
+ line->setRecipientType( Recipient::To );
+ } else {
+ line->setRecipientType( Recipient::Cc );
+ }
+ }
+ } else {
+ line->setRecipientType( mLines.last()->recipientType() );
+ }
+ line->fixTabOrder( mLines.last()->tabOut() );
+ }
+
+ mLines.append( line );
+ // If there is only one line, removing it makes no sense
+ if ( mLines.count() == 1 ) {
+ mLines.first()->setRemoveLineButtonEnabled( false );
+ } else {
+ mLines.first()->setRemoveLineButtonEnabled( true );
+ }
+
+ mFirstColumnWidth = line->setComboWidth( mFirstColumnWidth );
+
+ mLineHeight = line->minimumSizeHint().height();
+
+ line->resize( viewport()->width(), mLineHeight );
+
+ resizeView();
+
+ calculateTotal();
+
+ ensureVisible( 0, mLines.count() * mLineHeight );
+
+ return line;
+}
+
+void RecipientsView::slotTypeModified( RecipientLine *line )
+{
+ if ( mLines.count() == 2 ||
+ ( mLines.count() == 3 && mLines.at( 2 )->isEmpty() ) ) {
+ if ( mLines.at( 1 ) == line ) {
+ if ( line->recipientType() == Recipient::To ) {
+ GlobalSettings::self()->setSecondRecipientTypeDefault(
+ GlobalSettings::EnumSecondRecipientTypeDefault::To );
+ } else if ( line->recipientType() == Recipient::Cc ) {
+ GlobalSettings::self()->setSecondRecipientTypeDefault(
+ GlobalSettings::EnumSecondRecipientTypeDefault::Cc );
+ }
+ }
+ }
+}
+
+void RecipientsView::calculateTotal()
+{
+ int count = 0;
+ int empty = 0;
+
+ RecipientLine *line;
+ for( line = mLines.first(); line; line = mLines.next() ) {
+ if ( line->isEmpty() ) ++empty;
+ else count += line->recipientsCount();
+ }
+
+ if ( empty == 0 ) addLine();
+
+ emit totalChanged( count, mLines.count() );
+}
+
+void RecipientsView::slotReturnPressed( RecipientLine *line )
+{
+ if ( !line->recipient().isEmpty() ) {
+ RecipientLine *empty = emptyLine();
+ if ( !empty ) empty = addLine();
+ activateLine( empty );
+ }
+}
+
+void RecipientsView::slotDownPressed( RecipientLine *line )
+{
+ int pos = mLines.find( line );
+ if ( pos >= (int)mLines.count() - 1 ) {
+ emit focusDown();
+ } else if ( pos >= 0 ) {
+ activateLine( mLines.at( pos + 1 ) );
+ }
+}
+
+void RecipientsView::slotUpPressed( RecipientLine *line )
+{
+ int pos = mLines.find( line );
+ if ( pos > 0 ) {
+ activateLine( mLines.at( pos - 1 ) );
+ } else {
+ emit focusUp();
+ }
+}
+
+void RecipientsView::slotDecideLineDeletion( RecipientLine *line )
+{
+ if ( !line->isEmpty() )
+ mModified = true;
+ if ( mLines.count() == 1 ) {
+ line->clear();
+ } else {
+ mCurDelLine = line;
+ QTimer::singleShot( 0, this, SLOT( slotDeleteLine( ) ) );
+ }
+}
+
+void RecipientsView::slotDeleteLine()
+{
+ if ( !mCurDelLine )
+ return;
+
+ RecipientLine *line = mCurDelLine;
+ int pos = mLines.find( line );
+
+ int newPos;
+ if ( pos == 0 ) newPos = pos + 1;
+ else newPos = pos - 1;
+
+ // if there is something left to activate, do so
+ if ( mLines.at( newPos ) )
+ mLines.at( newPos )->activate();
+
+ mLines.remove( line );
+ removeChild( line );
+ delete line;
+
+ bool atLeastOneToLine = false;
+ unsigned int firstCC = 0;
+ for( uint i = pos; i < mLines.count(); ++i ) {
+ RecipientLine *line = mLines.at( i );
+ moveChild( line, childX( line ), childY( line ) - mLineHeight );
+ if ( line->recipientType() == Recipient::To )
+ atLeastOneToLine = true;
+ else if ( ( line->recipientType() == Recipient::Cc ) && ( i == 0 ) )
+ firstCC = i;
+ }
+ // only one left, can't remove that one
+ if ( mLines.count() == 1 )
+ mLines.first()->setRemoveLineButtonEnabled( false );
+
+ if ( !atLeastOneToLine )
+ mLines.at( firstCC )->setRecipientType( Recipient::To );
+
+ calculateTotal();
+
+ resizeView();
+}
+
+void RecipientsView::resizeView()
+{
+ resizeContents( width(), mLines.count() * mLineHeight );
+
+ if ( mLines.count() < 6 ) {
+// setFixedHeight( mLineHeight * mLines.count() );
+ }
+
+ parentWidget()->layout()->activate();
+ emit sizeHintChanged();
+ QTimer::singleShot( 0, this, SLOT(moveCompletionPopup()) );
+}
+
+void RecipientsView::activateLine( RecipientLine *line )
+{
+ line->activate();
+ ensureVisible( 0, childY( line ) );
+}
+
+void RecipientsView::viewportResizeEvent ( QResizeEvent *ev )
+{
+ for( uint i = 0; i < mLines.count(); ++i ) {
+ mLines.at( i )->resize( ev->size().width(), mLineHeight );
+ }
+ ensureVisible( 0, mLines.count() * mLineHeight );
+}
+
+QSize RecipientsView::sizeHint() const
+{
+ return QSize( 200, mLineHeight * mLines.count() );
+}
+
+QSize RecipientsView::minimumSizeHint() const
+{
+ int height;
+ uint numLines = 5;
+ if ( mLines.count() < numLines ) height = mLineHeight * mLines.count();
+ else height = mLineHeight * numLines;
+ return QSize( 200, height );
+}
+
+Recipient::List RecipientsView::recipients() const
+{
+ Recipient::List recipients;
+
+ QPtrListIterator<RecipientLine> it( mLines );
+ RecipientLine *line;
+ while( ( line = it.current() ) ) {
+ if ( !line->recipient().isEmpty() ) {
+ recipients.append( line->recipient() );
+ }
+
+ ++it;
+ }
+
+ return recipients;
+}
+
+void RecipientsView::setCompletionMode ( KGlobalSettings::Completion mode )
+{
+ if ( mCompletionMode == mode )
+ return;
+ mCompletionMode = mode;
+
+ QPtrListIterator<RecipientLine> it( mLines );
+ RecipientLine *line;
+ while( ( line = it.current() ) ) {
+ line->mEdit->blockSignals( true );
+ line->mEdit->setCompletionMode( mode );
+ line->mEdit->blockSignals( false );
+ ++it;
+ }
+ emit completionModeChanged( mode ); //report change to RecipientsEditor
+}
+
+void RecipientsView::removeRecipient( const QString & recipient,
+ Recipient::Type type )
+{
+ // search a line which matches recipient and type
+ QPtrListIterator<RecipientLine> it( mLines );
+ RecipientLine *line;
+ while( ( line = it.current() ) ) {
+ if ( ( line->recipient().email() == recipient ) &&
+ ( line->recipientType() == type ) ) {
+ break;
+ }
+ ++it;
+ }
+ if ( line )
+ line->slotPropagateDeletion();
+}
+
+bool RecipientsView::isModified()
+{
+ if ( mModified )
+ return true;
+
+ QPtrListIterator<RecipientLine> it( mLines );
+ RecipientLine *line;
+ while( ( line = it.current() ) ) {
+ if ( line->isModified() ) {
+ return true;
+ }
+ ++it;
+ }
+
+ return false;
+}
+
+void RecipientsView::clearModified()
+{
+ mModified = false;
+
+ QPtrListIterator<RecipientLine> it( mLines );
+ RecipientLine *line;
+ while( ( line = it.current() ) ) {
+ line->clearModified();
+ ++it;
+ }
+}
+
+void RecipientsView::setFocus()
+{
+ if ( mLines.last()->isActive() ) setFocusBottom();
+ else setFocusTop();
+}
+
+void RecipientsView::setFocusTop()
+{
+ RecipientLine *line = mLines.first();
+ if ( line ) line->activate();
+ else kdWarning() << "No first" << endl;
+}
+
+void RecipientsView::setFocusBottom()
+{
+ RecipientLine *line = mLines.last();
+ if ( line ) line->activate();
+ else kdWarning() << "No last" << endl;
+}
+
+int RecipientsView::setFirstColumnWidth( int w )
+{
+ mFirstColumnWidth = w;
+
+ QPtrListIterator<RecipientLine> it( mLines );
+ RecipientLine *line;
+ while( ( line = it.current() ) ) {
+ mFirstColumnWidth = line->setComboWidth( mFirstColumnWidth );
+ ++it;
+ }
+
+ resizeView();
+ return mFirstColumnWidth;
+}
+
+void RecipientsView::moveCompletionPopup()
+{
+ for( RecipientLine* line = mLines.first(); line; line = mLines.next() ) {
+ if ( line->lineEdit()->completionBox( false ) ) {
+ if ( line->lineEdit()->completionBox()->isVisible() ) {
+ // ### trigger moving, is there a nicer way to do that?
+ line->lineEdit()->completionBox()->hide();
+ line->lineEdit()->completionBox()->show();
+ }
+ }
+ }
+
+}
+
+RecipientsToolTip::RecipientsToolTip( RecipientsView *view, QWidget *parent )
+ : QToolTip( parent ), mView( view )
+{
+}
+
+QString RecipientsToolTip::line( const Recipient &r )
+{
+ QString txt = r.email();
+
+ return "&nbsp;&nbsp;" + QStyleSheet::escape( txt ) + "<br/>";
+}
+
+void RecipientsToolTip::maybeTip( const QPoint & p )
+{
+ QString text = "<qt>";
+
+ QString to;
+ QString cc;
+ QString bcc;
+
+ Recipient::List recipients = mView->recipients();
+ Recipient::List::ConstIterator it;
+ for( it = recipients.begin(); it != recipients.end(); ++it ) {
+ switch( (*it).type() ) {
+ case Recipient::To:
+ to += line( *it );
+ break;
+ case Recipient::Cc:
+ cc += line( *it );
+ break;
+ case Recipient::Bcc:
+ bcc += line( *it );
+ break;
+ default:
+ break;
+ }
+ }
+
+ text += i18n("<b>To:</b><br/>") + to;
+ if ( !cc.isEmpty() ) text += i18n("<b>CC:</b><br/>") + cc;
+ if ( !bcc.isEmpty() ) text += i18n("<b>BCC:</b><br/>") + bcc;
+
+ text.append( "</qt>" );
+
+ QRect geometry( p + QPoint( 2, 2 ), QPoint( 400, 100 ) );
+
+ tip( QRect( p.x() - 20, p.y() - 20, 40, 40 ), text, geometry );
+}
+
+
+SideWidget::SideWidget( RecipientsView *view, QWidget *parent )
+ : QWidget( parent ), mView( view ), mRecipientPicker( 0 )
+{
+ QBoxLayout *topLayout = new QVBoxLayout( this );
+
+ topLayout->setSpacing( KDialog::spacingHint() );
+ topLayout->addStretch( 1 );
+
+ mTotalLabel = new QLabel( this );
+ mTotalLabel->setAlignment( AlignCenter );
+ topLayout->addWidget( mTotalLabel );
+ mTotalLabel->hide();
+
+ topLayout->addStretch( 1 );
+
+ new RecipientsToolTip( view, mTotalLabel );
+
+ mDistributionListButton = new QPushButton( i18n("Save List..."), this );
+ topLayout->addWidget( mDistributionListButton );
+ mDistributionListButton->hide();
+ connect( mDistributionListButton, SIGNAL( clicked() ),
+ SIGNAL( saveDistributionList() ) );
+ QToolTip::add( mDistributionListButton,
+ i18n("Save recipients as distribution list") );
+
+ mSelectButton = new QPushButton( i18n("Se&lect..."), this );
+ topLayout->addWidget( mSelectButton );
+ connect( mSelectButton, SIGNAL( clicked() ), SLOT( pickRecipient() ) );
+ QToolTip::add( mSelectButton, i18n("Select recipients from address book") );
+}
+
+SideWidget::~SideWidget()
+{
+}
+
+RecipientsPicker* SideWidget::picker() const
+{
+ if ( !mRecipientPicker ) {
+ // hacks to allow picker() to be const in the presence of lazy loading
+ SideWidget *non_const_this = const_cast<SideWidget*>( this );
+ mRecipientPicker = new RecipientsPicker( non_const_this );
+ connect( mRecipientPicker, SIGNAL( pickedRecipient( const Recipient & ) ),
+ non_const_this, SIGNAL( pickedRecipient( const Recipient & ) ) );
+ mPickerPositioner = new KWindowPositioner( non_const_this, mRecipientPicker );
+ }
+ return mRecipientPicker;
+}
+
+void SideWidget::setFocus()
+{
+ mSelectButton->setFocus();
+}
+
+void SideWidget::setTotal( int recipients, int lines )
+{
+#if 0
+ kdDebug() << "SideWidget::setTotal() recipients: " << recipients <<
+ " lines: " << lines << endl;
+#endif
+
+ QString labelText;
+ if ( recipients == 0 ) labelText = i18n("No recipients");
+ else labelText = i18n("1 recipient","%n recipients", recipients );
+ mTotalLabel->setText( labelText );
+
+ if ( lines > 3 ) mTotalLabel->show();
+ else mTotalLabel->hide();
+
+ if ( lines > 2 ) mDistributionListButton->show();
+ else mDistributionListButton->hide();
+}
+
+void SideWidget::pickRecipient()
+{
+#if 0
+ QString rec = KInputDialog::getText( "Pick Recipient",
+ "Email address of recipient" );
+ if ( !rec.isEmpty() ) emit pickedRecipient( rec );
+#else
+ RecipientsPicker *p = picker();
+ p->setDefaultType( mView->activeLine()->recipientType() );
+ p->setRecipients( mView->recipients() );
+ p->show();
+ mPickerPositioner->reposition();
+ p->raise();
+#endif
+}
+
+
+RecipientsEditor::RecipientsEditor( QWidget *parent )
+ : QWidget( parent ), mModified( false )
+{
+ QBoxLayout *topLayout = new QHBoxLayout( this );
+ topLayout->setSpacing( KDialog::spacingHint() );
+
+ mRecipientsView = new RecipientsView( this );
+ topLayout->addWidget( mRecipientsView );
+ connect( mRecipientsView, SIGNAL( focusUp() ), SIGNAL( focusUp() ) );
+ connect( mRecipientsView, SIGNAL( focusDown() ), SIGNAL( focusDown() ) );
+ connect( mRecipientsView, SIGNAL( completionModeChanged( KGlobalSettings::Completion ) ),
+ SIGNAL( completionModeChanged( KGlobalSettings::Completion ) ) );
+
+ mSideWidget = new SideWidget( mRecipientsView, this );
+ topLayout->addWidget( mSideWidget );
+ connect( mSideWidget, SIGNAL( pickedRecipient( const Recipient & ) ),
+ SLOT( slotPickedRecipient( const Recipient & ) ) );
+ connect( mSideWidget, SIGNAL( saveDistributionList() ),
+ SLOT( saveDistributionList() ) );
+
+ connect( mRecipientsView, SIGNAL( totalChanged( int, int ) ),
+ mSideWidget, SLOT( setTotal( int, int ) ) );
+ connect( mRecipientsView, SIGNAL( focusRight() ),
+ mSideWidget, SLOT( setFocus() ) );
+
+ connect( mRecipientsView, SIGNAL(sizeHintChanged()),
+ SIGNAL(sizeHintChanged()) );
+}
+
+RecipientsEditor::~RecipientsEditor()
+{
+}
+
+RecipientsPicker* RecipientsEditor::picker() const
+{
+ return mSideWidget->picker();
+}
+
+void RecipientsEditor::slotPickedRecipient( const Recipient &rec )
+{
+ RecipientLine *line = mRecipientsView->activeLine();
+ if ( !line->isEmpty() ) line = mRecipientsView->addLine();
+
+ Recipient r = rec;
+ if ( r.type() == Recipient::Undefined ) {
+ r.setType( line->recipientType() );
+ }
+
+ line->setRecipient( r );
+ mModified = true;
+}
+
+void RecipientsEditor::saveDistributionList()
+{
+ DistributionListDialog *dlg = new DistributionListDialog( this );
+ dlg->setRecipients( mRecipientsView->recipients() );
+ dlg->show();
+}
+
+Recipient::List RecipientsEditor::recipients() const
+{
+ return mRecipientsView->recipients();
+}
+
+void RecipientsEditor::setRecipientString( const QString &str,
+ Recipient::Type type )
+{
+ clear();
+
+ int count = 1;
+
+ QStringList r = KPIM::splitEmailAddrList( str );
+ QStringList::ConstIterator it;
+ for( it = r.begin(); it != r.end(); ++it ) {
+ if ( count++ > GlobalSettings::self()->maximumRecipients() ) {
+ KMessageBox::sorry( this,
+ i18n("Truncating recipients list to %1 of %2 entries.")
+ .arg( GlobalSettings::self()->maximumRecipients() )
+ .arg( r.count() ) );
+ break;
+ }
+ addRecipient( *it, type );
+ }
+}
+
+QString RecipientsEditor::recipientString( Recipient::Type type )
+{
+ QString str;
+
+ Recipient::List recipients = mRecipientsView->recipients();
+ Recipient::List::ConstIterator it;
+ for( it = recipients.begin(); it != recipients.end(); ++it ) {
+ if ( (*it).type() == type ) {
+ if ( !str.isEmpty() ) str += ", ";
+ str.append( (*it).email() );
+ }
+ }
+
+ return str;
+}
+
+void RecipientsEditor::addRecipient( const QString & recipient,
+ Recipient::Type type )
+{
+ RecipientLine *line = mRecipientsView->emptyLine();
+ if ( !line ) line = mRecipientsView->addLine();
+ line->setRecipient( Recipient( recipient, type ) );
+}
+
+void RecipientsEditor::removeRecipient( const QString & recipient,
+ Recipient::Type type )
+{
+ mRecipientsView->removeRecipient( recipient, type );
+}
+
+bool RecipientsEditor::isModified()
+{
+ return mModified || mRecipientsView->isModified();
+}
+
+void RecipientsEditor::clearModified()
+{
+ mModified = false;
+ mRecipientsView->clearModified();
+}
+
+void RecipientsEditor::clear()
+{
+}
+
+void RecipientsEditor::setFocus()
+{
+ mRecipientsView->setFocus();
+}
+
+void RecipientsEditor::setFocusTop()
+{
+ mRecipientsView->setFocusTop();
+}
+
+void RecipientsEditor::setFocusBottom()
+{
+ mRecipientsView->setFocusBottom();
+}
+
+int RecipientsEditor::setFirstColumnWidth( int w )
+{
+ return mRecipientsView->setFirstColumnWidth( w );
+}
+
+void RecipientsEditor::selectRecipients()
+{
+ mSideWidget->pickRecipient();
+}
+
+void RecipientsEditor::setCompletionMode( KGlobalSettings::Completion mode )
+{
+ mRecipientsView->setCompletionMode( mode );
+}
+
+#include "recipientseditor.moc"
diff --git a/kmail/recipientseditor.h b/kmail/recipientseditor.h
new file mode 100644
index 00000000..91adee7d
--- /dev/null
+++ b/kmail/recipientseditor.h
@@ -0,0 +1,366 @@
+/*
+ This file is part of KMail.
+
+ Copyright (c) 2004 Cornelius Schumacher <schumacher@kde.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ As a special exception, permission is given to link this program
+ with any edition of Qt, and distribute the resulting executable,
+ without including the source code for Qt in the source distribution.
+*/
+#ifndef RECIPIENTSEDITOR_H
+#define RECIPIENTSEDITOR_H
+
+#include <qwidget.h>
+#include <qscrollview.h>
+#include <qguardedptr.h>
+#include <qlineedit.h>
+#include <qtooltip.h>
+
+#include "kmlineeditspell.h"
+#include <qcombobox.h>
+
+class RecipientsPicker;
+
+class KWindowPositioner;
+
+class QLabel;
+class QPushButton;
+class SideWidget;
+
+class Recipient
+{
+ public:
+ typedef QValueList<Recipient> List;
+
+ enum Type { To, Cc, Bcc, Undefined };
+
+ Recipient( const QString &email = QString::null, Type type = To );
+
+ void setType( Type );
+ Type type() const;
+
+ void setEmail( const QString & );
+ QString email() const;
+
+ bool isEmpty() const;
+
+ static int typeToId( Type );
+ static Type idToType( int );
+
+ QString typeLabel() const; static QString typeLabel( Type );
+ static QStringList allTypeLabels();
+
+ private:
+ QString mEmail;
+ Type mType;
+};
+
+class RecipientComboBox : public QComboBox
+{
+ Q_OBJECT
+ public:
+ RecipientComboBox( QWidget *parent );
+
+ signals:
+ void rightPressed();
+
+ protected:
+ void keyPressEvent( QKeyEvent *ev );
+};
+
+class RecipientLineEdit : public KMLineEdit
+{
+ Q_OBJECT
+ public:
+ RecipientLineEdit( QWidget * parent ) :
+ KMLineEdit( true, parent ) {}
+
+ signals:
+ void deleteMe();
+ void leftPressed();
+ void rightPressed();
+
+ protected:
+ void keyPressEvent( QKeyEvent *ev );
+};
+
+class RecipientLine : public QWidget
+{
+ Q_OBJECT
+ public:
+ RecipientLine( QWidget *parent );
+
+ void setRecipient( const Recipient & );
+ Recipient recipient() const;
+
+ void setRecipientType( Recipient::Type );
+ Recipient::Type recipientType() const;
+
+ void setRecipient( const QString & );
+
+ void activate();
+ bool isActive();
+
+ bool isEmpty();
+
+ /** Returns true if the user has made any modifications to this
+ RecipientLine.
+ */
+ bool isModified();
+
+ /** Resets the modified flag to false.
+ */
+ void clearModified();
+
+ int setComboWidth( int w );
+
+ void fixTabOrder( QWidget *previous );
+ QWidget *tabOut() const;
+
+ void clear();
+
+ int recipientsCount();
+
+ void setRemoveLineButtonEnabled( bool b );
+
+ signals:
+ void returnPressed( RecipientLine * );
+ void downPressed( RecipientLine * );
+ void upPressed( RecipientLine * );
+ void rightPressed();
+ void deleteLine( RecipientLine * );
+ void countChanged();
+ void typeModified( RecipientLine * );
+
+ protected:
+ void keyPressEvent( QKeyEvent * );
+ RecipientLineEdit* lineEdit() const { return mEdit; }
+
+ protected slots:
+ void slotReturnPressed();
+ void analyzeLine( const QString & );
+ void slotFocusUp();
+ void slotFocusDown();
+ void slotPropagateDeletion();
+ void slotTypeModified();
+
+ private:
+ friend class RecipientsView;
+ QComboBox *mCombo;
+ RecipientLineEdit *mEdit;
+ QPushButton *mRemoveButton;
+ int mRecipientsCount;
+ bool mModified;
+};
+
+class RecipientsView : public QScrollView
+{
+ Q_OBJECT
+ public:
+ RecipientsView( QWidget *parent );
+
+ QSize minimumSizeHint() const;
+ QSize sizeHint() const;
+
+ RecipientLine *activeLine();
+
+ RecipientLine *emptyLine();
+
+ Recipient::List recipients() const;
+
+ /** Removes the recipient provided it can be found and has the given type.
+ @param recipient The recipient(s) you want to remove.
+ @param type The recipient type.
+ */
+ void removeRecipient( const QString & recipient, Recipient::Type type );
+
+ /** Returns true if the user has made any modifications to the list of
+ recipients.
+ */
+ bool isModified();
+
+ /** Resets the modified flag to false.
+ */
+ void clearModified();
+
+ void activateLine( RecipientLine * );
+
+ /**
+ * Set the width of the left most column to be the argument width.
+ * This method allows other widgets to align their label/combobox column with ours
+ * by communicating how many pixels that first colum is for them.
+ * Returns the width that is actually being used.
+ */
+ int setFirstColumnWidth( int );
+
+ public slots:
+ void setCompletionMode( KGlobalSettings::Completion );
+ RecipientLine *addLine();
+
+ void setFocus();
+ void setFocusTop();
+ void setFocusBottom();
+
+ signals:
+ void totalChanged( int recipients, int lines );
+ void focusUp();
+ void focusDown();
+ void focusRight();
+ void completionModeChanged( KGlobalSettings::Completion );
+ void sizeHintChanged();
+
+ protected:
+ void viewportResizeEvent( QResizeEvent * );
+ void resizeView();
+
+ protected slots:
+ void slotReturnPressed( RecipientLine * );
+ void slotDownPressed( RecipientLine * );
+ void slotUpPressed( RecipientLine * );
+ void slotDecideLineDeletion( RecipientLine * );
+ void slotDeleteLine();
+ void calculateTotal();
+ void slotTypeModified( RecipientLine * );
+ void moveCompletionPopup();
+
+ private:
+ QPtrList<RecipientLine> mLines;
+ QGuardedPtr<RecipientLine> mCurDelLine;
+ int mLineHeight;
+ int mFirstColumnWidth;
+ bool mModified;
+ KGlobalSettings::Completion mCompletionMode;
+};
+
+class RecipientsToolTip : public QToolTip
+{
+ public:
+ RecipientsToolTip( RecipientsView *, QWidget *parent );
+
+ protected:
+ void maybeTip( const QPoint & p );
+
+ QString line( const Recipient & );
+
+ private:
+ RecipientsView *mView;
+};
+
+class SideWidget : public QWidget
+{
+ Q_OBJECT
+ public:
+ SideWidget( RecipientsView *view, QWidget *parent );
+ ~SideWidget();
+
+ RecipientsPicker* picker() const;
+
+ public slots:
+ void setTotal( int recipients, int lines );
+ void setFocus();
+
+ void pickRecipient();
+
+ signals:
+ void pickedRecipient( const Recipient & );
+ void saveDistributionList();
+
+ private:
+ RecipientsView *mView;
+ QLabel *mTotalLabel;
+ QPushButton *mDistributionListButton;
+ QPushButton *mSelectButton;
+ /** The RecipientsPicker is lazy loaded, never access it directly,
+ only through picker() */
+ mutable RecipientsPicker *mRecipientPicker;
+ /** lazy loaded, don't access directly, unless you've called picker() */
+ mutable KWindowPositioner *mPickerPositioner;
+};
+
+class RecipientsEditor : public QWidget
+{
+ Q_OBJECT
+ public:
+ RecipientsEditor( QWidget *parent );
+ ~RecipientsEditor();
+
+ void clear();
+
+ Recipient::List recipients() const;
+ RecipientsPicker* picker() const;
+
+ void setRecipientString( const QString &, Recipient::Type );
+ QString recipientString( Recipient::Type );
+
+ /** Adds a recipient (or multiple recipients) to one line of the editor.
+ @param recipient The recipient(s) you want to add.
+ @param type The recipient type.
+ */
+ void addRecipient( const QString & recipient, Recipient::Type type );
+
+ /** Removes the recipient provided it can be found and has the given type.
+ @param recipient The recipient(s) you want to remove.
+ @param type The recipient type.
+ */
+ void removeRecipient( const QString & recipient, Recipient::Type type );
+
+ /** Returns true if the user has made any modifications to the list of
+ recipients.
+ */
+ bool isModified();
+
+ /** Resets the modified flag to false.
+ */
+ void clearModified();
+
+ /**
+ * Set the width of the left most column to be the argument width.
+ * This method allows other widgets to align their label/combobox column with ours
+ * by communicating how many pixels that first colum is for them.
+ * Returns the width that is actually being used.
+ */
+ int setFirstColumnWidth( int );
+
+ /**
+ * Set completion mode for all lines
+ */
+ void setCompletionMode( KGlobalSettings::Completion );
+
+ public slots:
+ void setFocus();
+ void setFocusTop();
+ void setFocusBottom();
+
+ void selectRecipients();
+ void saveDistributionList();
+
+ signals:
+ void focusUp();
+ void focusDown();
+ void completionModeChanged( KGlobalSettings::Completion );
+ void sizeHintChanged();
+
+ protected slots:
+ void slotPickedRecipient( const Recipient & );
+
+ private:
+ RecipientsView *mRecipientsView;
+ SideWidget* mSideWidget;
+ bool mModified;
+};
+
+#endif
diff --git a/kmail/recipientseditortest.cpp b/kmail/recipientseditortest.cpp
new file mode 100644
index 00000000..2455fdba
--- /dev/null
+++ b/kmail/recipientseditortest.cpp
@@ -0,0 +1,112 @@
+/*
+ This file is part of KMail.
+
+ Copyright (c) 2004 Cornelius Schumacher <schumacher@kde.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ As a special exception, permission is given to link this program
+ with any edition of Qt, and distribute the resulting executable,
+ without including the source code for Qt in the source distribution.
+*/
+
+#include "recipientseditortest.h"
+
+#include "recipientseditor.h"
+
+#include <kapplication.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <kcmdlineargs.h>
+#include <kmessagebox.h>
+#include "aboutdata.h"
+
+#include <qpushbutton.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qlineedit.h>
+#include <qtextedit.h>
+
+Composer::Composer( QWidget *parent )
+ : QWidget( parent )
+{
+ QGridLayout *topLayout = new QGridLayout( this );
+ topLayout->setMargin( 4 );
+ topLayout->setSpacing( 4 );
+
+ QLabel *label = new QLabel( "From:", this );
+ topLayout->addWidget( label, 0, 0 );
+ QLineEdit *edit = new QLineEdit( this );
+ topLayout->addWidget( edit, 0, 1 );
+
+ mRecipients = new RecipientsEditor( this );
+ topLayout->addMultiCellWidget( mRecipients, 1, 1, 0, 1 );
+
+ kdDebug() << "SIZEHINT: " << mRecipients->sizeHint() << endl;
+
+// mRecipients->setFixedHeight( 10 );
+
+ QTextEdit *editor = new QTextEdit( this );
+ topLayout->addMultiCellWidget( editor, 2, 2, 0, 1 );
+ topLayout->setRowStretch( 2, 1 );
+
+ QPushButton *button = new QPushButton( "&Close", this );
+ topLayout->addMultiCellWidget( button, 3, 3, 0, 1 );
+ connect( button, SIGNAL( clicked() ), SLOT( slotClose() ) );
+}
+
+void Composer::slotClose()
+{
+#if 0
+ QString text;
+
+ text += "<qt>";
+
+ Recipient::List recipients = mRecipients->recipients();
+ Recipient::List::ConstIterator it;
+ for( it = recipients.begin(); it != recipients.end(); ++it ) {
+ text += "<b>" + (*it).typeLabel() + ":</b> " + (*it).email() + "<br/>";
+ }
+
+ text += "</qt>";
+
+ KMessageBox::information( this, text );
+#endif
+
+ close();
+}
+
+int main( int argc, char **argv )
+{
+ KAboutData aboutData( "testrecipienteditor",
+ "Test Recipient Editor", "0.1" );
+ KCmdLineArgs::init( argc, argv, &aboutData );
+
+ KApplication app;
+
+ QObject::connect( &app, SIGNAL( lastWindowClosed() ), &app, SLOT( quit() ) );
+
+ QWidget *wid = new Composer( 0 );
+
+ wid->show();
+
+ int ret = app.exec();
+
+ delete wid;
+
+ return ret;
+}
+
+#include "recipientseditortest.moc"
diff --git a/kmail/recipientseditortest.h b/kmail/recipientseditortest.h
new file mode 100644
index 00000000..5a24c4e6
--- /dev/null
+++ b/kmail/recipientseditortest.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of KMail.
+
+ Copyright (c) 2004 Cornelius Schumacher <schumacher@kde.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ As a special exception, permission is given to link this program
+ with any edition of Qt, and distribute the resulting executable,
+ without including the source code for Qt in the source distribution.
+*/
+#ifndef RECIPIENTSEDITORTEST_H
+#define RECIPIENTSEDITORTEST_H
+
+#include <qwidget.h>
+
+class RecipientsEditor;
+
+class Composer : public QWidget
+{
+ Q_OBJECT
+ public:
+ Composer( QWidget *parent );
+
+ public slots:
+ void slotClose();
+
+ private:
+ RecipientsEditor *mRecipients;
+};
+
+#endif
diff --git a/kmail/recipientspicker.cpp b/kmail/recipientspicker.cpp
new file mode 100644
index 00000000..7f0d8fa3
--- /dev/null
+++ b/kmail/recipientspicker.cpp
@@ -0,0 +1,878 @@
+/*
+ This file is part of KMail.
+
+ Copyright (c) 2005 Cornelius Schumacher <schumacher@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "recipientspicker.h"
+
+#include "globalsettings.h"
+
+#include <libkdepim/recentaddresses.h>
+#include <libkdepim/ldapsearchdialog.h>
+
+#include <libemailfunctions/email.h>
+
+#ifndef KDEPIM_NEW_DISTRLISTS
+#include <kabc/distributionlist.h>
+#endif
+
+#include <klistview.h>
+#include <klocale.h>
+#include <kabc/resource.h>
+#include <kiconloader.h>
+#include <kdialog.h>
+#include <kwin.h>
+#include <kmessagebox.h>
+
+#include <qlayout.h>
+#include <qcombobox.h>
+#include <qpushbutton.h>
+#include <qtoolbutton.h>
+#include <qlabel.h>
+
+#ifdef KDEPIM_NEW_DISTRLISTS
+RecipientItem::RecipientItem( KABC::AddressBook *ab )
+ : mAddressBook( ab )
+{
+}
+#else
+RecipientItem::RecipientItem()
+ : mDistributionList( 0 )
+{
+}
+#endif
+
+#ifdef KDEPIM_NEW_DISTRLISTS
+void RecipientItem::setDistributionList( KPIM::DistributionList &list )
+{
+ mDistributionList = list;
+
+ mIcon = KGlobal::iconLoader()->loadIcon( "kdmconfig", KIcon::Small );
+
+ mName = list.name();
+ mKey = list.name();
+
+ int count = list.entries( mAddressBook ).count();
+ mEmail = i18n( "1 email address", "%n email addresses", count );
+
+ mRecipient = mName;
+
+ mTooltip = createTooltip( list );
+}
+#else
+void RecipientItem::setDistributionList( KABC::DistributionList *list )
+{
+ mDistributionList = list;
+
+ mIcon = KGlobal::iconLoader()->loadIcon( "kdmconfig", KIcon::Small );
+
+ mName = list->name();
+ mKey = list->name();
+
+ int count = list->entries().count();
+ mEmail = i18n( "1 email address", "%n email addresses", count );
+
+ mRecipient = mName;
+
+ mTooltip = createTooltip( list );
+}
+#endif
+
+void RecipientItem::setAddressee( const KABC::Addressee &a,
+ const QString &email )
+{
+ mAddressee = a;
+ mEmail = email;
+ mRecipient = mAddressee.fullEmail( mEmail );
+
+ QImage img = a.photo().data();
+ if ( !img.isNull() )
+ mIcon = img.smoothScale( 20, 20, QImage::ScaleMin );
+ else
+ mIcon = KGlobal::iconLoader()->loadIcon( "personal", KIcon::Small );
+
+ mName = mAddressee.realName();
+ mKey = mAddressee.realName() + '|' + mEmail;
+
+ mTooltip = "<qt>";
+ if ( !mAddressee.realName().isEmpty() ) {
+ mTooltip += mAddressee.realName() + "<br/>";
+ }
+ mTooltip += "<b>" + mEmail + "</b>";
+}
+
+QPixmap RecipientItem::icon() const
+{
+ return mIcon;
+}
+
+QString RecipientItem::name() const
+{
+ return mName;
+}
+
+QString RecipientItem::email() const
+{
+ return mEmail;
+}
+
+QString RecipientItem::recipient() const
+{
+ return mRecipient;
+}
+
+QString RecipientItem::tooltip() const
+{
+ return mTooltip;
+}
+
+#ifdef KDEPIM_NEW_DISTRLISTS
+KPIM::DistributionList& RecipientItem::distributionList() {
+ return mDistributionList;
+}
+#else
+KABC::DistributionList * RecipientItem::distributionList() {
+ return mDistributionList;
+}
+#endif
+
+#ifdef KDEPIM_NEW_DISTRLISTS
+QString RecipientItem::createTooltip( KPIM::DistributionList &distributionList ) const
+{
+ QString txt = "<qt>";
+
+ txt += "<b>" + i18n( "Distribution List %1" ).arg ( distributionList.name() ) + "</b>";
+ txt += "<ul>";
+ KPIM::DistributionList::Entry::List entries = distributionList.entries( mAddressBook );
+ KPIM::DistributionList::Entry::List::ConstIterator it;
+ for( it = entries.begin(); it != entries.end(); ++it ) {
+ txt += "<li>";
+ txt += (*it).addressee.realName() + ' ';
+ txt += "<em>";
+ if ( (*it).email.isEmpty() ) txt += (*it).addressee.preferredEmail();
+ else txt += (*it).email;
+ txt += "</em>";
+ txt += "<li/>";
+ }
+ txt += "</ul>";
+ txt += "</qt>";
+
+ return txt;
+}
+#else
+QString RecipientItem::createTooltip( KABC::DistributionList *distributionList ) const
+{
+ QString txt = "<qt>";
+
+ txt += "<b>" + i18n("Distribution List %1" ).arg ( distributionList->name() ) + "</b>";
+ txt += "<ul>";
+ KABC::DistributionList::Entry::List entries = distributionList->entries();
+ KABC::DistributionList::Entry::List::ConstIterator it;
+ for( it = entries.begin(); it != entries.end(); ++it ) {
+ txt += "<li>";
+ txt += (*it).addressee.realName() + ' ';
+ txt += "<em>";
+ if ( (*it).email.isEmpty() ) txt += (*it).addressee.preferredEmail();
+ else txt += (*it).email;
+ txt += "</em>";
+ txt += "</li>";
+ }
+ txt += "</ul>";
+ txt += "</qt>";
+
+ return txt;
+}
+#endif
+
+void RecipientItem::setRecipientType( const QString &type )
+{
+ mType = type;
+}
+
+QString RecipientItem::recipientType() const
+{
+ return mType;
+}
+
+
+RecipientViewItem::RecipientViewItem( RecipientItem *item, KListView *listView )
+ : KListViewItem( listView ), mRecipientItem( item )
+{
+ setText( 0, item->recipientType() );
+ setText( 1, item->name() );
+ setText( 2, item->email() );
+
+ setPixmap( 1, item->icon() );
+}
+
+RecipientItem *RecipientViewItem::recipientItem() const
+{
+ return mRecipientItem;
+}
+
+
+RecipientsListToolTip::RecipientsListToolTip( QWidget *parent,
+ KListView *listView )
+ : QToolTip( parent )
+{
+ mListView = listView;
+}
+
+void RecipientsListToolTip::maybeTip( const QPoint & pos )
+{
+ QRect r;
+ QListViewItem *item = mListView->itemAt( pos );
+ RecipientViewItem *i = static_cast<RecipientViewItem *>( item );
+
+ if( item ) {
+ r = mListView->itemRect( item );
+ QString tipText( i->recipientItem()->tooltip() );
+ if ( !tipText.isEmpty() ) {
+ tip( r, tipText );
+ }
+ }
+}
+
+
+RecipientsCollection::RecipientsCollection( const QString &id )
+{
+ mId = id;
+ mTitle = id;
+ mIsReferenceContainer = false;
+}
+
+RecipientsCollection::~RecipientsCollection()
+{
+ deleteAll();
+}
+
+void RecipientsCollection::setReferenceContainer( bool isReferenceContainer )
+{
+ mIsReferenceContainer = isReferenceContainer;
+}
+
+bool RecipientsCollection::isReferenceContainer() const
+{
+ return mIsReferenceContainer;
+}
+
+void RecipientsCollection::setTitle( const QString &title )
+{
+ mTitle = title;
+}
+
+QString RecipientsCollection::title() const
+{
+ return mTitle;
+}
+
+void RecipientsCollection::addItem( RecipientItem *item )
+{
+ mKeyMap.insert( item->key(), item );
+}
+
+RecipientItem::List RecipientsCollection::items() const
+{
+ return mKeyMap.values();
+}
+
+bool RecipientsCollection::hasEquivalentItem( RecipientItem *item ) const
+{
+ return mKeyMap.find( item->key() ) != mKeyMap.end();
+}
+
+RecipientItem * RecipientsCollection::getEquivalentItem( RecipientItem *item) const
+{
+ QMap<QString, RecipientItem *>::ConstIterator it;
+ it = mKeyMap.find( item->key() );
+ if ( it == mKeyMap.end() )
+ return 0;
+ return (*it);
+}
+
+void RecipientsCollection::clear()
+{
+ mKeyMap.clear();
+}
+
+void RecipientsCollection::deleteAll()
+{
+ if ( !isReferenceContainer() ) {
+ QMap<QString, RecipientItem *>::ConstIterator it;
+ for( it = mKeyMap.begin(); it != mKeyMap.end(); ++it ) {
+ delete *it;
+ }
+ }
+ clear();
+}
+
+QString RecipientsCollection::id() const
+{
+ return mId;
+}
+
+SearchLine::SearchLine( QWidget *parent, KListView *listView )
+ : KListViewSearchLine( parent, listView )
+{
+}
+
+void SearchLine::keyPressEvent( QKeyEvent *ev )
+{
+ if ( ev->key() == Key_Down ) emit downPressed();
+
+ KListViewSearchLine::keyPressEvent( ev );
+}
+
+
+RecipientsPicker::RecipientsPicker( QWidget *parent )
+ : QDialog( parent, "RecipientsPicker" )
+#ifndef KDEPIM_NEW_DISTRLISTS
+ , mDistributionListManager( 0 )
+#endif
+ ,mLdapSearchDialog( 0 )
+{
+// KWin::setType( winId(), NET::Dock );
+
+ setCaption( i18n("Select Recipient") );
+
+ QBoxLayout *topLayout = new QVBoxLayout( this );
+ topLayout->setSpacing( KDialog::spacingHint() );
+ topLayout->setMargin( KDialog::marginHint() );
+
+ QBoxLayout *resLayout = new QHBoxLayout( topLayout );
+
+ QLabel *label = new QLabel( i18n("Address book:"), this );
+ resLayout->addWidget( label );
+
+ mCollectionCombo = new QComboBox( this );
+ resLayout->addWidget( mCollectionCombo );
+ resLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding));
+
+// connect( mCollectionCombo, SIGNAL( highlighted( int ) ),
+// SLOT( updateList() ) );
+ connect( mCollectionCombo, SIGNAL( activated( int ) ),
+ SLOT( updateList() ) );
+
+ QBoxLayout *searchLayout = new QHBoxLayout( topLayout );
+
+ QToolButton *button = new QToolButton( this );
+ button->setIconSet( KGlobal::iconLoader()->loadIconSet(
+ KApplication::reverseLayout() ? "clear_left":"locationbar_erase", KIcon::Small, 0 ) );
+ searchLayout->addWidget( button );
+ connect( button, SIGNAL( clicked() ), SLOT( resetSearch() ) );
+
+ label = new QLabel( i18n("&Search:"), this );
+ searchLayout->addWidget( label );
+
+ mRecipientList = new KListView( this );
+ mRecipientList->setSelectionMode( QListView::Extended );
+ mRecipientList->setAllColumnsShowFocus( true );
+ mRecipientList->setFullWidth( true );
+ topLayout->addWidget( mRecipientList );
+ mRecipientList->addColumn( i18n("->") );
+ mRecipientList->addColumn( i18n("Name") );
+ mRecipientList->addColumn( i18n("Email") );
+ connect( mRecipientList, SIGNAL( doubleClicked( QListViewItem *,
+ const QPoint &, int ) ), SLOT( slotPicked() ) );
+ connect( mRecipientList, SIGNAL( returnPressed( QListViewItem * ) ),
+ SLOT( slotPicked() ) );
+
+ new RecipientsListToolTip( mRecipientList->viewport(), mRecipientList );
+
+ mSearchLine = new SearchLine( this, mRecipientList );
+ searchLayout->addWidget( mSearchLine );
+ label->setBuddy( label );
+ connect( mSearchLine, SIGNAL( downPressed() ), SLOT( setFocusList() ) );
+
+ mSearchLDAPButton = new QPushButton( i18n("Search &Directory Service"), this );
+ searchLayout->addWidget( mSearchLDAPButton );
+ connect( mSearchLDAPButton, SIGNAL( clicked() ), SLOT( slotSearchLDAP() ) );
+
+ QBoxLayout *buttonLayout = new QHBoxLayout( topLayout );
+
+ buttonLayout->addStretch( 1 );
+
+ mToButton = new QPushButton( i18n("Add as To"), this );
+ buttonLayout->addWidget( mToButton );
+ connect( mToButton, SIGNAL( clicked() ), SLOT( slotToClicked() ) );
+
+ mCcButton = new QPushButton( i18n("Add as CC"), this );
+ buttonLayout->addWidget( mCcButton );
+ connect( mCcButton, SIGNAL( clicked() ), SLOT( slotCcClicked() ) );
+
+ mBccButton = new QPushButton( i18n("Add as BCC"), this );
+ buttonLayout->addWidget( mBccButton );
+ connect( mBccButton, SIGNAL( clicked() ), SLOT( slotBccClicked() ) );
+ // BCC isn't commonly used, so hide it for now
+ //mBccButton->hide();
+
+ QPushButton *closeButton = new QPushButton( i18n("&Cancel"), this );
+ buttonLayout->addWidget( closeButton );
+ connect( closeButton, SIGNAL( clicked() ), SLOT( close() ) );
+
+ {
+ using namespace KABC;
+ mAddressBook = KABC::StdAddressBook::self( true );
+ connect( mAddressBook, SIGNAL( addressBookChanged( AddressBook * ) ),
+ this, SLOT( insertAddressBook( AddressBook * ) ) );
+ }
+
+ initCollections();
+
+ mCollectionCombo->setCurrentItem( 0 );
+
+ updateList();
+
+ mSearchLine->setFocus();
+
+ readConfig();
+
+ setTabOrder( mCollectionCombo, mSearchLine );
+ setTabOrder( mSearchLine, mRecipientList );
+ setTabOrder( closeButton, mCollectionCombo );
+}
+
+RecipientsPicker::~RecipientsPicker()
+{
+ writeConfig();
+
+#ifndef KDEPIM_NEW_DISTRLISTS
+ delete mDistributionListManager;
+#endif
+
+ QMap<int,RecipientsCollection *>::ConstIterator it;
+ for( it = mCollectionMap.begin(); it != mCollectionMap.end(); ++it ) {
+ delete *it;
+ }
+}
+
+void RecipientsPicker::initCollections()
+{
+ mAllRecipients = new RecipientsCollection( i18n("All") );
+ mAllRecipients->setReferenceContainer( true );
+ mDistributionLists = new RecipientsCollection( i18n("Distribution Lists") );
+ mSelectedRecipients = new RecipientsCollection( i18n("Selected Recipients") );
+
+ insertCollection( mAllRecipients );
+ insertAddressBook( mAddressBook );
+ insertCollection( mDistributionLists );
+ insertRecentAddresses();
+ insertCollection( mSelectedRecipients );
+
+ rebuildAllRecipientsList();
+}
+
+void RecipientsPicker::insertAddressBook( KABC::AddressBook *addressbook )
+{
+ QMap<KABC::Resource *,RecipientsCollection *> collectionMap;
+
+ QPtrList<KABC::Resource> resources = addressbook->resources();
+ KABC::Resource *res;
+ for( res = resources.first(); res; res = resources.next() ) {
+ RecipientsCollection *collection = new RecipientsCollection( res->identifier() );
+ collectionMap.insert( res, collection );
+ collection->setTitle( res->resourceName() );
+ }
+
+ QMap<QString,RecipientsCollection *> categoryMap;
+
+ KABC::AddressBook::Iterator it;
+ for( it = addressbook->begin(); it != addressbook->end(); ++it ) {
+ QStringList emails = (*it).emails();
+ QStringList::ConstIterator it3;
+ for( it3 = emails.begin(); it3 != emails.end(); ++it3 ) {
+#ifdef KDEPIM_NEW_DISTRLISTS
+ RecipientItem *item = new RecipientItem( mAddressBook );
+#else
+ RecipientItem *item = new RecipientItem;
+#endif
+ item->setAddressee( *it, *it3 );
+
+ QMap<KABC::Resource *,RecipientsCollection *>::ConstIterator collIt;
+ collIt = collectionMap.find( it->resource() );
+ if ( collIt != collectionMap.end() ) {
+ (*collIt)->addItem( item );
+ }
+
+ QStringList categories = (*it).categories();
+ QStringList::ConstIterator catIt;
+ for( catIt = categories.begin(); catIt != categories.end(); ++catIt ) {
+ QMap<QString, RecipientsCollection *>::ConstIterator catMapIt;
+ catMapIt = categoryMap.find( *catIt );
+ RecipientsCollection *collection;
+ if ( catMapIt == categoryMap.end() ) {
+ collection = new RecipientsCollection( *catIt );
+ collection->setReferenceContainer( true );
+ categoryMap.insert( *catIt, collection );
+ } else {
+ collection = *catMapIt;
+ }
+ collection->addItem( item );
+ }
+ }
+ }
+
+ QMap<KABC::Resource *,RecipientsCollection *>::ConstIterator it2;
+ for( it2 = collectionMap.begin(); it2 != collectionMap.end(); ++it2 ) {
+ insertCollection( *it2 );
+ }
+
+ QMap<QString, RecipientsCollection *>::ConstIterator it3;
+ for( it3 = categoryMap.begin(); it3 != categoryMap.end(); ++it3 ) {
+ insertCollection( *it3 );
+ }
+
+ insertDistributionLists();
+ rebuildAllRecipientsList();
+ updateList();
+}
+
+void RecipientsPicker::insertDistributionLists()
+{
+ mDistributionLists->deleteAll();
+
+#ifdef KDEPIM_NEW_DISTRLISTS
+ QValueList<KPIM::DistributionList> lists = KPIM::DistributionList::allDistributionLists( mAddressBook );
+ for ( uint i = 0; i < lists.count(); ++i ) {
+ RecipientItem *item = new RecipientItem( mAddressBook );
+ item->setDistributionList( lists[ i ] );
+ mDistributionLists->addItem( item );
+ }
+#else
+ delete mDistributionListManager;
+ mDistributionListManager =
+ new KABC::DistributionListManager( KABC::StdAddressBook::self( true ) );
+
+ mDistributionListManager->load();
+
+ QStringList lists = mDistributionListManager->listNames();
+
+ QStringList::Iterator listIt;
+ for ( listIt = lists.begin(); listIt != lists.end(); ++listIt ) {
+ KABC::DistributionList *list = mDistributionListManager->list( *listIt );
+ RecipientItem *item = new RecipientItem;
+ item->setDistributionList( list );
+ mDistributionLists->addItem( item );
+ }
+#endif
+}
+
+void RecipientsPicker::insertRecentAddresses()
+{
+ RecipientsCollection *collection = new RecipientsCollection( i18n("Recent Addresses") );
+
+ KConfig config( "kmailrc" );
+ KABC::Addressee::List recents =
+ KRecentAddress::RecentAddresses::self( &config )->kabcAddresses();
+
+ KABC::Addressee::List::ConstIterator it;
+ for( it = recents.begin(); it != recents.end(); ++it ) {
+#ifdef KDEPIM_NEW_DISTRLISTS
+ RecipientItem *item = new RecipientItem( mAddressBook );
+#else
+ RecipientItem *item = new RecipientItem;
+#endif
+ item->setAddressee( *it, (*it).preferredEmail() );
+ collection->addItem( item );
+ }
+
+ insertCollection( collection );
+}
+
+void RecipientsPicker::insertCollection( RecipientsCollection *coll )
+{
+ int index = 0;
+ QMap<int,RecipientsCollection *>::ConstIterator it;
+ for ( it = mCollectionMap.begin(); it != mCollectionMap.end(); ++it ) {
+ if ( (*it)->id() == coll->id() ) {
+ delete *it;
+ mCollectionMap.remove( index );
+ mCollectionMap.insert( index, coll );
+ return;
+ }
+ index++;
+ }
+
+ mCollectionCombo->insertItem( coll->title(), index );
+ mCollectionMap.insert( index, coll );
+}
+
+void RecipientsPicker::updateRecipient( const Recipient &recipient )
+{
+ RecipientItem::List allRecipients = mAllRecipients->items();
+ RecipientItem::List::ConstIterator itAll;
+ for( itAll = allRecipients.begin(); itAll != allRecipients.end(); ++itAll ) {
+ if ( (*itAll)->recipient() == recipient.email() ) {
+ (*itAll)->setRecipientType( recipient.typeLabel() );
+ }
+ }
+ updateList();
+}
+
+void RecipientsPicker::setRecipients( const Recipient::List &recipients )
+{
+ mSelectedRecipients->deleteAll();
+
+ Recipient::List::ConstIterator it;
+ for( it = recipients.begin(); it != recipients.end(); ++it ) {
+ RecipientItem *item = 0;
+
+ // if recipient is a distribution list, create
+ // a detached copy.
+ RecipientItem::List items = mDistributionLists->items();
+ RecipientItem::List::ConstIterator distIt;
+#ifdef KDEPIM_NEW_DISTRLISTS
+ for ( distIt = items.begin(); distIt != items.end(); ++distIt ) {
+ if ( (*it).email() == (*distIt)->name() ) {
+ item = new RecipientItem( mAddressBook );
+ item->setDistributionList( (*distIt)->distributionList() );
+ }
+ }
+#else
+ for ( distIt = items.begin(); distIt != items.end(); ++distIt ) {
+ if ( (*it).email() == (*distIt)->name() ) {
+ item = new RecipientItem();
+ item->setDistributionList( (*distIt)->distributionList() );
+ }
+ }
+#endif
+
+ if ( !item ) {
+ KABC::Addressee a;
+ QString name;
+ QString email;
+ KABC::Addressee::parseEmailAddress( (*it).email(), name, email );
+ a.setNameFromString( name );
+ a.insertEmail( email );
+
+#ifdef KDEPIM_NEW_DISTRLISTS
+ item = new RecipientItem( mAddressBook );
+#else
+ item = new RecipientItem;
+#endif
+ item->setAddressee( a, a.preferredEmail() );
+ }
+
+ item->setRecipientType( (*it).typeLabel() );
+ mSelectedRecipients->addItem( item );
+ }
+
+ updateList();
+}
+
+void RecipientsPicker::setDefaultButton( QPushButton *button )
+{
+// button->setText( "<qt><b>" + button->text() + "</b></qt>" );
+ button->setDefault( true );
+}
+
+void RecipientsPicker::setDefaultType( Recipient::Type type )
+{
+ mDefaultType = type;
+
+ if ( type == Recipient::To ) {
+ setDefaultButton( mToButton );
+ } else if ( type == Recipient::Cc ) {
+ setDefaultButton( mCcButton );
+ } else if ( type == Recipient::Bcc ) {
+ setDefaultButton( mBccButton );
+ }
+}
+
+void RecipientsPicker::rebuildAllRecipientsList()
+{
+ mAllRecipients->clear();
+
+ QMap<int,RecipientsCollection *>::ConstIterator it;
+ for( it = mCollectionMap.begin(); it != mCollectionMap.end(); ++it ) {
+ // skip self
+ if ( (*it) == mAllRecipients )
+ continue;
+
+ RecipientItem::List coll = (*it)->items();
+
+ RecipientItem::List::ConstIterator rcptIt;
+ for ( rcptIt = coll.begin(); rcptIt != coll.end(); ++rcptIt ) {
+ mAllRecipients->addItem( *rcptIt );
+ }
+ }
+}
+
+void RecipientsPicker::updateList()
+{
+ mRecipientList->clear();
+
+ RecipientsCollection *coll = mCollectionMap[ mCollectionCombo->currentItem() ];
+
+ RecipientItem::List items = coll->items();
+ RecipientItem::List::ConstIterator it;
+ for( it = items.begin(); it != items.end(); ++it ) {
+ if ( coll != mSelectedRecipients ) {
+ RecipientItem *selItem = mSelectedRecipients->getEquivalentItem( *it );
+ if ( selItem ) {
+ (*it)->setRecipientType( selItem->recipientType() );
+ } else {
+ (*it)->setRecipientType( QString() );
+ }
+ }
+ new RecipientViewItem( *it, mRecipientList );
+ }
+
+ mSearchLine->updateSearch();
+}
+
+void RecipientsPicker::slotToClicked()
+{
+ pick( Recipient::To );
+}
+
+void RecipientsPicker::slotCcClicked()
+{
+ pick( Recipient::Cc );
+}
+
+void RecipientsPicker::slotBccClicked()
+{
+ pick( Recipient::Bcc );
+}
+
+void RecipientsPicker::slotPicked( QListViewItem *viewItem )
+{
+ RecipientViewItem *item = static_cast<RecipientViewItem *>( viewItem );
+ if ( item ) {
+ RecipientItem *i = item->recipientItem();
+ emit pickedRecipient( Recipient( i->recipient(), Recipient::Undefined ) );
+ }
+ close();
+}
+
+void RecipientsPicker::slotPicked()
+{
+ pick( mDefaultType );
+}
+
+void RecipientsPicker::pick( Recipient::Type type )
+{
+ kdDebug() << "RecipientsPicker::pick " << int( type ) << endl;
+
+ int count = 0;
+ QListViewItemIterator it( mRecipientList ,
+ QListViewItemIterator::Visible | QListViewItemIterator::Selected );
+ for ( ; it.current(); ++it )
+ ++count;
+
+ if ( count > GlobalSettings::self()->maximumRecipients() ) {
+ KMessageBox::sorry( this,
+ i18n("You selected 1 recipient. The maximum supported number of "
+ "recipients is %1. Please adapt the selection.",
+ "You selected %n recipients. The maximum supported number of "
+ "recipients is %1. Please adapt the selection.", count)
+ .arg( GlobalSettings::self()->maximumRecipients() ) );
+ return;
+ }
+
+ it = QListViewItemIterator( mRecipientList ,
+ QListViewItemIterator::Visible | QListViewItemIterator::Selected );
+ for ( ; it.current(); ++it ) {
+ RecipientViewItem *item = static_cast<RecipientViewItem *>( it.current() );
+ if ( item ) {
+ RecipientItem *i = item->recipientItem();
+ Recipient r = i->recipient();
+ r.setType( type );
+ emit pickedRecipient( r );
+ }
+ }
+ close();
+}
+
+void RecipientsPicker::keyPressEvent( QKeyEvent *ev )
+{
+ if ( ev->key() == Key_Escape ) close();
+
+ QWidget::keyPressEvent( ev );
+}
+
+void RecipientsPicker::readConfig()
+{
+ KConfig *cfg = KGlobal::config();
+ cfg->setGroup( "RecipientsPicker" );
+ QSize size = cfg->readSizeEntry( "Size" );
+ if ( !size.isEmpty() ) {
+ resize( size );
+ }
+ int currentCollection = cfg->readNumEntry( "CurrentCollection", -1 );
+ if ( currentCollection >= 0 &&
+ currentCollection < mCollectionCombo->count() ) {
+ mCollectionCombo->setCurrentItem( currentCollection );
+ }
+}
+
+void RecipientsPicker::writeConfig()
+{
+ KConfig *cfg = KGlobal::config();
+ cfg->setGroup( "RecipientsPicker" );
+ cfg->writeEntry( "Size", size() );
+ cfg->writeEntry( "CurrentCollection", mCollectionCombo->currentItem() );
+}
+
+void RecipientsPicker::setFocusList()
+{
+ mRecipientList->setFocus();
+}
+
+void RecipientsPicker::resetSearch()
+{
+ mSearchLine->setText( QString::null );
+}
+
+void RecipientsPicker::slotSearchLDAP()
+{
+ if ( !mLdapSearchDialog ) {
+ mLdapSearchDialog = new KPIM::LDAPSearchDialog( this );
+ connect( mLdapSearchDialog, SIGNAL( addresseesAdded() ),
+ SLOT(ldapSearchResult() ) );
+ }
+ mLdapSearchDialog->setSearchText( mSearchLine->text() );
+ mLdapSearchDialog->show();
+
+}
+
+void RecipientsPicker::ldapSearchResult()
+{
+ QStringList emails = QStringList::split(',', mLdapSearchDialog->selectedEMails() );
+ QStringList::iterator it( emails.begin() );
+ QStringList::iterator end( emails.end() );
+ for ( ; it != end; ++it ){
+ QString name;
+ QString email;
+ KPIM::getNameAndMail( (*it), name, email );
+ KABC::Addressee ad;
+ ad.setNameFromString( name );
+ ad.insertEmail( email );
+#ifdef KDEPIM_NEW_DISTRLISTS
+ RecipientItem *item = new RecipientItem( mAddressBook );
+#else
+ RecipientItem *item = new RecipientItem;
+#endif
+ item->setAddressee( ad, ad.preferredEmail() );
+ emit pickedRecipient( Recipient( item->recipient(), Recipient::Undefined ) );
+ }
+}
+
+#include "recipientspicker.moc"
diff --git a/kmail/recipientspicker.h b/kmail/recipientspicker.h
new file mode 100644
index 00000000..4f8b82bb
--- /dev/null
+++ b/kmail/recipientspicker.h
@@ -0,0 +1,248 @@
+/*
+ This file is part of KMail.
+
+ Copyright (c) 2005 Cornelius Schumacher <schumacher@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef RECIPIENTSPICKER_H
+#define RECIPIENTSPICKER_H
+
+#include <config.h> // for KDEPIM_NEW_DISTRLISTS
+
+#include "recipientseditor.h"
+
+#include <klistview.h>
+#include <klistviewsearchline.h>
+#include <kabc/addressee.h>
+#include <kabc/stdaddressbook.h>
+
+#include <qwidget.h>
+#include <qdialog.h>
+#include <qtooltip.h>
+
+class QComboBox;
+
+#ifdef KDEPIM_NEW_DISTRLISTS
+#include <libkdepim/distributionlist.h>
+#else
+namespace KABC {
+class DistributionList;
+class DistributionListManager;
+}
+#endif
+
+namespace KPIM {
+class LDAPSearchDialog;
+}
+
+class RecipientItem
+{
+ public:
+ typedef QValueList<RecipientItem *> List;
+
+#ifdef KDEPIM_NEW_DISTRLISTS
+ RecipientItem( KABC::AddressBook *ab );
+ void setDistributionList( KPIM::DistributionList& );
+ KPIM::DistributionList& distributionList();
+#else
+ RecipientItem();
+ void setDistributionList( KABC::DistributionList * );
+ KABC::DistributionList * distributionList();
+#endif
+ void setAddressee( const KABC::Addressee &, const QString &email );
+
+ void setRecipientType( const QString &type );
+ QString recipientType() const;
+
+ QString recipient() const;
+
+ QPixmap icon() const;
+ QString name() const;
+ QString email() const;
+
+ QString key() const { return mKey; }
+
+ QString tooltip() const;
+
+ private:
+#ifdef KDEPIM_NEW_DISTRLISTS
+ QString createTooltip( KPIM::DistributionList & ) const;
+#else
+ QString createTooltip( KABC::DistributionList * ) const;
+#endif
+
+ KABC::Addressee mAddressee;
+ QString mName;
+ QString mEmail;
+ QString mRecipient;
+#ifdef KDEPIM_NEW_DISTRLISTS
+ KPIM::DistributionList mDistributionList;
+ KABC::AddressBook *mAddressBook;
+#else
+ KABC::DistributionList *mDistributionList;
+#endif
+ QString mType;
+ QString mTooltip;
+
+ QPixmap mIcon;
+
+ QString mKey;
+};
+
+class RecipientViewItem : public KListViewItem
+{
+ public:
+ RecipientViewItem( RecipientItem *, KListView * );
+
+ RecipientItem *recipientItem() const;
+
+ private:
+ RecipientItem *mRecipientItem;
+};
+
+class RecipientsListToolTip : public QToolTip
+{
+ public:
+ RecipientsListToolTip( QWidget *parent, KListView * );
+
+ protected:
+ void maybeTip( const QPoint &pos );
+
+ private:
+ KListView *mListView;
+};
+
+class RecipientsCollection
+{
+ public:
+ RecipientsCollection( const QString & );
+ ~RecipientsCollection();
+
+ void setReferenceContainer( bool );
+ bool isReferenceContainer() const;
+
+ void setTitle( const QString & );
+ QString title() const;
+
+ void addItem( RecipientItem * );
+
+ RecipientItem::List items() const;
+
+ bool hasEquivalentItem( RecipientItem * ) const;
+ RecipientItem * getEquivalentItem( RecipientItem *) const;
+
+ void clear();
+
+ void deleteAll();
+
+ QString id() const;
+
+ private:
+ // flag to indicate if this collection contains just references
+ // or should manage memory (de)allocation as well.
+ bool mIsReferenceContainer;
+ QString mId;
+ QString mTitle;
+ QMap<QString, RecipientItem *> mKeyMap;
+};
+
+class SearchLine : public KListViewSearchLine
+{
+ Q_OBJECT
+ public:
+ SearchLine( QWidget *parent, KListView *listView );
+
+ signals:
+ void downPressed();
+
+ protected:
+ void keyPressEvent( QKeyEvent * );
+};
+
+using namespace KABC;
+
+class RecipientsPicker : public QDialog
+{
+ Q_OBJECT
+ public:
+ RecipientsPicker( QWidget *parent );
+ ~RecipientsPicker();
+
+ void setRecipients( const Recipient::List & );
+ void updateRecipient( const Recipient & );
+
+ void setDefaultType( Recipient::Type );
+
+ signals:
+ void pickedRecipient( const Recipient & );
+
+ protected:
+ void initCollections();
+ void insertDistributionLists();
+ void insertRecentAddresses();
+ void insertCollection( RecipientsCollection *coll );
+
+ void keyPressEvent( QKeyEvent *ev );
+
+ void readConfig();
+ void writeConfig();
+
+ void pick( Recipient::Type );
+
+ void setDefaultButton( QPushButton *button );
+
+ void rebuildAllRecipientsList();
+
+ protected slots:
+ void updateList();
+ void slotToClicked();
+ void slotCcClicked();
+ void slotBccClicked();
+ void slotPicked( QListViewItem * );
+ void slotPicked();
+ void setFocusList();
+ void resetSearch();
+ void insertAddressBook( AddressBook * );
+ void slotSearchLDAP();
+ void ldapSearchResult();
+ private:
+ KABC::StdAddressBook *mAddressBook;
+
+ QComboBox *mCollectionCombo;
+ KListView *mRecipientList;
+ KListViewSearchLine *mSearchLine;
+
+ QPushButton *mToButton;
+ QPushButton *mCcButton;
+ QPushButton *mBccButton;
+
+ QPushButton *mSearchLDAPButton;
+ KPIM::LDAPSearchDialog *mLdapSearchDialog;
+
+ QMap<int,RecipientsCollection *> mCollectionMap;
+ RecipientsCollection *mAllRecipients;
+ RecipientsCollection *mDistributionLists;
+ RecipientsCollection *mSelectedRecipients;
+
+#ifndef KDEPIM_NEW_DISTRLISTS
+ KABC::DistributionListManager *mDistributionListManager;
+#endif
+
+ Recipient::Type mDefaultType;
+};
+
+#endif
diff --git a/kmail/redirectdialog.cpp b/kmail/redirectdialog.cpp
new file mode 100644
index 00000000..b81a0482
--- /dev/null
+++ b/kmail/redirectdialog.cpp
@@ -0,0 +1,140 @@
+/*
+ This file is part of KMail.
+ Copyright (c) 2003 Andreas Gungl <a.gungl@gmx.de>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "redirectdialog.h"
+
+#include "kmkernel.h"
+#include "kmlineeditspell.h"
+
+#include <libemailfunctions/email.h>
+#include <addressesdialog.h>
+using KPIM::AddressesDialog;
+#include "recentaddresses.h"
+using KRecentAddress::RecentAddresses;
+
+#include <kiconloader.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+
+#include <qvbox.h>
+#include <qhbox.h>
+#include <qtooltip.h>
+#include <qwhatsthis.h>
+#include <qlabel.h>
+#include <qpushbutton.h>
+#include <qstringlist.h>
+
+using namespace KMail;
+
+RedirectDialog::RedirectDialog( QWidget *parent, const char *name,
+ bool modal, bool immediate )
+ : KDialogBase( parent, name, modal, i18n( "Redirect Message" ),
+ User1|User2|Cancel, ( immediate ? User1 : User2 ), false )
+{
+ QVBox *vbox = makeVBoxMainWidget();
+ mLabelTo = new QLabel( i18n( "Select the recipient &addresses "
+ "to redirect to:" ), vbox );
+
+ QHBox *hbox = new QHBox( vbox );
+ hbox->setSpacing(4);
+ mEditTo = new KMLineEdit( true, hbox, "toLine" );
+ mEditTo->setMinimumWidth( 300 );
+
+ mBtnTo = new QPushButton( QString::null, hbox, "toBtn" );
+ mBtnTo->setPixmap( BarIcon( "contents", KIcon::SizeSmall ) );
+ mBtnTo->setMinimumSize( mBtnTo->sizeHint() * 1.2 );
+ QToolTip::add( mBtnTo, i18n("Use the Address-Selection Dialog") );
+ QWhatsThis::add( mBtnTo, i18n("This button opens a separate dialog "
+ "where you can select recipients out "
+ "of all available addresses." ) );
+
+ connect( mBtnTo, SIGNAL(clicked()), SLOT(slotAddrBook()) );
+
+ mLabelTo->setBuddy( mBtnTo );
+ mEditTo->setFocus();
+
+ setButtonGuiItem( User1, KGuiItem( i18n("&Send Now"), "mail_send" ) );
+ setButtonGuiItem( User2, KGuiItem( i18n("Send &Later"), "queue" ) );
+}
+
+
+//-----------------------------------------------------------------------------
+void RedirectDialog::slotUser1()
+{
+ mImmediate = true;
+ accept();
+}
+
+//-----------------------------------------------------------------------------
+void RedirectDialog::slotUser2()
+{
+ mImmediate = false;
+ accept();
+}
+
+//-----------------------------------------------------------------------------
+void RedirectDialog::accept()
+{
+ mResentTo = mEditTo->text();
+ if ( mResentTo.isEmpty() ) {
+ KMessageBox::sorry( this,
+ i18n("You cannot redirect the message without an address."),
+ i18n("Empty Redirection Address") );
+ }
+ else done( Ok );
+}
+
+
+//-----------------------------------------------------------------------------
+void RedirectDialog::slotAddrBook()
+{
+ AddressesDialog dlg( this );
+
+ mResentTo = mEditTo->text();
+ if ( !mResentTo.isEmpty() ) {
+ QStringList lst = KPIM::splitEmailAddrList( mResentTo );
+ dlg.setSelectedTo( lst );
+ }
+
+ dlg.setRecentAddresses(
+ RecentAddresses::self( KMKernel::config() )->kabcAddresses() );
+
+ // Make it impossible to specify Cc or Bcc addresses as we support
+ // only the Redirect-To header!
+ dlg.setShowCC( false );
+ dlg.setShowBCC( false );
+
+ if (dlg.exec()==QDialog::Rejected) return;
+
+ mEditTo->setText( dlg.to().join(", ") );
+ mEditTo->setEdited( true );
+}
+
+
+#include "redirectdialog.moc"
diff --git a/kmail/redirectdialog.h b/kmail/redirectdialog.h
new file mode 100644
index 00000000..de2472d4
--- /dev/null
+++ b/kmail/redirectdialog.h
@@ -0,0 +1,92 @@
+/* -*- mode: C++ -*-
+ This file is part of KMail.
+ Copyright (c) 2003 Andreas Gungl <a.gungl@gmx.de>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+#ifndef KMAIL_REDIRECTDIALOG_H
+#define KMAIL_REDIRECTDIALOG_H
+
+#include <kdialogbase.h>
+
+class KMLineEdit;
+class QPushButton;
+class QLabel;
+
+namespace KMail {
+
+ //---------------------------------------------------------------------------
+ /**
+ @short KMail message redirection dialog.
+ @author Andreas Gungl <a.gungl@gmx.de>
+
+ The dialog is used to collect redirect addresses when
+ manually redirecting messages. Only Redirect-To is
+ supported so far.
+
+ */
+ class RedirectDialog : public KDialogBase
+ {
+ Q_OBJECT
+
+ public:
+ /** Constructor
+ @param parent parent QWidget
+ @param name dialog name
+ @param modal dialog modal type
+ @param immediate True, if the Send Now button should be default
+ or false if the Queue button should be default
+ */
+ RedirectDialog( QWidget *parent=0, const char *name=0,
+ bool modal=false, bool immediate=true );
+
+ /** Return the addresses for the redirection */
+ QString to() { return mResentTo; };
+
+ /** Returns the send mode */
+ bool sendImmediate() { return mImmediate; };
+
+ protected:
+ /** Evaluate the settings, an empty To field is not allowed. */
+ void accept();
+
+ protected slots:
+ /** Open addressbook editor dialog. */
+ void slotAddrBook();
+
+ void slotUser1();
+ void slotUser2();
+
+ private:
+ QLabel *mLabelTo;
+ KMLineEdit *mEditTo;
+ QPushButton *mBtnTo;
+ QString mResentTo;
+ bool mImmediate;
+ };
+
+} // namespace KMail
+
+#endif // KMAIL_REDIRECTDIALOG_H
diff --git a/kmail/regexplineedit.cpp b/kmail/regexplineedit.cpp
new file mode 100644
index 00000000..d629d401
--- /dev/null
+++ b/kmail/regexplineedit.cpp
@@ -0,0 +1,136 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ regexplineedit.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2004 Ingo Kloecker <kloecker@kde.org>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "regexplineedit.h"
+
+#include <klocale.h>
+#include <klineedit.h>
+#include <kparts/componentfactory.h>
+#include <kregexpeditorinterface.h>
+#include <kdialog.h>
+
+#include <qlayout.h>
+#include <qstring.h>
+#include <qpushbutton.h>
+#include <qdialog.h>
+
+namespace KMail {
+
+ RegExpLineEdit::RegExpLineEdit( QWidget *parent, const char *name )
+ : QWidget( parent, name ),
+ mLineEdit( 0 ),
+ mRegExpEditButton( 0 ),
+ mRegExpEditDialog( 0 )
+ {
+ initWidget();
+ }
+
+ RegExpLineEdit::RegExpLineEdit( const QString &str, QWidget *parent,
+ const char *name )
+ : QWidget( parent, name ),
+ mLineEdit( 0 ),
+ mRegExpEditButton( 0 ),
+ mRegExpEditDialog( 0 )
+ {
+ initWidget( str );
+ }
+
+ void RegExpLineEdit::initWidget( const QString &str )
+ {
+ QHBoxLayout * hlay = new QHBoxLayout( this, 0, KDialog::spacingHint() );
+
+ mLineEdit = new KLineEdit( str, this );
+ setFocusProxy( mLineEdit );
+ hlay->addWidget( mLineEdit );
+
+ connect( mLineEdit, SIGNAL( textChanged( const QString & ) ),
+ this, SIGNAL( textChanged( const QString & ) ) );
+
+ if( !KTrader::self()->query("KRegExpEditor/KRegExpEditor").isEmpty() ) {
+ mRegExpEditButton = new QPushButton( i18n("Edit..."), this,
+ "mRegExpEditButton" );
+ mRegExpEditButton->setSizePolicy( QSizePolicy::Minimum,
+ QSizePolicy::Fixed );
+ hlay->addWidget( mRegExpEditButton );
+
+ connect( mRegExpEditButton, SIGNAL( clicked() ),
+ this, SLOT( slotEditRegExp() ) );
+ }
+ }
+
+ void RegExpLineEdit::clear()
+ {
+ mLineEdit->clear();
+ }
+
+ QString RegExpLineEdit::text() const
+ {
+ return mLineEdit->text();
+ }
+
+ void RegExpLineEdit::setText( const QString & str )
+ {
+ mLineEdit->setText( str );
+ }
+
+ void RegExpLineEdit::showEditButton( bool show )
+ {
+ if ( !mRegExpEditButton )
+ return;
+
+ if ( show )
+ mRegExpEditButton->show();
+ else
+ mRegExpEditButton->hide();
+ }
+
+ void RegExpLineEdit::slotEditRegExp()
+ {
+ if ( !mRegExpEditDialog )
+ mRegExpEditDialog = KParts::ComponentFactory::createInstanceFromQuery<QDialog>( "KRegExpEditor/KRegExpEditor", QString::null, this );
+
+ KRegExpEditorInterface *iface =
+ static_cast<KRegExpEditorInterface *>( mRegExpEditDialog->qt_cast( "KRegExpEditorInterface" ) );
+ if( iface ) {
+ iface->setRegExp( mLineEdit->text() );
+ if( mRegExpEditDialog->exec() == QDialog::Accepted )
+ mLineEdit->setText( iface->regExp() );
+ }
+ }
+
+} // namespace KMail
+
+#include "regexplineedit.moc"
diff --git a/kmail/regexplineedit.h b/kmail/regexplineedit.h
new file mode 100644
index 00000000..23332fed
--- /dev/null
+++ b/kmail/regexplineedit.h
@@ -0,0 +1,79 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ regexplineedit.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2004 Ingo Kloecker <kloecker@kde.org>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_REGEXPLINEEDIT_H__
+#define __KMAIL_REGEXPLINEEDIT_H__
+
+#include <qobject.h>
+#include <qwidget.h>
+
+class KLineEdit;
+
+class QString;
+class QPushButton;
+class QDialog;
+
+namespace KMail {
+
+ class RegExpLineEdit : public QWidget
+ {
+ Q_OBJECT
+
+ public:
+ RegExpLineEdit( const QString &str, QWidget *parent = 0,
+ const char *name = 0 );
+ RegExpLineEdit( QWidget *parent = 0, const char *name = 0 );
+
+ QString text() const;
+
+ public slots:
+ void clear();
+ void setText( const QString & );
+ void showEditButton( bool );
+
+ signals:
+ void textChanged( const QString & );
+
+ protected slots:
+ void slotEditRegExp();
+
+ private:
+ void initWidget( const QString & = QString::null );
+
+ KLineEdit * mLineEdit;
+ QPushButton * mRegExpEditButton;
+ QDialog * mRegExpEditDialog;
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_REGEXPLINEEDIT_H__
diff --git a/kmail/renamejob.cpp b/kmail/renamejob.cpp
new file mode 100644
index 00000000..79b0793e
--- /dev/null
+++ b/kmail/renamejob.cpp
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) 2004 Carsten Burghardt <burghardt@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#include "renamejob.h"
+#include "copyfolderjob.h"
+#include "kmfolderimap.h"
+#include "kmfoldercachedimap.h"
+#include "folderstorage.h"
+#include "kmfolder.h"
+#include "kmfolderdir.h"
+#include "kmfoldermgr.h"
+#include "imapaccountbase.h"
+#include "kmacctimap.h"
+#include "kmacctcachedimap.h"
+#include "kmmsgbase.h"
+
+#include <kdebug.h>
+#include <kurl.h>
+#include <kio/scheduler.h>
+#include <kio/job.h>
+#include <kio/global.h>
+#include <klocale.h>
+#include <config.h>
+
+#include <qmap.h>
+
+using namespace KMail;
+
+template <typename T> static QStringList imapPaths( FolderStorage* storage )
+{
+ QStringList rv;
+ rv.append( static_cast<T>( storage )->imapPath() );
+ KMFolderDir* dir = storage->folder()->child();
+ if ( dir ) {
+ KMFolderNode *node = dir->first();
+ while ( node ) {
+ if ( !node->isDir() ) {
+ rv += imapPaths<T>( static_cast<KMFolder*>( node )->storage() );
+ }
+ node = dir->next();
+ }
+ }
+ return rv;
+}
+
+RenameJob::RenameJob( FolderStorage* storage, const QString& newName,
+ KMFolderDir* newParent )
+ : FolderJob( 0, tOther, (storage ? storage->folder() : 0) ),
+ mStorage( storage ), mNewParent( newParent ),
+ mNewName( newName ), mNewFolder( 0 ), mCopyFolderJob( 0 )
+{
+ mStorageTempOpened = 0;
+ if ( storage ) {
+ mOldName = storage->name();
+ if ( storage->folderType() == KMFolderTypeImap ) {
+ mOldImapPath = static_cast<KMFolderImap*>(storage)->imapPath();
+// mOldImapPaths = imapPaths<KMFolderImap*>( storage );
+ } else if ( storage->folderType() == KMFolderTypeCachedImap ) {
+ mOldImapPath = static_cast<KMFolderCachedImap*>(storage)->imapPath();
+ mOldImapPaths = imapPaths<KMFolderCachedImap*>( storage );
+ }
+ }
+}
+
+RenameJob::~RenameJob()
+{
+}
+
+// FIXME: move on the server for online imap given source and target are on the same server
+void RenameJob::execute()
+{
+ if ( mNewParent )
+ {
+ // move the folder to a different parent
+ KMFolderType type = mStorage->folderType();
+ if ( ( type == KMFolderTypeMbox || type == KMFolderTypeMaildir ) &&
+ mNewParent->type() == KMStandardDir &&
+ mStorage->folderType() != KMFolderTypeCachedImap )
+ {
+ // local folders can handle this on their own
+ mStorage->rename( mNewName, mNewParent );
+ emit renameDone( mNewName, true );
+ deleteLater();
+ return;
+ }
+ // copy to the new folder
+ mCopyFolderJob = new CopyFolderJob( mStorage, mNewParent );
+ connect( mCopyFolderJob, SIGNAL(folderCopyComplete(bool)), SLOT(folderCopyComplete(bool)) );
+ mCopyFolderJob->execute();
+
+ } else
+ {
+ // only rename the folder
+ if ( mStorage->folderType() != KMFolderTypeImap )
+ {
+ // local and dimap folder handle this directly
+ mStorage->rename( mNewName );
+ emit renameDone( mNewName, true );
+ deleteLater();
+ return;
+ }
+ if ( mOldImapPath.isEmpty() )
+ {
+ // sanity
+ emit renameDone( mNewName, false );
+ deleteLater();
+ return;
+ } else if ( mOldName == mNewName || mOldImapPath == "/INBOX/" ) {
+ emit renameDone( mNewName, true ); // noop
+ deleteLater();
+ return;
+ }
+ ImapAccountBase* account = static_cast<KMFolderImap*>(mStorage)->account();
+ // first rename it on the server
+ mNewImapPath = mOldImapPath;
+ mNewImapPath = mNewImapPath.replace( mOldName, mNewName );
+ KURL src( account->getUrl() );
+ src.setPath( mOldImapPath );
+ KURL dst( account->getUrl() );
+ dst.setPath( mNewImapPath );
+ KIO::SimpleJob *job = KIO::rename( src, dst, true );
+ kdDebug(5006)<< "RenameJob::rename - " << src.prettyURL()
+ << " |=> " << dst.prettyURL() << endl;
+ ImapAccountBase::jobData jd( src.url() );
+ account->insertJob( job, jd );
+ KIO::Scheduler::assignJobToSlave( account->slave(), job );
+ connect( job, SIGNAL(result(KIO::Job*)),
+ SLOT(slotRenameResult(KIO::Job*)) );
+ }
+}
+
+void RenameJob::slotRenameResult( KIO::Job *job )
+{
+ ImapAccountBase* account = static_cast<KMFolderImap*>(mStorage)->account();
+ ImapAccountBase::JobIterator it = account->findJob(job);
+ if ( it == account->jobsEnd() )
+ {
+ emit renameDone( mNewName, false );
+ deleteLater();
+ return;
+ }
+ if ( job->error() )
+ {
+ account->handleJobError( job, i18n("Error while renaming a folder.") );
+ emit renameDone( mNewName, false );
+ deleteLater();
+ return;
+ }
+ account->removeJob(it);
+ // set the new path
+ if ( mStorage->folderType() == KMFolderTypeImap )
+ static_cast<KMFolderImap*>(mStorage)->setImapPath( mNewImapPath );
+ // unsubscribe old (we don't want ghosts)
+ account->changeSubscription( false, mOldImapPath );
+ // subscribe new
+ account->changeSubscription( true, mNewImapPath );
+
+ // local part (will set the new name)
+ mStorage->rename( mNewName );
+
+ emit renameDone( mNewName, true );
+ deleteLater();
+}
+
+void RenameJob::folderCopyComplete(bool success)
+{
+ kdDebug(5006) << k_funcinfo << success << endl;
+ if ( !success ) {
+ kdWarning(5006) << k_funcinfo << "could not copy folder" << endl;
+ emit renameDone( mNewName, false );
+ deleteLater();
+ return;
+ }
+ mNewFolder = mCopyFolderJob->targetFolder();
+ mCopyFolderJob = 0;
+
+ if ( mStorageTempOpened ) {
+ mStorageTempOpened->close( "renamejob" );
+ mStorageTempOpened = 0;
+ }
+
+ kdDebug(5006) << "deleting old folder" << endl;
+ // move complete or not necessary
+ // save our settings
+ QString oldconfig = "Folder-" + mStorage->folder()->idString();
+ KConfig* config = KMKernel::config();
+ QMap<QString, QString> entries = config->entryMap( oldconfig );
+ KConfigGroupSaver saver(config, "Folder-" + mNewFolder->idString());
+ for ( QMap<QString, QString>::Iterator it = entries.begin();
+ it != entries.end(); ++it )
+ {
+ if ( it.key() == "Id" || it.key() == "ImapPath" ||
+ it.key() == "UidValidity" )
+ continue;
+ config->writeEntry( it.key(), it.data() );
+ }
+ mNewFolder->readConfig( config );
+ // make sure the children state is correct
+ if ( mNewFolder->child() &&
+ ( mNewFolder->storage()->hasChildren() == FolderStorage::HasNoChildren ) )
+ mNewFolder->storage()->updateChildrenState();
+
+ // delete the old folder
+ mStorage->blockSignals( false );
+ if ( mStorage->folderType() == KMFolderTypeImap )
+ {
+ kmkernel->imapFolderMgr()->remove( mStorage->folder() );
+ } else if ( mStorage->folderType() == KMFolderTypeCachedImap )
+ {
+ // tell the account (see KMFolderCachedImap::listDirectory2)
+ KMAcctCachedImap* acct = static_cast<KMFolderCachedImap*>(mStorage)->account();
+ if ( acct ) {
+ for ( QStringList::ConstIterator it = mOldImapPaths.constBegin(); it != mOldImapPaths.constEnd(); ++it )
+ acct->addDeletedFolder( *it );
+ }
+ kmkernel->dimapFolderMgr()->remove( mStorage->folder() );
+ } else if ( mStorage->folderType() == KMFolderTypeSearch )
+ {
+ // invalid
+ kdWarning(5006) << k_funcinfo << "cannot remove a search folder" << endl;
+ } else {
+ kmkernel->folderMgr()->remove( mStorage->folder() );
+ }
+
+ emit renameDone( mNewName, true );
+}
+
+#include "renamejob.moc"
diff --git a/kmail/renamejob.h b/kmail/renamejob.h
new file mode 100644
index 00000000..3c4c1543
--- /dev/null
+++ b/kmail/renamejob.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2004 Carsten Burghardt <burghardt@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+#ifndef RENAMEJOB_H
+#define RENAMEJOB_H
+
+#include "folderjob.h"
+
+class FolderStorage;
+class KMFolderDir;
+class KMFolder;
+class KMCommand;
+
+namespace KIO {
+ class Job;
+}
+
+namespace KMail {
+
+class CopyFolderJob;
+
+/**
+ * Rename and move (d)imap folders
+ * They can be moved everywhere (except search folders) as a new folder is
+ * created, all messages are moved there and the original folder is deleted
+ */
+class RenameJob : public FolderJob
+{
+ Q_OBJECT
+public:
+ /**
+ * Create a new job
+ * @param storage the folder that should be renames
+ * @param newName the new name of the folder
+ * @param newParent the new parent if the folder should be moved, else 0
+ */
+ RenameJob( FolderStorage* storage, const QString& newName,
+ KMFolderDir* newParent = 0 );
+
+ virtual ~RenameJob();
+
+ virtual void execute();
+
+protected slots:
+ /** Rename the folder */
+ void slotRenameResult( KIO::Job* job );
+
+ /** All messages are copied so remove the original folder */
+ void folderCopyComplete( bool success );
+
+signals:
+ /** Emitted when the job is done, check the success bool */
+ void renameDone( QString newName, bool success );
+
+protected:
+ FolderStorage* mStorage;
+ FolderStorage* mStorageTempOpened;
+ KMFolderDir* mNewParent;
+ QString mNewName;
+ QString mNewImapPath;
+ QString mOldName;
+ QString mOldImapPath;
+ QStringList mOldImapPaths;
+ KMFolder* mNewFolder;
+ CopyFolderJob *mCopyFolderJob;
+};
+
+} // namespace KMail
+
+#endif /* RENAMEJOB_H */
+
diff --git a/kmail/replyphrases.kcfg b/kmail/replyphrases.kcfg
new file mode 100644
index 00000000..4651dfe8
--- /dev/null
+++ b/kmail/replyphrases.kcfg
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
+ http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
+ <kcfgfile name="kmailrc">
+ <parameter name="langId" />
+ </kcfgfile>
+
+ <group name="KMMessage #$(langId)">
+ <entry name="IndentPrefix" type="String" key="indent-prefix">
+ <label></label>
+ <whatsthis></whatsthis>
+ <default>>%_</default>
+ </entry>
+ <entry name="Language" type="String" key="language">
+ <label></label>
+ <whatsthis></whatsthis>
+ </entry>
+ <entry name="PhraseForward" type="String" key="phrase-forward">
+ <label></label>
+ <whatsthis></whatsthis>
+ </entry>
+ <entry name="PhraseReplySender" type="String" key="phrase-reply">
+ <label></label>
+ <whatsthis></whatsthis>
+ </entry>
+ <entry name="PhraseReplyAll" type="String" key="phrase-reply-all">
+ <label></label>
+ <whatsthis></whatsthis>
+ </entry>
+
+ </group>
+
+</kcfg>
diff --git a/kmail/replyphrases.kcfgc b/kmail/replyphrases.kcfgc
new file mode 100644
index 00000000..5c8f7089
--- /dev/null
+++ b/kmail/replyphrases.kcfgc
@@ -0,0 +1,5 @@
+File=replyphrases.kcfg
+ClassName=ReplyPhrases
+Mutators=true
+ItemAccessors=true
+SetUserTexts=true
diff --git a/kmail/rulewidgethandlermanager.cpp b/kmail/rulewidgethandlermanager.cpp
new file mode 100644
index 00000000..c965db35
--- /dev/null
+++ b/kmail/rulewidgethandlermanager.cpp
@@ -0,0 +1,1520 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ rulewidgethandlermanager.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2004 Ingo Kloecker <kloecker@kde.org>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "rulewidgethandlermanager.h"
+
+#include "interfaces/rulewidgethandler.h"
+#include "stl_util.h"
+
+#include <kdebug.h>
+#include <kiconloader.h>
+
+#include <qwidgetstack.h>
+#include <qstring.h>
+#include <qcstring.h>
+#include <qobject.h>
+#include <qobjectlist.h>
+
+#include <assert.h>
+
+#include <algorithm>
+using std::for_each;
+using std::remove;
+
+KMail::RuleWidgetHandlerManager * KMail::RuleWidgetHandlerManager::self = 0;
+
+namespace {
+ class TextRuleWidgetHandler : public KMail::RuleWidgetHandler {
+ public:
+ TextRuleWidgetHandler() : KMail::RuleWidgetHandler() {}
+ ~TextRuleWidgetHandler() {}
+
+ QWidget * createFunctionWidget( int number,
+ QWidgetStack *functionStack,
+ const QObject *receiver ) const;
+ QWidget * createValueWidget( int number,
+ QWidgetStack *valueStack,
+ const QObject *receiver ) const;
+ KMSearchRule::Function function( const QCString & field,
+ const QWidgetStack *functionStack ) const;
+ QString value( const QCString & field,
+ const QWidgetStack *functionStack,
+ const QWidgetStack *valueStack ) const;
+ QString prettyValue( const QCString & field,
+ const QWidgetStack *functionStack,
+ const QWidgetStack *valueStack ) const;
+ bool handlesField( const QCString & field ) const;
+ void reset( QWidgetStack *functionStack,
+ QWidgetStack *valueStack ) const;
+ bool setRule( QWidgetStack *functionStack,
+ QWidgetStack *valueStack,
+ const KMSearchRule *rule ) const;
+ bool update( const QCString & field,
+ QWidgetStack *functionStack,
+ QWidgetStack *valueStack ) const;
+
+ private:
+ KMSearchRule::Function currentFunction( const QWidgetStack *functionStack ) const;
+ QString currentValue( const QWidgetStack *valueStack,
+ KMSearchRule::Function func ) const;
+ };
+
+ class MessageRuleWidgetHandler : public KMail::RuleWidgetHandler {
+ public:
+ MessageRuleWidgetHandler() : KMail::RuleWidgetHandler() {}
+ ~MessageRuleWidgetHandler() {}
+
+ QWidget * createFunctionWidget( int number,
+ QWidgetStack *functionStack,
+ const QObject *receiver ) const;
+ QWidget * createValueWidget( int number,
+ QWidgetStack *valueStack,
+ const QObject *receiver ) const;
+ KMSearchRule::Function function( const QCString & field,
+ const QWidgetStack *functionStack ) const;
+ QString value( const QCString & field,
+ const QWidgetStack *functionStack,
+ const QWidgetStack *valueStack ) const;
+ QString prettyValue( const QCString & field,
+ const QWidgetStack *functionStack,
+ const QWidgetStack *valueStack ) const;
+ bool handlesField( const QCString & field ) const;
+ void reset( QWidgetStack *functionStack,
+ QWidgetStack *valueStack ) const;
+ bool setRule( QWidgetStack *functionStack,
+ QWidgetStack *valueStack,
+ const KMSearchRule *rule ) const;
+ bool update( const QCString & field,
+ QWidgetStack *functionStack,
+ QWidgetStack *valueStack ) const;
+
+ private:
+ KMSearchRule::Function currentFunction( const QWidgetStack *functionStack ) const;
+ QString currentValue( const QWidgetStack *valueStack,
+ KMSearchRule::Function func ) const;
+ };
+
+
+ class StatusRuleWidgetHandler : public KMail::RuleWidgetHandler {
+ public:
+ StatusRuleWidgetHandler() : KMail::RuleWidgetHandler() {}
+ ~StatusRuleWidgetHandler() {}
+
+ QWidget * createFunctionWidget( int number,
+ QWidgetStack *functionStack,
+ const QObject *receiver ) const;
+ QWidget * createValueWidget( int number,
+ QWidgetStack *valueStack,
+ const QObject *receiver ) const;
+ KMSearchRule::Function function( const QCString & field,
+ const QWidgetStack *functionStack ) const;
+ QString value( const QCString & field,
+ const QWidgetStack *functionStack,
+ const QWidgetStack *valueStack ) const;
+ QString prettyValue( const QCString & field,
+ const QWidgetStack *functionStack,
+ const QWidgetStack *valueStack ) const;
+ bool handlesField( const QCString & field ) const;
+ void reset( QWidgetStack *functionStack,
+ QWidgetStack *valueStack ) const;
+ bool setRule( QWidgetStack *functionStack,
+ QWidgetStack *valueStack,
+ const KMSearchRule *rule ) const;
+ bool update( const QCString & field,
+ QWidgetStack *functionStack,
+ QWidgetStack *valueStack ) const;
+
+ private:
+ KMSearchRule::Function currentFunction( const QWidgetStack *functionStack ) const;
+ int currentStatusValue( const QWidgetStack *valueStack ) const;
+ };
+
+ class NumericRuleWidgetHandler : public KMail::RuleWidgetHandler {
+ public:
+ NumericRuleWidgetHandler() : KMail::RuleWidgetHandler() {}
+ ~NumericRuleWidgetHandler() {}
+
+ QWidget * createFunctionWidget( int number,
+ QWidgetStack *functionStack,
+ const QObject *receiver ) const;
+ QWidget * createValueWidget( int number,
+ QWidgetStack *valueStack,
+ const QObject *receiver ) const;
+ KMSearchRule::Function function( const QCString & field,
+ const QWidgetStack *functionStack ) const;
+ QString value( const QCString & field,
+ const QWidgetStack *functionStack,
+ const QWidgetStack *valueStack ) const;
+ QString prettyValue( const QCString & field,
+ const QWidgetStack *functionStack,
+ const QWidgetStack *valueStack ) const;
+ bool handlesField( const QCString & field ) const;
+ void reset( QWidgetStack *functionStack,
+ QWidgetStack *valueStack ) const;
+ bool setRule( QWidgetStack *functionStack,
+ QWidgetStack *valueStack,
+ const KMSearchRule *rule ) const;
+ bool update( const QCString & field,
+ QWidgetStack *functionStack,
+ QWidgetStack *valueStack ) const;
+
+ private:
+ KMSearchRule::Function currentFunction( const QWidgetStack *functionStack ) const;
+ QString currentValue( const QWidgetStack *valueStack ) const;
+ };
+}
+
+KMail::RuleWidgetHandlerManager::RuleWidgetHandlerManager()
+{
+ registerHandler( new NumericRuleWidgetHandler() );
+ registerHandler( new StatusRuleWidgetHandler() );
+ registerHandler( new MessageRuleWidgetHandler() );
+ // the TextRuleWidgetHandler is the fallback handler, so it has to be added
+ // as last handler
+ registerHandler( new TextRuleWidgetHandler() );
+}
+
+KMail::RuleWidgetHandlerManager::~RuleWidgetHandlerManager()
+{
+ for_each( mHandlers.begin(), mHandlers.end(),
+ DeleteAndSetToZero<RuleWidgetHandler>() );
+}
+
+void KMail::RuleWidgetHandlerManager::registerHandler( const RuleWidgetHandler * handler )
+{
+ if ( !handler )
+ return;
+ unregisterHandler( handler ); // don't produce duplicates
+ mHandlers.push_back( handler );
+}
+
+void KMail::RuleWidgetHandlerManager::unregisterHandler( const RuleWidgetHandler * handler )
+{
+ // don't delete them, only remove them from the list!
+ mHandlers.erase( remove( mHandlers.begin(), mHandlers.end(), handler ), mHandlers.end() );
+}
+
+namespace {
+ /** Returns the number of immediate children of parent with the given object
+ name. Used by RuleWidgetHandlerManager::createWidgets().
+ */
+ int childCount( const QObject *parent, const char *objName )
+ {
+ QObjectList *list = parent->queryList( 0, objName, false, false );
+ if ( !list )
+ return 0;
+ const int count = list->count();
+ delete list; list = 0;
+ return count;
+ }
+}
+
+void KMail::RuleWidgetHandlerManager::createWidgets( QWidgetStack *functionStack,
+ QWidgetStack *valueStack,
+ const QObject *receiver ) const
+{
+ for ( const_iterator it = mHandlers.begin(); it != mHandlers.end(); ++it ) {
+ QWidget *w = 0;
+ for ( int i = 0;
+ ( w = (*it)->createFunctionWidget( i, functionStack, receiver ) );
+ ++i ) {
+ if ( childCount( functionStack, w->name() ) < 2 ) {
+ // there wasn't already a widget with this name, so add this widget
+ functionStack->addWidget( w );
+ }
+ else {
+ // there was already a widget with this name, so discard this widget
+ kdDebug(5006) << "RuleWidgetHandlerManager::createWidgets: "
+ << w->name() << " already exists in functionStack"
+ << endl;
+ delete w; w = 0;
+ }
+ }
+ for ( int i = 0;
+ ( w = (*it)->createValueWidget( i, valueStack, receiver ) );
+ ++i ) {
+ if ( childCount( valueStack, w->name() ) < 2 ) {
+ // there wasn't already a widget with this name, so add this widget
+ valueStack->addWidget( w );
+ }
+ else {
+ // there was already a widget with this name, so discard this widget
+ kdDebug(5006) << "RuleWidgetHandlerManager::createWidgets: "
+ << w->name() << " already exists in valueStack"
+ << endl;
+ delete w; w = 0;
+ }
+ }
+ }
+}
+
+KMSearchRule::Function KMail::RuleWidgetHandlerManager::function( const QCString& field,
+ const QWidgetStack *functionStack ) const
+{
+ for ( const_iterator it = mHandlers.begin(); it != mHandlers.end(); ++it ) {
+ const KMSearchRule::Function func = (*it)->function( field,
+ functionStack );
+ if ( func != KMSearchRule::FuncNone )
+ return func;
+ }
+ return KMSearchRule::FuncNone;
+}
+
+QString KMail::RuleWidgetHandlerManager::value( const QCString& field,
+ const QWidgetStack *functionStack,
+ const QWidgetStack *valueStack ) const
+{
+ for ( const_iterator it = mHandlers.begin(); it != mHandlers.end(); ++it ) {
+ const QString val = (*it)->value( field, functionStack, valueStack );
+ if ( !val.isEmpty() )
+ return val;
+ }
+ return QString::null;
+}
+
+QString KMail::RuleWidgetHandlerManager::prettyValue( const QCString& field,
+ const QWidgetStack *functionStack,
+ const QWidgetStack *valueStack ) const
+{
+ for ( const_iterator it = mHandlers.begin(); it != mHandlers.end(); ++it ) {
+ const QString val = (*it)->prettyValue( field, functionStack, valueStack );
+ if ( !val.isEmpty() )
+ return val;
+ }
+ return QString::null;
+}
+
+void KMail::RuleWidgetHandlerManager::reset( QWidgetStack *functionStack,
+ QWidgetStack *valueStack ) const
+{
+ for ( const_iterator it = mHandlers.begin(); it != mHandlers.end(); ++it ) {
+ (*it)->reset( functionStack, valueStack );
+ }
+ update( "", functionStack, valueStack );
+}
+
+void KMail::RuleWidgetHandlerManager::setRule( QWidgetStack *functionStack,
+ QWidgetStack *valueStack,
+ const KMSearchRule *rule ) const
+{
+ assert( rule );
+ reset( functionStack, valueStack );
+ for ( const_iterator it = mHandlers.begin(); it != mHandlers.end(); ++it ) {
+ if ( (*it)->setRule( functionStack, valueStack, rule ) )
+ return;
+ }
+}
+
+void KMail::RuleWidgetHandlerManager::update( const QCString &field,
+ QWidgetStack *functionStack,
+ QWidgetStack *valueStack ) const
+{
+ //kdDebug(5006) << "RuleWidgetHandlerManager::update( \"" << field
+ // << "\", ... )" << endl;
+ for ( const_iterator it = mHandlers.begin(); it != mHandlers.end(); ++it ) {
+ if ( (*it)->update( field, functionStack, valueStack ) )
+ return;
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+namespace {
+ // FIXME (Qt >= 4.0):
+ // This is a simplified and constified copy of QObject::child(). According
+ // to a comment in qobject.h QObject::child() will be made const in Qt 4.0.
+ // So once we require Qt 4.0 this can be removed.
+ QObject* QObject_child_const( const QObject *parent,
+ const char *objName )
+ {
+ const QObjectList *list = parent->children();
+ if ( !list )
+ return 0;
+
+ QObjectListIterator it( *list );
+ QObject *obj;
+ while ( ( obj = it.current() ) ) {
+ ++it;
+ if ( !objName || qstrcmp( objName, obj->name() ) == 0 )
+ break;
+ }
+ return obj;
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+// these includes are temporary and should not be needed for the code
+// above this line, so they appear only here:
+#include "kmaddrbook.h"
+#include "kmsearchpattern.h"
+#include "regexplineedit.h"
+using KMail::RegExpLineEdit;
+
+#include <klocale.h>
+#include <knuminput.h>
+
+#include <qcombobox.h>
+#include <qlabel.h>
+
+//=============================================================================
+//
+// class TextRuleWidgetHandler
+//
+//=============================================================================
+
+namespace {
+ // also see KMSearchRule::matches() and KMSearchRule::Function
+ // if you change the following strings!
+ static const struct {
+ const KMSearchRule::Function id;
+ const char *displayName;
+ } TextFunctions[] = {
+ { KMSearchRule::FuncContains, I18N_NOOP( "contains" ) },
+ { KMSearchRule::FuncContainsNot, I18N_NOOP( "does not contain" ) },
+ { KMSearchRule::FuncEquals, I18N_NOOP( "equals" ) },
+ { KMSearchRule::FuncNotEqual, I18N_NOOP( "does not equal" ) },
+ { KMSearchRule::FuncRegExp, I18N_NOOP( "matches regular expr." ) },
+ { KMSearchRule::FuncNotRegExp, I18N_NOOP( "does not match reg. expr." ) },
+ { KMSearchRule::FuncIsInAddressbook, I18N_NOOP( "is in address book" ) },
+ { KMSearchRule::FuncIsNotInAddressbook, I18N_NOOP( "is not in address book" ) },
+ { KMSearchRule::FuncIsInCategory, I18N_NOOP( "is in category" ) },
+ { KMSearchRule::FuncIsNotInCategory, I18N_NOOP( "is not in category" ) }
+ };
+ static const int TextFunctionCount =
+ sizeof( TextFunctions ) / sizeof( *TextFunctions );
+
+ //---------------------------------------------------------------------------
+
+ QWidget * TextRuleWidgetHandler::createFunctionWidget( int number,
+ QWidgetStack *functionStack,
+ const QObject *receiver ) const
+ {
+ if ( number != 0 )
+ return 0;
+
+ QComboBox *funcCombo = new QComboBox( functionStack, "textRuleFuncCombo" );
+ for ( int i = 0; i < TextFunctionCount; ++i ) {
+ funcCombo->insertItem( i18n( TextFunctions[i].displayName ) );
+ }
+ funcCombo->adjustSize();
+ QObject::connect( funcCombo, SIGNAL( activated( int ) ),
+ receiver, SLOT( slotFunctionChanged() ) );
+ return funcCombo;
+ }
+
+ //---------------------------------------------------------------------------
+
+ QWidget * TextRuleWidgetHandler::createValueWidget( int number,
+ QWidgetStack *valueStack,
+ const QObject *receiver ) const
+ {
+ if ( number == 0 ) {
+ RegExpLineEdit *lineEdit =
+ new RegExpLineEdit( valueStack, "regExpLineEdit" );
+ QObject::connect( lineEdit, SIGNAL( textChanged( const QString & ) ),
+ receiver, SLOT( slotValueChanged() ) );
+ return lineEdit;
+ }
+
+ // blank QLabel to hide value widget for in-address-book rule
+ if ( number == 1 ) {
+ return new QLabel( valueStack, "textRuleValueHider" );
+ }
+
+ if ( number == 2 ) {
+ QComboBox *combo = new QComboBox( valueStack, "categoryCombo" );
+ QStringList categories = KabcBridge::categories();
+ combo->insertStringList( categories );
+ QObject::connect( combo, SIGNAL( activated( int ) ),
+ receiver, SLOT( slotValueChanged() ) );
+ return combo;
+ }
+
+ return 0;
+ }
+
+ //---------------------------------------------------------------------------
+
+ KMSearchRule::Function TextRuleWidgetHandler::currentFunction( const QWidgetStack *functionStack ) const
+ {
+ const QComboBox *funcCombo =
+ dynamic_cast<QComboBox*>( QObject_child_const( functionStack,
+ "textRuleFuncCombo" ) );
+ // FIXME (Qt >= 4.0): Use the following when QObject::child() is const.
+ // dynamic_cast<QComboBox*>( functionStack->child( "textRuleFuncCombo",
+ // 0, false ) );
+ if ( funcCombo ) {
+ return TextFunctions[funcCombo->currentItem()].id;
+ }
+ else
+ kdDebug(5006) << "TextRuleWidgetHandler::currentFunction: "
+ "textRuleFuncCombo not found." << endl;
+ return KMSearchRule::FuncNone;
+ }
+
+ //---------------------------------------------------------------------------
+
+ KMSearchRule::Function TextRuleWidgetHandler::function( const QCString &,
+ const QWidgetStack *functionStack ) const
+ {
+ return currentFunction( functionStack );
+ }
+
+ //---------------------------------------------------------------------------
+
+ QString TextRuleWidgetHandler::currentValue( const QWidgetStack *valueStack,
+ KMSearchRule::Function func ) const
+ {
+ // here we gotta check the combobox which contains the categories
+ if ( func == KMSearchRule::FuncIsInCategory ||
+ func == KMSearchRule::FuncIsNotInCategory ) {
+ const QComboBox *combo=
+ dynamic_cast<QComboBox*>( QObject_child_const( valueStack,
+ "categoryCombo" ) );
+ // FIXME (Qt >= 4.0): Use the following when QObject::child() is const.
+ // dynamic_cast<QComboBox*>( valueStack->child( "categoryCombo",
+ // 0, false ) );
+ if ( combo ) {
+ return combo->currentText();
+ }
+ else {
+ kdDebug(5006) << "TextRuleWidgetHandler::currentValue: "
+ "categoryCombo not found." << endl;
+ return QString::null;
+ }
+ }
+
+ //in other cases of func it is a lineedit
+ const RegExpLineEdit *lineEdit =
+ dynamic_cast<RegExpLineEdit*>( QObject_child_const( valueStack,
+ "regExpLineEdit" ) );
+ // FIXME (Qt >= 4.0): Use the following when QObject::child() is const.
+ // dynamic_cast<RegExpLineEdit*>( valueStack->child( "regExpLineEdit",
+ // 0, false ) );
+ if ( lineEdit ) {
+ return lineEdit->text();
+ }
+ else
+ kdDebug(5006) << "TextRuleWidgetHandler::currentValue: "
+ "regExpLineEdit not found." << endl;
+
+ // or anything else, like addressbook
+ return QString::null;
+ }
+
+ //---------------------------------------------------------------------------
+
+ QString TextRuleWidgetHandler::value( const QCString &,
+ const QWidgetStack *functionStack,
+ const QWidgetStack *valueStack ) const
+ {
+ KMSearchRule::Function func = currentFunction( functionStack );
+ if ( func == KMSearchRule::FuncIsInAddressbook )
+ return "is in address book"; // just a non-empty dummy value
+ else if ( func == KMSearchRule::FuncIsNotInAddressbook )
+ return "is not in address book"; // just a non-empty dummy value
+ else
+ return currentValue( valueStack, func );
+ }
+
+ //---------------------------------------------------------------------------
+
+ QString TextRuleWidgetHandler::prettyValue( const QCString &,
+ const QWidgetStack *functionStack,
+ const QWidgetStack *valueStack ) const
+ {
+ KMSearchRule::Function func = currentFunction( functionStack );
+ if ( func == KMSearchRule::FuncIsInAddressbook )
+ return i18n( "is in address book" );
+ else if ( func == KMSearchRule::FuncIsNotInAddressbook )
+ return i18n( "is not in address book" );
+ else
+ return currentValue( valueStack, func );
+ }
+
+ //---------------------------------------------------------------------------
+
+ bool TextRuleWidgetHandler::handlesField( const QCString & ) const
+ {
+ return true; // we handle all fields (as fallback)
+ }
+
+ //---------------------------------------------------------------------------
+
+ void TextRuleWidgetHandler::reset( QWidgetStack *functionStack,
+ QWidgetStack *valueStack ) const
+ {
+ // reset the function combo box
+ QComboBox *funcCombo =
+ dynamic_cast<QComboBox*>( functionStack->child( "textRuleFuncCombo",
+ 0, false ) );
+ if ( funcCombo ) {
+ funcCombo->blockSignals( true );
+ funcCombo->setCurrentItem( 0 );
+ funcCombo->blockSignals( false );
+ }
+
+ // reset the value widget
+ RegExpLineEdit *lineEdit =
+ dynamic_cast<RegExpLineEdit*>( valueStack->child( "regExpLineEdit",
+ 0, false ) );
+ if ( lineEdit ) {
+ lineEdit->blockSignals( true );
+ lineEdit->clear();
+ lineEdit->blockSignals( false );
+ lineEdit->showEditButton( false );
+ valueStack->raiseWidget( lineEdit );
+ }
+
+ QComboBox *combo =
+ dynamic_cast<QComboBox*>( valueStack->child( "categoryCombo",
+ 0, false ) );
+ if (combo) {
+ combo->blockSignals( true );
+ combo->setCurrentItem( 0 );
+ combo->blockSignals( false );
+ }
+ }
+
+ //---------------------------------------------------------------------------
+
+ bool TextRuleWidgetHandler::setRule( QWidgetStack *functionStack,
+ QWidgetStack *valueStack,
+ const KMSearchRule *rule ) const
+ {
+ if ( !rule ) {
+ reset( functionStack, valueStack );
+ return false;
+ }
+
+ const KMSearchRule::Function func = rule->function();
+ int i = 0;
+ for ( ; i < TextFunctionCount; ++i )
+ if ( func == TextFunctions[i].id )
+ break;
+ QComboBox *funcCombo =
+ dynamic_cast<QComboBox*>( functionStack->child( "textRuleFuncCombo",
+ 0, false ) );
+ if ( funcCombo ) {
+ funcCombo->blockSignals( true );
+ if ( i < TextFunctionCount )
+ funcCombo->setCurrentItem( i );
+ else {
+ kdDebug(5006) << "TextRuleWidgetHandler::setRule( "
+ << rule->asString()
+ << " ): unhandled function" << endl;
+ funcCombo->setCurrentItem( 0 );
+ }
+ funcCombo->blockSignals( false );
+ functionStack->raiseWidget( funcCombo );
+ }
+
+ if ( func == KMSearchRule::FuncIsInAddressbook ||
+ func == KMSearchRule::FuncIsNotInAddressbook ) {
+ QWidget *w =
+ static_cast<QWidget*>( valueStack->child( "textRuleValueHider",
+ 0, false ) );
+ valueStack->raiseWidget( w );
+ }
+ else if ( func == KMSearchRule::FuncIsInCategory ||
+ func == KMSearchRule::FuncIsNotInCategory) {
+ QComboBox *combo =
+ static_cast<QComboBox*>( valueStack->child( "categoryCombo",
+ 0, false ) );
+ combo->blockSignals( true );
+ for ( i = 0; i < combo->count(); ++i )
+ if ( rule->contents() == combo->text( i ) ) {
+ combo->setCurrentItem( i );
+ break;
+ }
+ if ( i == combo->count() )
+ combo->setCurrentItem( 0 );
+
+ combo->blockSignals( false );
+ valueStack->raiseWidget( combo );
+ }
+ else {
+ RegExpLineEdit *lineEdit =
+ dynamic_cast<RegExpLineEdit*>( valueStack->child( "regExpLineEdit",
+ 0, false ) );
+ if ( lineEdit ) {
+ lineEdit->blockSignals( true );
+ lineEdit->setText( rule->contents() );
+ lineEdit->blockSignals( false );
+ lineEdit->showEditButton( func == KMSearchRule::FuncRegExp ||
+ func == KMSearchRule::FuncNotRegExp );
+ valueStack->raiseWidget( lineEdit );
+ }
+ }
+ return true;
+ }
+
+
+ //---------------------------------------------------------------------------
+
+ bool TextRuleWidgetHandler::update( const QCString &,
+ QWidgetStack *functionStack,
+ QWidgetStack *valueStack ) const
+ {
+ // raise the correct function widget
+ functionStack->raiseWidget(
+ static_cast<QWidget*>( functionStack->child( "textRuleFuncCombo",
+ 0, false ) ) );
+
+ // raise the correct value widget
+ KMSearchRule::Function func = currentFunction( functionStack );
+ if ( func == KMSearchRule::FuncIsInAddressbook ||
+ func == KMSearchRule::FuncIsNotInAddressbook ) {
+ valueStack->raiseWidget(
+ static_cast<QWidget*>( valueStack->child( "textRuleValueHider",
+ 0, false ) ) );
+ }
+ else if ( func == KMSearchRule::FuncIsInCategory ||
+ func == KMSearchRule::FuncIsNotInCategory) {
+ valueStack->raiseWidget(
+ static_cast<QWidget*>( valueStack->child( "categoryCombo",
+ 0, false ) ) );
+ }
+ else {
+ RegExpLineEdit *lineEdit =
+ dynamic_cast<RegExpLineEdit*>( valueStack->child( "regExpLineEdit",
+ 0, false ) );
+ if ( lineEdit ) {
+ lineEdit->showEditButton( func == KMSearchRule::FuncRegExp ||
+ func == KMSearchRule::FuncNotRegExp );
+ valueStack->raiseWidget( lineEdit );
+ }
+ }
+ return true;
+ }
+
+} // anonymous namespace for TextRuleWidgetHandler
+
+
+//=============================================================================
+//
+// class MessageRuleWidgetHandler
+//
+//=============================================================================
+
+namespace {
+ // also see KMSearchRule::matches() and KMSearchRule::Function
+ // if you change the following strings!
+ static const struct {
+ const KMSearchRule::Function id;
+ const char *displayName;
+ } MessageFunctions[] = {
+ { KMSearchRule::FuncContains, I18N_NOOP( "contains" ) },
+ { KMSearchRule::FuncContainsNot, I18N_NOOP( "does not contain" ) },
+ { KMSearchRule::FuncRegExp, I18N_NOOP( "matches regular expr." ) },
+ { KMSearchRule::FuncNotRegExp, I18N_NOOP( "does not match reg. expr." ) },
+ { KMSearchRule::FuncHasAttachment, I18N_NOOP( "has an attachment" ) },
+ { KMSearchRule::FuncHasNoAttachment, I18N_NOOP( "has no attachment" ) },
+ };
+ static const int MessageFunctionCount =
+ sizeof( MessageFunctions ) / sizeof( *MessageFunctions );
+
+ //---------------------------------------------------------------------------
+
+ QWidget * MessageRuleWidgetHandler::createFunctionWidget( int number,
+ QWidgetStack *functionStack,
+ const QObject *receiver ) const
+ {
+ if ( number != 0 )
+ return 0;
+
+ QComboBox *funcCombo = new QComboBox( functionStack, "messageRuleFuncCombo" );
+ for ( int i = 0; i < MessageFunctionCount; ++i ) {
+ funcCombo->insertItem( i18n( MessageFunctions[i].displayName ) );
+ }
+ funcCombo->adjustSize();
+ QObject::connect( funcCombo, SIGNAL( activated( int ) ),
+ receiver, SLOT( slotFunctionChanged() ) );
+ return funcCombo;
+ }
+
+ //---------------------------------------------------------------------------
+
+ QWidget * MessageRuleWidgetHandler::createValueWidget( int number,
+ QWidgetStack *valueStack,
+ const QObject *receiver ) const
+ {
+ if ( number == 0 ) {
+ RegExpLineEdit *lineEdit =
+ new RegExpLineEdit( valueStack, "regExpLineEdit" );
+ QObject::connect( lineEdit, SIGNAL( textChanged( const QString & ) ),
+ receiver, SLOT( slotValueChanged() ) );
+ return lineEdit;
+ }
+
+ // blank QLabel to hide value widget for has-attachment rule
+ if ( number == 1 ) {
+ return new QLabel( valueStack, "textRuleValueHider" );
+ }
+
+ return 0;
+ }
+
+ //---------------------------------------------------------------------------
+
+ KMSearchRule::Function MessageRuleWidgetHandler::currentFunction( const QWidgetStack *functionStack ) const
+ {
+ const QComboBox *funcCombo =
+ dynamic_cast<QComboBox*>( QObject_child_const( functionStack,
+ "messageRuleFuncCombo" ) );
+ // FIXME (Qt >= 4.0): Use the following when QObject::child() is const.
+ // dynamic_cast<QComboBox*>( functionStack->child( "messageRuleFuncCombo",
+ // 0, false ) );
+ if ( funcCombo ) {
+ return MessageFunctions[funcCombo->currentItem()].id;
+ }
+ else
+ kdDebug(5006) << "MessageRuleWidgetHandler::currentFunction: "
+ "messageRuleFuncCombo not found." << endl;
+ return KMSearchRule::FuncNone;
+ }
+
+ //---------------------------------------------------------------------------
+
+ KMSearchRule::Function MessageRuleWidgetHandler::function( const QCString & field,
+ const QWidgetStack *functionStack ) const
+ {
+ if ( !handlesField( field ) )
+ return KMSearchRule::FuncNone;
+
+ return currentFunction( functionStack );
+ }
+
+ //---------------------------------------------------------------------------
+
+ QString MessageRuleWidgetHandler::currentValue( const QWidgetStack *valueStack,
+ KMSearchRule::Function ) const
+ {
+ const RegExpLineEdit *lineEdit =
+ dynamic_cast<RegExpLineEdit*>( QObject_child_const( valueStack,
+ "regExpLineEdit" ) );
+ // FIXME (Qt >= 4.0): Use the following when QObject::child() is const.
+ // dynamic_cast<RegExpLineEdit*>( valueStack->child( "regExpLineEdit",
+ // 0, false ) );
+ if ( lineEdit ) {
+ return lineEdit->text();
+ }
+ else
+ kdDebug(5006) << "MessageRuleWidgetHandler::currentValue: "
+ "regExpLineEdit not found." << endl;
+
+ return QString::null;
+ }
+
+ //---------------------------------------------------------------------------
+
+ QString MessageRuleWidgetHandler::value( const QCString & field,
+ const QWidgetStack *functionStack,
+ const QWidgetStack *valueStack ) const
+ {
+ if ( !handlesField( field ) )
+ return QString::null;
+
+ KMSearchRule::Function func = currentFunction( functionStack );
+ if ( func == KMSearchRule::FuncHasAttachment )
+ return "has an attachment"; // just a non-empty dummy value
+ else if ( func == KMSearchRule::FuncHasNoAttachment )
+ return "has no attachment"; // just a non-empty dummy value
+ else
+ return currentValue( valueStack, func );
+ }
+
+ //---------------------------------------------------------------------------
+
+ QString MessageRuleWidgetHandler::prettyValue( const QCString & field,
+ const QWidgetStack *functionStack,
+ const QWidgetStack *valueStack ) const
+ {
+ if ( !handlesField( field ) )
+ return QString::null;
+
+ KMSearchRule::Function func = currentFunction( functionStack );
+ if ( func == KMSearchRule::FuncHasAttachment )
+ return i18n( "has an attachment" );
+ else if ( func == KMSearchRule::FuncHasNoAttachment )
+ return i18n( "has no attachment" );
+ else
+ return currentValue( valueStack, func );
+ }
+
+ //---------------------------------------------------------------------------
+
+ bool MessageRuleWidgetHandler::handlesField( const QCString & field ) const
+ {
+ return ( field == "<message>" );
+ }
+
+ //---------------------------------------------------------------------------
+
+ void MessageRuleWidgetHandler::reset( QWidgetStack *functionStack,
+ QWidgetStack *valueStack ) const
+ {
+ // reset the function combo box
+ QComboBox *funcCombo =
+ dynamic_cast<QComboBox*>( functionStack->child( "messageRuleFuncCombo",
+ 0, false ) );
+ if ( funcCombo ) {
+ funcCombo->blockSignals( true );
+ funcCombo->setCurrentItem( 0 );
+ funcCombo->blockSignals( false );
+ }
+
+ // reset the value widget
+ RegExpLineEdit *lineEdit =
+ dynamic_cast<RegExpLineEdit*>( valueStack->child( "regExpLineEdit",
+ 0, false ) );
+ if ( lineEdit ) {
+ lineEdit->blockSignals( true );
+ lineEdit->clear();
+ lineEdit->blockSignals( false );
+ lineEdit->showEditButton( false );
+ valueStack->raiseWidget( lineEdit );
+ }
+ }
+
+ //---------------------------------------------------------------------------
+
+ bool MessageRuleWidgetHandler::setRule( QWidgetStack *functionStack,
+ QWidgetStack *valueStack,
+ const KMSearchRule *rule ) const
+ {
+ if ( !rule || !handlesField( rule->field() ) ) {
+ reset( functionStack, valueStack );
+ return false;
+ }
+
+ const KMSearchRule::Function func = rule->function();
+ int i = 0;
+ for ( ; i < MessageFunctionCount; ++i )
+ if ( func == MessageFunctions[i].id )
+ break;
+ QComboBox *funcCombo =
+ dynamic_cast<QComboBox*>( functionStack->child( "messageRuleFuncCombo",
+ 0, false ) );
+ if ( funcCombo ) {
+ funcCombo->blockSignals( true );
+ if ( i < MessageFunctionCount )
+ funcCombo->setCurrentItem( i );
+ else {
+ kdDebug(5006) << "MessageRuleWidgetHandler::setRule( "
+ << rule->asString()
+ << " ): unhandled function" << endl;
+ funcCombo->setCurrentItem( 0 );
+ }
+ funcCombo->blockSignals( false );
+ functionStack->raiseWidget( funcCombo );
+ }
+
+ if ( func == KMSearchRule::FuncHasAttachment ||
+ func == KMSearchRule::FuncHasNoAttachment ) {
+ QWidget *w =
+ static_cast<QWidget*>( valueStack->child( "textRuleValueHider",
+ 0, false ) );
+ valueStack->raiseWidget( w );
+ }
+ else {
+ RegExpLineEdit *lineEdit =
+ dynamic_cast<RegExpLineEdit*>( valueStack->child( "regExpLineEdit",
+ 0, false ) );
+ if ( lineEdit ) {
+ lineEdit->blockSignals( true );
+ lineEdit->setText( rule->contents() );
+ lineEdit->blockSignals( false );
+ lineEdit->showEditButton( func == KMSearchRule::FuncRegExp ||
+ func == KMSearchRule::FuncNotRegExp );
+ valueStack->raiseWidget( lineEdit );
+ }
+ }
+ return true;
+ }
+
+
+ //---------------------------------------------------------------------------
+
+ bool MessageRuleWidgetHandler::update( const QCString & field,
+ QWidgetStack *functionStack,
+ QWidgetStack *valueStack ) const
+ {
+ if ( !handlesField( field ) )
+ return false;
+ // raise the correct function widget
+ functionStack->raiseWidget(
+ static_cast<QWidget*>( functionStack->child( "messageRuleFuncCombo",
+ 0, false ) ) );
+
+ // raise the correct value widget
+ KMSearchRule::Function func = currentFunction( functionStack );
+ if ( func == KMSearchRule::FuncHasAttachment ||
+ func == KMSearchRule::FuncHasNoAttachment ) {
+ QWidget *w =
+ static_cast<QWidget*>( valueStack->child( "textRuleValueHider",
+ 0, false ) );
+ valueStack->raiseWidget( w );
+ }
+ else {
+ RegExpLineEdit *lineEdit =
+ dynamic_cast<RegExpLineEdit*>( valueStack->child( "regExpLineEdit",
+ 0, false ) );
+ if ( lineEdit ) {
+ lineEdit->showEditButton( func == KMSearchRule::FuncRegExp ||
+ func == KMSearchRule::FuncNotRegExp );
+ valueStack->raiseWidget( lineEdit );
+ }
+ }
+ return true;
+ }
+
+} // anonymous namespace for MessageRuleWidgetHandler
+
+
+//=============================================================================
+//
+// class StatusRuleWidgetHandler
+//
+//=============================================================================
+
+namespace {
+ static const struct {
+ const KMSearchRule::Function id;
+ const char *displayName;
+ } StatusFunctions[] = {
+ { KMSearchRule::FuncContains, I18N_NOOP( "is" ) },
+ { KMSearchRule::FuncContainsNot, I18N_NOOP( "is not" ) }
+ };
+ static const int StatusFunctionCount =
+ sizeof( StatusFunctions ) / sizeof( *StatusFunctions );
+
+ //---------------------------------------------------------------------------
+
+ QWidget * StatusRuleWidgetHandler::createFunctionWidget( int number,
+ QWidgetStack *functionStack,
+ const QObject *receiver ) const
+ {
+ if ( number != 0 )
+ return 0;
+
+ QComboBox *funcCombo = new QComboBox( functionStack,
+ "statusRuleFuncCombo" );
+ for ( int i = 0; i < StatusFunctionCount; ++i ) {
+ funcCombo->insertItem( i18n( StatusFunctions[i].displayName ) );
+ }
+ funcCombo->adjustSize();
+ QObject::connect( funcCombo, SIGNAL( activated( int ) ),
+ receiver, SLOT( slotFunctionChanged() ) );
+ return funcCombo;
+ }
+
+ //---------------------------------------------------------------------------
+
+ QWidget * StatusRuleWidgetHandler::createValueWidget( int number,
+ QWidgetStack *valueStack,
+ const QObject *receiver ) const
+ {
+ if ( number != 0 )
+ return 0;
+
+ QComboBox *statusCombo = new QComboBox( valueStack,
+ "statusRuleValueCombo" );
+ for ( int i = 0; i < KMail::StatusValueCountWithoutHidden; ++i ) {
+ statusCombo->insertItem( UserIcon( KMail::StatusValues[ i ].icon ), i18n( KMail::StatusValues[ i ].text ) );
+ }
+ statusCombo->adjustSize();
+ QObject::connect( statusCombo, SIGNAL( activated( int ) ),
+ receiver, SLOT( slotValueChanged() ) );
+ return statusCombo;
+ }
+
+ //---------------------------------------------------------------------------
+
+ KMSearchRule::Function StatusRuleWidgetHandler::currentFunction( const QWidgetStack *functionStack ) const
+ {
+ const QComboBox *funcCombo =
+ dynamic_cast<QComboBox*>( QObject_child_const( functionStack,
+ "statusRuleFuncCombo" ) );
+ // FIXME (Qt >= 4.0): Use the following when QObject::child() is const.
+ // dynamic_cast<QComboBox*>( functionStack->child( "statusRuleFuncCombo",
+ // 0, false ) );
+ if ( funcCombo ) {
+ return StatusFunctions[funcCombo->currentItem()].id;
+ }
+ else
+ kdDebug(5006) << "StatusRuleWidgetHandler::currentFunction: "
+ "statusRuleFuncCombo not found." << endl;
+ return KMSearchRule::FuncNone;
+ }
+
+ //---------------------------------------------------------------------------
+
+ KMSearchRule::Function StatusRuleWidgetHandler::function( const QCString & field,
+ const QWidgetStack *functionStack ) const
+ {
+ if ( !handlesField( field ) )
+ return KMSearchRule::FuncNone;
+
+ return currentFunction( functionStack );
+ }
+
+ //---------------------------------------------------------------------------
+
+ int StatusRuleWidgetHandler::currentStatusValue( const QWidgetStack *valueStack ) const
+ {
+ const QComboBox *statusCombo =
+ dynamic_cast<QComboBox*>( QObject_child_const( valueStack,
+ "statusRuleValueCombo" ) );
+ // FIXME (Qt >= 4.0): Use the following when QObject::child() is const.
+ // dynamic_cast<QComboBox*>( valueStack->child( "statusRuleValueCombo",
+ // 0, false ) );
+ if ( statusCombo ) {
+ return statusCombo->currentItem();
+ }
+ else
+ kdDebug(5006) << "StatusRuleWidgetHandler::currentStatusValue: "
+ "statusRuleValueCombo not found." << endl;
+ return -1;
+ }
+
+ //---------------------------------------------------------------------------
+
+ QString StatusRuleWidgetHandler::value( const QCString & field,
+ const QWidgetStack *,
+ const QWidgetStack *valueStack ) const
+ {
+ if ( !handlesField( field ) )
+ return QString::null;
+
+ const int status = currentStatusValue( valueStack );
+ if ( status != -1 )
+ return QString::fromLatin1( KMail::StatusValues[ status ].text );
+ else
+ return QString::null;
+ }
+
+ //---------------------------------------------------------------------------
+
+ QString StatusRuleWidgetHandler::prettyValue( const QCString & field,
+ const QWidgetStack *,
+ const QWidgetStack *valueStack ) const
+ {
+ if ( !handlesField( field ) )
+ return QString::null;
+
+ const int status = currentStatusValue( valueStack );
+ if ( status != -1 )
+ return i18n( KMail::StatusValues[ status ].text );
+ else
+ return QString::null;
+ }
+
+ //---------------------------------------------------------------------------
+
+ bool StatusRuleWidgetHandler::handlesField( const QCString & field ) const
+ {
+ return ( field == "<status>" );
+ }
+
+ //---------------------------------------------------------------------------
+
+ void StatusRuleWidgetHandler::reset( QWidgetStack *functionStack,
+ QWidgetStack *valueStack ) const
+ {
+ // reset the function combo box
+ QComboBox *funcCombo =
+ dynamic_cast<QComboBox*>( functionStack->child( "statusRuleFuncCombo",
+ 0, false ) );
+ if ( funcCombo ) {
+ funcCombo->blockSignals( true );
+ funcCombo->setCurrentItem( 0 );
+ funcCombo->blockSignals( false );
+ }
+
+ // reset the status value combo box
+ QComboBox *statusCombo =
+ dynamic_cast<QComboBox*>( valueStack->child( "statusRuleValueCombo",
+ 0, false ) );
+ if ( statusCombo ) {
+ statusCombo->blockSignals( true );
+ statusCombo->setCurrentItem( 0 );
+ statusCombo->blockSignals( false );
+ }
+ }
+
+ //---------------------------------------------------------------------------
+
+ bool StatusRuleWidgetHandler::setRule( QWidgetStack *functionStack,
+ QWidgetStack *valueStack,
+ const KMSearchRule *rule ) const
+ {
+ if ( !rule || !handlesField( rule->field() ) ) {
+ reset( functionStack, valueStack );
+ return false;
+ }
+
+ // set the function
+ const KMSearchRule::Function func = rule->function();
+ int funcIndex = 0;
+ for ( ; funcIndex < StatusFunctionCount; ++funcIndex )
+ if ( func == StatusFunctions[funcIndex].id )
+ break;
+ QComboBox *funcCombo =
+ dynamic_cast<QComboBox*>( functionStack->child( "statusRuleFuncCombo",
+ 0, false ) );
+ if ( funcCombo ) {
+ funcCombo->blockSignals( true );
+ if ( funcIndex < StatusFunctionCount )
+ funcCombo->setCurrentItem( funcIndex );
+ else {
+ kdDebug(5006) << "StatusRuleWidgetHandler::setRule( "
+ << rule->asString()
+ << " ): unhandled function" << endl;
+ funcCombo->setCurrentItem( 0 );
+ }
+ funcCombo->blockSignals( false );
+ functionStack->raiseWidget( funcCombo );
+ }
+
+ // set the value
+ const QString value = rule->contents();
+ int valueIndex = 0;
+ for ( ; valueIndex < KMail::StatusValueCountWithoutHidden; ++valueIndex )
+ if ( value == QString::fromLatin1(
+ KMail::StatusValues[ valueIndex ].text ) )
+ break;
+ QComboBox *statusCombo =
+ dynamic_cast<QComboBox*>( valueStack->child( "statusRuleValueCombo",
+ 0, false ) );
+ if ( statusCombo ) {
+ statusCombo->blockSignals( true );
+ if ( valueIndex < KMail::StatusValueCountWithoutHidden )
+ statusCombo->setCurrentItem( valueIndex );
+ else {
+ kdDebug(5006) << "StatusRuleWidgetHandler::setRule( "
+ << rule->asString()
+ << " ): unhandled value" << endl;
+ statusCombo->setCurrentItem( 0 );
+ }
+ statusCombo->blockSignals( false );
+ valueStack->raiseWidget( statusCombo );
+ }
+ return true;
+ }
+
+
+ //---------------------------------------------------------------------------
+
+ bool StatusRuleWidgetHandler::update( const QCString &field,
+ QWidgetStack *functionStack,
+ QWidgetStack *valueStack ) const
+ {
+ if ( !handlesField( field ) )
+ return false;
+
+ // raise the correct function widget
+ functionStack->raiseWidget(
+ static_cast<QWidget*>( functionStack->child( "statusRuleFuncCombo",
+ 0, false ) ) );
+
+ // raise the correct value widget
+ valueStack->raiseWidget(
+ static_cast<QWidget*>( valueStack->child( "statusRuleValueCombo",
+ 0, false ) ) );
+ return true;
+ }
+
+} // anonymous namespace for StatusRuleWidgetHandler
+
+
+//=============================================================================
+//
+// class NumericRuleWidgetHandler
+//
+//=============================================================================
+
+namespace {
+ static const struct {
+ const KMSearchRule::Function id;
+ const char *displayName;
+ } NumericFunctions[] = {
+ { KMSearchRule::FuncEquals, I18N_NOOP( "is equal to" ) },
+ { KMSearchRule::FuncNotEqual, I18N_NOOP( "is not equal to" ) },
+ { KMSearchRule::FuncIsGreater, I18N_NOOP( "is greater than" ) },
+ { KMSearchRule::FuncIsLessOrEqual, I18N_NOOP( "is less than or equal to" ) },
+ { KMSearchRule::FuncIsLess, I18N_NOOP( "is less than" ) },
+ { KMSearchRule::FuncIsGreaterOrEqual, I18N_NOOP( "is greater than or equal to" ) }
+ };
+ static const int NumericFunctionCount =
+ sizeof( NumericFunctions ) / sizeof( *NumericFunctions );
+
+ //---------------------------------------------------------------------------
+
+ QWidget * NumericRuleWidgetHandler::createFunctionWidget( int number,
+ QWidgetStack *functionStack,
+ const QObject *receiver ) const
+ {
+ if ( number != 0 )
+ return 0;
+
+ QComboBox *funcCombo = new QComboBox( functionStack,
+ "numericRuleFuncCombo" );
+ for ( int i = 0; i < NumericFunctionCount; ++i ) {
+ funcCombo->insertItem( i18n( NumericFunctions[i].displayName ) );
+ }
+ funcCombo->adjustSize();
+ QObject::connect( funcCombo, SIGNAL( activated( int ) ),
+ receiver, SLOT( slotFunctionChanged() ) );
+ return funcCombo;
+ }
+
+ //---------------------------------------------------------------------------
+
+ QWidget * NumericRuleWidgetHandler::createValueWidget( int number,
+ QWidgetStack *valueStack,
+ const QObject *receiver ) const
+ {
+ if ( number != 0 )
+ return 0;
+
+ KIntNumInput *numInput = new KIntNumInput( valueStack, "KIntNumInput" );
+ QObject::connect( numInput, SIGNAL( valueChanged( int ) ),
+ receiver, SLOT( slotValueChanged() ) );
+ return numInput;
+ }
+
+ //---------------------------------------------------------------------------
+
+ KMSearchRule::Function NumericRuleWidgetHandler::currentFunction( const QWidgetStack *functionStack ) const
+ {
+ const QComboBox *funcCombo =
+ dynamic_cast<QComboBox*>( QObject_child_const( functionStack,
+ "numericRuleFuncCombo" ) );
+ // FIXME (Qt >= 4.0): Use the following when QObject::child() is const.
+ // dynamic_cast<QComboBox*>( functionStack->child( "numericRuleFuncCombo",
+ // 0, false ) );
+ if ( funcCombo ) {
+ return NumericFunctions[funcCombo->currentItem()].id;
+ }
+ else
+ kdDebug(5006) << "NumericRuleWidgetHandler::currentFunction: "
+ "numericRuleFuncCombo not found." << endl;
+ return KMSearchRule::FuncNone;
+ }
+
+ //---------------------------------------------------------------------------
+
+ KMSearchRule::Function NumericRuleWidgetHandler::function( const QCString & field,
+ const QWidgetStack *functionStack ) const
+ {
+ if ( !handlesField( field ) )
+ return KMSearchRule::FuncNone;
+
+ return currentFunction( functionStack );
+ }
+
+ //---------------------------------------------------------------------------
+
+ QString NumericRuleWidgetHandler::currentValue( const QWidgetStack *valueStack ) const
+ {
+ const KIntNumInput *numInput =
+ dynamic_cast<KIntNumInput*>( QObject_child_const( valueStack,
+ "KIntNumInput" ) );
+ // FIXME (Qt >= 4.0): Use the following when QObject::child() is const.
+ // dynamic_cast<KIntNumInput*>( valueStack->child( "KIntNumInput",
+ // 0, false ) );
+ if ( numInput ) {
+ return QString::number( numInput->value() );
+ }
+ else
+ kdDebug(5006) << "NumericRuleWidgetHandler::currentValue: "
+ "KIntNumInput not found." << endl;
+ return QString::null;
+ }
+
+ //---------------------------------------------------------------------------
+
+ QString NumericRuleWidgetHandler::value( const QCString & field,
+ const QWidgetStack *,
+ const QWidgetStack *valueStack ) const
+ {
+ if ( !handlesField( field ) )
+ return QString::null;
+
+ return currentValue( valueStack );
+ }
+
+ //---------------------------------------------------------------------------
+
+ QString NumericRuleWidgetHandler::prettyValue( const QCString & field,
+ const QWidgetStack *,
+ const QWidgetStack *valueStack ) const
+ {
+ if ( !handlesField( field ) )
+ return QString::null;
+
+ return currentValue( valueStack );
+ }
+
+ //---------------------------------------------------------------------------
+
+ bool NumericRuleWidgetHandler::handlesField( const QCString & field ) const
+ {
+ return ( field == "<size>" || field == "<age in days>" );
+ }
+
+ //---------------------------------------------------------------------------
+
+ void NumericRuleWidgetHandler::reset( QWidgetStack *functionStack,
+ QWidgetStack *valueStack ) const
+ {
+ // reset the function combo box
+ QComboBox *funcCombo =
+ dynamic_cast<QComboBox*>( functionStack->child( "numericRuleFuncCombo",
+ 0, false ) );
+ if ( funcCombo ) {
+ funcCombo->blockSignals( true );
+ funcCombo->setCurrentItem( 0 );
+ funcCombo->blockSignals( false );
+ }
+
+ // reset the value widget
+ KIntNumInput *numInput =
+ dynamic_cast<KIntNumInput*>( valueStack->child( "KIntNumInput",
+ 0, false ) );
+ if ( numInput ) {
+ numInput->blockSignals( true );
+ numInput->setValue( 0 );
+ numInput->blockSignals( false );
+ }
+ }
+
+ //---------------------------------------------------------------------------
+
+ void initNumInput( KIntNumInput *numInput, const QCString &field )
+ {
+ if ( field == "<size>" ) {
+ numInput->setMinValue( 0 );
+ numInput->setSuffix( i18n( " bytes" ) );
+ }
+ else {
+ numInput->setMinValue( -10000 );
+ numInput->setSuffix( i18n( " days" ) );
+ }
+ }
+
+ //---------------------------------------------------------------------------
+
+ bool NumericRuleWidgetHandler::setRule( QWidgetStack *functionStack,
+ QWidgetStack *valueStack,
+ const KMSearchRule *rule ) const
+ {
+ if ( !rule || !handlesField( rule->field() ) ) {
+ reset( functionStack, valueStack );
+ return false;
+ }
+
+ // set the function
+ const KMSearchRule::Function func = rule->function();
+ int funcIndex = 0;
+ for ( ; funcIndex < NumericFunctionCount; ++funcIndex )
+ if ( func == NumericFunctions[funcIndex].id )
+ break;
+ QComboBox *funcCombo =
+ dynamic_cast<QComboBox*>( functionStack->child( "numericRuleFuncCombo",
+ 0, false ) );
+ if ( funcCombo ) {
+ funcCombo->blockSignals( true );
+ if ( funcIndex < NumericFunctionCount )
+ funcCombo->setCurrentItem( funcIndex );
+ else {
+ kdDebug(5006) << "NumericRuleWidgetHandler::setRule( "
+ << rule->asString()
+ << " ): unhandled function" << endl;
+ funcCombo->setCurrentItem( 0 );
+ }
+ funcCombo->blockSignals( false );
+ functionStack->raiseWidget( funcCombo );
+ }
+
+ // set the value
+ bool ok;
+ int value = rule->contents().toInt( &ok );
+ if ( !ok )
+ value = 0;
+ KIntNumInput *numInput =
+ dynamic_cast<KIntNumInput*>( valueStack->child( "KIntNumInput",
+ 0, false ) );
+ if ( numInput ) {
+ initNumInput( numInput, rule->field() );
+ numInput->blockSignals( true );
+ numInput->setValue( value );
+ numInput->blockSignals( false );
+ valueStack->raiseWidget( numInput );
+ }
+ return true;
+ }
+
+
+ //---------------------------------------------------------------------------
+
+ bool NumericRuleWidgetHandler::update( const QCString &field,
+ QWidgetStack *functionStack,
+ QWidgetStack *valueStack ) const
+ {
+ if ( !handlesField( field ) )
+ return false;
+
+ // raise the correct function widget
+ functionStack->raiseWidget(
+ static_cast<QWidget*>( functionStack->child( "numericRuleFuncCombo",
+ 0, false ) ) );
+
+ // raise the correct value widget
+ KIntNumInput *numInput =
+ dynamic_cast<KIntNumInput*>( valueStack->child( "KIntNumInput",
+ 0, false ) );
+ if ( numInput ) {
+ initNumInput( numInput, field );
+ valueStack->raiseWidget( numInput );
+ }
+ return true;
+ }
+
+} // anonymous namespace for NumericRuleWidgetHandler
+
diff --git a/kmail/rulewidgethandlermanager.h b/kmail/rulewidgethandlermanager.h
new file mode 100644
index 00000000..eee848c6
--- /dev/null
+++ b/kmail/rulewidgethandlermanager.h
@@ -0,0 +1,100 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ rulewidgethandlermanager.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2004 Ingo Kloecker <kloecker@kde.org>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_RULEWIDGETHANDLERMANAGER_H__
+#define __KMAIL_RULEWIDGETHANDLERMANAGER_H__
+
+#include "kmsearchpattern.h"
+
+#include <qvaluevector.h>
+
+class QObject;
+class QCString;
+class QString;
+class QWidgetStack;
+
+namespace KMail {
+
+ class RuleWidgetHandler;
+
+ /**
+ * @short Singleton to manage the list of RuleWidgetHandlers
+ */
+ class RuleWidgetHandlerManager {
+ static RuleWidgetHandlerManager * self;
+
+ RuleWidgetHandlerManager();
+ public:
+ ~RuleWidgetHandlerManager();
+
+ static RuleWidgetHandlerManager * instance() {
+ if ( !self )
+ self = new RuleWidgetHandlerManager();
+ return self;
+ }
+
+ void registerHandler( const RuleWidgetHandler * handler );
+ void unregisterHandler( const RuleWidgetHandler * handler );
+
+ void createWidgets( QWidgetStack *functionStack,
+ QWidgetStack *valueStack,
+ const QObject *receiver ) const;
+ KMSearchRule::Function function( const QCString & field,
+ const QWidgetStack *functionStack ) const;
+ QString value( const QCString & field,
+ const QWidgetStack *functionStack,
+ const QWidgetStack *valueStack ) const;
+ QString prettyValue( const QCString & field,
+ const QWidgetStack *functionStack,
+ const QWidgetStack *valueStack ) const;
+ bool handlesField( const QCString & field,
+ const QWidgetStack *functionStack,
+ const QWidgetStack *valueStack ) const;
+ void reset( QWidgetStack *functionStack,
+ QWidgetStack *valueStack ) const;
+ void setRule( QWidgetStack *functionStack,
+ QWidgetStack *valueStack,
+ const KMSearchRule *rule ) const;
+ void update( const QCString & field,
+ QWidgetStack *functionStack,
+ QWidgetStack *valueStack ) const;
+
+ private:
+ typedef QValueVector<const RuleWidgetHandler*>::const_iterator const_iterator;
+ typedef QValueVector<const RuleWidgetHandler*>::iterator iterator;
+
+ QValueVector<const RuleWidgetHandler*> mHandlers;
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_RULEWIDGETHANDLERMANAGER_H__
diff --git a/kmail/scalix.cpp b/kmail/scalix.cpp
new file mode 100644
index 00000000..aef77ff8
--- /dev/null
+++ b/kmail/scalix.cpp
@@ -0,0 +1,104 @@
+/*
+ * This file is part of KMail.
+ *
+ * Copyright (C) 2007 Trolltech ASA. All rights reserved.
+ *
+ * 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 <qmap.h>
+#include <qstringlist.h>
+
+#include "scalix.h"
+
+#include "kmfolder.h"
+#include "kmfolderdir.h"
+
+using namespace Scalix;
+
+FolderAttributeParser::FolderAttributeParser( const QString &attribute )
+{
+ QStringList parts = QStringList::split( ",", attribute, false );
+
+ for ( uint i = 0; i < parts.count(); ++i ) {
+ if ( parts[i].startsWith( "\\X-SpecialFolder=" ) )
+ mFolderName = parts[i].mid( 17 );
+ else if ( parts[i].startsWith( "\\X-FolderClass=" ) )
+ mFolderClass = parts[i].mid( 15 );
+ }
+}
+
+QString FolderAttributeParser::folderClass() const
+{
+ return mFolderClass;
+}
+
+QString FolderAttributeParser::folderName() const
+{
+ return mFolderName;
+}
+
+KMFolder* Utils::findStandardResourceFolder( KMFolderDir* folderParentDir,
+ KMail::FolderContentsType contentsType,
+ const QStringList &attributes )
+{
+ QMap<int, QString> typeMap;
+ typeMap.insert( KMail::ContentsTypeCalendar, "IPF.Appointment" );
+ typeMap.insert( KMail::ContentsTypeContact, "IPF.Contact" );
+ typeMap.insert( KMail::ContentsTypeNote, "IPF.StickyNote" );
+ typeMap.insert( KMail::ContentsTypeTask, "IPF.Task" );
+
+ if ( !typeMap.contains( contentsType ) )
+ return 0;
+
+ for ( uint i = 0; i < attributes.count(); ++i ) {
+ FolderAttributeParser parser( attributes[ i ] );
+ if ( parser.folderClass() == typeMap[ contentsType ] ) {
+ KMFolderNode* node = folderParentDir->hasNamedFolder( parser.folderName() );
+ if ( node && !node->isDir() )
+ return static_cast<KMFolder*>( node );
+ }
+ }
+
+ return 0;
+}
+
+KMail::FolderContentsType Utils::scalixIdToContentsType( const QString &name )
+{
+ if ( name == "IPF.Appointment" )
+ return KMail::ContentsTypeCalendar;
+ else if ( name == "IPF.Contact" )
+ return KMail::ContentsTypeContact;
+ else if ( name == "IPF.StickyNote" )
+ return KMail::ContentsTypeNote;
+ else if ( name == "IPF.Task" )
+ return KMail::ContentsTypeTask;
+ else
+ return KMail::ContentsTypeMail;
+}
+
+QString Utils::contentsTypeToScalixId( KMail::FolderContentsType type )
+{
+ if ( type == KMail::ContentsTypeCalendar )
+ return "IPF.Appointment";
+ else if ( type == KMail::ContentsTypeContact )
+ return "IPF.Contact";
+ else if ( type == KMail::ContentsTypeNote )
+ return "IPF.StickyNote";
+ else if ( type == KMail::ContentsTypeTask )
+ return "IPF.Task";
+ else
+ return "IPF.Note";
+}
diff --git a/kmail/scalix.h b/kmail/scalix.h
new file mode 100644
index 00000000..cb202cff
--- /dev/null
+++ b/kmail/scalix.h
@@ -0,0 +1,83 @@
+/*
+ * This file is part of KMail.
+ *
+ * Copyright (C) 2007 Trolltech ASA. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * This file contains helper classes for Scalix groupware support.
+ * As the storage system of Scalix is quite similiar to Kolab we reuse some of
+ * the exsiting code and replace other code by our own.
+ *
+ * Differences between Kolab and Scalix:
+ *
+ * Groupware folder:
+ * Kolab marks the groupware folders (e.g. Contacts, Calendar etc.) by setting special
+ * annotations. Scalix however passes the information, which folder to treat as a
+ * special groupware folder, via custom IMAP attributes in the LIST result of the
+ * toplevel folder.
+ *
+ * Creating groupware folders:
+ * Kolab creates a normal folder and marks it as groupware folder by setting the right
+ * annotation. Scalix however has a special IMAP command 'X-CREATE-SPECIAL <type> <name>'
+ * to create a new groupware folder.
+ */
+
+#ifndef SCALIX_H
+#define SCALIX_H
+
+#include <qstring.h>
+
+#include "kmfoldertype.h"
+
+class KMFolder;
+class KMFolderDir;
+
+namespace Scalix {
+
+/**
+ * This class takes a folder attribute string as argument and provides access to the single
+ * parts.
+ */
+class FolderAttributeParser
+{
+ public:
+ FolderAttributeParser( const QString &attribute );
+
+ QString folderClass() const;
+ QString folderName() const;
+
+ private:
+ QString mFolderClass;
+ QString mFolderName;
+};
+
+class Utils
+{
+ public:
+ static KMFolder* findStandardResourceFolder( KMFolderDir* folderParentDir,
+ KMail::FolderContentsType contentsType,
+ const QStringList &attributes );
+
+ static KMail::FolderContentsType scalixIdToContentsType( const QString &name );
+
+ static QString contentsTypeToScalixId( KMail::FolderContentsType type );
+};
+
+}
+
+#endif
diff --git a/kmail/searchjob.cpp b/kmail/searchjob.cpp
new file mode 100644
index 00000000..dde86495
--- /dev/null
+++ b/kmail/searchjob.cpp
@@ -0,0 +1,454 @@
+/*
+ * Copyright (c) 2004 Carsten Burghardt <burghardt@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+
+#include "searchjob.h"
+#include "kmfolderimap.h"
+#include "imapaccountbase.h"
+#include "kmsearchpattern.h"
+#include "kmfolder.h"
+#include "imapjob.h"
+#include "kmmsgdict.h"
+
+#include <progressmanager.h>
+using KPIM::ProgressItem;
+using KPIM::ProgressManager;
+
+#include <kdebug.h>
+#include <kurl.h>
+#include <kio/scheduler.h>
+#include <kio/job.h>
+#include <kio/global.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+
+#include <qstylesheet.h>
+
+namespace KMail {
+
+SearchJob::SearchJob( KMFolderImap* folder, ImapAccountBase* account,
+ const KMSearchPattern* pattern, Q_UINT32 serNum )
+ : FolderJob( 0, tOther, (folder ? folder->folder() : 0) ),
+ mFolder( folder ), mAccount( account ), mSearchPattern( pattern ),
+ mSerNum( serNum ), mRemainingMsgs( 0 ), mProgress( 0 ),
+ mUngetCurrentMsg( false )
+{
+}
+
+SearchJob::~SearchJob()
+{
+}
+
+void SearchJob::execute()
+{
+ if ( mSerNum == 0 )
+ {
+ searchCompleteFolder();
+ } else {
+ searchSingleMessage();
+ }
+}
+
+//-----------------------------------------------------------------------------
+void SearchJob::searchCompleteFolder()
+{
+ // generate imap search command and save local search patterns
+ QString searchString = searchStringFromPattern( mSearchPattern );
+
+ if ( searchString.isEmpty() ) // skip imap search and download the messages
+ return slotSearchData( 0, QString::null );
+
+ // do the IMAP search
+ KURL url = mAccount->getUrl();
+ url.setPath( mFolder->imapPath() + ";SECTION=" + searchString );
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+ stream << (int) 'E' << url;
+ KIO::SimpleJob *job = KIO::special( url, packedArgs, false );
+ if ( mFolder->imapPath() != QString( "/" ) )
+ {
+ KIO::Scheduler::assignJobToSlave( mAccount->slave(), job );
+ connect( job, SIGNAL( infoMessage( KIO::Job*, const QString& ) ),
+ SLOT( slotSearchData( KIO::Job*, const QString& ) ) );
+ connect( job, SIGNAL( result( KIO::Job * ) ),
+ SLOT( slotSearchResult( KIO::Job * ) ) );
+ }
+ else
+ { // for the "/ folder" of an imap account, searching blocks the kioslave
+ slotSearchData( job, QString() );
+ slotSearchResult( job );
+ }
+}
+
+//-----------------------------------------------------------------------------
+QString SearchJob::searchStringFromPattern( const KMSearchPattern* pattern )
+{
+ QStringList parts;
+ // this is for the search pattern that can only be done local
+ mLocalSearchPattern = new KMSearchPattern();
+ mLocalSearchPattern->setOp( pattern->op() );
+
+ for ( QPtrListIterator<KMSearchRule> it( *pattern ) ; it.current() ; ++it )
+ {
+ // construct an imap search command
+ bool accept = true;
+ QString result;
+ QString field = (*it)->field();
+ // check if the operation is supported
+ if ( (*it)->function() == KMSearchRule::FuncContainsNot ) {
+ result = "NOT ";
+ } else if ( (*it)->function() == KMSearchRule::FuncIsGreater &&
+ (*it)->field() == "<size>" ) {
+ result = "LARGER ";
+ } else if ( (*it)->function() == KMSearchRule::FuncIsLess &&
+ (*it)->field() == "<size>" ) {
+ result = "SMALLER ";
+ } else if ( (*it)->function() != KMSearchRule::FuncContains ) {
+ // can't be handled by imap
+ accept = false;
+ }
+
+ // now see what should be searched
+ if ( (*it)->field() == "<message>" ) {
+ result += "TEXT \"" + (*it)->contents() + "\"";
+ } else if ( (*it)->field() == "<body>" ) {
+ result += "BODY \"" + (*it)->contents() + "\"";
+ } else if ( (*it)->field() == "<recipients>" ) {
+ result += " (OR HEADER To \"" + (*it)->contents() + "\" HEADER Cc \"" +
+ (*it)->contents() + "\" HEADER Bcc \"" + (*it)->contents() + "\")";
+ } else if ( (*it)->field() == "<size>" ) {
+ result += (*it)->contents();
+ } else if ( (*it)->field() == "<age in days>" ||
+ (*it)->field() == "<status>" ||
+ (*it)->field() == "<any header>" ) {
+ accept = false;
+ } else {
+ result += "HEADER "+ field + " \"" + (*it)->contents() + "\"";
+ }
+
+ if ( result.isEmpty() ) {
+ accept = false;
+ }
+
+ if ( accept ) {
+ parts += result;
+ } else {
+ mLocalSearchPattern->append( *it );
+ }
+ }
+
+ QString search;
+ if ( !parts.isEmpty() ) {
+ if ( pattern->op() == KMSearchPattern::OpOr && parts.size() > 1 ) {
+ search = "(OR " + parts.join(" ") + ")";
+ } else {
+ // and's are simply joined
+ search = parts.join(" ");
+ }
+ }
+
+ kdDebug(5006) << k_funcinfo << search << ";localSearch=" << mLocalSearchPattern->asString() << endl;
+ return search;
+}
+
+//-----------------------------------------------------------------------------
+void SearchJob::slotSearchData( KIO::Job* job, const QString& data )
+{
+ if ( job && job->error() ) {
+ // error is handled in slotSearchResult
+ return;
+ }
+
+ if ( mLocalSearchPattern->isEmpty() && data.isEmpty() )
+ {
+ // no local search and the server found nothing
+ QValueList<Q_UINT32> serNums;
+ emit searchDone( serNums, mSearchPattern, true );
+ } else
+ {
+ // remember the uids the server found
+ mImapSearchHits = QStringList::split( " ", data );
+
+ if ( canMapAllUIDs() )
+ {
+ slotSearchFolder();
+ } else
+ {
+ // get the folder to make sure we have all messages
+ connect ( mFolder, SIGNAL( folderComplete( KMFolderImap*, bool ) ),
+ this, SLOT( slotSearchFolder()) );
+ mFolder->getFolder();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+bool SearchJob::canMapAllUIDs()
+{
+ for ( QStringList::Iterator it = mImapSearchHits.begin();
+ it != mImapSearchHits.end(); ++it )
+ {
+ if ( mFolder->serNumForUID( (*it).toULong() ) == 0 )
+ return false;
+ }
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+void SearchJob::slotSearchFolder()
+{
+ disconnect ( mFolder, SIGNAL( folderComplete( KMFolderImap*, bool ) ),
+ this, SLOT( slotSearchFolder()) );
+
+ if ( mLocalSearchPattern->isEmpty() ) {
+ // pure imap search - now get the serial number for the UIDs
+ QValueList<Q_UINT32> serNums;
+ for ( QStringList::Iterator it = mImapSearchHits.begin();
+ it != mImapSearchHits.end(); ++it )
+ {
+ ulong serNum = mFolder->serNumForUID( (*it).toULong() );
+ // we need to check that the local folder does contain a message for this UID.
+ // scenario: server responds with a list of UIDs. While the search was running, filtering or bad juju moved a message locally
+ // serNumForUID will happily return 0 for the missing message, and KMFolderSearch::addSerNum() will fail its assertion.
+ if ( serNum != 0 )
+ serNums.append( serNum );
+ }
+ emit searchDone( serNums, mSearchPattern, true );
+ } else {
+ // we have search patterns that can not be handled by the server
+ mRemainingMsgs = mFolder->count();
+ if ( mRemainingMsgs == 0 ) {
+ emit searchDone( mSearchSerNums, mSearchPattern, true );
+ return;
+ }
+
+ // Let's see if all we need is status, that we can do locally. Optimization.
+ bool needToDownload = needsDownload();
+ if ( needToDownload ) {
+ // so we need to download all messages and check
+ QString question = i18n("To execute your search all messages of the folder %1 "
+ "have to be downloaded from the server. This may take some time. "
+ "Do you want to continue your search?").arg( mFolder->label() );
+ if ( KMessageBox::warningContinueCancel( 0, question,
+ i18n("Continue Search"), i18n("&Search"),
+ "continuedownloadingforsearch" ) != KMessageBox::Continue )
+ {
+ QValueList<Q_UINT32> serNums;
+ emit searchDone( serNums, mSearchPattern, true );
+ return;
+ }
+ }
+ unsigned int numMsgs = mRemainingMsgs;
+ // progress
+ mProgress = ProgressManager::createProgressItem(
+ "ImapSearchDownload" + ProgressManager::getUniqueID(),
+ i18n("Downloading emails from IMAP server"),
+ i18n( "URL: %1" ).arg( QStyleSheet::escape( mFolder->folder()->prettyURL() ) ),
+ true,
+ mAccount->useSSL() || mAccount->useTLS() );
+ mProgress->setTotalItems( numMsgs );
+ connect ( mProgress, SIGNAL( progressItemCanceled( KPIM::ProgressItem*)),
+ this, SLOT( slotAbortSearch( KPIM::ProgressItem* ) ) );
+
+ for ( unsigned int i = 0; i < numMsgs ; ++i ) {
+ KMMessage * msg = mFolder->getMsg( i );
+ if ( needToDownload ) {
+ ImapJob *job = new ImapJob( msg );
+ job->setParentFolder( mFolder );
+ job->setParentProgressItem( mProgress );
+ connect( job, SIGNAL(messageRetrieved(KMMessage*)),
+ this, SLOT(slotSearchMessageArrived(KMMessage*)) );
+ job->start();
+ } else {
+ slotSearchMessageArrived( msg );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void SearchJob::slotSearchMessageArrived( KMMessage* msg )
+{
+ if ( mProgress )
+ {
+ mProgress->incCompletedItems();
+ mProgress->updateProgress();
+ }
+ --mRemainingMsgs;
+ bool matches = false;
+ if ( msg ) { // messageRetrieved(0) is always possible
+ if ( mLocalSearchPattern->op() == KMSearchPattern::OpAnd ) {
+ // imap and local search have to match
+ if ( mLocalSearchPattern->matches( msg ) &&
+ ( mImapSearchHits.isEmpty() ||
+ mImapSearchHits.find( QString::number(msg->UID() ) ) != mImapSearchHits.end() ) ) {
+ Q_UINT32 serNum = msg->getMsgSerNum();
+ mSearchSerNums.append( serNum );
+ matches = true;
+ }
+ } else if ( mLocalSearchPattern->op() == KMSearchPattern::OpOr ) {
+ // imap or local search have to match
+ if ( mLocalSearchPattern->matches( msg ) ||
+ mImapSearchHits.find( QString::number(msg->UID()) ) != mImapSearchHits.end() ) {
+ Q_UINT32 serNum = msg->getMsgSerNum();
+ mSearchSerNums.append( serNum );
+ matches = true;
+ }
+ }
+ int idx = -1;
+ KMFolder * p = 0;
+ KMMsgDict::instance()->getLocation( msg, &p, &idx );
+ if ( idx != -1 && mUngetCurrentMsg )
+ mFolder->unGetMsg( idx );
+ }
+ if ( mSerNum > 0 )
+ {
+ emit searchDone( mSerNum, mSearchPattern, matches );
+ } else {
+ bool complete = ( mRemainingMsgs == 0 );
+ if ( complete && mProgress )
+ {
+ mProgress->setComplete();
+ mProgress = 0;
+ }
+ if ( matches || complete )
+ {
+ emit searchDone( mSearchSerNums, mSearchPattern, complete );
+ mSearchSerNums.clear();
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void SearchJob::slotSearchResult( KIO::Job *job )
+{
+ if ( job->error() )
+ {
+ mAccount->handleJobError( job, i18n("Error while searching.") );
+ if ( mSerNum == 0 )
+ {
+ // folder
+ QValueList<Q_UINT32> serNums;
+ emit searchDone( serNums, mSearchPattern, true );
+ } else {
+ // message
+ emit searchDone( mSerNum, mSearchPattern, false );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void SearchJob::searchSingleMessage()
+{
+ QString searchString = searchStringFromPattern( mSearchPattern );
+ if ( searchString.isEmpty() )
+ {
+ // no imap search
+ slotSearchDataSingleMessage( 0, QString::null );
+ } else
+ {
+ // imap search
+ int idx = -1;
+ KMFolder *aFolder = 0;
+ KMMsgDict::instance()->getLocation( mSerNum, &aFolder, &idx );
+ assert(aFolder && (idx != -1));
+ KMMsgBase *mb = mFolder->getMsgBase( idx );
+
+ // only search for that UID
+ searchString += " UID " + QString::number( mb->UID() );
+ KURL url = mAccount->getUrl();
+ url.setPath( mFolder->imapPath() + ";SECTION=" + searchString );
+ QByteArray packedArgs;
+ QDataStream stream( packedArgs, IO_WriteOnly );
+ stream << (int) 'E' << url;
+ KIO::SimpleJob *job = KIO::special( url, packedArgs, false );
+ KIO::Scheduler::assignJobToSlave(mAccount->slave(), job);
+ connect( job, SIGNAL(infoMessage(KIO::Job*,const QString&)),
+ SLOT(slotSearchDataSingleMessage(KIO::Job*,const QString&)) );
+ connect( job, SIGNAL(result(KIO::Job *)),
+ SLOT(slotSearchResult(KIO::Job *)) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void SearchJob::slotSearchDataSingleMessage( KIO::Job* job, const QString& data )
+{
+ if ( job && job->error() ) {
+ // error is handled in slotSearchResult
+ return;
+ }
+
+ if ( mLocalSearchPattern->isEmpty() ) {
+ // we are done
+ emit searchDone( mSerNum, mSearchPattern, !data.isEmpty() );
+ return;
+ }
+ // remember what the server found
+ mImapSearchHits = QStringList::split( " ", data );
+
+ // add the local search
+ int idx = -1;
+ KMFolder *aFolder = 0;
+ KMMsgDict::instance()->getLocation( mSerNum, &aFolder, &idx );
+ assert(aFolder && (idx != -1));
+ mUngetCurrentMsg = !mFolder->getMsgBase( idx )->isMessage();
+ KMMessage * msg = mFolder->getMsg( idx );
+ if ( needsDownload() ) {
+ ImapJob *job = new ImapJob( msg );
+ job->setParentFolder( mFolder );
+ connect( job, SIGNAL(messageRetrieved(KMMessage*)),
+ this, SLOT(slotSearchMessageArrived(KMMessage*)) );
+ job->start();
+ } else {
+ slotSearchMessageArrived( msg );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void SearchJob::slotAbortSearch( KPIM::ProgressItem* item )
+{
+ if ( item )
+ item->setComplete();
+ mAccount->killAllJobs();
+ QValueList<Q_UINT32> serNums;
+ emit searchDone( serNums, mSearchPattern, true );
+}
+
+//-----------------------------------------------------------------------------
+bool SearchJob::needsDownload()
+{
+ for ( QPtrListIterator<KMSearchRule> it( *mLocalSearchPattern ) ; it.current() ; ++it ) {
+ if ( (*it)->field() != "<status>" ) {
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace KMail
+
+#include "searchjob.moc"
diff --git a/kmail/searchjob.h b/kmail/searchjob.h
new file mode 100644
index 00000000..5a655c62
--- /dev/null
+++ b/kmail/searchjob.h
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2004 Carsten Burghardt <burghardt@kde.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of this program with any edition of
+ * the Qt library by Trolltech AS, Norway (or with modified versions
+ * of Qt that use the same license as Qt), and distribute linked
+ * combinations including the two. You must obey the GNU General
+ * Public License in all respects for all of the code used other than
+ * Qt. If you modify this file, you may extend this exception to
+ * your version of the file, but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from
+ * your version.
+ */
+#ifndef SEARCHJOB_H
+#define SEARCHJOB_H
+
+#include <qstringlist.h>
+#include "folderjob.h"
+
+class KMFolderImap;
+class KMSearchPattern;
+class KURL;
+
+namespace KIO {
+ class Job;
+}
+
+namespace KPIM {
+ class ProgressItem;
+}
+
+namespace KMail {
+
+class ImapAccountBase;
+
+/**
+ * Search job
+ */
+class SearchJob : public FolderJob
+{
+ Q_OBJECT
+public:
+ /**
+ * Creates a new job
+ * @param folder the folder that should be searched
+ * @param account the ImapAccountBase of the folder
+ * @param pattern the search pattern
+ * @param serNum if you specify the serNum only this is checked
+ */
+ SearchJob( KMFolderImap* folder, ImapAccountBase* account,
+ const KMSearchPattern* pattern, Q_UINT32 serNum = 0 );
+
+ virtual ~SearchJob();
+
+ // Execute
+ virtual void execute();
+
+protected:
+ // searches the complete folder with the pattern
+ void searchCompleteFolder();
+
+ // checks a single message with the pattern
+ void searchSingleMessage();
+
+ // creates an imap search command
+ QString searchStringFromPattern( const KMSearchPattern* );
+
+ // returns true if all uids can be mapped to sernums
+ bool canMapAllUIDs();
+
+ // if we need to download messages
+ bool needsDownload();
+
+protected slots:
+ // search the folder
+ // is called when all uids can be mapped to sernums
+ void slotSearchFolder();
+
+ // processes the server answer
+ void slotSearchData( KIO::Job* job, const QString& data );
+
+ // message is downloaded and searched
+ void slotSearchMessageArrived( KMMessage* msg );
+
+ // error handling for all cases
+ void slotSearchResult( KIO::Job *job );
+
+ // imap search result from a single message
+ void slotSearchDataSingleMessage( KIO::Job* job, const QString& data );
+
+ // the user cancelled the search progress
+ void slotAbortSearch( KPIM::ProgressItem* item );
+
+signals:
+ // emitted when a list of matching serial numbers was found
+ void searchDone( QValueList<Q_UINT32>, const KMSearchPattern*, bool complete );
+
+ // emitted when a single message (identified by the serial number) was checked
+ void searchDone( Q_UINT32, const KMSearchPattern*, bool matches );
+
+protected:
+ KMFolderImap* mFolder;
+ ImapAccountBase* mAccount;
+ const KMSearchPattern* mSearchPattern;
+ KMSearchPattern* mLocalSearchPattern;
+ Q_UINT32 mSerNum;
+ // saves the results of the imap search
+ QStringList mImapSearchHits;
+ // collects the serial numbers from imap and local search
+ QValueList<Q_UINT32> mSearchSerNums;
+ // the remaining messages that have to be downloaded for local search
+ uint mRemainingMsgs;
+ // progress item for local searches
+ KPIM::ProgressItem *mProgress;
+ bool mUngetCurrentMsg;
+
+};
+
+} // namespace
+
+#endif /* SEARCHJOB_H */
+
diff --git a/kmail/searchwindow.cpp b/kmail/searchwindow.cpp
new file mode 100644
index 00000000..7b9ef558
--- /dev/null
+++ b/kmail/searchwindow.cpp
@@ -0,0 +1,946 @@
+/*
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ * Copyright (c) 2001 Aaron J. Seigo <aseigo@kde.org>
+ *
+ * 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 <config.h>
+#include "kmcommands.h"
+#include "searchwindow.h"
+#include "kmmainwidget.h"
+#include "kmmsgdict.h"
+#include "kmmsgpart.h"
+#include "kmfolderimap.h"
+#include "kmfoldermgr.h"
+#include "kmfoldersearch.h"
+#include "kmfoldertree.h"
+#include "kmheaders.h"
+#include "kmsearchpatternedit.h"
+#include "kmsearchpattern.h"
+#include "folderrequester.h"
+#include "messagecopyhelper.h"
+#include "textsource.h"
+
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kstatusbar.h>
+#include <kwin.h>
+#include <kconfig.h>
+#include <kstdaction.h>
+#include <kiconloader.h>
+
+#include <qcheckbox.h>
+#include <qlayout.h>
+#include <klineedit.h>
+#include <qpushbutton.h>
+#include <qradiobutton.h>
+#include <qbuttongroup.h>
+#include <qcombobox.h>
+#include <qobjectlist.h> //for mPatternEdit->queryList( 0, "mRuleField" )->first();
+#include <qcursor.h>
+#include <qpopupmenu.h>
+
+#include <maillistdrag.h>
+using namespace KPIM;
+
+#include <mimelib/enum.h>
+#include <mimelib/boyermor.h>
+
+#include <assert.h>
+#include <stdlib.h>
+
+namespace KMail {
+
+const int SearchWindow::MSGID_COLUMN = 4;
+
+// KListView sub-class for dnd support
+class MatchListView : public KListView
+{
+ public:
+ MatchListView( QWidget *parent, SearchWindow* sw, const char* name = 0 ) :
+ KListView( parent, name ),
+ mSearchWindow( sw )
+ {}
+
+ protected:
+ virtual QDragObject* dragObject()
+ {
+ KMMessageList list = mSearchWindow->selectedMessages();
+ MailList mailList;
+ for ( KMMsgBase* msg = list.first(); msg; msg = list.next() ) {
+ if ( !msg )
+ continue;
+ MailSummary mailSummary( msg->getMsgSerNum(), msg->msgIdMD5(),
+ msg->subject(), msg->fromStrip(),
+ msg->toStrip(), msg->date() );
+ mailList.append( mailSummary );
+ }
+ MailListDrag *d = new MailListDrag( mailList, viewport(), new KMTextSource );
+
+ QPixmap pixmap;
+ if( mailList.count() == 1 )
+ pixmap = QPixmap( DesktopIcon("message", KIcon::SizeSmall) );
+ else
+ pixmap = QPixmap( DesktopIcon("kmultiple", KIcon::SizeSmall) );
+
+ d->setPixmap( pixmap );
+ return d;
+ }
+
+ private:
+ SearchWindow* mSearchWindow;
+};
+
+//-----------------------------------------------------------------------------
+SearchWindow::SearchWindow(KMMainWidget* w, const char* name,
+ KMFolder *curFolder, bool modal):
+ KDialogBase(0, name, modal, i18n("Find Messages"),
+ User1 | User2 | Close, User1, false,
+ KGuiItem( i18n("&Search"), "find" ),
+ KStdGuiItem::stop()),
+ mStopped(false),
+ mCloseRequested(false),
+ mSortColumn(0),
+ mSortOrder(Ascending),
+ mFolder(0),
+ mTimer(new QTimer(this, "mTimer")),
+ mLastFocus(0),
+ mKMMainWidget(w)
+{
+#if !KDE_IS_VERSION( 3, 2, 91 )
+ // HACK - KWin keeps all dialogs on top of their mainwindows, but that's probably
+ // wrong (#76026), and should be done only for modals. CVS HEAD should get
+ // proper fix in KWin (l.lunak@kde.org)
+ XDeleteProperty( qt_xdisplay(), winId(), XA_WM_TRANSIENT_FOR );
+#endif
+ KWin::setIcons(winId(), kapp->icon(), kapp->miniIcon());
+
+ KConfig* config = KMKernel::config();
+ config->setGroup("SearchDialog");
+
+ QWidget* searchWidget = new QWidget(this);
+ QVBoxLayout *vbl = new QVBoxLayout( searchWidget, 0, spacingHint(), "kmfs_vbl" );
+
+ QButtonGroup * radioGroup = new QButtonGroup( searchWidget );
+ radioGroup->hide();
+
+ mChkbxAllFolders = new QRadioButton(i18n("Search in &all local folders"), searchWidget);
+ vbl->addWidget( mChkbxAllFolders );
+ radioGroup->insert( mChkbxAllFolders );
+
+ QHBoxLayout *hbl = new QHBoxLayout( vbl, spacingHint(), "kmfs_hbl" );
+ mChkbxSpecificFolders = new QRadioButton(i18n("Search &only in:"), searchWidget);
+ hbl->addWidget(mChkbxSpecificFolders);
+ mChkbxSpecificFolders->setChecked(true);
+ radioGroup->insert( mChkbxSpecificFolders );
+
+ mCbxFolders = new FolderRequester( searchWidget,
+ kmkernel->getKMMainWidget()->folderTree() );
+ mCbxFolders->setMustBeReadWrite( false );
+ mCbxFolders->setFolder(curFolder);
+ hbl->addWidget(mCbxFolders);
+
+ mChkSubFolders = new QCheckBox(i18n("I&nclude sub-folders"), searchWidget);
+ mChkSubFolders->setChecked(true);
+ hbl->addWidget(mChkSubFolders);
+
+ QWidget *spacer = new QWidget( searchWidget, "spacer" );
+ spacer->setMinimumHeight( 2 );
+ vbl->addWidget( spacer );
+
+ mPatternEdit = new KMSearchPatternEdit( "", searchWidget , "spe", false, true );
+ mPatternEdit->setFrameStyle( QFrame::NoFrame | QFrame::Plain );
+ mPatternEdit->setInsideMargin( 0 );
+ mSearchPattern = new KMSearchPattern();
+ KMFolderSearch *searchFolder = 0;
+ if (curFolder)
+ searchFolder = dynamic_cast<KMFolderSearch*>(curFolder->storage());
+ if (searchFolder) {
+ KConfig config(curFolder->location());
+ KMFolder *root = searchFolder->search()->root();
+ config.setGroup("Search Folder");
+ mSearchPattern->readConfig(&config);
+ if (root) {
+ mChkbxSpecificFolders->setChecked(true);
+ mCbxFolders->setFolder(root);
+ mChkSubFolders->setChecked(searchFolder->search()->recursive());
+ } else {
+ mChkbxAllFolders->setChecked(true);
+ }
+ mFolder = searchFolder;
+ }
+ mPatternEdit->setSearchPattern( mSearchPattern );
+ QObjectList *list = mPatternEdit->queryList( 0, "mRuleField" );
+ QObject *object = 0;
+ if ( list )
+ object = list->first();
+ delete list;
+ if (!searchFolder && object && ::qt_cast<QComboBox*>(object))
+ static_cast<QComboBox*>(object)->setCurrentText("Subject");
+
+ vbl->addWidget( mPatternEdit );
+
+ // enable/disable widgets depending on radio buttons:
+ connect( mChkbxSpecificFolders, SIGNAL(toggled(bool)),
+ mCbxFolders, SLOT(setEnabled(bool)) );
+ connect( mChkbxSpecificFolders, SIGNAL(toggled(bool)),
+ mChkSubFolders, SLOT(setEnabled(bool)) );
+ connect( mChkbxAllFolders, SIGNAL(toggled(bool)),
+ this, SLOT(setEnabledSearchButton(bool)) );
+
+ mLbxMatches = new MatchListView(searchWidget, this, "Find Messages");
+
+ /*
+ Default is to sort by date. TODO: Unfortunately this sorts *while*
+ inserting, which looks rather strange - the user cannot read
+ the results so far as they are constantly re-sorted --dnaber
+
+ Sorting is now disabled when a search is started and reenabled
+ when it stops. Items are appended to the list. This not only
+ solves the above problem, but speeds searches with many hits
+ up considerably. - till
+
+ TODO: subclass KListViewItem and do proper (and performant)
+ comapare functions
+ */
+ mLbxMatches->setSorting(2, false);
+ mLbxMatches->setShowSortIndicator(true);
+ mLbxMatches->setAllColumnsShowFocus(true);
+ mLbxMatches->setSelectionModeExt(KListView::Extended);
+ mLbxMatches->addColumn(i18n("Subject"),
+ config->readNumEntry("SubjectWidth", 150));
+ mLbxMatches->addColumn(i18n("Sender/Receiver"),
+ config->readNumEntry("SenderWidth", 120));
+ mLbxMatches->addColumn(i18n("Date"),
+ config->readNumEntry("DateWidth", 120));
+ mLbxMatches->addColumn(i18n("Folder"),
+ config->readNumEntry("FolderWidth", 100));
+
+ mLbxMatches->addColumn(""); // should be hidden
+ mLbxMatches->setColumnWidthMode( MSGID_COLUMN, QListView::Manual );
+ mLbxMatches->setColumnWidth(MSGID_COLUMN, 0);
+ mLbxMatches->header()->setResizeEnabled(false, MSGID_COLUMN);
+
+ mLbxMatches->setDragEnabled( true );
+
+ connect(mLbxMatches, SIGNAL(doubleClicked(QListViewItem *)),
+ this, SLOT(slotShowMsg(QListViewItem *)));
+ connect(mLbxMatches, SIGNAL(currentChanged(QListViewItem *)),
+ this, SLOT(slotCurrentChanged(QListViewItem *)));
+ connect( mLbxMatches, SIGNAL( contextMenuRequested( QListViewItem*, const QPoint &, int )),
+ this, SLOT( slotContextMenuRequested( QListViewItem*, const QPoint &, int )));
+ vbl->addWidget(mLbxMatches);
+
+ QHBoxLayout *hbl2 = new QHBoxLayout( vbl, spacingHint(), "kmfs_hbl2" );
+ mSearchFolderLbl = new QLabel(i18n("Search folder &name:"), searchWidget);
+ hbl2->addWidget(mSearchFolderLbl);
+ mSearchFolderEdt = new KLineEdit(searchWidget);
+ if (searchFolder)
+ mSearchFolderEdt->setText(searchFolder->folder()->name());
+ else
+ mSearchFolderEdt->setText(i18n("Last Search"));
+
+ mSearchFolderLbl->setBuddy(mSearchFolderEdt);
+ hbl2->addWidget(mSearchFolderEdt);
+ mSearchFolderOpenBtn = new QPushButton(i18n("Op&en Search Folder"), searchWidget);
+ mSearchFolderOpenBtn->setEnabled(false);
+ hbl2->addWidget(mSearchFolderOpenBtn);
+ connect( mSearchFolderEdt, SIGNAL( textChanged( const QString &)),
+ this, SLOT( scheduleRename( const QString & )));
+ connect( &mRenameTimer, SIGNAL( timeout() ),
+ this, SLOT( renameSearchFolder() ));
+ connect( mSearchFolderOpenBtn, SIGNAL( clicked() ),
+ this, SLOT( openSearchFolder() ));
+ mSearchResultOpenBtn = new QPushButton(i18n("Open &Message"), searchWidget);
+ mSearchResultOpenBtn->setEnabled(false);
+ hbl2->addWidget(mSearchResultOpenBtn);
+ connect( mSearchResultOpenBtn, SIGNAL( clicked() ),
+ this, SLOT( slotShowSelectedMsg() ));
+ mStatusBar = new KStatusBar(searchWidget);
+ mStatusBar->insertFixedItem(i18n("AMiddleLengthText..."), 0, true);
+ mStatusBar->changeItem(i18n("Ready."), 0);
+ mStatusBar->setItemAlignment(0, AlignLeft | AlignVCenter);
+ mStatusBar->insertItem(QString::null, 1, 1, true);
+ mStatusBar->setItemAlignment(1, AlignLeft | AlignVCenter);
+ vbl->addWidget(mStatusBar);
+
+ int mainWidth = config->readNumEntry("SearchWidgetWidth", 0);
+ int mainHeight = config->readNumEntry("SearchWidgetHeight", 0);
+
+ if (mainWidth || mainHeight)
+ resize(mainWidth, mainHeight);
+
+ setMainWidget(searchWidget);
+ setButtonBoxOrientation(QWidget::Vertical);
+
+ mBtnSearch = actionButton(KDialogBase::User1);
+ mBtnStop = actionButton(KDialogBase::User2);
+ mBtnStop->setEnabled(false);
+
+ connect(this, SIGNAL(user1Clicked()), SLOT(slotSearch()));
+ connect(this, SIGNAL(user2Clicked()), SLOT(slotStop()));
+ connect(this, SIGNAL(finished()), this, SLOT(deleteLater()));
+
+ // give focus to the value field of the first search rule
+ object = mPatternEdit->child( "regExpLineEdit" );
+ if ( object && object->isWidgetType() ) {
+ static_cast<QWidget*>(object)->setFocus();
+ //kdDebug(5006) << "SearchWindow: focus has been given to widget "
+ // << object->name() << endl;
+ }
+ else
+ kdDebug(5006) << "SearchWindow: regExpLineEdit not found" << endl;
+
+ //set up actions
+ KActionCollection *ac = actionCollection();
+ ac->setWidget( this );
+ mReplyAction = new KAction( i18n("&Reply..."), "mail_reply", 0, this,
+ SLOT(slotReplyToMsg()), ac, "search_reply" );
+ mReplyAllAction = new KAction( i18n("Reply to &All..."), "mail_replyall",
+ 0, this, SLOT(slotReplyAllToMsg()),
+ ac, "search_reply_all" );
+ mReplyListAction = new KAction( i18n("Reply to Mailing-&List..."),
+ "mail_replylist", 0, this,
+ SLOT(slotReplyListToMsg()), ac,
+ "search_reply_list" );
+ mForwardActionMenu = new KActionMenu( i18n("Message->","&Forward"),
+ "mail_forward", ac,
+ "search_message_forward" );
+ connect( mForwardActionMenu, SIGNAL(activated()), this,
+ SLOT(slotForwardInlineMsg()) );
+ mForwardAttachedAction = new KAction( i18n("Message->Forward->","As &Attachment..."),
+ "mail_forward", 0, this,
+ SLOT(slotForwardAttachedMsg()), ac,
+ "search_message_forward_as_attachment" );
+ mForwardInlineAction = new KAction( i18n("&Inline..."),
+ "mail_forward", 0, this,
+ SLOT(slotForwardInlineMsg()), ac,
+ "search_message_forward_inline" );
+ if ( GlobalSettings::self()->forwardingInlineByDefault() ) {
+ mForwardActionMenu->insert( mForwardInlineAction );
+ mForwardActionMenu->insert( mForwardAttachedAction );
+ } else {
+ mForwardActionMenu->insert( mForwardAttachedAction );
+ mForwardActionMenu->insert( mForwardInlineAction );
+ }
+
+ mForwardDigestAction = new KAction( i18n("Message->Forward->","As Di&gest..."),
+ "mail_forward", 0, this,
+ SLOT(slotForwardDigestMsg()), ac,
+ "search_message_forward_as_digest" );
+ mForwardActionMenu->insert( mForwardDigestAction );
+ mRedirectAction = new KAction( i18n("Message->Forward->","&Redirect..."),
+ "mail_forward", 0, this,
+ SLOT(slotRedirectMsg()), ac,
+ "search_message_forward_redirect" );
+ mForwardActionMenu->insert( mRedirectAction );
+ mSaveAsAction = KStdAction::saveAs( this, SLOT(slotSaveMsg()), ac, "search_file_save_as" );
+ mSaveAtchAction = new KAction( i18n("Save Attachments..."), "attach", 0,
+ this, SLOT(slotSaveAttachments()), ac, "search_save_attachments" );
+
+ mPrintAction = KStdAction::print( this, SLOT(slotPrintMsg()), ac, "search_print" );
+ mClearAction = new KAction( i18n("Clear Selection"), 0, 0, this,
+ SLOT(slotClearSelection()), ac, "search_clear_selection" );
+
+ mCopyAction = KStdAction::copy( this, SLOT(slotCopyMsgs()), ac, "search_copy_messages" );
+ mCutAction = KStdAction::cut( this, SLOT(slotCutMsgs()), ac, "search_cut_messages" );
+
+ connect(mTimer, SIGNAL(timeout()), this, SLOT(updStatus()));
+ connect(kmkernel->searchFolderMgr(), SIGNAL(folderInvalidated(KMFolder*)),
+ this, SLOT(folderInvalidated(KMFolder*)));
+
+ connect(mCbxFolders, SIGNAL(folderChanged(KMFolder*)),
+ this, SLOT(slotFolderActivated()));
+
+}
+
+//-----------------------------------------------------------------------------
+SearchWindow::~SearchWindow()
+{
+ QValueListIterator<QGuardedPtr<KMFolder> > fit;
+ for ( fit = mFolders.begin(); fit != mFolders.end(); ++fit ) {
+ if (!(*fit))
+ continue;
+ (*fit)->close("searchwindow");
+ }
+
+ KConfig* config = KMKernel::config();
+ config->setGroup("SearchDialog");
+ config->writeEntry("SubjectWidth", mLbxMatches->columnWidth(0));
+ config->writeEntry("SenderWidth", mLbxMatches->columnWidth(1));
+ config->writeEntry("DateWidth", mLbxMatches->columnWidth(2));
+ config->writeEntry("FolderWidth", mLbxMatches->columnWidth(3));
+ config->writeEntry("SearchWidgetWidth", width());
+ config->writeEntry("SearchWidgetHeight", height());
+ config->sync();
+}
+
+void SearchWindow::setEnabledSearchButton(bool)
+{
+ //Make sure that button is enable
+ //Before when we selected a folder == "Local Folder" as that it was not a folder
+ //search button was disable, and when we select "Search in all local folder"
+ //Search button was never enabled :(
+ mBtnSearch->setEnabled( true );
+}
+
+//-----------------------------------------------------------------------------
+void SearchWindow::updStatus(void)
+{
+ QString genMsg, detailMsg, procMsg;
+ int numMatches = 0, numProcessed = 0;
+ KMSearch const *search = (mFolder) ? (mFolder->search()) : 0;
+ QString folderName;
+ if (search) {
+ numMatches = search->foundCount();
+ numProcessed = search->searchCount();
+ folderName = search->currentFolder();
+ }
+
+ if (search && !search->running()) {
+ procMsg = i18n("%n message searched", "%n messages searched",
+ numProcessed);
+ if(!mStopped) {
+ genMsg = i18n("Done.");
+ detailMsg = i18n("%n match in %1", "%n matches in %1",
+ numMatches).arg(procMsg);
+ } else {
+ genMsg = i18n("Search canceled.");
+ detailMsg = i18n("%n match so far in %1", "%n matches so far in %1",
+ numMatches).arg(procMsg);
+ }
+ } else {
+ procMsg = i18n("%n message", "%n messages", numProcessed);
+ genMsg = i18n("%n match", "%n matches", numMatches);
+ detailMsg = i18n("Searching in %1. %2 searched so far")
+ .arg(folderName).arg(procMsg);
+ }
+
+ mStatusBar->changeItem(genMsg, 0);
+ mStatusBar->changeItem(detailMsg, 1);
+}
+
+
+//-----------------------------------------------------------------------------
+void SearchWindow::keyPressEvent(QKeyEvent *evt)
+{
+ KMSearch const *search = (mFolder) ? mFolder->search() : 0;
+ bool searching = (search) ? search->running() : false;
+ if (evt->key() == Key_Escape && searching) {
+ mFolder->stopSearch();
+ return;
+ }
+
+ KDialogBase::keyPressEvent(evt);
+}
+
+
+//-----------------------------------------------------------------------------
+void SearchWindow::slotFolderActivated()
+{
+ mChkbxSpecificFolders->setChecked(true);
+}
+
+//-----------------------------------------------------------------------------
+void SearchWindow::activateFolder(KMFolder *curFolder)
+{
+ mChkbxSpecificFolders->setChecked(true);
+ mCbxFolders->setFolder(curFolder);
+}
+
+//-----------------------------------------------------------------------------
+void SearchWindow::slotSearch()
+{
+ mLastFocus = focusWidget();
+ mBtnSearch->setFocus(); // set focus so we don't miss key event
+
+ mStopped = false;
+ mFetchingInProgress = 0;
+
+ mSearchFolderOpenBtn->setEnabled(true);
+ mBtnSearch->setEnabled(false);
+ mBtnStop->setEnabled(true);
+
+ mLbxMatches->clear();
+
+ mSortColumn = mLbxMatches->sortColumn();
+ mSortOrder = mLbxMatches->sortOrder();
+ mLbxMatches->setSorting(-1);
+ mLbxMatches->setShowSortIndicator(false);
+
+ // If we haven't openend an existing search folder, find or
+ // create one.
+ if (!mFolder) {
+ KMFolderMgr *mgr = kmkernel->searchFolderMgr();
+ if (mSearchFolderEdt->text().isEmpty())
+ mSearchFolderEdt->setText(i18n("Last Search"));
+ QString baseName = mSearchFolderEdt->text();
+ QString fullName = baseName;
+ int count = 0;
+ KMFolder *folder;
+ while ((folder = mgr->find(fullName))) {
+ if (folder->storage()->inherits("KMFolderSearch"))
+ break;
+ fullName = QString("%1 %2").arg(baseName).arg(++count);
+ }
+
+ if (!folder)
+ folder = mgr->createFolder(fullName, false, KMFolderTypeSearch,
+ &mgr->dir());
+
+ mFolder = dynamic_cast<KMFolderSearch*>( folder->storage() );
+ }
+ mFolder->stopSearch();
+ disconnect(mFolder, SIGNAL(msgAdded(int)),
+ this, SLOT(slotAddMsg(int)));
+ disconnect(mFolder, SIGNAL(msgRemoved(KMFolder*, Q_UINT32)),
+ this, SLOT(slotRemoveMsg(KMFolder*, Q_UINT32)));
+ connect(mFolder, SIGNAL(msgAdded(int)),
+ this, SLOT(slotAddMsg(int)));
+ connect(mFolder, SIGNAL(msgRemoved(KMFolder*, Q_UINT32)),
+ this, SLOT(slotRemoveMsg(KMFolder*, Q_UINT32)));
+ mSearchFolderEdt->setEnabled(false);
+ KMSearch *search = new KMSearch();
+ connect(search, SIGNAL(finished(bool)),
+ this, SLOT(searchDone()));
+ if (mChkbxAllFolders->isChecked()) {
+ search->setRecursive(true);
+ } else {
+ search->setRoot(mCbxFolders->folder());
+ search->setRecursive(mChkSubFolders->isChecked());
+ }
+
+ mPatternEdit->updateSearchPattern();
+ KMSearchPattern *searchPattern = new KMSearchPattern();
+ *searchPattern = *mSearchPattern; //deep copy
+ searchPattern->purify();
+ search->setSearchPattern(searchPattern);
+ mFolder->setSearch(search);
+ enableGUI();
+
+ mTimer->start(200);
+}
+
+//-----------------------------------------------------------------------------
+void SearchWindow::searchDone()
+{
+ mTimer->stop();
+ updStatus();
+
+ QTimer::singleShot(0, this, SLOT(enableGUI()));
+ if(mLastFocus)
+ mLastFocus->setFocus();
+ if (mCloseRequested)
+ close();
+
+ mLbxMatches->setSorting(mSortColumn, mSortOrder == Ascending);
+ mLbxMatches->setShowSortIndicator(true);
+
+ mSearchFolderEdt->setEnabled(true);
+}
+
+void SearchWindow::slotAddMsg(int idx)
+{
+ if (!mFolder)
+ return;
+ bool unget = !mFolder->isMessage(idx);
+ KMMessage *msg = mFolder->getMsg(idx);
+ QString from, fName;
+ KMFolder *pFolder = msg->parent();
+ if (!mFolders.contains(pFolder)) {
+ mFolders.append(pFolder);
+ pFolder->open("searchwindow");
+ }
+ if(pFolder->whoField() == "To")
+ from = msg->to();
+ else
+ from = msg->from();
+ if (pFolder->isSystemFolder())
+ fName = i18n(pFolder->name().utf8());
+ else
+ fName = pFolder->name();
+
+ (void)new KListViewItem(mLbxMatches, mLbxMatches->lastItem(),
+ msg->subject(), from, msg->dateIsoStr(),
+ fName,
+ QString::number(mFolder->serNum(idx)));
+ if (unget)
+ mFolder->unGetMsg(idx);
+}
+
+void SearchWindow::slotRemoveMsg(KMFolder *, Q_UINT32 serNum)
+{
+ if (!mFolder)
+ return;
+ QListViewItemIterator it(mLbxMatches);
+ while (it.current()) {
+ QListViewItem *item = *it;
+ if (serNum == (*it)->text(MSGID_COLUMN).toUInt()) {
+ delete item;
+ return;
+ }
+ ++it;
+ }
+}
+
+//-----------------------------------------------------------------------------
+void SearchWindow::slotStop()
+{
+ if (mFolder)
+ mFolder->stopSearch();
+ mStopped = true;
+ mBtnStop->setEnabled(false);
+}
+
+//-----------------------------------------------------------------------------
+void SearchWindow::slotClose()
+{
+ accept();
+}
+
+
+//-----------------------------------------------------------------------------
+void SearchWindow::closeEvent(QCloseEvent *e)
+{
+ if (mFolder && mFolder->search() && mFolder->search()->running()) {
+ mCloseRequested = true;
+ //Cancel search in progress by setting the search folder search to
+ //the null search
+ mFolder->setSearch(new KMSearch());
+ QTimer::singleShot(0, this, SLOT(slotClose()));
+ } else {
+ KDialogBase::closeEvent(e);
+ }
+}
+
+//-----------------------------------------------------------------------------
+void SearchWindow::scheduleRename( const QString &s)
+{
+ if (!s.isEmpty() && s != i18n("Last Search")) {
+ mRenameTimer.start(250, true);
+ mSearchFolderOpenBtn->setEnabled(false);
+ } else {
+ mRenameTimer.stop();
+ mSearchFolderOpenBtn->setEnabled(true);
+ }
+}
+
+//-----------------------------------------------------------------------------
+void SearchWindow::renameSearchFolder()
+{
+ if (mFolder && (mFolder->folder()->name() != mSearchFolderEdt->text())) {
+ int i = 1;
+ QString name = mSearchFolderEdt->text();
+ while (i < 100) {
+ if (!kmkernel->searchFolderMgr()->find( name )) {
+ mFolder->rename( name );
+ kmkernel->searchFolderMgr()->contentsChanged();
+ break;
+ }
+ name.setNum( i );
+ name = mSearchFolderEdt->text() + " " + name;
+ ++i;
+ }
+ }
+ mSearchFolderOpenBtn->setEnabled(true);
+}
+
+void SearchWindow::openSearchFolder()
+{
+ renameSearchFolder();
+ mKMMainWidget->slotSelectFolder( mFolder->folder() );
+ slotClose();
+}
+
+//-----------------------------------------------------------------------------
+void SearchWindow::folderInvalidated(KMFolder *folder)
+{
+ if (folder->storage() == mFolder) {
+ mLbxMatches->clear();
+ if (mFolder->search())
+ connect(mFolder->search(), SIGNAL(finished(bool)),
+ this, SLOT(searchDone()));
+ mTimer->start(200);
+ enableGUI();
+ }
+}
+
+//-----------------------------------------------------------------------------
+bool SearchWindow::slotShowMsg(QListViewItem *item)
+{
+ if(!item)
+ return false;
+
+ KMFolder* folder;
+ int msgIndex;
+ KMMsgDict::instance()->getLocation(item->text(MSGID_COLUMN).toUInt(),
+ &folder, &msgIndex);
+
+ if (!folder || msgIndex < 0)
+ return false;
+
+ mKMMainWidget->slotSelectFolder(folder);
+ KMMessage* message = folder->getMsg(msgIndex);
+ if (!message)
+ return false;
+
+ mKMMainWidget->slotSelectMessage(message);
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+void SearchWindow::slotShowSelectedMsg()
+{
+ slotShowMsg(mLbxMatches->currentItem());
+}
+
+//-----------------------------------------------------------------------------
+void SearchWindow::slotCurrentChanged(QListViewItem *item)
+{
+ mSearchResultOpenBtn->setEnabled(item!=0);
+}
+
+//-----------------------------------------------------------------------------
+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);
+ mChkbxAllFolders->setEnabled(!searching);
+ mChkbxSpecificFolders->setEnabled(!searching);
+ mPatternEdit->setEnabled(!searching);
+ mBtnSearch->setEnabled(!searching);
+ mBtnStop->setEnabled(searching);
+}
+
+
+//-----------------------------------------------------------------------------
+KMMessageList SearchWindow::selectedMessages()
+{
+ KMMessageList msgList;
+ KMFolder* folder = 0;
+ int msgIndex = -1;
+ for (QListViewItemIterator it(mLbxMatches); it.current(); it++)
+ if (it.current()->isSelected()) {
+ KMMsgDict::instance()->getLocation((*it)->text(MSGID_COLUMN).toUInt(),
+ &folder, &msgIndex);
+ if (folder && msgIndex >= 0)
+ msgList.append(folder->getMsgBase(msgIndex));
+ }
+ return msgList;
+}
+
+//-----------------------------------------------------------------------------
+KMMessage* SearchWindow::message()
+{
+ QListViewItem *item = mLbxMatches->currentItem();
+ KMFolder* folder = 0;
+ int msgIndex = -1;
+ if (!item)
+ return 0;
+ KMMsgDict::instance()->getLocation(item->text(MSGID_COLUMN).toUInt(),
+ &folder, &msgIndex);
+ if (!folder || msgIndex < 0)
+ return 0;
+
+ return folder->getMsg(msgIndex);
+}
+
+//-----------------------------------------------------------------------------
+void SearchWindow::moveSelectedToFolder( int menuId )
+{
+ KMFolder *dest = mMenuToFolder[menuId];
+ if (!dest)
+ return;
+
+ KMMessageList msgList = selectedMessages();
+ KMCommand *command = new KMMoveCommand( dest, msgList );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void SearchWindow::copySelectedToFolder( int menuId )
+{
+ KMFolder *dest = mMenuToFolder[menuId];
+ if (!dest)
+ return;
+
+ KMMessageList msgList = selectedMessages();
+ KMCommand *command = new KMCopyCommand( dest, msgList );
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void SearchWindow::updateContextMenuActions()
+{
+ int count = selectedMessages().count();
+ bool single_actions = count == 1;
+ mReplyAction->setEnabled( single_actions );
+ mReplyAllAction->setEnabled( single_actions );
+ mReplyListAction->setEnabled( single_actions );
+ mPrintAction->setEnabled( single_actions );
+ mForwardDigestAction->setEnabled( !single_actions );
+ mRedirectAction->setEnabled( single_actions );
+ mCopyAction->setEnabled( count > 0 );
+ mCutAction->setEnabled( count > 0 );
+}
+
+//-----------------------------------------------------------------------------
+void SearchWindow::slotContextMenuRequested( QListViewItem *lvi, const QPoint &, int )
+{
+ if (!lvi)
+ return;
+ mLbxMatches->setSelected( lvi, true );
+ mLbxMatches->setCurrentItem( lvi );
+ // FIXME is this ever unGetMsg()'d?
+ if (!message())
+ return;
+ QPopupMenu *menu = new QPopupMenu(this);
+ updateContextMenuActions();
+
+ mMenuToFolder.clear();
+ QPopupMenu *msgMoveMenu = new QPopupMenu(menu);
+ mKMMainWidget->folderTree()->folderToPopupMenu( KMFolderTree::MoveMessage,
+ this, &mMenuToFolder, msgMoveMenu );
+ QPopupMenu *msgCopyMenu = new QPopupMenu(menu);
+ mKMMainWidget->folderTree()->folderToPopupMenu( KMFolderTree::CopyMessage,
+ this, &mMenuToFolder, msgCopyMenu );
+
+ // show most used actions
+ mReplyAction->plug(menu);
+ mReplyAllAction->plug(menu);
+ mReplyListAction->plug(menu);
+ mForwardActionMenu->plug(menu);
+ menu->insertSeparator();
+ mCopyAction->plug(menu);
+ mCutAction->plug(menu);
+ menu->insertItem(i18n("&Copy To"), msgCopyMenu);
+ menu->insertItem(i18n("&Move To"), msgMoveMenu);
+ menu->insertSeparator();
+ mSaveAsAction->plug(menu);
+ mSaveAtchAction->plug(menu);
+ mPrintAction->plug(menu);
+ menu->insertSeparator();
+ mClearAction->plug(menu);
+ menu->exec (QCursor::pos(), 0);
+ delete menu;
+}
+
+//-----------------------------------------------------------------------------
+void SearchWindow::slotClearSelection()
+{
+ mLbxMatches->clearSelection();
+}
+
+//-----------------------------------------------------------------------------
+void SearchWindow::slotReplyToMsg()
+{
+ KMCommand *command = new KMReplyToCommand(this, message());
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void SearchWindow::slotReplyAllToMsg()
+{
+ KMCommand *command = new KMReplyToAllCommand(this, message());
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void SearchWindow::slotReplyListToMsg()
+{
+ KMCommand *command = new KMReplyListCommand(this, message());
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void SearchWindow::slotForwardInlineMsg()
+{
+ KMCommand *command = new KMForwardInlineCommand(this, selectedMessages());
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void SearchWindow::slotForwardAttachedMsg()
+{
+ KMCommand *command = new KMForwardAttachedCommand(this, selectedMessages());
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void SearchWindow::slotForwardDigestMsg()
+{
+ KMCommand *command = new KMForwardDigestCommand(this, selectedMessages());
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void SearchWindow::slotRedirectMsg()
+{
+ KMCommand *command = new KMRedirectCommand(this, message());
+ command->start();
+}
+
+//-----------------------------------------------------------------------------
+void SearchWindow::slotSaveMsg()
+{
+ KMSaveMsgCommand *saveCommand = new KMSaveMsgCommand(this,
+ selectedMessages());
+ if (saveCommand->url().isEmpty())
+ delete saveCommand;
+ else
+ saveCommand->start();
+}
+//-----------------------------------------------------------------------------
+void SearchWindow::slotSaveAttachments()
+{
+ KMSaveAttachmentsCommand *saveCommand = new KMSaveAttachmentsCommand(this,
+ selectedMessages());
+ saveCommand->start();
+}
+
+
+//-----------------------------------------------------------------------------
+void SearchWindow::slotPrintMsg()
+{
+ KMCommand *command = new KMPrintCommand(this, message());
+ command->start();
+}
+
+void SearchWindow::slotCopyMsgs()
+{
+ QValueList<Q_UINT32> list = MessageCopyHelper::serNumListFromMsgList( selectedMessages() );
+ mKMMainWidget->headers()->setCopiedMessages( list, false );
+}
+
+void SearchWindow::slotCutMsgs()
+{
+ QValueList<Q_UINT32> list = MessageCopyHelper::serNumListFromMsgList( selectedMessages() );
+ mKMMainWidget->headers()->setCopiedMessages( list, true );
+}
+
+
+void SearchWindow::setSearchPattern( const KMSearchPattern& pattern )
+{
+ *mSearchPattern = pattern;
+ mPatternEdit->setSearchPattern( mSearchPattern );
+}
+
+} // namespace KMail
+#include "searchwindow.moc"
diff --git a/kmail/searchwindow.h b/kmail/searchwindow.h
new file mode 100644
index 00000000..62c6e6ce
--- /dev/null
+++ b/kmail/searchwindow.h
@@ -0,0 +1,195 @@
+/*
+ * kmail: KDE mail client
+ * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
+ * Copyright (c) 2001 Aaron J. Seigo <aseigo@kde.org>
+ *
+ * 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 searchwindow_h
+#define searchwindow_h
+
+#include <qvaluelist.h>
+#include <qptrlist.h>
+#include <qstringlist.h>
+#include <qguardedptr.h>
+#include <qtimer.h>
+
+#include <kdialogbase.h>
+#include <kxmlguiclient.h>
+
+class QCheckBox;
+class QComboBox;
+class QGridLayout;
+class QLabel;
+class QLineEdit;
+class KListView;
+class QListViewItem;
+class QPushButton;
+class QRadioButton;
+class KAction;
+class KActionMenu;
+class KMFolder;
+class KMFolderSearch;
+class KMFolderImap;
+class KMFolderMgr;
+class KMMainWidget;
+class KMMessage;
+class KMSearchPattern;
+class KMSearchPatternEdit;
+class KStatusBar;
+class DwBoyerMoore;
+namespace KMail {
+ class FolderRequester;
+}
+
+typedef QPtrList<KMMsgBase> KMMessageList;
+
+namespace KMail {
+
+ /**
+ * The SearchWindow class provides a dialog for triggering a search on
+ * folders and storing that search as a search folder. It shows the search
+ * results in a listview and allows triggering of operations such as printing
+ * or moving on them.
+ */
+class SearchWindow: public KDialogBase, virtual public KXMLGUIClient
+{
+ Q_OBJECT
+
+public:
+ /**
+ * Creates a new search window.
+ * @param parent The parent widget.
+ * @param name The (widget) name of the dialog.
+ * @param curFolder The folder which will be pre-selected as the base folder
+ * of search operations.
+ * @param modal Whether the dialog is to be shown modal.
+ */
+ SearchWindow( KMMainWidget* parent, const char* name=0,
+ KMFolder *curFolder=0, bool modal=false );
+ virtual ~SearchWindow();
+
+ /**
+ * Changes the base folder for search operations to a different folder.
+ * @param curFolder The folder to use as the new base for searches.
+ */
+ void activateFolder( KMFolder* curFolder );
+
+ /**
+ * Provides access to the list of currently selected message in the listview.
+ * @return The list of currenty selected search result messages.
+ */
+ KMMessageList selectedMessages();
+
+ /**
+ * Provides access to the currently selected message.
+ * @return the currently selected message.
+ */
+ KMMessage* message();
+
+ void setSearchPattern( const KMSearchPattern& pattern );
+
+protected slots:
+ /** Update status line widget. */
+ virtual void updStatus(void);
+
+ virtual void slotClose();
+ virtual void slotSearch();
+ virtual void slotStop();
+ void scheduleRename( const QString &);
+ void renameSearchFolder();
+ void openSearchFolder();
+ void folderInvalidated(KMFolder *);
+ virtual bool slotShowMsg(QListViewItem *);
+ void slotShowSelectedMsg();
+ void slotCurrentChanged(QListViewItem *);
+ virtual void updateContextMenuActions();
+ virtual void slotContextMenuRequested( QListViewItem*, const QPoint &, int );
+ virtual void copySelectedToFolder( int menuId );
+ virtual void moveSelectedToFolder( int menuId );
+ virtual void slotFolderActivated();
+ void slotClearSelection();
+ void slotReplyToMsg();
+ void slotReplyAllToMsg();
+ void slotReplyListToMsg();
+ void slotForwardInlineMsg();
+ void slotForwardAttachedMsg();
+ void slotForwardDigestMsg();
+ void slotRedirectMsg();
+ void slotSaveMsg();
+ void slotSaveAttachments();
+ void slotPrintMsg();
+ void slotCopyMsgs();
+ void slotCutMsgs();
+
+ /** GUI cleanup after search */
+ virtual void searchDone();
+ virtual void slotAddMsg(int idx);
+ virtual void slotRemoveMsg(KMFolder *, Q_UINT32 serNum);
+ void enableGUI();
+
+ void setEnabledSearchButton(bool);
+
+protected:
+
+ /** Reimplemented to react to Escape. */
+ virtual void keyPressEvent(QKeyEvent*);
+
+ /** Reimplemented to stop searching when the window is closed */
+ virtual void closeEvent(QCloseEvent*);
+
+protected:
+ bool mStopped;
+ bool mCloseRequested;
+ int mFetchingInProgress;
+ int mSortColumn;
+ SortOrder mSortOrder;
+ QGuardedPtr<KMFolderSearch> mFolder;
+ QTimer *mTimer;
+
+ // GC'd by Qt
+ QRadioButton *mChkbxAllFolders;
+ QRadioButton *mChkbxSpecificFolders;
+ KMail::FolderRequester *mCbxFolders;
+ QPushButton *mBtnSearch;
+ QPushButton *mBtnStop;
+ QCheckBox *mChkSubFolders;
+ KListView* mLbxMatches;
+ QLabel *mSearchFolderLbl;
+ QLineEdit *mSearchFolderEdt;
+ QPushButton *mSearchFolderOpenBtn;
+ QPushButton *mSearchResultOpenBtn;
+ KStatusBar* mStatusBar;
+ QWidget* mLastFocus; // to remember the position of the focus
+ QMap<int,KMFolder*> mMenuToFolder;
+ KAction *mReplyAction, *mReplyAllAction, *mReplyListAction, *mSaveAsAction,
+ *mForwardInlineAction, *mForwardAttachedAction, *mForwardDigestAction,
+ *mRedirectAction, *mPrintAction, *mClearAction, *mSaveAtchAction,
+ *mCopyAction, *mCutAction;
+ KActionMenu *mForwardActionMenu;
+ QValueList<QGuardedPtr<KMFolder> > mFolders;
+ QTimer mRenameTimer;
+
+ // not owned by us
+ KMMainWidget* mKMMainWidget;
+ KMSearchPatternEdit *mPatternEdit;
+ KMSearchPattern *mSearchPattern;
+
+ static const int MSGID_COLUMN;
+};
+
+} // namespace KMail
+#endif /*searchwindow_h*/
diff --git a/kmail/secondarywindow.cpp b/kmail/secondarywindow.cpp
new file mode 100644
index 00000000..396036e2
--- /dev/null
+++ b/kmail/secondarywindow.cpp
@@ -0,0 +1,81 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ secondarywindow.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2003 Ingo Kloecker <kloecker@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "secondarywindow.h"
+
+#include "kmkernel.h"
+
+#include <kapplication.h>
+
+namespace KMail {
+
+ //---------------------------------------------------------------------------
+ SecondaryWindow::SecondaryWindow( const char * name )
+ : KMainWindow( 0, name )
+ {
+ // Set this to be the group leader for all subdialogs - this means
+ // modal subdialogs will only affect this window, not the other windows
+ setWFlags( getWFlags() | WGroupLeader );
+
+ kapp->ref();
+ }
+
+
+ //---------------------------------------------------------------------------
+ SecondaryWindow::~SecondaryWindow()
+ {
+ kapp->deref();
+ }
+
+
+ //---------------------------------------------------------------------------
+ void SecondaryWindow::closeEvent( QCloseEvent * e )
+ {
+ // if there's a system tray applet then just do what needs to be done if a
+ // window is closed.
+ if ( kmkernel->haveSystemTrayApplet() ) {
+ // BEGIN of code borrowed from KMainWindow::closeEvent
+ // Save settings if auto-save is enabled, and settings have changed
+ if ( settingsDirty() && autoSaveSettings() )
+ saveAutoSaveSettings();
+
+ if ( queryClose() ) {
+ e->accept();
+ }
+ // END of code borrowed from KMainWindow::closeEvent
+ }
+ else
+ KMainWindow::closeEvent( e );
+ }
+
+} // namespace KMail
+
+#include "secondarywindow.moc"
diff --git a/kmail/secondarywindow.h b/kmail/secondarywindow.h
new file mode 100644
index 00000000..3319c07a
--- /dev/null
+++ b/kmail/secondarywindow.h
@@ -0,0 +1,63 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ secondarywindow.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2003 Ingo Kloecker <kloecker@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+#ifndef __KMAIL_SECONDARYWINDOW_H__
+#define __KMAIL_SECONDARYWINDOW_H__
+
+#include <kmainwindow.h>
+
+class QCloseEvent;
+
+namespace KMail {
+
+ /**
+ * Window class for secondary KMail window like the composer window and
+ * the separate message window.
+ */
+ class SecondaryWindow : public KMainWindow
+ {
+ Q_OBJECT
+
+ public:
+ SecondaryWindow( const char * name = 0 );
+ ~SecondaryWindow();
+
+ protected:
+ /**
+ * Reimplemented because we don't want the application to quit when the
+ * last _visible_ secondary window is closed in case a system tray applet
+ * exists.
+ */
+ virtual void closeEvent( QCloseEvent * );
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_SECONDARYWINDOW_H__
diff --git a/kmail/sieveconfig.cpp b/kmail/sieveconfig.cpp
new file mode 100644
index 00000000..872dee01
--- /dev/null
+++ b/kmail/sieveconfig.cpp
@@ -0,0 +1,170 @@
+/* -*- c++ -*-
+ sieveconfig.cpp
+
+ KMail, the KDE mail client.
+ Copyright (c) 2002 Marc Mutz <mutz@kde.org>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License,
+ version 2.0, as published by the Free Software Foundation.
+ 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, US
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "sieveconfig.h"
+
+#include <knuminput.h>
+#include <klocale.h>
+#include <kdialog.h>
+#include <kconfigbase.h>
+
+#include <qlayout.h>
+#include <qcheckbox.h>
+#include <qlabel.h>
+#include <klineedit.h>
+
+
+namespace KMail {
+
+ void SieveConfig::readConfig( const KConfigBase & config ) {
+ mManagesieveSupported = config.readBoolEntry( "sieve-support", false );
+ mReuseConfig = config.readBoolEntry( "sieve-reuse-config", true );
+
+ int port = config.readNumEntry( "sieve-port", 2000 );
+ if ( port < 1 || port > USHRT_MAX ) port = 2000;
+ mPort = static_cast<unsigned short>( port );
+
+ mAlternateURL = config.readEntry( "sieve-alternate-url" );
+ mVacationFileName = config.readEntry( "sieve-vacation-filename", "kmail-vacation.siv" );
+ if ( mVacationFileName.isEmpty() )
+ mVacationFileName = "kmail-vacation.siv";
+ }
+
+ void SieveConfig::writeConfig( KConfigBase & config ) const {
+ config.writeEntry( "sieve-support", managesieveSupported() );
+ config.writeEntry( "sieve-reuse-config", reuseConfig() );
+ config.writeEntry( "sieve-port", port() );
+ config.writeEntry( "sieve-alternate-url", mAlternateURL.url() );
+ config.writeEntry( "sieve-vacation-filename", mVacationFileName );
+ }
+
+ SieveConfigEditor::SieveConfigEditor( QWidget * parent, const char * name )
+ : QWidget( parent, name )
+ {
+ // tmp. vars:
+ int row = -1;
+ QLabel * label;
+
+ QGridLayout * glay = new QGridLayout( this, 5, 2, 0, KDialog::spacingHint() );
+ glay->setRowStretch( 4, 1 );
+ glay->setColStretch( 1, 1 );
+
+
+ // "Server supports sieve" checkbox:
+ ++row;
+ mManagesieveCheck = new QCheckBox( i18n("&Server supports Sieve"), this );
+ glay->addMultiCellWidget( mManagesieveCheck, row, row, 0, 1 );
+
+ connect( mManagesieveCheck, SIGNAL(toggled(bool)), SLOT(slotEnableWidgets()) );
+
+ // "reuse host and login config" checkbox:
+ ++row;
+ mSameConfigCheck = new QCheckBox( i18n("&Reuse host and login configuration"), this );
+ mSameConfigCheck->setChecked( true );
+ mSameConfigCheck->setEnabled( false );
+ glay->addMultiCellWidget( mSameConfigCheck, row, row, 0, 1 );
+
+ connect( mSameConfigCheck, SIGNAL(toggled(bool)), SLOT(slotEnableWidgets()) );
+
+ // "Managesieve port" spinbox and label:
+ ++row;
+ mPortSpin = new KIntSpinBox( 1, USHRT_MAX, 1, 2000, 10, this );
+ mPortSpin->setEnabled( false );
+ label = new QLabel( mPortSpin, i18n("Managesieve &port:"), this );
+ glay->addWidget( label, row, 0 );
+ glay->addWidget( mPortSpin, row, 1 );
+
+ // "Alternate URL" lineedit and label:
+ ++row;
+ mAlternateURLEdit = new KLineEdit( this );
+ mAlternateURLEdit->setEnabled( false );
+ glay->addWidget( new QLabel( mAlternateURLEdit, i18n("&Alternate URL:"), this ), row, 0 );
+ glay->addWidget( mAlternateURLEdit, row, 1 );
+
+ // row 4 is spacer
+
+ }
+
+ void SieveConfigEditor::slotEnableWidgets() {
+ bool haveSieve = mManagesieveCheck->isChecked();
+ bool reuseConfig = mSameConfigCheck->isChecked();
+
+ mSameConfigCheck->setEnabled( haveSieve );
+ mPortSpin->setEnabled( haveSieve && reuseConfig );
+ mAlternateURLEdit->setEnabled( haveSieve && !reuseConfig );
+ }
+
+ bool SieveConfigEditor::managesieveSupported() const {
+ return mManagesieveCheck->isChecked();
+ }
+
+ void SieveConfigEditor::setManagesieveSupported( bool enable ) {
+ mManagesieveCheck->setChecked( enable );
+ }
+
+ bool SieveConfigEditor::reuseConfig() const {
+ return mSameConfigCheck->isChecked();
+ }
+
+ void SieveConfigEditor::setReuseConfig( bool reuse ) {
+ mSameConfigCheck->setChecked( reuse );
+ }
+
+ unsigned short SieveConfigEditor::port() const {
+ return static_cast<unsigned short>( mPortSpin->value() );
+ }
+
+ void SieveConfigEditor::setPort( unsigned short port ) {
+ mPortSpin->setValue( port );
+ }
+
+ KURL SieveConfigEditor::alternateURL() const {
+ KURL url ( mAlternateURLEdit->text() );
+ if ( !url.isValid() )
+ return KURL();
+
+ if ( url.hasPass() )
+ url.setPass( QString::null );
+
+ return url;
+ }
+
+ void SieveConfigEditor::setAlternateURL( const KURL & url ) {
+ mAlternateURLEdit->setText( url.url() );
+ }
+
+
+ QString SieveConfigEditor::vacationFileName() const {
+ return mVacationFileName;
+ }
+
+ void SieveConfigEditor::setVacationFileName( const QString& name ) {
+ mVacationFileName = name;
+ }
+
+ void SieveConfigEditor::setConfig( const SieveConfig & config ) {
+ setManagesieveSupported( config.managesieveSupported() );
+ setReuseConfig( config.reuseConfig() );
+ setPort( config.port() );
+ setAlternateURL( config.alternateURL() );
+ setVacationFileName( config.vacationFileName() );
+ }
+
+} // namespace KMail
+
+#include "sieveconfig.moc"
diff --git a/kmail/sieveconfig.h b/kmail/sieveconfig.h
new file mode 100644
index 00000000..55c63cc5
--- /dev/null
+++ b/kmail/sieveconfig.h
@@ -0,0 +1,128 @@
+/* -*- c++ -*-
+ sieveconfig.h
+
+ KMail, the KDE mail client.
+ Copyright (c) 2002 Marc Mutz <mutz@kde.org>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License,
+ version 2.0, as published by the Free Software Foundation.
+ 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, US
+*/
+
+#ifndef __KMAIL_SIEVECONFIG_H__
+#define __KMAIL_SIEVECONFIG_H__
+
+#include <qwidget.h>
+
+#include <kurl.h>
+
+class QCheckBox;
+class QLineEdit;
+class KIntSpinBox;
+class KConfigBase;
+
+namespace KMail {
+
+ class SieveConfig {
+ public:
+ SieveConfig( bool managesieveSupported=false, bool reuseConfig=true,
+ unsigned int port=2000, const KURL & alternateURL=KURL(),
+ const QString& vacationFileName = QString::null )
+ : mManagesieveSupported( managesieveSupported ),
+ mReuseConfig( reuseConfig ),
+ mPort( port ),
+ mAlternateURL( alternateURL ),
+ mVacationFileName( vacationFileName ) {}
+
+ SieveConfig( const SieveConfig & other )
+ : mManagesieveSupported( other.managesieveSupported() ),
+ mReuseConfig( other.reuseConfig() ),
+ mPort( other.port() ),
+ mAlternateURL( other.alternateURL() ),
+ mVacationFileName( other.vacationFileName() ) {}
+
+ bool managesieveSupported() const {
+ return mManagesieveSupported;
+ }
+ void setManagesieveSupported( bool enable ) {
+ mManagesieveSupported = enable;
+ }
+
+ bool reuseConfig() const {
+ return mReuseConfig;
+ }
+ void setReuseConfig( bool reuse ) {
+ mReuseConfig = reuse;
+ }
+
+ unsigned short port() const {
+ return mPort;
+ }
+ void setPort( unsigned short port ) {
+ mPort = port;
+ }
+
+ KURL alternateURL() const {
+ return mAlternateURL;
+ }
+ void setAlternateURL( const KURL & url ) {
+ mAlternateURL = url;
+ }
+
+ QString vacationFileName() const { return mVacationFileName; }
+
+ void readConfig( const KConfigBase & config );
+ void writeConfig( KConfigBase & config ) const;
+
+ protected:
+ bool mManagesieveSupported;
+ bool mReuseConfig;
+ unsigned short mPort;
+ KURL mAlternateURL;
+ QString mVacationFileName;
+ };
+
+ class SieveConfigEditor : public QWidget {
+ Q_OBJECT
+ public:
+ SieveConfigEditor( QWidget * parent=0, const char * name=0 );
+
+ bool managesieveSupported() const;
+ virtual void setManagesieveSupported( bool enable );
+
+ bool reuseConfig() const;
+ virtual void setReuseConfig( bool reuse );
+
+ unsigned short port() const;
+ virtual void setPort( unsigned short port );
+
+ KURL alternateURL() const;
+ virtual void setAlternateURL( const KURL & url );
+
+ QString vacationFileName() const;
+ virtual void setVacationFileName( const QString & url );
+
+ SieveConfig config() const {
+ return SieveConfig( managesieveSupported(), reuseConfig(),
+ port(), alternateURL(), vacationFileName() );
+ }
+
+ virtual void setConfig( const SieveConfig & config );
+
+ protected slots:
+ void slotEnableWidgets();
+
+ protected:
+ QCheckBox * mManagesieveCheck;
+ QCheckBox * mSameConfigCheck;
+ KIntSpinBox * mPortSpin;
+ QLineEdit * mAlternateURLEdit;
+ QString mVacationFileName;
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_SIEVECONFIG_H__
diff --git a/kmail/sievedebugdialog.cpp b/kmail/sievedebugdialog.cpp
new file mode 100644
index 00000000..30a34136
--- /dev/null
+++ b/kmail/sievedebugdialog.cpp
@@ -0,0 +1,410 @@
+/*
+ sievedebugdialog.cpp
+
+ KMail, the KDE mail client.
+ Copyright (c) 2005 Martijn Klingens <klingens@kde.org>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License,
+ version 2.0, as published by the Free Software Foundation.
+ 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, US
+*/
+
+// This file is only compiled when debug is enabled, it is
+// not useful enough for non-developers to have this in releases.
+#if !defined(NDEBUG)
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "sievedebugdialog.h"
+
+#include <cassert>
+#include <limits.h>
+
+#include <qdatetime.h>
+
+#include <kdebug.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+
+#include <kmime_header_parsing.h>
+#include <ksieve/error.h>
+#include <ksieve/parser.h>
+#include <ksieve/scriptbuilder.h>
+#include <libkpimidentities/identity.h>
+#include <libkpimidentities/identitymanager.h>
+
+#include "kmacctimap.h"
+#include "accountmanager.h"
+using KMail::AccountManager;
+#include "kmkernel.h"
+#include "sievejob.h"
+#include <qtextedit.h>
+
+using KMail::SieveJob;
+using KMime::Types::AddrSpecList;
+
+namespace
+{
+
+class SieveDebugDataExtractor : public KSieve::ScriptBuilder
+{
+ enum Context
+ {
+ None = 0,
+
+ // command itself:
+ SieveDebugCommand,
+
+ // tagged args:
+ Days, Addresses
+ };
+
+public:
+ SieveDebugDataExtractor()
+ : KSieve::ScriptBuilder()
+ {
+ kdDebug( 5006 ) << k_funcinfo << endl;
+ }
+
+ virtual ~SieveDebugDataExtractor()
+ {
+ kdDebug( 5006 ) << k_funcinfo << endl;
+ }
+
+private:
+ void commandStart( const QString & identifier )
+ {
+ kdDebug( 5006 ) << k_funcinfo << "Identifier: '" << identifier << "'" << endl;
+ reset();
+ }
+
+ void commandEnd()
+ {
+ kdDebug( 5006 ) << k_funcinfo << endl;
+ }
+
+ void testStart( const QString & )
+ {
+ kdDebug( 5006 ) << k_funcinfo << endl;
+ }
+
+ void testEnd()
+ {
+ kdDebug( 5006 ) << k_funcinfo << endl;
+ }
+
+ void testListStart()
+ {
+ kdDebug( 5006 ) << k_funcinfo << endl;
+ }
+
+ void testListEnd()
+ {
+ kdDebug( 5006 ) << k_funcinfo << endl;
+ }
+
+ void blockStart()
+ {
+ kdDebug( 5006 ) << k_funcinfo << endl;
+ }
+
+ void blockEnd()
+ {
+ kdDebug( 5006 ) << k_funcinfo << endl;
+ }
+
+ void hashComment( const QString & )
+ {
+ kdDebug( 5006 ) << k_funcinfo << endl;
+ }
+
+ void bracketComment( const QString & )
+ {
+ kdDebug( 5006 ) << k_funcinfo << endl;
+ }
+
+ void lineFeed()
+ {
+ kdDebug( 5006 ) << k_funcinfo << endl;
+ }
+
+ void error( const KSieve::Error & e )
+ {
+ kdDebug( 5006 ) << "### " << k_funcinfo << "Error: " <<
+ e.asString() << " @ " << e.line() << "," << e.column() << endl;
+ }
+
+ void finished()
+ {
+ kdDebug( 5006 ) << k_funcinfo << endl;
+ }
+
+ void taggedArgument( const QString & tag )
+ {
+ kdDebug( 5006 ) << k_funcinfo << "Tag: '" << tag << "'" << endl;
+ }
+
+ void stringArgument( const QString & string, bool, const QString & )
+ {
+ kdDebug( 5006 ) << k_funcinfo << "String: '" << string << "'" << endl;
+ }
+
+ void numberArgument( unsigned long number, char )
+ {
+ kdDebug( 5006 ) << k_funcinfo << "Number: " << number << endl;
+ }
+
+ void stringListArgumentStart()
+ {
+ kdDebug( 5006 ) << k_funcinfo << endl;
+ }
+
+ void stringListEntry( const QString & string, bool, const QString & )
+ {
+ kdDebug( 5006 ) << k_funcinfo << "String: '" << string << "'" << endl;
+ }
+
+ void stringListArgumentEnd()
+ {
+ kdDebug( 5006 ) << k_funcinfo << endl;
+ }
+
+private:
+ void reset()
+ {
+ kdDebug( 5006 ) << k_funcinfo << endl;
+ }
+};
+
+} // Anon namespace
+
+namespace KMail
+{
+
+SieveDebugDialog::SieveDebugDialog( QWidget *parent, const char *name )
+: KDialogBase( parent, name, true, i18n( "Sieve Diagnostics" ), KDialogBase::Ok,
+ KDialogBase::Ok, true ),
+ mSieveJob( 0 )
+{
+ // Collect all accounts
+ AccountManager *am = kmkernel->acctMgr();
+ assert( am );
+ for ( KMAccount *a = am->first(); a; a = am->next() )
+ mAccountList.append( a );
+
+ mEdit = new QTextEdit( this );
+ mEdit->setReadOnly(true);
+ setMainWidget( mEdit );
+
+ mEdit->setText( i18n( "Collecting diagnostic information about Sieve support...\n\n" ) );
+
+ setInitialSize( QSize( 640, 480 ) );
+
+ if ( !mAccountList.isEmpty() )
+ QTimer::singleShot( 0, this, SLOT( slotDiagNextAccount() ) );
+}
+
+SieveDebugDialog::~SieveDebugDialog()
+{
+ if ( mSieveJob )
+ {
+ mSieveJob->kill();
+ mSieveJob = 0;
+ }
+ kdDebug( 5006 ) << k_funcinfo << endl;
+}
+
+void SieveDebugDialog::slotDiagNextAccount()
+{
+ if ( mAccountList.isEmpty() )
+ return;
+
+ KMAccount *acc = mAccountList.first();
+ mAccountList.pop_front();
+
+ mEdit->append( i18n( "Collecting data for account '%1'...\n" ).arg( acc->name() ) );
+ mEdit->append( i18n( "------------------------------------------------------------\n" ) );
+ mAccountBase = dynamic_cast<KMail::ImapAccountBase *>( acc );
+ if ( mAccountBase )
+ {
+ // Detect URL for this IMAP account
+ SieveConfig sieve = mAccountBase->sieveConfig();
+ if ( !sieve.managesieveSupported() )
+ {
+ 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() );
+ }
+
+ mSieveJob = SieveJob::list( mUrl );
+
+ connect( mSieveJob, SIGNAL( gotList( KMail::SieveJob *, bool, const QStringList &, const QString & ) ),
+ SLOT( slotGetScriptList( KMail::SieveJob *, bool, const QStringList &, const QString & ) ) );
+
+ // Bypass the singleShot timer -- it's fired when we get our data
+ return;
+ }
+ } else {
+ mEdit->append( i18n( "(Account is not an IMAP account)\n\n" ) );
+ }
+
+ // Handle next account async
+ QTimer::singleShot( 0, this, SLOT( slotDiagNextAccount() ) );
+}
+
+void SieveDebugDialog::slotDiagNextScript()
+{
+ if ( mScriptList.isEmpty() )
+ {
+ // Continue handling accounts instead
+ mScriptList.clear();
+ QTimer::singleShot( 0, this, SLOT( slotDiagNextAccount() ) );
+ return;
+ }
+
+ QString scriptFile = mScriptList.first();
+ 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 );
+ }
+
+ mSieveJob = SieveJob::get( mUrl );
+
+ connect( mSieveJob, SIGNAL( gotScript( KMail::SieveJob *, bool, const QString &, bool ) ),
+ SLOT( slotGetScript( KMail::SieveJob *, bool, const QString &, bool ) ) );
+}
+
+void SieveDebugDialog::slotGetScript( SieveJob * /* job */, bool success,
+ const QString &script, bool active )
+{
+ kdDebug( 5006 ) << "SieveDebugDialog::slotGetScript( ??, " << success
+ << ", ?, " << active << " )" << endl
+ << "script:" << endl
+ << script << endl;
+ mSieveJob = 0; // job deletes itself after returning from this slot!
+
+ if ( script.isEmpty() )
+ {
+ mEdit->append( i18n( "(This script is empty.)\n\n" ) );
+ }
+ else
+ {
+ mEdit->append( i18n(
+ "------------------------------------------------------------\n"
+ "%1\n"
+ "------------------------------------------------------------\n\n" ).arg( script ) );
+ }
+
+ // Fetch next script
+ QTimer::singleShot( 0, this, SLOT( slotDiagNextScript() ) );
+}
+
+void SieveDebugDialog::slotGetScriptList( SieveJob *job, bool success,
+ const QStringList &scriptList, const QString &activeScript )
+{
+ kdDebug( 5006 ) << k_funcinfo << "Success: " << success << ", List: " << scriptList.join( ", " ) <<
+ ", active: " << activeScript << endl;
+ mSieveJob = 0; // job deletes itself after returning from this slot!
+
+ mEdit->append( i18n( "Sieve capabilities:\n" ) );
+ QStringList caps = job->sieveCapabilities();
+ if ( caps.isEmpty() )
+ {
+ mEdit->append( i18n( "(No special capabilities available)" ) );
+ }
+ else
+ {
+ for ( QStringList::const_iterator it = caps.begin(); it != caps.end(); ++it )
+ mEdit->append( "* " + *it + "\n" );
+ mEdit->append( "\n" );
+ }
+
+ mEdit->append( i18n( "Available Sieve scripts:\n" ) );
+
+ if ( scriptList.isEmpty() )
+ {
+ mEdit->append( i18n( "(No Sieve scripts available on this server)\n\n" ) );
+ }
+ else
+ {
+ mScriptList = scriptList;
+ for ( QStringList::const_iterator it = scriptList.begin(); it != scriptList.end(); ++it )
+ mEdit->append( "* " + *it + "\n" );
+ mEdit->append( "\n" );
+ mEdit->append( i18n( "Active script: %1\n\n" ).arg( activeScript ) );
+ }
+
+ // Handle next job: dump scripts for this server
+ QTimer::singleShot( 0, this, SLOT( slotDiagNextScript() ) );
+}
+
+void SieveDebugDialog::slotDialogOk()
+{
+ kdDebug(5006) << "SieveDebugDialog::slotDialogOk()" << endl;
+}
+
+void SieveDebugDialog::slotPutActiveResult( SieveJob * job, bool success )
+{
+ handlePutResult( job, success, true );
+}
+
+void SieveDebugDialog::slotPutInactiveResult( SieveJob * job, bool success )
+{
+ handlePutResult( job, success, false );
+}
+
+void SieveDebugDialog::handlePutResult( SieveJob *, bool success, bool activated )
+{
+ if ( success )
+ {
+ KMessageBox::information( 0, activated ? i18n(
+ "Sieve script installed successfully on the server.\n"
+ "Out of Office reply is now active." )
+ : i18n( "Sieve script installed successfully on the server.\n"
+ "Out of Office reply has been deactivated." ) );
+ }
+
+ kdDebug( 5006 ) << "SieveDebugDialog::handlePutResult( ???, " << success << ", ? )" << endl;
+ mSieveJob = 0; // job deletes itself after returning from this slot!
+}
+
+
+} // namespace KMail
+
+#include "sievedebugdialog.moc"
+
+#endif // NDEBUG
+
diff --git a/kmail/sievedebugdialog.h b/kmail/sievedebugdialog.h
new file mode 100644
index 00000000..ba51368a
--- /dev/null
+++ b/kmail/sievedebugdialog.h
@@ -0,0 +1,94 @@
+/*
+ sievedebugdialog.h
+
+ KMail, the KDE mail client.
+ Copyright (c) 2005 Martijn Klingens <klingens@kde.org>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License,
+ version 2.0, as published by the Free Software Foundation.
+ 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, US
+*/
+
+#ifndef __sievedebugdialog_h__
+#define __sievedebugdialog_h__
+
+// This file is only compiled when debug is enabled, it is
+// not useful enough for non-developers to have this in releases.
+#if !defined(NDEBUG)
+
+#include <kdialogbase.h>
+#include <kurl.h>
+
+class QString;
+class QStringList;
+class QTextEdit;
+template <typename T> class QValueList;
+
+class KMAccount;
+
+namespace KMime
+{
+ namespace Types
+ {
+ struct AddrSpec;
+ typedef QValueList<AddrSpec> AddrSpecList;
+ }
+}
+
+namespace KMail
+{
+class ImapAccountBase;
+class SieveJob;
+
+/**
+ * Diagnostic info for Sieve. Only compiled when debug is enabled, it is
+ * not useful enough for non-developers to have this in releases.
+ */
+class SieveDebugDialog : public KDialogBase
+{
+ Q_OBJECT
+
+public:
+ SieveDebugDialog( QWidget *parent = 0, const char *name = 0 );
+ virtual ~SieveDebugDialog();
+
+protected:
+ void handlePutResult( KMail::SieveJob *job, bool success, bool );
+
+signals:
+ void result( bool success );
+
+protected slots:
+ void slotGetScript( KMail::SieveJob *job, bool success, const QString &script, bool active );
+ void slotGetScriptList( KMail::SieveJob *job, bool success, const QStringList &scriptList, const QString &activeScript );
+
+ void slotDialogOk();
+ void slotPutActiveResult( KMail::SieveJob*, bool );
+ void slotPutInactiveResult( KMail::SieveJob*, bool );
+ void slotDiagNextAccount();
+ void slotDiagNextScript();
+
+protected:
+ KMail::SieveJob *mSieveJob;
+ KURL mUrl;
+
+ QTextEdit *mEdit;
+
+ // Copied from AccountManager, because we have to do an async iteration
+ // WARNING: When copy/pasting this code, be aware that accounts may
+ // get removed inbetween! For debugging this is good enough
+ // though. - Martijn
+ QValueList<KMAccount *> mAccountList;
+ QStringList mScriptList;
+ KMail::ImapAccountBase *mAccountBase;
+};
+
+} // namespace KMail
+
+#endif // NDEBUG
+
+#endif // __sievedebugdialog_h__
+
diff --git a/kmail/sievejob.cpp b/kmail/sievejob.cpp
new file mode 100644
index 00000000..c876c428
--- /dev/null
+++ b/kmail/sievejob.cpp
@@ -0,0 +1,289 @@
+/* -*- c++ -*-
+ sievejob.h
+
+ KMail, the KDE mail client.
+ Copyright (c) 2002 Marc Mutz <mutz@kde.org>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License,
+ version 2.0, as published by the Free Software Foundation.
+ 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, US
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "sievejob.h"
+
+#include <kio/job.h>
+using KIO::Job;
+// <kio/global.h>
+using KIO::UDSAtomTypes;
+using KIO::UDSEntryList;
+using KIO::UDSEntry;
+#include <kdebug.h>
+
+#include <qtextcodec.h>
+
+#include <cassert>
+
+namespace KMail {
+
+ SieveJob::SieveJob( const KURL & url, const QString & script,
+ const QValueStack<Command> & commands,
+ QObject * parent, const char * name )
+ : QObject( parent, name ),
+ mUrl( url ), mJob( 0 ), mDec( 0 ),
+ mScript( script ), mFileExists( DontKnow ), mCommands( commands ),
+ mShowProgressInfo(true)
+ {
+ assert( !commands.isEmpty() );
+ schedule( commands.top(), true );
+ }
+
+ SieveJob::SieveJob( const KURL & url, const QString & script,
+ const QValueStack<Command> & commands,
+ bool showProgressInfo,
+ QObject * parent, const char * name )
+ : QObject( parent, name ),
+ mUrl( url ), mJob( 0 ), mDec( 0 ),
+ mScript( script ), mFileExists( DontKnow ), mCommands( commands ),
+ mShowProgressInfo(showProgressInfo)
+ {
+ assert( !commands.isEmpty() );
+ schedule( commands.top(), showProgressInfo );
+ }
+
+ SieveJob::~SieveJob() {
+ kill();
+ delete mDec;
+ kdDebug(5006) << "~SieveJob()" << endl;
+ }
+
+ void SieveJob::kill( bool quiet ) {
+ if ( mJob ) mJob->kill( quiet );
+ }
+
+ void SieveJob::schedule( Command command, bool showProgressInfo ) {
+ switch ( command ) {
+ case Get:
+ kdDebug(5006) << "SieveJob::schedule: get( " << mUrl.prettyURL() << " )" << endl;
+ mJob = KIO::get( mUrl, false /*reload*/, showProgressInfo );
+ connect( mJob, SIGNAL(data(KIO::Job*,const QByteArray&)),
+ SLOT(slotData(KIO::Job*,const QByteArray&)) );
+ break;
+ case Put:
+ kdDebug(5006) << "SieveJob::schedule: put( " << mUrl.prettyURL() << " )" << endl;
+ mJob = KIO::put( mUrl, 0600, true /*overwrite*/, false /*resume*/, showProgressInfo );
+ connect( mJob, SIGNAL(dataReq(KIO::Job*,QByteArray&)),
+ SLOT(slotDataReq(KIO::Job*,QByteArray&)) );
+ break;
+ case Activate:
+ kdDebug(5006) << "SieveJob::schedule: chmod( " << mUrl.prettyURL() << ", 0700 )"
+ << endl;
+ mJob = KIO::chmod( mUrl, 0700 );
+ break;
+ case Deactivate:
+ kdDebug(5006) << "SieveJob::schedule: chmod( " << mUrl.prettyURL() << ", 0600 )"
+ << endl;
+ mJob = KIO::chmod( mUrl, 0600 );
+ break;
+ case SearchActive:
+ kdDebug(5006) << "SieveJob::schedule: listDir( " << mUrl.prettyURL() << " )" << endl;
+ {
+ KURL url = mUrl;
+ QString query = url.query(); //save query part, because KURL::cd() erases it
+ if ( !url.fileName().isEmpty() )
+ url.cd("..");
+ url.setQuery( query );
+ kdDebug(5006) << "SieveJob::schedule: listDir's real URL: " << url.prettyURL()
+ << endl;
+ mJob = KIO::listDir( url, showProgressInfo );
+ connect( mJob, SIGNAL(entries(KIO::Job*,const KIO::UDSEntryList&)),
+ SLOT(slotEntries(KIO::Job*,const KIO::UDSEntryList&)) );
+ break;
+ }
+ case List:
+ kdDebug(5006) << "SieveJob::schedule: listDir( " << mUrl.prettyURL() << " )" << endl;
+ {
+ mJob = KIO::listDir( mUrl, showProgressInfo );
+ connect( mJob, SIGNAL( entries(KIO::Job *, const KIO::UDSEntryList & ) ),
+ SLOT( slotEntries( KIO::Job *, const KIO::UDSEntryList & ) ) );
+ break;
+ }
+ case Delete:
+ kdDebug(5006) << "SieveJob::schedule: delete( " << mUrl.prettyURL() << " )" << endl;
+ mJob = KIO::del( mUrl, false/*shred*/, showProgressInfo );
+ break;
+ default:
+ assert( 0 );
+ }
+ mJob->setInteractive(showProgressInfo);
+ // common to all jobs:
+ connect( mJob, SIGNAL(result(KIO::Job*)), SLOT(slotResult(KIO::Job*)) );
+ }
+
+ void SieveJob::slotData( Job *, const QByteArray & data ) {
+ // check for end-of-data marker:
+ if ( data.size() == 0 )
+ return;
+
+ // make sure we have a textdecoder;
+ if ( !mDec )
+ mDec = QTextCodec::codecForMib( 106 /*utf8*/ )->makeDecoder();
+
+ // decode utf8; add to mScript:
+ mScript += mDec->toUnicode( data.data(), data.size() );
+ }
+
+ void SieveJob::slotDataReq( Job *, QByteArray & data ) {
+ // check whether we have already sent our data:
+ if ( mScript.isEmpty() ) {
+ data = QByteArray(); // end-of-data marker
+ return;
+ }
+
+ // Convert mScript into UTF-8:
+ data = mScript.utf8();
+
+ // "data" contains a trailing NUL, remove:
+ if ( data.size() > 0 && data[(int)data.size() - 1] == '\0' )
+ data.resize( data.size() - 1 );
+
+ // mark mScript sent:
+ mScript = QString::null;
+ }
+
+ void SieveJob::slotEntries( Job *, const UDSEntryList & l ) {
+ // loop over entries:
+ for ( UDSEntryList::const_iterator it = l.begin() ; it != l.end() ; ++it ) {
+ // Loop over all UDS atoms to find the UDS_ACCESS and UDS_NAME atoms;
+ // note if we find an exec'able file ( == active script )
+ // or the requested filename (mUrl.fileName()).
+ QString filename;
+ bool isActive = false;
+ for ( UDSEntry::const_iterator et = (*it).begin() ; et != (*it).end() ; ++ et ) {
+ if ( ( *et ).m_uds == KIO::UDS_NAME ) {
+ filename = ( *et ).m_str;
+ mAvailableScripts.append( filename );
+ } else if ( ( *et ).m_uds == KIO::UDS_ACCESS && ( *et ).m_long == 0700 )
+ isActive = true;
+ }
+
+ if ( isActive )
+ mActiveScriptName = filename;
+
+ if ( mFileExists == DontKnow && filename == mUrl.fileName() )
+ mFileExists = Yes;
+ emit item( this, filename, isActive );
+ if ( mFileExists == Yes && !mActiveScriptName.isEmpty() )
+ return; // early return if we have all information
+ }
+ }
+
+ void SieveJob::slotResult( Job * job ) {
+ Command lastCmd = mCommands.top();
+
+ // First, let's see if we come back from a SearchActive. If so, set
+ // mFileExists to No if we didn't see the mUrl.fileName() during
+ // listDir...
+ if ( lastCmd == SearchActive && mFileExists == DontKnow && !job->error() )
+ mFileExists = No;
+ // prepare for next round:
+ mCommands.pop();
+ delete mDec; mDec = 0;
+
+ if ( mSieveCapabilities.empty() ) {
+ mSieveCapabilities = QStringList::split( ' ', job->queryMetaData( "sieveExtensions" ) );
+ kdDebug(5006) << "Received Sieve extensions supported:" << endl
+ << mSieveCapabilities.join("\n") << endl;
+ }
+
+ // check for errors:
+ if ( job->error() ) {
+ if ( job->isInteractive() ) {
+ job->showErrorDialog( 0 );
+ }
+
+ emit result( this, false, mScript, mUrl.fileName() == mActiveScriptName );
+
+ if ( lastCmd == List )
+ emit gotList( this, false, mAvailableScripts, mActiveScriptName );
+ else
+ emit gotScript( this, false, mScript, mUrl.fileName() == mActiveScriptName );
+
+ mJob = 0;
+ delete this;
+ return;
+ }
+
+ // check for new tasks:
+ if ( !mCommands.empty() ) {
+ // Don't fail get'ting a non-existant script:
+ if ( mCommands.top() == Get && mFileExists == No ) {
+ mScript = QString::null;
+ mCommands.pop();
+ }
+ }
+
+ if ( mCommands.empty() ) {
+ // was last command; report success and delete this object:
+ emit result( this, true, mScript, mUrl.fileName() == mActiveScriptName );
+ if ( lastCmd == List )
+ emit gotList( this, true, mAvailableScripts, mActiveScriptName );
+ else
+ emit gotScript( this, true, mScript, mUrl.fileName() == mActiveScriptName );
+
+ mJob = 0; // deletes itself on returning from this slot
+ delete this;
+ return;
+ } else {
+ // schedule the next command:
+ schedule( mCommands.top(), mShowProgressInfo );
+ }
+ }
+
+ SieveJob * SieveJob::put( const KURL & dest, const QString & script,
+ bool makeActive, bool wasActive ) {
+ QValueStack<Command> commands;
+ if ( makeActive )
+ commands.push( Activate );
+ if ( wasActive )
+ commands.push( Deactivate );
+ commands.push( Put );
+ return new SieveJob( dest, script, commands );
+ }
+
+ SieveJob * SieveJob::get( const KURL & src, bool showProgressInfo ) {
+ QValueStack<Command> commands;
+ commands.push( Get );
+ commands.push( SearchActive );
+ return new SieveJob( src, QString::null, commands, showProgressInfo );
+ }
+
+ SieveJob * SieveJob::list( const KURL & src ) {
+ QValueStack<Command> commands;
+ commands.push( List );
+ return new SieveJob( src, QString::null, commands );
+ }
+ SieveJob * SieveJob::del( const KURL & url ) {
+ QValueStack<Command> commands;
+ commands.push( Delete );
+ return new SieveJob( url, QString::null, commands );
+ }
+
+ SieveJob * SieveJob::activate( const KURL & url ) {
+ QValueStack<Command> commands;
+ commands.push( Activate );
+ return new SieveJob( url, QString::null, commands );
+ }
+
+} // namespace KMail
+
+#include "sievejob.moc"
+
+// vim: set noet sts=2 ts=8 sw=2:
+
diff --git a/kmail/sievejob.h b/kmail/sievejob.h
new file mode 100644
index 00000000..f7c353cf
--- /dev/null
+++ b/kmail/sievejob.h
@@ -0,0 +1,129 @@
+/* -*- c++ -*-
+ sievejob.h
+
+ KMail, the KDE mail client.
+ Copyright (c) 2002 Marc Mutz <mutz@kde.org>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License,
+ version 2.0, as published by the Free Software Foundation.
+ 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, US
+*/
+
+#ifndef __KMAIL_SIEVE_JOB_H__
+#define __KMAIL_SIEVE_JOB_H__
+
+#include <qobject.h>
+#include <qvaluestack.h>
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qcstring.h>
+
+#include <kurl.h>
+#include <kio/global.h>
+
+class QTextDecoder;
+namespace KIO {
+ class Job;
+}
+
+namespace KMail {
+
+ class SieveJob : public QObject {
+ Q_OBJECT
+ protected:
+ enum Command { Get, Put, Activate, Deactivate, SearchActive, List, Delete };
+ SieveJob( const KURL & url, const QString & script,
+ const QValueStack<Command> & commands,
+ QObject * parent=0, const char * name=0 );
+ SieveJob( const KURL & url, const QString & script,
+ const QValueStack<Command> & commands,
+ bool showProgressInfo,
+ QObject * parent=0, const char * name=0 );
+ virtual ~SieveJob();
+
+ public:
+ enum Existence { DontKnow, Yes, No };
+
+ /**
+ * Store a Sieve script. If @param makeActive is set, also mark the
+ * script active
+ */
+ static SieveJob * put( const KURL & dest, const QString & script,
+ bool makeActive, bool wasActive );
+
+ /**
+ * Get a specific Sieve script
+ */
+ static SieveJob * get( const KURL & src, bool showProgressInfo=true );
+
+ /**
+ * List all available scripts
+ */
+ static SieveJob * list( const KURL & url );
+
+ static SieveJob * del( const KURL & url );
+
+ static SieveJob * activate( const KURL & url );
+
+ void kill( bool quiet=true );
+
+ const QStringList & sieveCapabilities() const {
+ return mSieveCapabilities;
+ }
+
+ bool fileExists() const {
+ return mFileExists;
+ }
+
+ signals:
+ void gotScript( KMail::SieveJob * job, bool success,
+ const QString & script, bool active );
+
+ /**
+ * We got the list of available scripts
+ *
+ * @param scriptList is the list of script filenames
+ * @param activeScript lists the filename of the active script, or an
+ * empty string if no script is active.
+ */
+ void gotList( KMail::SieveJob *job, bool success,
+ const QStringList &scriptList, const QString &activeScript );
+
+ void result( KMail::SieveJob * job, bool success,
+ const QString & script, bool active );
+
+ void item( KMail::SieveJob * job, const QString & filename, bool active );
+
+ protected:
+ void schedule( Command command, bool showProgressInfo );
+
+ protected slots:
+ void slotData( KIO::Job *, const QByteArray & ); // for get
+ void slotDataReq( KIO::Job *, QByteArray & ); // for put
+ void slotEntries( KIO::Job *, const KIO::UDSEntryList & ); // for listDir
+ void slotResult( KIO::Job * ); // for all commands
+
+ protected:
+ KURL mUrl;
+ KIO::Job * mJob;
+ QTextDecoder * mDec;
+ QString mScript;
+ QString mActiveScriptName;
+ Existence mFileExists;
+ QStringList mSieveCapabilities;
+ QValueStack<Command> mCommands;
+ bool mShowProgressInfo;
+
+ // List of Sieve scripts on the server, used by @ref list()
+ QStringList mAvailableScripts;
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_SIEVE_JOB_H__
+
+// vim: set noet sts=2 ts=8 sw=2:
+
diff --git a/kmail/signatureconfigurator.cpp b/kmail/signatureconfigurator.cpp
new file mode 100644
index 00000000..68bfd194
--- /dev/null
+++ b/kmail/signatureconfigurator.cpp
@@ -0,0 +1,268 @@
+/* -*- c++ -*-
+ signatureconfigurator.cpp
+
+ KMail, the KDE mail client.
+ Copyright (c) 2002 the KMail authors.
+ See file AUTHORS for details
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License,
+ version 2.0, as published by the Free Software Foundation.
+ 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, US
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "signatureconfigurator.h"
+
+#include <klocale.h>
+#include <kdialog.h>
+#include <klineedit.h>
+#include <kurlrequester.h>
+#include <kshellcompletion.h>
+#include <krun.h>
+
+#include <qcheckbox.h>
+#include <qcombobox.h>
+#include <qdir.h>
+#include <qfileinfo.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qtextedit.h>
+#include <qwhatsthis.h>
+#include <qwidgetstack.h>
+
+#include <assert.h>
+
+using namespace KMail;
+
+namespace KMail {
+
+ SignatureConfigurator::SignatureConfigurator( QWidget * parent, const char * name )
+ : QWidget( parent, name )
+ {
+ // tmp. vars:
+ QLabel * label;
+ QWidget * page;
+ QHBoxLayout * hlay;
+ QVBoxLayout * vlay;
+ QVBoxLayout * page_vlay;
+
+ vlay = new QVBoxLayout( this, 0, KDialog::spacingHint(), "main layout" );
+
+ // "enable signatue" checkbox:
+ mEnableCheck = new QCheckBox( i18n("&Enable signature"), this );
+ QWhatsThis::add(mEnableCheck,
+ i18n("Check this box if you want KMail to append a signature to mails "
+ "written with this identity."));
+ vlay->addWidget( mEnableCheck );
+
+ // "obtain signature text from" combo and label:
+ hlay = new QHBoxLayout( vlay ); // inherits spacing
+ mSourceCombo = new QComboBox( false, this );
+ QWhatsThis::add(mSourceCombo,
+ i18n("Click on the widgets below to obtain help on the input methods."));
+ mSourceCombo->setEnabled( false ); // since !mEnableCheck->isChecked()
+ mSourceCombo->insertStringList( QStringList()
+ << i18n("continuation of \"obtain signature text from\"",
+ "Input Field Below")
+ << i18n("continuation of \"obtain signature text from\"",
+ "File")
+ << i18n("continuation of \"obtain signature text from\"",
+ "Output of Command")
+ );
+ label = new QLabel( mSourceCombo,
+ i18n("Obtain signature &text from:"), this );
+ label->setEnabled( false ); // since !mEnableCheck->isChecked()
+ hlay->addWidget( label );
+ hlay->addWidget( mSourceCombo, 1 );
+
+ // widget stack that is controlled by the source combo:
+ QWidgetStack * widgetStack = new QWidgetStack( this );
+ widgetStack->setEnabled( false ); // since !mEnableCheck->isChecked()
+ vlay->addWidget( widgetStack, 1 );
+ connect( mSourceCombo, SIGNAL(highlighted(int)),
+ widgetStack, SLOT(raiseWidget(int)) );
+ // connects for the enabling of the widgets depending on
+ // signatureEnabled:
+ connect( mEnableCheck, SIGNAL(toggled(bool)),
+ mSourceCombo, SLOT(setEnabled(bool)) );
+ connect( mEnableCheck, SIGNAL(toggled(bool)),
+ widgetStack, SLOT(setEnabled(bool)) );
+ connect( mEnableCheck, SIGNAL(toggled(bool)),
+ label, SLOT(setEnabled(bool)) );
+ // The focus might be still in the widget that is disabled
+ connect( mEnableCheck, SIGNAL(clicked()),
+ mEnableCheck, SLOT(setFocus()) );
+
+ int pageno = 0;
+ // page 0: input field for direct entering:
+ mTextEdit = new QTextEdit( widgetStack );
+ QWhatsThis::add(mTextEdit,
+ i18n("Use this field to enter an arbitrary static signature."));
+ widgetStack->addWidget( mTextEdit, pageno );
+ mTextEdit->setFont( KGlobalSettings::fixedFont() );
+ mTextEdit->setWordWrap( QTextEdit::NoWrap );
+ mTextEdit->setTextFormat( Qt::PlainText );
+
+ widgetStack->raiseWidget( 0 ); // since mSourceCombo->currentItem() == 0
+
+ // page 1: "signature file" requester, label, "edit file" button:
+ ++pageno;
+ page = new QWidget( widgetStack );
+ widgetStack->addWidget( page, pageno ); // force sequential numbers (play safe)
+ page_vlay = new QVBoxLayout( page, 0, KDialog::spacingHint() );
+ hlay = new QHBoxLayout( page_vlay ); // inherits spacing
+ mFileRequester = new KURLRequester( page );
+ QWhatsThis::add(mFileRequester,
+ i18n("Use this requester to specify a text file that contains your "
+ "signature. It will be read every time you create a new mail or "
+ "append a new signature."));
+ hlay->addWidget( new QLabel( mFileRequester,
+ i18n("S&pecify file:"), page ) );
+ hlay->addWidget( mFileRequester, 1 );
+ mFileRequester->button()->setAutoDefault( false );
+ connect( mFileRequester, SIGNAL(textChanged(const QString &)),
+ this, SLOT(slotEnableEditButton(const QString &)) );
+ mEditButton = new QPushButton( i18n("Edit &File"), page );
+ QWhatsThis::add(mEditButton, i18n("Opens the specified file in a text editor."));
+ connect( mEditButton, SIGNAL(clicked()), SLOT(slotEdit()) );
+ mEditButton->setAutoDefault( false );
+ mEditButton->setEnabled( false ); // initially nothing to edit
+ hlay->addWidget( mEditButton );
+ page_vlay->addStretch( 1 ); // spacer
+
+ // page 2: "signature command" requester and label:
+ ++pageno;
+ page = new QWidget( widgetStack );
+ widgetStack->addWidget( page, pageno );
+ page_vlay = new QVBoxLayout( page, 0, KDialog::spacingHint() );
+ hlay = new QHBoxLayout( page_vlay ); // inherits spacing
+ mCommandEdit = new KLineEdit( page );
+ mCommandEdit->setCompletionObject( new KShellCompletion() );
+ mCommandEdit->setAutoDeleteCompletionObject( true );
+ QWhatsThis::add(mCommandEdit,
+ i18n("You can add an arbitrary command here, either with or without path "
+ "depending on whether or not the command is in your Path. For every "
+ "new mail, KMail will execute the command and use what it outputs (to "
+ "standard output) as a signature. Usual commands for use with this "
+ "mechanism are \"fortune\" or \"ksig -random\"."));
+ hlay->addWidget( new QLabel( mCommandEdit,
+ i18n("S&pecify command:"), page ) );
+ hlay->addWidget( mCommandEdit, 1 );
+ page_vlay->addStretch( 1 ); // spacer
+
+ }
+
+ SignatureConfigurator::~SignatureConfigurator() {
+
+ }
+
+ bool SignatureConfigurator::isSignatureEnabled() const {
+ return mEnableCheck->isChecked();
+ }
+
+ void SignatureConfigurator::setSignatureEnabled( bool enable ) {
+ mEnableCheck->setChecked( enable );
+ }
+
+ Signature::Type SignatureConfigurator::signatureType() const {
+ if ( !isSignatureEnabled() ) return Signature::Disabled;
+
+ switch ( mSourceCombo->currentItem() ) {
+ case 0: return Signature::Inlined;
+ case 1: return Signature::FromFile;
+ case 2: return Signature::FromCommand;
+ default: return Signature::Disabled;
+ }
+ }
+
+ void SignatureConfigurator::setSignatureType( Signature::Type type ) {
+ setSignatureEnabled( type != Signature::Disabled );
+
+ int idx = 0;
+ switch( type ) {
+ case Signature::Inlined: idx = 0; break;
+ case Signature::FromFile: idx = 1; break;
+ case Signature::FromCommand: idx = 2; break;
+ default: idx = 0; break;
+ };
+
+ mSourceCombo->setCurrentItem( idx );
+ }
+
+ QString SignatureConfigurator::inlineText() const {
+ return mTextEdit->text();
+ }
+
+ void SignatureConfigurator::setInlineText( const QString & text ) {
+ mTextEdit->setText( text );
+ }
+
+ QString SignatureConfigurator::fileURL() const {
+ QString file = mFileRequester->url().stripWhiteSpace();
+
+ // Force the filename to be relative to ~ instead of $PWD depending
+ // on the rest of the code (KRun::run in Edit and KFileItem on save)
+ if ( !file.isEmpty() && QFileInfo( file ).isRelative() )
+ file = QDir::home().absPath() + QDir::separator() + file;
+
+ return file;
+ }
+
+ void SignatureConfigurator::setFileURL( const QString & url ) {
+ mFileRequester->setURL( url );
+ }
+
+ QString SignatureConfigurator::commandURL() const {
+ return mCommandEdit->text();
+ }
+
+ void SignatureConfigurator::setCommandURL( const QString & url ) {
+ mCommandEdit->setText( url );
+ }
+
+
+ Signature SignatureConfigurator::signature() const {
+ Signature sig;
+ sig.setType( signatureType() );
+ sig.setText( inlineText() );
+ if ( signatureType() == Signature::FromCommand )
+ sig.setUrl( commandURL(), true );
+ if ( signatureType() == Signature::FromFile )
+ sig.setUrl( fileURL(), false );
+ return sig;
+ }
+
+ void SignatureConfigurator::setSignature( const Signature & sig ) {
+ setSignatureType( sig.type() );
+ setInlineText( sig.text() );
+ if ( sig.type() == Signature::FromFile )
+ setFileURL( sig.url() );
+ else
+ setFileURL( QString::null );
+ if ( sig.type() == Signature::FromCommand )
+ setCommandURL( sig.url() );
+ else
+ setCommandURL( QString::null );
+ }
+
+ void SignatureConfigurator::slotEnableEditButton( const QString & url ) {
+ mEditButton->setDisabled( url.stripWhiteSpace().isEmpty() );
+ }
+
+ void SignatureConfigurator::slotEdit() {
+ QString url = fileURL();
+ // slotEnableEditButton should prevent this assert from being hit:
+ assert( !url.isEmpty() );
+
+ (void)KRun::runURL( KURL( url ), QString::fromLatin1("text/plain") );
+ }
+
+} // namespace KMail
+
+#include "signatureconfigurator.moc"
diff --git a/kmail/signatureconfigurator.h b/kmail/signatureconfigurator.h
new file mode 100644
index 00000000..f67a9f9d
--- /dev/null
+++ b/kmail/signatureconfigurator.h
@@ -0,0 +1,82 @@
+/* -*- c++ -*-
+ signatureconfigurator.cpp
+
+ KMail, the KDE mail client.
+ Copyright (c) 2002 the KMail authors.
+ See file AUTHORS for details
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License,
+ version 2.0, as published by the Free Software Foundation.
+ 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, US
+*/
+
+#ifndef __KMAIL_SIGNATURECONFIGURATOR_H__
+#define __KMAIL_SIGNATURECONFIGURATOR_H__
+
+#include <qwidget.h>
+
+#include <libkpimidentities/identity.h> // for Signature::Type
+using KPIM::Signature;
+
+class QComboBox;
+class QCheckBox;
+class KURLRequester;
+class KLineEdit;
+class QString;
+class QPushButton;
+class QTextEdit;
+
+namespace KMail {
+
+ class SignatureConfigurator : public QWidget {
+ Q_OBJECT
+ public:
+ SignatureConfigurator( QWidget * parent=0, const char * name=0 );
+ virtual ~SignatureConfigurator();
+
+ bool isSignatureEnabled() const;
+ void setSignatureEnabled( bool enable );
+
+ Signature::Type signatureType() const;
+ void setSignatureType( Signature::Type type );
+
+ QString inlineText() const;
+ void setInlineText( const QString & text );
+
+ QString fileURL() const;
+ void setFileURL( const QString & url );
+
+ QString commandURL() const;
+ void setCommandURL( const QString & url );
+
+ /**
+ Conveniece method.
+ @return a Signature object representing the state of the widgets.
+ **/
+ Signature signature() const;
+ /**
+ Convenience method. Sets the widgets according to @p sig
+ **/
+ void setSignature( const Signature & sig );
+
+ protected slots:
+ void slotEnableEditButton( const QString & );
+ void slotEdit();
+
+ protected:
+ QCheckBox * mEnableCheck;
+ QComboBox * mSourceCombo;
+ KURLRequester * mFileRequester;
+ QPushButton * mEditButton;
+ KLineEdit * mCommandEdit;
+ QTextEdit * mTextEdit;
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_SIGNATURECONFIGURATOR_H__
+
+
diff --git a/kmail/simplestringlisteditor.cpp b/kmail/simplestringlisteditor.cpp
new file mode 100644
index 00000000..e8dc5647
--- /dev/null
+++ b/kmail/simplestringlisteditor.cpp
@@ -0,0 +1,307 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ simplestringlisteditor.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2001 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "simplestringlisteditor.h"
+
+#include <kinputdialog.h>
+#include <kiconloader.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kpushbutton.h>
+
+#include <qlayout.h>
+
+
+//********************************************************
+// SimpleStringListEditor
+//********************************************************
+
+// small helper function:
+static inline QListBoxItem * findSelectedItem( QListBox * lb ) {
+ QListBoxItem * item = 0;
+ for ( item = lb->firstItem() ; item && !item->isSelected() ;
+ item = item->next() ) ;
+ return item;
+}
+
+SimpleStringListEditor::SimpleStringListEditor( QWidget * parent,
+ const char * name,
+ ButtonCode buttons,
+ const QString & addLabel,
+ const QString & removeLabel,
+ const QString & modifyLabel,
+ const QString & addDialogLabel )
+ : QWidget( parent, name ),
+ mAddButton(0), mRemoveButton(0), mModifyButton(0),
+ mUpButton(0), mDownButton(0),
+ mAddDialogLabel( addDialogLabel.isEmpty() ?
+ i18n("New entry:") : addDialogLabel )
+{
+ QHBoxLayout * hlay = new QHBoxLayout( this, 0, KDialog::spacingHint() );
+
+ mListBox = new QListBox( this );
+ hlay->addWidget( mListBox, 1 );
+
+ if ( buttons == None )
+ kdDebug(5006) << "SimpleStringListBox called with no buttons. "
+ "Consider using a plain QListBox instead!" << endl;
+
+ QVBoxLayout * vlay = new QVBoxLayout( hlay ); // inherits spacing
+
+ if ( buttons & Add ) {
+ if ( addLabel.isEmpty() )
+ mAddButton = new QPushButton( i18n("&Add..."), this );
+ else
+ mAddButton = new QPushButton( addLabel, this );
+ mAddButton->setAutoDefault( false );
+ vlay->addWidget( mAddButton );
+ connect( mAddButton, SIGNAL(clicked()),
+ this, SLOT(slotAdd()) );
+ }
+
+ if ( buttons & Remove ) {
+ if ( removeLabel.isEmpty() )
+ mRemoveButton = new QPushButton( i18n("&Remove"), this );
+ else
+ mRemoveButton = new QPushButton( removeLabel, this );
+ mRemoveButton->setAutoDefault( false );
+ mRemoveButton->setEnabled( false ); // no selection yet
+ vlay->addWidget( mRemoveButton );
+ connect( mRemoveButton, SIGNAL(clicked()),
+ this, SLOT(slotRemove()) );
+ }
+
+ if ( buttons & Modify ) {
+ if ( modifyLabel.isEmpty() )
+ mModifyButton = new QPushButton( i18n("&Modify..."), this );
+ else
+ mModifyButton = new QPushButton( modifyLabel, this );
+ mModifyButton->setAutoDefault( false );
+ mModifyButton->setEnabled( false ); // no selection yet
+ vlay->addWidget( mModifyButton );
+ connect( mModifyButton, SIGNAL(clicked()),
+ this, SLOT(slotModify()) );
+ connect( mListBox, SIGNAL( doubleClicked( QListBoxItem* ) ),
+ this, SLOT( slotModify() ) );
+ }
+
+ if ( buttons & Up ) {
+ if ( !(buttons & Down) )
+ kdDebug(5006) << "Are you sure you want to use an Up button "
+ "without a Down button??" << endl;
+ mUpButton = new KPushButton( QString::null, this );
+ mUpButton->setIconSet( BarIconSet( "up", KIcon::SizeSmall ) );
+ mUpButton->setAutoDefault( false );
+ mUpButton->setEnabled( false ); // no selection yet
+ vlay->addWidget( mUpButton );
+ connect( mUpButton, SIGNAL(clicked()),
+ this, SLOT(slotUp()) );
+ }
+
+ if ( buttons & Down ) {
+ if ( !(buttons & Up) )
+ kdDebug(5006) << "Are you sure you want to use a Down button "
+ "without an Up button??" << endl;
+ mDownButton = new KPushButton( QString::null, this );
+ mDownButton->setIconSet( BarIconSet( "down", KIcon::SizeSmall ) );
+ mDownButton->setAutoDefault( false );
+ mDownButton->setEnabled( false ); // no selection yet
+ vlay->addWidget( mDownButton );
+ connect( mDownButton, SIGNAL(clicked()),
+ this, SLOT(slotDown()) );
+ }
+
+ vlay->addStretch( 1 ); // spacer
+
+ connect( mListBox, SIGNAL(selectionChanged()),
+ this, SLOT(slotSelectionChanged()) );
+}
+
+void SimpleStringListEditor::setStringList( const QStringList & strings ) {
+ mListBox->clear();
+ mListBox->insertStringList( strings );
+}
+
+void SimpleStringListEditor::appendStringList( const QStringList & strings ) {
+ mListBox->insertStringList( strings );
+}
+
+QStringList SimpleStringListEditor::stringList() const {
+ QStringList result;
+ for ( QListBoxItem * item = mListBox->firstItem() ;
+ item ; item = item->next() )
+ result << item->text();
+ return result;
+}
+
+void SimpleStringListEditor::setButtonText( ButtonCode button,
+ const QString & text ) {
+ switch ( button ) {
+ case Add:
+ if ( !mAddButton ) break;
+ mAddButton->setText( text );
+ return;
+ case Remove:
+ if ( !mRemoveButton ) break;
+ mRemoveButton->setText( text );
+ return;
+ case Modify:
+ if ( !mModifyButton ) break;
+ mModifyButton->setText( text );
+ return;
+ case Up:
+ case Down:
+ kdDebug(5006) << "SimpleStringListEditor: Cannot change text of "
+ "Up and Down buttons: they don't contains text!" << endl;
+ return;
+ default:
+ if ( button & All )
+ kdDebug(5006) << "SimpleStringListEditor::setButtonText: No such button!"
+ << endl;
+ else
+ kdDebug(5006) << "SimpleStringListEditor::setButtonText: Can only set "
+ "text for one button at a time!" << endl;
+ return;
+ }
+
+ kdDebug(5006) << "SimpleStringListEditor::setButtonText: the requested "
+ "button has not been created!" << endl;
+}
+
+void SimpleStringListEditor::slotAdd() {
+ bool ok = false;
+ QString newEntry = KInputDialog::getText( i18n("New Value"),
+ mAddDialogLabel, QString::null,
+ &ok, this );
+ // let the user verify the string before adding
+ emit aboutToAdd( newEntry );
+ if ( ok && !newEntry.isEmpty() )
+ mListBox->insertItem( newEntry );
+ emit changed();
+}
+
+void SimpleStringListEditor::slotRemove() {
+ delete findSelectedItem( mListBox ); // delete 0 is well-behaved...
+ emit changed();
+}
+
+void SimpleStringListEditor::slotModify() {
+ QListBoxItem * item = findSelectedItem( mListBox );
+ if ( !item ) return;
+
+ bool ok = false;
+ QString newText = KInputDialog::getText( i18n("Change Value"),
+ mAddDialogLabel, item->text(),
+ &ok, this );
+ emit aboutToAdd( newText );
+ if ( !ok || newText.isEmpty() || newText == item->text() ) return;
+
+ int index = mListBox->index( item );
+ delete item;
+ mListBox->insertItem( newText, index );
+ mListBox->setCurrentItem( index );
+ emit changed();
+}
+
+void SimpleStringListEditor::slotUp() {
+ QListBoxItem * item = findSelectedItem( mListBox );
+ if ( !item || !item->prev() ) return;
+
+ // find the item that we want to insert after:
+ QListBoxItem * pprev = item->prev()->prev();
+ // take the item from it's current position...
+ mListBox->takeItem( item );
+ // ...and insert it after the above mentioned item:
+ mListBox->insertItem( item, pprev );
+ // make sure the old item is still the current one:
+ mListBox->setCurrentItem( item );
+ // enable and disable controls:
+ if ( mRemoveButton )
+ mRemoveButton->setEnabled( true );
+ if ( mModifyButton )
+ mModifyButton->setEnabled( true );
+ if ( mUpButton )
+ mUpButton->setEnabled( item->prev() );
+ if ( mDownButton )
+ mDownButton->setEnabled( true );
+ emit changed();
+}
+
+void SimpleStringListEditor::slotDown() {
+ QListBoxItem * item = findSelectedItem( mListBox );
+ if ( !item || !item->next() ) return;
+
+ // find the item that we want to insert after:
+ QListBoxItem * next = item->next();
+ // take the item from it's current position...
+ mListBox->takeItem( item );
+ // ...and insert it after the above mentioned item:
+ if ( next )
+ mListBox->insertItem( item, next );
+ else
+ mListBox->insertItem( item );
+ // make sure the old item is still the current one:
+ mListBox->setCurrentItem( item );
+ // enable and disable controls:
+ if ( mRemoveButton )
+ mRemoveButton->setEnabled( true );
+ if ( mModifyButton )
+ mModifyButton->setEnabled( true );
+ if ( mUpButton )
+ mUpButton->setEnabled( true );
+ if ( mDownButton )
+ mDownButton->setEnabled( item->next() );
+ emit changed();
+}
+
+void SimpleStringListEditor::slotSelectionChanged() {
+ // try to find a selected item:
+ QListBoxItem * item = findSelectedItem( mListBox );
+
+ // if there is one, item will be non-null (ie. true), else 0
+ // (ie. false):
+ if ( mRemoveButton )
+ mRemoveButton->setEnabled( item );
+ if ( mModifyButton )
+ mModifyButton->setEnabled( item );
+ if ( mUpButton )
+ mUpButton->setEnabled( item && item->prev() );
+ if ( mDownButton )
+ mDownButton->setEnabled( item && item->next() );
+}
+
+
+
+#include "simplestringlisteditor.moc"
diff --git a/kmail/simplestringlisteditor.h b/kmail/simplestringlisteditor.h
new file mode 100644
index 00000000..fc9c0367
--- /dev/null
+++ b/kmail/simplestringlisteditor.h
@@ -0,0 +1,106 @@
+/* -*- c++ -*-
+ simplestringlisteditor.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2001 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef _SIMPLESTRINGLISTEDITOR_H_
+#define _SIMPLESTRINGLISTEDITOR_H_
+
+#include <qwidget.h>
+#include <qstringlist.h>
+#include <qstring.h>
+
+class QListBox;
+class QPushButton;
+
+//
+//
+// SimpleStringListEditor (a listbox with "add..." and "remove" buttons)
+//
+//
+
+class SimpleStringListEditor : public QWidget {
+ Q_OBJECT
+public:
+ enum ButtonCode {
+ None = 0x00, Add = 0x01,
+ Remove = 0x02, Modify = 0x04,
+ Up = 0x08, Down = 0x10,
+ All = Add|Remove|Modify|Up|Down,
+ Unsorted = Add|Remove|Modify
+ };
+
+ /** Constructor. Populates the list with @p strings. */
+ SimpleStringListEditor( QWidget * parent=0, const char * name=0,
+ ButtonCode buttons=Unsorted,
+ const QString & addLabel=QString::null,
+ const QString & removeLabel=QString::null,
+ const QString & modifyLabel=QString::null,
+ const QString & addDialogLabel=QString::null );
+
+ /** Sets the list of strings displayed to @p strings */
+ void setStringList( const QStringList & strings );
+ /** Adds @p strings to the list of displayed strings */
+ void appendStringList( const QStringList & strings );
+ /** Retrieves the current list of strings */
+ QStringList stringList() const;
+
+ /** Sets the text of button @p button to @p text */
+ void setButtonText( ButtonCode button, const QString & text );
+
+signals:
+ /** Connected slots can alter the argument to be added or set the
+ argument to QString::null to suppress adding.
+ */
+ void aboutToAdd(QString&);
+ void changed(void);
+
+protected slots:
+ void slotAdd();
+ void slotRemove();
+ void slotModify();
+ void slotUp();
+ void slotDown();
+
+ void slotSelectionChanged();
+
+protected:
+ QListBox *mListBox;
+ QPushButton *mAddButton;
+ QPushButton *mRemoveButton;
+ QPushButton *mModifyButton;
+ QPushButton *mUpButton;
+ QPushButton *mDownButton;
+ const QString mAddDialogLabel;
+};
+
+
+
+
+#endif // _SIMPLESTRINGLISTEDITOR_H_
diff --git a/kmail/smimeconfiguration.ui b/kmail/smimeconfiguration.ui
new file mode 100644
index 00000000..c1f2ca40
--- /dev/null
+++ b/kmail/smimeconfiguration.ui
@@ -0,0 +1,413 @@
+<!DOCTYPE UI><UI version="3.2" stdsetdef="1">
+<class>SMimeConfiguration</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>SMimeConfiguration</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>557</width>
+ <height>586</height>
+ </rect>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QRadioButton">
+ <property name="name">
+ <cstring>CRLRB</cstring>
+ </property>
+ <property name="text">
+ <string>Validate certificates using CRLs</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>If this option is selected, S/MIME certificates are validated using Certificate Revocation Lists (CRLs).</string>
+ </property>
+ </widget>
+ <widget class="QRadioButton">
+ <property name="name">
+ <cstring>OCSPRB</cstring>
+ </property>
+ <property name="text">
+ <string>Validate certificates online (OCSP)</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>If this option is selected, S/MIME certificates are validated online using the Online Certificates Status Protocol (OCSP). Fill in the URL of the OCSP responder below.</string>
+ </property>
+ </widget>
+ <widget class="QGroupBox">
+ <property name="name">
+ <cstring>OCSPGroupBox</cstring>
+ </property>
+ <property name="title">
+ <string>Online Certificate Validation</string>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout4</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>textLabel1</cstring>
+ </property>
+ <property name="text">
+ <string>OCSP responder URL:</string>
+ </property>
+ </widget>
+ <widget class="QLineEdit">
+ <property name="name">
+ <cstring>OCSPResponderURL</cstring>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Enter here the address of the server for online validation of certificates (OCSP responder). The URL is usually starting with http://.</string>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout5</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>textLabel2</cstring>
+ </property>
+ <property name="text">
+ <string>OCSP responder signature:</string>
+ </property>
+ </widget>
+ <widget class="Kleo::KeyRequester">
+ <property name="name">
+ <cstring>OCSPResponderSignature</cstring>
+ </property>
+ </widget>
+ <spacer>
+ <property name="name">
+ <cstring>spacer7</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>150</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </hbox>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>ignoreServiceURLCB</cstring>
+ </property>
+ <property name="text">
+ <string>Ignore service URL of certificates</string>
+ </property>
+ </widget>
+ </vbox>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>doNotCheckCertPolicyCB</cstring>
+ </property>
+ <property name="text">
+ <string>Do not check certificate policies</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>By default GnuPG uses the file ~/.gnupg/policies.txt to check if a certificate policy is allowed. If this option is selected, policies are not checked.</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>neverConsultCB</cstring>
+ </property>
+ <property name="text">
+ <string>Never consult a CRL</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>If this option is checked, Certificate Revocation Lists are never used to validate S/MIME certificates.</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>fetchMissingCB</cstring>
+ </property>
+ <property name="text">
+ <string>Fetch missing issuer certificates</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>If this option is checked, missing issuer certificates are fetched when necessary (this applies to both validation methods, CRLs and OCSP)</string>
+ </property>
+ </widget>
+ <widget class="QButtonGroup">
+ <property name="name">
+ <cstring>HTTPGroupBox</cstring>
+ </property>
+ <property name="title">
+ <string>HTTP Requests</string>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>disableHTTPCB</cstring>
+ </property>
+ <property name="text">
+ <string>Do not perform any HTTP requests</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Entirely disables the use of HTTP for S/MIME.</string>
+ </property>
+ </widget>
+ <widget class="QFrame">
+ <property name="name">
+ <cstring>frameHTTP</cstring>
+ </property>
+ <property name="frameShape">
+ <enum>NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Plain</enum>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <widget class="QLayoutWidget" row="1" column="0" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>layout5</cstring>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel" row="0" column="1">
+ <property name="name">
+ <cstring>systemHTTPProxy</cstring>
+ </property>
+ <property name="text">
+ <string>no proxy</string>
+ </property>
+ </widget>
+ <widget class="QRadioButton" row="1" column="0">
+ <property name="name">
+ <cstring>useCustomHTTPProxyRB</cstring>
+ </property>
+ <property name="text">
+ <string>Use this proxy for HTTP requests: </string>
+ </property>
+ </widget>
+ <widget class="QRadioButton" row="0" column="0">
+ <property name="name">
+ <cstring>honorHTTPProxyRB</cstring>
+ </property>
+ <property name="text">
+ <string>Use system HTTP proxy:</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>If this option is selected, the value of the HTTP proxy shown on the right (which comes from the environment variable http_proxy) will be used for any HTTP request.</string>
+ </property>
+ </widget>
+ <widget class="QLineEdit" row="1" column="1">
+ <property name="name">
+ <cstring>customHTTPProxy</cstring>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Enter here the location of your HTTP Proxy, which will be used for all HTTP requests relating to S/MIME. The syntax is host:port, for instance myproxy.nowhere.com:3128.</string>
+ </property>
+ </widget>
+ </grid>
+ </widget>
+ <widget class="QCheckBox" row="0" column="0">
+ <property name="name">
+ <cstring>ignoreHTTPDPCB</cstring>
+ </property>
+ <property name="text">
+ <string>Ignore HTTP CRL distribution point of certificates</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>When looking for the location of a CRL, the to-be-tested certificate usually contains what are known as "CRL Distribution Point" (DP) entries, which are URLs describing the way to access the URL. The first found DP entry is used. With this option all entries using the HTTP scheme are ignored when looking for a suitable DP.</string>
+ </property>
+ </widget>
+ </grid>
+ </widget>
+ </vbox>
+ </widget>
+ <widget class="QGroupBox">
+ <property name="name">
+ <cstring>LDAPGroupBox</cstring>
+ </property>
+ <property name="title">
+ <string>LDAP Requests</string>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>disableLDAPCB</cstring>
+ </property>
+ <property name="text">
+ <string>Do not perform any LDAP requests</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Entirely disables the use of LDAP for S/MIME.</string>
+ </property>
+ </widget>
+ <widget class="QFrame">
+ <property name="name">
+ <cstring>frameLDAP</cstring>
+ </property>
+ <property name="frameShape">
+ <enum>NoFrame</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Plain</enum>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>ignoreLDAPDPCB</cstring>
+ </property>
+ <property name="text">
+ <string>Ignore LDAP CRL distribution point of certificates</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>When looking for the location of a CRL, the to-be-tested certificate usually contains what are known as "CRL Distribution Point" (DP) entries, which are URLs describing the way to access the URL. The first found DP entry is used. With this option all entries using the LDAP scheme are ignored when looking for a suitable DP.</string>
+ </property>
+ </widget>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout5</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>customLDAPLabel</cstring>
+ </property>
+ <property name="text">
+ <string>Primary host for LDAP requests:</string>
+ </property>
+ </widget>
+ <widget class="QLineEdit">
+ <property name="name">
+ <cstring>customLDAPProxy</cstring>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Entering a LDAP server here will make all LDAP requests go to that server first. More precisely, this setting overrides any specified host and port part in a LDAP URL and will also be used if host and port have been omitted from the URL. Other LDAP servers will be used only if the connection to the "proxy" failed.
+The syntax is "HOST" or "HOST:PORT". If PORT is omitted, port 389 (standard LDAP port) is used.</string>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ </vbox>
+ </widget>
+ </vbox>
+ </widget>
+ <spacer>
+ <property name="name">
+ <cstring>spacer23</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>73</height>
+ </size>
+ </property>
+ </spacer>
+ </vbox>
+</widget>
+<customwidgets>
+ <customwidget>
+ <class>Kleo::KeyRequester</class>
+ <header location="local">../certmanager/lib/ui/keyrequester.h</header>
+ <sizehint>
+ <width>-1</width>
+ <height>-1</height>
+ </sizehint>
+ <container>0</container>
+ <sizepolicy>
+ <hordata>5</hordata>
+ <verdata>5</verdata>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </customwidget>
+</customwidgets>
+<connections>
+ <connection>
+ <sender>CRLRB</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>OCSPGroupBox</receiver>
+ <slot>setDisabled(bool)</slot>
+ </connection>
+ <connection>
+ <sender>OCSPRB</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>OCSPGroupBox</receiver>
+ <slot>setEnabled(bool)</slot>
+ </connection>
+ <connection>
+ <sender>useCustomHTTPProxyRB</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>customHTTPProxy</receiver>
+ <slot>setEnabled(bool)</slot>
+ </connection>
+ <connection>
+ <sender>honorHTTPProxyRB</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>customHTTPProxy</receiver>
+ <slot>setDisabled(bool)</slot>
+ </connection>
+ <connection>
+ <sender>disableLDAPCB</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>frameLDAP</receiver>
+ <slot>setDisabled(bool)</slot>
+ </connection>
+</connections>
+<layoutdefaults spacing="6" margin="11"/>
+</UI>
diff --git a/kmail/snippetconfig.cpp b/kmail/snippetconfig.cpp
new file mode 100644
index 00000000..2424bbd2
--- /dev/null
+++ b/kmail/snippetconfig.cpp
@@ -0,0 +1,25 @@
+/***************************************************************************
+ * snippet feature from kdevelop/plugins/snippet/ *
+ * *
+ * Copyright (C) 2007 by Robert Gruber *
+ * rgruber@users.sourceforge.net *
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+#include "snippetconfig.h"
+
+SnippetConfig::SnippetConfig()
+{
+}
+
+
+SnippetConfig::~SnippetConfig()
+{
+}
+
+
diff --git a/kmail/snippetconfig.h b/kmail/snippetconfig.h
new file mode 100644
index 00000000..9baad64c
--- /dev/null
+++ b/kmail/snippetconfig.h
@@ -0,0 +1,61 @@
+/***************************************************************************
+ * snippet feature from kdevelop/plugins/snippet/ *
+ * *
+ * Copyright (C) 2007 by Robert Gruber *
+ * rgruber@users.sourceforge.net *
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+#ifndef SNIPPETCONFIG_H
+#define SNIPPETCONFIG_H
+
+#include <qstring.h>
+#include <qrect.h>
+
+
+/**
+This class stores the values that can be configured via the
+KDevelop settings dialog
+@author Robert Gruber
+*/
+class SnippetConfig{
+public:
+ SnippetConfig();
+
+ ~SnippetConfig();
+
+ bool useToolTips() { return (bToolTip); };
+ int getInputMethod() { return (iInputMethod); };
+ QString getDelimiter() { return (strDelimiter); };
+ QRect getSingleRect() { return (rSingle); };
+ QRect getMultiRect() { return (rMulti); };
+ int getAutoOpenGroups() { return iAutoOpenGroups; }
+
+ void setToolTips(bool b) { bToolTip=b; };
+ void setInputMethod(int i) { iInputMethod=i; };
+ void setDelimiter(QString s) { strDelimiter=s; };
+ void setSingleRect(QRect r) {
+ rSingle = (r.isValid())?r:QRect();
+ }
+ void setMultiRect(QRect r) {
+ rMulti = (r.isValid())?r:QRect();
+ }
+ void setAutoOpenGroups(int autoopen) { iAutoOpenGroups = autoopen; }
+
+protected:
+ bool bToolTip;
+ int iInputMethod;
+ QString strDelimiter;
+ QRect rSingle;
+ QRect rMulti;
+ int iMultiBasicHeight;
+ int iMultiCount;
+ int iAutoOpenGroups;
+};
+
+#endif
diff --git a/kmail/snippetdlg.cpp b/kmail/snippetdlg.cpp
new file mode 100644
index 00000000..76b4fe17
--- /dev/null
+++ b/kmail/snippetdlg.cpp
@@ -0,0 +1,108 @@
+/***************************************************************************
+ * snippet feature from kdevelop/plugins/snippet/ *
+ * *
+ * Copyright (C) 2007 by Robert Gruber *
+ * rgruber@users.sourceforge.net *
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+#include "snippetdlg.h"
+
+#include <kdialog.h>
+#include <klocale.h>
+
+#include <qlabel.h>
+#include <qlayout.h>
+#include <kpushbutton.h>
+#include <ktextedit.h>
+#include "kkeybutton.h"
+#include "kactioncollection.h"
+#include "kmessagebox.h"
+
+/*
+ * Constructs a SnippetDlg as a child of 'parent', with the
+ * name 'name' and widget flags set to 'f'.
+ *
+ * The dialog will by default be modeless, unless you set 'modal' to
+ * TRUE to construct a modal dialog.
+ */
+SnippetDlg::SnippetDlg( KActionCollection* ac, QWidget* parent, const char* name, bool modal, WFlags fl )
+ : SnippetDlgBase( parent, name, modal, fl ), actionCollection( ac )
+{
+ if ( !name )
+ setName( "SnippetDlg" );
+
+ textLabel3 = new QLabel( this, "textLabel3" );
+ keyButton = new KKeyButton( this );
+ connect( keyButton, SIGNAL( capturedShortcut( const KShortcut& ) ),
+ this, SLOT( slotCapturedShortcut( const KShortcut& ) ) );
+
+ layout3->addWidget( textLabel3, 7, 0 );
+ layout3->addWidget( keyButton, 7, 1 );
+
+ // tab order
+ setTabOrder( snippetText, keyButton );
+ setTabOrder( keyButton, btnAdd );
+ setTabOrder( btnAdd, btnCancel );
+
+ textLabel3->setBuddy( keyButton );
+ languageChange();
+}
+
+/*
+ * Destroys the object and frees any allocated resources
+ */
+SnippetDlg::~SnippetDlg()
+{
+ // no need to delete child widgets, Qt does it all for us
+}
+
+/*
+ * Sets the strings of the subwidgets using the current
+ * language.
+ */
+void SnippetDlg::languageChange()
+{
+ textLabel3->setText( tr2i18n( "Sh&ortcut:" ) );
+}
+
+static bool shortcutIsValid( const KActionCollection* actionCollection, const KShortcut &sc )
+{
+ KActionPtrList actions = actionCollection->actions();
+ KActionPtrList::Iterator it( actions.begin() );
+ for ( ; it != actions.end(); it++ ) {
+ if ( (*it)->shortcut() == sc ) return false;
+ }
+ return true;
+}
+
+void SnippetDlg::slotCapturedShortcut( const KShortcut& sc )
+{
+
+ if ( sc == keyButton->shortcut() ) return;
+ if ( sc.toString().isNull() ) {
+ // null is fine, that's reset, but sc.іsNull() will be false :/
+ keyButton->setShortcut( KShortcut::null(), false );
+ } else {
+ if( !shortcutIsValid( actionCollection, sc ) ) {
+ QString msg( i18n( "The selected shortcut is already used, "
+ "please select a different one." ) );
+ KMessageBox::sorry( this, msg );
+ } else {
+ keyButton->setShortcut( sc, false );
+ }
+ }
+}
+
+void SnippetDlg::setShowShortcut( bool show )
+{
+ textLabel3->setShown( show );
+ keyButton->setShown( show );
+}
+
+#include "snippetdlg.moc"
diff --git a/kmail/snippetdlg.h b/kmail/snippetdlg.h
new file mode 100644
index 00000000..1a9a1f02
--- /dev/null
+++ b/kmail/snippetdlg.h
@@ -0,0 +1,42 @@
+/****************************************************************************
+** Form interface generated from reading ui file '/Users/till/Documents/Code/kde/enterprise/kdepim/kmail/snippetdlg.ui'
+**
+** Created: Tue Sep 25 16:03:02 2007
+** by: The User Interface Compiler ($Id: qt/main.cpp 3.3.8 edited Jan 11 14:47 $)
+**
+** WARNING! All changes made in this file will be lost!
+****************************************************************************/
+
+#ifndef SNIPPETDLG_H
+#define SNIPPETDLG_H
+
+#include "snippetdlgbase.h"
+
+class KKeyButton;
+class KActionCollection;
+class KShortcut;
+
+class SnippetDlg : public SnippetDlgBase
+{
+ Q_OBJECT
+
+public:
+ SnippetDlg( KActionCollection* ac, QWidget* parent = 0, const char* name = 0, bool modal = FALSE, WFlags fl = 0 );
+ ~SnippetDlg();
+
+ void setShowShortcut( bool show );
+
+ QLabel* textLabel3;
+ QLabel* textLabelGroup;
+ KKeyButton* keyButton;
+ KActionCollection* actionCollection;
+
+private slots:
+ void slotCapturedShortcut( const KShortcut& );
+
+protected slots:
+ virtual void languageChange();
+
+};
+
+#endif // SNIPPETDLG_H
diff --git a/kmail/snippetdlgbase.ui b/kmail/snippetdlgbase.ui
new file mode 100644
index 00000000..5d87b564
--- /dev/null
+++ b/kmail/snippetdlgbase.ui
@@ -0,0 +1,177 @@
+<!DOCTYPE UI><UI version="3.3" stdsetdef="1">
+<class>SnippetDlgBase</class>
+<widget class="QDialog">
+ <property name="name">
+ <cstring>SnippetDlgBase</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>344</width>
+ <height>289</height>
+ </rect>
+ </property>
+ <property name="caption">
+ <string>Add Snippet</string>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLayoutWidget" row="1" column="0">
+ <property name="name">
+ <cstring>layout5</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <spacer>
+ <property name="name">
+ <cstring>spacer1</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="KPushButton">
+ <property name="name">
+ <cstring>btnAdd</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Add</string>
+ </property>
+ </widget>
+ <widget class="KPushButton">
+ <property name="name">
+ <cstring>btnCancel</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Cancel</string>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ <widget class="QLayoutWidget" row="0" column="0">
+ <property name="name">
+ <cstring>layout3</cstring>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="KLineEdit" row="0" column="1">
+ <property name="name">
+ <cstring>snippetName</cstring>
+ </property>
+ </widget>
+ <spacer row="4" column="0">
+ <property name="name">
+ <cstring>spacer2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>80</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QLabel" row="0" column="0">
+ <property name="name">
+ <cstring>textLabel1</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Name:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>snippetName</cstring>
+ </property>
+ </widget>
+ <widget class="QLabel" row="2" column="0">
+ <property name="name">
+ <cstring>textLabel2</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Snippet:</string>
+ </property>
+ <property name="alignment">
+ <set>AlignTop</set>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>snippetText</cstring>
+ </property>
+ </widget>
+ <widget class="QLabel" row="1" column="0">
+ <property name="name">
+ <cstring>textLabelGroup</cstring>
+ </property>
+ <property name="text">
+ <string>Group:</string>
+ </property>
+ </widget>
+ <widget class="KComboBox" row="1" column="1">
+ <property name="name">
+ <cstring>cbGroup</cstring>
+ </property>
+ </widget>
+ <widget class="KTextEdit" row="2" column="1" rowspan="3" colspan="1">
+ <property name="name">
+ <cstring>snippetText</cstring>
+ </property>
+ <property name="font">
+ <font>
+ <family>Courier</family>
+ <pointsize>11</pointsize>
+ </font>
+ </property>
+ </widget>
+ </grid>
+ </widget>
+ </grid>
+</widget>
+<connections>
+ <connection>
+ <sender>btnAdd</sender>
+ <signal>clicked()</signal>
+ <receiver>SnippetDlgBase</receiver>
+ <slot>accept()</slot>
+ </connection>
+ <connection>
+ <sender>btnCancel</sender>
+ <signal>clicked()</signal>
+ <receiver>SnippetDlgBase</receiver>
+ <slot>reject()</slot>
+ </connection>
+</connections>
+<tabstops>
+ <tabstop>snippetName</tabstop>
+ <tabstop>cbGroup</tabstop>
+ <tabstop>snippetText</tabstop>
+ <tabstop>btnAdd</tabstop>
+ <tabstop>btnCancel</tabstop>
+</tabstops>
+<layoutdefaults spacing="6" margin="11"/>
+<includehints>
+ <includehint>kpushbutton.h</includehint>
+ <includehint>kpushbutton.h</includehint>
+ <includehint>klineedit.h</includehint>
+ <includehint>kcombobox.h</includehint>
+ <includehint>ktextedit.h</includehint>
+</includehints>
+</UI>
diff --git a/kmail/snippetitem.cpp b/kmail/snippetitem.cpp
new file mode 100644
index 00000000..46d28c94
--- /dev/null
+++ b/kmail/snippetitem.cpp
@@ -0,0 +1,154 @@
+/***************************************************************************
+ * snippet feature from kdevelop/plugins/snippet/ *
+ * *
+ * Copyright (C) 2007 by Robert Gruber *
+ * rgruber@users.sourceforge.net *
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+#include "snippetitem.h"
+
+#include <kaction.h>
+
+#include <qstring.h>
+
+SnippetItem::SnippetItem(QListView * parent, QString name, QString text )
+ : QListViewItem( parent, name ), action(0)
+{
+ strName = name;
+ strText = text;
+ iParent = -1;
+}
+
+SnippetItem::SnippetItem(QListViewItem * parent, QString name, QString text)
+ : QListViewItem( parent, name ), action(0)
+{
+ strName = name;
+ strText = text;
+ iParent = ((SnippetGroup *)parent)->getId();
+}
+
+SnippetItem::~SnippetItem()
+{
+ if ( action ) {
+ action->unplugAll();
+ delete action;
+ }
+}
+
+
+/*!
+ \fn SnippetItem::getName()
+ */
+QString SnippetItem::getName()
+{
+ return strName;
+}
+
+
+/*!
+ \fn SnippetItem::getText
+ */
+QString SnippetItem::getText()
+{
+ return strText;
+}
+
+
+/*!
+ \fn SnippetItem::setText(QString text)
+ */
+void SnippetItem::setText(QString text)
+{
+ strText = text;
+}
+
+
+/*!
+ \fn SnippetItem::setName(QString name)
+ */
+void SnippetItem::setName(QString name)
+{
+ strName = name;
+}
+
+void SnippetItem::resetParent()
+{
+ SnippetGroup * group = dynamic_cast<SnippetGroup*>(parent());
+ if (group)
+ iParent = group->getId();
+}
+
+
+KAction* SnippetItem::getAction()
+{
+ return action;
+}
+
+void SnippetItem::setAction(KAction * anAction)
+{
+ action = anAction;
+}
+
+void SnippetItem::slotExecute()
+{
+ emit execute( this );
+}
+
+
+SnippetItem * SnippetItem::findItemByName(QString name, QPtrList<SnippetItem> &list)
+{
+ for ( SnippetItem * item = list.first(); item; item = list.next() ) { //write the snippet-list
+ if (item->getName() == name)
+ return item;
+ }
+ return NULL;
+}
+
+SnippetGroup * SnippetItem::findGroupById(int id, QPtrList<SnippetItem> &list)
+{
+ for ( SnippetItem * item = list.first(); item; item = list.next() ) { //write the snippet-list
+ SnippetGroup * group = dynamic_cast<SnippetGroup*>(item);
+ if (group && group->getId() == id)
+ return group;
+ }
+ return NULL;
+}
+
+
+/* * * * * * * * * * * * * * * * * * * *
+Deklaration for class SnippetGroup
+* * * * * * * * * * * * * * * * * * * */
+
+int SnippetGroup::iMaxId = 1;
+
+SnippetGroup::SnippetGroup(QListView * parent, QString name, int id)
+ : SnippetItem(parent, name, "GROUP")
+{
+ if (id > 0) {
+ iId = id;
+ if (id >= iMaxId)
+ iMaxId = id+1;
+ } else {
+ iId = iMaxId;
+ iMaxId++;
+ }
+}
+
+SnippetGroup::~SnippetGroup()
+{
+}
+
+void SnippetGroup::setId(int id)
+{
+ iId = id;
+ if (iId >= iMaxId)
+ iMaxId = iId+1;
+}
+
+#include "snippetitem.moc"
diff --git a/kmail/snippetitem.h b/kmail/snippetitem.h
new file mode 100644
index 00000000..ce7cd180
--- /dev/null
+++ b/kmail/snippetitem.h
@@ -0,0 +1,85 @@
+/***************************************************************************
+ * snippet feature from kdevelop/plugins/snippet/ *
+ * *
+ * Copyright (C) 2007 by Robert Gruber *
+ * rgruber@users.sourceforge.net *
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+#ifndef SNIPPETITEM_H
+#define SNIPPETITEM_H
+
+#include <klistview.h>
+#include <klocale.h>
+
+#include <qobject.h>
+
+class QString;
+class KAction;
+class SnippetGroup;
+
+
+/**
+This class represents one CodeSnippet-Item in the listview.
+It also holds the needed data for one snippet.
+@author Robert Gruber
+*/
+class SnippetItem : public QObject, public QListViewItem {
+friend class SnippetGroup;
+
+ Q_OBJECT
+public:
+ SnippetItem(QListViewItem * parent, QString name, QString text);
+
+ ~SnippetItem();
+ QString getName();
+ QString getText();
+ using QListViewItem::parent;
+ int getParent() { return iParent; }
+ void resetParent();
+ void setText(QString text);
+ void setName(QString name);
+ void setAction( KAction* );
+ KAction* getAction();
+ static SnippetItem * findItemByName(QString name, QPtrList<SnippetItem> &list);
+ static SnippetGroup * findGroupById(int id, QPtrList<SnippetItem> &list);
+signals:
+ void execute( QListViewItem * );
+public slots:
+ void slotExecute();
+
+private:
+ SnippetItem(QListView * parent, QString name, QString text);
+ QString strName;
+ QString strText;
+ int iParent;
+ KAction *action;
+};
+
+/**
+This class represents one group in the listview.
+It is derived from SnippetItem in order to allow storing
+it in the main QPtrList<SnippetItem>.
+@author Robert Gruber
+*/
+class SnippetGroup : public SnippetItem {
+public:
+ SnippetGroup(QListView * parent, QString name, int id);
+ ~SnippetGroup();
+
+ int getId() { return iId; }
+ static int getMaxId() { return iMaxId; }
+
+ void setId(int id);
+
+private:
+ static int iMaxId;
+ int iId;
+};
+
+#endif
diff --git a/kmail/snippetsettings.cpp b/kmail/snippetsettings.cpp
new file mode 100644
index 00000000..0e34fb73
--- /dev/null
+++ b/kmail/snippetsettings.cpp
@@ -0,0 +1,53 @@
+/***************************************************************************
+ * snippet feature from kdevelop/plugins/snippet/ *
+ * *
+ * Copyright (C) 2007 by Robert Gruber *
+ * rgruber@users.sourceforge.net *
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+#include <qstring.h>
+#include <klineedit.h>
+#include <qcheckbox.h>
+#include <qbuttongroup.h>
+
+#include "snippetsettings.h"
+#include "snippetwidget.h"
+
+
+SnippetSettings::SnippetSettings(QWidget *parent, const char *name)
+ : SnippetSettingsBase(parent, name)
+{
+ _widget = NULL;
+}
+
+SnippetSettings::SnippetSettings(SnippetWidget * w, QWidget *parent, const char *name)
+ : SnippetSettingsBase(parent, name)
+{
+ _cfg = w->getSnippetConfig();
+ _widget = w;
+}
+
+
+SnippetSettings::~SnippetSettings()
+{
+}
+
+
+/*!
+ \fn SnippetSettings::slotOKClicked()
+ */
+void SnippetSettings::slotOKClicked()
+{
+ _cfg->setToolTips(cbToolTip->isChecked());
+ _cfg->setDelimiter(leDelimiter->text());
+ _cfg->setInputMethod(btnGroup->selectedId());
+}
+
+
+#include "snippetsettings.moc"
diff --git a/kmail/snippetsettings.h b/kmail/snippetsettings.h
new file mode 100644
index 00000000..849cc438
--- /dev/null
+++ b/kmail/snippetsettings.h
@@ -0,0 +1,46 @@
+/***************************************************************************
+ * snippet feature from kdevelop/plugins/snippet/ *
+ * *
+ * Copyright (C) 2007 by Robert Gruber *
+ * rgruber@users.sourceforge.net *
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+#ifndef SNIPPETSETTINGS_H
+#define SNIPPETSETTINGS_H
+
+#include "snippetsettingsbase.h"
+
+class SnippetWidget;
+class SnippetConfig;
+
+/**
+This class is the widget that is showen in the
+KDevelop settings dialog. It inherits the
+class SnippetSettingsBase which is created by the
+same named .ui file
+@author Robert Gruber
+*/
+class SnippetSettings : public SnippetSettingsBase
+{
+Q_OBJECT
+public:
+ SnippetSettings(QWidget *parent = 0, const char *name = 0);
+ SnippetSettings(SnippetWidget * w, QWidget *parent = 0, const char *name = 0);
+
+ ~SnippetSettings();
+
+public slots:
+ void slotOKClicked();
+
+private:
+ SnippetConfig * _cfg;
+ SnippetWidget * _widget;
+};
+
+#endif
diff --git a/kmail/snippetsettingsbase.ui b/kmail/snippetsettingsbase.ui
new file mode 100644
index 00000000..9c5461fe
--- /dev/null
+++ b/kmail/snippetsettingsbase.ui
@@ -0,0 +1,184 @@
+<!DOCTYPE UI><UI version="3.3" stdsetdef="1">
+<class>SnippetSettingsBase</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>SnippetSettingsBase</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>574</width>
+ <height>398</height>
+ </rect>
+ </property>
+ <property name="caption">
+ <string>Snippet Settings</string>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QGroupBox" row="0" column="0">
+ <property name="name">
+ <cstring>groupBox1</cstring>
+ </property>
+ <property name="title">
+ <string>Tooltips</string>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QCheckBox" row="0" column="0">
+ <property name="name">
+ <cstring>cbToolTip</cstring>
+ </property>
+ <property name="text">
+ <string>Show snippet's text in &amp;tooltip</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Decides if a tooltip should be shown containing text from the bookmarked line</string>
+ </property>
+ </widget>
+ </grid>
+ </widget>
+ <spacer row="3" column="0">
+ <property name="name">
+ <cstring>spacer2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>70</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QButtonGroup" row="1" column="0">
+ <property name="name">
+ <cstring>buttonGroup1</cstring>
+ </property>
+ <property name="title">
+ <string>Variables</string>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QButtonGroup" row="1" column="0">
+ <property name="name">
+ <cstring>btnGroup</cstring>
+ </property>
+ <property name="title">
+ <string>Input Method for Variables</string>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QRadioButton" row="0" column="0">
+ <property name="name">
+ <cstring>rbSingle</cstring>
+ </property>
+ <property name="text">
+ <string>Single dialog for each variable within a snippet</string>
+ </property>
+ <property name="accel">
+ <string></string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>An input dialog will be displayed for every variable within a snippet</string>
+ </property>
+ </widget>
+ <widget class="QRadioButton" row="1" column="0">
+ <property name="name">
+ <cstring>rbAll</cstring>
+ </property>
+ <property name="text">
+ <string>One dialog for all variables within a snippet</string>
+ </property>
+ <property name="accel">
+ <string></string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>A single dialog will be displayed where you can enter the values for all variables within a snippet</string>
+ </property>
+ </widget>
+ </grid>
+ </widget>
+ <widget class="QLayoutWidget" row="0" column="0">
+ <property name="name">
+ <cstring>layout1</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>textLabel1</cstring>
+ </property>
+ <property name="text">
+ <string>Delimiter:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>kLineEdit1</cstring>
+ </property>
+ </widget>
+ <widget class="KLineEdit">
+ <property name="name">
+ <cstring>leDelimiter</cstring>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>40</width>
+ <height>32767</height>
+ </size>
+ </property>
+ <property name="maxLength">
+ <number>1</number>
+ </property>
+ </widget>
+ <spacer>
+ <property name="name">
+ <cstring>spacer3</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </hbox>
+ </widget>
+ </grid>
+ </widget>
+ </grid>
+</widget>
+<layoutdefaults spacing="6" margin="11"/>
+<includehints>
+ <includehint>klineedit.h</includehint>
+</includehints>
+</UI>
diff --git a/kmail/snippetwidget.cpp b/kmail/snippetwidget.cpp
new file mode 100644
index 00000000..1682e258
--- /dev/null
+++ b/kmail/snippetwidget.cpp
@@ -0,0 +1,957 @@
+/***************************************************************************
+ * snippet feature from kdevelop/plugins/snippet/ *
+ * *
+ * Copyright (C) 2007 by Robert Gruber *
+ * rgruber@users.sourceforge.net *
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+#include <kurl.h>
+#include <kdebug.h>
+#include <klocale.h>
+#include <qlayout.h>
+#include <kpushbutton.h>
+#include <klistview.h>
+#include <qheader.h>
+#include <klineedit.h>
+#include <ktextedit.h>
+#include <kmessagebox.h>
+#include <kconfig.h>
+#include <qtooltip.h>
+#include <kpopupmenu.h>
+#include <qregexp.h>
+#include <qinputdialog.h>
+#include <qlabel.h>
+#include <qcheckbox.h>
+#include <qwhatsthis.h>
+#include <qdragobject.h>
+#include <qtimer.h>
+#include <kcombobox.h>
+#include <kmedit.h>
+#include <kiconloader.h>
+#include <kshortcut.h>
+#include <kaction.h>
+#include <kkeybutton.h>
+
+#include "snippetdlg.h"
+#include "snippetitem.h"
+#include "snippetwidget.h"
+
+#include <cassert>
+
+SnippetWidget::SnippetWidget(KMEdit* editor, KActionCollection* actionCollection, QWidget* parent)
+ : KListView(parent, "snippet widget"), QToolTip( viewport() ),
+ mEditor( editor ), mActionCollection( actionCollection )
+{
+ // init the QPtrList
+ _list.setAutoDelete(TRUE);
+
+ // init the KListView
+ setSorting( -1 );
+ addColumn( "" );
+ setFullWidth(true);
+ header()->hide();
+ setAcceptDrops(true);
+ setDragEnabled(true);
+ setDropVisualizer(false);
+ setRootIsDecorated(true);
+
+ //connect the signals
+ connect( this, SIGNAL( contextMenuRequested ( QListViewItem *, const QPoint & , int ) ),
+ this, SLOT( showPopupMenu(QListViewItem *, const QPoint & , int ) ) );
+
+ connect( this, SIGNAL( doubleClicked (QListViewItem *) ),
+ this, SLOT( slotEdit( QListViewItem *) ) );
+ connect( this, SIGNAL( returnPressed (QListViewItem *) ),
+ this, SLOT( slotExecuted( QListViewItem *) ) );
+
+ connect( this, SIGNAL( dropped(QDropEvent *, QListViewItem *) ),
+ this, SLOT( slotDropped(QDropEvent *, QListViewItem *) ) );
+
+ connect( editor, SIGNAL( insertSnippet() ), this, SLOT( slotExecute() ));
+
+ _cfg = 0;
+
+ QTimer::singleShot(0, this, SLOT(initConfig()));
+}
+
+SnippetWidget::~SnippetWidget()
+{
+ writeConfig();
+ delete _cfg;
+
+ /* We need to delete the child items before the parent items
+ otherwise KDevelop would crash on exiting */
+ SnippetItem * item;
+ while (_list.count() > 0) {
+ for (item=_list.first(); item; item=_list.next()) {
+ if (item->childCount() == 0)
+ _list.remove(item);
+ }
+ }
+}
+
+
+/*!
+ \fn SnippetWidget::slotAdd()
+ Opens the dialog to add a snippet
+ */
+void SnippetWidget::slotAdd()
+{
+ //kdDebug(5006) << "Ender slotAdd()" << endl;
+ SnippetDlg dlg( mActionCollection, this, "SnippetDlg");
+
+ /*check if the user clicked a SnippetGroup
+ If not, we set the group variable to the SnippetGroup
+ which the selected item is a child of*/
+ SnippetGroup * group = dynamic_cast<SnippetGroup*>(selectedItem());
+ if ( !group && selectedItem() )
+ group = dynamic_cast<SnippetGroup*>(selectedItem()->parent());
+
+ /* still no group, let's make a default one */
+ if (!group ) {
+ if ( _list.isEmpty() ) {
+ group = new SnippetGroup(this, i18n("General"), SnippetGroup::getMaxId() );
+ _list.append( group );
+ } else {
+ group = dynamic_cast<SnippetGroup*>( _list.first() );
+ }
+ }
+ assert( group );
+
+ /*fill the combobox with the names of all SnippetGroup entries*/
+ for (SnippetItem *it=_list.first(); it; it=_list.next()) {
+ if (dynamic_cast<SnippetGroup*>(it)) {
+ dlg.cbGroup->insertItem(it->getName());
+ }
+ }
+ dlg.cbGroup->setCurrentText(group->getName());
+
+ if (dlg.exec() == QDialog::Accepted) {
+ group = dynamic_cast<SnippetGroup*>(SnippetItem::findItemByName(dlg.cbGroup->currentText(), _list));
+ _list.append( makeItem( group, dlg.snippetName->text(), dlg.snippetText->text(), dlg.keyButton->shortcut() ) );
+ }
+}
+
+/*!
+ \fn SnippetWidget::makeItem( SnippetItem* parent, const QString& name, const QString& text )
+ Helper factory method.
+ */
+SnippetItem* SnippetWidget::makeItem( SnippetItem* parent, const QString& name, const QString& text, const KShortcut& shortcut )
+{
+ SnippetItem * item = new SnippetItem(parent, name, text);
+ const QString actionName = i18n("Snippet %1").arg(name);
+ const QString normalizedName = QString(actionName).replace(" ", "_");
+ if ( !mActionCollection->action(normalizedName.utf8() ) ) {
+ KAction * action = new KAction( actionName, shortcut, item,
+ SLOT( slotExecute() ), mActionCollection,
+ normalizedName.utf8() );
+ item->setAction(action);
+ connect( item, SIGNAL( execute( QListViewItem* ) ),
+ this, SLOT( slotExecuted( QListViewItem* ) ) );
+ }
+ return item;
+}
+
+/*!
+ \fn SnippetWidget::slotAddGroup()
+ Opens the didalog to add a snippet
+ */
+void SnippetWidget::slotAddGroup()
+{
+ //kdDebug(5006) << "Ender slotAddGroup()" << endl;
+ SnippetDlg dlg( mActionCollection, this, "SnippetDlg");
+ dlg.setShowShortcut( false );
+ dlg.snippetText->setEnabled(false);
+ dlg.snippetText->setText("GROUP");
+ dlg.setCaption(i18n("Add Group"));
+ dlg.cbGroup->insertItem(i18n("All"));
+ dlg.cbGroup->setCurrentText(i18n("All"));
+
+ if (dlg.exec() == QDialog::Accepted) {
+ _list.append( new SnippetGroup(this, dlg.snippetName->text(), SnippetGroup::getMaxId() ) );
+ }
+}
+
+
+/*!
+ \fn SnippetWidget::slotRemove()
+ Removes the selected snippet
+ */
+void SnippetWidget::slotRemove()
+{
+ //get current data
+ QListViewItem * item = currentItem();
+ SnippetItem *snip = dynamic_cast<SnippetItem*>( item );
+ SnippetGroup *group = dynamic_cast<SnippetGroup*>( item );
+ if (!snip)
+ return;
+
+ if (group) {
+ if (group->childCount() > 0 &&
+ KMessageBox::warningContinueCancel(this, i18n("Do you really want to remove this group and all its snippets?"),QString::null,KStdGuiItem::del())
+ == KMessageBox::Cancel)
+ return;
+
+ SnippetItem *it=_list.first();
+ while ( it ) {
+ if (it->getParent() == group->getId()) {
+ SnippetItem *doomed = it;
+ it = _list.next();
+ //kdDebug(5006) << "remove " << doomed->getName() << endl;
+ _list.remove(doomed);
+ } else {
+ it = _list.next();
+ }
+ }
+ }
+
+ //kdDebug(5006) << "remove " << snip->getName() << endl;
+ _list.remove(snip);
+}
+
+
+
+/*!
+ \fn SnippetWidget::slotEdit()
+ Opens the dialog of editing the selected snippet
+ */
+void SnippetWidget::slotEdit( QListViewItem* item )
+{
+ if( item == 0 ) {
+ item = currentItem();
+ }
+
+ SnippetGroup *pGroup = dynamic_cast<SnippetGroup*>(item);
+ SnippetItem *pSnippet = dynamic_cast<SnippetItem*>( item );
+ if (!pSnippet || pGroup) /*selected item must be a SnippetItem but MUST not be a SnippetGroup*/
+ return;
+
+ //init the dialog
+ SnippetDlg dlg( mActionCollection, this, "SnippetDlg");
+ dlg.snippetName->setText(pSnippet->getName());
+ dlg.snippetText->setText(pSnippet->getText());
+ dlg.keyButton->setShortcut( pSnippet->getAction()->shortcut(), false );
+ dlg.btnAdd->setText(i18n("&Apply"));
+
+ dlg.setCaption(i18n("Edit Snippet"));
+ /*fill the combobox with the names of all SnippetGroup entries*/
+ for (SnippetItem *it=_list.first(); it; it=_list.next()) {
+ if (dynamic_cast<SnippetGroup*>(it)) {
+ dlg.cbGroup->insertItem(it->getName());
+ }
+ }
+ dlg.cbGroup->setCurrentText(SnippetItem::findGroupById(pSnippet->getParent(), _list)->getName());
+
+ if (dlg.exec() == QDialog::Accepted) {
+ //update the KListView and the SnippetItem
+ item->setText( 0, dlg.snippetName->text() );
+ pSnippet->setName( dlg.snippetName->text() );
+ pSnippet->setText( dlg.snippetText->text() );
+ pSnippet->getAction()->setShortcut( dlg.keyButton->shortcut());
+
+ /* if the user changed the parent we need to move the snippet */
+ if ( SnippetItem::findGroupById(pSnippet->getParent(), _list)->getName() != dlg.cbGroup->currentText() ) {
+ SnippetGroup * newGroup = dynamic_cast<SnippetGroup*>(SnippetItem::findItemByName(dlg.cbGroup->currentText(), _list));
+ pSnippet->parent()->takeItem(pSnippet);
+ newGroup->insertItem(pSnippet);
+ pSnippet->resetParent();
+ }
+
+ setSelected(item, TRUE);
+ }
+}
+
+/*!
+ \fn SnippetWidget::slotEditGroup()
+ Opens the dialog of editing the selected snippet-group
+ */
+void SnippetWidget::slotEditGroup()
+{
+ //get current data
+ QListViewItem * item = currentItem();
+
+ SnippetGroup *pGroup = dynamic_cast<SnippetGroup*>( item );
+ if (!pGroup) /*selected item MUST be a SnippetGroup*/
+ return;
+
+ //init the dialog
+ SnippetDlg dlg( mActionCollection, this, "SnippetDlg" );
+ dlg.setShowShortcut( false );
+ dlg.snippetName->setText(pGroup->getName());
+ dlg.snippetText->setText(pGroup->getText());
+ dlg.btnAdd->setText(i18n("&Apply"));
+ dlg.snippetText->setEnabled(FALSE);
+ dlg.setCaption(i18n("Edit Group"));
+ dlg.cbGroup->insertItem(i18n("All"));
+
+ if (dlg.exec() == QDialog::Accepted) {
+ //update the KListView and the SnippetGroup
+ item->setText( 0, dlg.snippetName->text() );
+ pGroup->setName( dlg.snippetName->text() );
+
+ setSelected(item, TRUE);
+ }
+}
+
+void SnippetWidget::slotExecuted(QListViewItem * item)
+{
+ if( item == 0 )
+ {
+ item = currentItem();
+ }
+
+ SnippetItem *pSnippet = dynamic_cast<SnippetItem*>( item );
+ if (!pSnippet || dynamic_cast<SnippetGroup*>(item))
+ return;
+
+ //process variables if any, then insert into the active view
+ insertIntoActiveView( parseText(pSnippet->getText(), _SnippetConfig.getDelimiter()) );
+}
+
+
+/*!
+ \fn SnippetWidget::insertIntoActiveView(QString text)
+ Inserts the parameter text into the activ view
+ */
+void SnippetWidget::insertIntoActiveView( const QString &text )
+{
+ mEditor->insert( text );
+}
+
+
+/*!
+ \fn SnippetWidget::writeConfig()
+ Write the cofig file
+ */
+void SnippetWidget::writeConfig()
+{
+ if( !_cfg )
+ return;
+ _cfg->deleteGroup("SnippetPart"); //this is neccessary otherwise delete entries will stay in list until
+ //they get overwritten by a more recent entry
+ _cfg->setGroup("SnippetPart");
+
+ QString strKeyName="";
+ QString strKeyText="";
+ QString strKeyId="";
+
+ int iSnipCount = 0;
+ int iGroupCount = 0;
+
+ SnippetItem* item=_list.first();
+ while ( item != 0) {
+
+ //kdDebug(5006) << "-->ERROR " << item->getName() << endl;
+ //kdDebug(5006) << "SnippetWidget::writeConfig() " << item->getName() << endl;
+ SnippetGroup * group = dynamic_cast<SnippetGroup*>(item);
+ if (group) {
+ //kdDebug(5006) << "-->GROUP " << item->getName() << group->getId() << " " << iGroupCount<< endl;
+ strKeyName=QString("snippetGroupName_%1").arg(iGroupCount);
+ strKeyId=QString("snippetGroupId_%1").arg(iGroupCount);
+
+ _cfg->writeEntry(strKeyName, group->getName());
+ _cfg->writeEntry(strKeyId, group->getId());
+
+ iGroupCount++;
+ } else if (dynamic_cast<SnippetItem*>(item)) {
+ //kdDebug(5006) << "-->ITEM " << item->getName() << item->getParent() << " " << iSnipCount << endl;
+ strKeyName=QString("snippetName_%1").arg(iSnipCount);
+ strKeyText=QString("snippetText_%1").arg(iSnipCount);
+ strKeyId=QString("snippetParent_%1").arg(iSnipCount);
+
+ _cfg->writeEntry(strKeyName, item->getName());
+ _cfg->writeEntry(strKeyText, item->getText());
+ _cfg->writeEntry(strKeyId, item->getParent());
+
+ KAction * action = item->getAction();
+ assert( action );
+ const KShortcut& sc = action->shortcut();
+ if (!sc.isNull() ) {
+ _cfg->writeEntry( QString("snippetShortcut_%1").arg(iSnipCount), sc.toString() );
+ }
+ iSnipCount++;
+ } else {
+ //kdDebug(5006) << "-->ERROR " << item->getName() << endl;
+ }
+ item = _list.next();
+ }
+ _cfg->writeEntry("snippetCount", iSnipCount);
+ _cfg->writeEntry("snippetGroupCount", iGroupCount);
+
+ int iCount = 1;
+ QMap<QString, QString>::Iterator it;
+ for ( it = _mapSaved.begin(); it != _mapSaved.end(); ++it ) { //write the saved variable values
+ if (it.data().length()<=0) continue; //is the saved value has no length -> no need to save it
+
+ strKeyName=QString("snippetSavedName_%1").arg(iCount);
+ strKeyText=QString("snippetSavedVal_%1").arg(iCount);
+
+ _cfg->writeEntry(strKeyName, it.key());
+ _cfg->writeEntry(strKeyText, it.data());
+
+ iCount++;
+ }
+ _cfg->writeEntry("snippetSavedCount", iCount-1);
+
+
+ _cfg->writeEntry( "snippetDelimiter", _SnippetConfig.getDelimiter() );
+ _cfg->writeEntry( "snippetVarInput", _SnippetConfig.getInputMethod() );
+ _cfg->writeEntry( "snippetToolTips", _SnippetConfig.useToolTips() );
+ _cfg->writeEntry( "snippetGroupAutoOpen", _SnippetConfig.getAutoOpenGroups() );
+
+ _cfg->writeEntry("snippetSingleRect", _SnippetConfig.getSingleRect() );
+ _cfg->writeEntry("snippetMultiRect", _SnippetConfig.getMultiRect() );
+
+ _cfg->sync();
+}
+
+/*!
+ \fn SnippetWidget::initConfig()
+ Initial read the cofig file
+ */
+void SnippetWidget::initConfig()
+{
+ if (_cfg == NULL)
+ _cfg = new KConfig("kmailsnippetrc", false, false);
+
+ _cfg->setGroup("SnippetPart");
+
+ QString strKeyName="";
+ QString strKeyText="";
+ QString strKeyId="";
+ SnippetItem *item;
+ SnippetGroup *group;
+
+ //kdDebug(5006) << "SnippetWidget::initConfig() " << endl;
+
+ //if entry doesn't get found, this will return -1 which we will need a bit later
+ int iCount = _cfg->readNumEntry("snippetGroupCount", -1);
+
+ for ( int i=0; i<iCount; i++) { //read the group-list
+ strKeyName=QString("snippetGroupName_%1").arg(i);
+ strKeyId=QString("snippetGroupId_%1").arg(i);
+
+ QString strNameVal="";
+ int iIdVal=-1;
+
+ strNameVal = _cfg->readEntry(strKeyName, "");
+ iIdVal = _cfg->readNumEntry(strKeyId, -1);
+ //kdDebug(5006) << "Read group " << " " << iIdVal << endl;
+
+ if (strNameVal != "" && iIdVal != -1) {
+ group = new SnippetGroup(this, strNameVal, iIdVal);
+ //kdDebug(5006) << "Created group " << group->getName() << " " << group->getId() << endl;
+ _list.append(group);
+ }
+ }
+
+ /* Check if the snippetGroupCount property has been found
+ if iCount is -1 this means, that the user has his snippets
+ stored without groups. Therefore we will call the
+ initConfigOldVersion() function below */
+ // should not happen since this function has been removed
+
+ if (iCount != -1) {
+ iCount = _cfg->readNumEntry("snippetCount", 0);
+ for ( int i=0; i<iCount; i++) { //read the snippet-list
+ strKeyName=QString("snippetName_%1").arg(i);
+ strKeyText=QString("snippetText_%1").arg(i);
+ strKeyId=QString("snippetParent_%1").arg(i);
+
+ QString strNameVal="";
+ QString strTextVal="";
+ int iParentVal = -1;
+
+ strNameVal = _cfg->readEntry(strKeyName, "");
+ strTextVal = _cfg->readEntry(strKeyText, "");
+ iParentVal = _cfg->readNumEntry(strKeyId, -1);
+ //kdDebug(5006) << "Read item " << strNameVal << " " << iParentVal << endl;
+
+ if (strNameVal != "" && strTextVal != "" && iParentVal != -1) {
+ KShortcut shortcut( _cfg->readEntry( QString("snippetShortcut_%1").arg(i), QString() ) );
+ item = makeItem( SnippetItem::findGroupById(iParentVal, _list), strNameVal, strTextVal, shortcut );
+ //kdDebug(5006) << "Created item " << item->getName() << " " << item->getParent() << endl;
+ _list.append(item);
+ }
+ }
+ } else {
+ //kdDebug() << "iCount is not -1";
+ }
+
+ iCount = _cfg->readNumEntry("snippetSavedCount", 0);
+
+ for ( int i=1; i<=iCount; i++) { //read the saved-values and store in QMap
+ strKeyName=QString("snippetSavedName_%1").arg(i);
+ strKeyText=QString("snippetSavedVal_%1").arg(i);
+
+ QString strNameVal="";
+ QString strTextVal="";
+
+ strNameVal = _cfg->readEntry(strKeyName, "");
+ strTextVal = _cfg->readEntry(strKeyText, "");
+
+ if (strNameVal != "" && strTextVal != "") {
+ _mapSaved[strNameVal] = strTextVal;
+ }
+ }
+
+
+ _SnippetConfig.setDelimiter( _cfg->readEntry("snippetDelimiter", "$") );
+ _SnippetConfig.setInputMethod( _cfg->readNumEntry("snippetVarInput", 0) );
+ _SnippetConfig.setToolTips( _cfg->readBoolEntry("snippetToolTips", true) );
+ _SnippetConfig.setAutoOpenGroups( _cfg->readNumEntry("snippetGroupAutoOpen", 1) );
+
+ _SnippetConfig.setSingleRect( _cfg->readRectEntry("snippetSingleRect", 0L) );
+ _SnippetConfig.setMultiRect( _cfg->readRectEntry("snippetMultiRect", 0L) );
+}
+
+/*!
+ \fn SnippetWidget::maybeTip( const QPoint & p )
+ Shows the Snippet-Text as ToolTip
+ */
+void SnippetWidget::maybeTip( const QPoint & p )
+{
+ SnippetItem * item = dynamic_cast<SnippetItem*>( itemAt( p ) );
+ if (!item)
+ return;
+
+ QRect r = itemRect( item );
+
+ if (r.isValid() &&
+ _SnippetConfig.useToolTips() )
+ {
+ tip( r, item->getText() ); //show the snippet's text
+ }
+}
+
+/*!
+ \fn SnippetWidget::showPopupMenu( QListViewItem * item, const QPoint & p, int )
+ Shows the Popup-Menu depending item is a valid pointer
+*/
+void SnippetWidget::showPopupMenu( QListViewItem * item, const QPoint & p, int )
+{
+ KPopupMenu popup;
+
+ SnippetItem * selectedItem = static_cast<SnippetItem *>(item);
+ if ( item ) {
+ popup.insertTitle( selectedItem->getName() );
+ if (dynamic_cast<SnippetGroup*>(item)) {
+ popup.insertItem( i18n("Edit &group..."), this, SLOT( slotEditGroup() ) );
+ } else {
+ popup.insertItem( SmallIconSet("editpaste"), i18n("&Paste"), this, SLOT( slotExecuted() ) );
+ popup.insertItem( SmallIconSet("edit"), i18n("&Edit..."), this, SLOT( slotEdit() ) );
+ }
+ popup.insertItem( SmallIconSet("editdelete"), i18n("&Remove"), this, SLOT( slotRemove() ) );
+ popup.insertSeparator();
+ } else {
+ popup.insertTitle(i18n("Text Snippets"));
+ }
+ popup.insertItem( i18n("&Add Snippet..."), this, SLOT( slotAdd() ) );
+ popup.insertItem( i18n("Add G&roup..."), this, SLOT( slotAddGroup() ) );
+
+ popup.exec(p);
+}
+
+
+// fn SnippetWidget::parseText(QString text, QString del)
+/*!
+ This function is used to parse the given QString for variables. If found the user will be prompted
+ for a replacement value. It returns the string text with all replacements made
+ */
+QString SnippetWidget::parseText(QString text, QString del)
+{
+ QString str = text;
+ QString strName = "";
+ QString strNew = "";
+ QString strMsg="";
+ int iFound = -1;
+ int iEnd = -1;
+ QMap<QString, QString> mapVar;
+ int iInMeth = _SnippetConfig.getInputMethod();
+ QRect rSingle = _SnippetConfig.getSingleRect();
+ QRect rMulti = _SnippetConfig.getMultiRect();
+
+ do {
+ iFound = text.find(QRegExp("\\"+del+"[A-Za-z-_0-9\\s]*\\"+del), iEnd+1); //find the next variable by this QRegExp
+ if (iFound >= 0) {
+ iEnd = text.find(del, iFound+1)+1;
+ strName = text.mid(iFound, iEnd-iFound);
+
+ if ( strName != del+del ) { //if not doubel-delimiter
+ if (iInMeth == 0) { //if input-method "single" is selected
+ if ( mapVar[strName].length() <= 0 ) { // and not already in map
+ strMsg=i18n("Please enter the value for <b>%1</b>:").arg(strName);
+ strNew = showSingleVarDialog( strName, &_mapSaved, rSingle );
+ if (strNew=="")
+ return ""; //user clicked Cancle
+ } else {
+ continue; //we have already handled this variable
+ }
+ } else {
+ strNew = ""; //for inputmode "multi" just reset new valaue
+ }
+ } else {
+ strNew = del; //if double-delimiter -> replace by single character
+ }
+
+ if (iInMeth == 0) { //if input-method "single" is selected
+ str.replace(strName, strNew);
+ }
+
+ mapVar[strName] = strNew;
+ }
+ } while (iFound != -1);
+
+ if (iInMeth == 1) { //check config, if input-method "multi" is selected
+ int w, bh, oh;
+ w = rMulti.width();
+ bh = rMulti.height();
+ oh = rMulti.top();
+ if (showMultiVarDialog( &mapVar, &_mapSaved, w, bh, oh )) { //generate and show the dialog
+ QMap<QString, QString>::Iterator it;
+ for ( it = mapVar.begin(); it != mapVar.end(); ++it ) { //walk through the map and do the replacement
+ str.replace(it.key(), it.data());
+ }
+ } else {
+ return "";
+ }
+
+ rMulti.setWidth(w); //this is a hack to save the dialog's dimensions in only one QRect
+ rMulti.setHeight(bh);
+ rMulti.setTop(oh);
+ rMulti.setLeft(0);
+ _SnippetConfig.setMultiRect(rMulti);
+ }
+
+ _SnippetConfig.setSingleRect(rSingle);
+
+ return str;
+}
+
+
+// fn SnippetWidget::showMultiVarDialog()
+/*!
+ This function constructs a dialog which contains a label and a linedit for every
+ variable that is stored in the given map except the double-delimiter entry
+ It return true if everything was ok and false if the user hit cancel
+ */
+bool SnippetWidget::showMultiVarDialog(QMap<QString, QString> * map, QMap<QString, QString> * mapSave,
+ int & iWidth, int & iBasicHeight, int & iOneHeight)
+{
+ //if no var -> no need to show
+ if (map->count() == 0)
+ return true;
+
+ //if only var is the double-delimiter -> no need to show
+ QMap<QString, QString>::Iterator it = map->begin();
+ if ( map->count() == 1 && it.data()==_SnippetConfig.getDelimiter()+_SnippetConfig.getDelimiter() )
+ return true;
+
+ QMap<QString, KTextEdit *> mapVar2Te; //this map will help keeping track which TEXTEDIT goes with which variable
+ QMap<QString, QCheckBox *> mapVar2Cb; //this map will help keeping track which CHECKBOX goes with which variable
+
+ // --BEGIN-- building a dynamic dialog
+ QDialog dlg(this);
+ dlg.setCaption(i18n("Enter Values for Variables"));
+
+ QGridLayout * layout = new QGridLayout( &dlg, 1, 1, 11, 6, "layout");
+ QGridLayout * layoutTop = new QGridLayout( 0, 1, 1, 0, 6, "layoutTop");
+ QGridLayout * layoutVar = new QGridLayout( 0, 1, 1, 0, 6, "layoutVar");
+ QGridLayout * layoutBtn = new QGridLayout( 0, 1, 1, 0, 6, "layoutBtn");
+
+ KTextEdit *te = NULL;
+ QLabel * labTop = NULL;
+ QCheckBox * cb = NULL;
+
+ labTop = new QLabel( &dlg, "label" );
+ labTop->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)1, (QSizePolicy::SizeType)0, 0, 0,
+ labTop->sizePolicy().hasHeightForWidth() ) );
+ labTop->setText(i18n("Enter the replacement values for these variables:"));
+ layoutTop->addWidget(labTop, 0, 0);
+ layout->addMultiCellLayout( layoutTop, 0, 0, 0, 1 );
+
+
+ int i = 0; //walk through the variable map and add
+ for ( it = map->begin(); it != map->end(); ++it ) { //a checkbox, a lable and a lineedit to the main layout
+ if (it.key() == _SnippetConfig.getDelimiter() + _SnippetConfig.getDelimiter())
+ continue;
+
+ cb = new QCheckBox( &dlg, "cbVar" );
+ cb->setChecked( FALSE );
+ cb->setText(it.key());
+ layoutVar->addWidget( cb, i ,0, Qt::AlignTop );
+
+ te = new KTextEdit( &dlg, "teVar" );
+ layoutVar->addWidget( te, i, 1, Qt::AlignTop );
+
+ if ((*mapSave)[it.key()].length() > 0) {
+ cb->setChecked( TRUE );
+ te->setText((*mapSave)[it.key()]);
+ }
+
+ mapVar2Te[it.key()] = te;
+ mapVar2Cb[it.key()] = cb;
+
+ QToolTip::add( cb, i18n("Enable this to save the value entered to the right as the default value for this variable") );
+ QWhatsThis::add( cb, i18n("If you enable this option, the value entered to the right will be saved. "
+ "If you use the same variable later, even in another snippet, the value entered to the right "
+ "will be the default value for that variable.") );
+
+ i++;
+ }
+ layout->addMultiCellLayout( layoutVar, 1, 1, 0, 1 );
+
+ KPushButton * btn1 = new KPushButton( KStdGuiItem::cancel(), &dlg, "pushButton1" );
+ btn1->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)1, (QSizePolicy::SizeType)0, 0, 0,
+ btn1->sizePolicy().hasHeightForWidth() ) );
+ layoutBtn->addWidget( btn1, 0, 0 );
+
+ KPushButton * btn2 = new KPushButton( KStdGuiItem::apply(), &dlg, "pushButton2" );
+ btn2->setDefault( TRUE );
+ btn2->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)1, (QSizePolicy::SizeType)0, 0, 0,
+ btn2->sizePolicy().hasHeightForWidth() ) );
+ layoutBtn->addWidget( btn2, 0, 1 );
+
+ layout->addMultiCellLayout( layoutBtn, 2, 2, 0, 1 );
+ // --END-- building a dynamic dialog
+
+ //connect the buttons to the QDialog default slots
+ connect(btn1, SIGNAL(clicked()), &dlg, SLOT(reject()) );
+ connect(btn2, SIGNAL(clicked()), &dlg, SLOT(accept()) );
+
+ //prepare to execute the dialog
+ bool bReturn = false;
+ //resize the textedits
+ if (iWidth > 1) {
+ QRect r = dlg.geometry();
+ r.setHeight(iBasicHeight + iOneHeight*mapVar2Te.count());
+ r.setWidth(iWidth);
+ dlg.setGeometry(r);
+ }
+ if ( i > 0 && // only if there are any variables
+ dlg.exec() == QDialog::Accepted ) {
+
+ QMap<QString, KTextEdit *>::Iterator it2;
+ for ( it2 = mapVar2Te.begin(); it2 != mapVar2Te.end(); ++it2 ) {
+ if (it2.key() == _SnippetConfig.getDelimiter() + _SnippetConfig.getDelimiter())
+ continue;
+ (*map)[it2.key()] = it2.data()->text(); //copy the entered values back to the given map
+
+ if (mapVar2Cb[it2.key()]->isChecked()) //if the checkbox is on; save the values for later
+ (*mapSave)[it2.key()] = it2.data()->text();
+ else
+ (*mapSave).erase(it2.key());
+ }
+ bReturn = true;
+
+ iBasicHeight = dlg.geometry().height() - layoutVar->geometry().height();
+ iOneHeight = layoutVar->geometry().height() / mapVar2Te.count();
+ iWidth = dlg.geometry().width();
+ }
+
+ //do some cleanup
+ QMap<QString, KTextEdit *>::Iterator it1;
+ for (it1 = mapVar2Te.begin(); it1 != mapVar2Te.end(); ++it1)
+ delete it1.data();
+ mapVar2Te.clear();
+ QMap<QString, QCheckBox *>::Iterator it2;
+ for (it2 = mapVar2Cb.begin(); it2 != mapVar2Cb.end(); ++it2)
+ delete it2.data();
+ mapVar2Cb.clear();
+ delete layoutTop;
+ delete layoutVar;
+ delete layoutBtn;
+ delete layout;
+
+ if (i==0) //if nothing happened this means, that there are no variables to translate
+ return true; //.. so just return OK
+
+ return bReturn;
+// return true;
+}
+
+
+// fn SnippetWidget::showSingleVarDialog(QString var, QMap<QString, QString> * mapSave)
+/*!
+ This function constructs a dialog which contains a label and a linedit for the given variable
+ It return either the entered value or an empty string if the user hit cancel
+ */
+QString SnippetWidget::showSingleVarDialog(QString var, QMap<QString, QString> * mapSave, QRect & dlgSize)
+{
+ // --BEGIN-- building a dynamic dialog
+ QDialog dlg(this);
+ dlg.setCaption(i18n("Enter Values for Variables"));
+
+ QGridLayout * layout = new QGridLayout( &dlg, 1, 1, 11, 6, "layout");
+ QGridLayout * layoutTop = new QGridLayout( 0, 1, 1, 0, 6, "layoutTop");
+ QGridLayout * layoutVar = new QGridLayout( 0, 1, 1, 0, 6, "layoutVar");
+ QGridLayout * layoutBtn = new QGridLayout( 0, 2, 1, 0, 6, "layoutBtn");
+
+ KTextEdit *te = NULL;
+ QLabel * labTop = NULL;
+ QCheckBox * cb = NULL;
+
+ labTop = new QLabel( &dlg, "label" );
+ layoutTop->addWidget(labTop, 0, 0);
+ labTop->setText(i18n("Enter the replacement values for %1:").arg( var ));
+ layout->addMultiCellLayout( layoutTop, 0, 0, 0, 1 );
+
+
+ cb = new QCheckBox( &dlg, "cbVar" );
+ cb->setChecked( FALSE );
+ cb->setText(i18n( "Make value &default" ));
+
+ te = new KTextEdit( &dlg, "teVar" );
+ layoutVar->addWidget( te, 0, 1, Qt::AlignTop);
+ layoutVar->addWidget( cb, 1, 1, Qt::AlignTop);
+ if ((*mapSave)[var].length() > 0) {
+ cb->setChecked( TRUE );
+ te->setText((*mapSave)[var]);
+ }
+
+ QToolTip::add( cb, i18n("Enable this to save the value entered to the right as the default value for this variable") );
+ QWhatsThis::add( cb, i18n("If you enable this option, the value entered to the right will be saved. "
+ "If you use the same variable later, even in another snippet, the value entered to the right "
+ "will be the default value for that variable.") );
+
+ layout->addMultiCellLayout( layoutVar, 1, 1, 0, 1 );
+
+ KPushButton * btn1 = new KPushButton( KStdGuiItem::cancel(), &dlg, "pushButton1" );
+ layoutBtn->addWidget( btn1, 0, 0 );
+
+ KPushButton * btn2 = new KPushButton( KStdGuiItem::apply(), &dlg, "pushButton2" );
+ btn2->setDefault( TRUE );
+ layoutBtn->addWidget( btn2, 0, 1 );
+
+ layout->addMultiCellLayout( layoutBtn, 2, 2, 0, 1 );
+ te->setFocus();
+ // --END-- building a dynamic dialog
+
+ //connect the buttons to the QDialog default slots
+ connect(btn1, SIGNAL(clicked()), &dlg, SLOT(reject()) );
+ connect(btn2, SIGNAL(clicked()), &dlg, SLOT(accept()) );
+
+ //execute the dialog
+ QString strReturn = "";
+ if (dlgSize.isValid())
+ dlg.setGeometry(dlgSize);
+ if ( dlg.exec() == QDialog::Accepted ) {
+ if (cb->isChecked()) //if the checkbox is on; save the values for later
+ (*mapSave)[var] = te->text();
+ else
+ (*mapSave).erase(var);
+
+ strReturn = te->text(); //copy the entered values back the the given map
+
+ dlgSize = dlg.geometry();
+ }
+
+ //do some cleanup
+ delete cb;
+ delete te;
+ delete labTop;
+ delete btn1;
+ delete btn2;
+ delete layoutTop;
+ delete layoutVar;
+ delete layoutBtn;
+ delete layout;
+
+ return strReturn;
+}
+
+// fn SnippetWidget::acceptDrag (QDropEvent *event) const
+/*!
+ Reimplementation from KListView.
+ Check here if the data the user is about to drop fits our restrictions.
+ We only accept dropps of plaintext, because from the dropped text
+ we will create a snippet.
+ */
+bool SnippetWidget::acceptDrag (QDropEvent *event) const
+{
+ //kdDebug(5006) << "Format: " << event->format() << "" << event->pos() << endl;
+
+ QListViewItem * item = itemAt(event->pos());
+
+ if (item &&
+ QString(event->format()).startsWith("text/plain") &&
+ static_cast<SnippetWidget *>(event->source()) != this) {
+ ///kdDebug(5006) << "returning TRUE " << endl;
+ return TRUE;
+ } else if(item &&
+ QString(event->format()).startsWith("x-kmailsnippet") &&
+ static_cast<SnippetWidget *>(event->source()) != this)
+ {
+ //kdDebug(5006) << "returning TRUE " << endl;
+ return TRUE;
+ } else {
+ //kdDebug(5006) << "returning FALSE" << endl;
+ event->acceptAction(FALSE);
+ return FALSE;
+ }
+}
+
+// fn SnippetWidget::slotDropped(QDropEvent *e, QListViewItem *after)
+/*!
+ This slot is connected to the dropped signal.
+ If it is emitted, we need to construct a new snippet entry with
+ the data given
+ */
+void SnippetWidget::slotDropped(QDropEvent *e, QListViewItem *)
+{
+ QListViewItem * item2 = itemAt(e->pos());
+
+ SnippetGroup *group = dynamic_cast<SnippetGroup *>(item2);
+ if (!group)
+ group = dynamic_cast<SnippetGroup *>(item2->parent());
+
+ QCString dropped;
+ QByteArray data = e->encodedData("text/plain");
+ if ( e->provides("text/plain") && data.size()>0 ) {
+ //get the data from the event...
+ QString encData(data.data());
+ //kdDebug(5006) << "encData: " << encData << endl;
+
+ //... then fill the dialog with the given data
+ SnippetDlg dlg( mActionCollection, this, "SnippetDlg" );
+ dlg.snippetName->clear();
+ dlg.snippetText->setText(encData);
+
+ /*fill the combobox with the names of all SnippetGroup entries*/
+ for (SnippetItem *it=_list.first(); it; it=_list.next()) {
+ if (dynamic_cast<SnippetGroup*>(it)) {
+ dlg.cbGroup->insertItem(it->getName());
+ }
+ }
+ dlg.cbGroup->setCurrentText(group->getName());
+
+ if (dlg.exec() == QDialog::Accepted) {
+ /* get the group that the user selected with the combobox */
+ group = dynamic_cast<SnippetGroup*>(SnippetItem::findItemByName(dlg.cbGroup->currentText(), _list));
+ _list.append( makeItem(group, dlg.snippetName->text(), dlg.snippetText->text(), dlg.keyButton->shortcut() ) );
+ }
+ }
+}
+
+void SnippetWidget::startDrag()
+{
+ QString text = dynamic_cast<SnippetItem*>( currentItem() )->getText();
+ QTextDrag *drag = new QTextDrag(text, this);
+ drag->setSubtype("x-textsnippet");
+ drag->drag();
+}
+
+void SnippetWidget::slotExecute()
+{
+ slotExecuted(currentItem());
+}
+
+
+#include "snippetwidget.moc"
+
diff --git a/kmail/snippetwidget.h b/kmail/snippetwidget.h
new file mode 100644
index 00000000..e267ff5d
--- /dev/null
+++ b/kmail/snippetwidget.h
@@ -0,0 +1,95 @@
+/***************************************************************************
+ * snippet feature from kdevelop/plugins/snippet/ *
+ * *
+ * Copyright (C) 2007 by Robert Gruber *
+ * rgruber@users.sourceforge.net *
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+#ifndef __SNIPPET_WIDGET_H__
+#define __SNIPPET_WIDGET_H__
+
+#include <qwidget.h>
+#include <qstring.h>
+#include <klistview.h>
+#include <qtooltip.h>
+#include <qrect.h>
+
+#include <ktexteditor/editinterface.h>
+#include <ktexteditor/view.h>
+#include "snippetconfig.h"
+
+class KDevProject;
+class SnippetPart;
+class QPushButton;
+class KListView;
+class QListViewItem;
+class QPoint;
+class SnippetDlg;
+class SnippetItem;
+class KTextEdit;
+class KConfig;
+class KMEdit;
+class KActionCollection;
+
+/**
+This is the widget which gets added to the right TreeToolView.
+It inherits KListView and QToolTip which is needed for showing the
+tooltips which contains the text of the snippet
+@author Robert Gruber
+*/
+class SnippetWidget : public KListView, public QToolTip
+{
+ Q_OBJECT
+
+public:
+ SnippetWidget(KMEdit* editor, KActionCollection* actionCollection, QWidget* parent = 0);
+ ~SnippetWidget();
+ QPtrList<SnippetItem> * getList() { return (&_list); }
+ void writeConfig();
+ SnippetConfig * getSnippetConfig() { return (&_SnippetConfig); }
+
+
+private slots:
+ void initConfig();
+
+protected:
+ void maybeTip( const QPoint & );
+ bool acceptDrag (QDropEvent *event) const;
+
+private:
+ void insertIntoActiveView( const QString &text );
+ QString parseText(QString text, QString del="$");
+ bool showMultiVarDialog(QMap<QString, QString> * map, QMap<QString, QString> * mapSave,
+ int & iWidth, int & iBasicHeight, int & iOneHeight);
+ QString showSingleVarDialog(QString var, QMap<QString, QString> * mapSave, QRect & dlgSize);
+ SnippetItem* makeItem( SnippetItem* parent, const QString& name, const QString& text, const KShortcut& shortcut );
+
+ QPtrList<SnippetItem> _list;
+ QMap<QString, QString> _mapSaved;
+ KConfig * _cfg;
+ SnippetConfig _SnippetConfig;
+ KMEdit* mEditor;
+ KActionCollection* mActionCollection;
+
+public slots:
+ void slotRemove();
+ void slotEdit( QListViewItem* item_ = 0 );
+ void slotEditGroup();
+ void slotAdd();
+ void slotAddGroup();
+ void slotExecute();
+
+protected slots:
+ void showPopupMenu( QListViewItem * item, const QPoint & p, int );
+ void slotExecuted(QListViewItem * item = 0);
+ void slotDropped(QDropEvent *e, QListViewItem *after);
+ void startDrag();
+};
+
+
+#endif
diff --git a/kmail/spamheaderanalyzer.cpp b/kmail/spamheaderanalyzer.cpp
new file mode 100644
index 00000000..b6430363
--- /dev/null
+++ b/kmail/spamheaderanalyzer.cpp
@@ -0,0 +1,157 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ spamheaderanalyzer.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2004 Patrick Audley <paudley@blackcat.ca>
+ Copyright (c) 2004 Ingo Kloecker <kloecker@kde.org>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "spamheaderanalyzer.h"
+
+#include "antispamconfig.h"
+#include "kmmessage.h"
+
+#include <kdebug.h>
+
+using namespace KMail;
+
+// static
+SpamScores SpamHeaderAnalyzer::getSpamScores( const KMMessage* message ) {
+ kdDebug(5006) << k_funcinfo << endl;
+ SpamScores scores;
+ SpamAgents agents = AntiSpamConfig::instance()->uniqueAgents();
+
+ for ( SpamAgentsIterator it = agents.begin(); it != agents.end(); ++it ) {
+ float score = -2.0;
+
+ // Skip bogus agents
+ if ( (*it).scoreType() == SpamAgentNone )
+ continue;
+
+ // Do we have the needed field for this agent?
+ QString mField = message->headerField( (*it).header() );
+ if ( mField.isEmpty() )
+ continue;
+
+ QString scoreString;
+ bool scoreValid = false;
+
+ if ( (*it).scoreType() != SpamAgentBool ) {
+ // Can we extract the score?
+ QRegExp scorePattern = (*it).scorePattern();
+ if ( scorePattern.search( mField ) != -1 ) {
+ scoreString = scorePattern.cap( 1 );
+ scoreValid = true;
+ }
+ } else
+ scoreValid = true;
+
+ if ( !scoreValid ) {
+ score = -5.0;
+ kdDebug(5006) << "Score could not be extracted from header '"
+ << mField << "'" << endl;
+ } else {
+ bool floatValid = false;
+ switch ( (*it).scoreType() ) {
+ case SpamAgentNone:
+ score = -2.0;
+ break;
+
+ case SpamAgentBool:
+ if( (*it).scorePattern().search( mField ) == -1 )
+ score = 0.0;
+ else
+ score = 100.0;
+ break;
+
+ case SpamAgentFloat:
+ score = scoreString.toFloat( &floatValid );
+ if ( !floatValid ) {
+ score = -3.0;
+ kdDebug(5006) << "Score (" << scoreString << ") is no number"
+ << endl;
+ }
+ else
+ score *= 100.0;
+ break;
+
+ case SpamAgentFloatLarge:
+ score = scoreString.toFloat( &floatValid );
+ if ( !floatValid ) {
+ score = -3.0;
+ kdDebug(5006) << "Score (" << scoreString << ") is no number"
+ << endl;
+ }
+ break;
+
+ case SpamAgentAdjustedFloat:
+ score = scoreString.toFloat( &floatValid );
+ if ( !floatValid ) {
+ score = -3.0;
+ kdDebug(5006) << "Score (" << scoreString << ") is no number"
+ << endl;
+ break;
+ }
+
+ // Find the threshold value.
+ QString thresholdString;
+ QRegExp thresholdPattern = (*it).thresholdPattern();
+ if ( thresholdPattern.search( mField ) != -1 ) {
+ thresholdString = thresholdPattern.cap( 1 );
+ }
+ else {
+ score = -6.0;
+ kdDebug(5006) << "Threshold could not be extracted from header '"
+ << mField << "'" << endl;
+ break;
+ }
+ float threshold = thresholdString.toFloat( &floatValid );
+ if ( !floatValid || ( threshold <= 0.0 ) ) {
+ score = -4.0;
+ kdDebug(5006) << "Threshold (" << thresholdString << ") is no "
+ << "number or is negative" << endl;
+ break;
+ }
+
+ // Normalize the score. Anything below 0 means 0%, anything above
+ // threshold mean 100%. Values between 0 and threshold are mapped
+ // linearily to 0% - 100%.
+ if ( score < 0.0 )
+ score = 0.0;
+ else if ( score > threshold )
+ score = 100.0;
+ else
+ score = score / threshold * 100.0;
+ break;
+ }
+ }
+ scores.append( SpamScore( (*it).name(), score, mField ) );
+ }
+
+ return scores;
+}
diff --git a/kmail/spamheaderanalyzer.h b/kmail/spamheaderanalyzer.h
new file mode 100644
index 00000000..b74f6029
--- /dev/null
+++ b/kmail/spamheaderanalyzer.h
@@ -0,0 +1,90 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ spamheaderanalyzer.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2004 Patrick Audley <paudley@blackcat.ca>
+ Copyright (c) 2004 Ingo Kloecker <kloecker@kde.org>
+
+ KMail 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.
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_SPAMHEADERANALYZER_H__
+#define __KMAIL_SPAMHEADERANALYZER_H__
+
+#include <qvaluelist.h>
+
+class QString;
+class KMMessage;
+
+namespace KMail {
+
+ /**
+ @short A simple tupel of agent, score and header.
+
+ The score returned is positive if no error has occurred.
+ Negative values indicate the following errors:
+ -1.0 Unintialized struct used
+ -2.0 Error extracing agent string
+ -3.0 Couldn't convert score to float
+ -4.0 Couldn't convert threshold to float or threshold is negative
+ -5.0 Couldn't find the score field
+ -6.0 Couldn't find the threshold field
+ */
+ class SpamScore {
+ public:
+ SpamScore() : mScore( -2.0 ) {}
+ SpamScore( const QString & agent, float score, const QString & header )
+ : mAgent( agent ), mScore( score ), mHeader( header ) {}
+ QString agent() const { return mAgent; }
+ float score() const { return mScore; }
+ QString spamHeader() const { return mHeader; }
+
+ private:
+ QString mAgent;
+ float mScore;
+ QString mHeader;
+ };
+ typedef QValueList<SpamScore> SpamScores;
+ typedef QValueListIterator<SpamScore> SpamScoresIterator;
+
+
+ /**
+ @short Flyweight for analysing spam headers.
+ @author Patrick Audley <paudley@blackcat.ca>
+ */
+ class SpamHeaderAnalyzer {
+ public:
+ /**
+ @short Extract scores from known anti-spam headers
+ @param message A KMMessage to examine
+ @return A list of detected scores. See SpamScore
+ */
+ static SpamScores getSpamScores( const KMMessage *message );
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_SPAMHEADERANALYZER_H__
diff --git a/kmail/stl_util.h b/kmail/stl_util.h
new file mode 100644
index 00000000..69963df7
--- /dev/null
+++ b/kmail/stl_util.h
@@ -0,0 +1,47 @@
+/* -*- c++ -*-
+ stl_util.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2004 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KDEPIM__KMAIL__STL_UTIL_H__
+#define __KDEPIM__KMAIL__STL_UTIL_H__
+
+template <typename T>
+struct DeleteAndSetToZero {
+ void operator()( const T * & t ) { delete t; t = 0; }
+};
+
+template <typename T>
+static inline void deleteAll( T & c ) {
+ for ( typename T::iterator it = c.begin() ; it != c.end() ; ++it ) {
+ delete *it ; *it = 0;
+ }
+}
+
+#endif // __KDEPIM__KMAIL__STL_UTIL_H__
diff --git a/kmail/subscriptiondialog.cpp b/kmail/subscriptiondialog.cpp
new file mode 100644
index 00000000..ac590993
--- /dev/null
+++ b/kmail/subscriptiondialog.cpp
@@ -0,0 +1,444 @@
+/* -*- c++ -*-
+ subscriptiondialog.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (C) 2002 Carsten Burghardt <burghardt@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "subscriptiondialog.h"
+#include "folderstorage.h"
+#include "listjob.h"
+#include "imapaccountbase.h"
+
+#include <klocale.h>
+#include <kdebug.h>
+#include <kmessagebox.h>
+
+
+namespace KMail {
+
+SubscriptionDialogBase::SubscriptionDialogBase( QWidget *parent, const QString &caption,
+ KAccount *acct, QString startPath )
+ : KSubscription( parent, caption, acct, User1, QString::null, false ),
+ mStartPath( startPath ), mSubscribed( false ), mForceSubscriptionEnable( false)
+{
+ // hide unneeded checkboxes
+ hideTreeCheckbox();
+ hideNewOnlyCheckbox();
+
+ // ok-button
+ connect(this, SIGNAL(okClicked()), SLOT(slotSave()));
+
+ // reload-list button
+ connect(this, SIGNAL(user1Clicked()), SLOT(slotLoadFolders()));
+
+ // get the folders, delayed execution style, otherwise there's bother
+ // with virtuals from ctors and whatnot
+ QTimer::singleShot(0, this, SLOT(slotLoadFolders()));
+}
+
+//------------------------------------------------------------------------------
+void SubscriptionDialogBase::slotListDirectory( const QStringList& subfolderNames,
+ const QStringList& subfolderPaths,
+ const QStringList& subfolderMimeTypes,
+ const QStringList& subfolderAttributes,
+ const ImapAccountBase::jobData& jobData )
+{
+ mFolderNames = subfolderNames;
+ mFolderPaths = subfolderPaths;
+ mFolderMimeTypes = subfolderMimeTypes;
+ mFolderAttributes = subfolderAttributes;
+ mJobData = jobData;
+
+ mCount = 0;
+
+ processFolderListing();
+}
+
+void SubscriptionDialogBase::moveChildrenToNewParent( GroupItem *oldItem, GroupItem *item )
+{
+ if ( !oldItem || !item ) return;
+
+ QPtrList<QListViewItem> itemsToMove;
+ QListViewItem * myChild = oldItem->firstChild();
+ while (myChild)
+ {
+ itemsToMove.append(myChild);
+ myChild = myChild->nextSibling();
+ }
+ QPtrListIterator<QListViewItem> it( itemsToMove );
+ QListViewItem *cur;
+ while ((cur = it.current()))
+ {
+ oldItem->takeItem(cur);
+ item->insertItem(cur);
+ if ( cur->isSelected() ) // we have new parents so open them
+ folderTree()->ensureItemVisible( cur );
+ ++it;
+ }
+ delete oldItem;
+ itemsToMove.clear();
+}
+
+void SubscriptionDialogBase::createListViewItem( int i )
+{
+ GroupItem *item = 0;
+ GroupItem *parent = 0;
+
+ // get the parent
+ GroupItem *oldItem = 0;
+ QString parentPath;
+ findParentItem( mFolderNames[i], mFolderPaths[i], parentPath, &parent, &oldItem );
+
+ if (!parent && parentPath != "/")
+ {
+ // the parent is not available and it's no root-item
+ // this happens when the folders do not arrive in hierarchical order
+ // so we create each parent in advance
+ QStringList folders = QStringList::split(mDelimiter, parentPath);
+ uint i = 0;
+ for ( QStringList::Iterator it = folders.begin(); it != folders.end(); ++it )
+ {
+ QString name = *it;
+ if (name.startsWith("/"))
+ name = name.right(name.length()-1);
+ if (name.endsWith("/"))
+ name.truncate(name.length()-1);
+ KGroupInfo info(name);
+ info.subscribed = false;
+
+ QStringList tmpPath;
+ for ( uint j = 0; j <= i; ++j )
+ tmpPath << folders[j];
+ QString path = tmpPath.join(mDelimiter);
+ if (!path.startsWith("/"))
+ path = "/" + path;
+ if (!path.endsWith("/"))
+ path = path + "/";
+ info.path = path;
+ item = 0;
+ if (folders.count() > 1)
+ {
+ // we have to create more then one level, so better check if this
+ // folder already exists somewhere
+ item = mItemDict[path];
+ }
+ // as these items are "dummies" we create them non-checkable
+ if (!item)
+ {
+ if (parent)
+ item = new GroupItem(parent, info, this, false);
+ else
+ item = new GroupItem(folderTree(), info, this, false);
+ mItemDict.insert(info.path, item);
+ }
+
+ parent = item;
+ ++i;
+ } // folders
+ } // parent
+
+ KGroupInfo info(mFolderNames[i]);
+ if (mFolderNames[i].upper() == "INBOX" &&
+ mFolderPaths[i] == "/INBOX/")
+ info.name = i18n("inbox");
+ info.subscribed = false;
+ info.path = mFolderPaths[i];
+ // only checkable when the folder is selectable
+ bool checkable = ( mFolderMimeTypes[i] == "inode/directory" ) ? false : true;
+ // create a new item
+ if (parent)
+ item = new GroupItem(parent, info, this, checkable);
+ else
+ item = new GroupItem(folderTree(), info, this, checkable);
+
+ if (oldItem) // remove old item
+ mItemDict.remove(info.path);
+
+ mItemDict.insert(info.path, item);
+ if (oldItem)
+ moveChildrenToNewParent( oldItem, item );
+
+ // select the start item
+ if ( mFolderPaths[i] == mStartPath )
+ {
+ item->setSelected( true );
+ folderTree()->ensureItemVisible( item );
+ }
+}
+
+
+
+//------------------------------------------------------------------------------
+void SubscriptionDialogBase::findParentItem( QString &name, QString &path, QString &parentPath,
+ GroupItem **parent, GroupItem **oldItem )
+{
+ // remove the name (and the separator) from the path to get the parent path
+ int start = path.length() - (name.length()+2);
+ int length = name.length()+1;
+ if (start < 0) start = 0;
+ parentPath = path;
+ parentPath.remove(start, length);
+
+ // find the parent by it's path
+ *parent = mItemDict[parentPath];
+
+ // check if the item already exists
+ *oldItem = mItemDict[path];
+}
+
+//------------------------------------------------------------------------------
+void SubscriptionDialogBase::slotSave()
+{
+ doSave();
+}
+
+//------------------------------------------------------------------------------
+void SubscriptionDialogBase::slotLoadFolders()
+{
+ ImapAccountBase* ai = static_cast<ImapAccountBase*>(account());
+ // we need a connection
+ if ( ai->makeConnection() == ImapAccountBase::Error )
+ {
+ kdWarning(5006) << "SubscriptionDialog - got no connection" << endl;
+ return;
+ } else if ( ai->makeConnection() == ImapAccountBase::Connecting )
+ {
+ // We'll wait for the connectionResult signal from the account.
+ kdDebug(5006) << "SubscriptionDialog - waiting for connection" << endl;
+ connect( ai, SIGNAL( connectionResult(int, const QString&) ),
+ this, SLOT( slotConnectionResult(int, const QString&) ) );
+ return;
+ }
+ // clear the views
+ KSubscription::slotLoadFolders();
+ mItemDict.clear();
+ mSubscribed = false;
+ mLoading = true;
+
+ // first step is to load a list of all available folders and create listview
+ // items for them
+ listAllAvailableAndCreateItems();
+}
+
+//------------------------------------------------------------------------------
+void SubscriptionDialogBase::processNext()
+{
+ if ( mPrefixList.isEmpty() )
+ {
+ if ( !mSubscribed )
+ {
+ mSubscribed = true;
+ initPrefixList();
+ if ( mPrefixList.isEmpty() )
+ {
+ // still empty? then we have nothing to do here as this is an error
+ loadingComplete();
+ return;
+ }
+ } else {
+ loadingComplete();
+ return;
+ }
+ }
+ ImapAccountBase* ai = static_cast<ImapAccountBase*>(account());
+ ImapAccountBase::ListType type = ( mSubscribed ?
+ ImapAccountBase::ListSubscribedNoCheck : ImapAccountBase::List );
+
+ bool completeListing = true;
+ mCurrentNamespace = mPrefixList.first();
+ mDelimiter = ai->delimiterForNamespace( mCurrentNamespace );
+ mPrefixList.pop_front();
+ if ( mCurrentNamespace == "/INBOX/" )
+ {
+ type = mSubscribed ?
+ ImapAccountBase::ListFolderOnlySubscribed : ImapAccountBase::ListFolderOnly;
+ completeListing = false;
+ }
+
+// kdDebug(5006) << "process " << mCurrentNamespace << ",subscribed=" << mSubscribed << endl;
+ ListJob* job = new ListJob( ai, type, 0, ai->addPathToNamespace( mCurrentNamespace ), completeListing );
+ connect( job, SIGNAL(receivedFolders(const QStringList&, const QStringList&,
+ const QStringList&, const QStringList&, const ImapAccountBase::jobData&)),
+ this, SLOT(slotListDirectory(const QStringList&, const QStringList&,
+ const QStringList&, const QStringList&, const ImapAccountBase::jobData&)));
+ job->start();
+}
+
+void SubscriptionDialogBase::loadingComplete()
+{
+ slotLoadingComplete();
+}
+
+
+//------------------------------------------------------------------------------
+// implementation for server side subscription
+//------------------------------------------------------------------------------
+
+SubscriptionDialog::SubscriptionDialog( QWidget *parent, const QString &caption,
+ KAccount *acct, QString startPath )
+ : SubscriptionDialogBase( parent, caption, acct, startPath )
+{
+}
+
+/* virtual */
+SubscriptionDialog::~SubscriptionDialog()
+{
+
+}
+
+/* virtual */
+void SubscriptionDialog::listAllAvailableAndCreateItems()
+{
+ initPrefixList();
+ processNext();
+}
+
+//------------------------------------------------------------------------------
+void SubscriptionDialogBase::initPrefixList()
+{
+ ImapAccountBase* ai = static_cast<ImapAccountBase*>(account());
+ ImapAccountBase::nsMap map = ai->namespaces();
+ mPrefixList.clear();
+
+ bool hasInbox = false;
+ const QStringList ns = map[ImapAccountBase::PersonalNS];
+ for ( QStringList::ConstIterator it = ns.begin(); it != ns.end(); ++it )
+ {
+ if ( (*it).isEmpty() )
+ hasInbox = true;
+ }
+ if ( !hasInbox && !ns.isEmpty() )
+ {
+ // the namespaces includes no listing for the root so start a special
+ // listing for the INBOX to make sure we get it
+ mPrefixList += "/INBOX/";
+ }
+
+ mPrefixList += map[ImapAccountBase::PersonalNS];
+ mPrefixList += map[ImapAccountBase::OtherUsersNS];
+ mPrefixList += map[ImapAccountBase::SharedNS];
+}
+
+void SubscriptionDialogBase::slotConnectionResult( int errorCode, const QString& errorMsg )
+{
+ Q_UNUSED( errorMsg );
+ if ( !errorCode )
+ slotLoadFolders();
+}
+
+void SubscriptionDialogBase::show()
+{
+ KDialogBase::show();
+}
+
+// =======
+/* virtual */
+void SubscriptionDialog::processFolderListing()
+{
+ processItems();
+}
+
+/* virtual */
+void SubscriptionDialog::doSave()
+{
+ KMail::ImapAccountBase *a = static_cast<KMail::ImapAccountBase*>(mAcct);
+ if( !a->onlySubscribedFolders() ) {
+ int result = KMessageBox::questionYesNoCancel( this,
+ 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) {
+ case KMessageBox::Yes:
+ mForceSubscriptionEnable = true;
+ break;
+ case KMessageBox::No:
+ break;
+ case KMessageBox::Cancel:
+ cancel();
+ }
+ }
+
+ // subscribe
+ QListViewItemIterator it(subView);
+ for ( ; it.current(); ++it)
+ {
+ static_cast<ImapAccountBase*>(account())->changeSubscription(true,
+ static_cast<GroupItem*>(it.current())->info().path);
+ }
+
+ // unsubscribe
+ QListViewItemIterator it2(unsubView);
+ for ( ; it2.current(); ++it2)
+ {
+ static_cast<ImapAccountBase*>(account())->changeSubscription(false,
+ static_cast<GroupItem*>(it2.current())->info().path);
+ }
+
+ if ( mForceSubscriptionEnable ) {
+ a->setOnlySubscribedFolders(true);
+ }
+}
+
+void SubscriptionDialog::processItems()
+{
+ bool onlySubscribed = mJobData.onlySubscribed;
+ uint done = 0;
+ for (uint i = mCount; i < mFolderNames.count(); ++i)
+ {
+ // give the dialog a chance to repaint
+ if (done == 1000)
+ {
+ emit listChanged();
+ QTimer::singleShot(0, this, SLOT(processItems()));
+ return;
+ }
+ ++mCount;
+ ++done;
+ if (!onlySubscribed && mFolderPaths.size() > 0)
+ {
+ createListViewItem( i );
+ } else if (onlySubscribed)
+ {
+ // find the item
+ if ( mItemDict[mFolderPaths[i]] )
+ {
+ GroupItem* item = mItemDict[mFolderPaths[i]];
+ item->setOn( true );
+ }
+ }
+ }
+
+ processNext();
+}
+} // namespace
+
+#include "subscriptiondialog.moc"
diff --git a/kmail/subscriptiondialog.h b/kmail/subscriptiondialog.h
new file mode 100644
index 00000000..011a529b
--- /dev/null
+++ b/kmail/subscriptiondialog.h
@@ -0,0 +1,156 @@
+/* -*- c++ -*-
+ subscriptiondialog.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (C) 2002 Carsten Burghardt <burghardt@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __SUBSCRIPTIONDIALOG
+#define __SUBSCRIPTIONDIALOG
+
+#include <qdict.h>
+#include <ksubscription.h>
+#include "imapaccountbase.h"
+
+class KMMessage;
+class FolderStorage;
+
+namespace KMail {
+
+ // Abstract base class for the server side and client side subscription dialogs.
+ // Scott Meyers says: "Make non-leaf classes abstract" and he is right, I think.
+ // (More Effective C++, Item 33)
+ class SubscriptionDialogBase : public KSubscription
+ {
+ Q_OBJECT
+
+ public:
+ SubscriptionDialogBase( QWidget *parent,
+ const QString &caption,
+ KAccount* acct,
+ QString startPath = QString::null );
+ virtual ~SubscriptionDialogBase() {}
+
+ void show();
+
+ protected:
+ /**
+ * Find the parent item
+ */
+ void findParentItem ( QString &name, QString &path, QString &compare,
+ GroupItem **parent, GroupItem **oldItem );
+
+ /**
+ * Process the next prefix in mPrefixList
+ */
+ void processNext();
+
+ /**
+ * Fill mPrefixList
+ */
+ void initPrefixList();
+
+ virtual void loadingComplete();
+
+ public slots:
+ /**
+ * get the listing from the imap-server
+ */
+ void slotListDirectory(const QStringList&, const QStringList&,
+ const QStringList&, const QStringList&, const ImapAccountBase::jobData &);
+
+ /**
+ * called by Ok-button, saves the changes
+ */
+ void slotSave();
+
+ /**
+ * Called from the account when a connection was established
+ */
+ void slotConnectionResult( int errorCode, const QString& errorMsg );
+
+ protected slots:
+ /**
+ * Loads the folders
+ */
+ void slotLoadFolders();
+
+ protected:
+ virtual void listAllAvailableAndCreateItems() = 0;
+ virtual void processFolderListing() = 0;
+ virtual void doSave() = 0;
+
+ // helpers
+ /** Move all child items of @param oldItem under @param item */
+ void moveChildrenToNewParent( GroupItem *oldItem, GroupItem *item );
+
+ /** Create a listview item for the i-th entry in the list of available
+ * folders. */
+ void createListViewItem( int i );
+
+ QString mDelimiter;
+ QStringList mFolderNames, mFolderPaths,
+ mFolderMimeTypes, mFolderAttributes;
+ ImapAccountBase::jobData mJobData;
+ uint mCount;
+ QDict<GroupItem> mItemDict;
+ QString mStartPath;
+ bool mSubscribed, mForceSubscriptionEnable;
+ QStringList mPrefixList;
+ QString mCurrentNamespace;
+ };
+
+ class SubscriptionDialog : public SubscriptionDialogBase
+ {
+ Q_OBJECT
+ public:
+
+ SubscriptionDialog( QWidget *parent,
+ const QString &caption,
+ KAccount* acct,
+ QString startPath = QString::null );
+ virtual ~SubscriptionDialog();
+ protected:
+ /** reimpl */
+ virtual void listAllAvailableAndCreateItems();
+ /** reimpl */
+ virtual void processFolderListing();
+ /** reimpl */
+ virtual void doSave();
+
+ private:
+ /**
+ * Create or update the listitems, depending on whether we are listing
+ * all available folders, or only subscribed ones.
+ */
+ void processItems();
+
+ };
+
+} // namespace KMail
+
+#endif
diff --git a/kmail/teehtmlwriter.cpp b/kmail/teehtmlwriter.cpp
new file mode 100644
index 00000000..91bc05a9
--- /dev/null
+++ b/kmail/teehtmlwriter.cpp
@@ -0,0 +1,98 @@
+/* -*- c++ -*-
+ teehtmlwriter.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "teehtmlwriter.h"
+
+#include <kdebug.h>
+
+#include <qvaluelist.h>
+
+namespace KMail {
+
+ TeeHtmlWriter::TeeHtmlWriter( HtmlWriter * writer1, HtmlWriter * writer2 )
+ : HtmlWriter()
+ {
+ if ( writer1 )
+ mWriters.append( writer1 );
+ if ( writer2 )
+ mWriters.append( writer2 );
+ }
+
+ TeeHtmlWriter::~TeeHtmlWriter() {
+ for ( QValueListIterator<HtmlWriter*> it = mWriters.begin(); it != mWriters.end(); ++it )
+ delete (*it);
+ }
+
+ void TeeHtmlWriter::addHtmlWriter( HtmlWriter * writer ) {
+ if ( writer )
+ mWriters.append( writer );
+ }
+
+ void TeeHtmlWriter::begin( const QString & css ) {
+ for ( QValueListIterator<HtmlWriter*> it = mWriters.begin(); it != mWriters.end(); ++it )
+ (*it)->begin( css );
+ }
+
+ void TeeHtmlWriter::end() {
+ for ( QValueListIterator<HtmlWriter*> it = mWriters.begin(); it != mWriters.end(); ++it )
+ (*it)->end();
+ }
+
+ void TeeHtmlWriter::reset() {
+ for ( QValueListIterator<HtmlWriter*> it = mWriters.begin(); it != mWriters.end(); ++it )
+ (*it)->reset();
+ }
+
+ void TeeHtmlWriter::write( const QString & str ) {
+ for ( QValueListIterator<HtmlWriter*> it = mWriters.begin(); it != mWriters.end(); ++it )
+ (*it)->write( str );
+ }
+
+ void TeeHtmlWriter::queue( const QString & str ) {
+ for ( QValueListIterator<HtmlWriter*> it = mWriters.begin(); it != mWriters.end(); ++it )
+ (*it)->queue( str );
+ }
+
+ void TeeHtmlWriter::flush() {
+ for ( QValueListIterator<HtmlWriter*> it = mWriters.begin(); it != mWriters.end(); ++it )
+ (*it)->flush();
+ }
+
+ void TeeHtmlWriter::embedPart( const QCString & contentId, const QString & url ) {
+ for ( QValueListIterator<HtmlWriter*> it = mWriters.begin(); it != mWriters.end(); ++it )
+ (*it)->embedPart( contentId, url );
+ }
+
+} // namespace KMail
diff --git a/kmail/teehtmlwriter.h b/kmail/teehtmlwriter.h
new file mode 100644
index 00000000..d7b1cec2
--- /dev/null
+++ b/kmail/teehtmlwriter.h
@@ -0,0 +1,71 @@
+/* -*- c++ -*-
+ teehtmlwriter.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_TEEHTMLWRITER_H__
+#define __KMAIL_TEEHTMLWRITER_H__
+
+#include "interfaces/htmlwriter.h"
+
+#include <qvaluelist.h>
+
+class QString;
+
+namespace KMail {
+
+ /** @short A HtmlWriter that dispatches all calls to a list of other HtmlWriters
+ @author Marc Mutz <mutz@kde.org>
+ **/
+ class TeeHtmlWriter : public KMail::HtmlWriter {
+ public:
+ TeeHtmlWriter( KMail::HtmlWriter * writer1=0, KMail::HtmlWriter * writer2=0 );
+ virtual ~TeeHtmlWriter();
+
+ void addHtmlWriter( KMail::HtmlWriter * writer );
+
+ //
+ // HtmlWriter Interface
+ //
+ void begin( const QString & cssDefs );
+ void end();
+ void reset();
+ void write( const QString & str );
+ void queue( const QString & str );
+ void flush();
+ void embedPart( const QCString & contentId, const QString & url );
+
+ private:
+ /** We own the HtmlWriters added to us! */
+ QValueList<KMail::HtmlWriter*> mWriters;
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_TEEHTMLWRITER_H__
diff --git a/kmail/templateparser.cpp b/kmail/templateparser.cpp
new file mode 100644
index 00000000..39591358
--- /dev/null
+++ b/kmail/templateparser.cpp
@@ -0,0 +1,1093 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ * kmail: KDE mail client
+ * This file: Copyright (C) 2006 Dmitry Morozhnikov <dmiceman@mail.ru>
+ *
+ * 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 <config.h>
+
+#include <qstring.h>
+#include <qdatetime.h>
+#include <klocale.h>
+#include <kcalendarsystem.h>
+#include <kmime_util.h>
+#include <kglobal.h>
+#include <kprocess.h>
+#include <qregexp.h>
+#include <qfile.h>
+#include <kmessagebox.h>
+#include <kshell.h>
+#include <qfileinfo.h>
+
+#include "kmmessage.h"
+#include "kmmsgbase.h"
+#include "kmfolder.h"
+#include "templatesconfiguration.h"
+#include "templatesconfiguration_kfg.h"
+#include "customtemplates_kfg.h"
+#include "globalsettings_base.h"
+#include "kmkernel.h"
+#include <libkpimidentities/identity.h>
+#include <libkpimidentities/identitymanager.h>
+
+#include "templateparser.h"
+
+TemplateParser::TemplateParser( KMMessage *amsg, const Mode amode,
+ const QString 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 )
+{
+ mMsg = amsg;
+}
+
+int TemplateParser::parseQuotes( const QString &prefix, const QString &str,
+ QString &quote ) const
+{
+ int pos = prefix.length();
+ int len;
+ int str_len = str.length();
+ QChar qc = '"';
+ QChar prev = 0;
+
+ pos++;
+ len = pos;
+
+ while ( pos < str_len ) {
+ QChar c = str[pos];
+
+ pos++;
+ len++;
+
+ if ( prev ) {
+ quote.append( c );
+ prev = 0;
+ } else {
+ if ( c == '\\' ) {
+ prev = c;
+ } else if ( c == qc ) {
+ break;
+ } else {
+ quote.append( c );
+ }
+ }
+ }
+
+ return len;
+}
+
+QString TemplateParser::getFName( const QString &str )
+{
+ // simple logic:
+ // if there is ',' in name, than format is 'Last, First'
+ // else format is 'First Last'
+ // last resort -- return 'name' from 'name@domain'
+ int sep_pos;
+ QString res;
+ if ( ( sep_pos = str.find( '@' ) ) > 0 ) {
+ int i;
+ for ( i = (sep_pos - 1); i >= 0; --i ) {
+ QChar c = str[i];
+ if ( c.isLetterOrNumber() ) {
+ res.prepend( c );
+ } else {
+ break;
+ }
+ }
+ } else if ( ( sep_pos = str.find(',') ) > 0 ) {
+ unsigned int i;
+ bool begin = false;
+ for ( i = sep_pos; i < str.length(); ++i ) {
+ QChar c = str[i];
+ if ( c.isLetterOrNumber() ) {
+ begin = true;
+ res.append( c );
+ } else if ( begin ) {
+ break;
+ }
+ }
+ } else {
+ unsigned int i;
+ for ( i = 0; i < str.length(); ++i ) {
+ QChar c = str[i];
+ if ( c.isLetterOrNumber() ) {
+ res.append( c );
+ } else {
+ break;
+ }
+ }
+ }
+ return res;
+}
+
+QString TemplateParser::getLName( const QString &str )
+{
+ // simple logic:
+ // if there is ',' in name, than format is 'Last, First'
+ // else format is 'First Last'
+ int sep_pos;
+ QString res;
+ if ( ( sep_pos = str.find(',') ) > 0 ) {
+ int i;
+ for ( i = sep_pos; i >= 0; --i ) {
+ QChar c = str[i];
+ if ( c.isLetterOrNumber() ) {
+ res.prepend( c );
+ } else {
+ break;
+ }
+ }
+ } else {
+ if ( ( sep_pos = str.find( ' ' ) ) > 0 ) {
+ unsigned int i;
+ bool begin = false;
+ for ( i = sep_pos; i < str.length(); ++i ) {
+ QChar c = str[i];
+ if ( c.isLetterOrNumber() ) {
+ begin = true;
+ res.append( c );
+ } else if ( begin ) {
+ break;
+ }
+ }
+ }
+ }
+ return res;
+}
+
+void TemplateParser::process( KMMessage *aorig_msg, KMFolder *afolder, bool append )
+{
+ mAppend = append;
+ mOrigMsg = aorig_msg;
+ mFolder = afolder;
+ QString tmpl = findTemplate();
+ return processWithTemplate( tmpl );
+}
+
+void TemplateParser::process( const QString &tmplName, KMMessage *aorig_msg,
+ KMFolder *afolder, bool append )
+{
+ mAppend = append;
+ mOrigMsg = aorig_msg;
+ mFolder = afolder;
+ QString tmpl = findCustomTemplate( tmplName );
+ return processWithTemplate( tmpl );
+}
+
+void TemplateParser::processWithTemplate( const QString &tmpl )
+{
+ QString body;
+ int tmpl_len = tmpl.length();
+ bool dnl = false;
+ for ( int i = 0; i < tmpl_len; ++i ) {
+ QChar c = tmpl[i];
+ // kdDebug() << "Next char: " << c << endl;
+ if ( c == '%' ) {
+ QString cmd = tmpl.mid( i + 1 );
+
+ if ( cmd.startsWith( "-" ) ) {
+ // dnl
+ kdDebug() << "Command: -" << endl;
+ dnl = true;
+ i += 1;
+
+ } else if ( cmd.startsWith( "REM=" ) ) {
+ // comments
+ kdDebug() << "Command: REM=" << endl;
+ QString q;
+ int len = parseQuotes( "REM=", cmd, q );
+ i += len;
+
+ } else if ( cmd.startsWith( "INSERT=" ) ) {
+ // insert content of specified file as is
+ kdDebug() << "Command: INSERT=" << endl;
+ QString q;
+ int len = parseQuotes( "INSERT=", cmd, q );
+ i += len;
+ QString path = KShell::tildeExpand( q );
+ QFileInfo finfo( path );
+ if (finfo.isRelative() ) {
+ path = KShell::homeDir( "" );
+ path += '/';
+ path += q;
+ }
+ QFile file( path );
+ if ( file.open( IO_ReadOnly ) ) {
+ QByteArray content = file.readAll();
+ QString str = QString::fromLocal8Bit( content, content.size() );
+ body.append( str );
+ } else if ( mDebug ) {
+ KMessageBox::error( 0,
+ i18n( "Cannot insert content from file %1: %2" ).
+ arg( path ).arg( file.errorString() ) );
+ }
+
+ } else if ( cmd.startsWith( "SYSTEM=" ) ) {
+ // insert content of specified file as is
+ kdDebug() << "Command: SYSTEM=" << endl;
+ QString q;
+ int len = parseQuotes( "SYSTEM=", cmd, q );
+ i += len;
+ QString pipe_cmd = q;
+ QString str = pipe( pipe_cmd, "" );
+ body.append( str );
+
+ } else if ( cmd.startsWith( "PUT=" ) ) {
+ // insert content of specified file as is
+ kdDebug() << "Command: PUT=" << endl;
+ QString q;
+ int len = parseQuotes( "PUT=", cmd, q );
+ i += len;
+ QString path = KShell::tildeExpand( q );
+ QFileInfo finfo( path );
+ if (finfo.isRelative() ) {
+ path = KShell::homeDir( "" );
+ path += '/';
+ path += q;
+ }
+ QFile file( path );
+ if ( file.open( IO_ReadOnly ) ) {
+ QByteArray content = file.readAll();
+ body.append( QString::fromLocal8Bit( content, content.size() ) );
+ } else if ( mDebug ) {
+ KMessageBox::error( 0,
+ i18n( "Cannot insert content from file %1: %2").
+ arg( path ).arg(file.errorString() ));
+ }
+
+ } else if ( cmd.startsWith( "QUOTEPIPE=" ) ) {
+ // pipe message body throw command and insert it as quotation
+ kdDebug() << "Command: QUOTEPIPE=" << endl;
+ QString q;
+ int len = parseQuotes( "QUOTEPIPE=", cmd, q );
+ i += len;
+ QString pipe_cmd = q;
+ if ( mOrigMsg && !mNoQuote ) {
+ QString str = pipe( pipe_cmd, mSelection );
+ QString quote = mOrigMsg->asQuotedString( "", mQuoteString, str,
+ mSmartQuote, mAllowDecryption );
+ body.append( quote );
+ }
+
+ } else if ( cmd.startsWith( "QUOTE" ) ) {
+ kdDebug() << "Command: QUOTE" << endl;
+ i += strlen( "QUOTE" );
+ if ( mOrigMsg && !mNoQuote ) {
+ QString quote = mOrigMsg->asQuotedString( "", mQuoteString, mSelection,
+ mSmartQuote, mAllowDecryption );
+ body.append( quote );
+ }
+
+ } else if ( cmd.startsWith( "QHEADERS" ) ) {
+ kdDebug() << "Command: QHEADERS" << endl;
+ i += strlen( "QHEADERS" );
+ if ( mOrigMsg && !mNoQuote ) {
+ QString quote = mOrigMsg->asQuotedString( "", mQuoteString,
+ mOrigMsg->headerAsSendableString(),
+ mSmartQuote, false );
+ body.append( quote );
+ }
+
+ } else if ( cmd.startsWith( "HEADERS" ) ) {
+ kdDebug() << "Command: HEADERS" << endl;
+ i += strlen( "HEADERS" );
+ if ( mOrigMsg && !mNoQuote ) {
+ QString str = mOrigMsg->headerAsSendableString();
+ body.append( str );
+ }
+
+ } else if ( cmd.startsWith( "TEXTPIPE=" ) ) {
+ // pipe message body throw command and insert it as is
+ kdDebug() << "Command: TEXTPIPE=" << endl;
+ QString q;
+ int len = parseQuotes( "TEXTPIPE=", cmd, q );
+ i += len;
+ QString pipe_cmd = q;
+ if ( mOrigMsg ) {
+ QString str = pipe(pipe_cmd, mSelection );
+ body.append( str );
+ }
+
+ } else if ( cmd.startsWith( "MSGPIPE=" ) ) {
+ // pipe full message throw command and insert result as is
+ kdDebug() << "Command: MSGPIPE=" << endl;
+ QString q;
+ int len = parseQuotes( "MSGPIPE=", cmd, q );
+ i += len;
+ QString pipe_cmd = q;
+ if ( mOrigMsg ) {
+ QString str = pipe(pipe_cmd, mOrigMsg->asString() );
+ body.append( str );
+ }
+
+ } else if ( cmd.startsWith( "BODYPIPE=" ) ) {
+ // pipe message body generated so far throw command and insert result as is
+ kdDebug() << "Command: BODYPIPE=" << endl;
+ QString q;
+ int len = parseQuotes( "BODYPIPE=", cmd, q );
+ i += len;
+ QString pipe_cmd = q;
+ QString str = pipe( pipe_cmd, body );
+ body.append( str );
+
+ } else if ( cmd.startsWith( "CLEARPIPE=" ) ) {
+ // pipe message body generated so far throw command and
+ // insert result as is replacing current body
+ kdDebug() << "Command: CLEARPIPE=" << endl;
+ QString q;
+ int len = parseQuotes( "CLEARPIPE=", cmd, q );
+ i += len;
+ QString pipe_cmd = q;
+ QString str = pipe( pipe_cmd, body );
+ body = str;
+ mMsg->setCursorPos( 0 );
+
+ } else if ( cmd.startsWith( "TEXT" ) ) {
+ kdDebug() << "Command: TEXT" << endl;
+ i += strlen( "TEXT" );
+ if ( mOrigMsg ) {
+ QString quote = mOrigMsg->asPlainText( false, mAllowDecryption );
+ body.append( quote );
+ }
+
+ } else if ( cmd.startsWith( "OTEXTSIZE" ) ) {
+ kdDebug() << "Command: OTEXTSIZE" << endl;
+ i += strlen( "OTEXTSIZE" );
+ if ( mOrigMsg ) {
+ QString str = QString( "%1" ).arg( mOrigMsg->body().length() );
+ body.append( str );
+ }
+
+ } else if ( cmd.startsWith( "OTEXT" ) ) {
+ kdDebug() << "Command: OTEXT" << endl;
+ i += strlen( "OTEXT" );
+ if ( mOrigMsg ) {
+ QString quote = mOrigMsg->asPlainText( false, mAllowDecryption );
+ body.append( quote );
+ }
+
+ } else if ( cmd.startsWith( "CCADDR" ) ) {
+ kdDebug() << "Command: CCADDR" << endl;
+ i += strlen( "CCADDR" );
+ QString str = mMsg->cc();
+ body.append( str );
+
+ } else if ( cmd.startsWith( "CCNAME" ) ) {
+ kdDebug() << "Command: CCNAME" << endl;
+ i += strlen( "CCNAME" );
+ QString str = mMsg->ccStrip();
+ body.append( str );
+
+ } else if ( cmd.startsWith( "CCFNAME" ) ) {
+ kdDebug() << "Command: CCFNAME" << endl;
+ i += strlen( "CCFNAME" );
+ QString str = mMsg->ccStrip();
+ body.append( getFName( str ) );
+
+ } else if ( cmd.startsWith( "CCLNAME" ) ) {
+ kdDebug() << "Command: CCLNAME" << endl;
+ i += strlen( "CCLNAME" );
+ QString str = mMsg->ccStrip();
+ body.append( getLName( str ) );
+
+ } else if ( cmd.startsWith( "TOADDR" ) ) {
+ kdDebug() << "Command: TOADDR" << endl;
+ i += strlen( "TOADDR" );
+ QString str = mMsg->to();
+ body.append( str );
+
+ } else if ( cmd.startsWith( "TONAME" ) ) {
+ kdDebug() << "Command: TONAME" << endl;
+ i += strlen( "TONAME" );
+ QString str = mMsg->toStrip();
+ body.append( str );
+
+ } else if ( cmd.startsWith( "TOFNAME" ) ) {
+ kdDebug() << "Command: TOFNAME" << endl;
+ i += strlen( "TOFNAME" );
+ QString str = mMsg->toStrip();
+ body.append( getFName( str ) );
+
+ } else if ( cmd.startsWith( "TOLNAME" ) ) {
+ kdDebug() << "Command: TOLNAME" << endl;
+ i += strlen( "TOLNAME" );
+ QString str = mMsg->toStrip();
+ body.append( getLName( str ) );
+
+ } else if ( cmd.startsWith( "TOLIST" ) ) {
+ kdDebug() << "Command: TOLIST" << endl;
+ i += strlen( "TOLIST" );
+ QString str = mMsg->to();
+ body.append( str );
+
+ } else if ( cmd.startsWith( "FROMADDR" ) ) {
+ kdDebug() << "Command: FROMADDR" << endl;
+ i += strlen( "FROMADDR" );
+ QString str = mMsg->from();
+ body.append( str );
+
+ } else if ( cmd.startsWith( "FROMNAME" ) ) {
+ kdDebug() << "Command: FROMNAME" << endl;
+ i += strlen( "FROMNAME" );
+ QString str = mMsg->fromStrip();
+ body.append( str );
+
+ } else if ( cmd.startsWith( "FROMFNAME" ) ) {
+ kdDebug() << "Command: FROMFNAME" << endl;
+ i += strlen( "FROMFNAME" );
+ QString str = mMsg->fromStrip();
+ body.append( getFName( str ) );
+
+ } else if ( cmd.startsWith( "FROMLNAME" ) ) {
+ kdDebug() << "Command: FROMLNAME" << endl;
+ i += strlen( "FROMLNAME" );
+ QString str = mMsg->fromStrip();
+ body.append( getLName( str ) );
+
+ } else if ( cmd.startsWith( "FULLSUBJECT" ) ) {
+ kdDebug() << "Command: FULLSUBJECT" << endl;
+ i += strlen( "FULLSUBJECT" );
+ QString str = mMsg->subject();
+ body.append( str );
+
+ } else if ( cmd.startsWith( "FULLSUBJ" ) ) {
+ kdDebug() << "Command: FULLSUBJ" << endl;
+ i += strlen( "FULLSUBJ" );
+ QString str = mMsg->subject();
+ body.append( str );
+
+ } else if ( cmd.startsWith( "MSGID" ) ) {
+ kdDebug() << "Command: MSGID" << endl;
+ i += strlen( "MSGID" );
+ QString str = mMsg->id();
+ body.append( str );
+
+ } else if ( cmd.startsWith( "OHEADER=" ) ) {
+ // insert specified content of header from original message
+ kdDebug() << "Command: OHEADER=" << endl;
+ QString q;
+ int len = parseQuotes( "OHEADER=", cmd, q );
+ i += len;
+ if ( mOrigMsg ) {
+ QString hdr = q;
+ QString str = mOrigMsg->headerFields(hdr.local8Bit() ).join( ", " );
+ body.append( str );
+ }
+
+ } else if ( cmd.startsWith( "HEADER=" ) ) {
+ // insert specified content of header from current message
+ kdDebug() << "Command: HEADER=" << endl;
+ QString q;
+ int len = parseQuotes( "HEADER=", cmd, q );
+ i += len;
+ QString hdr = q;
+ QString str = mMsg->headerFields(hdr.local8Bit() ).join( ", " );
+ body.append( str );
+
+ } else if ( cmd.startsWith( "HEADER( " ) ) {
+ // insert specified content of header from current message
+ kdDebug() << "Command: HEADER( " << endl;
+ QRegExp re = QRegExp( "^HEADER\\((.+)\\)" );
+ re.setMinimal( true );
+ int res = re.search( cmd );
+ if ( res != 0 ) {
+ // something wrong
+ i += strlen( "HEADER( " );
+ } else {
+ i += re.matchedLength();
+ QString hdr = re.cap( 1 );
+ QString str = mMsg->headerFields( hdr.local8Bit() ).join( ", " );
+ body.append( str );
+ }
+
+ } else if ( cmd.startsWith( "OCCADDR" ) ) {
+ kdDebug() << "Command: OCCADDR" << endl;
+ i += strlen( "OCCADDR" );
+ if ( mOrigMsg ) {
+ QString str = mOrigMsg->cc();
+ body.append( str );
+ }
+
+ } else if ( cmd.startsWith( "OCCNAME" ) ) {
+ kdDebug() << "Command: OCCNAME" << endl;
+ i += strlen( "OCCNAME" );
+ if ( mOrigMsg ) {
+ QString str = mOrigMsg->ccStrip();
+ body.append( str );
+ }
+
+ } else if ( cmd.startsWith( "OCCFNAME" ) ) {
+ kdDebug() << "Command: OCCFNAME" << endl;
+ i += strlen( "OCCFNAME" );
+ if ( mOrigMsg ) {
+ QString str = mOrigMsg->ccStrip();
+ body.append( getFName( str ) );
+ }
+
+ } else if ( cmd.startsWith( "OCCLNAME" ) ) {
+ kdDebug() << "Command: OCCLNAME" << endl;
+ i += strlen( "OCCLNAME" );
+ if ( mOrigMsg ) {
+ QString str = mOrigMsg->ccStrip();
+ body.append( getLName( str ) );
+ }
+
+ } else if ( cmd.startsWith( "OTOADDR" ) ) {
+ kdDebug() << "Command: OTOADDR" << endl;
+ i += strlen( "OTOADDR" );
+ if ( mOrigMsg ) {
+ QString str = mOrigMsg->to();
+ body.append( str );
+ }
+
+ } else if ( cmd.startsWith( "OTONAME" ) ) {
+ kdDebug() << "Command: OTONAME" << endl;
+ i += strlen( "OTONAME" );
+ if ( mOrigMsg ) {
+ QString str = mOrigMsg->toStrip();
+ body.append( str );
+ }
+
+ } else if ( cmd.startsWith( "OTOFNAME" ) ) {
+ kdDebug() << "Command: OTOFNAME" << endl;
+ i += strlen( "OTOFNAME" );
+ if ( mOrigMsg ) {
+ QString str = mOrigMsg->toStrip();
+ body.append( getFName( str ) );
+ }
+
+ } else if ( cmd.startsWith( "OTOLNAME" ) ) {
+ kdDebug() << "Command: OTOLNAME" << endl;
+ i += strlen( "OTOLNAME" );
+ if ( mOrigMsg ) {
+ QString str = mOrigMsg->toStrip();
+ body.append( getLName( str ) );
+ }
+
+ } else if ( cmd.startsWith( "OTOLIST" ) ) {
+ kdDebug() << "Command: OTOLIST" << endl;
+ i += strlen( "OTOLIST" );
+ if ( mOrigMsg ) {
+ QString str = mOrigMsg->to();
+ body.append( str );
+ }
+
+ } else if ( cmd.startsWith( "OTO" ) ) {
+ kdDebug() << "Command: OTO" << endl;
+ i += strlen( "OTO" );
+ if ( mOrigMsg ) {
+ QString str = mOrigMsg->to();
+ body.append( str );
+ }
+
+ } else if ( cmd.startsWith( "OFROMADDR" ) ) {
+ kdDebug() << "Command: OFROMADDR" << endl;
+ i += strlen( "OFROMADDR" );
+ if ( mOrigMsg ) {
+ QString str = mOrigMsg->from();
+ body.append( str );
+ }
+
+ } else if ( cmd.startsWith( "OFROMNAME" ) ) {
+ kdDebug() << "Command: OFROMNAME" << endl;
+ i += strlen( "OFROMNAME" );
+ if ( mOrigMsg ) {
+ QString str = mOrigMsg->fromStrip();
+ body.append( str );
+ }
+
+ } else if ( cmd.startsWith( "OFROMFNAME" ) ) {
+ kdDebug() << "Command: OFROMFNAME" << endl;
+ i += strlen( "OFROMFNAME" );
+ if ( mOrigMsg ) {
+ QString str = mOrigMsg->fromStrip();
+ body.append( getFName( str ) );
+ }
+
+ } else if ( cmd.startsWith( "OFROMLNAME" ) ) {
+ kdDebug() << "Command: OFROMLNAME" << endl;
+ i += strlen( "OFROMLNAME" );
+ if ( mOrigMsg ) {
+ QString str = mOrigMsg->fromStrip();
+ body.append( getLName( str ) );
+ }
+
+ } else if ( cmd.startsWith( "OFULLSUBJECT" ) ) {
+ kdDebug() << "Command: OFULLSUBJECT" << endl;
+ i += strlen( "OFULLSUBJECT" );
+ if ( mOrigMsg ) {
+ QString str = mOrigMsg->subject();
+ body.append( str );
+ }
+
+ } else if ( cmd.startsWith( "OFULLSUBJ" ) ) {
+ kdDebug() << "Command: OFULLSUBJ" << endl;
+ i += strlen( "OFULLSUBJ" );
+ if ( mOrigMsg ) {
+ QString str = mOrigMsg->subject();
+ body.append( str );
+ }
+
+ } else if ( cmd.startsWith( "OMSGID" ) ) {
+ kdDebug() << "Command: OMSGID" << endl;
+ i += strlen( "OMSGID" );
+ if ( mOrigMsg ) {
+ QString str = mOrigMsg->id();
+ body.append( str );
+ }
+
+ } else if ( cmd.startsWith( "DATEEN" ) ) {
+ kdDebug() << "Command: DATEEN" << endl;
+ i += strlen( "DATEEN" );
+ QDateTime date = QDateTime::currentDateTime();
+ KLocale locale( "C" );
+ QString str = locale.formatDate( date.date(), false );
+ body.append( str );
+
+ } else if ( cmd.startsWith( "DATESHORT" ) ) {
+ kdDebug() << "Command: DATESHORT" << endl;
+ i += strlen( "DATESHORT" );
+ QDateTime date = QDateTime::currentDateTime();
+ QString str = KGlobal::locale()->formatDate( date.date(), true );
+ body.append( str );
+
+ } else if ( cmd.startsWith( "DATE" ) ) {
+ kdDebug() << "Command: DATE" << endl;
+ i += strlen( "DATE" );
+ QDateTime date = QDateTime::currentDateTime();
+ QString str = KGlobal::locale()->formatDate( date.date(), false );
+ body.append( str );
+
+ } else if ( cmd.startsWith( "DOW" ) ) {
+ kdDebug() << "Command: DOW" << endl;
+ i += strlen( "DOW" );
+ QDateTime date = QDateTime::currentDateTime();
+ QString str = KGlobal::locale()->calendar()->weekDayName( date.date(), false );
+ body.append( str );
+
+ } else if ( cmd.startsWith( "TIMELONGEN" ) ) {
+ kdDebug() << "Command: TIMELONGEN" << endl;
+ i += strlen( "TIMELONGEN" );
+ QDateTime date = QDateTime::currentDateTime();
+ KLocale locale( "C");
+ QString str = locale.formatTime( date.time(), true );
+ body.append( str );
+
+ } else if ( cmd.startsWith( "TIMELONG" ) ) {
+ kdDebug() << "Command: TIMELONG" << endl;
+ i += strlen( "TIMELONG" );
+ QDateTime date = QDateTime::currentDateTime();
+ QString str = KGlobal::locale()->formatTime( date.time(), true );
+ body.append( str );
+
+ } else if ( cmd.startsWith( "TIME" ) ) {
+ kdDebug() << "Command: TIME" << endl;
+ i += strlen( "TIME" );
+ QDateTime date = QDateTime::currentDateTime();
+ QString str = KGlobal::locale()->formatTime( date.time(), false );
+ body.append( str );
+
+ } else if ( cmd.startsWith( "ODATEEN" ) ) {
+ kdDebug() << "Command: ODATEEN" << endl;
+ i += strlen( "ODATEEN" );
+ if ( mOrigMsg ) {
+ QDateTime date;
+ date.setTime_t( mOrigMsg->date() );
+ KLocale locale( "C");
+ QString str = locale.formatDate( date.date(), false );
+ body.append( str );
+ }
+
+ } else if ( cmd.startsWith( "ODATESHORT") ) {
+ kdDebug() << "Command: ODATESHORT" << endl;
+ i += strlen( "ODATESHORT");
+ if ( mOrigMsg ) {
+ QDateTime date;
+ date.setTime_t( mOrigMsg->date() );
+ QString str = KGlobal::locale()->formatDate( date.date(), true );
+ body.append( str );
+ }
+
+ } else if ( cmd.startsWith( "ODATE") ) {
+ kdDebug() << "Command: ODATE" << endl;
+ i += strlen( "ODATE");
+ if ( mOrigMsg ) {
+ QDateTime date;
+ date.setTime_t( mOrigMsg->date() );
+ QString str = KGlobal::locale()->formatDate( date.date(), false );
+ body.append( str );
+ }
+
+ } else if ( cmd.startsWith( "ODOW") ) {
+ kdDebug() << "Command: ODOW" << endl;
+ i += strlen( "ODOW");
+ if ( mOrigMsg ) {
+ QDateTime date;
+ date.setTime_t( mOrigMsg->date() );
+ QString str = KGlobal::locale()->calendar()->weekDayName( date.date(), false );
+ body.append( str );
+ }
+
+ } else if ( cmd.startsWith( "OTIMELONGEN") ) {
+ kdDebug() << "Command: OTIMELONGEN" << endl;
+ i += strlen( "OTIMELONGEN");
+ if ( mOrigMsg ) {
+ QDateTime date;
+ date.setTime_t( mOrigMsg->date() );
+ KLocale locale( "C");
+ QString str = locale.formatTime( date.time(), true );
+ body.append( str );
+ }
+
+ } else if ( cmd.startsWith( "OTIMELONG") ) {
+ kdDebug() << "Command: OTIMELONG" << endl;
+ i += strlen( "OTIMELONG");
+ if ( mOrigMsg ) {
+ QDateTime date;
+ date.setTime_t( mOrigMsg->date() );
+ QString str = KGlobal::locale()->formatTime( date.time(), true );
+ body.append( str );
+ }
+
+ } else if ( cmd.startsWith( "OTIME") ) {
+ kdDebug() << "Command: OTIME" << endl;
+ i += strlen( "OTIME");
+ if ( mOrigMsg ) {
+ QDateTime date;
+ date.setTime_t( mOrigMsg->date() );
+ QString str = KGlobal::locale()->formatTime( date.time(), false );
+ body.append( str );
+ }
+
+ } else if ( cmd.startsWith( "BLANK" ) ) {
+ // do nothing
+ kdDebug() << "Command: BLANK" << endl;
+ i += strlen( "BLANK" );
+
+ } else if ( cmd.startsWith( "NOP" ) ) {
+ // do nothing
+ kdDebug() << "Command: NOP" << endl;
+ i += strlen( "NOP" );
+
+ } else if ( cmd.startsWith( "CLEAR" ) ) {
+ // clear body buffer; not too useful yet
+ kdDebug() << "Command: CLEAR" << endl;
+ i += strlen( "CLEAR" );
+ body = "";
+ mMsg->setCursorPos( 0 );
+
+ } else if ( cmd.startsWith( "DEBUGOFF" ) ) {
+ // turn off debug
+ kdDebug() << "Command: DEBUGOFF" << endl;
+ i += strlen( "DEBUGOFF" );
+ mDebug = false;
+
+ } else if ( cmd.startsWith( "DEBUG" ) ) {
+ // turn on debug
+ kdDebug() << "Command: DEBUG" << endl;
+ i += strlen( "DEBUG" );
+ mDebug = true;
+
+ } else if ( cmd.startsWith( "CURSOR" ) ) {
+ // turn on debug
+ kdDebug() << "Command: CURSOR" << endl;
+ i += strlen( "CURSOR" );
+ mMsg->setCursorPos( body.length() );
+
+ } else {
+ // wrong command, do nothing
+ body.append( c );
+ }
+
+ } else if ( dnl && ( c == '\n' || c == '\r') ) {
+ // skip
+ if ( ( c == '\n' && tmpl[i + 1] == '\r' ) ||
+ ( c == '\r' && tmpl[i + 1] == '\n' ) ) {
+ // skip one more
+ i += 1;
+ }
+ dnl = false;
+ } else {
+ body.append( c );
+ }
+ }
+
+ // kdDebug() << "Message body: " << body << endl;
+
+ if ( mAppend ) {
+ QCString msg_body = mMsg->body();
+ msg_body.append( body.utf8() );
+ mMsg->setBody( msg_body );
+ } else {
+ mMsg->setBodyFromUnicode( body );
+ }
+}
+
+QString TemplateParser::findCustomTemplate( const QString &tmplName )
+{
+ CTemplates t( tmplName );
+ QString content = t.content();
+ if ( !content.isEmpty() ) {
+ return content;
+ } else {
+ return findTemplate();
+ }
+}
+
+QString TemplateParser::findTemplate()
+{
+ // import 'Phrases' if it not done yet
+ if ( !GlobalSettings::self()->phrasesConverted() ) {
+ TemplatesConfiguration::importFromPhrases();
+ }
+
+ // kdDebug() << "Trying to find template for mode " << mode << endl;
+
+ QString tmpl;
+
+ if ( !mFolder ) { // find folder message belongs to
+ mFolder = mMsg->parent();
+ if ( !mFolder ) {
+ if ( mOrigMsg ) {
+ mFolder = mOrigMsg->parent();
+ }
+ if ( !mFolder ) {
+ kdDebug(5006) << "Oops! No folder for message" << endl;
+ }
+ }
+ }
+ kdDebug(5006) << "Folder found: " << mFolder << endl;
+
+ if ( mFolder ) // only if a folder was found
+ {
+ QString fid = mFolder->idString();
+ Templates fconf( fid );
+ if ( fconf.useCustomTemplates() ) { // does folder use custom templates?
+ switch( mMode ) {
+ case NewMessage:
+ tmpl = fconf.templateNewMessage();
+ break;
+ case Reply:
+ tmpl = fconf.templateReply();
+ break;
+ case ReplyAll:
+ tmpl = fconf.templateReplyAll();
+ break;
+ case Forward:
+ tmpl = fconf.templateForward();
+ break;
+ default:
+ kdDebug(5006) << "Unknown message mode: " << mMode << endl;
+ return "";
+ }
+ mQuoteString = fconf.quoteString();
+ if ( !tmpl.isEmpty() ) {
+ return tmpl; // use folder-specific template
+ }
+ }
+ }
+
+ if ( !mIdentity ) { // find identity message belongs to
+ mIdentity = mMsg->identityUoid();
+ if ( !mIdentity && mOrigMsg ) {
+ mIdentity = mOrigMsg->identityUoid();
+ }
+ mIdentity = kmkernel->identityManager()->identityForUoidOrDefault( mIdentity ).uoid();
+ if ( !mIdentity ) {
+ kdDebug(5006) << "Oops! No identity for message" << endl;
+ }
+ }
+ kdDebug(5006) << "Identity found: " << mIdentity << endl;
+
+ QString iid;
+ if ( mIdentity ) {
+ iid = QString("IDENTITY_%1").arg( mIdentity ); // templates ID for that identity
+ }
+ else {
+ iid = "IDENTITY_NO_IDENTITY"; // templates ID for no identity
+ }
+
+ Templates iconf( iid );
+ if ( iconf.useCustomTemplates() ) { // does identity use custom templates?
+ switch( mMode ) {
+ case NewMessage:
+ tmpl = iconf.templateNewMessage();
+ break;
+ case Reply:
+ tmpl = iconf.templateReply();
+ break;
+ case ReplyAll:
+ tmpl = iconf.templateReplyAll();
+ break;
+ case Forward:
+ tmpl = iconf.templateForward();
+ break;
+ default:
+ kdDebug(5006) << "Unknown message mode: " << mMode << endl;
+ return "";
+ }
+ mQuoteString = iconf.quoteString();
+ if ( !tmpl.isEmpty() ) {
+ return tmpl; // use identity-specific template
+ }
+ }
+
+ switch( mMode ) { // use the global template
+ case NewMessage:
+ tmpl = GlobalSettings::self()->templateNewMessage();
+ break;
+ case Reply:
+ tmpl = GlobalSettings::self()->templateReply();
+ break;
+ case ReplyAll:
+ tmpl = GlobalSettings::self()->templateReplyAll();
+ break;
+ case Forward:
+ tmpl = GlobalSettings::self()->templateForward();
+ break;
+ default:
+ kdDebug(5006) << "Unknown message mode: " << mMode << endl;
+ return "";
+ }
+
+ mQuoteString = GlobalSettings::self()->quoteString();
+ return tmpl;
+}
+
+QString TemplateParser::pipe( const QString &cmd, const QString &buf )
+{
+ mPipeOut = "";
+ mPipeErr = "";
+ mPipeRc = 0;
+
+ KProcess proc;
+ QCString data = buf.local8Bit();
+
+ // kdDebug() << "Command data: " << data << endl;
+
+ proc << KShell::splitArgs( cmd, KShell::TildeExpand );
+ proc.setUseShell( true );
+ connect( &proc, SIGNAL( receivedStdout( KProcess *, char *, int ) ),
+ this, SLOT( onReceivedStdout( KProcess *, char *, int ) ) );
+ connect( &proc, SIGNAL( receivedStderr( KProcess *, char *, int ) ),
+ this, SLOT( onReceivedStderr( KProcess *, char *, int ) ) );
+ connect( &proc, SIGNAL( wroteStdin( KProcess * ) ),
+ this, SLOT( onWroteStdin( KProcess * ) ) );
+
+ if ( proc.start( KProcess::NotifyOnExit, KProcess::All ) ) {
+
+ bool pipe_filled = proc.writeStdin( data, data.length() );
+ if ( pipe_filled ) {
+ proc.closeStdin();
+
+ bool exited = proc.wait( PipeTimeout );
+ if ( exited ) {
+
+ if ( proc.normalExit() ) {
+
+ mPipeRc = proc.exitStatus();
+ if ( mPipeRc != 0 && mDebug ) {
+ if ( mPipeErr.isEmpty() ) {
+ KMessageBox::error( 0,
+ i18n( "Pipe command exit with status %1: %2").
+ arg( mPipeRc ).arg( cmd ) );
+ } else {
+ KMessageBox::detailedError( 0,
+ i18n( "Pipe command exit with status %1: %2" ).
+ arg( mPipeRc ).arg( cmd ), mPipeErr );
+ }
+ }
+
+ } else {
+
+ mPipeRc = -( proc.exitSignal() );
+ if ( mPipeRc != 0 && mDebug ) {
+ if ( mPipeErr.isEmpty() ) {
+ KMessageBox::error( 0,
+ i18n( "Pipe command killed by signal %1: %2" ).
+ arg( -(mPipeRc) ).arg( cmd ) );
+ } else {
+ KMessageBox::detailedError( 0,
+ i18n( "Pipe command killed by signal %1: %2" ).
+ arg( -(mPipeRc) ).arg( cmd ), mPipeErr );
+ }
+ }
+ }
+
+ } else {
+ // process does not exited after TemplateParser::PipeTimeout seconds, kill it
+ proc.kill();
+ proc.detach();
+ if ( mDebug ) {
+ KMessageBox::error( 0,
+ i18n( "Pipe command did not finish within %1 seconds: %2" ).
+ arg( PipeTimeout ).arg( cmd ) );
+ }
+ }
+
+ } else {
+ // can`t write to stdin of process
+ proc.kill();
+ proc.detach();
+ if ( mDebug ) {
+ if ( mPipeErr.isEmpty() ) {
+ KMessageBox::error( 0,
+ i18n( "Cannot write to process stdin: %1" ).arg( cmd ) );
+ } else {
+ KMessageBox::detailedError( 0,
+ i18n( "Cannot write to process stdin: %1" ).
+ arg( cmd ), mPipeErr );
+ }
+ }
+ }
+
+ } else if ( mDebug ) {
+ KMessageBox::error( 0,
+ i18n( "Cannot start pipe command from template: %1" ).
+ arg( cmd ) );
+ }
+
+ return mPipeOut;
+}
+
+void TemplateParser::onProcessExited( KProcess *proc )
+{
+ Q_UNUSED( proc );
+ // do nothing for now
+}
+
+void TemplateParser::onReceivedStdout( KProcess *proc, char *buffer, int buflen )
+{
+ Q_UNUSED( proc );
+ mPipeOut += QString::fromLocal8Bit( buffer, buflen );
+}
+
+void TemplateParser::onReceivedStderr( KProcess *proc, char *buffer, int buflen )
+{
+ Q_UNUSED( proc );
+ mPipeErr += QString::fromLocal8Bit( buffer, buflen );
+}
+
+void TemplateParser::onWroteStdin( KProcess *proc )
+{
+ proc->closeStdin();
+}
+
+#include "templateparser.moc"
diff --git a/kmail/templateparser.h b/kmail/templateparser.h
new file mode 100644
index 00000000..dac78e5f
--- /dev/null
+++ b/kmail/templateparser.h
@@ -0,0 +1,90 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ * kmail: KDE mail client
+ * This file: Copyright (C) 2006 Dmitry Morozhnikov <dmiceman@mail.ru>
+ *
+ * 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_TEMPLATEPARSER_H__
+#define __KMAIL_TEMPLATEPARSER_H__
+
+#include <qobject.h>
+
+class KMMessage;
+class QString;
+class KMFolder;
+class QObject;
+class KProcess;
+
+class TemplateParser : public QObject
+{
+ Q_OBJECT
+
+ public:
+ enum Mode {
+ NewMessage,
+ Reply,
+ ReplyAll,
+ Forward
+ };
+
+ static const int PipeTimeout = 15;
+
+ public:
+ TemplateParser( KMMessage *amsg, const Mode amode, const QString aselection,
+ bool aSmartQuote, bool anoQuote, bool aallowDecryption,
+ bool aselectionIsBody );
+
+ virtual void process( KMMessage *aorig_msg, KMFolder *afolder = NULL, bool append = false );
+ virtual void process( const QString &tmplName, KMMessage *aorig_msg,
+ KMFolder *afolder = NULL, bool append = false );
+ virtual void processWithTemplate( const QString &tmpl );
+ virtual QString findTemplate();
+ virtual QString findCustomTemplate( const QString &tmpl );
+ virtual QString pipe( const QString &cmd, const QString &buf );
+
+ virtual QString getFName( const QString &str );
+ virtual QString getLName( const QString &str );
+
+ protected:
+ Mode mMode;
+ KMFolder *mFolder;
+ uint mIdentity;
+ KMMessage *mMsg;
+ KMMessage *mOrigMsg;
+ QString mSelection;
+ bool mSmartQuote;
+ bool mNoQuote;
+ bool mAllowDecryption;
+ bool mSelectionIsBody;
+ int mPipeRc;
+ QString mPipeOut;
+ QString mPipeErr;
+ bool mDebug;
+ QString mQuoteString;
+ bool mAppend;
+
+ int parseQuotes( const QString &prefix, const QString &str,
+ QString &quote ) const;
+
+ protected slots:
+ void onProcessExited( KProcess *proc );
+ void onReceivedStdout( KProcess *proc, char *buffer, int buflen );
+ void onReceivedStderr( KProcess *proc, char *buffer, int buflen );
+ void onWroteStdin( KProcess *proc );
+};
+
+#endif // __KMAIL_TEMPLATEPARSER_H__
diff --git a/kmail/templatesconfiguration.cpp b/kmail/templatesconfiguration.cpp
new file mode 100644
index 00000000..bc09eff4
--- /dev/null
+++ b/kmail/templatesconfiguration.cpp
@@ -0,0 +1,561 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ * kmail: KDE mail client
+ * This file: Copyright (C) 2006 Dmitry Morozhnikov
+ *
+ * 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 <config.h>
+
+#include <klocale.h>
+#include <kglobal.h>
+#include <qpopupmenu.h>
+#include <qpushbutton.h>
+#include <qtextedit.h>
+#include <qlineedit.h>
+#include <qtoolbox.h>
+#include <kdebug.h>
+#include <qfont.h>
+#include <kactivelabel.h>
+
+#include "templatesconfiguration_base.h"
+#include "templatesconfiguration_kfg.h"
+#include "globalsettings.h"
+#include "replyphrases.h"
+
+#include "templatesconfiguration.h"
+
+TemplatesConfiguration::TemplatesConfiguration( QWidget *parent, const char *name )
+ :TemplatesConfigurationBase( parent, name )
+{
+ QFont f = KGlobalSettings::fixedFont();
+ textEdit_new->setFont( f );
+ textEdit_reply->setFont( f );
+ textEdit_reply_all->setFont( f );
+ textEdit_forward->setFont( f );
+
+ setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
+ sizeHint();
+
+ connect( textEdit_new, SIGNAL( textChanged() ),
+ this, SLOT( slotTextChanged( void ) ) );
+ connect( textEdit_reply, SIGNAL( textChanged() ),
+ this, SLOT( slotTextChanged( void ) ) );
+ connect( textEdit_reply_all, SIGNAL( textChanged() ),
+ this, SLOT( slotTextChanged( void ) ) );
+ connect( textEdit_forward, SIGNAL( textChanged() ),
+ this, SLOT( slotTextChanged( void ) ) );
+ connect( lineEdit_quote, SIGNAL( textChanged( const QString & ) ),
+ this, SLOT( slotTextChanged( void ) ) );
+
+ connect( mInsertCommand, SIGNAL( insertCommand(QString, int) ),
+ this, SLOT( slotInsertCommand(QString, int) ) );
+
+ QString help;
+ if ( QString( name ) == "folder-templates" ) {
+ help =
+ i18n( "<qt>"
+ "<p>Here you can create message templates to use when you "
+ "compose new messages or replies, or when you forward messages.</p>"
+ "<p>The message templates support substitution commands "
+ "by simple typing them or selecting them from menu "
+ "<i>Insert command</i>.</p>"
+ "<p>Templates specified here are folder-specific. "
+ "They override both global templates and per-identity "
+ "templates if they are specified.</p>"
+ "</qt>" );
+ } else if ( QString( name ) == "identity-templates" ) {
+ help =
+ i18n( "<qt>"
+ "<p>Here you can create message templates to use when you "
+ "compose new messages or replies, or when you forward messages.</p>"
+ "<p>The message templates support substitution commands "
+ "by simple typing them or selecting them from menu "
+ "<i>Insert command</i>.</p>"
+ "<p>Templates specified here are mail identity-wide. "
+ "They override global templates and are being overridden by per-folder "
+ "templates if they are specified.</p>"
+ "</qt>" );
+ } else {
+ help =
+ i18n( "<qt>"
+ "<p>Here you can create message templates to use when you "
+ "compose new messages or replies, or when you forward messages.</p>"
+ "<p>The message templates support substitution commands "
+ "by simple typing them or selecting them from menu "
+ "<i>Insert command</i>.</p>"
+ "<p>This is a global (default) template. They can be overridden "
+ "by per-identity templates and by per-folder templates "
+ "if they are specified.</p>"
+ "</qt>" );
+ }
+ mHelp->setText( i18n( "<a href=\"whatsthis:%1\">How does this work?</a>" ).arg( help ) );
+}
+
+void TemplatesConfiguration::slotTextChanged()
+{
+ emit changed();
+}
+
+void TemplatesConfiguration::loadFromGlobal()
+{
+ if ( !GlobalSettings::self()->phrasesConverted() ) {
+ kdDebug() << "Phrases to templates conversion" << endl;
+ importFromPhrases();
+ }
+
+ QString str;
+ str = GlobalSettings::self()->templateNewMessage();
+ if ( str.isEmpty() ) {
+ textEdit_new->setText( defaultNewMessage() );
+ } else {
+ textEdit_new->setText(str);
+ }
+ str = GlobalSettings::self()->templateReply();
+ if ( str.isEmpty() ) {
+ textEdit_reply->setText( defaultReply() );
+ } else {
+ textEdit_reply->setText( str );
+ }
+ str = GlobalSettings::self()->templateReplyAll();
+ if ( str.isEmpty() ) {
+ textEdit_reply_all->setText( defaultReplyAll() );
+ } else {
+ textEdit_reply_all->setText( str );
+ }
+ str = GlobalSettings::self()->templateForward();
+ if ( str.isEmpty() ) {
+ textEdit_forward->setText( defaultForward() );
+ } else {
+ textEdit_forward->setText( str );
+ }
+ str = GlobalSettings::self()->quoteString();
+ if ( str.isEmpty() ) {
+ lineEdit_quote->setText( defaultQuoteString() );
+ } else {
+ lineEdit_quote->setText( str );
+ }
+}
+
+void TemplatesConfiguration::saveToGlobal()
+{
+ GlobalSettings::self()->setTemplateNewMessage( strOrBlank( textEdit_new->text() ) );
+ GlobalSettings::self()->setTemplateReply( strOrBlank( textEdit_reply->text() ) );
+ GlobalSettings::self()->setTemplateReplyAll( strOrBlank( textEdit_reply_all->text() ) );
+ GlobalSettings::self()->setTemplateForward( strOrBlank( textEdit_forward->text() ) );
+ GlobalSettings::self()->setQuoteString( lineEdit_quote->text() );
+ GlobalSettings::self()->setPhrasesConverted( true );
+ GlobalSettings::self()->writeConfig();
+}
+
+void TemplatesConfiguration::loadFromIdentity( uint id )
+{
+ Templates t( QString("IDENTITY_%1").arg( id ) );
+
+ QString str;
+
+ str = t.templateNewMessage();
+ if ( str.isEmpty() ) {
+ str = GlobalSettings::self()->templateNewMessage();
+ }
+ if ( str.isEmpty() ) {
+ str = defaultNewMessage();
+ }
+ textEdit_new->setText( str );
+
+ str = t.templateReply();
+ if ( str.isEmpty() ) {
+ str = GlobalSettings::self()->templateReply();
+ }
+ if ( str.isEmpty() ) {
+ str = defaultReply();
+ }
+ textEdit_reply->setText( str );
+
+ str = t.templateReplyAll();
+ if ( str.isEmpty() ) {
+ str = GlobalSettings::self()->templateReplyAll();
+ }
+ if ( str.isEmpty() ) {
+ str = defaultReplyAll();
+ }
+ textEdit_reply_all->setText( str );
+
+ str = t.templateForward();
+ if ( str.isEmpty() ) {
+ str = GlobalSettings::self()->templateForward();
+ }
+ if ( str.isEmpty() ) {
+ str = defaultForward();
+ }
+ textEdit_forward->setText( str );
+
+ str = t.quoteString();
+ if ( str.isEmpty() ) {
+ str = GlobalSettings::self()->quoteString();
+ }
+ if ( str.isEmpty() ) {
+ str = defaultQuoteString();
+ }
+ lineEdit_quote->setText( str );
+}
+
+void TemplatesConfiguration::saveToIdentity( uint id )
+{
+ Templates t( QString("IDENTITY_%1").arg( id ) );
+
+ t.setTemplateNewMessage( strOrBlank( textEdit_new->text() ) );
+ t.setTemplateReply( strOrBlank( textEdit_reply->text() ) );
+ t.setTemplateReplyAll( strOrBlank( textEdit_reply_all->text() ) );
+ t.setTemplateForward( strOrBlank( textEdit_forward->text() ) );
+ t.setQuoteString( lineEdit_quote->text() );
+ t.writeConfig();
+}
+
+void TemplatesConfiguration::loadFromFolder( QString id, uint identity )
+{
+ Templates t( id );
+ Templates* tid = 0;
+
+ if ( identity ) {
+ tid = new Templates( QString("IDENTITY_%1").arg( identity ) );
+ }
+
+ QString str;
+
+ str = t.templateNewMessage();
+ if ( str.isEmpty() && tid ) {
+ str = tid->templateNewMessage();
+ }
+ if ( str.isEmpty() ) {
+ str = GlobalSettings::self()->templateNewMessage();
+ }
+ if ( str.isEmpty() ) {
+ str = defaultNewMessage();
+ }
+ textEdit_new->setText( str );
+
+ str = t.templateReply();
+ if ( str.isEmpty() && tid ) {
+ str = tid->templateReply();
+ }
+ if ( str.isEmpty() ) {
+ str = GlobalSettings::self()->templateReply();
+ }
+ if ( str.isEmpty() ) {
+ str = defaultReply();
+ }
+ textEdit_reply->setText( str );
+
+ str = t.templateReplyAll();
+ if ( str.isEmpty() && tid ) {
+ str = tid->templateReplyAll();
+ }
+ if ( str.isEmpty() ) {
+ str = GlobalSettings::self()->templateReplyAll();
+ }
+ if ( str.isEmpty() ) {
+ str = defaultReplyAll();
+ }
+ textEdit_reply_all->setText( str );
+
+ str = t.templateForward();
+ if ( str.isEmpty() && tid ) {
+ str = tid->templateForward();
+ }
+ if ( str.isEmpty() ) {
+ str = GlobalSettings::self()->templateForward();
+ }
+ if ( str.isEmpty() ) {
+ str = defaultForward();
+ }
+ textEdit_forward->setText( str );
+
+ str = t.quoteString();
+ if ( str.isEmpty() && tid ) {
+ str = tid->quoteString();
+ }
+ if ( str.isEmpty() ) {
+ str = GlobalSettings::self()->quoteString();
+ }
+ if ( str.isEmpty() ) {
+ str = defaultQuoteString();
+ }
+ lineEdit_quote->setText( str );
+
+ delete(tid);
+}
+
+void TemplatesConfiguration::saveToFolder( QString id )
+{
+ Templates t( id );
+
+ t.setTemplateNewMessage( strOrBlank( textEdit_new->text() ) );
+ t.setTemplateReply( strOrBlank( textEdit_reply->text() ) );
+ t.setTemplateReplyAll( strOrBlank( textEdit_reply_all->text() ) );
+ t.setTemplateForward( strOrBlank( textEdit_forward->text() ) );
+ t.setQuoteString( lineEdit_quote->text() );
+ t.writeConfig();
+}
+
+void TemplatesConfiguration::loadFromPhrases()
+{
+ int currentNr = GlobalSettings::self()->replyCurrentLanguage();
+
+ ReplyPhrases replyPhrases( QString::number( currentNr ) );
+
+ textEdit_new->setText( defaultNewMessage() );
+
+ QString str;
+
+ str = replyPhrases.phraseReplySender();
+ if ( !str.isEmpty() ) {
+ textEdit_reply->setText( convertPhrases( str ) + "\n%QUOTE\n%CURSOR\n" );
+ }
+ else {
+ textEdit_reply->setText( defaultReply() );
+ }
+
+ str = replyPhrases.phraseReplyAll();
+ if ( !str.isEmpty() ) {
+ textEdit_reply_all->setText( convertPhrases( str ) + "\n%QUOTE\n%CURSOR\n" );
+ }
+ else {
+ textEdit_reply_all->setText( defaultReplyAll() );
+ }
+
+ str = replyPhrases.phraseForward();
+ if ( !str.isEmpty() ) {
+ textEdit_forward->setText( QString( i18n(
+ "%REM=\"Default forward template\"%-\n"
+ "---------- %1 ----------\n"
+ "%TEXT\n"
+ "-------------------------------------------------------\n"
+ ) ).arg( convertPhrases( str ) ) );
+ }
+ else {
+ textEdit_forward->setText( defaultForward() );
+ }
+
+ str = replyPhrases.indentPrefix();
+ if ( !str.isEmpty() ) {
+ // no need to convert indentPrefix() because it is passed to KMMessage::asQuotedString() as is
+ lineEdit_quote->setText( str );
+ }
+ else {
+ lineEdit_quote->setText( defaultQuoteString() );
+ }
+}
+
+void TemplatesConfiguration::importFromPhrases()
+{
+ kdDebug() << "TemplatesConfiguration::importFromPhrases()" << endl;
+
+ int currentNr = GlobalSettings::self()->replyCurrentLanguage();
+
+ ReplyPhrases replyPhrases( QString::number( currentNr ) );
+
+ QString str;
+
+ str = replyPhrases.phraseReplySender();
+ if ( !str.isEmpty() ) {
+ GlobalSettings::self()->setTemplateReply( convertPhrases( str ) + "\n%QUOTE\n%CURSOR\n" );
+ }
+ else {
+ GlobalSettings::self()->setTemplateReply( defaultReply() );
+ }
+
+ str = replyPhrases.phraseReplyAll();
+ if ( !str.isEmpty() ) {
+ GlobalSettings::self()->setTemplateReplyAll( convertPhrases( str ) + "\n%QUOTE\n%CURSOR\n" );
+ }
+ else {
+ GlobalSettings::self()->setTemplateReplyAll( defaultReplyAll() );
+ }
+
+ str = replyPhrases.phraseForward();
+ if ( !str.isEmpty() ) {
+ GlobalSettings::self()->setTemplateForward( QString( i18n(
+ "%REM=\"Default forward template\"%-\n"
+ "\n"
+ "---------- %1 ----------\n"
+ "\n"
+ "Subject: %OFULLSUBJECT\n"
+ "Date: %ODATE\n"
+ "From: %OFROMADDR\n"
+ "To: %OTOADDR\n"
+ "\n"
+ "%TEXT\n"
+ "-------------------------------------------------------\n"
+ ) ).arg( convertPhrases( str ) ) );
+ }
+ else {
+ GlobalSettings::self()->setTemplateForward( defaultForward() );
+ }
+
+ str = replyPhrases.indentPrefix();
+ if ( !str.isEmpty() ) {
+ // no need to convert indentPrefix() because it is passed to KMMessage::asQuotedString() as is
+ GlobalSettings::self()->setQuoteString( str );
+ }
+ else {
+ GlobalSettings::self()->setQuoteString( defaultQuoteString() );
+ }
+
+ GlobalSettings::self()->setPhrasesConverted( true );
+ GlobalSettings::self()->writeConfig();
+}
+
+QString TemplatesConfiguration::convertPhrases( QString &str )
+{
+ QString result;
+ QChar ch;
+
+ unsigned int strLength( str.length() );
+ for ( uint i = 0; i < strLength; ) {
+ ch = str[i++];
+ if ( ch == '%' ) {
+ ch = str[i++];
+ switch ( (char)ch ) {
+ case 'D':
+ result += "%ODATE";
+ break;
+ case 'e':
+ result += "%OFROMADDR";
+ break;
+ case 'F':
+ result += "%OFROMNAME";
+ break;
+ case 'f':
+ // is this used for something like FIDO quotes, like "AB>" ?
+ // not supported right now
+ break;
+ case 'T':
+ result += "%OTONAME";
+ break;
+ case 't':
+ result += "%OTOADDR";
+ break;
+ case 'C':
+ result += "%OCCNAME";
+ break;
+ case 'c':
+ result += "%OCCADDR";
+ break;
+ case 'S':
+ result += "%OFULLSUBJECT";
+ break;
+ case '_':
+ result += ' ';
+ break;
+ case 'L':
+ result += "\n";
+ break;
+ case '%':
+ result += "%%";
+ break;
+ default:
+ result += '%';
+ result += ch;
+ break;
+ }
+ } else
+ result += ch;
+ }
+ return result;
+}
+
+void TemplatesConfiguration::slotInsertCommand( QString cmd, int adjustCursor )
+{
+ QTextEdit* edit;
+
+ if( toolBox1->currentItem() == page_new ) {
+ edit = textEdit_new;
+ } else if( toolBox1->currentItem() == page_reply ) {
+ edit = textEdit_reply;
+ } else if( toolBox1->currentItem() == page_reply_all ) {
+ edit = textEdit_reply_all;
+ } else if( toolBox1->currentItem() == page_forward ) {
+ edit = textEdit_forward;
+ } else {
+ kdDebug() << "Unknown current page in TemplatesConfiguration!" << endl;
+ return;
+ }
+
+ // kdDebug() << "Insert command: " << cmd << endl;
+
+ int para, index;
+ edit->getCursorPosition( &para, &index );
+ edit->insertAt( cmd, para, index );
+
+ index += adjustCursor;
+
+ edit->setCursorPosition( para, index + cmd.length() );
+}
+
+QString TemplatesConfiguration::defaultNewMessage() {
+ return i18n(
+ "%REM=\"Default new message template\"%-\n"
+ "%BLANK"
+ );
+}
+
+QString TemplatesConfiguration::defaultReply() {
+ return i18n(
+ "%REM=\"Default reply template\"%-\n"
+ "On %ODATEEN %OTIMELONGEN you wrote:\n"
+ "%QUOTE\n"
+ "%CURSOR\n"
+ );
+}
+
+QString TemplatesConfiguration::defaultReplyAll() {
+ return i18n(
+ "%REM=\"Default reply all template\"%-\n"
+ "On %ODATEEN %OTIMELONGEN %OFROMNAME wrote:\n"
+ "%QUOTE\n"
+ "%CURSOR\n"
+ );
+}
+
+QString TemplatesConfiguration::defaultForward() {
+ return i18n(
+ "%REM=\"Default forward template\"%-\n"
+ "\n"
+ "---------- Forwarded Message ----------\n"
+ "\n"
+ "Subject: %OFULLSUBJECT\n"
+ "Date: %ODATE\n"
+ "From: %OFROMADDR\n"
+ "To: %OTOADDR\n"
+ "\n"
+ "%TEXT\n"
+ "-------------------------------------------------------\n"
+ );
+}
+
+QString TemplatesConfiguration::defaultQuoteString() {
+ return "> ";
+}
+
+QString TemplatesConfiguration::strOrBlank( QString str ) {
+ if ( str.stripWhiteSpace().isEmpty() ) {
+ return QString( "%BLANK" );
+ }
+ return str;
+}
+
+#include "templatesconfiguration.moc"
diff --git a/kmail/templatesconfiguration.h b/kmail/templatesconfiguration.h
new file mode 100644
index 00000000..cb248005
--- /dev/null
+++ b/kmail/templatesconfiguration.h
@@ -0,0 +1,75 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ * kmail: KDE mail client
+ * This file: Copyright (C) 2006 Dmitry Morozhnikov
+ *
+ * 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 <config.h>
+
+#ifndef TEMPLATESCONFIGURATION_H
+#define TEMPLATESCONFIGURATION_H
+
+#include "templatesconfiguration_base.h"
+#include "templatesinsertcommand.h"
+
+class TemplatesConfiguration : public TemplatesConfigurationBase
+{
+ Q_OBJECT
+
+ public:
+
+ TemplatesConfiguration( QWidget *parent = 0, const char *name = 0 );
+
+ void loadFromGlobal();
+ void saveToGlobal();
+ void loadFromIdentity( uint id );
+ void saveToIdentity( uint id );
+ void loadFromFolder( QString id, uint identity = 0 );
+ void saveToFolder( QString id );
+
+ /** Do import settings from 'Phrases' configuration. */
+ void loadFromPhrases();
+
+ /** Do import 'Phrases' settings and store them as global templates. */
+ static void importFromPhrases();
+
+ /** Convert 'Phrases'-like placeholders into 'Templates' compatible. */
+ static QString convertPhrases( QString &str );
+
+ static QString defaultNewMessage();
+ static QString defaultReply();
+ static QString defaultReplyAll();
+ static QString defaultForward();
+ static QString defaultQuoteString();
+
+ public slots:
+
+ void slotInsertCommand( QString cmd, int adjustCursor = 0 );
+
+ void slotTextChanged();
+
+ signals:
+
+ void changed();
+
+ protected:
+
+ QString strOrBlank( QString str );
+
+};
+
+#endif // TEMPLATESCONFIGURATION_H
diff --git a/kmail/templatesconfiguration_base.ui b/kmail/templatesconfiguration_base.ui
new file mode 100644
index 00000000..20345e57
--- /dev/null
+++ b/kmail/templatesconfiguration_base.ui
@@ -0,0 +1,332 @@
+<!DOCTYPE UI><UI version="3.3" stdsetdef="1">
+<class>TemplatesConfigurationBase</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>TemplatesConfigurationBase</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>316</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>7</hsizetype>
+ <vsizetype>7</vsizetype>
+ <horstretch>3</horstretch>
+ <verstretch>3</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>400</width>
+ <height>300</height>
+ </size>
+ </property>
+ <property name="caption">
+ <string>TemplatesConfiguration</string>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QToolBox">
+ <property name="name">
+ <cstring>toolBox1</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>7</hsizetype>
+ <vsizetype>7</vsizetype>
+ <horstretch>3</horstretch>
+ <verstretch>3</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="frameShape">
+ <enum>Panel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Sunken</enum>
+ </property>
+ <property name="currentIndex">
+ <number>3</number>
+ </property>
+ <widget class="QWidget">
+ <property name="name">
+ <cstring>page_new</cstring>
+ </property>
+ <property name="backgroundMode">
+ <enum>PaletteBackground</enum>
+ </property>
+ <attribute name="label">
+ <string>New Message</string>
+ </attribute>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QTextEdit">
+ <property name="name">
+ <cstring>textEdit_new</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>7</hsizetype>
+ <vsizetype>7</vsizetype>
+ <horstretch>3</horstretch>
+ <verstretch>3</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <family>Monospace</family>
+ </font>
+ </property>
+ <property name="resizePolicy">
+ <enum>Manual</enum>
+ </property>
+ <property name="vScrollBarMode">
+ <enum>Auto</enum>
+ </property>
+ <property name="hScrollBarMode">
+ <enum>Auto</enum>
+ </property>
+ <property name="textFormat">
+ <enum>PlainText</enum>
+ </property>
+ <property name="wordWrap">
+ <enum>NoWrap</enum>
+ </property>
+ <property name="autoFormatting">
+ <set>AutoNone</set>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ <widget class="QWidget">
+ <property name="name">
+ <cstring>page_reply</cstring>
+ </property>
+ <property name="backgroundMode">
+ <enum>PaletteBackground</enum>
+ </property>
+ <attribute name="label">
+ <string>Reply to Sender</string>
+ </attribute>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QTextEdit">
+ <property name="name">
+ <cstring>textEdit_reply</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>3</hsizetype>
+ <vsizetype>3</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <family>Monospace</family>
+ </font>
+ </property>
+ <property name="textFormat">
+ <enum>PlainText</enum>
+ </property>
+ <property name="wordWrap">
+ <enum>NoWrap</enum>
+ </property>
+ <property name="autoFormatting">
+ <set>AutoNone</set>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ <widget class="QWidget">
+ <property name="name">
+ <cstring>page_reply_all</cstring>
+ </property>
+ <property name="backgroundMode">
+ <enum>PaletteBackground</enum>
+ </property>
+ <attribute name="label">
+ <string>Reply to All / Reply to List</string>
+ </attribute>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QTextEdit">
+ <property name="name">
+ <cstring>textEdit_reply_all</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>3</hsizetype>
+ <vsizetype>3</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <family>Monospace</family>
+ </font>
+ </property>
+ <property name="textFormat">
+ <enum>PlainText</enum>
+ </property>
+ <property name="wordWrap">
+ <enum>NoWrap</enum>
+ </property>
+ <property name="autoFormatting">
+ <set>AutoNone</set>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ <widget class="QWidget">
+ <property name="name">
+ <cstring>page_forward</cstring>
+ </property>
+ <property name="backgroundMode">
+ <enum>PaletteBackground</enum>
+ </property>
+ <attribute name="label">
+ <string>Forward Message</string>
+ </attribute>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QTextEdit">
+ <property name="name">
+ <cstring>textEdit_forward</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>3</hsizetype>
+ <vsizetype>3</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="font">
+ <font>
+ <family>Monospace</family>
+ </font>
+ </property>
+ <property name="textFormat">
+ <enum>PlainText</enum>
+ </property>
+ <property name="wordWrap">
+ <enum>NoWrap</enum>
+ </property>
+ <property name="autoFormatting">
+ <set>AutoNone</set>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ </widget>
+ <widget class="KActiveLabel">
+ <property name="name">
+ <cstring>mHelp</cstring>
+ </property>
+ <property name="text">
+ <string>How does this work?</string>
+ </property>
+ </widget>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout5</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="TemplatesInsertCommand">
+ <property name="name">
+ <cstring>mInsertCommand</cstring>
+ </property>
+ </widget>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>textLabel1</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>5</vsizetype>
+ <horstretch>2</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>&amp;Quote indicator:</string>
+ </property>
+ <property name="alignment">
+ <set>AlignVCenter|AlignRight</set>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>lineEdit_quote</cstring>
+ </property>
+ </widget>
+ <widget class="QLineEdit">
+ <property name="name">
+ <cstring>lineEdit_quote</cstring>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ </vbox>
+</widget>
+<customwidgets>
+ <customwidget>
+ <class>TemplatesInsertCommand</class>
+ <header location="local">templatesinsertcommand.h</header>
+ <sizehint>
+ <width>50</width>
+ <height>10</height>
+ </sizehint>
+ <container>0</container>
+ <sizepolicy>
+ <hordata>5</hordata>
+ <verdata>5</verdata>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ <pixmap>image0</pixmap>
+ <signal>insertCommand(int)</signal>
+ </customwidget>
+</customwidgets>
+<images>
+ <image name="image0">
+ <data format="PNG" length="807">89504e470d0a1a0a0000000d49484452000000100000001008060000001ff3ff61000002ee49444154388d7d93cd6b5c6514c67fe7bdef9d8f24d349c66412a203b1b4282d21140918824816418a0b454d75e1a28ab8c8dfe1c25d70e34e0812ea4644d13610c43a1121b5d84cab6943486227499d869ae6633e32f7de79ef71d58644f0593dabdf39cf7938a2aa4c4f4f9b300c0b954ae56ca55249876128aa2a9c90738eadadada0542afd954ea7cb636363a19d9f9f4755cff6f4a53e4d67ed78f7b37ebb8b05e3810818017d0a68f1dcc36463a7ea8a4b8bdb9fcccdcdddf6eaf5bae4f3f9f7bb7a773faee94fed81b983a6d6d9ab6eb2f5f73affec6de1a536c9e4d7497696c9f63cf237d66b85b5e570cf3977c7aeaeae9a42a130d03d506bdbaddda3d1805faec6dc5a68e2a208e30124187a39c9ebefa6c8f544f84949a9ea69e75cd60641c0e1e1a1b8b849142ac51f226ece1f32fc4a86f31732f86db076178ad70e20765cfae8142207009eaafac639270022c2fd15a1b470c8f81b59defed0637024c44596c269cb3b1fe4585c68f2c76f0e23064000639e5c5804caab2d8c24187ed5c3265adc5bf4b9f2f91e377eae73fe254b6fbfc7ea7283588f9a314756089b31ed5925dd61b87b2bc5d75fd4a91d843c7fa68d5caf23fb4c8246fdc9f01300e3412e9f60f7313c285b6edf0c696bf338f3620773df3de2ea15d8de0ce92f2448f8472bd8a700515e18146e5c3714afd5197f3343ae3b463ce1cbcf94efbf7a48b6cb303462595c3801505514280c182ebed5ce37333bec3f8e387721859f80da7e8c114714097b3be65804eb799e562a95e641358e4d47cb8cbc66c9f5f6f1eb8f55967eafa140bedfe7e27b7dac2c4588a7ec6cb7622004629b4c2629954a7fda6c6b3f5f08baac1f61bd2683c3867a358931865339c8e69a9c1b323cb87fc0fa72d81091351189646262426667677b620e2fabea25942e504144fef34d80735a752daeab9a6f7ddf5f9162b1c8e4e4a45f2e974f0541d0a3aa19c03f16f4b862a061adddc96432bba2aaccccccc8d4d494b7b1b1910c8220a1aae6ff00d6da56676767303a3a1afd0b29d2596f22d0b7b20000000049454e44ae426082</data>
+ </image>
+</images>
+<layoutdefaults spacing="6" margin="11"/>
+<includehints>
+ <includehint>kactivelabel.h</includehint>
+ <includehint>templatesinsertcommand.h</includehint>
+</includehints>
+</UI>
diff --git a/kmail/templatesconfiguration_kfg.kcfg b/kmail/templatesconfiguration_kfg.kcfg
new file mode 100644
index 00000000..5fab53e7
--- /dev/null
+++ b/kmail/templatesconfiguration_kfg.kcfg
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
+ http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
+ <kcfgfile name="kmailrc">
+ <parameter name="folder" />
+ </kcfgfile>
+
+ <group name="Templates #$(folder)">
+ <entry name="UseCustomTemplates" type="Bool" key="UseCustomTemplates">
+ <label></label>
+ <whatsthis></whatsthis>
+ <default>false</default>
+ </entry>
+ <entry name="TemplateNewMessage" type="String" key="TemplateNewMessage">
+ <label></label>
+ <whatsthis></whatsthis>
+ <default></default>
+ </entry>
+ <entry name="TemplateReply" type="String" key="TemplateReply">
+ <label></label>
+ <whatsthis></whatsthis>
+ <default></default>
+ </entry>
+ <entry name="TemplateReplyAll" type="String" key="TemplateReplyAll">
+ <label></label>
+ <whatsthis></whatsthis>
+ <default></default>
+ </entry>
+ <entry name="TemplateForward" type="String" key="TemplateForward">
+ <label></label>
+ <whatsthis></whatsthis>
+ <default></default>
+ </entry>
+ <entry name="QuoteString" type="String" key="QuoteString">
+ <label></label>
+ <whatsthis></whatsthis>
+ <default></default>
+ </entry>
+ </group>
+
+</kcfg>
diff --git a/kmail/templatesconfiguration_kfg.kcfgc b/kmail/templatesconfiguration_kfg.kcfgc
new file mode 100644
index 00000000..c306a41f
--- /dev/null
+++ b/kmail/templatesconfiguration_kfg.kcfgc
@@ -0,0 +1,5 @@
+File=templatesconfiguration_kfg.kcfg
+ClassName=Templates
+Mutators=true
+ItemAccessors=true
+SetUserTexts=true
diff --git a/kmail/templatesinsertcommand.cpp b/kmail/templatesinsertcommand.cpp
new file mode 100644
index 00000000..f7482035
--- /dev/null
+++ b/kmail/templatesinsertcommand.cpp
@@ -0,0 +1,402 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ * kmail: KDE mail client
+ * This file: Copyright (C) 2006 Dmitry Morozhnikov <dmiceman@mail.ru>
+ *
+ * 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 <config.h>
+
+#include <qpushbutton.h>
+#include <kaction.h>
+#include <kactionclasses.h>
+#include <kpopupmenu.h>
+#include <klocale.h>
+#include <qsignalmapper.h>
+#include <kdebug.h>
+
+#include "templatesinsertcommand.h"
+
+TemplatesInsertCommand::TemplatesInsertCommand( QWidget *parent,
+ const char *name )
+ : QPushButton( parent, name )
+{
+ setText( i18n( "&Insert Command..." ) );
+ connect( this, SIGNAL( clicked() ),
+ this, SLOT( slotClicked() ) );
+
+ KAction *action;
+ KActionMenu *menu;
+
+ QSignalMapper *mapper = new QSignalMapper( this );
+ connect( mapper, SIGNAL( mapped(int) ),
+ this, SLOT( slotMapped(int) ) );
+
+ mMenu = new KActionMenu( i18n( "Insert Command..." ), this );
+
+ // ******************************************************
+ menu = new KActionMenu( i18n( "Original Message" ), mMenu );
+ mMenu->insert( menu );
+
+ action = new KAction( i18n("Quoted Message"),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CQuote );
+ menu->insert( action );
+ action = new KAction( i18n("Message Text as Is"),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CText );
+ menu->insert( action );
+ action = new KAction( i18n("Message Id"),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, COMsgId );
+ menu->insert( action );
+ action = new KAction( i18n("Date"),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CODate );
+ menu->insert( action );
+ action = new KAction( i18n("Date in Short Format"),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CODateShort );
+ menu->insert( action );
+ action = new KAction( i18n("Date in C Locale"),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CODateEn );
+ menu->insert( action );
+ action = new KAction( i18n("Day of Week"),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CODow );
+ menu->insert( action );
+ action = new KAction( i18n("Time"),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, COTime );
+ menu->insert( action );
+ action = new KAction( i18n("Time in Long Format"),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, COTimeLong );
+ menu->insert( action );
+ action = new KAction( i18n("Time in C Locale"),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, COTimeLongEn );
+ menu->insert( action );
+ action = new KAction( i18n("To Field Address"),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, COToAddr );
+ menu->insert( action );
+ action = new KAction( i18n("To Field Name"),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, COToName );
+ menu->insert( action );
+ action = new KAction( i18n("To Field First Name"),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, COToFName );
+ menu->insert( action );
+ action = new KAction( i18n("To Field Last Name"),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, COToLName );
+ menu->insert( action );
+ action = new KAction( i18n( "CC Field Address" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, COCCAddr );
+ menu->insert( action );
+ action = new KAction( i18n( "CC Field Name" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, COCCName );
+ menu->insert( action );
+ action = new KAction( i18n( "CC Field First Name" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, COCCFName );
+ menu->insert( action );
+ action = new KAction( i18n( "CC Field Last Name" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, COCCLName );
+ menu->insert( action );
+ action = new KAction( i18n( "From Field Address" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, COFromAddr );
+ menu->insert( action );
+ action = new KAction( i18n( "From Field Name" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, COFromName );
+ menu->insert( action );
+ action = new KAction( i18n( "From Field First Name" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, COFromFName );
+ menu->insert( action );
+ action = new KAction( i18n( "From Field Last Name" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, COFromLName );
+ menu->insert( action );
+ action = new KAction( i18n( "Subject" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, COFullSubject );
+ menu->insert( action );
+ action = new KAction( i18n( "Quoted Headers" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CQHeaders );
+ menu->insert( action );
+ action = new KAction( i18n( "Headers as Is" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CHeaders );
+ menu->insert( action );
+ action = new KAction( i18n( "Header Content" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, COHeader );
+ menu->insert( action );
+
+ // ******************************************************
+ menu = new KActionMenu( i18n( "Current Message" ), mMenu );
+ mMenu->insert( menu );
+
+ action = new KAction( i18n( "Message Id" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CMsgId );
+ menu->insert( action );
+ action = new KAction( i18n( "Date" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CDate );
+ menu->insert( action );
+ action = new KAction( i18n( "Date in Short Format" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CDateShort );
+ menu->insert( action );
+ action = new KAction( i18n( "Date in C Locale" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CDateEn );
+ menu->insert( action );
+ action = new KAction( i18n( "Day of Week" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CDow );
+ menu->insert( action );
+ action = new KAction( i18n( "Time" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CTime );
+ menu->insert( action );
+ action = new KAction( i18n( "Time in Long Format" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CTimeLong );
+ menu->insert( action );
+ action = new KAction( i18n( "Time in C Locale" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CTimeLongEn );
+ menu->insert( action );
+ action = new KAction( i18n( "To Field Address" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CToAddr );
+ menu->insert( action );
+ action = new KAction( i18n( "To Field Name" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CToName );
+ menu->insert( action );
+ action = new KAction( i18n( "To Field First Name" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CToFName );
+ menu->insert( action );
+ action = new KAction( i18n( "To Field Last Name" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CToLName );
+ menu->insert( action );
+ action = new KAction( i18n( "CC Field Address" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CCCAddr );
+ menu->insert( action );
+ action = new KAction( i18n( "CC Field Name" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CCCName );
+ menu->insert( action );
+ action = new KAction( i18n( "CC Field First Name" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CCCFName );
+ menu->insert( action );
+ action = new KAction( i18n( "CC Field Last Name" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CCCLName );
+ menu->insert( action );
+ action = new KAction( i18n( "From Field Address" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CFromAddr );
+ menu->insert( action );
+ action = new KAction( i18n( "From Field Name" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CFromName );
+ menu->insert( action );
+ action = new KAction( i18n( "From Field First Name" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CFromFName );
+ menu->insert( action );
+ action = new KAction( i18n( "From Field Last Name" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CFromLName );
+ menu->insert( action );
+ action = new KAction( i18n( "Subject" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CFullSubject );
+ menu->insert( action );
+ action = new KAction( i18n( "Header Content" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CHeader );
+ menu->insert( action );
+
+ // ******************************************************
+ menu = new KActionMenu( i18n( "Process With External Programs" ), mMenu );
+ mMenu->insert( menu );
+
+ action = new KAction( i18n( "Insert Result of Command" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CSystem );
+ menu->insert( action );
+ action = new KAction( i18n( "Pipe Original Message Body and Insert Result as Quoted Text" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CQuotePipe );
+ menu->insert( action );
+ action = new KAction( i18n( "Pipe Original Message Body and Insert Result as Is" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CTextPipe );
+ menu->insert( action );
+ action = new KAction( i18n( "Pipe Original Message with Headers and Insert Result as Is" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CMsgPipe );
+ menu->insert( action );
+ action = new KAction( i18n( "Pipe Current Message Body and Insert Result as Is" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CBodyPipe );
+ menu->insert( action );
+ action = new KAction( i18n( "Pipe Current Message Body and Replace with Result" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CClearPipe );
+ menu->insert( action );
+
+ // ******************************************************
+ menu = new KActionMenu( i18n( "Miscellaneous" ), mMenu );
+ mMenu->insert( menu );
+
+ action = new KAction( i18n( "Set Cursor Position" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CCursor );
+ menu->insert( action );
+ action = new KAction( i18n( "Insert File Content" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CInsert );
+ menu->insert( action );
+ action = new KAction( i18n( "DNL" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CDnl );
+ menu->insert( action );
+ action = new KAction( i18n( "Template Comment" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CRem );
+ menu->insert( action );
+ action = new KAction( i18n( "No Operation" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CNop );
+ menu->insert( action );
+ action = new KAction( i18n( "Clear Generated Message" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CClear );
+ menu->insert( action );
+ action = new KAction( i18n( "Turn Debug On" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CDebug );
+ menu->insert( action );
+ action = new KAction( i18n( "Turn Debug Off" ),
+ 0, mapper, SLOT( map() ), menu );
+ mapper->setMapping( action, CDebugOff );
+ menu->insert( action );
+}
+
+TemplatesInsertCommand::~TemplatesInsertCommand()
+{
+}
+
+void TemplatesInsertCommand::slotClicked()
+{
+ QSize ps = mMenu->popupMenu()->sizeHint();
+ mMenu->popup( mapToGlobal( QPoint( 0, -(ps.height()) ) ) );
+}
+
+void TemplatesInsertCommand::slotMapped( int cmd )
+{
+ emit insertCommand( static_cast<Command>( cmd ) );
+
+ switch( cmd ) {
+ case TemplatesInsertCommand::CQuote: emit insertCommand("%QUOTE"); break;
+ case TemplatesInsertCommand::CText: emit insertCommand("%TEXT"); break;
+ case TemplatesInsertCommand::COMsgId: emit insertCommand("%OMSGID"); break;
+ case TemplatesInsertCommand::CODate: emit insertCommand("%ODATE"); break;
+ case TemplatesInsertCommand::CODateShort: emit insertCommand("%ODATESHORT"); break;
+ case TemplatesInsertCommand::CODateEn: emit insertCommand("%ODATEEN"); break;
+ case TemplatesInsertCommand::CODow: emit insertCommand("%ODOW"); break;
+ case TemplatesInsertCommand::COTime: emit insertCommand("%OTIME"); break;
+ case TemplatesInsertCommand::COTimeLong: emit insertCommand("%OTIMELONG"); break;
+ case TemplatesInsertCommand::COTimeLongEn: emit insertCommand("%OTIMELONGEN"); break;
+ case TemplatesInsertCommand::COToAddr: emit insertCommand("%OTOADDR"); break;
+ case TemplatesInsertCommand::COToName: emit insertCommand("%OTONAME"); break;
+ case TemplatesInsertCommand::COToFName: emit insertCommand("%OTOFNAME"); break;
+ case TemplatesInsertCommand::COToLName: emit insertCommand("%OTOLNAME"); break;
+ case TemplatesInsertCommand::COCCAddr: emit insertCommand("%OCCADDR"); break;
+ case TemplatesInsertCommand::COCCName: emit insertCommand("%OCCNAME"); break;
+ case TemplatesInsertCommand::COCCFName: emit insertCommand("%OCCFNAME"); break;
+ case TemplatesInsertCommand::COCCLName: emit insertCommand("%OCCLNAME"); break;
+ case TemplatesInsertCommand::COFromAddr: emit insertCommand("%OFROMADDR"); break;
+ case TemplatesInsertCommand::COFromName: emit insertCommand("%OFROMNAME"); break;
+ case TemplatesInsertCommand::COFromFName: emit insertCommand("%OFROMFNAME"); break;
+ case TemplatesInsertCommand::COFromLName: emit insertCommand("%OFROMLNAME"); break;
+ case TemplatesInsertCommand::COFullSubject: emit insertCommand("%OFULLSUBJECT"); break;
+ case TemplatesInsertCommand::CQHeaders: emit insertCommand("%QHEADERS"); break;
+ case TemplatesInsertCommand::CHeaders: emit insertCommand("%HEADERS"); break;
+ case TemplatesInsertCommand::COHeader: emit insertCommand("%OHEADER=\"\"", -1); break;
+ case TemplatesInsertCommand::CMsgId: emit insertCommand("%MSGID"); break;
+ case TemplatesInsertCommand::CDate: emit insertCommand("%DATE"); break;
+ case TemplatesInsertCommand::CDateShort: emit insertCommand("%DATESHORT"); break;
+ case TemplatesInsertCommand::CDateEn: emit insertCommand("%DATEEN"); break;
+ case TemplatesInsertCommand::CDow: emit insertCommand("%DOW"); break;
+ case TemplatesInsertCommand::CTime: emit insertCommand("%TIME"); break;
+ case TemplatesInsertCommand::CTimeLong: emit insertCommand("%TIMELONG"); break;
+ case TemplatesInsertCommand::CTimeLongEn: emit insertCommand("%TIMELONGEN"); break;
+ case TemplatesInsertCommand::CToAddr: emit insertCommand("%TOADDR"); break;
+ case TemplatesInsertCommand::CToName: emit insertCommand("%TONAME"); break;
+ case TemplatesInsertCommand::CToFName: emit insertCommand("%TOFNAME"); break;
+ case TemplatesInsertCommand::CToLName: emit insertCommand("%TOLNAME"); break;
+ case TemplatesInsertCommand::CCCAddr: emit insertCommand("%CCADDR"); break;
+ case TemplatesInsertCommand::CCCName: emit insertCommand("%CCNAME"); break;
+ case TemplatesInsertCommand::CCCFName: emit insertCommand("%CCFNAME"); break;
+ case TemplatesInsertCommand::CCCLName: emit insertCommand("%CCLNAME"); break;
+ case TemplatesInsertCommand::CFromAddr: emit insertCommand("%FROMADDR"); break;
+ case TemplatesInsertCommand::CFromName: emit insertCommand("%FROMNAME"); break;
+ case TemplatesInsertCommand::CFromFName: emit insertCommand("%FROMFNAME"); break;
+ case TemplatesInsertCommand::CFromLName: emit insertCommand("%FROMLNAME"); break;
+ case TemplatesInsertCommand::CFullSubject: emit insertCommand("%FULLSUBJECT"); break;
+ case TemplatesInsertCommand::CHeader: emit insertCommand("%HEADER=\"\"", -1); break;
+ case TemplatesInsertCommand::CSystem: emit insertCommand("%SYSTEM=\"\"", -1); break;
+ case TemplatesInsertCommand::CQuotePipe: emit insertCommand("%QUOTEPIPE=\"\"", -1); break;
+ case TemplatesInsertCommand::CTextPipe: emit insertCommand("%TEXTPIPE=\"\"", -1); break;
+ case TemplatesInsertCommand::CMsgPipe: emit insertCommand("%MSGPIPE=\"\"", -1); break;
+ case TemplatesInsertCommand::CBodyPipe: emit insertCommand("%BODYPIPE=\"\"", -1); break;
+ case TemplatesInsertCommand::CClearPipe: emit insertCommand("%CLEARPIPE=\"\"", -1); break;
+ case TemplatesInsertCommand::CCursor: emit insertCommand("%CURSOR"); break;
+ case TemplatesInsertCommand::CInsert: emit insertCommand("%INSERT=\"\"", -1); break;
+ case TemplatesInsertCommand::CDnl: emit insertCommand("%-"); break;
+ case TemplatesInsertCommand::CRem: emit insertCommand("%REM=\"\"", -1); break;
+ case TemplatesInsertCommand::CNop: emit insertCommand("%NOP"); break;
+ case TemplatesInsertCommand::CClear: emit insertCommand("%CLEAR"); break;
+ case TemplatesInsertCommand::CDebug: emit insertCommand("%DEBUG"); break;
+ case TemplatesInsertCommand::CDebugOff: emit insertCommand("%DEBUGOFF"); break;
+ default:
+ kdDebug() << "Unknown template command index: " << cmd << endl;
+ break;
+ }
+}
+
+#include "templatesinsertcommand.moc"
diff --git a/kmail/templatesinsertcommand.h b/kmail/templatesinsertcommand.h
new file mode 100644
index 00000000..f6d54cc1
--- /dev/null
+++ b/kmail/templatesinsertcommand.h
@@ -0,0 +1,61 @@
+/* -*- mode: C++; c-file-style: "gnu" -*-
+ * kmail: KDE mail client
+ * This file: Copyright (C) 2006 Dmitry Morozhnikov <dmiceman@mail.ru>
+ *
+ * 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 <config.h>
+
+#ifndef TEMPLATESINSERTCOMMAND_H
+#define TEMPLATESINSERTCOMMAND_H
+
+#include <qpushbutton.h>
+
+class KActionMenu;
+
+class TemplatesInsertCommand : public QPushButton
+{
+ Q_OBJECT
+
+ public:
+ TemplatesInsertCommand( QWidget *parent, const char *name = 0 );
+ ~TemplatesInsertCommand();
+
+ public:
+ enum Command {CDnl = 1, CRem, CInsert, CSystem, CQuotePipe, CQuote, CQHeaders, CHeaders,
+ CTextPipe, CMsgPipe, CBodyPipe, CClearPipe, CText,
+ CToAddr, CToName, CFromAddr, CFromName, CFullSubject, CMsgId,
+ COHeader, CHeader, COToAddr, COToName, COFromAddr, COFromName, COFullSubject,
+ COMsgId, CDateEn, CDateShort, CDate, CDow, CTimeLongEn, CTimeLong, CTime,
+ 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 };
+
+ signals:
+ void insertCommand( TemplatesInsertCommand::Command cmd );
+ void insertCommand( QString cmd, int adjustCursor = 0 );
+
+ public slots:
+ void slotClicked();
+ void slotMapped( int cmd );
+
+ protected:
+ KActionMenu *mMenu;
+};
+
+#endif // TEMPLATESINSERTCOMMAND_H
diff --git a/kmail/tests/Makefile.am b/kmail/tests/Makefile.am
new file mode 100644
index 00000000..f8bc6ce4
--- /dev/null
+++ b/kmail/tests/Makefile.am
@@ -0,0 +1,23 @@
+INCLUDES = -I$(top_srcdir)/kmail -I$(top_srcdir)/mimelib $(all_includes)
+AM_CPPFLAGS = -DKDESRCDIR=\"$(srcdir)\"
+METASOURCES = AUTO
+
+check_LTLIBRARIES = kunittest_storagelayermodule.la \
+ kunittest_utilmodule.la \
+ kunittest_mimelibmodule.la
+
+kunittest_storagelayermodule_la_SOURCES = storagelayermodule.cpp messagedicttests.cpp ../kmdict.cpp
+kunittest_storagelayermodule_la_LIBADD = -lkunittest
+kunittest_utilmodule_la_SOURCES = utiltests.cpp ../util.cpp
+kunittest_utilmodule_la_LIBADD = -lkunittest ../../mimelib/libmimelib.la
+kunittest_mimelibmodule_la_SOURCES = mimelibtests.cpp ../util.cpp
+kunittest_mimelibmodule_la_LIBADD = -lkunittest ../../mimelib/libmimelib.la
+
+#LIBADD = -lkunittest
+AM_LDFLAGS = -module $(KDE_CHECK_PLUGIN) $(all_libraries)
+
+check-local:
+ kunittestmodrunner
+
+guicheck:
+ kunittestguimodrunner
diff --git a/kmail/tests/messagedicttests.cpp b/kmail/tests/messagedicttests.cpp
new file mode 100644
index 00000000..063fc1cb
--- /dev/null
+++ b/kmail/tests/messagedicttests.cpp
@@ -0,0 +1,79 @@
+/**
+ * Copyright (C) 2005 Till Adam <adam@kde.org>
+ * This file is subject to the GPL version 2.
+ */
+
+#include <kdebug.h>
+#include <kunittest/runner.h>
+#include <kunittest/module.h>
+
+#include "kmdict.h"
+
+#include "messagedicttests.h"
+
+static void p( const QString & str )
+{
+ kdDebug() << str << endl;
+}
+
+void MessageDictTester::setUp()
+{
+ kdDebug() << "setUp" << endl;
+ m_dict = new KMDict( 4 ); // will be thrown away in init
+}
+
+void MessageDictTester::tearDown()
+{
+ kdDebug() << "tearDown" << endl;
+ delete m_dict;
+}
+
+void MessageDictTester::testKMDictCreation()
+{
+ p("MessageDictTester::testKMDict()");
+ p("Check creation with size of next prime: ");
+ CHECK( m_dict->size(), 31 );
+ m_dict->init( 13 ); // will be created with a 13, no nextPrime()
+ CHECK( m_dict->size(), 13 );
+}
+
+void MessageDictTester::testKMDictInsert()
+{
+ p("Insert: ");
+ KMDictItem *item = new KMDictItem();
+ m_dict->insert( 12345, item );
+ KMDictItem *found = m_dict->find( 12345 );
+ CHECK( item, found);
+}
+
+void MessageDictTester::testKMDictRemove()
+{
+ p("Remove: ");
+ m_dict->remove( 12345 );
+ KMDictItem *item = m_dict->find( 12345 );
+ CHECK( item, (KMDictItem*)0 );
+}
+
+void MessageDictTester::testKMDictClear()
+{
+ p("Check clear: ");
+ for ( unsigned int i=0; i<11; ++i )
+ m_dict->insert( i, new KMDictItem() );
+ m_dict->clear();
+ CHECK( m_dict->mVecs, (KMDictItem**)0 );
+}
+
+void MessageDictTester::testKMDictReplace()
+{
+ p("Check replace: ");
+ m_dict->init( 31 );
+ KMDictItem *oldItem = new KMDictItem();
+ KMDictItem *newItem = new KMDictItem();
+ m_dict->insert( 12345, oldItem );
+ m_dict->replace( 12345, newItem );
+ KMDictItem *found = m_dict->find( 12345 );
+ CHECK( found, newItem );
+}
+
+#include "messagedicttests.moc"
+
diff --git a/kmail/tests/messagedicttests.h b/kmail/tests/messagedicttests.h
new file mode 100644
index 00000000..81385eff
--- /dev/null
+++ b/kmail/tests/messagedicttests.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2005 Till Adam <adam@kde.org>
+ *
+ * This file is subject to the GPL version 2.
+ */
+
+#ifndef MESSAGEDICTTESTS_H
+#define MESSAGEDICTTESTS_H
+
+#include <kunittest/tester.h>
+
+class KMDict;
+
+class MessageDictTester : public KUnitTest::SlotTester
+{
+ Q_OBJECT
+
+public slots:
+ void setUp();
+ void tearDown();
+ void testKMDictCreation();
+ void testKMDictInsert();
+ void testKMDictRemove();
+ void testKMDictClear();
+ void testKMDictReplace();
+private:
+ KMDict *m_dict;
+};
+
+#endif
diff --git a/kmail/tests/mimelibtests.cpp b/kmail/tests/mimelibtests.cpp
new file mode 100644
index 00000000..8d9cf9e7
--- /dev/null
+++ b/kmail/tests/mimelibtests.cpp
@@ -0,0 +1,117 @@
+/* This file is part of the KDE project
+ Copyright (C) 2007 David Faure <faure@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "mimelibtests.h"
+#include "mimelibtests.moc"
+
+#include <kdebug.h>
+#include <kunittest/runner.h>
+#include <kunittest/module.h>
+
+using namespace KUnitTest;
+
+KUNITTEST_MODULE( kunittest_mimelibmodule, "Mimelib Tests" );
+KUNITTEST_MODULE_REGISTER_TESTER( MimeLibTester );
+
+#include <mimelib/string.h>
+#include <mimelib/message.h>
+#include "util.h"
+#include <qfile.h>
+#include <assert.h>
+
+#if 0
+static QString makePrintable( const QCString& str )
+{
+ QString a = str;
+ a = a.replace( '\r', "\\r" );
+ a = a.replace( '\n', "\\n" );
+ return a;
+}
+#endif
+
+static QString makePrintable( const DwString& str )
+{
+ QString a = KMail::Util::CString( str ); // ## we assume latin1
+ a = a.replace( '\r', "\\r" );
+ a = a.replace( '\n', "\\n" );
+ return a;
+}
+
+QCString MimeLibTester::readFile(const QString& fileName)
+{
+ QFile file( fileName );
+ // #!@#$& kunittest... VERIFY() does nothing in setUp. Using assert instead.
+ bool ok = file.open( IO_ReadOnly );
+ if ( !ok ) {
+ kdError() << fileName << " not found!" << endl;
+ abort();
+ }
+ QByteArray data = file.readAll();
+ assert( data.size() > 1 );
+ QCString result;
+ KMail::Util::setFromByteArray( result, data );
+ return result;
+}
+
+void MimeLibTester::setUp()
+{
+ // This multipart-mixed mail has a part that starts without headers;
+ // the newline after the (empty) headers must be preserved.
+ mMultipartMixedMail = readFile( KDESRCDIR "/multipartmixed.mbox" );
+ // This is the full signed mail which was simplified to above.
+ // Kept around in case we want to do anything else with a signed mail later :)
+ mSignedMail = readFile( KDESRCDIR "/signedmail.mbox" );
+}
+
+void MimeLibTester::tearDown()
+{
+}
+
+// Simply test creating a DwMessage and then calling AsString on it.
+// Then the same with Parse+Assemble
+bool MimeLibTester::test_dwMessage_AsString( const DwString& text )
+{
+ VERIFY( text.size() > 0 );
+
+ // First without Parse + Assemble
+ {
+ DwMessage* msg = new DwMessage( text, 0 );
+ COMPARE( makePrintable( msg->AsString() ), makePrintable( text ) );
+ delete msg;
+ }
+ // Then with Parse + Assemble
+ {
+ DwMessage* msg = new DwMessage( text, 0 );
+ msg->Parse();
+ msg->Assemble();
+ COMPARE( makePrintable( msg->AsString() ), makePrintable( text ) );
+ if ( msg->AsString() != text )
+ return false;
+ delete msg;
+ }
+ return true;
+}
+
+void MimeLibTester::test_dwMessage_AsString()
+{
+ if ( !test_dwMessage_AsString( mMultipartMixedMail.data() ) )
+ return;
+ if ( !test_dwMessage_AsString( mSignedMail.data() ) )
+ return;
+}
diff --git a/kmail/tests/mimelibtests.h b/kmail/tests/mimelibtests.h
new file mode 100644
index 00000000..3c0dad71
--- /dev/null
+++ b/kmail/tests/mimelibtests.h
@@ -0,0 +1,44 @@
+/* This file is part of the KDE project
+ Copyright (C) 2007 David Faure <faure@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef MIMELIBTEST_H
+#define MIMELIBTEST_H
+
+#include <kunittest/tester.h>
+class DwString;
+
+class MimeLibTester : public KUnitTest::SlotTester
+{
+ Q_OBJECT
+
+public slots:
+ void setUp();
+ void tearDown();
+ void test_dwMessage_AsString();
+
+private:
+ QCString readFile(const QString& fileName);
+ bool test_dwMessage_AsString( const DwString& );
+
+ QCString mMultipartMixedMail;
+ QCString mSignedMail;
+};
+
+#endif /* MIMELIBTEST_H */
+
diff --git a/kmail/tests/multipartmixed.mbox b/kmail/tests/multipartmixed.mbox
new file mode 100644
index 00000000..a1c593ee
--- /dev/null
+++ b/kmail/tests/multipartmixed.mbox
@@ -0,0 +1,10 @@
+From wilde@intevation.de Thu Mar 15 15:16:02 2007
+Subject: aegpyten/issue734
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="==-=-=";
+
+--==-=-=
+
+One empty line above this line.
+--==-=-=--
+
diff --git a/kmail/tests/signedmail.mbox b/kmail/tests/signedmail.mbox
new file mode 100644
index 00000000..1df5f3ee
--- /dev/null
+++ b/kmail/tests/signedmail.mbox
@@ -0,0 +1,111 @@
+From wilde@intevation.de Thu Mar 15 15:16:02 2007
+Return-Path: <nobody@example.org>
+Received: from localhost (localhost [127.0.0.1])
+ by smykowski.kdab.net (Cyrus v2.2.12) with LMTPA;
+ Tue, 03 Apr 2007 10:38:19 +0200
+X-Sieve: CMU Sieve 2.2
+Received: from localhost (localhost [127.0.0.1])
+ by smykowski.kdab.net (Postfix) with ESMTP id 0DEF8E6C750
+ for <dfaure@kdab.net>; Tue, 3 Apr 2007 10:38:19 +0200 (CEST)
+Received: from smykowski.kdab.net ([127.0.0.1])
+ by localhost (smykowski.kdab.net [127.0.0.1]) (amavisd-new, port 10024)
+ with ESMTP id 16175-09 for <dfaure@kdab.net>;
+ Tue, 3 Apr 2007 10:38:17 +0200 (CEST)
+Received: from localhost (localhost [127.0.0.1])
+ by smykowski.kdab.net (Postfix) with ESMTP id F1D4DE6C751
+ for <dfaure@kdab.net>; Tue, 3 Apr 2007 10:38:16 +0200 (CEST)
+Received: from smtp4-g19.free.fr (smtp4-g19.free.fr [212.27.42.30])
+ by smykowski.kdab.net (Postfix) with ESMTP id D7976E6C750
+ for <dfaure@kdab.net>; Tue, 3 Apr 2007 10:38:10 +0200 (CEST)
+Received: from [192.168.0.1] (lns-bzn-51f-81-56-143-51.adsl.proxad.net [81.56.143.51])
+ by smtp4-g19.free.fr (Postfix) with ESMTP id C961269606
+ for <dfaure@kdab.net>; Tue, 3 Apr 2007 10:41:56 +0200 (CEST)
+Subject: Test for aegpyten/issue734-1, OpenPGP signed
+From: Sascha Wilde <wilde@intevation.de>
+Date: Thu, 15 Mar 2007 15:16:02 +0100
+MIME-Version: 1.0
+Content-Type: multipart/signed;
+ boundary="==-=-=";
+ micalg=pgp-sha1;
+ protocol="application/pgp-signature"
+Message-Id: <20070403084156.C961269606@smtp4-g19.free.fr>
+To: undisclosed-recipients:;
+X-Virus-Scanned: by amavisd-new at kdab.net
+X-Spam-Status: Yes, score=7.598 tagged_above=3 required=6.3
+ tests=[BAYES_50=0.001, DATE_IN_PAST_96_XX=2.02, DNS_FROM_AHBL_RHSBL=0.231,
+ DNS_FROM_RFC_ABUSE=0.2, DNS_FROM_RFC_DSN=2.597, DNS_FROM_RFC_POST=1.708,
+ UNDISC_RECIPS=0.841]
+X-Spam-Score: 7.598
+X-Spam-Level: *******
+X-Spam-Flag: YES
+X-Kolab-Scheduling-Message: FALSE
+X-UID: 68768
+X-Length: 3235
+Status: RO
+X-Status: RPT
+X-KMail-EncryptionState:
+X-KMail-SignatureState:
+X-KMail-MDN-Sent:
+
+--==-=-=
+Content-Type: multipart/mixed; boundary="=-=-="
+
+--=-=-=
+
+
+Hallo Bernhard
+
+hier also die mail....
+jetzt kommt gleich der Anhang...
+3 ...
+2 ...
+1 ...
+BOOOM
+
+
+--=-=-=
+Content-Type: text/plain; charset=iso-8859-1
+Content-Disposition: attachment; filename=x.txt
+Content-Transfer-Encoding: quoted-printable
+Content-Description: Anhang
+
+
+Ein Anhang mit Text.
+Weil das wohl auch in der urspr=FCnglichen Mail so war
+
+und nicht ein wenig mehr Text, damit es nicht so langweilig ist...
+
+
+
+--=-=-=
+Content-Type: text/plain; charset=iso-8859-1
+Content-Transfer-Encoding: quoted-printable
+
+
+und hier geht der Text weiter...
+sch=F6n mit Umlaut, wie besprochen!
+
+Und das ganz nat=FCrlich signiert...
+
+und? hilft's?
+sascha
+=2D-=20
+Sascha Wilde OpenPGP key: 4BB86568
+Intevation GmbH, Osnabr=FCck http://www.intevation.de/~wilde/
+Amtsgericht Osnabr=FCck, HR B 18998 http://www.intevation.de/
+Gesch=E4ftsf=FChrer: Frank Koormann, Bernhard Reiter, Dr. Jan-Oliver Wagner
+
+--=-=-=--
+
+--==-=-=
+Content-Type: application/pgp-signature
+
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.6 (GNU/Linux)
+
+iD8DBQFF+VUtuyGFFEu4ZWgRApmZAKCGHUi440uPEggdKZoE1WHWO0ah6QCfaqLv
+HYelJfvqQQJkoW99SKRUVec=
+=AmlK
+-----END PGP SIGNATURE-----
+--==-=-=--
+
diff --git a/kmail/tests/storagelayermodule.cpp b/kmail/tests/storagelayermodule.cpp
new file mode 100644
index 00000000..91362471
--- /dev/null
+++ b/kmail/tests/storagelayermodule.cpp
@@ -0,0 +1,16 @@
+/**
+ * Copyright (C) 2005 Till Adam <adam@kde.org>
+ *
+ * This software is under the "if you run these tests and they break you
+ * have to fix it" license.
+ */
+
+#include <kunittest/runner.h>
+#include <kunittest/module.h>
+
+#include "messagedicttests.h"
+
+using namespace KUnitTest;
+
+KUNITTEST_MODULE( kunittest_storagelayermodule, "KMail Storage Layer Tests" );
+KUNITTEST_MODULE_REGISTER_TESTER( MessageDictTester );
diff --git a/kmail/tests/utiltests.cpp b/kmail/tests/utiltests.cpp
new file mode 100644
index 00000000..6f09416f
--- /dev/null
+++ b/kmail/tests/utiltests.cpp
@@ -0,0 +1,207 @@
+/**
+ * Copyright (C) 2007 David Faure <faure@kde.org>
+ * This file is subject to the GPL version 2.
+ */
+
+#include <kdebug.h>
+#include <kunittest/runner.h>
+#include <kunittest/module.h>
+#include "utiltests.h"
+
+using namespace KUnitTest;
+
+KUNITTEST_MODULE( kunittest_utilmodule, "KMail::Util Tests" );
+KUNITTEST_MODULE_REGISTER_TESTER( UtilTester );
+
+#include "util.h"
+#include <mimelib/string.h>
+
+
+void UtilTester::setUp()
+{
+ kdDebug() << "setUp" << endl;
+}
+
+void UtilTester::tearDown()
+{
+ kdDebug() << "tearDown" << endl;
+}
+
+static QString makePrintable( const QCString& str )
+{
+ QString a = str;
+ a = a.replace( '\r', "\\r" );
+ a = a.replace( '\n', "\\n" );
+ return a;
+}
+static QString makePrintable( const QByteArray& arr )
+{
+ QCString str;
+ KMail::Util::setFromByteArray( str, arr );
+ return makePrintable( str );
+}
+
+void UtilTester::test_lf2crlf()
+{
+ QCString src = "\nfoo\r\n\nbar\rblah\n\r\r\n\n\r";
+ QCString conv = KMail::Util::lf2crlf( src );
+ COMPARE( makePrintable( conv ), makePrintable("\r\nfoo\r\n\r\nbar\rblah\r\n\r\r\n\r\n\r") );
+ COMPARE( KMail::Util::lf2crlf( QCString("") ), QCString("") );
+
+ // QByteArray version
+ QByteArray arr; KMail::Util::setFromQCString( arr, src );
+ COMPARE( arr[arr.size()-1], '\r' );
+ QByteArray arrConv = KMail::Util::lf2crlf( arr );
+ COMPARE( arrConv[arrConv.size()-1], '\r' );
+ COMPARE( makePrintable( arrConv ), makePrintable("\r\nfoo\r\n\r\nbar\rblah\r\n\r\r\n\r\n\r") );
+ QByteArray empty;
+ arrConv = KMail::Util::lf2crlf( empty );
+ COMPARE( makePrintable( arrConv ), QString("") );
+}
+
+void UtilTester::test_crlf2lf()
+{
+ QCString src = "\r\n\r\nfoo\r\n\r\nbar\rblah\r\n\r\r\n\r\n\r";
+ int len = src.length();
+ COMPARE( src[len], '\0' );
+ int newLen = KMail::Util::crlf2lf( src.data(), len );
+ VERIFY( newLen <= len );
+ QCString cstr( src.data(), newLen + 1 );
+ COMPARE( makePrintable( cstr ), makePrintable("\n\nfoo\n\nbar\rblah\n\r\n\n\r") );
+}
+
+void UtilTester::test_escapeFrom()
+{
+ // TODO should take a DwString, then fix kmfoldermbox.cpp:1021 msgText = escapeFrom( aMsg->asString() );
+
+}
+
+void UtilTester::test_append()
+{
+ QCString test;
+ QCString str = "foo";
+ COMPARE( (int)str.size(), 4 ); // trailing nul included
+ QByteArray s1 = KMail::Util::byteArrayFromQCStringNoDetach( str );
+ COMPARE( (int)s1.size(), 3 );
+ COMPARE( (int)str.size(), 3 ); // trailing nul got removed
+ COMPARE( s1.data(), str.data() ); // yes, no detach
+ COMPARE( s1[2], 'o' );
+
+ QCString bar( "bar" );
+ QByteArray s2 = KMail::Util::byteArrayFromQCStringNoDetach( bar );
+ COMPARE( (int)s2.size(), 3 );
+
+ KMail::Util::append( s1, s2 );
+ COMPARE( (int)s1.size(), 6 );
+ KMail::Util::setFromByteArray( test, s1 );
+ COMPARE( test, QCString( "foobar" ) );
+
+ KMail::Util::append( s1, 0 ); // no-op
+ COMPARE( (int)s1.size(), 6 );
+ KMail::Util::setFromByteArray( test, s1 );
+ COMPARE( test, QCString( "foobar" ) );
+
+ KMail::Util::append( s1, "blah" );
+ COMPARE( (int)s1.size(), 10 );
+ KMail::Util::setFromByteArray( test, s1 );
+ COMPARE( test, QCString( "foobarblah" ) );
+
+ KMail::Util::append( s1, QCString( " str" ) );
+ COMPARE( (int)s1.size(), 14 );
+ KMail::Util::setFromByteArray( test, s1 );
+ COMPARE( test, QCString( "foobarblah str" ) );
+
+ QByteArray empty;
+ KMail::Util::append( empty, "a" );
+ COMPARE( (int)empty.size(), 1 );
+ COMPARE( empty[0], 'a' );
+}
+
+void UtilTester::test_insert()
+{
+ QCString test;
+ QCString str = "foo";
+ COMPARE( (int)str.size(), 4 ); // trailing nul included
+ QByteArray s1;
+ KMail::Util::setFromQCString( s1, str );
+
+ KMail::Util::insert( s1, 1, "bar" );
+ COMPARE( (int)s1.size(), 6 );
+ COMPARE( makePrintable(s1), QString( "fbaroo" ) );
+
+ KMail::Util::insert( s1, 6, "END" );
+ COMPARE( (int)s1.size(), 9 );
+ COMPARE( makePrintable(s1), QString( "fbarooEND" ) );
+
+ KMail::Util::insert( s1, 0, "BEGIN" );
+ COMPARE( (int)s1.size(), 14 );
+ COMPARE( makePrintable(s1), QString( "BEGINfbarooEND" ) );
+}
+
+void UtilTester::test_DwStringConversions( const QCString& cstr )
+{
+ // QCString->DwString->QCString
+ COMPARE( (int)cstr.size(), 8 );
+ DwString dwstr = KMail::Util::dwString( cstr );
+ COMPARE( (int)dwstr.size(), 7 );
+ COMPARE( dwstr[6], 'r' );
+ QCString cstr2 = KMail::Util::CString( dwstr );
+ COMPARE( (int)cstr2.size(), 8 );
+ COMPARE( cstr2, cstr );
+ COMPARE( cstr2[6], 'r' );
+
+ // And also QCString->QByteArray
+ QByteArray arr;
+ KMail::Util::setFromQCString( arr, cstr );
+ COMPARE( (int)arr.size(), 7 );
+ COMPARE( arr[6], 'r' );
+
+ KMail::Util::setFromQCString( arr, QCString() );
+ COMPARE( (int)arr.size(), 0 );
+
+ // DwString->QByteArray
+ QByteArray ba = KMail::Util::ByteArray( dwstr );
+ COMPARE( (int)ba.size(), 7 );
+ COMPARE( ba[6], 'r' );
+
+ ba = KMail::Util::ByteArray( DwString() );
+ COMPARE( (int)ba.size(), 0 );
+}
+
+void UtilTester::test_DwStringConversions()
+{
+ QCString cstr = "foo&bar";
+ test_DwStringConversions( cstr );
+ // now embed a nul. Note that cstr="foo\0bar" wouldn't work.
+ cstr[3] = '\0';
+ test_DwStringConversions( cstr );
+
+ cstr = QCString();
+ DwString dwstr = KMail::Util::dwString( cstr );
+ COMPARE( (int)dwstr.size(), 0 );
+ VERIFY( dwstr.empty() );
+
+ dwstr = KMail::Util::dwString( QByteArray() );
+ COMPARE( (int)dwstr.size(), 0 );
+ VERIFY( dwstr.empty() );
+}
+
+void UtilTester::test_QByteArrayQCString()
+{
+ QCString str = "foobar";
+ COMPARE( (int)str.size(), 7 ); // trailing nul included
+ QByteArray s1 = KMail::Util::byteArrayFromQCStringNoDetach( str );
+ COMPARE( (int)str.size(), 6 ); // trailing nul got removed
+ COMPARE( s1.data(), str.data() ); // yes, no detach
+ COMPARE( s1[5], 'r' );
+ COMPARE( str[5], 'r' );
+
+ KMail::Util::restoreQCString( str );
+ COMPARE( (int)str.size(), 7 ); // trailing nul included
+ COMPARE( str[5], 'r' );
+ COMPARE( str[6], '\0' );
+
+}
+
+#include "utiltests.moc"
+
diff --git a/kmail/tests/utiltests.h b/kmail/tests/utiltests.h
new file mode 100644
index 00000000..58de4090
--- /dev/null
+++ b/kmail/tests/utiltests.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2007 David Faure <faure@kde.org>
+ *
+ * This file is subject to the GPL version 2.
+ */
+
+#ifndef UTILTESTS_H
+#define UTILTESTS_H
+
+#include <kunittest/tester.h>
+
+class UtilTester : public KUnitTest::SlotTester
+{
+ Q_OBJECT
+
+public slots:
+ void setUp();
+ void tearDown();
+ void test_lf2crlf();
+ void test_crlf2lf();
+ void test_escapeFrom();
+ void test_append();
+ void test_insert();
+ void test_DwStringConversions();
+ void test_QByteArrayQCString();
+private:
+ void test_DwStringConversions( const QCString& cstr );
+};
+
+#endif
diff --git a/kmail/textsource.cpp b/kmail/textsource.cpp
new file mode 100644
index 00000000..fdb82f7e
--- /dev/null
+++ b/kmail/textsource.cpp
@@ -0,0 +1,65 @@
+/*
+ This file is part of KMail.
+
+ Copyright (c) 2005 George Staikos <staikos@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#include "textsource.h"
+#include "kmmsgbase.h"
+#include "kmmsgdict.h"
+#include "kmfolder.h"
+#include "kmfolderindex.h"
+
+KMTextSource::KMTextSource() : MailTextSource() {
+}
+
+
+KMTextSource::~KMTextSource() {
+}
+
+
+QCString KMTextSource::text(Q_UINT32 serialNumber) const {
+ QCString rc;
+ KMFolder *folder = 0;
+ int idx;
+ KMMsgDict::instance()->getLocation(serialNumber, &folder, &idx);
+ if (folder) {
+ KMMsgBase *msgBase = folder->getMsgBase(idx);
+ if (msgBase) {
+ KMMessage *msg = msgBase->storage()->readTemporaryMsg(idx);
+ if (msg) {
+ rc = msg->asString();
+ delete msg;
+ }
+ }
+ }
+
+ return rc;
+}
+
diff --git a/kmail/textsource.h b/kmail/textsource.h
new file mode 100644
index 00000000..499af4b7
--- /dev/null
+++ b/kmail/textsource.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of KMail.
+
+ Copyright (c) 2005 George Staikos <staikos@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef _TEXTSOURCE_H
+#define _TEXTSOURCE_H
+
+#include <maillistdrag.h>
+
+class KMTextSource : public KPIM::MailTextSource {
+public:
+ KMTextSource();
+ ~KMTextSource();
+
+ QCString text(Q_UINT32 serialNumber) const;
+};
+
+#endif
diff --git a/kmail/tips b/kmail/tips
new file mode 100644
index 00000000..1defde79
--- /dev/null
+++ b/kmail/tips
@@ -0,0 +1,78 @@
+<tip category="KMail|Keyboard">
+<html>
+<p>...that you can go to the next and previous message by using the
+right and left arrow keys respectively?</p>
+</html>
+</tip>
+
+<tip category="KMail|Filters">
+<html>
+<p>...that you can rapidly create filters on sender, recipient,
+subject and mailing lists with <em>Tools-&gt;Create&nbsp;Filter</em>?</p>
+</html>
+</tip>
+
+<tip category="KMail|Filters">
+<html>
+<p>...that you can get rid of the &quot;[mailing list name]&quot;
+added to the subject of some mailing lists by using the <em>rewrite
+header</em> filter action? Just use
+<pre>rewrite header &quot;Subject&quot;
+ replace &quot;\s*\[mailing list name\]\s*&quot;
+ with &quot;&quot;</pre>
+</p>
+</html>
+</tip>
+
+<tip category="KMail|General">
+<html>
+<p>...that you can associate mailing lists with folders in the
+<em>Folder-&gt;Mailing List Management</em> dialog? You can then use
+<em>Message-&gt;New&nbsp;Message&nbsp;to&nbsp;Mailing&nbsp;List...</em>
+to open the composer with the mailing list address preset.
+Alternatively, you can click with the middle mouse button on the folder.</p>
+</html>
+</tip>
+
+<tip category="KMail|General">
+<html>
+<p>...that you can assign custom icons to each folder individually?
+See <em>Folder-&gt;Properties</em></p>
+</html>
+</tip>
+
+<tip category="KMail|Security">
+<html>
+<p>...that KMail can show a color bar indicating the type of message
+(Plain text/HTML/OpenPGP) currently displayed?</p>
+<p>This thwarts attempts to fake successful signature verification by
+sending HTML mails mimicking KMail's signature status frames.</p>
+</html>
+</tip>
+
+<tip category="KMail|Filters">
+<html>
+<p>...that you can filter on any header by simply entering its name
+in the first edit field of a search rule?</p>
+</html>
+</tip>
+
+<tip category="KMail|Filters">
+<html>
+<p>...that you can filter out HTML only messages with the rule
+<pre>&quot;Content-type&quot; contains &quot;text/html&quot;?</pre>
+</p>
+</html>
+</tip>
+
+<tip category="KMail|General">
+<html>
+<p>...that when replying, only the selected part of the message is quoted?</p>
+<p>If nothing is selected, the full message is quoted.</p>
+<p>This even works with text of attachments when
+<em>View-&gt;Attachments-&gt;Inline</em> is selected.</p>
+<p>This feature is available with all reply commands except
+<em>Message-&gt;Reply Without Quote</em>.</p>
+<p align="right"><em>contributed by David F. Newman</em></p>
+</html>
+</tip>
diff --git a/kmail/transportmanager.cpp b/kmail/transportmanager.cpp
new file mode 100644
index 00000000..7b9b26ee
--- /dev/null
+++ b/kmail/transportmanager.cpp
@@ -0,0 +1,68 @@
+/*
+ transportmanager.cpp
+
+ KMail, the KDE mail client.
+ Copyright (c) 2002 Ingo Kloecker <kloecker@kde.org>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License,
+ version 2.0, as published by the Free Software Foundation.
+ 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, US
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "transportmanager.h"
+
+#include "kmtransport.h"
+#include "kmkernel.h"
+#include <kapplication.h>
+#include <kconfig.h>
+
+namespace KMail {
+
+ QStringList TransportManager::transportNames()
+ {
+ KConfigGroup general( KMKernel::config(), "General");
+
+ int numTransports = general.readNumEntry("transports", 0);
+
+ QStringList transportNames;
+ for ( int i = 1 ; i <= numTransports ; i++ ) {
+ KMTransportInfo ti;
+ ti.readConfig(i);
+ transportNames << ti.name;
+ }
+
+ return transportNames;
+ }
+
+ // more or less copied from AccountManager
+ uint TransportManager::createId()
+ {
+ QValueList<unsigned int> usedIds;
+
+ KConfigGroup general( KMKernel::config(), "General");
+ int numTransports = general.readNumEntry( "transports", 0 );
+
+ for ( int i = 1 ; i <= numTransports ; i++ ) {
+ KMTransportInfo ti;
+ ti.readConfig( i );
+ usedIds << ti.id();
+ }
+
+ usedIds << 0; // 0 is default for unknown
+ int newId;
+ do
+ {
+ newId = kapp->random();
+ } while ( usedIds.find(newId) != usedIds.end() );
+
+ return newId;
+ }
+
+} // namespace KMail
diff --git a/kmail/transportmanager.h b/kmail/transportmanager.h
new file mode 100644
index 00000000..22ab02f8
--- /dev/null
+++ b/kmail/transportmanager.h
@@ -0,0 +1,42 @@
+/*
+ transportmanager.h
+
+ KMail, the KDE mail client.
+ Copyright (c) 2002 Ingo Kloecker <kloecker@kde.org>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License,
+ version 2.0, as published by the Free Software Foundation.
+ 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, US
+*/
+
+
+#ifndef _KMAIL_TRANSPORTMANAGER_H_
+#define _KMAIL_TRANSPORTMANAGER_H_
+
+class QStringList;
+
+namespace KMail {
+
+ /**
+ * @short Currently only used to provide a function for reading the transport list.
+ * @author Ingo Kloecker <kloecker@kde.org>
+ **/
+ class TransportManager {
+
+ public:
+ TransportManager() {};
+ virtual ~TransportManager() {};
+
+ /** Returns the list for transport names */
+ static QStringList transportNames();
+
+ /** Create a unique id for a transport info item */
+ static unsigned int createId();
+ };
+
+} // namespace KMail
+
+#endif // _KMAIL_TRANSPORTMANAGER_H_
diff --git a/kmail/undostack.cpp b/kmail/undostack.cpp
new file mode 100644
index 00000000..f1729f3a
--- /dev/null
+++ b/kmail/undostack.cpp
@@ -0,0 +1,157 @@
+/*
+ This file is part of KMail
+
+ Copyright (C) 1999 Waldo Bastian (bastian@kde.org)
+ Copyright (c) 2003 Zack Rusin <zack@kde.org>
+
+ This library 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.
+
+ This software 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 library; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "undostack.h"
+
+#include "kmmainwin.h"
+#include "kmfolder.h"
+#include "kmmsgdict.h"
+
+#include <kmessagebox.h>
+#include <klocale.h>
+#include <kdebug.h>
+
+namespace KMail {
+
+UndoStack::UndoStack(int size)
+ : QObject(0, "undostack"), mSize(size), mLastId(0),
+ mCachedInfo(0)
+{
+ mStack.setAutoDelete(true);
+}
+
+void UndoStack::clear()
+{
+ mStack.clear();
+}
+
+int UndoStack::newUndoAction( KMFolder *srcFolder, KMFolder *destFolder )
+{
+ UndoInfo *info = new UndoInfo;
+ info->id = ++mLastId;
+ info->srcFolder = srcFolder;
+ info->destFolder = destFolder;
+ if ((int) mStack.count() == mSize)
+ mStack.removeLast();
+ mStack.prepend( info );
+ emit undoStackChanged();
+ return info->id;
+}
+
+void UndoStack::addMsgToAction( int undoId, ulong serNum )
+{
+ if ( !mCachedInfo || mCachedInfo->id != undoId ) {
+ QPtrListIterator<UndoInfo> itr( mStack );
+ while ( itr.current() ) {
+ if ( itr.current()->id == undoId ) {
+ mCachedInfo = itr.current();
+ break;
+ }
+ ++itr;
+ }
+ }
+
+ Q_ASSERT( mCachedInfo );
+ mCachedInfo->serNums.append( serNum );
+}
+
+void UndoStack::undo()
+{
+ KMMessage *msg;
+ ulong serNum;
+ int idx = -1;
+ KMFolder *curFolder;
+ if ( mStack.count() > 0 )
+ {
+ UndoInfo *info = mStack.take(0);
+ emit undoStackChanged();
+ QValueList<ulong>::iterator itr;
+ KMFolderOpener openDestFolder(info->destFolder, "undodest");
+ for( itr = info->serNums.begin(); itr != info->serNums.end(); ++itr ) {
+ serNum = *itr;
+ KMMsgDict::instance()->getLocation(serNum, &curFolder, &idx);
+ if ( idx == -1 || curFolder != info->destFolder ) {
+ kdDebug(5006)<<"Serious undo error!"<<endl;
+ delete info;
+ return;
+ }
+ msg = curFolder->getMsg( idx );
+ info->srcFolder->moveMsg( msg );
+ if ( info->srcFolder->count() > 1 )
+ info->srcFolder->unGetMsg( info->srcFolder->count() - 1 );
+ }
+ delete info;
+ }
+ else
+ {
+ // Sorry.. stack is empty..
+ KMessageBox::sorry( kmkernel->mainWin(), i18n("There is nothing to undo."));
+ }
+}
+
+void
+UndoStack::pushSingleAction(ulong serNum, KMFolder *folder, KMFolder *destFolder)
+{
+ int id = newUndoAction( folder, destFolder );
+ addMsgToAction( id, serNum );
+}
+
+void
+UndoStack::msgDestroyed( KMMsgBase* /*msg*/)
+{
+ /*
+ for(UndoInfo *info = mStack.first(); info; )
+ {
+ if (info->msgIdMD5 == msg->msgIdMD5())
+ {
+ mStack.removeRef( info );
+ info = mStack.current();
+ }
+ else
+ info = mStack.next();
+ }
+ */
+}
+
+void
+UndoStack::folderDestroyed( KMFolder *folder)
+{
+ for( UndoInfo *info = mStack.first(); info; )
+ {
+ if ( (info->srcFolder == folder) ||
+ (info->destFolder == folder) )
+ {
+ mStack.removeRef( info );
+ info = mStack.current();
+ }
+ else
+ info = mStack.next();
+ }
+ emit undoStackChanged();
+}
+
+}
+
+#include "undostack.moc"
diff --git a/kmail/undostack.h b/kmail/undostack.h
new file mode 100644
index 00000000..78c993fb
--- /dev/null
+++ b/kmail/undostack.h
@@ -0,0 +1,72 @@
+/*
+ This file is part of KMail
+
+ Copyright (C) 1999 Waldo Bastian (bastian@kde.org)
+ Copyright (c) 2003 Zack Rusin <zack@kde.org>
+
+ This library 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.
+
+ This software 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 library; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef UNDOSTACK_H
+#define UNDOSTACK_H
+
+#include <qptrlist.h>
+#include <qstring.h>
+#include <qobject.h>
+
+class KMFolder;
+class KMMsgBase;
+
+namespace KMail {
+
+/** A class for storing Undo information. */
+class UndoInfo
+{
+public:
+ int id;
+ QValueList<ulong> serNums;
+ KMFolder *srcFolder;
+ KMFolder *destFolder;
+};
+
+class UndoStack : public QObject
+{
+ Q_OBJECT
+
+public:
+ UndoStack(int size);
+
+ void clear();
+ int size() const { return mStack.count(); }
+ int newUndoAction( KMFolder* srcFolder, KMFolder* destFolder );
+ void addMsgToAction( int undoId, ulong serNum );
+ void undo();
+
+ void pushSingleAction(ulong serNum, KMFolder *folder, KMFolder* destFolder);
+ void msgDestroyed( KMMsgBase *msg);
+ void folderDestroyed( KMFolder *folder);
+protected:
+ QPtrList<UndoInfo> mStack;
+ int mSize;
+ int mLastId;
+ UndoInfo *mCachedInfo;
+
+signals:
+ void undoStackChanged();
+};
+
+}
+
+#endif
diff --git a/kmail/upgrade-signature.pl b/kmail/upgrade-signature.pl
new file mode 100755
index 00000000..23c3d63b
--- /dev/null
+++ b/kmail/upgrade-signature.pl
@@ -0,0 +1,63 @@
+#!/usr/bin/perl
+
+my (%data);
+my $section = "";
+
+sub process {
+ # delete obsolete keys:
+ print "# DELETE [$section]UseSignatureFile\n";
+
+ # now determine the type of signature:
+ if ( $data{'usefile'} =~ /false/i ) {
+ # type = inline
+ if ( $data{'inline'} ne "" ) {
+ print "[$section]\nSignature Type=inline\n";
+ } else {
+ print "[$section]\nSignature Type=disabled\n";
+ print "# DELETE [$section]Inline Signature\n";
+ }
+ print "# DELETE [$section]Signature File\n";
+ } else {
+ # type = file or command
+ if ( $data{'file'} =~ /\|$/ ) {
+ # a trailing pipe means:
+ # type = command
+ chop $data{'file'};
+ print "[$section]\nSignature Type=command\n";
+ print "[$section]\nSignature Command=", $data{'file'}, "\n";
+ print "# DELETE [$section]Signature File\n";
+ print "# DELETE [$section]Inline Signature\n";
+ } elsif ( $data{'file'} eq "" ) {
+ # empty filename means:
+ # type = disabled
+ print "[$section]\nSignature Type=disabled\n";
+ print "# DELETE [$section]Inline Signature\n";
+ print "# DELETE [$section]Signature File\n";
+ } else {
+ # type = file
+ print "[$section]\nSignature Type=file\n";
+ print "# DELETE [$section]Inline Signature\n";
+ }
+ }
+}
+
+#loop over all lines to find Identity sections:
+while (<>) {
+ if ( /\[(Identity[^]]*)\]/ ) {
+ # new group means that we have to process the old group:
+ if ( $section ne "" ) { process(); }
+ $section = $1;
+ %data = ();
+ next;
+ }
+ chomp;
+ # We need to prevent this script from begin run twice
+ # since it would set all signatures to 'disabled' then.
+ # Presence of the Signature Type key is the best indicator.
+ /^Signature Type/ and exit;
+ /^Inline Signature=(.*)$/ and $data{'inline'} = $1;
+ /^Signature File=(.*)$/ and $data{'file'} = $1;
+ /^UseSignatureFile=(.*)$/ and $data{'usefile'} = $1;
+}
+#and don't forget to preocess the last group ;-)
+if ( $section ne "" ) { process(); }
diff --git a/kmail/upgrade-transport.pl b/kmail/upgrade-transport.pl
new file mode 100755
index 00000000..806a7a35
--- /dev/null
+++ b/kmail/upgrade-transport.pl
@@ -0,0 +1,36 @@
+#!/usr/bin/perl
+my (%data);
+
+#read in all the data and split it up into hashes.
+while (<>) {
+ $data{$1} = $2 if /^([^=]*)=(.*)$/;
+}
+
+# Delete obsolete entries from the [sending mail] section
+print "# DELETE Mailer\n";
+print "# DELETE Method\n";
+print "# DELETE Precommand\n";
+print "# DELETE Smtp Host\n";
+print "# DELETE Smtp Password\n";
+print "# DELETE Smtp Port\n";
+print "# DELETE Smtp Username\n";
+
+# Write entries to the [Transport 1] section
+print "precommand=$data{'Precommand'}\n" if (defined $data{'Precommand'});
+print "port=$data{'Smtp Port'}\n" if (defined $data{'Smtp Port'});
+
+if (defined $data{'Method'}) {
+ if ($data{'Method'} eq "smtp") {
+ print "type=smtp\n";
+ print "host=$data{'Smtp Host'}\n";
+ print "name=$data{'Smtp Host'}\n";
+ }
+ else {
+ print "type=sendmail\n";
+ print "host=$data{'Mailer'}\n";
+ print "name=Sendmail\n";
+ }
+ print "\n[General]\n";
+ print "transports=1\n";
+}
+
diff --git a/kmail/urlhandlermanager.cpp b/kmail/urlhandlermanager.cpp
new file mode 100644
index 00000000..cf5b92d6
--- /dev/null
+++ b/kmail/urlhandlermanager.cpp
@@ -0,0 +1,597 @@
+/* -*- c++ -*-
+ urlhandlermanager.cpp
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2002-2003 Klar�vdalens Datakonsult AB
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "urlhandlermanager.h"
+
+#include "interfaces/urlhandler.h"
+#include "interfaces/bodyparturlhandler.h"
+#include "partNode.h"
+#include "partnodebodypart.h"
+#include "kmreaderwin.h"
+#include "kmkernel.h"
+#include "callback.h"
+
+#include <kimproxy.h>
+#include "stl_util.h"
+#include <kurl.h>
+
+#include <algorithm>
+using std::for_each;
+using std::remove;
+using std::find;
+
+KMail::URLHandlerManager * KMail::URLHandlerManager::self = 0;
+
+namespace {
+ class KMailProtocolURLHandler : public KMail::URLHandler {
+ public:
+ KMailProtocolURLHandler() : KMail::URLHandler() {}
+ ~KMailProtocolURLHandler() {}
+
+ bool handleClick( const KURL &, KMReaderWin * ) const;
+ bool handleContextMenuRequest( const KURL & url, const QPoint &, KMReaderWin * ) const {
+ return url.protocol() == "kmail";
+ }
+ QString statusBarMessage( const KURL &, KMReaderWin * ) const;
+ };
+
+ class ExpandCollapseQuoteURLManager : public KMail::URLHandler {
+ public:
+ ExpandCollapseQuoteURLManager() : KMail::URLHandler() {}
+ ~ExpandCollapseQuoteURLManager() {}
+
+ bool handleClick( const KURL &, KMReaderWin * ) const;
+ bool handleContextMenuRequest( const KURL &, const QPoint &, KMReaderWin * ) const {
+ return false;
+ }
+ QString statusBarMessage( const KURL &, KMReaderWin * ) const;
+
+ };
+
+ class SMimeURLHandler : public KMail::URLHandler {
+ public:
+ SMimeURLHandler() : KMail::URLHandler() {}
+ ~SMimeURLHandler() {}
+
+ bool handleClick( const KURL &, KMReaderWin * ) const;
+ bool handleContextMenuRequest( const KURL &, const QPoint &, KMReaderWin * ) const {
+ return false;
+ }
+ QString statusBarMessage( const KURL &, KMReaderWin * ) const;
+ };
+
+ class MailToURLHandler : public KMail::URLHandler {
+ public:
+ MailToURLHandler() : KMail::URLHandler() {}
+ ~MailToURLHandler() {}
+
+ bool handleClick( const KURL &, KMReaderWin * ) const { return false; }
+ bool handleContextMenuRequest( const KURL &, const QPoint &, KMReaderWin * ) const {
+ return false;
+ }
+ QString statusBarMessage( const KURL &, KMReaderWin * ) const;
+ };
+
+ class HtmlAnchorHandler : public KMail::URLHandler {
+ public:
+ HtmlAnchorHandler() : KMail::URLHandler() {}
+ ~HtmlAnchorHandler() {}
+
+ bool handleClick( const KURL &, KMReaderWin * ) const;
+ bool handleContextMenuRequest( const KURL &, const QPoint &, KMReaderWin * ) const {
+ return false;
+ }
+ QString statusBarMessage( const KURL &, KMReaderWin * ) const { return QString::null; }
+ };
+
+ class AttachmentURLHandler : public KMail::URLHandler {
+ public:
+ AttachmentURLHandler() : KMail::URLHandler() {}
+ ~AttachmentURLHandler() {}
+
+ bool handleClick( const KURL &, KMReaderWin * ) const;
+ bool handleContextMenuRequest( const KURL &, const QPoint &, KMReaderWin * ) const;
+ QString statusBarMessage( const KURL &, KMReaderWin * ) const;
+ };
+
+ class ShowAuditLogURLHandler : public KMail::URLHandler {
+ public:
+ ShowAuditLogURLHandler() : KMail::URLHandler() {}
+ ~ShowAuditLogURLHandler() {}
+
+ bool handleClick( const KURL &, KMReaderWin * ) const;
+ bool handleContextMenuRequest( const KURL &, const QPoint &, KMReaderWin * ) const;
+ QString statusBarMessage( const KURL &, KMReaderWin * ) const;
+ };
+
+ class FallBackURLHandler : public KMail::URLHandler {
+ public:
+ FallBackURLHandler() : KMail::URLHandler() {}
+ ~FallBackURLHandler() {}
+
+ bool handleClick( const KURL &, KMReaderWin * ) const;
+ bool handleContextMenuRequest( const KURL &, const QPoint &, KMReaderWin * ) const;
+ QString statusBarMessage( const KURL & url, KMReaderWin * ) const {
+ return url.prettyURL();
+ }
+ };
+
+} // anon namespace
+
+
+//
+//
+// BodyPartURLHandlerManager
+//
+//
+
+class KMail::URLHandlerManager::BodyPartURLHandlerManager : public KMail::URLHandler {
+public:
+ BodyPartURLHandlerManager() : KMail::URLHandler() {}
+ ~BodyPartURLHandlerManager();
+
+ bool handleClick( const KURL &, KMReaderWin * ) const;
+ bool handleContextMenuRequest( const KURL &, const QPoint &, KMReaderWin * ) const;
+ QString statusBarMessage( const KURL &, KMReaderWin * ) const;
+
+ void registerHandler( const Interface::BodyPartURLHandler * handler );
+ void unregisterHandler( const Interface::BodyPartURLHandler * handler );
+
+private:
+ typedef QValueVector<const Interface::BodyPartURLHandler*> BodyPartHandlerList;
+ BodyPartHandlerList mHandlers;
+};
+
+KMail::URLHandlerManager::BodyPartURLHandlerManager::~BodyPartURLHandlerManager() {
+ for_each( mHandlers.begin(), mHandlers.end(),
+ DeleteAndSetToZero<Interface::BodyPartURLHandler>() );
+}
+
+void KMail::URLHandlerManager::BodyPartURLHandlerManager::registerHandler( const Interface::BodyPartURLHandler * handler ) {
+ if ( !handler )
+ return;
+ unregisterHandler( handler ); // don't produce duplicates
+ mHandlers.push_back( handler );
+}
+
+void KMail::URLHandlerManager::BodyPartURLHandlerManager::unregisterHandler( const Interface::BodyPartURLHandler * handler ) {
+ // don't delete them, only remove them from the list!
+ mHandlers.erase( remove( mHandlers.begin(), mHandlers.end(), handler ), mHandlers.end() );
+}
+
+static partNode * partNodeFromXKMailUrl( const KURL & url, KMReaderWin * w, QString * path ) {
+ assert( path );
+
+ if ( !w || url.protocol() != "x-kmail" )
+ return 0;
+ const QString urlPath = url.path();
+
+ // urlPath format is: /bodypart/<random number>/<part id>/<path>
+
+ kdDebug( 5006 ) << "BodyPartURLHandler: urlPath == \"" << urlPath << "\"" << endl;
+ if ( !urlPath.startsWith( "/bodypart/" ) )
+ return 0;
+
+ const QStringList urlParts = QStringList::split( '/', urlPath.mid( 10 ), true );
+ if ( urlParts.size() != 3 )
+ return 0;
+ bool ok = false;
+ const int part_id = urlParts[1].toInt( &ok );
+ if ( !ok )
+ return 0;
+ *path = KURL::decode_string( urlParts[2], 106 );
+ return w->partNodeForId( part_id );
+}
+
+bool KMail::URLHandlerManager::BodyPartURLHandlerManager::handleClick( const KURL & url, KMReaderWin * w ) const {
+ QString path;
+ partNode * node = partNodeFromXKMailUrl( url, w, &path );
+ if ( !node )
+ return false;
+ KMMessage *msg = w->message();
+ if ( !msg ) return false;
+ Callback callback( msg, w );
+ KMail::PartNodeBodyPart part( *node, w->overrideCodec() );
+ for ( BodyPartHandlerList::const_iterator it = mHandlers.begin() ; it != mHandlers.end() ; ++it )
+ if ( (*it)->handleClick( &part, path, callback ) )
+ return true;
+ return false;
+}
+
+bool KMail::URLHandlerManager::BodyPartURLHandlerManager::handleContextMenuRequest( const KURL & url, const QPoint & p, KMReaderWin * w ) const {
+ QString path;
+ partNode * node = partNodeFromXKMailUrl( url, w, &path );
+ if ( !node )
+ return false;
+
+ KMail::PartNodeBodyPart part( *node, w->overrideCodec() );
+ for ( BodyPartHandlerList::const_iterator it = mHandlers.begin() ; it != mHandlers.end() ; ++it )
+ if ( (*it)->handleContextMenuRequest( &part, path, p ) )
+ return true;
+ return false;
+}
+
+QString KMail::URLHandlerManager::BodyPartURLHandlerManager::statusBarMessage( const KURL & url, KMReaderWin * w ) const {
+ QString path;
+ partNode * node = partNodeFromXKMailUrl( url, w, &path );
+ if ( !node )
+ return QString::null;
+
+ KMail::PartNodeBodyPart part( *node, w->overrideCodec() );
+ for ( BodyPartHandlerList::const_iterator it = mHandlers.begin() ; it != mHandlers.end() ; ++it ) {
+ const QString msg = (*it)->statusBarMessage( &part, path );
+ if ( !msg.isEmpty() )
+ return msg;
+ }
+ return QString::null;
+}
+
+//
+//
+// URLHandlerManager
+//
+//
+
+KMail::URLHandlerManager::URLHandlerManager() {
+ registerHandler( new KMailProtocolURLHandler() );
+ registerHandler( new ExpandCollapseQuoteURLManager() );
+ registerHandler( new SMimeURLHandler() );
+ registerHandler( new MailToURLHandler() );
+ registerHandler( new HtmlAnchorHandler() );
+ registerHandler( new AttachmentURLHandler() );
+ registerHandler( mBodyPartURLHandlerManager = new BodyPartURLHandlerManager() );
+ registerHandler( new ShowAuditLogURLHandler() );
+ registerHandler( new FallBackURLHandler() );
+}
+
+KMail::URLHandlerManager::~URLHandlerManager() {
+ for_each( mHandlers.begin(), mHandlers.end(),
+ DeleteAndSetToZero<URLHandler>() );
+}
+
+void KMail::URLHandlerManager::registerHandler( const URLHandler * handler ) {
+ if ( !handler )
+ return;
+ unregisterHandler( handler ); // don't produce duplicates
+ mHandlers.push_back( handler );
+}
+
+void KMail::URLHandlerManager::unregisterHandler( const URLHandler * handler ) {
+ // don't delete them, only remove them from the list!
+ mHandlers.erase( remove( mHandlers.begin(), mHandlers.end(), handler ), mHandlers.end() );
+}
+
+void KMail::URLHandlerManager::registerHandler( const Interface::BodyPartURLHandler * handler ) {
+ if ( mBodyPartURLHandlerManager )
+ mBodyPartURLHandlerManager->registerHandler( handler );
+}
+
+void KMail::URLHandlerManager::unregisterHandler( const Interface::BodyPartURLHandler * handler ) {
+ if ( mBodyPartURLHandlerManager )
+ mBodyPartURLHandlerManager->unregisterHandler( handler );
+}
+
+bool KMail::URLHandlerManager::handleClick( const KURL & url, KMReaderWin * w ) const {
+ for ( HandlerList::const_iterator it = mHandlers.begin() ; it != mHandlers.end() ; ++it )
+ if ( (*it)->handleClick( url, w ) )
+ return true;
+ return false;
+}
+
+bool KMail::URLHandlerManager::handleContextMenuRequest( const KURL & url, const QPoint & p, KMReaderWin * w ) const {
+ for ( HandlerList::const_iterator it = mHandlers.begin() ; it != mHandlers.end() ; ++it )
+ if ( (*it)->handleContextMenuRequest( url, p, w ) )
+ return true;
+ return false;
+}
+
+QString KMail::URLHandlerManager::statusBarMessage( const KURL & url, KMReaderWin * w ) const {
+ for ( HandlerList::const_iterator it = mHandlers.begin() ; it != mHandlers.end() ; ++it ) {
+ const QString msg = (*it)->statusBarMessage( url, w );
+ if ( !msg.isEmpty() )
+ return msg;
+ }
+ return QString::null;
+}
+
+
+//
+//
+// URLHandler
+//
+//
+
+// these includes are temporary and should not be needed for the code
+// above this line, so they appear only here:
+#include "kmmessage.h"
+#include "kmreaderwin.h"
+#include "partNode.h"
+#include "kmmsgpart.h"
+
+#include <ui/messagebox.h>
+
+#include <klocale.h>
+#include <kprocess.h>
+#include <kmessagebox.h>
+#include <khtml_part.h>
+
+#include <qstring.h>
+
+namespace {
+ bool KMailProtocolURLHandler::handleClick( const KURL & url, KMReaderWin * w ) const {
+ if ( url.protocol() == "kmail" ) {
+ if ( !w )
+ return false;
+
+ if ( url.path() == "showHTML" ) {
+ w->setHtmlOverride( !w->htmlOverride() );
+ w->update( true );
+ return true;
+ }
+
+ if ( url.path() == "loadExternal" ) {
+ w->setHtmlLoadExtOverride( !w->htmlLoadExtOverride() );
+ w->update( true );
+ return true;
+ }
+
+ if ( url.path() == "goOnline" ) {
+ kmkernel->resumeNetworkJobs();
+ return true;
+ }
+
+ if ( url.path() == "decryptMessage" ) {
+ w->setDecryptMessageOverwrite( true );
+ w->update( true );
+ return true;
+ }
+
+ if ( url.path() == "showSignatureDetails" ) {
+ w->setShowSignatureDetails( true );
+ w->update( true );
+ return true;
+ }
+
+ if ( url.path() == "hideSignatureDetails" ) {
+ w->setShowSignatureDetails( false );
+ w->update( true );
+ return true;
+ }
+
+ if ( url.path() == "showAttachmentQuicklist" ) {
+ w->saveRelativePosition();
+ w->setShowAttachmentQuicklist( true );
+ w->update( true );
+ return true;
+ }
+
+ if ( url.path() == "hideAttachmentQuicklist" ) {
+ w->saveRelativePosition();
+ w->setShowAttachmentQuicklist( false );
+ w->update( true );
+ return true;
+ }
+
+// if ( url.path() == "startIMApp" )
+// {
+// kmkernel->imProxy()->startPreferredApp();
+// return true;
+// }
+// //FIXME: handle startIMApp urls in their own handler, or rename this one
+ }
+ return false;
+ }
+
+ QString KMailProtocolURLHandler::statusBarMessage( const KURL & url, KMReaderWin * ) const {
+ if ( url.protocol() == "kmail" )
+ {
+ if ( url.path() == "showHTML" )
+ return i18n("Turn on HTML rendering for this message.");
+ if ( url.path() == "loadExternal" )
+ return i18n("Load external references from the Internet for this message.");
+ if ( url.path() == "goOnline" )
+ return i18n("Work online.");
+ if ( url.path() == "decryptMessage" )
+ return i18n("Decrypt message.");
+ if ( url.path() == "showSignatureDetails" )
+ return i18n("Show signature details.");
+ if ( url.path() == "hideSignatureDetails" )
+ return i18n("Hide signature details.");
+ }
+ return QString::null ;
+ }
+}
+
+namespace {
+
+ bool ExpandCollapseQuoteURLManager::handleClick(
+ const KURL & url, KMReaderWin * w ) const
+ {
+ // kmail:levelquote/?num -> the level quote to collapse.
+ // kmail:levelquote/?-num -> expand all levels quote.
+ if ( url.protocol() == "kmail" && url.path()=="levelquote" )
+ {
+ QString levelStr= url.query().mid( 1,url.query().length() );
+ bool isNumber;
+ int levelQuote= levelStr.toInt(&isNumber);
+ if ( isNumber )
+ w->slotLevelQuote( levelQuote );
+ return true;
+ }
+ return false;
+ }
+ QString ExpandCollapseQuoteURLManager::statusBarMessage(
+ const KURL & url, KMReaderWin * ) const
+ {
+ if ( url.protocol() == "kmail" && url.path() == "levelquote" )
+ {
+ QString query= url.query();
+ if ( query.length()>=2 )
+ if ( query[ 1 ] =='-' )
+ return i18n("Expand all quoted text.");
+ else
+ return i18n("Collapse quoted text.");
+ }
+ return QString::null ;
+ }
+
+}
+
+// defined in kmreaderwin.cpp...
+extern bool foundSMIMEData( const QString aUrl, QString & displayName,
+ QString & libName, QString & keyId );
+
+namespace {
+ bool SMimeURLHandler::handleClick( const KURL & url, KMReaderWin * w ) const {
+ if ( !url.hasRef() )
+ return false;
+ QString displayName, libName, keyId;
+ if ( !foundSMIMEData( url.path() + '#' + url.ref(), displayName, libName, keyId ) )
+ return false;
+ KProcess cmp;
+ cmp << "kleopatra" << "-query" << keyId;
+ if ( !cmp.start( KProcess::DontCare ) )
+ KMessageBox::error( w, i18n("Could not start certificate manager. "
+ "Please check your installation."),
+ i18n("KMail Error") );
+ return true;
+ }
+
+ QString SMimeURLHandler::statusBarMessage( const KURL & url, KMReaderWin * ) const {
+ QString displayName, libName, keyId;
+ if ( !foundSMIMEData( url.path() + '#' + url.ref(), displayName, libName, keyId ) )
+ return QString::null;
+ return i18n("Show certificate 0x%1").arg( keyId );
+ }
+}
+
+namespace {
+ bool HtmlAnchorHandler::handleClick( const KURL & url, KMReaderWin * w ) const {
+ if ( url.hasHost() || url.path() != "/" || !url.hasRef() )
+ return false;
+ if ( w && !w->htmlPart()->gotoAnchor( url.ref() ) )
+ static_cast<QScrollView*>( w->htmlPart()->widget() )->ensureVisible( 0, 0 );
+ return true;
+ }
+}
+
+namespace {
+ QString MailToURLHandler::statusBarMessage( const KURL & url, KMReaderWin * ) const {
+ if ( url.protocol() != "mailto" )
+ return QString::null;
+ return KMMessage::decodeMailtoUrl( url.url() );
+ }
+}
+
+namespace {
+ bool AttachmentURLHandler::handleClick( const KURL & url, KMReaderWin * w ) const {
+ if ( !w || !w->message() )
+ return false;
+ const int id = KMReaderWin::msgPartFromUrl( url );
+ if ( id <= 0 )
+ return false;
+ w->openAttachment( id, url.path() );
+ return true;
+ }
+
+ bool AttachmentURLHandler::handleContextMenuRequest( const KURL & url, const QPoint & p, KMReaderWin * w ) const {
+ if ( !w || !w->message() )
+ return false;
+ const int id = KMReaderWin::msgPartFromUrl( url );
+ if ( id <= 0 )
+ return false;
+ w->showAttachmentPopup( id, url.path(), p );
+ return true;
+ }
+
+ QString AttachmentURLHandler::statusBarMessage( const KURL & url, KMReaderWin * w ) const {
+ if ( !w || !w->message() )
+ return QString::null;
+ const partNode * node = w->partNodeFromUrl( url );
+ if ( !node )
+ return QString::null;
+ const KMMessagePart & msgPart = node->msgPart();
+ QString name = msgPart.fileName();
+ if ( name.isEmpty() )
+ name = msgPart.name();
+ if ( !name.isEmpty() )
+ return i18n( "Attachment: %1" ).arg( name );
+ return i18n( "Attachment #%1 (unnamed)" ).arg( KMReaderWin::msgPartFromUrl( url ) );
+ }
+}
+
+namespace {
+ static QString extractAuditLog( const KURL & url ) {
+ if ( url.protocol() != "kmail" || url.path() != "showAuditLog" )
+ return QString();
+ assert( !url.queryItem( "log" ).isEmpty() );
+ return url.queryItem( "log" );
+ }
+
+ bool ShowAuditLogURLHandler::handleClick( const KURL & url, KMReaderWin * w ) const {
+ const QString auditLog = extractAuditLog( url );
+ if ( auditLog.isEmpty() )
+ return false;
+ Kleo::MessageBox::auditLog( w, auditLog );
+ return true;
+ }
+
+ bool ShowAuditLogURLHandler::handleContextMenuRequest( const KURL & url, const QPoint &, KMReaderWin * w ) const {
+ // disable RMB for my own links:
+ return !extractAuditLog( url ).isEmpty();
+ }
+
+ QString ShowAuditLogURLHandler::statusBarMessage( const KURL & url, KMReaderWin * ) const {
+ if ( extractAuditLog( url ).isEmpty() )
+ return QString();
+ else
+ return i18n("Show GnuPG Audit Log for this operation");
+ }
+}
+
+namespace {
+ bool FallBackURLHandler::handleClick( const KURL & url, KMReaderWin * w ) const {
+ if ( w )
+ w->emitUrlClicked( url, Qt::LeftButton );
+ return true;
+ }
+
+ bool FallBackURLHandler::handleContextMenuRequest( const KURL & url, const QPoint & p, KMReaderWin * w ) const {
+ if ( w )
+ w->emitPopupMenu( url, p );
+ return true;
+ }
+}
diff --git a/kmail/urlhandlermanager.h b/kmail/urlhandlermanager.h
new file mode 100644
index 00000000..dce3646e
--- /dev/null
+++ b/kmail/urlhandlermanager.h
@@ -0,0 +1,88 @@
+/* -*- c++ -*-
+ urlhandlermanager.h
+
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2003 Marc Mutz <mutz@kde.org>
+
+ 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 Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifndef __KMAIL_URLHANDLERMANAGER_H__
+#define __KMAIL_URLHANDLERMANAGER_H__
+
+#include <qvaluevector.h>
+
+class KURL;
+
+class QString;
+class QPoint;
+class KMReaderWin;
+
+namespace KMail {
+
+ class URLHandler;
+
+ namespace Interface {
+ class BodyPartURLHandler;
+ }
+
+ /**
+ * @short Singleton to manage the list of URLHandlers
+ * @author Marc Mutz <mutz@kde.org>
+ */
+ class URLHandlerManager {
+ static URLHandlerManager * self;
+
+ URLHandlerManager();
+ public:
+ ~URLHandlerManager();
+
+ static URLHandlerManager * instance() {
+ if ( !self )
+ self = new URLHandlerManager();
+ return self;
+ }
+
+ void registerHandler( const URLHandler * handler );
+ void unregisterHandler( const URLHandler * handler );
+
+ void registerHandler( const Interface::BodyPartURLHandler * handler );
+ void unregisterHandler( const Interface::BodyPartURLHandler * handler );
+
+ bool handleClick( const KURL & url, KMReaderWin * w=0 ) const;
+ bool handleContextMenuRequest( const KURL & url, const QPoint & p, KMReaderWin * w=0 ) const;
+ QString statusBarMessage( const KURL & url, KMReaderWin * w=0 ) const;
+
+ private:
+ typedef QValueVector<const URLHandler*> HandlerList;
+ HandlerList mHandlers;
+ class BodyPartURLHandlerManager;
+ BodyPartURLHandlerManager * mBodyPartURLHandlerManager;
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_URLHANDLERMANAGER_H__
+
diff --git a/kmail/util.cpp b/kmail/util.cpp
new file mode 100644
index 00000000..1c45d445
--- /dev/null
+++ b/kmail/util.cpp
@@ -0,0 +1,194 @@
+/*******************************************************************************
+**
+** Filename : util
+** Created on : 03 April, 2005
+** Copyright : (c) 2005 Till Adam
+** Email : <adam@kde.org>
+**
+*******************************************************************************/
+
+/*******************************************************************************
+**
+** 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.
+**
+** It is distributed in the hope that it will be useful, but
+** WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+** General Public License for more details.
+**
+** You should have received a copy of the GNU General Public License
+** along with this program; if not, write to the Free Software
+** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+**
+** In addition, as a special exception, the copyright holders give
+** permission to link the code of this program with any edition of
+** the Qt library by Trolltech AS, Norway (or with modified versions
+** of Qt that use the same license as Qt), and distribute linked
+** combinations including the two. You must obey the GNU General
+** Public License in all respects for all of the code used other than
+** Qt. If you modify this file, you may extend this exception to
+** your version of the file, but you are not obligated to do so. If
+** you do not wish to do so, delete this exception statement from
+** your version.
+**
+*******************************************************************************/
+#include "util.h"
+
+#include <stdlib.h>
+#include <qcstring.h>
+#include <mimelib/string.h>
+
+size_t KMail::Util::crlf2lf( char* str, const size_t strLen )
+{
+ if ( !str || strLen == 0 )
+ return 0;
+
+ const char* source = str;
+ const char* sourceEnd = source + strLen;
+
+ // search the first occurrence of "\r\n"
+ for ( ; source < sourceEnd - 1; ++source ) {
+ if ( *source == '\r' && *( source + 1 ) == '\n' )
+ break;
+ }
+
+ if ( source == sourceEnd - 1 ) {
+ // no "\r\n" found
+ return strLen;
+ }
+
+ // replace all occurrences of "\r\n" with "\n" (in place)
+ char* target = const_cast<char*>( source ); // target points to '\r'
+ ++source; // source points to '\n'
+ for ( ; source < sourceEnd; ++source ) {
+ if ( *source != '\r' || *( source + 1 ) != '\n' )
+ * target++ = *source;
+ }
+ *target = '\0'; // terminate result
+ return target - str;
+}
+
+QCString KMail::Util::lf2crlf( const QCString & src )
+{
+ QCString result( 1 + 2*src.size() ); // maximal possible length
+
+ QCString::ConstIterator s = src.begin();
+ QCString::Iterator d = result.begin();
+ // we use cPrev to make sure we insert '\r' only there where it is missing
+ char cPrev = '?';
+ while ( *s ) {
+ if ( ('\n' == *s) && ('\r' != cPrev) )
+ *d++ = '\r';
+ cPrev = *s;
+ *d++ = *s++;
+ }
+ result.truncate( d - result.begin() ); // adds trailing NUL
+ return result;
+}
+
+QByteArray KMail::Util::lf2crlf( const QByteArray & src )
+{
+ const char* s = src.data();
+ if ( !s )
+ return QByteArray();
+
+ QByteArray result( 2 * src.size() ); // maximal possible length
+ QByteArray::Iterator d = result.begin();
+ // we use cPrev to make sure we insert '\r' only there where it is missing
+ char cPrev = '?';
+ const char* end = src.end();
+ while ( s != end ) {
+ if ( ('\n' == *s) && ('\r' != cPrev) )
+ *d++ = '\r';
+ cPrev = *s;
+ *d++ = *s++;
+ }
+ result.truncate( d - result.begin() ); // does not add trailing NUL, as expected
+ return result;
+}
+
+QCString KMail::Util::CString( const DwString& str )
+{
+ const int strLen = str.size();
+ QCString cstr( strLen + 1 );
+ memcpy( cstr.data(), str.data(), strLen );
+ cstr[ strLen ] = 0;
+ return cstr;
+}
+
+QByteArray KMail::Util::ByteArray( const DwString& str )
+{
+ const int strLen = str.size();
+ QByteArray arr( strLen );
+ memcpy( arr.data(), str.data(), strLen );
+ return arr;
+}
+
+DwString KMail::Util::dwString( const QCString& str )
+{
+ if ( !str.data() ) // DwString doesn't like char*=0
+ return DwString();
+ return DwString( str.data(), str.size() - 1 );
+}
+
+DwString KMail::Util::dwString( const QByteArray& str )
+{
+ if ( !str.data() ) // DwString doesn't like char*=0
+ return DwString();
+ return DwString( str.data(), str.size() );
+}
+
+void KMail::Util::append( QByteArray& that, const QByteArray& str )
+{
+ that.detach();
+ uint len1 = that.size();
+ uint len2 = str.size();
+ if ( that.resize( len1 + len2, QByteArray::SpeedOptim ) )
+ memcpy( that.data() + len1, str.data(), len2 );
+}
+
+void KMail::Util::append( QByteArray& that, const char* str )
+{
+ if ( !str )
+ return; // nothing to append
+ that.detach();
+ uint len1 = that.size();
+ uint len2 = qstrlen(str);
+ if ( that.resize( len1 + len2, QByteArray::SpeedOptim ) )
+ memcpy( that.data() + len1, str, len2 );
+}
+
+void KMail::Util::append( QByteArray& that, const QCString& str )
+{
+ that.detach();
+ uint len1 = that.size();
+ uint len2 = str.size() - 1;
+ if ( that.resize( len1 + len2, QByteArray::SpeedOptim ) )
+ memcpy( that.data() + len1, str.data(), len2 );
+}
+
+// Code taken from QCString::insert, but trailing nul removed
+void KMail::Util::insert( QByteArray& that, uint index, const char* s )
+{
+ int len = qstrlen(s);
+ if ( len == 0 )
+ return;
+ uint olen = that.size();
+ int nlen = olen + len;
+ if ( index >= olen ) { // insert after end of string
+ that.detach();
+ if ( that.resize(nlen+index-olen, QByteArray::SpeedOptim ) ) {
+ memset( that.data()+olen, ' ', index-olen );
+ memcpy( that.data()+index, s, len );
+ }
+ } else {
+ that.detach();
+ if ( that.resize(nlen, QByteArray::SpeedOptim ) ) { // normal insert
+ memmove( that.data()+index+len, that.data()+index, olen-index );
+ memcpy( that.data()+index, s, len );
+ }
+ }
+}
diff --git a/kmail/util.h b/kmail/util.h
new file mode 100644
index 00000000..05eb9084
--- /dev/null
+++ b/kmail/util.h
@@ -0,0 +1,222 @@
+/*******************************************************************************
+**
+** Filename : util
+** Created on : 03 April, 2005
+** Copyright : (c) 2005 Till Adam
+** Email : <adam@kde.org>
+**
+*******************************************************************************/
+
+/*******************************************************************************
+**
+** 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.
+**
+** It is distributed in the hope that it will be useful, but
+** WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+** General Public License for more details.
+**
+** You should have received a copy of the GNU General Public License
+** along with this program; if not, write to the Free Software
+** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+**
+** In addition, as a special exception, the copyright holders give
+** permission to link the code of this program with any edition of
+** the Qt library by Trolltech AS, Norway (or with modified versions
+** of Qt that use the same license as Qt), and distribute linked
+** combinations including the two. You must obey the GNU General
+** Public License in all respects for all of the code used other than
+** Qt. If you modify this file, you may extend this exception to
+** your version of the file, but you are not obligated to do so. If
+** you do not wish to do so, delete this exception statement from
+** your version.
+**
+*******************************************************************************/
+#ifndef KMAILUTIL_H
+#define KMAILUTIL_H
+
+#include <stdlib.h>
+
+#include <qobject.h>
+#include <qcstring.h>
+
+#include <kio/netaccess.h>
+#include <kmessagebox.h>
+#include <klocale.h>
+
+class DwString;
+class KURL;
+class QWidget;
+
+namespace KMail
+{
+ /**
+ * The Util namespace contains a collection of helper functions use in
+ * various places.
+ */
+namespace Util {
+ /**
+ * Convert all sequences of "\r\n" (carriage return followed by a line
+ * feed) to a single "\n" (line feed). The conversion happens in place.
+ * Returns the length of the resulting string.
+ * @param str The string to convert.
+ * @param strLen The length of the string to convert.
+ * @return The new length of the converted string.
+ */
+ size_t crlf2lf( char* str, const size_t strLen );
+
+
+ /**
+ * Convert "\n" line endings to "\r\n".
+ * @param src The source string to convert.
+ * @return The result string.
+ */
+ QCString lf2crlf( const QCString & src );
+ /**
+ * Convert "\n" line endings to "\r\n".
+ * @param src The source string to convert. NOT null-terminated.
+ * @return The result string. NOT null-terminated.
+ */
+ QByteArray lf2crlf( const QByteArray & src );
+
+ /**
+ * Construct a QCString from a DwString
+ */
+ QCString CString( const DwString& str );
+
+ /**
+ * Construct a QByteArray from a DwString
+ */
+ QByteArray ByteArray( const DwString& str );
+
+ /**
+ * Construct a DwString from a QCString
+ */
+ DwString dwString( const QCString& str );
+
+ /**
+ * Construct a DwString from a QByteArray
+ */
+ DwString dwString( const QByteArray& str );
+
+ /**
+ * Fills a QByteArray from a QCString - removing the trailing null.
+ */
+ void setFromQCString( QByteArray& arr, const QCString& cstr );
+
+ inline void setFromQCString( QByteArray& arr, const QCString& cstr )
+ {
+ if ( cstr.size() )
+ arr.duplicate( cstr.data(), cstr.size()-1 );
+ else
+ arr.resize(0);
+ }
+
+ /**
+ * Creates a QByteArray from a QCString without detaching (duplicating the data).
+ * Fast, but be careful, the QCString gets modified by this; this is only good for
+ * the case where the QCString is going to be thrown away afterwards anyway.
+ */
+ QByteArray byteArrayFromQCStringNoDetach( QCString& cstr );
+ inline QByteArray byteArrayFromQCStringNoDetach( QCString& cstr )
+ {
+ QByteArray arr = cstr;
+ if ( arr.size() )
+ arr.resize( arr.size() - 1 );
+ return arr;
+ }
+
+ /**
+ * Restore the QCString after byteArrayFromQCStringNoDetach modified it
+ */
+ void restoreQCString( QCString& str );
+ inline void restoreQCString( QCString& str )
+ {
+ if ( str.data() )
+ str.resize( str.size() + 1 );
+ }
+
+ /**
+ * Fills a QCString from a QByteArray - adding the trailing null.
+ */
+ void setFromByteArray( QCString& cstr, const QByteArray& arr );
+
+ inline void setFromByteArray( QCString& result, const QByteArray& arr )
+ {
+ const int len = arr.size();
+ result.resize( len + 1 /* trailing NUL */ );
+ memcpy(result.data(), arr.data(), len);
+ result[len] = 0;
+ }
+
+ /**
+ * Append a bytearray to a bytearray. No trailing nuls anywhere.
+ */
+ void append( QByteArray& that, const QByteArray& str );
+
+ /**
+ * Append a char* to a bytearray. Trailing nul not copied.
+ */
+ void append( QByteArray& that, const char* str );
+
+ /**
+ * Append a QCString to a bytearray. Trailing nul not copied.
+ */
+ void append( QByteArray& that, const QCString& str );
+
+ void insert( QByteArray& that, uint index, const char* s );
+
+ /**
+ * A LaterDeleter is intended to be used with the RAII ( Resource
+ * Acquisition is Initialization ) paradigm. When an instance of it
+ * goes out of scope it deletes the associated object It can be
+ * disabled, in case the deletion needs to be avoided for some
+ * reason, since going out-of-scope cannot be avoided.
+ */
+ class LaterDeleter
+ {
+ public:
+ LaterDeleter( QObject *o)
+ :m_object( o ), m_disabled( false )
+ {
+ }
+ virtual ~LaterDeleter()
+ {
+ if ( !m_disabled ) {
+ m_object->deleteLater();
+ }
+ }
+ void setDisabled( bool v )
+ {
+ m_disabled = v;
+ }
+ protected:
+ QObject *m_object;
+ bool m_disabled;
+ };
+
+ // return true if we should proceed, false if we should abort
+ inline bool checkOverwrite( const KURL& url, QWidget* w )
+ {
+ if ( KIO::NetAccess::exists( url, false /*dest*/, w ) ) {
+ if ( KMessageBox::Cancel ==
+ KMessageBox::warningContinueCancel(
+ w,
+ i18n( "A file named \"%1\" already exists. "
+ "Are you sure you want to overwrite it?" ).arg( url.prettyURL() ),
+ i18n( "Overwrite File?" ),
+ i18n( "&Overwrite" ) ) )
+ return false;
+ }
+ return true;
+ }
+
+
+
+}
+}
+
+#endif
diff --git a/kmail/vacation.cpp b/kmail/vacation.cpp
new file mode 100644
index 00000000..af0c5e16
--- /dev/null
+++ b/kmail/vacation.cpp
@@ -0,0 +1,729 @@
+/* -*- c++ -*-
+ vacation.cpp
+
+ KMail, the KDE mail client.
+ Copyright (c) 2002 Marc Mutz <mutz@kde.org>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License,
+ version 2.0, as published by the Free Software Foundation.
+ 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, US
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "vacation.h"
+#include <limits.h>
+
+#include "vacationdialog.h"
+#include "sievejob.h"
+using KMail::SieveJob;
+#include "kmkernel.h"
+#include "kmmainwidget.h"
+#include "accountmanager.h"
+using KMail::AccountManager;
+#include "kmacctimap.h"
+#include "kmmessage.h"
+#include "globalsettings.h"
+#include <libkpimidentities/identitymanager.h>
+#include <libkpimidentities/identity.h>
+
+#include <kmime_header_parsing.h>
+using KMime::Types::AddrSpecList;
+
+#include <ksieve/parser.h>
+#include <ksieve/scriptbuilder.h>
+#include <ksieve/error.h>
+
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+
+#include <qdatetime.h>
+
+#include <cassert>
+#include <vector>
+#include <map>
+#include <set>
+
+namespace KSieveExt {
+
+ class MultiScriptBuilder : public KSieve::ScriptBuilder {
+ std::vector<KSieve::ScriptBuilder*> mBuilders;
+ public:
+ MultiScriptBuilder() : KSieve::ScriptBuilder() {}
+ MultiScriptBuilder( KSieve::ScriptBuilder * sb1 )
+ : KSieve::ScriptBuilder(), mBuilders( 1 )
+ {
+ mBuilders[0] = sb1;
+ assert( sb1 );
+ }
+ MultiScriptBuilder( KSieve::ScriptBuilder * sb1,
+ KSieve::ScriptBuilder * sb2 )
+ : KSieve::ScriptBuilder(), mBuilders( 2 )
+ {
+ mBuilders[0] = sb1;
+ mBuilders[1] = sb2;
+ assert( sb1 ); assert( sb2 );
+ }
+ MultiScriptBuilder( KSieve::ScriptBuilder * sb1,
+ KSieve::ScriptBuilder * sb2,
+ KSieve::ScriptBuilder * sb3 )
+ : KSieve::ScriptBuilder(), mBuilders( 3 )
+ {
+ mBuilders[0] = sb1;
+ mBuilders[1] = sb2;
+ mBuilders[2] = sb3;
+ assert( sb1 ); assert( sb2 ); assert( sb3 );
+ }
+ ~MultiScriptBuilder() {}
+ private:
+#ifdef FOREACH
+#undef FOREACH
+#endif
+#define FOREACH for ( std::vector<KSieve::ScriptBuilder*>::const_iterator it = mBuilders.begin(), end = mBuilders.end() ; it != end ; ++it ) (*it)->
+ void commandStart( const QString & identifier ) { FOREACH commandStart( identifier ); }
+ void commandEnd() { FOREACH commandEnd(); }
+ void testStart( const QString & identifier ) { FOREACH testStart( identifier ); }
+ void testEnd() { FOREACH testEnd(); }
+ void testListStart() { FOREACH testListStart(); }
+ void testListEnd() { FOREACH testListEnd(); }
+ void blockStart() { FOREACH blockStart(); }
+ void blockEnd() { FOREACH blockEnd(); }
+ void hashComment( const QString & comment ) { FOREACH hashComment( comment ); }
+ void bracketComment( const QString & comment ) { FOREACH bracketComment( comment ); }
+ void lineFeed() { FOREACH lineFeed(); }
+ void error( const KSieve::Error & e ) { FOREACH error( e ); }
+ void finished() { FOREACH finished(); }
+ void taggedArgument( const QString & tag ) { FOREACH taggedArgument( tag ); }
+ void stringArgument( const QString & string, bool multiline, const QString & fixme ) { FOREACH stringArgument( string, multiline, fixme ); }
+ void numberArgument( unsigned long number, char quantifier ) { FOREACH numberArgument( number, quantifier ); }
+ void stringListArgumentStart() { FOREACH stringListArgumentStart(); }
+ void stringListEntry( const QString & string, bool multiline, const QString & fixme) { FOREACH stringListEntry( string, multiline, fixme ); }
+ void stringListArgumentEnd() { FOREACH stringListArgumentEnd(); }
+#undef FOREACH
+ };
+
+}
+
+namespace {
+
+ class GenericInformationExtractor : public KSieve::ScriptBuilder {
+ public:
+ enum BuilderMethod {
+ Any,
+ TaggedArgument,
+ StringArgument,
+ NumberArgument,
+ CommandStart,
+ CommandEnd,
+ TestStart,
+ TestEnd,
+ TestListStart,
+ TestListEnd,
+ BlockStart,
+ BlockEnd,
+ StringListArgumentStart,
+ StringListEntry,
+ StringListArgumentEnd
+ };
+
+ struct StateNode {
+ // expectation:
+ int depth;
+ BuilderMethod method;
+ const char * string;
+ // actions:
+ int if_found;
+ int if_not_found;
+ const char * save_tag;
+ };
+
+ const std::vector<StateNode> mNodes;
+ std::map<QString,QString> mResults;
+ std::set<unsigned int> mRecursionGuard;
+ unsigned int mState;
+ int mNestingDepth;
+
+ public:
+ GenericInformationExtractor( const std::vector<StateNode> & nodes )
+ : KSieve::ScriptBuilder(), mNodes( nodes ), mState( 0 ), mNestingDepth( 0 ) {}
+
+ const std::map<QString,QString> & results() const { return mResults; }
+
+ private:
+ void process( BuilderMethod method, const QString & string=QString::null ) {
+ doProcess( method, string );
+ mRecursionGuard.clear();
+ }
+ void doProcess( BuilderMethod method, const QString & string ) {
+ mRecursionGuard.insert( mState );
+ bool found = true;
+ const StateNode & expected = mNodes[mState];
+ if ( expected.depth != -1 && mNestingDepth != expected.depth )
+ found = false;
+ if ( expected.method != Any && method != expected.method )
+ found = false;
+ if ( const char * str = expected.string )
+ if ( string.lower() != QString::fromUtf8( str ).lower() )
+ found = false;
+ kdDebug(5006) << ( found ? "found: " : "not found: " )
+ << mState << " -> "
+ << ( found ? expected.if_found : expected.if_not_found ) << endl;
+ mState = found ? expected.if_found : expected.if_not_found ;
+ assert( mState < mNodes.size() );
+ if ( found )
+ if ( const char * save_tag = expected.save_tag )
+ mResults[save_tag] = string;
+ if ( !found && !mRecursionGuard.count( mState ) ) {
+ doProcess( method, string );
+ }
+ }
+ void commandStart( const QString & identifier ) { kdDebug(5006) << k_funcinfo << endl; process( CommandStart, identifier ); }
+ void commandEnd() { kdDebug(5006) << k_funcinfo << endl; process( CommandEnd ); }
+ void testStart( const QString & identifier ) { kdDebug(5006) << k_funcinfo << endl; process( TestStart, identifier ); }
+ void testEnd() { kdDebug(5006) << k_funcinfo << endl; process( TestEnd ); }
+ void testListStart() { kdDebug(5006) << k_funcinfo << endl; process( TestListStart ); }
+ void testListEnd() { kdDebug(5006) << k_funcinfo << endl; process( TestListEnd ); }
+ void blockStart() { kdDebug(5006) << k_funcinfo << endl; process( BlockStart ); ++mNestingDepth; }
+ void blockEnd() { kdDebug(5006) << k_funcinfo << endl; --mNestingDepth; process( BlockEnd ); }
+ void hashComment( const QString & ) { kdDebug(5006) << k_funcinfo << endl; }
+ void bracketComment( const QString & ) { kdDebug(5006) << k_funcinfo << endl; }
+ void lineFeed() { kdDebug(5006) << k_funcinfo << endl; }
+ void error( const KSieve::Error & ) {
+ kdDebug(5006) << k_funcinfo << endl;
+ mState = 0;
+ }
+ void finished() { kdDebug(5006) << k_funcinfo << endl; }
+
+ void taggedArgument( const QString & tag ) { kdDebug(5006) << k_funcinfo << endl; process( TaggedArgument, tag ); }
+ void stringArgument( const QString & string, bool, const QString & ) { kdDebug(5006) << k_funcinfo << endl; process( StringArgument, string ); }
+ void numberArgument( unsigned long number, char ) { kdDebug(5006) << k_funcinfo << endl; process( NumberArgument, QString::number( number ) ); }
+ void stringListArgumentStart() { kdDebug(5006) << k_funcinfo << endl; process( StringListArgumentStart ); }
+ void stringListEntry( const QString & string, bool, const QString & ) { kdDebug(5006) << k_funcinfo << endl; process( StringListEntry, string ); }
+ void stringListArgumentEnd() { kdDebug(5006) << k_funcinfo << endl; process( StringListArgumentEnd ); }
+ };
+
+ typedef GenericInformationExtractor GIE;
+ static const GenericInformationExtractor::StateNode spamNodes[] = {
+ { 0, GIE::CommandStart, "if", 1, 0, 0 }, // 0
+ { 0, GIE::TestStart, "header", 2, 0, 0 }, // 1
+ { 0, GIE::TaggedArgument, "contains", 3, 0, 0 }, // 2
+
+ // accept both string and string-list:
+ { 0, GIE::StringArgument, "x-spam-flag", 9, 4, "x-spam-flag" }, // 3
+ { 0, GIE::StringListArgumentStart, 0, 5, 0, 0 }, // 4
+ { 0, GIE::StringListEntry, "x-spam-flag", 6, 7, "x-spam-flag" }, // 5
+ { 0, GIE::StringListEntry, 0, 6, 8, 0 }, // 6
+ { 0, GIE::StringListArgumentEnd, 0, 0, 5, 0 }, // 7
+ { 0, GIE::StringListArgumentEnd, 0, 9, 0, 0 }, // 8
+
+ // accept both string and string-list:
+ { 0, GIE::StringArgument, "yes", 15, 10, "spam-flag-yes" }, // 9
+ { 0, GIE::StringListArgumentStart, 0, 11, 0, 0 }, // 10
+ { 0, GIE::StringListEntry, "yes", 12, 13, "spam-flag-yes" }, // 11
+ { 0, GIE::StringListEntry, 0, 12, 14, 0 }, // 12
+ { 0, GIE::StringListArgumentEnd, 0, 0, 11, 0 }, // 13
+ { 0, GIE::StringListArgumentEnd, 0, 15, 0, 0 }, // 14
+
+ { 0, GIE::TestEnd, 0, 16, 0, 0 }, // 15
+
+ // block of command, find "stop", take nested if's into account:
+ { 0, GIE::BlockStart, 0, 17, 0, 0 }, // 16
+ { 1, GIE::CommandStart, "stop", 20, 19, "stop" }, // 17
+ { -1, GIE::Any, 0, 17, 0, 0 }, // 18
+ { 0, GIE::BlockEnd, 0, 0, 18, 0 }, // 19
+
+ { -1, GIE::Any, 0, 20, 20, 0 }, // 20 end state
+ };
+ static const unsigned int numSpamNodes = sizeof spamNodes / sizeof *spamNodes ;
+
+ class SpamDataExtractor : public GenericInformationExtractor {
+ public:
+ SpamDataExtractor()
+ : GenericInformationExtractor( std::vector<StateNode>( spamNodes, spamNodes + numSpamNodes ) )
+ {
+
+ }
+
+ bool found() const {
+ return mResults.count( "x-spam-flag" ) &&
+ mResults.count( "spam-flag-yes" ) &&
+ mResults.count( "stop" ) ;
+ }
+ };
+
+ // to understand this table, study the output of
+ // libksieve/tests/parsertest
+ // 'if not address :domain :contains ["from"] ["mydomain.org"] { keep; stop; }'
+ static const GenericInformationExtractor::StateNode domainNodes[] = {
+ { 0, GIE::CommandStart, "if", 1, 0, 0 }, // 0
+ { 0, GIE::TestStart, "not", 2, 0, 0, }, // 1
+ { 0, GIE::TestStart, "address", 3, 0, 0 }, // 2
+
+ // :domain and :contains in arbitrary order:
+ { 0, GIE::TaggedArgument, "domain", 4, 5, 0 }, // 3
+ { 0, GIE::TaggedArgument, "contains", 7, 0, 0 }, // 4
+ { 0, GIE::TaggedArgument, "contains", 6, 0, 0 }, // 5
+ { 0, GIE::TaggedArgument, "domain", 7, 0, 0 }, // 6
+
+ // accept both string and string-list:
+ { 0, GIE::StringArgument, "from", 13, 8, "from" }, // 7
+ { 0, GIE::StringListArgumentStart, 0, 9, 0, 0 }, // 8
+ { 0, GIE::StringListEntry, "from", 10, 11, "from" }, // 9
+ { 0, GIE::StringListEntry, 0, 10, 12, 0 }, // 10
+ { 0, GIE::StringListArgumentEnd, 0, 0, 9, 0 }, // 11
+ { 0, GIE::StringListArgumentEnd, 0, 13, 0, 0 }, // 12
+
+ // string: save, string-list: save last
+ { 0, GIE::StringArgument, 0, 17, 14, "domainName" }, // 13
+ { 0, GIE::StringListArgumentStart, 0, 15, 0, 0 }, // 14
+ { 0, GIE::StringListEntry, 0, 15, 16, "domainName" }, // 15
+ { 0, GIE::StringListArgumentEnd, 0, 17, 0, 0 }, // 16
+
+ { 0, GIE::TestEnd, 0, 18, 0, 0 }, // 17
+ { 0, GIE::TestEnd, 0, 19, 0, 0 }, // 18
+
+ // block of commands, find "stop", take nested if's into account:
+ { 0, GIE::BlockStart, 0, 20, 0, 0 }, // 19
+ { 1, GIE::CommandStart, "stop", 23, 22, "stop" }, // 20
+ { -1, GIE::Any, 0, 20, 0, 0 }, // 21
+ { 0, GIE::BlockEnd, 0, 0, 21, 0 }, // 22
+
+ { -1, GIE::Any, 0, 23, 23, 0 } // 23 end state
+ };
+ static const unsigned int numDomainNodes = sizeof domainNodes / sizeof *domainNodes ;
+
+ class DomainRestrictionDataExtractor : public GenericInformationExtractor {
+ public:
+ DomainRestrictionDataExtractor()
+ : GenericInformationExtractor( std::vector<StateNode>( domainNodes, domainNodes+numDomainNodes ) )
+ {
+
+ }
+
+ QString domainName() /*not const, since map::op[] isn't const*/ {
+ return mResults.count( "stop" ) && mResults.count( "from" )
+ ? mResults["domainName"] : QString::null ;
+ }
+ };
+
+ class VacationDataExtractor : public KSieve::ScriptBuilder {
+ enum Context {
+ None = 0,
+ // command itself:
+ VacationCommand,
+ // tagged args:
+ Days, Addresses
+ };
+ public:
+ VacationDataExtractor()
+ : KSieve::ScriptBuilder(),
+ mContext( None ), mNotificationInterval( 0 )
+ {
+ kdDebug(5006) << "VacationDataExtractor instantiated" << endl;
+ }
+ virtual ~VacationDataExtractor() {}
+
+ int notificationInterval() const { return mNotificationInterval; }
+ const QString & messageText() const { return mMessageText; }
+ const QStringList & aliases() const { return mAliases; }
+
+ private:
+ void commandStart( const QString & identifier ) {
+ kdDebug( 5006 ) << "VacationDataExtractor::commandStart( \"" << identifier << "\" )" << endl;
+ if ( identifier != "vacation" )
+ return;
+ reset();
+ mContext = VacationCommand;
+ }
+
+ void commandEnd() {
+ kdDebug( 5006 ) << "VacationDataExtractor::commandEnd()" << endl;
+ mContext = None;
+ }
+
+ void testStart( const QString & ) {}
+ void testEnd() {}
+ void testListStart() {}
+ void testListEnd() {}
+ void blockStart() {}
+ void blockEnd() {}
+ void hashComment( const QString & ) {}
+ void bracketComment( const QString & ) {}
+ void lineFeed() {}
+ void error( const KSieve::Error & e ) {
+ kdDebug( 5006 ) << "VacationDataExtractor::error() ### "
+ << e.asString() << " @ " << e.line() << "," << e.column()
+ << endl;
+ }
+ void finished() {}
+
+ void taggedArgument( const QString & tag ) {
+ kdDebug( 5006 ) << "VacationDataExtractor::taggedArgument( \"" << tag << "\" )" << endl;
+ if ( mContext != VacationCommand )
+ return;
+ if ( tag == "days" )
+ mContext = Days;
+ else if ( tag == "addresses" )
+ mContext = Addresses;
+ }
+
+ void stringArgument( const QString & string, bool, const QString & ) {
+ kdDebug( 5006 ) << "VacationDataExtractor::stringArgument( \"" << string << "\" )" << endl;
+ if ( mContext == Addresses ) {
+ mAliases.push_back( string );
+ mContext = VacationCommand;
+ } else if ( mContext == VacationCommand ) {
+ mMessageText = string;
+ mContext = VacationCommand;
+ }
+ }
+
+ void numberArgument( unsigned long number, char ) {
+ kdDebug( 5006 ) << "VacationDataExtractor::numberArgument( \"" << number << "\" )" << endl;
+ if ( mContext != Days )
+ return;
+ if ( number > INT_MAX )
+ mNotificationInterval = INT_MAX;
+ else
+ mNotificationInterval = number;
+ mContext = VacationCommand;
+ }
+
+ void stringListArgumentStart() {}
+ void stringListEntry( const QString & string, bool, const QString & ) {
+ kdDebug( 5006 ) << "VacationDataExtractor::stringListEntry( \"" << string << "\" )" << endl;
+ if ( mContext != Addresses )
+ return;
+ mAliases.push_back( string );
+ }
+ void stringListArgumentEnd() {
+ kdDebug( 5006 ) << "VacationDataExtractor::stringListArgumentEnd()" << endl;
+ if ( mContext != Addresses )
+ return;
+ mContext = VacationCommand;
+ }
+
+ private:
+ Context mContext;
+ int mNotificationInterval;
+ QString mMessageText;
+ QStringList mAliases;
+
+ void reset() {
+ kdDebug(5006) << "VacationDataExtractor::reset()" << endl;
+ mContext = None;
+ mNotificationInterval = 0;
+ mAliases.clear();
+ mMessageText = QString::null;
+ }
+ };
+
+}
+
+namespace KMail {
+
+ Vacation::Vacation( QObject * parent, bool checkOnly, const char * name )
+ : QObject( parent, name ), mSieveJob( 0 ), mDialog( 0 ), mWasActive( false ), mCheckOnly( checkOnly )
+ {
+ mUrl = findURL();
+ kdDebug(5006) << "Vacation: found url \"" << mUrl.prettyURL() << "\"" << endl;
+ if ( mUrl.isEmpty() ) // nothing to do...
+ return;
+ mSieveJob = SieveJob::get( mUrl, !checkOnly );
+ connect( mSieveJob, SIGNAL(gotScript(KMail::SieveJob*,bool,const QString&,bool)),
+ SLOT(slotGetResult(KMail::SieveJob*,bool,const QString&,bool)) );
+ }
+
+ Vacation::~Vacation() {
+ if ( mSieveJob ) mSieveJob->kill(); mSieveJob = 0;
+ delete mDialog; mDialog = 0;
+ kdDebug(5006) << "~Vacation()" << endl;
+ }
+
+ static inline QString dotstuff( QString s ) {
+ if ( s.startsWith( "." ) )
+ return '.' + s.replace( "\n.", "\n.." );
+ else
+ return s.replace( "\n.", "\n.." );
+ }
+
+ QString Vacation::composeScript( const QString & messageText,
+ int notificationInterval,
+ const AddrSpecList & addrSpecs,
+ bool sendForSpam, const QString & domain )
+ {
+ QString addressesArgument;
+ QStringList aliases;
+ if ( !addrSpecs.empty() ) {
+ addressesArgument += ":addresses [ ";
+ QStringList sl;
+ for ( AddrSpecList::const_iterator it = addrSpecs.begin() ; it != addrSpecs.end() ; ++it ) {
+ sl.push_back( '"' + (*it).asString().replace( '\\', "\\\\" ).replace( '"', "\\\"" ) + '"' );
+ aliases.push_back( (*it).asString() );
+ }
+ addressesArgument += sl.join( ", " ) + " ] ";
+ }
+ QString script = QString::fromLatin1("require \"vacation\";\n\n" );
+ if ( !sendForSpam )
+ script += QString::fromLatin1( "if header :contains \"X-Spam-Flag\" \"YES\""
+ " { keep; stop; }\n" ); // FIXME?
+
+ if ( !domain.isEmpty() ) // FIXME
+ script += QString::fromLatin1( "if not address :domain :contains \"from\" \"%1\" { keep; stop; }\n" ).arg( domain );
+
+ script += "vacation ";
+ script += addressesArgument;
+ if ( notificationInterval > 0 )
+ script += QString::fromLatin1(":days %1 ").arg( notificationInterval );
+ script += QString::fromLatin1("text:\n");
+ script += dotstuff( messageText.isEmpty() ? defaultMessageText() : messageText );
+ script += QString::fromLatin1( "\n.\n;\n" );
+ return script;
+ }
+
+ static KURL findUrlForAccount( const KMail::ImapAccountBase * a ) {
+ assert( a );
+ const SieveConfig sieve = a->sieveConfig();
+ if ( !sieve.managesieveSupported() )
+ return KURL();
+ if ( sieve.reuseConfig() ) {
+ // assemble Sieve url from the settings of the account:
+ KURL u;
+ u.setProtocol( "sieve" );
+ u.setHost( a->host() );
+ 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.setFileName( sieve.vacationFileName() );
+ return u;
+ } else {
+ KURL u = sieve.alternateURL();
+ u.setFileName( sieve.vacationFileName() );
+ return u;
+ }
+ }
+
+ KURL Vacation::findURL() const {
+ AccountManager * am = kmkernel->acctMgr();
+ assert( am );
+ for ( KMAccount * a = am->first() ; a ; a = am->next() )
+ if ( KMail::ImapAccountBase * iab = dynamic_cast<KMail::ImapAccountBase*>( a ) ) {
+ KURL u = findUrlForAccount( iab );
+ if ( !u.isEmpty() )
+ return u;
+ }
+ return KURL();
+ }
+
+ bool Vacation::parseScript( const QString & script, QString & messageText,
+ int & notificationInterval, QStringList & aliases,
+ bool & sendForSpam, QString & domainName ) {
+ if ( script.stripWhiteSpace().isEmpty() ) {
+ messageText = defaultMessageText();
+ notificationInterval = defaultNotificationInterval();
+ aliases = defaultMailAliases();
+ sendForSpam = defaultSendForSpam();
+ domainName = defaultDomainName();
+ return true;
+ }
+
+ // The stripWhiteSpace() call below prevents parsing errors. The
+ // slave somehow omits the last \n, which results in a lone \r at
+ // the end, leading to a parse error.
+ const QCString scriptUTF8 = script.stripWhiteSpace().utf8();
+ kdDebug(5006) << "scriptUtf8 = \"" + scriptUTF8 + "\"" << endl;
+ KSieve::Parser parser( scriptUTF8.begin(),
+ scriptUTF8.begin() + scriptUTF8.length() );
+ VacationDataExtractor vdx;
+ SpamDataExtractor sdx;
+ DomainRestrictionDataExtractor drdx;
+ KSieveExt::MultiScriptBuilder tsb( &vdx, &sdx, &drdx );
+ parser.setScriptBuilder( &tsb );
+ if ( !parser.parse() )
+ return false;
+ messageText = vdx.messageText().stripWhiteSpace();
+ notificationInterval = vdx.notificationInterval();
+ aliases = vdx.aliases();
+ if ( !GlobalSettings::allowOutOfOfficeUploadButNoSettings() ) {
+ sendForSpam = !sdx.found();
+ domainName = drdx.domainName();
+ }
+ return true;
+ }
+
+ QString Vacation::defaultMessageText() {
+ return i18n("I am out of office till %1.\n"
+ "\n"
+ "In urgent cases, please contact Mrs. <vacation replacement>\n"
+ "\n"
+ "email: <email address of vacation replacement>\n"
+ "phone: +49 711 1111 11\n"
+ "fax.: +49 711 1111 12\n"
+ "\n"
+ "Yours sincerely,\n"
+ "-- <enter your name and email address here>\n")
+ .arg( KGlobal::locale()->formatDate( QDate::currentDate().addDays( 1 ) ) );
+ }
+
+ int Vacation::defaultNotificationInterval() {
+ return 7; // days
+ }
+
+ QStringList Vacation::defaultMailAliases() {
+ QStringList sl;
+ for ( KPIM::IdentityManager::ConstIterator it = kmkernel->identityManager()->begin() ;
+ it != kmkernel->identityManager()->end() ; ++it )
+ if ( !(*it).emailAddr().isEmpty() )
+ sl.push_back( (*it).emailAddr() );
+ return sl;
+ }
+
+ bool Vacation::defaultSendForSpam() {
+ return GlobalSettings::outOfOfficeReactToSpam();
+ }
+
+ QString Vacation::defaultDomainName() {
+ return GlobalSettings::outOfOfficeDomain();
+ }
+
+ void Vacation::slotGetResult( SieveJob * job, bool success,
+ const QString & script, bool active ) {
+ kdDebug(5006) << "Vacation::slotGetResult( ??, " << success
+ << ", ?, " << active << " )" << endl
+ << "script:" << endl
+ << script << endl;
+ mSieveJob = 0; // job deletes itself after returning from this slot!
+
+ if ( !mCheckOnly && mUrl.protocol() == "sieve" && !job->sieveCapabilities().isEmpty() &&
+ !job->sieveCapabilities().contains("vacation") ) {
+ KMessageBox::sorry( 0, i18n("Your server did not list \"vacation\" in "
+ "its list of supported Sieve extensions;\n"
+ "without it, KMail cannot install out-of-"
+ "office replies for you.\n"
+ "Please contact you system administrator.") );
+ emit result( false );
+ return;
+ }
+
+ if ( !mDialog && !mCheckOnly )
+ mDialog = new VacationDialog( i18n("Configure \"Out of Office\" Replies"), 0, 0, false );
+
+ QString messageText = defaultMessageText();
+ int notificationInterval = defaultNotificationInterval();
+ QStringList aliases = defaultMailAliases();
+ bool sendForSpam = defaultSendForSpam();
+ QString domainName = defaultDomainName();
+ if ( !success ) active = false; // default to inactive
+
+ if ( !mCheckOnly && ( !success || !parseScript( script, messageText, notificationInterval, aliases, sendForSpam, domainName ) ) )
+ KMessageBox::information( 0, i18n("Someone (probably you) changed the "
+ "vacation script on the server.\n"
+ "KMail is no longer able to determine "
+ "the parameters for the autoreplies.\n"
+ "Default values will be used." ) );
+
+ mWasActive = active;
+ if ( mDialog ) {
+ mDialog->setActivateVacation( active );
+ mDialog->setMessageText( messageText );
+ mDialog->setNotificationInterval( notificationInterval );
+ mDialog->setMailAliases( aliases.join(", ") );
+ mDialog->setSendForSpam( sendForSpam );
+ mDialog->setDomainName( domainName );
+ mDialog->enableDomainAndSendForSpam( !GlobalSettings::allowOutOfOfficeUploadButNoSettings() );
+
+ connect( mDialog, SIGNAL(okClicked()), SLOT(slotDialogOk()) );
+ connect( mDialog, SIGNAL(cancelClicked()), SLOT(slotDialogCancel()) );
+ connect( mDialog, SIGNAL(defaultClicked()), SLOT(slotDialogDefaults()) );
+
+ mDialog->show();
+ }
+
+ emit scriptActive( mWasActive );
+ if ( mCheckOnly && mWasActive ) {
+ if ( KMessageBox::questionYesNo( 0, i18n( "There is still an active out-of-office reply configured.\n"
+ "Do you want to edit it?"), i18n("Out-of-office reply still active"),
+ KGuiItem( i18n( "Edit"), "edit" ), KGuiItem( i18n("Ignore"), "button_cancel" ) )
+ == KMessageBox::Yes ) {
+ kmkernel->getKMMainWidget()->slotEditVacation();
+ }
+ }
+ }
+
+ void Vacation::slotDialogDefaults() {
+ if ( !mDialog )
+ return;
+ mDialog->setActivateVacation( true );
+ mDialog->setMessageText( defaultMessageText() );
+ mDialog->setNotificationInterval( defaultNotificationInterval() );
+ mDialog->setMailAliases( defaultMailAliases().join(", ") );
+ mDialog->setSendForSpam( defaultSendForSpam() );
+ mDialog->setDomainName( defaultDomainName() );
+ }
+
+ void Vacation::slotDialogOk() {
+ kdDebug(5006) << "Vacation::slotDialogOk()" << endl;
+ // compose a new script:
+ const QString script = composeScript( mDialog->messageText(),
+ mDialog->notificationInterval(),
+ mDialog->mailAliases(),
+ mDialog->sendForSpam(),
+ mDialog->domainName() );
+ const bool active = mDialog->activateVacation();
+ emit scriptActive( active );
+
+ kdDebug(5006) << "script:" << endl << script << endl;
+
+ // and commit the dialog's settings to the server:
+ mSieveJob = SieveJob::put( mUrl, script, active, mWasActive );
+ connect( mSieveJob, SIGNAL(gotScript(KMail::SieveJob*,bool,const QString&,bool)),
+ active
+ ? SLOT(slotPutActiveResult(KMail::SieveJob*,bool))
+ : SLOT(slotPutInactiveResult(KMail::SieveJob*,bool)) );
+
+ // destroy the dialog:
+ mDialog->delayedDestruct();
+ mDialog = 0;
+ }
+
+ void Vacation::slotDialogCancel() {
+ kdDebug(5006) << "Vacation::slotDialogCancel()" << endl;
+ mDialog->delayedDestruct();
+ mDialog = 0;
+ emit result( false );
+ }
+
+ void Vacation::slotPutActiveResult( SieveJob * job, bool success ) {
+ handlePutResult( job, success, true );
+ }
+
+ void Vacation::slotPutInactiveResult( SieveJob * job, bool success ) {
+ handlePutResult( job, success, false );
+ }
+
+ void Vacation::handlePutResult( SieveJob *, bool success, bool activated ) {
+ if ( success )
+ KMessageBox::information( 0, activated
+ ? i18n("Sieve script installed successfully on the server.\n"
+ "Out of Office reply is now active.")
+ : i18n("Sieve script installed successfully on the server.\n"
+ "Out of Office reply has been deactivated.") );
+
+ kdDebug(5006) << "Vacation::handlePutResult( ???, " << success << ", ? )"
+ << endl;
+ mSieveJob = 0; // job deletes itself after returning from this slot!
+ emit result( success );
+ emit scriptActive( activated );
+ }
+
+
+} // namespace KMail
+
+#include "vacation.moc"
diff --git a/kmail/vacation.h b/kmail/vacation.h
new file mode 100644
index 00000000..18a48d23
--- /dev/null
+++ b/kmail/vacation.h
@@ -0,0 +1,89 @@
+/* -*- c++ -*-
+ vacation.cpp
+
+ KMail, the KDE mail client.
+ Copyright (c) 2002 Marc Mutz <mutz@kde.org>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License,
+ version 2.0, as published by the Free Software Foundation.
+ 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, US
+*/
+
+#ifndef __KMAIL_VACATION_H__
+#define __KMAIL_VACATION_H__
+
+#include <qobject.h>
+
+#include <kurl.h>
+
+class QString;
+class QStringList;
+template <typename T> class QValueList;
+namespace KMail {
+ class SieveJob;
+ class VacationDialog;
+}
+namespace KMime {
+ namespace Types {
+ struct AddrSpec;
+ typedef QValueList<AddrSpec> AddrSpecList;
+ }
+}
+
+namespace KMail {
+
+ class Vacation : public QObject {
+ Q_OBJECT
+ public:
+ Vacation( QObject * parent=0, bool checkOnly = false, const char * name=0 );
+ virtual ~Vacation();
+
+ bool isUsable() const { return !mUrl.isEmpty(); }
+
+ static QString defaultMessageText();
+ static int defaultNotificationInterval();
+ static QStringList defaultMailAliases();
+ static bool defaultSendForSpam();
+ static QString defaultDomainName();
+
+ protected:
+ static QString composeScript( const QString & messageText,
+ int notificationInterval,
+ const KMime::Types::AddrSpecList & aliases,
+ bool sendForSpam, const QString & excludeDomain );
+ static bool parseScript( const QString & script, QString & messageText,
+ int & notificationInterval, QStringList & aliases,
+ bool & sendForSpam, QString & domainName );
+ KURL findURL() const;
+ void handlePutResult( KMail::SieveJob * job, bool success, bool );
+
+
+ signals:
+ void result( bool success );
+ // indicates if the vaction script is active or not
+ void scriptActive( bool active );
+
+ protected slots:
+ void slotDialogDefaults();
+ void slotGetResult( KMail::SieveJob * job, bool success,
+ const QString & script, bool active );
+ void slotDialogOk();
+ void slotDialogCancel();
+ void slotPutActiveResult( KMail::SieveJob *, bool );
+ void slotPutInactiveResult( KMail::SieveJob *, bool );
+ protected:
+ // IO:
+ KMail::SieveJob * mSieveJob;
+ KURL mUrl;
+ // GUI:
+ KMail::VacationDialog * mDialog;
+ bool mWasActive;
+ bool mCheckOnly;
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_VACATION_H__
diff --git a/kmail/vacationdialog.cpp b/kmail/vacationdialog.cpp
new file mode 100644
index 00000000..b05f3fbf
--- /dev/null
+++ b/kmail/vacationdialog.cpp
@@ -0,0 +1,195 @@
+/* -*- c++ -*-
+ vacationdialog.cpp
+
+ KMail, the KDE mail client.
+ Copyright (c) 2002 Marc Mutz <mutz@kde.org>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License,
+ version 2.0, as published by the Free Software Foundation.
+ 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, US
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "vacationdialog.h"
+
+#include <kmime_header_parsing.h>
+using KMime::Types::AddrSpecList;
+using KMime::Types::AddressList;
+using KMime::Types::MailboxList;
+using KMime::HeaderParsing::parseAddressList;
+
+#include <knuminput.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kwin.h>
+#include <kapplication.h>
+
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qcheckbox.h>
+#include <qlineedit.h>
+#include <qtextedit.h>
+#include <qvalidator.h>
+
+namespace KMail {
+
+ VacationDialog::VacationDialog( const QString & caption, QWidget * parent,
+ const char * name, bool modal )
+ : KDialogBase( Plain, caption, Ok|Cancel|Default, Ok, parent, name, modal )
+ {
+ KWin::setIcons( winId(), kapp->icon(), kapp->miniIcon() );
+
+ static const int rows = 7;
+ int row = -1;
+
+ QGridLayout * glay = new QGridLayout( plainPage(), rows, 2, 0, spacingHint() );
+ glay->setColStretch( 1, 1 );
+
+ // explanation label:
+ ++row;
+ glay->addMultiCellWidget( new QLabel( i18n("Configure vacation "
+ "notifications to be sent:"),
+ plainPage() ), row, row, 0, 1 );
+
+ // Activate checkbox:
+ ++row;
+ mActiveCheck = new QCheckBox( i18n("&Activate vacation notifications"), plainPage() );
+ glay->addMultiCellWidget( mActiveCheck, row, row, 0, 1 );
+
+ // Message text edit:
+ ++row;
+ glay->setRowStretch( row, 1 );
+ mTextEdit = new QTextEdit( plainPage(), "mTextEdit" );
+ mTextEdit->setTextFormat( QTextEdit::PlainText );
+ glay->addMultiCellWidget( mTextEdit, row, row, 0, 1 );
+
+ // "Resent only after" spinbox and label:
+ ++row;
+ mIntervalSpin = new KIntSpinBox( 1, 356, 1, 7, 10, plainPage(), "mIntervalSpin" );
+ connect(mIntervalSpin, SIGNAL( valueChanged( int )), SLOT( slotIntervalSpinChanged( int ) ) );
+ glay->addWidget( new QLabel( mIntervalSpin, i18n("&Resend notification only after:"), plainPage() ), row, 0 );
+ glay->addWidget( mIntervalSpin, row, 1 );
+
+ // "Send responses for these addresses" lineedit and label:
+ ++row;
+ mMailAliasesEdit = new QLineEdit( plainPage(), "mMailAliasesEdit" );
+ glay->addWidget( new QLabel( mMailAliasesEdit, i18n("&Send responses for these addresses:"), plainPage() ), row, 0 );
+ glay->addWidget( mMailAliasesEdit, row, 1 );
+
+ // "Send responses also to SPAM mail" checkbox:
+ ++row;
+ mSpamCheck = new QCheckBox( i18n("Do not send vacation replies to spam messages"), plainPage(), "mSpamCheck" );
+ mSpamCheck->setChecked( true );
+ glay->addMultiCellWidget( mSpamCheck, row, row, 0, 1 );
+
+ // domain checkbox and linedit:
+ ++row;
+ mDomainCheck = new QCheckBox( i18n("Only react to mail coming from domain"), plainPage(), "mDomainCheck" );
+ mDomainCheck->setChecked( false );
+ mDomainEdit = new QLineEdit( plainPage(), "mDomainEdit" );
+ mDomainEdit->setEnabled( false );
+ mDomainEdit->setValidator( new QRegExpValidator( QRegExp( "[a-zA-Z0-9+-]+(?:\\.[a-zA-Z0-9+-]+)*" ), mDomainEdit ) );
+ glay->addWidget( mDomainCheck, row, 0 );
+ glay->addWidget( mDomainEdit, row, 1 );
+ connect( mDomainCheck, SIGNAL(toggled(bool)),
+ mDomainEdit, SLOT(setEnabled(bool)) );
+
+ Q_ASSERT( row == rows - 1 );
+ }
+
+ VacationDialog::~VacationDialog() {
+ kdDebug(5006) << "~VacationDialog()" << endl;
+ }
+
+ bool VacationDialog::activateVacation() const {
+ return mActiveCheck->isChecked();
+ }
+
+ void VacationDialog::setActivateVacation( bool activate ) {
+ mActiveCheck->setChecked( activate );
+ }
+
+ QString VacationDialog::messageText() const {
+ return mTextEdit->text().stripWhiteSpace();
+ }
+
+ void VacationDialog::setMessageText( const QString & text ) {
+ mTextEdit->setText( text );
+ const int height = ( mTextEdit->fontMetrics().lineSpacing() + 1 ) * 11;
+ mTextEdit->setMinimumHeight( height );
+ }
+
+ int VacationDialog::notificationInterval() const {
+ return mIntervalSpin->value();
+ }
+
+ void VacationDialog::setNotificationInterval( int days ) {
+ mIntervalSpin->setValue( days );
+ }
+
+ AddrSpecList VacationDialog::mailAliases() const {
+ QCString text = mMailAliasesEdit->text().latin1(); // ### IMAA: !ok
+ AddressList al;
+ const char * s = text.begin();
+ parseAddressList( s, text.end(), al );
+
+ AddrSpecList asl;
+ for ( AddressList::const_iterator it = al.begin() ; it != al.end() ; ++it ) {
+ const MailboxList & mbl = (*it).mailboxList;
+ for ( MailboxList::const_iterator jt = mbl.begin() ; jt != mbl.end() ; ++jt )
+ asl.push_back( (*jt).addrSpec );
+ }
+ return asl;
+ }
+
+ void VacationDialog::setMailAliases( const AddrSpecList & aliases ) {
+ QStringList sl;
+ for ( AddrSpecList::const_iterator it = aliases.begin() ; it != aliases.end() ; ++it )
+ sl.push_back( (*it).asString() );
+ mMailAliasesEdit->setText( sl.join(", ") );
+ }
+
+ void VacationDialog::setMailAliases( const QString & aliases ) {
+ mMailAliasesEdit->setText( aliases );
+ }
+
+ void VacationDialog::slotIntervalSpinChanged ( int value ) {
+ mIntervalSpin->setSuffix( i18n(" day", " days", value) );
+ }
+
+ QString VacationDialog::domainName() const {
+ return mDomainCheck->isChecked() ? mDomainEdit->text() : QString::null ;
+ }
+
+ void VacationDialog::setDomainName( const QString & domain ) {
+ mDomainEdit->setText( domain );
+ if ( !domain.isEmpty() )
+ mDomainCheck->setChecked( true );
+ }
+
+ bool VacationDialog::sendForSpam() const {
+ return !mSpamCheck->isChecked();
+ }
+
+ void VacationDialog::setSendForSpam( bool enable ) {
+ mSpamCheck->setChecked( !enable );
+ }
+
+
+ /* virtual*/
+ void KMail::VacationDialog::enableDomainAndSendForSpam( bool enable ) {
+ mDomainCheck->setEnabled( enable );
+ mDomainEdit->setEnabled( enable );
+ mSpamCheck->setEnabled( enable );
+ }
+
+
+} // namespace KMail
+
+#include "vacationdialog.moc"
diff --git a/kmail/vacationdialog.h b/kmail/vacationdialog.h
new file mode 100644
index 00000000..f84ae6a7
--- /dev/null
+++ b/kmail/vacationdialog.h
@@ -0,0 +1,82 @@
+/* -*- c++ -*-
+ vacationdialog.h
+
+ KMail, the KDE mail client.
+ Copyright (c) 2002 Marc Mutz <mutz@kde.org>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License,
+ version 2.0, as published by the Free Software Foundation.
+ 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, US
+*/
+
+#ifndef __KMAIL_VACATIONDIALOG_H__
+#define __KMAIL_VACATIONDIALOG_H__
+
+#include "kdialogbase.h"
+
+class QString;
+class QCheckBox;
+class QLineEdit;
+class QTextEdit;
+class KDateWidget;
+class KIntSpinBox;
+template <typename T> class QValueList;
+
+namespace KMime {
+ namespace Types {
+ struct AddrSpec;
+ typedef QValueList<AddrSpec> AddrSpecList;
+ }
+}
+
+namespace KMail {
+
+ class VacationDialog : public KDialogBase {
+ Q_OBJECT
+ public:
+ VacationDialog( const QString & caption, QWidget * parent=0,
+ const char * name=0, bool modal=true );
+ virtual ~VacationDialog();
+
+ virtual void enableDomainAndSendForSpam( bool enable = true );
+
+ bool activateVacation() const;
+ virtual void setActivateVacation( bool activate );
+
+ QString messageText() const;
+ virtual void setMessageText( const QString & text );
+
+ int notificationInterval() const;
+ virtual void setNotificationInterval( int days );
+
+ KMime::Types::AddrSpecList mailAliases() const;
+ virtual void setMailAliases( const KMime::Types::AddrSpecList & aliases );
+ virtual void setMailAliases( const QString & aliases );
+
+ QString domainName() const;
+ virtual void setDomainName( const QString & domain );
+
+ bool sendForSpam() const;
+ virtual void setSendForSpam( bool enable );
+
+
+ private slots:
+ void slotIntervalSpinChanged( int value );
+
+ protected:
+ QCheckBox * mActiveCheck;
+ KIntSpinBox * mIntervalSpin;
+ QLineEdit * mMailAliasesEdit;
+ QTextEdit * mTextEdit;
+ QCheckBox * mSpamCheck;
+ QCheckBox * mDomainCheck;
+ QLineEdit * mDomainEdit;
+
+ };
+
+} // namespace KMail
+
+#endif // __KMAIL_VACATIONDIALOG_H__
diff --git a/kmail/vcardviewer.cpp b/kmail/vcardviewer.cpp
new file mode 100644
index 00000000..87bb2bad
--- /dev/null
+++ b/kmail/vcardviewer.cpp
@@ -0,0 +1,96 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Daniel Molkentin <molkentin@kde.org>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "vcardviewer.h"
+#include "kmaddrbook.h"
+#include <kaddrbook.h>
+
+#include <addresseeview.h>
+using KPIM::AddresseeView;
+
+#include <kabc/vcardconverter.h>
+#include <kabc/addressee.h>
+using KABC::VCardConverter;
+using KABC::Addressee;
+
+#include <klocale.h>
+
+#include <qstring.h>
+
+KMail::VCardViewer::VCardViewer(QWidget *parent, const QString& vCard, const char* name)
+ : KDialogBase( parent, name, false, i18n("VCard Viewer"), User1|User2|User3|Close, Close,
+ true, i18n("&Import"), i18n("&Next Card"), i18n("&Previous Card") )
+{
+ mAddresseeView = new AddresseeView(this);
+ mAddresseeView->enableLinks( 0 );
+ mAddresseeView->setVScrollBarMode(QScrollView::Auto);
+ setMainWidget(mAddresseeView);
+
+ VCardConverter vcc;
+ mAddresseeList = vcc.parseVCards( vCard );
+ if ( !mAddresseeList.empty() ) {
+ itAddresseeList = mAddresseeList.begin();
+ mAddresseeView->setAddressee( *itAddresseeList );
+ if ( mAddresseeList.size() <= 1 ) {
+ showButton(User2, false);
+ showButton(User3, false);
+ }
+ else
+ enableButton(User3, false);
+ }
+ else {
+ mAddresseeView->setText(i18n("Failed to parse vCard."));
+ enableButton(User1, false);
+ }
+
+ resize(300,400);
+}
+
+KMail::VCardViewer::~VCardViewer()
+{
+}
+
+void KMail::VCardViewer::slotUser1()
+{
+ KAddrBookExternal::addVCard( *itAddresseeList, this );
+}
+
+void KMail::VCardViewer::slotUser2()
+{
+ // next vcard
+ mAddresseeView->setAddressee( *(++itAddresseeList) );
+ if ( itAddresseeList == --(mAddresseeList.end()) )
+ enableButton(User2, false);
+ enableButton(User3, true);
+}
+
+void KMail::VCardViewer::slotUser3()
+{
+ // previous vcard
+ mAddresseeView->setAddressee( *(--itAddresseeList) );
+ if ( itAddresseeList == mAddresseeList.begin() )
+ enableButton(User3, false);
+ enableButton(User2, true);
+}
+
+#include "vcardviewer.moc"
diff --git a/kmail/vcardviewer.h b/kmail/vcardviewer.h
new file mode 100644
index 00000000..44d07c42
--- /dev/null
+++ b/kmail/vcardviewer.h
@@ -0,0 +1,58 @@
+/* This file is part of the KDE project
+ Copyright (C) 2002 Daniel Molkentin <molkentin@kde.org>
+
+ 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; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+ */
+
+#ifndef VCARDVIEWER_H
+#define VCARDVIEWER_H
+
+#include <kdialogbase.h>
+#include <kabc/addressee.h>
+
+#include <qvaluelist.h>
+
+class QString;
+
+namespace KPIM {
+ class AddresseeView;
+}
+
+namespace KMail {
+
+ class VCardViewer : public KDialogBase
+ {
+ Q_OBJECT
+ public:
+ VCardViewer(QWidget *parent, const QString& vCard, const char* name);
+ virtual ~VCardViewer();
+
+ protected:
+ virtual void slotUser1();
+ virtual void slotUser2();
+ virtual void slotUser3();
+
+ private:
+ KPIM::AddresseeView * mAddresseeView;
+ KABC::Addressee::List mAddresseeList;
+
+ QValueListIterator<KABC::Addressee> itAddresseeList;
+ };
+
+}
+
+#endif // VCARDVIEWER_H
+
diff --git a/kmail/warningconfiguration.ui b/kmail/warningconfiguration.ui
new file mode 100644
index 00000000..7ed3ec92
--- /dev/null
+++ b/kmail/warningconfiguration.ui
@@ -0,0 +1,408 @@
+<!DOCTYPE UI><UI version="3.2" stdsetdef="1">
+<class>WarningConfiguration</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>WarningConfiguration</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>552</width>
+ <height>325</height>
+ </rect>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>mWarnUnsigned</cstring>
+ </property>
+ <property name="text">
+ <string>Warn when trying to send &amp;unsigned messages</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Check to be warned when sending unsigned messages.</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>&lt;qt&gt;
+&lt;h1&gt;Warn When Trying To Send Unsigned Messages&lt;/h1&gt;
+If this box is checked, you will be warned when you try to send parts of or the whole message unsigned.
+&lt;p&gt;
+It is recommended to leave this option turned on for maximum integrity.
+&lt;/qt&gt;</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>warnUnencryptedCB</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Warn when trying to send unencrypted messages</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Check to be warned when sending unencrypted messages.</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>&lt;qt&gt;
+&lt;h1&gt;Warn When Trying To Send Unencrypted Messages&lt;/h1&gt;
+If this box is checked, you will be warned when you try to send parts of or the whole message unencrypted.
+&lt;p&gt;
+It is recommended to leave this option turned on for maximum integrity.
+&lt;/qt&gt;</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>warnReceiverNotInCertificateCB</cstring>
+ </property>
+ <property name="text">
+ <string>Warn if &amp;Receiver's Email Address is Not in Certificate</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Check to be warned if the address is not in the certificate</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>&lt;qt&gt;
+&lt;h1&gt;Warn If Receiver's Email Address Is Not In Certificate&lt;/h1&gt;
+If this option is checked, a warning is issued if the email address of the receiver is not contained in the certificate used for encrypting.
+&lt;p&gt;
+It is recommended to leave this option turned on for maximum security.
+&lt;/qt&gt;</string>
+ </property>
+ </widget>
+ <widget class="QGroupBox">
+ <property name="name">
+ <cstring>warnGroupBox</cstring>
+ </property>
+ <property name="title">
+ <string>Warn if certificates/keys expire soon (configure thresholds below)</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel" row="0" column="1">
+ <property name="name">
+ <cstring>textLabel2</cstring>
+ </property>
+ <property name="text">
+ <string>For signing</string>
+ </property>
+ <property name="alignment">
+ <set>AlignCenter</set>
+ </property>
+ </widget>
+ <widget class="QLabel" row="0" column="2">
+ <property name="name">
+ <cstring>textLabel2_2</cstring>
+ </property>
+ <property name="text">
+ <string>For encryption</string>
+ </property>
+ <property name="alignment">
+ <set>AlignCenter</set>
+ </property>
+ </widget>
+ <widget class="QSpinBox" row="1" column="1">
+ <property name="name">
+ <cstring>mWarnSignKeyExpiresSB</cstring>
+ </property>
+ <property name="suffix">
+ <string> days</string>
+ </property>
+ <property name="maxValue">
+ <number>999</number>
+ </property>
+ <property name="minValue">
+ <number>1</number>
+ </property>
+ <property name="value">
+ <number>14</number>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Select the number of days here</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>&lt;qt&gt;
+&lt;h1&gt;Warn If Signature Certificate Expires&lt;/h1&gt;
+Select the minimum number of days the signature certificate should be valid without issuing a warning.
+&lt;p&gt;
+The recommended SPHINX setting is 14 days.
+&lt;/qt&gt;</string>
+ </property>
+ </widget>
+ <widget class="QSpinBox" row="1" column="2">
+ <property name="name">
+ <cstring>mWarnEncrKeyExpiresSB</cstring>
+ </property>
+ <property name="suffix">
+ <string> days</string>
+ </property>
+ <property name="maxValue">
+ <number>999</number>
+ </property>
+ <property name="minValue">
+ <number>1</number>
+ </property>
+ <property name="value">
+ <number>14</number>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Select the number of days here</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>&lt;qt&gt;
+&lt;h1&gt;Warn If Encryption Certificate Expires&lt;/h1&gt;
+Select the minimum number of days the encryption certificate should be valid without issuing a warning.
+&lt;p&gt;
+The recommended SPHINX setting is 14 days.
+&lt;/qt&gt;</string>
+ </property>
+ </widget>
+ <widget class="QSpinBox" row="2" column="2">
+ <property name="name">
+ <cstring>mWarnEncrChainCertExpiresSB</cstring>
+ </property>
+ <property name="suffix">
+ <string> days</string>
+ </property>
+ <property name="maxValue">
+ <number>999</number>
+ </property>
+ <property name="minValue">
+ <number>1</number>
+ </property>
+ <property name="value">
+ <number>14</number>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Select the number of days here</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>&lt;qt&gt;
+&lt;h1&gt;Warn If A Certificate In The Chain Expires&lt;/h1&gt;
+Select the minimum number of days all certificates in the chain should be valid without issuing a warning.
+&lt;p&gt;
+The recommended SPHINX setting is 14 days.
+&lt;/qt&gt;</string>
+ </property>
+ </widget>
+ <widget class="QSpinBox" row="2" column="1">
+ <property name="name">
+ <cstring>mWarnSignChainCertExpiresSB</cstring>
+ </property>
+ <property name="suffix">
+ <string> days</string>
+ </property>
+ <property name="maxValue">
+ <number>999</number>
+ </property>
+ <property name="minValue">
+ <number>1</number>
+ </property>
+ <property name="value">
+ <number>14</number>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Select the number of days here</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>&lt;qt&gt;
+&lt;h1&gt;Warn If CA Certificate Expires&lt;/h1&gt;
+Select the minimum number of days the CA certificate should be valid without issuing a warning.
+&lt;p&gt;
+The recommended SPHINX setting is 14 days.
+&lt;/qt&gt;</string>
+ </property>
+ </widget>
+ <widget class="QSpinBox" row="3" column="1">
+ <property name="name">
+ <cstring>mWarnSignRootCertExpiresSB</cstring>
+ </property>
+ <property name="suffix">
+ <string> days</string>
+ </property>
+ <property name="maxValue">
+ <number>999</number>
+ </property>
+ <property name="minValue">
+ <number>1</number>
+ </property>
+ <property name="value">
+ <number>14</number>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Select the number of days here</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>&lt;qt&gt;
+&lt;h1&gt;Warn If Root Certificate Expires&lt;/h1&gt;
+Select the minimum number of days the root certificate should be valid without issuing a warning.
+&lt;p&gt;
+The recommended SPHINX setting is 14 days.
+&lt;/qt&gt;</string>
+ </property>
+ </widget>
+ <widget class="QSpinBox" row="3" column="2">
+ <property name="name">
+ <cstring>mWarnEncrRootCertExpiresSB</cstring>
+ </property>
+ <property name="suffix">
+ <string> days</string>
+ </property>
+ <property name="maxValue">
+ <number>999</number>
+ </property>
+ <property name="minValue">
+ <number>1</number>
+ </property>
+ <property name="value">
+ <number>14</number>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string>Select the number of days here</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>&lt;qt&gt;
+&lt;h1&gt;Warn If Root Certificate Expires&lt;/h1&gt;
+Select the minimum number of days the root certificate should be valid without issuing a warning.
+&lt;p&gt;
+The recommended SPHINX setting is 14 days.
+&lt;/qt&gt;</string>
+ </property>
+ </widget>
+ <widget class="QLabel" row="3" column="0">
+ <property name="name">
+ <cstring>textLabel1_2_2</cstring>
+ </property>
+ <property name="text">
+ <string>For root certificates:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>mWarnSignRootCertExpiresSB</cstring>
+ </property>
+ </widget>
+ <widget class="QLabel" row="2" column="0">
+ <property name="name">
+ <cstring>textLabel1_2</cstring>
+ </property>
+ <property name="text">
+ <string>For intermediate CA certificates:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>mWarnSignChainCertExpiresSB</cstring>
+ </property>
+ </widget>
+ <widget class="QLabel" row="1" column="0">
+ <property name="name">
+ <cstring>textLabel1</cstring>
+ </property>
+ <property name="text">
+ <string>For end-user certificates/keys:</string>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>mWarnSignKeyExpiresSB</cstring>
+ </property>
+ </widget>
+ <spacer row="2" column="3">
+ <property name="name">
+ <cstring>spacer5</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </grid>
+ </widget>
+ <spacer>
+ <property name="name">
+ <cstring>spacer23</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>90</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout1</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <spacer>
+ <property name="name">
+ <cstring>spacer4</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>enableAllWarningsPB</cstring>
+ </property>
+ <property name="text">
+ <string>Re-enable All "Don't Ask Again" Warnings</string>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ </vbox>
+</widget>
+<tabstops>
+ <tabstop>mWarnUnsigned</tabstop>
+ <tabstop>warnUnencryptedCB</tabstop>
+ <tabstop>warnReceiverNotInCertificateCB</tabstop>
+ <tabstop>mWarnSignKeyExpiresSB</tabstop>
+ <tabstop>mWarnSignChainCertExpiresSB</tabstop>
+ <tabstop>mWarnSignRootCertExpiresSB</tabstop>
+ <tabstop>mWarnEncrKeyExpiresSB</tabstop>
+ <tabstop>mWarnEncrChainCertExpiresSB</tabstop>
+ <tabstop>mWarnEncrRootCertExpiresSB</tabstop>
+ <tabstop>enableAllWarningsPB</tabstop>
+</tabstops>
+<layoutdefaults spacing="6" margin="11"/>
+</UI>
diff --git a/kmail/xfaceconfigurator.cpp b/kmail/xfaceconfigurator.cpp
new file mode 100644
index 00000000..ba91efaa
--- /dev/null
+++ b/kmail/xfaceconfigurator.cpp
@@ -0,0 +1,285 @@
+/*
+ This file is part of KMail.
+
+ Copyright (c) 2004 Jakob Schr�er <js@camaya.net>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+
+ In addition, as a special exception, the copyright holders give
+ permission to link the code of this program with any edition of
+ the Qt library by Trolltech AS, Norway (or with modified versions
+ of Qt that use the same license as Qt), and distribute linked
+ combinations including the two. You must obey the GNU General
+ Public License in all respects for all of the code used other than
+ Qt. If you modify this file, you may extend this exception to
+ your version of the file, but you are not obligated to do so. If
+ you do not wish to do so, delete this exception statement from
+ your version.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "xfaceconfigurator.h"
+
+#include <kactivelabel.h>
+#include <kdialog.h>
+#include <kfiledialog.h>
+#include <kglobalsettings.h>
+#include <kimageio.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kurl.h>
+#include <kio/netaccess.h>
+using namespace KIO;
+#include <kxface.h>
+using namespace KPIM;
+#include <kabc/stdaddressbook.h>
+#include <kabc/addressee.h>
+using namespace KABC;
+
+#include <qbitmap.h>
+#include <qcheckbox.h>
+#include <qcombobox.h>
+#include <qimage.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qpushbutton.h>
+#include <qwhatsthis.h>
+#include <qwidgetstack.h>
+
+
+// #include <assert.h>
+
+using namespace KMail;
+using namespace KPIM;
+
+namespace KMail {
+
+ XFaceConfigurator::XFaceConfigurator( QWidget * parent, const char * name )
+ : QWidget( parent, name )
+ {
+ // tmp. vars:
+ QLabel * label;
+ QLabel * label1;
+ KActiveLabel * label2;
+ QWidget * page;
+ QVBoxLayout * vlay;
+ QHBoxLayout * hlay;
+ QVBoxLayout * page_vlay;
+ QPushButton * mFromFileBtn;
+ QPushButton * mFromAddrbkBtn;
+
+ vlay = new QVBoxLayout( this, 0, KDialog::spacingHint(), "main layout" );
+ hlay = new QHBoxLayout( vlay );
+
+ // "enable X-Face" checkbox:
+ mEnableCheck = new QCheckBox( i18n("&Send picture with every message"), this );
+ QWhatsThis::add( mEnableCheck,
+ i18n( "Check this box if you want KMail to add a so-called X-Face header to messages "
+ "written with this identity. An X-Face is a small (48x48 pixels) black and "
+ "white image that some mail clients are able to display." ) );
+ hlay->addWidget( mEnableCheck, Qt::AlignLeft | Qt::AlignVCenter );
+
+ mXFaceLabel = new QLabel( this );
+ QWhatsThis::add(mXFaceLabel,
+ i18n( "This is a preview of the picture selected/entered below." ) );
+ mXFaceLabel->setFixedSize(48, 48);
+ mXFaceLabel->setFrameShape( QFrame::Box );
+ hlay->addWidget( mXFaceLabel );
+
+// label1 = new QLabel( "X-Face:", this );
+// vlay->addWidget( label1 );
+
+ // "obtain X-Face from" combo and label:
+ hlay = new QHBoxLayout( vlay ); // inherits spacing
+ mSourceCombo = new QComboBox( false, this );
+ QWhatsThis::add(mSourceCombo,
+ i18n("Click on the widgets below to obtain help on the input methods."));
+ mSourceCombo->setEnabled( false ); // since !mEnableCheck->isChecked()
+ mSourceCombo->insertStringList( QStringList()
+ << i18n( "continuation of \"obtain picture from\"",
+ "External Source" )
+ << i18n( "continuation of \"obtain picture from\"",
+ "Input Field Below" ) );
+ label = new QLabel( mSourceCombo,
+ i18n("Obtain pic&ture from:"), this );
+ label->setEnabled( false ); // since !mEnableCheck->isChecked()
+ hlay->addWidget( label );
+ hlay->addWidget( mSourceCombo, 1 );
+
+ // widget stack that is controlled by the source combo:
+ QWidgetStack * widgetStack = new QWidgetStack( this );
+ widgetStack->setEnabled( false ); // since !mEnableCheck->isChecked()
+ vlay->addWidget( widgetStack, 1 );
+ connect( mSourceCombo, SIGNAL(highlighted(int)),
+ widgetStack, SLOT(raiseWidget(int)) );
+ connect( mEnableCheck, SIGNAL(toggled(bool)),
+ mSourceCombo, SLOT(setEnabled(bool)) );
+ connect( mEnableCheck, SIGNAL(toggled(bool)),
+ widgetStack, SLOT(setEnabled(bool)) );
+ connect( mEnableCheck, SIGNAL(toggled(bool)),
+ label, SLOT(setEnabled(bool)) );
+ // The focus might be still in the widget that is disabled
+ connect( mEnableCheck, SIGNAL(clicked()),
+ mEnableCheck, SLOT(setFocus()) );
+
+ int pageno = 0;
+ // page 0: create X-Face from image file or address book entry
+ page = new QWidget( widgetStack );
+ widgetStack->addWidget( page, pageno ); // force sequential numbers (play safe)
+ page_vlay = new QVBoxLayout( page, 0, KDialog::spacingHint() );
+ hlay = new QHBoxLayout( page_vlay ); // inherits spacing
+ mFromFileBtn = new QPushButton( i18n("Select File..."), page );
+ QWhatsThis::add( mFromFileBtn,
+ i18n("Use this to select an image file to create the picture from. "
+ "The image should be of high contrast and nearly quadratic shape. "
+ "A light background helps improve the result." ) );
+ mFromFileBtn->setAutoDefault( false );
+ page_vlay->addWidget( mFromFileBtn, 1 );
+ connect( mFromFileBtn, SIGNAL(released()),
+ this, SLOT(slotSelectFile()) );
+ mFromAddrbkBtn = new QPushButton( i18n("Set From Address Book"), page );
+ QWhatsThis::add( mFromAddrbkBtn,
+ i18n( "You can use a scaled-down version of the picture "
+ "you have set in your address book entry." ) );
+ mFromAddrbkBtn->setAutoDefault( false );
+ page_vlay->addWidget( mFromAddrbkBtn, 1 );
+ connect( mFromAddrbkBtn, SIGNAL(released()),
+ this, SLOT(slotSelectFromAddressbook()) );
+ label1 = new QLabel( i18n("<qt>KMail can send a small (48x48 pixels), low-quality, "
+ "monochrome picture with every message. "
+ "For example, this could be a picture of you or a glyph. "
+ "It is shown in the recipient's mail client (if supported)." ), page );
+ label1->setAlignment( QLabel::WordBreak | QLabel::AlignVCenter );
+ page_vlay->addWidget( label1 );
+
+ widgetStack->raiseWidget( 0 ); // since mSourceCombo->currentItem() == 0
+
+ // page 1: input field for direct entering
+ ++pageno;
+ page = new QWidget( widgetStack );
+ widgetStack->addWidget( page, pageno );
+ page_vlay = new QVBoxLayout( page, 0, KDialog::spacingHint() );
+ mTextEdit = new QTextEdit( page );
+ page_vlay->addWidget( mTextEdit );
+ QWhatsThis::add( mTextEdit, i18n( "Use this field to enter an arbitrary X-Face string." ) );
+ mTextEdit->setFont( KGlobalSettings::fixedFont() );
+ mTextEdit->setWrapPolicy( QTextEdit::Anywhere );
+ mTextEdit->setTextFormat( Qt::PlainText );
+ label2 = new KActiveLabel( i18n("Examples are available at <a href=\"http://www.xs4all.nl/~ace/X-Faces/\">http://www.xs4all.nl/~ace/X-Faces/</a>."), page );
+ page_vlay->addWidget( label2 );
+
+
+ connect(mTextEdit, SIGNAL(textChanged()), this, SLOT(slotUpdateXFace()));
+ }
+
+ XFaceConfigurator::~XFaceConfigurator() {
+
+ }
+
+ bool XFaceConfigurator::isXFaceEnabled() const {
+ return mEnableCheck->isChecked();
+ }
+
+ void XFaceConfigurator::setXFaceEnabled( bool enable ) {
+ mEnableCheck->setChecked( enable );
+ }
+
+ QString XFaceConfigurator::xface() const {
+ return mTextEdit->text();
+ }
+
+ void XFaceConfigurator::setXFace( const QString & text ) {
+ mTextEdit->setText( text );
+ }
+
+ void XFaceConfigurator::setXfaceFromFile( const KURL &url )
+ {
+ QString tmpFile;
+ if( KIO::NetAccess::download( url, tmpFile, this ) )
+ {
+ KXFace xf;
+ mTextEdit->setText( xf.fromImage( QImage( tmpFile ) ) );
+ KIO::NetAccess::removeTempFile( tmpFile );
+ } else {
+ KMessageBox::error(this, KIO::NetAccess::lastErrorString() );
+ }
+ }
+
+ void XFaceConfigurator::slotSelectFile()
+ {
+ QStringList mimeTypes = KImageIO::mimeTypes (KImageIO::Reading);
+ QString filter = mimeTypes.join (" ");
+ KURL url = KFileDialog::getOpenURL( QString::null, filter, this, QString::null );
+ if ( !url.isEmpty() )
+ setXfaceFromFile( url );
+ }
+
+ void XFaceConfigurator::slotSelectFromAddressbook()
+ {
+ StdAddressBook *ab = StdAddressBook::self( true );
+ Addressee me = ab->whoAmI();
+ if ( !me.isEmpty() )
+ {
+ if ( me.photo().isIntern() )
+ {
+ QImage photo = me.photo().data();
+ if ( !photo.isNull() )
+ {
+ KXFace xf;
+ mTextEdit->setText( xf.fromImage( photo ) );
+ }
+ else
+ KMessageBox::information( this, i18n("No picture set for your address book entry."), i18n("No Picture") );
+
+ }
+ else
+ {
+ KURL url = me.photo().url();
+ if( !url.isEmpty() )
+ setXfaceFromFile( url );
+ else
+ KMessageBox::information( this, i18n("No picture set for your address book entry."), i18n("No Picture") );
+ }
+ }
+ else
+ KMessageBox::information( this, i18n("You do not have your own contact defined in the address book."), i18n("No Picture") );
+ }
+
+ void XFaceConfigurator::slotUpdateXFace()
+ {
+ QString str = mTextEdit->text();
+ if ( !str.isEmpty() )
+ {
+ if ( str.startsWith("x-face:", false) )
+ {
+ str = str.remove("x-face:", false);
+ mTextEdit->setText(str);
+ }
+ KXFace xf;
+ QPixmap p( 48, 48, true );
+ p.convertFromImage( xf.toImage(str) );
+ mXFaceLabel->setPixmap( p );
+ }
+ else
+ mXFaceLabel->setPixmap( 0L );
+ }
+
+} // namespace KMail
+
+#include "xfaceconfigurator.moc"
diff --git a/kmail/xfaceconfigurator.h b/kmail/xfaceconfigurator.h
new file mode 100644
index 00000000..a08c2e54
--- /dev/null
+++ b/kmail/xfaceconfigurator.h
@@ -0,0 +1,64 @@
+/* -*- c++ -*-
+ xfaceconfigurator.cpp
+
+ KMail, the KDE mail client.
+ Copyright (c) 2004 Jakob Schrter <js@camaya.net>
+ Copyright (c) 2002 the KMail authors.
+ See file AUTHORS for details
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License,
+ version 2.0, as published by the Free Software Foundation.
+ 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, US
+*/
+
+#ifndef __KMAIL_XFACECONFIGURATOR_H__
+#define __KMAIL_XFACECONFIGURATOR_H__
+
+#include <qwidget.h>
+#include <qtextedit.h>
+
+class KURL;
+
+class QCheckBox;
+class QString;
+class QLabel;
+class QComboBox;
+
+namespace KMail {
+
+ class XFaceConfigurator : public QWidget {
+ Q_OBJECT
+ public:
+ XFaceConfigurator( QWidget * parent=0, const char * name=0 );
+ virtual ~XFaceConfigurator();
+
+ bool isXFaceEnabled() const;
+ void setXFaceEnabled( bool enable );
+
+
+ QString xface() const;
+ void setXFace( const QString & text );
+
+ protected:
+ QCheckBox * mEnableCheck;
+ QTextEdit * mTextEdit;
+ QLabel * mXFaceLabel;
+ QComboBox * mSourceCombo;
+
+
+ private:
+ void setXfaceFromFile( const KURL &url );
+
+ private slots:
+ void slotSelectFile();
+ void slotSelectFromAddressbook();
+ void slotUpdateXFace();
+ };
+} // namespace KMail
+
+#endif // __KMAIL_XFACECONFIGURATOR_H__
+
+