diff --git a/kmail/editor/kmcomposewin.cpp b/kmail/editor/kmcomposewin.cpp index d362dcf354..2896253e2c 100644 --- a/kmail/editor/kmcomposewin.cpp +++ b/kmail/editor/kmcomposewin.cpp @@ -1,3601 +1,3601 @@ /* * This file is part of KMail. * Copyright (c) 2011,2012,2013,2014 Laurent Montel * * Copyright (c) 2009 Constantin Berzan * * Based on KMail code by: * Copyright (c) 1997 Markus Wuebben * * 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 "kmcomposewin.h" // KMail includes #include "job/addressvalidationjob.h" #include "attachmentcontroller.h" #include "messagecomposer/attachment/attachmentmodel.h" #include "attachmentview.h" #include "codecaction.h" #include #include "kleo_util.h" #include "kmcommands.h" #include "editor/kmcomposereditor.h" #include "kmkernel.h" #include "settings/globalsettings.h" #include "kmmainwin.h" #include "kmmainwidget.h" #include "mailcomposeradaptor.h" // TODO port all D-Bus stuff... #include "messageviewer/viewer/stl_util.h" #include "messageviewer/utils/util.h" #include "messagecore/utils/stringutil.h" #include "messagecore/attachment/attachmentcollector.h" #include "util.h" #include "editor/snippetwidget.h" #include "templatesconfiguration_kfg.h" #include "foldercollectionmonitor.h" #include "kernel/mailkernel.h" #include "custommimeheader.h" #include "pimcommon/autocorrection/widgets/lineeditwithautocorrection.h" #include "pimcommon/translator/translatorwidget.h" #include "pimcommon/widgets/customtoolswidget.h" #include "warningwidgets/attachmentmissingwarning.h" #include "job/createnewcontactjob.h" #include "warningwidgets/externaleditorwarning.h" #include "libkdepim/progresswidget/statusbarprogresswidget.h" #include "libkdepim/progresswidget/progressstatusbarwidget.h" #include "pimcommon/util/editorutil.h" #include "pimcommon/storageservice/storageservicemanager.h" #include "pimcommon/storageservice/storageserviceprogressmanager.h" #include "agents/sendlateragent/sendlaterutil.h" #include "agents/sendlateragent/sendlaterdialog.h" #include "agents/sendlateragent/sendlaterinfo.h" // KDEPIM includes #include #include #include #include "kleo/cryptobackendfactory.h" #include "kleo/exportjob.h" #include "kleo/specialjob.h" #include #ifndef QT_NO_CURSOR #include #endif #include #include //#include "messageviewer/chiasmuskeyselector.h" #include #include #include #include #include #include #include #include #include #include #include #include "messagecore/settings/globalsettings.h" #include #include #include "messagecore/helpers/nodehelper.h" #include #include "messagecore/helpers/messagehelpers.h" #include "mailcommon/folder/folderrequester.h" #include "mailcommon/folder/foldercollection.h" // LIBKDEPIM includes #include // KDEPIMLIBS includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDELIBS includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Qt includes #include #include #include #include // System includes #include #include #include #include #include // MOC using Sonnet::DictionaryComboBox; using MailTransport::TransportManager; using MailTransport::Transport; using KPIM::RecentAddresses; using MessageComposer::KMeditor; KMail::Composer *KMail::makeComposer( const KMime::Message::Ptr &msg, bool lastSignState, bool lastEncryptState, Composer::TemplateContext context, uint identity, const QString & textSelection, const QString & customTemplate ) { return KMComposeWin::create( msg, lastSignState, lastEncryptState, context, identity, textSelection, customTemplate ); } KMail::Composer *KMComposeWin::create( const KMime::Message::Ptr &msg, bool lastSignState, bool lastEncryptState, Composer::TemplateContext context, uint identity, const QString & textSelection, const QString & customTemplate ) { return new KMComposeWin( msg, lastSignState, lastEncryptState, context, identity, textSelection, customTemplate ); } int KMComposeWin::s_composerNumber = 0; //----------------------------------------------------------------------------- KMComposeWin::KMComposeWin( const KMime::Message::Ptr &aMsg, bool lastSignState, bool lastEncryptState, Composer::TemplateContext context, uint id, const QString & textSelection, const QString & customTemplate ) : KMail::Composer( "kmail-composer#" ), mDone( false ), mTextSelection( textSelection ), mCustomTemplate( customTemplate ), mSigningAndEncryptionExplicitlyDisabled( false ), mFolder( Akonadi::Collection( -1 ) ), mForceDisableHtml( false ), mId( id ), mContext( context ), mSignAction( 0 ), mEncryptAction( 0 ), mRequestMDNAction( 0 ), mUrgentAction( 0 ), mAllFieldsAction( 0 ), mFromAction( 0 ), mReplyToAction( 0 ), mSubjectAction( 0 ), mIdentityAction( 0 ), mTransportAction( 0 ), mFccAction( 0 ), mWordWrapAction( 0 ), mFixedFontAction( 0 ), mAutoSpellCheckingAction( 0 ), mDictionaryAction( 0 ), mSnippetAction( 0 ), mTranslateAction(0), mGenerateShortenUrl( 0 ), mCodecAction( 0 ), mCryptoModuleAction( 0 ), mFindText( 0 ), mFindNextText( 0 ), mReplaceText( 0 ), mSelectAll( 0 ), //mEncryptChiasmusAction( 0 ), mDummyComposer( 0 ), mLabelWidth( 0 ), mComposerBase( 0 ), mSelectSpecialChar( 0 ), mSignatureStateIndicator( 0 ), mEncryptionStateIndicator( 0 ), mPreventFccOverwrite( false ), mCheckForForgottenAttachments( true ), mIgnoreStickyFields( false ), mWasModified( false ), mNumProgressUploadFile(0) { mComposerBase = new MessageComposer::ComposerViewBase( this, this ); mComposerBase->setIdentityManager( kmkernel->identityManager() ); connect( mComposerBase, SIGNAL(disableHtml(MessageComposer::ComposerViewBase::Confirmation)), this, SLOT(disableHtml(MessageComposer::ComposerViewBase::Confirmation)) ); connect( mComposerBase, SIGNAL(enableHtml()), this, SLOT(enableHtml()) ); connect( mComposerBase, SIGNAL(failed(QString,MessageComposer::ComposerViewBase::FailedType)), this, SLOT(slotSendFailed(QString,MessageComposer::ComposerViewBase::FailedType)) ); connect( mComposerBase, SIGNAL(sentSuccessfully()), this, SLOT(slotSendSuccessful()) ); connect( mComposerBase, SIGNAL(modified(bool)), this, SLOT(setModified(bool)) ); (void) new MailcomposerAdaptor( this ); mdbusObjectPath = QLatin1String("/Composer_") + QString::number( ++s_composerNumber ); QDBusConnection::sessionBus().registerObject( mdbusObjectPath, this ); MessageComposer::SignatureController* sigController = new MessageComposer::SignatureController( this ); connect( sigController, SIGNAL(enableHtml()), SLOT(enableHtml()) ); mComposerBase->setSignatureController( sigController ); if ( kmkernel->xmlGuiInstance().isValid() ) { setComponentData( kmkernel->xmlGuiInstance() ); } mMainWidget = new QWidget( this ); // splitter between the headers area and the actual editor mHeadersToEditorSplitter = new QSplitter( Qt::Vertical, mMainWidget ); mHeadersToEditorSplitter->setObjectName( QLatin1String("mHeadersToEditorSplitter") ); mHeadersToEditorSplitter->setChildrenCollapsible( false ); mHeadersArea = new QWidget( mHeadersToEditorSplitter ); mHeadersArea->setSizePolicy( mHeadersToEditorSplitter->sizePolicy().horizontalPolicy(), QSizePolicy::Expanding ); mHeadersToEditorSplitter->addWidget( mHeadersArea ); QList defaultSizes; defaultSizes << 0; mHeadersToEditorSplitter->setSizes( defaultSizes ); QVBoxLayout *v = new QVBoxLayout( mMainWidget ); v->setMargin(0); v->addWidget( mHeadersToEditorSplitter ); KPIMIdentities::IdentityCombo* identity = new KPIMIdentities::IdentityCombo( kmkernel->identityManager(), mHeadersArea ); identity->setToolTip( i18n( "Select an identity for this message" ) ); mComposerBase->setIdentityCombo( identity ); sigController->setIdentityCombo( identity ); sigController->suspend(); // we have to do identity change tracking ourselves due to the template code mDictionaryCombo = new DictionaryComboBox( mHeadersArea ); mDictionaryCombo->setToolTip( i18n( "Select the dictionary to use when spell-checking this message" ) ); mFccFolder = new MailCommon::FolderRequester( mHeadersArea ); mFccFolder->setNotAllowToCreateNewFolder( true ); mFccFolder->setMustBeReadWrite( true ); mFccFolder->setToolTip( i18n( "Select the sent-mail folder where a copy of this message will be saved" ) ); connect( mFccFolder, SIGNAL(folderChanged(Akonadi::Collection)), this, SLOT(slotFccFolderChanged(Akonadi::Collection)) ); MailTransport::TransportComboBox* transport = new MailTransport::TransportComboBox( mHeadersArea ); transport->setToolTip( i18n( "Select the outgoing account to use for sending this message" ) ); mComposerBase->setTransportCombo( transport ); mEdtFrom = new MessageComposer::ComposerLineEdit( false, mHeadersArea ); mEdtFrom->setObjectName( QLatin1String("fromLine") ); mEdtFrom->setRecentAddressConfig( MessageComposer::MessageComposerSettings::self()->config() ); mEdtFrom->setToolTip( i18n( "Set the \"From:\" email address for this message" ) ); mEdtReplyTo = new MessageComposer::ComposerLineEdit( true, mHeadersArea ); mEdtReplyTo->setObjectName( QLatin1String("replyToLine") ); mEdtReplyTo->setRecentAddressConfig( MessageComposer::MessageComposerSettings::self()->config() ); mEdtReplyTo->setToolTip( i18n( "Set the \"Reply-To:\" email address for this message" ) ); connect( mEdtReplyTo, SIGNAL(completionModeChanged(KGlobalSettings::Completion)), SLOT(slotCompletionModeChanged(KGlobalSettings::Completion)) ); MessageComposer::RecipientsEditor* recipientsEditor = new MessageComposer::RecipientsEditor( mHeadersArea ); recipientsEditor->setRecentAddressConfig( MessageComposer::MessageComposerSettings::self()->config() ); connect( recipientsEditor, SIGNAL(completionModeChanged(KGlobalSettings::Completion)), SLOT(slotCompletionModeChanged(KGlobalSettings::Completion)) ); connect( recipientsEditor, SIGNAL(sizeHintChanged()), SLOT(recipientEditorSizeHintChanged()) ); mComposerBase->setRecipientsEditor( recipientsEditor ); mEdtSubject = new PimCommon::LineEditWithAutoCorrection( mHeadersArea, QLatin1String( "kmail2rc" ) ); mEdtSubject->setActivateLanguageMenu(false); mEdtSubject->setToolTip( i18n( "Set a subject for this message" ) ); mEdtSubject->setAutocorrection(KMKernel::self()->composerAutoCorrection()); mLblIdentity = new QLabel( i18n("&Identity:"), mHeadersArea ); mDictionaryLabel = new QLabel( i18n("&Dictionary:"), mHeadersArea ); mLblFcc = new QLabel( i18n("&Sent-Mail folder:"), mHeadersArea ); mLblTransport = new QLabel( i18n("&Mail transport:"), mHeadersArea ); mLblFrom = new QLabel( i18nc("sender address field", "&From:"), mHeadersArea ); mLblReplyTo = new QLabel( i18n("&Reply to:"), mHeadersArea ); mLblSubject = new QLabel( i18nc("@label:textbox Subject of email.", "S&ubject:"), mHeadersArea ); QString sticky = i18nc("@option:check Sticky identity.", "Sticky"); mBtnIdentity = new QCheckBox( sticky, mHeadersArea ); mBtnIdentity->setToolTip( i18n( "Use the selected value as your identity for future messages" ) ); mBtnFcc = new QCheckBox( sticky, mHeadersArea ); mBtnFcc->setToolTip( i18n( "Use the selected value as your sent-mail folder for future messages" ) ); mBtnTransport = new QCheckBox( sticky, mHeadersArea ); mBtnTransport->setToolTip( i18n( "Use the selected value as your outgoing account for future messages" ) ); mBtnDictionary = new QCheckBox( sticky, mHeadersArea ); mBtnDictionary->setToolTip( i18n( "Use the selected value as your dictionary for future messages" ) ); mShowHeaders = GlobalSettings::self()->headers(); mDone = false; mGrid = 0; mFixedFontAction = 0; // the attachment view is separated from the editor by a splitter mSplitter = new QSplitter( Qt::Vertical, mMainWidget ); mSplitter->setObjectName( QLatin1String("mSplitter") ); mSplitter->setChildrenCollapsible( false ); mSnippetSplitter = new QSplitter( Qt::Horizontal, mSplitter ); mSnippetSplitter->setObjectName( QLatin1String("mSnippetSplitter") ); mSnippetSplitter->setChildrenCollapsible( false ); mSplitter->addWidget( mSnippetSplitter ); QWidget *editorAndCryptoStateIndicators = new QWidget( mSplitter ); QVBoxLayout *vbox = new QVBoxLayout( editorAndCryptoStateIndicators ); vbox->setMargin(0); QHBoxLayout *hbox = new QHBoxLayout(); { hbox->setMargin(0); mSignatureStateIndicator = new QLabel( editorAndCryptoStateIndicators ); mSignatureStateIndicator->setAlignment( Qt::AlignHCenter ); hbox->addWidget( mSignatureStateIndicator ); // Get the colors for the label QPalette p( mSignatureStateIndicator->palette() ); KColorScheme scheme( QPalette::Active, KColorScheme::View ); QColor defaultSignedColor = // pgp signed scheme.background( KColorScheme::PositiveBackground ).color(); QColor defaultEncryptedColor( 0x00, 0x80, 0xFF ); // light blue // pgp encrypted QColor signedColor = defaultSignedColor; QColor encryptedColor = defaultEncryptedColor; if ( !MessageCore::GlobalSettings::self()->useDefaultColors() ) { signedColor = MessageCore::GlobalSettings::self()->pgpSignedMessageColor(); encryptedColor = MessageCore::GlobalSettings::self()->pgpEncryptedMessageColor(); } p.setColor( QPalette::Window, signedColor ); mSignatureStateIndicator->setPalette( p ); mSignatureStateIndicator->setAutoFillBackground( true ); mEncryptionStateIndicator = new QLabel( editorAndCryptoStateIndicators ); mEncryptionStateIndicator->setAlignment( Qt::AlignHCenter ); hbox->addWidget( mEncryptionStateIndicator ); p.setColor( QPalette::Window, encryptedColor); mEncryptionStateIndicator->setPalette( p ); mEncryptionStateIndicator->setAutoFillBackground( true ); } KMComposerEditor* editor = new KMComposerEditor( this, editorAndCryptoStateIndicators ); connect( editor, SIGNAL(textChanged()), this, SLOT(slotEditorTextChanged()) ); mComposerBase->setEditor( editor ); vbox->addLayout( hbox ); vbox->addWidget( editor ); mSnippetSplitter->insertWidget( 0, editorAndCryptoStateIndicators ); mSnippetSplitter->setOpaqueResize( true ); sigController->setEditor( editor ); mHeadersToEditorSplitter->addWidget( mSplitter ); editor->setAcceptDrops( true ); connect(sigController, SIGNAL(signatureAdded()), mComposerBase->editor(), SLOT(startExternalEditor())); connect( mDictionaryCombo, SIGNAL(dictionaryChanged(QString)), this, SLOT(slotSpellCheckingLanguage(QString)) ); connect( editor, SIGNAL(languageChanged(QString)), this, SLOT(slotLanguageChanged(QString)) ); connect( editor, SIGNAL(spellCheckStatus(QString)), this, SLOT(slotSpellCheckingStatus(QString)) ); connect( editor, SIGNAL(insertModeChanged()), this, SLOT(slotOverwriteModeChanged()) ); connect(editor,SIGNAL(spellCheckingFinished()),this,SLOT(slotCheckSendNow())); mSnippetWidget = new SnippetWidget( editor, actionCollection(), mSnippetSplitter ); mSnippetWidget->setVisible( GlobalSettings::self()->showSnippetManager() ); mSnippetSplitter->addWidget( mSnippetWidget ); mSnippetSplitter->setCollapsible( 0, false ); mSplitter->setOpaqueResize( true ); mBtnIdentity->setWhatsThis( GlobalSettings::self()->stickyIdentityItem()->whatsThis() ); mBtnFcc->setWhatsThis( GlobalSettings::self()->stickyFccItem()->whatsThis() ); mBtnTransport->setWhatsThis( GlobalSettings::self()->stickyTransportItem()->whatsThis() ); mBtnDictionary->setWhatsThis( GlobalSettings::self()->stickyDictionaryItem()->whatsThis() ); setCaption( i18n("Composer") ); setMinimumSize( 200, 200 ); mBtnIdentity->setFocusPolicy( Qt::NoFocus ); mBtnFcc->setFocusPolicy( Qt::NoFocus ); mBtnTransport->setFocusPolicy( Qt::NoFocus ); mBtnDictionary->setFocusPolicy( Qt::NoFocus ); mCustomToolsWidget = new PimCommon::CustomToolsWidget(this); mSplitter->addWidget(mCustomToolsWidget); connect(mCustomToolsWidget, SIGNAL(insertShortUrl(QString)), this, SLOT(slotInsertShortUrl(QString))); MessageComposer::AttachmentModel* attachmentModel = new MessageComposer::AttachmentModel( this ); KMail::AttachmentView *attachmentView = new KMail::AttachmentView( attachmentModel, mSplitter ); attachmentView->hideIfEmpty(); connect(attachmentView,SIGNAL(modified(bool)),SLOT(setModified(bool))); KMail::AttachmentController* attachmentController = new KMail::AttachmentController( attachmentModel, attachmentView, this ); mComposerBase->setAttachmentModel( attachmentModel ); mComposerBase->setAttachmentController( attachmentController ); mAttachmentMissing = new AttachmentMissingWarning(this); connect(mAttachmentMissing, SIGNAL(attachMissingFile()), this, SLOT(slotAttachMissingFile())); connect(mAttachmentMissing, SIGNAL(closeAttachMissingFile()), this, SLOT(slotCloseAttachMissingFile())); connect(mAttachmentMissing, SIGNAL(explicitClosedMissingAttachment()), this, SLOT(slotExplicitClosedMissingAttachment())); v->addWidget(mAttachmentMissing); m_verifyMissingAttachment = new QTimer(this); m_verifyMissingAttachment->start(1000*5); connect( m_verifyMissingAttachment, SIGNAL(timeout()), this, SLOT(slotVerifyMissingAttachmentTimeout()) ); connect( attachmentController, SIGNAL(fileAttached()), mAttachmentMissing, SLOT(slotFileAttached()) ); mExternalEditorWarning = new ExternalEditorWarning(this); v->addWidget(mExternalEditorWarning); readConfig(); setupStatusBar(attachmentView->widget()); setupActions(); setupEditor(); rethinkFields(); slotUpdateSignatureAndEncrypionStateIndicators(); applyMainWindowSettings( KMKernel::self()->config()->group( "Composer") ); connect( mEdtSubject, SIGNAL(textChanged()), SLOT(slotUpdWinTitle()) ); connect( identity, 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->folderCollectionMonitor(), SIGNAL(collectionRemoved(Akonadi::Collection)), SLOT(slotFolderRemoved(Akonadi::Collection)) ); connect( kmkernel, SIGNAL(configChanged()), this, SLOT(slotConfigChanged()) ); mMainWidget->resize( 480, 510 ); setCentralWidget( mMainWidget ); if ( GlobalSettings::self()->useHtmlMarkup() ) enableHtml(); else disableHtml( MessageComposer::ComposerViewBase::LetUserConfirm ); if ( GlobalSettings::self()->useExternalEditor() ) { editor->setUseExternalEditor( true ); editor->setExternalEditorPath( GlobalSettings::self()->externalEditor() ); } if ( aMsg ) { setMessage( aMsg, lastSignState, lastEncryptState ); } mComposerBase->recipientsEditor()->setFocus(); editor->updateActionStates(); // set toolbar buttons to correct values mDone = true; mDummyComposer = new MessageComposer::Composer( this ); mDummyComposer->globalPart()->setParentWidgetForGui( this ); connect(KMKernel::self()->storageServiceManager(), SIGNAL(uploadFileDone(QString,QString)), this, SLOT(slotUploadFileDone(QString,QString))); connect(KMKernel::self()->storageServiceManager(), SIGNAL(uploadFileFailed(QString,QString)), this, SLOT(slotUploadFileFailed(QString,QString))); connect(KMKernel::self()->storageServiceManager(), SIGNAL(shareLinkDone(QString,QString)), this, SLOT(slotShareLinkDone(QString,QString))); connect(KMKernel::self()->storageServiceManager(), SIGNAL(uploadFileStart(PimCommon::StorageServiceAbstract*)), this, SLOT(slotUploadFileStart(PimCommon::StorageServiceAbstract*))); connect(KMKernel::self()->storageServiceManager(), SIGNAL(actionFailed(QString,QString)), this, SLOT(slotActionFailed(QString,QString))); } //----------------------------------------------------------------------------- KMComposeWin::~KMComposeWin() { writeConfig(); // When we have a collection set, store the message back to that collection. // Note that when we save the message or sent it, mFolder is set back to 0. // So this for example kicks in when opening a draft and then closing the window. if ( mFolder.isValid() && mMsg && isModified() ) { Akonadi::Item item; item.setPayload( mMsg ); item.setMimeType( KMime::Message::mimeType() ); MessageStatus status; status.setRead(); item.setFlags( status.statusFlags() ); new Akonadi::ItemCreateJob( item, mFolder ); // FIXME: listen to the result signal. The whole thing needs to be moved // out of the destructor for this } delete mComposerBase; } void KMComposeWin::slotSpellCheckingLanguage(const QString& language) { mComposerBase->editor()->setSpellCheckingLanguage(language ); mEdtSubject->setSpellCheckingLanguage(language ); } QString KMComposeWin::dbusObjectPath() const { return mdbusObjectPath; } void KMComposeWin::slotEditorTextChanged() { const bool textIsNotEmpty = !mComposerBase->editor()->document()->isEmpty(); mFindText->setEnabled( textIsNotEmpty ); mFindNextText->setEnabled( textIsNotEmpty ); mReplaceText->setEnabled( textIsNotEmpty ); mSelectAll->setEnabled( textIsNotEmpty ); } //----------------------------------------------------------------------------- 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 ) { kDebug() << "addAttachment and sending!"; const int nbUrl = urls.count(); for ( int i =0; i < nbUrl; ++i ) { mComposerBase->addAttachmentUrlSync( urls[i], comment ); } send( how ); } //----------------------------------------------------------------------------- void KMComposeWin::addAttachment( const KUrl &url, const QString &comment ) { mComposerBase->addAttachment( url, comment ); } void KMComposeWin::addAttachment( const QString& name, KMime::Headers::contentEncoding cte, const QString& charset, const QByteArray& data, const QByteArray& mimeType ) { Q_UNUSED( cte ); mComposerBase->addAttachment( name, name, charset, data, mimeType ); } //----------------------------------------------------------------------------- void KMComposeWin::readConfig( bool reload /* = false */ ) { 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() ); const int currentTransport = GlobalSettings::self()->currentTransport().isEmpty() ? -1 : GlobalSettings::self()->currentTransport().toInt(); mBtnDictionary->setChecked( GlobalSettings::self()->stickyDictionary() ); mEdtFrom->setCompletionMode( (KGlobalSettings::Completion)GlobalSettings::self()->completionMode() ); mComposerBase->recipientsEditor()->setCompletionMode( (KGlobalSettings::Completion)GlobalSettings::self()->completionMode() ); mEdtReplyTo->setCompletionMode( (KGlobalSettings::Completion)GlobalSettings::self()->completionMode() ); if ( MessageCore::GlobalSettings::self()->useDefaultFonts() ) { mBodyFont = KGlobalSettings::generalFont(); mFixedFont = KGlobalSettings::fixedFont(); } else { mBodyFont = GlobalSettings::self()->composerFont(); mFixedFont = MessageViewer::GlobalSettings::self()->fixedFont(); } slotUpdateFont(); mEdtFrom->setFont( mBodyFont ); mEdtReplyTo->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 { QList defaults; defaults << (int)(width() * 0.8) << (int)(width() * 0.2); mSnippetSplitter->setSizes( defaults ); } } mComposerBase->identityCombo()->setCurrentIdentity( mId ); kDebug() << mComposerBase->identityCombo()->currentIdentityName(); const KPIMIdentities::Identity & ident = kmkernel->identityManager()->identityForUoid( mId ); if ( mBtnTransport->isChecked() && currentTransport != -1 ) { const Transport *transport = TransportManager::self()->transportById( currentTransport ); if ( transport ) mComposerBase->transportComboBox()->setCurrentTransport( transport->id() ); } mComposerBase->setAutoSaveInterval( GlobalSettings::self()->autosaveInterval() * 1000 * 60 ); if ( mBtnDictionary->isChecked() ) { mDictionaryCombo->setCurrentByDictionaryName( GlobalSettings::self()->previousDictionary() ); } else { mDictionaryCombo->setCurrentByDictionaryName( ident.dictionary() ); } 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()->setStickyFcc( mBtnFcc->isChecked() ); if ( !mIgnoreStickyFields ) { GlobalSettings::self()->setCurrentTransport( mComposerBase->transportComboBox()->currentText() ); GlobalSettings::self()->setStickyTransport( mBtnTransport->isChecked() ); GlobalSettings::self()->setStickyDictionary( mBtnDictionary->isChecked() ); GlobalSettings::self()->setStickyIdentity( mBtnIdentity->isChecked() ); GlobalSettings::self()->setPreviousIdentity( mComposerBase->identityCombo()->currentIdentity() ); } GlobalSettings::self()->setPreviousFcc( QString::number(mFccFolder->collection().id()) ); GlobalSettings::self()->setPreviousDictionary( mDictionaryCombo->currentDictionaryName() ); GlobalSettings::self()->setAutoSpellChecking( mAutoSpellCheckingAction->isChecked() ); MessageViewer::GlobalSettings::self()->setUseFixedFont( mFixedFontAction->isChecked() ); if ( !mForceDisableHtml ) GlobalSettings::self()->setUseHtmlMarkup( mComposerBase->editor()->textMode() == KMeditor::Rich ); GlobalSettings::self()->setComposerSize( size() ); GlobalSettings::self()->setShowSnippetManager( mSnippetAction->isChecked() ); saveMainWindowSettings( KMKernel::self()->config()->group( "Composer" ) ); if ( mSnippetAction->isChecked() ) GlobalSettings::setSnippetSplitterPosition( mSnippetSplitter->sizes() ); // make sure config changes are written to disk, cf. bug 127538 KMKernel::self()->slotSyncConfig(); } MessageComposer::Composer* KMComposeWin::createSimpleComposer() { QList< QByteArray > charsets = mCodecAction->mimeCharsets(); if( !mOriginalPreferredCharset.isEmpty() ) { charsets.insert( 0, mOriginalPreferredCharset ); } mComposerBase->setFrom( from() ); mComposerBase->setReplyTo( replyTo() ); mComposerBase->setSubject( subject() ); mComposerBase->setCharsets( charsets ); return mComposerBase->createSimpleComposer(); } //----------------------------------------------------------------------------- void KMComposeWin::slotView( void ) { if ( !mDone ) { return; // otherwise called from rethinkFields during the construction // which is not the intended behavior } //This sucks awfully, but no, I cannot get an activated(int id) from // actionContainer() KToggleAction *act = ::qobject_cast( sender() ); if ( !act ) { return; } int id; 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 == mSubjectAction ) { id = HDR_SUBJECT; } else if ( act == mFccAction ) { id = HDR_FCC; } else if ( act == mDictionaryAction ) { id = HDR_DICTIONARY; } else { id = 0; kDebug() <<"Something is wrong (Oh, yeah?)"; 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 ) const { 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( mComposerBase->editor() ); // 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; 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++; } } delete mGrid; mGrid = new QGridLayout( mHeadersArea ); mGrid->setSpacing( KDialog::spacingHint() ); mGrid->setMargin( KDialog::marginHint() / 2 ); mGrid->setColumnStretch( 0, 1 ); mGrid->setColumnStretch( 1, 100 ); mGrid->setColumnStretch( 2, 1 ); mGrid->setRowStretch( mNumHeaders + 1, 100 ); row = 0; kDebug(); mLabelWidth = mComposerBase->recipientsEditor()->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, mLblIdentity, mComposerBase->identityCombo(), mBtnIdentity ); if ( !fromSlot ) { mDictionaryAction->setChecked( abs( mShowHeaders )&HDR_DICTIONARY ); } rethinkHeaderLine( showHeaders,HDR_DICTIONARY, row, mDictionaryLabel, mDictionaryCombo, mBtnDictionary ); if ( !fromSlot ) { mFccAction->setChecked( abs( mShowHeaders )&HDR_FCC ); } rethinkHeaderLine( showHeaders,HDR_FCC, row, mLblFcc, mFccFolder, mBtnFcc ); if ( !fromSlot ) { mTransportAction->setChecked( abs( mShowHeaders )&HDR_TRANSPORT ); } rethinkHeaderLine( showHeaders,HDR_TRANSPORT, row, mLblTransport, mComposerBase->transportComboBox(), mBtnTransport ); if ( !fromSlot ) { mFromAction->setChecked( abs( mShowHeaders )&HDR_FROM ); } rethinkHeaderLine( showHeaders,HDR_FROM, row, mLblFrom, mEdtFrom ); QWidget *prevFocus = mEdtFrom; if ( !fromSlot ) { mReplyToAction->setChecked( abs( mShowHeaders )&HDR_REPLY_TO ); } rethinkHeaderLine( showHeaders, HDR_REPLY_TO, row, mLblReplyTo, mEdtReplyTo ); if ( showHeaders & HDR_REPLY_TO ) { prevFocus = connectFocusMoving( prevFocus, mEdtReplyTo ); } mGrid->addWidget( mComposerBase->recipientsEditor(), row, 0, 1, 3 ); ++row; if ( showHeaders & HDR_REPLY_TO ) { connect( mEdtReplyTo, SIGNAL(focusDown()), mComposerBase->recipientsEditor(), SLOT(setFocusTop()) ); connect( mComposerBase->recipientsEditor(), SIGNAL(focusUp()), mEdtReplyTo, SLOT(setFocus()) ); } else { connect( mEdtFrom, SIGNAL(focusDown()), mComposerBase->recipientsEditor(), SLOT(setFocusTop()) ); connect( mComposerBase->recipientsEditor(), SIGNAL(focusUp()), mEdtFrom, SLOT(setFocus()) ); } connect( mComposerBase->recipientsEditor(), SIGNAL(focusDown()), mEdtSubject, SLOT(setFocus()) ); connect( mEdtSubject, SIGNAL(focusUp()), mComposerBase->recipientsEditor(), SLOT(setFocusBottom()) ); prevFocus = mComposerBase->recipientsEditor(); if ( !fromSlot ) { mSubjectAction->setChecked( abs( mShowHeaders )&HDR_SUBJECT ); } rethinkHeaderLine(showHeaders,HDR_SUBJECT, row, mLblSubject, mEdtSubject ); connectFocusMoving( mEdtSubject, mComposerBase->editor() ); assert( row <= mNumHeaders + 1 ); mHeadersArea->setMaximumHeight( mHeadersArea->sizeHint().height() ); mIdentityAction->setEnabled(!mAllFieldsAction->isChecked()); mDictionaryAction->setEnabled( !mAllFieldsAction->isChecked() ); mTransportAction->setEnabled(!mAllFieldsAction->isChecked()); mFromAction->setEnabled(!mAllFieldsAction->isChecked()); if ( mReplyToAction ) { mReplyToAction->setEnabled( !mAllFieldsAction->isChecked() ); } mFccAction->setEnabled( !mAllFieldsAction->isChecked() ); mSubjectAction->setEnabled( !mAllFieldsAction->isChecked() ); mComposerBase->recipientsEditor()->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, QLabel *aLbl, QWidget *aEdt, QPushButton *aBtn ) { if ( aValue & aMask ) { aLbl->setFixedWidth( mLabelWidth ); aLbl->setBuddy( aEdt ); mGrid->addWidget( aLbl, aRow, 0 ); aEdt->show(); if ( aBtn ) { mGrid->addWidget( aEdt, aRow, 1 ); mGrid->addWidget( aBtn, aRow, 2 ); aBtn->show(); } else { mGrid->addWidget( aEdt, aRow, 1, 1, 2 ); } aRow++; } else { aLbl->hide(); aEdt->hide(); if ( aBtn ) { aBtn->hide(); } } } //----------------------------------------------------------------------------- void KMComposeWin::rethinkHeaderLine( int aValue, int aMask, int &aRow, QLabel *aLbl, QWidget *aCbx, QCheckBox *aChk ) { if ( aValue & aMask ) { aLbl->setBuddy( aCbx ); mGrid->addWidget( aLbl, aRow, 0 ); mGrid->addWidget( aCbx, aRow, 1 ); aCbx->show(); if ( aChk ) { mGrid->addWidget( aChk, aRow, 2 ); aChk->show(); } aRow++; } else { aLbl->hide(); aCbx->hide(); if ( aChk ) { aChk->hide(); } } } //----------------------------------------------------------------------------- void KMComposeWin::applyTemplate( uint uoid, uint uOldId ) { const KPIMIdentities::Identity &ident = kmkernel->identityManager()->identityForUoid( uoid ); if ( ident.isNull() ) return; KMime::Headers::Generic *header = new KMime::Headers::Generic( "X-KMail-Templates", mMsg.get(), ident.templates(), "utf-8" ); mMsg->setHeader( header ); TemplateParser::TemplateParser::Mode mode; switch ( mContext ) { case New: mode = TemplateParser::TemplateParser::NewMessage; break; case Reply: mode = TemplateParser::TemplateParser::Reply; break; case ReplyToAll: mode = TemplateParser::TemplateParser::ReplyAll; break; case Forward: mode = TemplateParser::TemplateParser::Forward; break; default: return; } if ( mode == TemplateParser::TemplateParser::NewMessage ) { TemplateParser::TemplateParser parser( mMsg, mode ); parser.setSelection( mTextSelection ); parser.setAllowDecryption( MessageViewer::GlobalSettings::self()->automaticDecrypt() ); parser.setIdentityManager( KMKernel::self()->identityManager() ); if ( !mCustomTemplate.isEmpty() ) parser.process( mCustomTemplate, mMsg, mCollectionForNewMessage ); else parser.processWithIdentity( uoid, mMsg, mCollectionForNewMessage ); mComposerBase->updateTemplate( mMsg ); updateSignature(uoid, uOldId); return; } if ( mMsg->headerByType( "X-KMail-Link-Message" ) ) { Akonadi::Item::List items; foreach( const QString& serNumStr, mMsg->headerByType( "X-KMail-Link-Message" )->asUnicodeString().split( QLatin1Char(',') ) ) items << Akonadi::Item( serNumStr.toLongLong() ); Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob( items, this ); job->fetchScope().fetchFullPayload( true ); job->fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); job->setProperty( "mode", (int)mode ); job->setProperty( "uoid", uoid ); job->setProperty( "uOldid", uOldId ); connect( job, SIGNAL(result(KJob*)), SLOT(slotDelayedApplyTemplate(KJob*)) ); } } void KMComposeWin::slotDelayedApplyTemplate( KJob *job ) { const Akonadi::ItemFetchJob *fetchJob = qobject_cast( job ); const Akonadi::Item::List items = fetchJob->items(); const TemplateParser::TemplateParser::Mode mode = static_cast( fetchJob->property( "mode" ).toInt() ); const uint uoid = fetchJob->property( "uoid" ).toUInt(); const uint uOldId = fetchJob->property( "uOldid" ).toUInt(); TemplateParser::TemplateParser parser( mMsg, mode ); parser.setSelection( mTextSelection ); parser.setAllowDecryption( MessageViewer::GlobalSettings::self()->automaticDecrypt() ); parser.setWordWrap( MessageComposer::MessageComposerSettings::self()->wordWrap(), MessageComposer::MessageComposerSettings::self()->lineWrapWidth() ); parser.setIdentityManager( KMKernel::self()->identityManager() ); foreach ( const Akonadi::Item &item, items ) { if ( !mCustomTemplate.isEmpty() ) parser.process( mCustomTemplate, MessageCore::Util::message( item ) ); else parser.processWithIdentity( uoid, MessageCore::Util::message( item ) ); } mComposerBase->updateTemplate( mMsg ); updateSignature(uoid, uOldId); } void KMComposeWin::updateSignature(uint uoid, uint uOldId) { const KPIMIdentities::Identity &ident = kmkernel->identityManager()->identityForUoid( uoid ); const KPIMIdentities::Identity &oldIdentity = kmkernel->identityManager()->identityForUoid( uOldId ); mComposerBase->identityChanged( ident, oldIdentity, true ); } void KMComposeWin::setCollectionForNewMessage( const Akonadi::Collection& folder) { mCollectionForNewMessage = folder; } void KMComposeWin::setQuotePrefix( uint uoid ) { QString quotePrefix = mMsg->headerByType( "X-KMail-QuotePrefix" ) ? mMsg->headerByType( "X-KMail-QuotePrefix" )->asUnicodeString() : QString(); if ( quotePrefix.isEmpty() ) { // no quote prefix header, set quote prefix according in identity // TODO port templates to ComposerViewBase if ( mCustomTemplate.isEmpty() ) { const KPIMIdentities::Identity &identity = kmkernel->identityManager()->identityForUoidOrDefault( uoid ); // Get quote prefix from template // ( custom templates don't specify custom quotes prefixes ) TemplateParser::Templates quoteTemplate( TemplateParser::TemplatesConfiguration::configIdString( identity.uoid() ) ); quotePrefix = quoteTemplate.quoteString(); } } mComposerBase->editor()->setQuotePrefixName( MessageCore::StringUtil::formatString( quotePrefix, mMsg->from()->asUnicodeString() ) ); } //----------------------------------------------------------------------------- void KMComposeWin::getTransportMenu() { mActNowMenu->clear(); mActLaterMenu->clear(); const QList transports = TransportManager::self()->transports(); foreach ( Transport *transport, transports ) { const QString name = transport->name().replace( QLatin1Char('&'), QLatin1String("&&") ); QAction *action1 = new QAction( name, mActNowMenu ); QAction *action2 = new QAction( name, mActLaterMenu ); action1->setData( transport->id() ); action2->setData( transport->id() ); mActNowMenu->addAction( action1 ); mActLaterMenu->addAction( action2 ); } } //----------------------------------------------------------------------------- void KMComposeWin::setupActions( void ) { KActionMenu *actActionNowMenu, *actActionLaterMenu; if ( MessageComposer::MessageComposerSettings::self()->sendImmediate() ) { //default = send now, alternative = queue KAction *action = new KAction(KIcon(QLatin1String("mail-send")), i18n("&Send Mail"), this); actionCollection()->addAction(QLatin1String("send_default"), action ); action->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_Return ) ); connect( action, SIGNAL(triggered(bool)), SLOT(slotSendNow())); // FIXME: change to mail_send_via icon when this exist. actActionNowMenu = new KActionMenu( KIcon( QLatin1String("mail-send") ), i18n("&Send Mail Via"), this ); actActionNowMenu->setIconText( i18n( "Send" ) ); actionCollection()->addAction( QLatin1String("send_default_via"), actActionNowMenu ); action = new KAction( KIcon( QLatin1String("mail-queue") ), i18n("Send &Later"), this ); actionCollection()->addAction( QLatin1String("send_alternative"), action ); connect( action, SIGNAL(triggered(bool)), SLOT(slotSendLater()) ); actActionLaterMenu = new KActionMenu( KIcon( QLatin1String("mail-queue") ), i18n("Send &Later Via"), this ); actActionLaterMenu->setIconText( i18nc( "Queue the message for sending at a later date", "Queue" ) ); actionCollection()->addAction( QLatin1String("send_alternative_via"), actActionLaterMenu ); } else { //default = queue, alternative = send now KAction *action = new KAction( KIcon( QLatin1String("mail-queue") ), i18n("Send &Later"), this ); actionCollection()->addAction( QLatin1String("send_default"), action ); connect( action, SIGNAL(triggered(bool)), SLOT(slotSendLater()) ); action->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_Return ) ); actActionLaterMenu = new KActionMenu( KIcon( QLatin1String("mail-queue") ), i18n("Send &Later Via"), this ); actionCollection()->addAction( QLatin1String("send_default_via"), actActionLaterMenu ); action = new KAction( KIcon( QLatin1String("mail-send") ), i18n("&Send Mail"), this ); actionCollection()->addAction( QLatin1String("send_alternative"), action ); connect( action, SIGNAL(triggered(bool)), SLOT(slotSendNow()) ); // FIXME: change to mail_send_via icon when this exits. actActionNowMenu = new KActionMenu( KIcon( QLatin1String("mail-send") ), i18n("&Send Mail Via"), this ); actionCollection()->addAction( QLatin1String("send_alternative_via"), actActionNowMenu ); } // needed for sending "default transport" actActionNowMenu->setDelayed( true ); actActionLaterMenu->setDelayed( true ); connect( actActionNowMenu, SIGNAL(triggered(bool)), this, SLOT(slotSendNow()) ); connect( actActionLaterMenu, SIGNAL(triggered(bool)), this, SLOT(slotSendLater()) ); mActNowMenu = actActionNowMenu->menu(); mActLaterMenu = actActionLaterMenu->menu(); connect( mActNowMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotSendNowVia(QAction*)) ); connect( mActNowMenu, SIGNAL(aboutToShow()), this, SLOT(getTransportMenu()) ); connect( mActLaterMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotSendLaterVia(QAction*)) ); connect( mActLaterMenu, SIGNAL(aboutToShow()), this, SLOT(getTransportMenu()) ); KAction *action = new KAction( KIcon( QLatin1String("document-save") ), i18n("Save as &Draft"), this ); actionCollection()->addAction(QLatin1String("save_in_drafts"), action ); action->setHelpText(i18n("Save email in Draft folder")); action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_S)); connect( action, SIGNAL(triggered(bool)), SLOT(slotSaveDraft()) ); action = new KAction( KIcon( QLatin1String("document-save") ), i18n("Save as &Template"), this ); action->setHelpText(i18n("Save email in Template folder")); actionCollection()->addAction( QLatin1String("save_in_templates"), action ); connect( action, SIGNAL(triggered(bool)), SLOT(slotSaveTemplate()) ); action = new KAction( KIcon( QLatin1String("document-save") ), i18n("Save as &File"), this ); action->setHelpText(i18n("Save email as text or html file")); actionCollection()->addAction( QLatin1String("save_as_file"), action ); connect( action, SIGNAL(triggered(bool)), SLOT(slotSaveAsFile()) ); action = new KAction(KIcon( QLatin1String( "contact-new" ) ), i18n("New AddressBook Contact..."),this); actionCollection()->addAction(QLatin1String("kmail_new_addressbook_contact"), action ); connect(action, SIGNAL(triggered(bool)), this, SLOT(slotCreateAddressBookContact())); action = new KAction(KIcon(QLatin1String("document-open")), i18n("&Insert Text File..."), this); actionCollection()->addAction(QLatin1String("insert_file"), action ); connect(action, SIGNAL(triggered(bool)), SLOT(slotInsertFile())); mRecentAction = new KRecentFilesAction( KIcon( QLatin1String("document-open") ), i18n( "&Insert Recent Text File" ), this ); actionCollection()->addAction(QLatin1String("insert_file_recent"), mRecentAction ); connect(mRecentAction, SIGNAL(urlSelected(KUrl)), SLOT(slotInsertRecentFile(KUrl))); connect(mRecentAction, SIGNAL(recentListCleared()), SLOT(slotRecentListFileClear())); mRecentAction->loadEntries( KMKernel::self()->config()->group( QString() ) ); action = new KAction(KIcon(QLatin1String("x-office-address-book")), i18n("&Address Book"), this); action->setHelpText(i18n("Open Address Book")); actionCollection()->addAction(QLatin1String("addressbook"), action ); if (KStandardDirs::findExe(QLatin1String("kaddressbook")).isEmpty()) action->setEnabled(false); connect(action, SIGNAL(triggered(bool)), SLOT(slotAddrBook())); action = new KAction(KIcon(QLatin1String("mail-message-new")), i18n("&New Composer"), this); actionCollection()->addAction(QLatin1String("new_composer"), action ); connect(action, SIGNAL(triggered(bool)), SLOT(slotNewComposer())); action->setShortcuts( KStandardShortcut::shortcut( KStandardShortcut::New ) ); action = new KAction( KIcon( QLatin1String("window-new") ), i18n("New Main &Window"), this ); actionCollection()->addAction( QLatin1String("open_mailreader"), action ); connect( action, SIGNAL(triggered(bool)), SLOT(slotNewMailReader()) ); action = new KAction( i18n("Select &Recipients..."), this ); actionCollection()->addAction( QLatin1String("select_recipients"), action ); connect( action, SIGNAL(triggered(bool)), mComposerBase->recipientsEditor(), SLOT(selectRecipients()) ); action = new KAction( i18n("Save &Distribution List..."), this ); actionCollection()->addAction( QLatin1String("save_distribution_list"), action ); connect( action, SIGNAL(triggered(bool)), mComposerBase->recipientsEditor(), SLOT(saveDistributionList()) ); KStandardAction::print( this, SLOT(slotPrint()), actionCollection() ); if(KPrintPreview::isAvailable()) KStandardAction::printPreview( this, SLOT(slotPrintPreview()), actionCollection() ); KStandardAction::close( this, SLOT(slotClose()), actionCollection() ); KStandardAction::undo( this, SLOT(slotUndo()), actionCollection() ); KStandardAction::redo( this, SLOT(slotRedo()), actionCollection() ); KStandardAction::cut( this, SLOT(slotCut()), actionCollection() ); KStandardAction::copy( this, SLOT(slotCopy()), actionCollection() ); KStandardAction::pasteText( this, SLOT(slotPaste()), actionCollection() ); mSelectAll = KStandardAction::selectAll( this, SLOT(slotMarkAll()), actionCollection() ); mFindText = KStandardAction::find( mComposerBase->editor(), SLOT(slotFind()), actionCollection() ); mFindNextText = KStandardAction::findNext( mComposerBase->editor(), SLOT(slotFindNext()), actionCollection() ); mReplaceText = KStandardAction::replace( mComposerBase->editor(), SLOT(slotReplace()), actionCollection() ); actionCollection()->addAction( KStandardAction::Spelling, QLatin1String("spellcheck"), mComposerBase->editor(), SLOT(checkSpelling()) ); action = new KAction( i18n("Paste as Attac&hment"), this ); actionCollection()->addAction( QLatin1String("paste_att"), action ); connect( action, SIGNAL(triggered(bool)), SLOT(slotPasteAsAttachment()) ); action = new KAction( i18n("Cl&ean Spaces"), this ); actionCollection()->addAction( QLatin1String("clean_spaces"), action ); connect( action, SIGNAL(triggered(bool)), mComposerBase->signatureController(), SLOT(cleanSpace()) ); mFixedFontAction = new KToggleAction( i18n("Use Fi&xed Font"), this ); actionCollection()->addAction( QLatin1String("toggle_fixedfont"), mFixedFontAction ); connect( mFixedFontAction, SIGNAL(triggered(bool)), SLOT(slotUpdateFont()) ); mFixedFontAction->setChecked( MessageViewer::GlobalSettings::self()->useFixedFont() ); //these are checkable!!! mUrgentAction = new KToggleAction( i18nc("@action:inmenu Mark the email as urgent.","&Urgent"), this ); actionCollection()->addAction( QLatin1String("urgent"), mUrgentAction ); mRequestMDNAction = new KToggleAction( i18n("&Request Disposition Notification"), this ); actionCollection()->addAction(QLatin1String("options_request_mdn"), mRequestMDNAction ); mRequestMDNAction->setChecked(GlobalSettings::self()->requestMDN()); //----- Message-Encoding Submenu mCodecAction = new CodecAction( CodecAction::ComposerMode, this ); actionCollection()->addAction( QLatin1String("charsets"), mCodecAction ); mWordWrapAction = new KToggleAction( i18n( "&Wordwrap" ), this ); actionCollection()->addAction( QLatin1String("wordwrap"), mWordWrapAction ); mWordWrapAction->setChecked( MessageComposer::MessageComposerSettings::self()->wordWrap() ); connect( mWordWrapAction, SIGNAL(toggled(bool)), SLOT(slotWordWrapToggled(bool)) ); mSnippetAction = new KToggleAction( i18n("&Snippets"), this ); actionCollection()->addAction( QLatin1String("snippets"), mSnippetAction ); connect( mSnippetAction, SIGNAL(toggled(bool)), mSnippetWidget, SLOT(setVisible(bool)) ); mSnippetAction->setChecked( GlobalSettings::self()->showSnippetManager() ); mAutoSpellCheckingAction = new KToggleAction( KIcon( QLatin1String("tools-check-spelling") ), i18n("&Automatic Spellchecking"), this ); actionCollection()->addAction( QLatin1String("options_auto_spellchecking"), mAutoSpellCheckingAction ); const bool spellChecking = GlobalSettings::self()->autoSpellChecking(); const bool useKmailEditor = !GlobalSettings::self()->useExternalEditor(); const bool spellCheckingEnabled = useKmailEditor && spellChecking; mAutoSpellCheckingAction->setEnabled( useKmailEditor ); mAutoSpellCheckingAction->setChecked( spellCheckingEnabled ); slotAutoSpellCheckingToggled( spellCheckingEnabled ); connect( mAutoSpellCheckingAction, SIGNAL(toggled(bool)), this, SLOT(slotAutoSpellCheckingToggled(bool)) ); connect( mComposerBase->editor(), SIGNAL(checkSpellingChanged(bool)), this, SLOT(slotAutoSpellCheckingToggled(bool)) ); connect( mComposerBase->editor(), SIGNAL(textModeChanged(KRichTextEdit::Mode)), this, SLOT(slotTextModeChanged(KRichTextEdit::Mode)) ); connect( mComposerBase->editor(), SIGNAL(externalEditorClosed()), this, SLOT(slotExternalEditorClosed())); connect( mComposerBase->editor(), SIGNAL(externalEditorStarted()), this, SLOT(slotExternalEditorStarted())); //these are checkable!!! markupAction = new KToggleAction( i18n("Rich Text Editing"), this ); markupAction->setIcon( KIcon( QLatin1String("preferences-desktop-font" )) ); markupAction->setIconText( i18n("Rich Text") ); markupAction->setToolTip( i18n( "Toggle rich text editing mode" ) ); actionCollection()->addAction( QLatin1String("html"), markupAction ); connect( markupAction, SIGNAL(triggered(bool)), SLOT(slotToggleMarkup()) ); mAllFieldsAction = new KToggleAction( i18n("&All Fields"), this); actionCollection()->addAction( QLatin1String("show_all_fields"), mAllFieldsAction ); connect( mAllFieldsAction, SIGNAL(triggered(bool)), SLOT(slotView())); mIdentityAction = new KToggleAction(i18n("&Identity"), this); actionCollection()->addAction(QLatin1String("show_identity"), mIdentityAction ); connect( mIdentityAction, SIGNAL(triggered(bool)), SLOT(slotView())); mDictionaryAction = new KToggleAction(i18n("&Dictionary"), this); actionCollection()->addAction(QLatin1String("show_dictionary"), mDictionaryAction ); connect( mDictionaryAction, SIGNAL(triggered(bool)), SLOT(slotView())); mFccAction = new KToggleAction(i18n("&Sent-Mail Folder"), this); actionCollection()->addAction(QLatin1String("show_fcc"), mFccAction ); connect( mFccAction, SIGNAL(triggered(bool)), SLOT(slotView())); mTransportAction = new KToggleAction(i18n("&Mail Transport"), this); actionCollection()->addAction(QLatin1String("show_transport"), mTransportAction ); connect( mTransportAction, SIGNAL(triggered(bool)), SLOT(slotView())); mFromAction = new KToggleAction(i18n("&From"), this); actionCollection()->addAction(QLatin1String("show_from"), mFromAction ); connect( mFromAction, SIGNAL(triggered(bool)), SLOT(slotView())); mReplyToAction = new KToggleAction(i18n("&Reply To"), this); actionCollection()->addAction(QLatin1String("show_reply_to"), mReplyToAction ); connect( mReplyToAction, SIGNAL(triggered(bool)), SLOT(slotView())); mSubjectAction = new KToggleAction( i18nc("@action:inmenu Show the subject in the composer window.", "S&ubject"), this); actionCollection()->addAction(QLatin1String("show_subject"), mSubjectAction ); connect(mSubjectAction, SIGNAL(triggered(bool)), SLOT(slotView())); //end of checkable action = new KAction( i18n("Append S&ignature"), this ); actionCollection()->addAction( QLatin1String("append_signature"), action ); connect( action, SIGNAL(triggered(bool)), mComposerBase->signatureController(), SLOT(appendSignature())); action = new KAction( i18n("Pr&epend Signature"), this ); actionCollection()->addAction( QLatin1String("prepend_signature"), action ); connect( action, SIGNAL(triggered(bool)), mComposerBase->signatureController(), SLOT(prependSignature()) ); action = new KAction( i18n("Insert Signature At C&ursor Position"), this ); actionCollection()->addAction( QLatin1String("insert_signature_at_cursor_position"), action ); connect( action, SIGNAL(triggered(bool)), mComposerBase->signatureController(), SLOT(insertSignatureAtCursor()) ); action = new KAction( i18n("Insert Special Character..."), this ); actionCollection()->addAction( QLatin1String("insert_special_character"), action ); connect( action, SIGNAL(triggered(bool)), this, SLOT(insertSpecialCharacter()) ); KAction *upperCase = new KAction( i18n("Uppercase"), this ); actionCollection()->addAction( QLatin1String("change_to_uppercase"), upperCase ); connect( upperCase, SIGNAL(triggered(bool)), this, SLOT(slotUpperCase()) ); KAction *lowerCase = new KAction( i18n("Lowercase"), this ); actionCollection()->addAction( QLatin1String("change_to_lowercase"), lowerCase ); connect( lowerCase, SIGNAL(triggered(bool)), this, SLOT(slotLowerCase()) ); mChangeCaseMenu = new KActionMenu(i18n("Change Case"), this); actionCollection()->addAction(QLatin1String("change_case_menu"), mChangeCaseMenu ); mChangeCaseMenu->addAction(upperCase); mChangeCaseMenu->addAction(lowerCase); mComposerBase->attachmentController()->createActions(); setStandardToolBarMenuEnabled( true ); KStandardAction::keyBindings( this, SLOT(slotEditKeys()), actionCollection()); KStandardAction::configureToolbars( this, SLOT(slotEditToolbars()), actionCollection()); KStandardAction::preferences( kmkernel, SLOT(slotShowConfigurationDialog()), actionCollection() ); action = new KAction( i18n("&Spellchecker..."), this ); action->setIconText( i18n("Spellchecker") ); actionCollection()->addAction( QLatin1String("setup_spellchecker"), action ); connect( action, SIGNAL(triggered(bool)), SLOT(slotSpellcheckConfig()) ); mTranslateAction = mCustomToolsWidget->action(PimCommon::CustomToolsWidget::TranslatorTool); actionCollection()->addAction( QLatin1String("translator"), mTranslateAction ); mGenerateShortenUrl = mCustomToolsWidget->action(PimCommon::CustomToolsWidget::ShortUrlTool); actionCollection()->addAction( QLatin1String("shorten_url"), mGenerateShortenUrl ); //Chiamus not supported in kmail2 #if 0 if ( Kleo::CryptoBackendFactory::instance()->protocol( QLatin1String("Chiasmus") ) ) { KToggleAction *a = new KToggleAction( KIcon( "chiasmus_chi" ), i18n("Encrypt Message with Chiasmus..."), this ); actionCollection()->addAction( "encrypt_message_chiasmus", a ); a->setCheckedState( KGuiItem( i18n( "Encrypt Message with Chiasmus..." ), "chiencrypted" ) ); mEncryptChiasmusAction = a; connect( mEncryptChiasmusAction, SIGNAL(toggled(bool)), this, SLOT(slotEncryptChiasmusToggled(bool)) ); } else { mEncryptChiasmusAction = 0; } #endif mEncryptAction = new KToggleAction(KIcon(QLatin1String("document-encrypt")), i18n("&Encrypt Message"), this); mEncryptAction->setIconText( i18n( "Encrypt" ) ); actionCollection()->addAction(QLatin1String("encrypt_message"), mEncryptAction ); mSignAction = new KToggleAction(KIcon(QLatin1String("document-sign")), i18n("&Sign Message"), this); mSignAction->setIconText( i18n( "Sign" ) ); actionCollection()->addAction(QLatin1String("sign_message"), mSignAction ); const KPIMIdentities::Identity &ident = KMKernel::self()->identityManager()->identityForUoidOrDefault( mComposerBase->identityCombo()->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 = ident.pgpAutoSign(); changeCryptoAction(); connect( mEncryptAction, SIGNAL(triggered(bool)), SLOT(slotEncryptToggled(bool)) ); connect( mSignAction, SIGNAL(triggered(bool)), SLOT(slotSignToggled(bool)) ); QStringList l; for ( int i=0 ; iaddAction(QLatin1String("options_select_crypto"), mCryptoModuleAction ); connect(mCryptoModuleAction, SIGNAL(triggered(int)), SLOT(slotSelectCryptoModule())); mCryptoModuleAction->setItems( l ); mCryptoModuleAction->setToolTip( i18n( "Select a cryptographic format for this message" ) ); mComposerBase->editor()->createActions( actionCollection() ); actionCollection()->addAction( QLatin1String("shared_link"), KMKernel::self()->storageServiceManager()->menuShareLinkServices(this) ); createGUI( QLatin1String("kmcomposerui.rc") ); connect( toolBar( QLatin1String("htmlToolBar") )->toggleViewAction(), SIGNAL(toggled(bool)), 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. QAction *configureAction = actionCollection()->action( QLatin1String("options_configure") ); if ( configureAction ) { configureAction->setText( i18n("Configure KMail..." ) ); } } void KMComposeWin::changeCryptoAction() { const KPIMIdentities::Identity &ident = KMKernel::self()->identityManager()->identityForUoidOrDefault( mComposerBase->identityCombo()->currentIdentity() ); 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 ) && ident.pgpAutoSign() ); } } //----------------------------------------------------------------------------- void KMComposeWin::setupStatusBar( QWidget *w ) { KPIM::ProgressStatusBarWidget * progressStatusBarWidget = new KPIM::ProgressStatusBarWidget(statusBar(), this, PimCommon::StorageServiceProgressManager::progressTypeValue()); statusBar()->addWidget(w); statusBar()->insertItem( QString(), 0, 1 ); statusBar()->setItemAlignment( 0, Qt::AlignLeft | Qt::AlignVCenter ); statusBar()->insertPermanentItem( overwriteModeStr(), 4,0 ); statusBar()->insertPermanentItem( i18n(" Spellcheck: %1 ", QLatin1String( " " )), 3, 0) ; statusBar()->insertPermanentItem( i18n(" Column: %1 ", QLatin1String( " " ) ), 2, 0 ); statusBar()->insertPermanentItem( i18nc("Shows the linenumber of the cursor position.", " Line: %1 " , QLatin1String( " " ) ), 1, 0 ); statusBar()->addPermanentWidget(progressStatusBarWidget->littleProgress()); } //----------------------------------------------------------------------------- void KMComposeWin::setupEditor( void ) { QFontMetrics fm( mBodyFont ); mComposerBase->editor()->setTabStopWidth( fm.width( QLatin1Char(' ') ) * 8 ); slotWordWrapToggled( MessageComposer::MessageComposerSettings::self()->wordWrap() ); // Font setup slotUpdateFont(); connect( mComposerBase->editor(), SIGNAL(cursorPositionChanged()), this, SLOT(slotCursorPositionChanged()) ); slotCursorPositionChanged(); } //----------------------------------------------------------------------------- QString KMComposeWin::subject() const { return MessageComposer::Util::cleanedUpHeaderString( mEdtSubject->toPlainText() ); } //----------------------------------------------------------------------------- QString KMComposeWin::from() const { return MessageComposer::Util::cleanedUpHeaderString( mEdtFrom->text() ); } //----------------------------------------------------------------------------- QString KMComposeWin::replyTo() const { if ( mEdtReplyTo ) { return MessageComposer::Util::cleanedUpHeaderString( mEdtReplyTo->text() ); } else { return QString(); } } //----------------------------------------------------------------------------- void KMComposeWin::decryptOrStripOffCleartextSignature( QByteArray &body ) { QList pgpBlocks; QList 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(); body.append( block.text() ); body.append( nonPgpBlocks.last() ); } } } } void KMComposeWin::setCurrentTransport( int transportId ) { mComposerBase->transportComboBox()->setCurrentTransport( transportId ); } void KMComposeWin::setCurrentReplyTo(const QString& replyTo) { if ( mEdtReplyTo ) { mEdtReplyTo->setText( replyTo ); } } //----------------------------------------------------------------------------- void KMComposeWin::setMessage( const KMime::Message::Ptr &newMsg, bool lastSignState, bool lastEncryptState, bool mayAutoSign, bool allowDecryption, bool isModified ) { if ( !newMsg ) { kDebug() << "newMsg == 0!"; return; } if( lastSignState ) mLastSignActionState = true; if ( lastEncryptState ) mLastEncryptActionState = true; - mComposerBase->setMessage( newMsg ); + mComposerBase->setMessage( newMsg, allowDecryption ); mMsg = newMsg; KPIMIdentities::IdentityManager * im = KMKernel::self()->identityManager(); mEdtFrom->setText( mMsg->from()->asUnicodeString() ); mEdtSubject->setText( mMsg->subject()->asUnicodeString() ); // Restore the quote prefix. We can't just use the global quote prefix here, // since the prefix is different for each message, it might for example depend // on the original sender in a reply. if ( mMsg->headerByType( "X-KMail-QuotePrefix" ) ) mComposerBase->editor()->setQuotePrefixName( mMsg->headerByType( "X-KMail-QuotePrefix" )->asUnicodeString() ); const bool stickyIdentity = mBtnIdentity->isChecked() && !mIgnoreStickyFields; bool messageHasIdentity = false; if( newMsg->headerByType("X-KMail-Identity") && !newMsg->headerByType("X-KMail-Identity")->asUnicodeString().isEmpty() ) messageHasIdentity = true; if ( !stickyIdentity && messageHasIdentity ) mId = newMsg->headerByType( "X-KMail-Identity" )->asUnicodeString().toUInt(); // don't overwrite the header values with identity specific values // unless the identity is sticky if ( !stickyIdentity ) { disconnect( mComposerBase->identityCombo(),SIGNAL(identityChanged(uint)), this, SLOT(slotIdentityChanged(uint)) ) ; } // load the mId into the gui, sticky or not, without emitting mComposerBase->identityCombo()->setCurrentIdentity( mId ); const uint idToApply = mId; if ( !stickyIdentity ) { connect( mComposerBase->identityCombo(),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->headerByType("X-KMail-Identity")->asUnicodeString().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, true /*initalChange*/ ); const KPIMIdentities::Identity &ident = im->identityForUoid( mComposerBase->identityCombo()->currentIdentity() ); const bool stickyTransport = mBtnTransport->isChecked() && !mIgnoreStickyFields; if( stickyTransport ) { mComposerBase->transportComboBox()->setCurrentTransport( ident.transport().toInt() ); } // TODO move the following to ComposerViewBase // however, requires the actions to be there as well in order to share with mobile client // check for the presence of a DNT header, indicating that MDN's were requested if( newMsg->headerByType( "Disposition-Notification-To" ) ) { QString mdnAddr = newMsg->headerByType( "Disposition-Notification-To" )->asUnicodeString(); mRequestMDNAction->setChecked( ( !mdnAddr.isEmpty() && im->thatIsMe( mdnAddr ) ) || GlobalSettings::self()->requestMDN() ); } // check for presence of a priority header, indicating urgent mail: if ( newMsg->headerByType( "X-PRIORITY" ) && newMsg->headerByType("Priority" ) ) { const QString xpriority = newMsg->headerByType( "X-PRIORITY" )->asUnicodeString(); const QString priority = newMsg->headerByType( "Priority" )->asUnicodeString(); if ( xpriority == QLatin1String( "2 (High)" ) && priority == QLatin1String( "urgent" ) ) mUrgentAction->setChecked( true ); } if ( !ident.isXFaceEnabled() || ident.xface().isEmpty() ) { if( mMsg->headerByType( "X-Face" ) ) mMsg->headerByType( "X-Face" )->clear(); } 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, QLatin1String("\n\t") ); } mMsg->setHeader( new KMime::Headers::Generic( "X-Face", mMsg.get(), xface, "utf-8" ) ); } } // if these headers are present, the state of the message should be overruled if ( mMsg->headerByType( "X-KMail-SignatureActionEnabled" ) ) mLastSignActionState = (mMsg->headerByType( "X-KMail-SignatureActionEnabled" )->as7BitString().contains( "true" )); if ( mMsg->headerByType( "X-KMail-EncryptActionEnabled" ) ) mLastEncryptActionState = (mMsg->headerByType( "X-KMail-EncryptActionEnabled" )->as7BitString().contains( "true") ); if ( mMsg->headerByType( "X-KMail-CryptoMessageFormat" ) ) { mCryptoModuleAction->setCurrentItem( format2cb( static_cast( mMsg->headerByType( "X-KMail-CryptoMessageFormat" )->asUnicodeString().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(); QString kmailFcc; if ( mMsg->headerByType( "X-KMail-Fcc" ) ) { kmailFcc = mMsg->headerByType( "X-KMail-Fcc" )->asUnicodeString(); } if ( !mBtnFcc->isChecked() ) { if ( kmailFcc.isEmpty() ) { setFcc( ident.fcc() ); } else setFcc( kmailFcc ); } const bool stickyDictionary = mBtnDictionary->isChecked() && !mIgnoreStickyFields; if ( !stickyDictionary ) { mDictionaryCombo->setCurrentByDictionaryName( ident.dictionary() ); } mEdtReplyTo->setText( mMsg->replyTo()->asUnicodeString() ); KMime::Content *msgContent = new KMime::Content; msgContent->setContent( mMsg->encodedContent() ); msgContent->parse(); MessageViewer::EmptySource emptySource; MessageViewer::ObjectTreeParser otp( &emptySource );//All default are ok emptySource.setAllowDecryption( allowDecryption ); otp.parseObjectTree( msgContent ); bool shouldSetCharset = false; if ( ( mContext == Reply || mContext == ReplyToAll || mContext == Forward ) && MessageComposer::MessageComposerSettings::forceReplyCharset() ) shouldSetCharset = true; if ( shouldSetCharset && !otp.plainTextContentCharset().isEmpty() ) mOriginalPreferredCharset = otp.plainTextContentCharset(); // always set auto charset, but prefer original when composing if force reply is set. setAutoCharset(); delete msgContent; #if 0 //TODO port to kmime /* Handle the special case of non-mime mails */ if ( mMsg->numBodyParts() == 0 && otp.textualContent().isEmpty() ) { mCharset=mMsg->charset(); if ( mCharset.isEmpty() || mCharset == "default" ) { mCharset = Util::defaultCharset(); } QByteArray bodyDecoded = mMsg->bodyDecoded(); if ( allowDecryption ) { decryptOrStripOffCleartextSignature( bodyDecoded ); } const QTextCodec *codec = KMail::Util::codecForName( mCharset ); if ( codec ) { mEditor->setText( codec->toUnicode( bodyDecoded ) ); } else { mEditor->setText( QString::fromLocal8Bit( bodyDecoded ) ); } } #endif if( (MessageComposer::MessageComposerSettings::self()->autoTextSignature()==QLatin1String( "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. // if ( MessageComposer::MessageComposerSettings::self()->prependSignature() ) { QTimer::singleShot( 0, mComposerBase->signatureController(), SLOT(prependSignature()) ); } else { QTimer::singleShot( 0, mComposerBase->signatureController(), SLOT(appendSignature()) ); } } else { mComposerBase->editor()->startExternalEditor(); } setModified( isModified ); // honor "keep reply in this folder" setting even when the identity is changed later on mPreventFccOverwrite = ( !kmailFcc.isEmpty() && ident.fcc() != kmailFcc ); QTimer::singleShot( 0, this, SLOT(forceAutoSaveMessage()) ); //Force autosaving to make sure this composer reappears if a crash happens before the autosave timer kicks in. } void KMComposeWin::setAutoSaveFileName(const QString& fileName) { mComposerBase->setAutoSaveFileName( fileName ); } //----------------------------------------------------------------------------- void KMComposeWin::setTextSelection( const QString& selection ) { mTextSelection = selection; } //----------------------------------------------------------------------------- void KMComposeWin::setCustomTemplate( const QString& customTemplate ) { mCustomTemplate = customTemplate; } //----------------------------------------------------------------------------- void KMComposeWin::setFcc( const QString &idString ) { // check if the sent-mail folder still exists Akonadi::Collection col; if ( idString.isEmpty() ) col = CommonKernel->sentCollectionFolder(); else col = Akonadi::Collection( idString.toLongLong() ); mComposerBase->setFcc( col ); mFccFolder->setCollection( col ); } bool KMComposeWin::isComposerModified() const { return ( mComposerBase->editor()->document()->isModified() || mEdtFrom->isModified() || ( mEdtReplyTo && mEdtReplyTo->isModified() ) || mComposerBase->recipientsEditor()->isModified() || mEdtSubject->document()->isModified() ); } //----------------------------------------------------------------------------- bool KMComposeWin::isModified() const { return mWasModified || isComposerModified(); } //----------------------------------------------------------------------------- void KMComposeWin::setModified( bool modified ) { mWasModified = modified; changeModifiedState( modified ); } void KMComposeWin::changeModifiedState( bool modified ) { mComposerBase->editor()->document()->setModified( modified ); if ( !modified ) { mEdtFrom->setModified( false ); if ( mEdtReplyTo ) mEdtReplyTo->setModified( false ); mComposerBase->recipientsEditor()->clearModified(); mEdtSubject->document()->setModified( false ); } } //----------------------------------------------------------------------------- bool KMComposeWin::queryClose () { if ( !mComposerBase->editor()->checkExternalEditorFinished() ) { return false; } if ( kmkernel->shuttingDown() || kapp->sessionSaving() ) { return true; } if ( isModified() ) { const bool istemplate = ( mFolder.isValid() && CommonKernel->folderIsTemplates( mFolder ) ); 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, QLatin1String("document-save"), QString(), savetext), KStandardGuiItem::discard(), KStandardGuiItem::cancel()); 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; } //else fall through: return true } mComposerBase->cleanupAutoSave(); if( !mMiscComposers.isEmpty() ) { kWarning() << "Tried to close while composer was active"; return false; } return true; } //----------------------------------------------------------------------------- MessageComposer::ComposerViewBase::MissingAttachment KMComposeWin::userForgotAttachment() { bool checkForForgottenAttachments = mCheckForForgottenAttachments && GlobalSettings::self()->showForgottenAttachmentWarning(); if ( !checkForForgottenAttachments ) return MessageComposer::ComposerViewBase::NoMissingAttachmentFound; mComposerBase->setSubject( subject() ); //be sure the composer knows the subject MessageComposer::ComposerViewBase::MissingAttachment missingAttachments = mComposerBase->checkForMissingAttachments( GlobalSettings::self()->attachmentKeywords() ); return missingAttachments; } void KMComposeWin::forceAutoSaveMessage() { autoSaveMessage( true ); } void KMComposeWin::autoSaveMessage(bool force) { if ( isComposerModified() || force ) { applyComposerSetting( mComposerBase ); mComposerBase->autoSaveMessage(); if ( !force ) { mWasModified = true; changeModifiedState( false ); } } else { mComposerBase->updateAutoSave(); } } bool KMComposeWin::encryptToSelf() { // return !Kpgp::Module::getKpgp() || Kpgp::Module::getKpgp()->encryptToSelf(); return MessageComposer::MessageComposerSettings::self()->cryptoEncryptToSelf(); } void KMComposeWin::slotSendFailed( const QString& msg,MessageComposer::ComposerViewBase::FailedType type) { // setModified( false ); setEnabled( true ); KMessageBox::sorry( mMainWidget, msg, (type == MessageComposer::ComposerViewBase::AutoSave) ? i18n( "Autosave Message Failed" ) : i18n( "Sending Message Failed" ) ); } void KMComposeWin::slotSendSuccessful() { setModified( false ); mComposerBase->cleanupAutoSave(); mFolder = Akonadi::Collection(); // see dtor close(); } const KPIMIdentities::Identity &KMComposeWin::identity() const { return KMKernel::self()->identityManager()->identityForUoidOrDefault( mComposerBase->identityCombo()->currentIdentity() ); } Kleo::CryptoMessageFormat KMComposeWin::cryptoMessageFormat() const { if ( !mCryptoModuleAction ) { return Kleo::AutoFormat; } return cb2format( mCryptoModuleAction->currentItem() ); } //----------------------------------------------------------------------------- void KMComposeWin::addAttach( KMime::Content *msgPart ) { mComposerBase->addAttachmentPart( msgPart ); setModified( true ); } //----------------------------------------------------------------------------- QString KMComposeWin::prettyMimeType( const QString &type ) { const QString t = type.toLower(); const KMimeType::Ptr st = KMimeType::mimeType( t ); if ( !st ) { kWarning() <<"unknown mimetype" << t; return t; } const QString pretty = !st->isDefault() ? st->comment() : t; if ( pretty.isEmpty() ) return type; else return pretty; } void KMComposeWin::setAutoCharset() { mCodecAction->setCurrentItem( 0 ); } // We can't simply use KCodecAction::setCurrentCodec(), since that doesn't // use fixEncoding(). static QString selectCharset( KSelectAction *root, const QString &encoding ) { foreach( QAction *action, root->actions() ) { KSelectAction *subMenu = dynamic_cast( action ); if ( subMenu ) { const QString codecNameToSet = selectCharset( subMenu, encoding ); if ( !codecNameToSet.isEmpty() ) return codecNameToSet; } else { const QString fixedActionText = MessageViewer::NodeHelper::fixEncoding( action->text() ); if ( KGlobal::charsets()->codecForName( KGlobal::charsets()->encodingForName( fixedActionText ) ) == KGlobal::charsets()->codecForName( encoding ) ) { return action->text(); } } } return QString(); } //----------------------------------------------------------------------------- void KMComposeWin::setCharset( const QByteArray &charset ) { const QString codecNameToSet = selectCharset( mCodecAction, QString::fromLatin1(charset) ); if ( codecNameToSet.isEmpty() ) { kWarning() << "Could not find charset" << charset; setAutoCharset(); } else mCodecAction->setCurrentCodec( codecNameToSet ); } //----------------------------------------------------------------------------- void KMComposeWin::slotAddrBook() { KRun::runCommand(QLatin1String("kaddressbook"), window()); } //----------------------------------------------------------------------------- void KMComposeWin::slotInsertFile() { KUrl u = mComposerBase->editor()->insertFile(); if ( u.isEmpty() ) return; mRecentAction->addUrl( u ); // Prevent race condition updating list when multiple composers are open { const QString encoding = MessageViewer::NodeHelper::encodingForName( u.fileEncoding() ); QStringList urls = GlobalSettings::self()->recentUrls(); QStringList encodings = GlobalSettings::self()->recentEncodings(); // Prevent config file from growing without bound // Would be nicer to get this constant from KRecentFilesAction const int mMaxRecentFiles = 30; while ( urls.count() > mMaxRecentFiles ) urls.removeLast(); while ( encodings.count() > mMaxRecentFiles ) encodings.removeLast(); // sanity check if ( urls.count() != encodings.count() ) { urls.clear(); encodings.clear(); } urls.prepend( u.prettyUrl() ); encodings.prepend( encoding ); GlobalSettings::self()->setRecentUrls( urls ); GlobalSettings::self()->setRecentEncodings( encodings ); mRecentAction->saveEntries( KMKernel::self()->config()->group( QString() ) ); } slotInsertRecentFile( u ); } void KMComposeWin::slotRecentListFileClear() { KSharedConfig::Ptr config = KMKernel::self()->config(); KConfigGroup group( config, "Composer" ); group.deleteEntry("recent-urls"); group.deleteEntry("recent-encodings"); mRecentAction->saveEntries( config->group( QString() ) ); } //----------------------------------------------------------------------------- void KMComposeWin::slotInsertRecentFile( const KUrl &u ) { if ( u.fileName().isEmpty() ) { return; } // Get the encoding previously used when inserting this file QString encoding; const QStringList urls = GlobalSettings::self()->recentUrls(); const QStringList encodings = GlobalSettings::self()->recentEncodings(); const int index = urls.indexOf( u.prettyUrl() ); if ( index != -1 ) { encoding = encodings[ index ]; } else { kDebug()<<" encoding not found so we can't insert text"; //see InsertTextFileJob return; } MessageComposer::InsertTextFileJob *job = new MessageComposer::InsertTextFileJob( mComposerBase->editor(), u ); job->setEncoding( encoding ); job->start(); // Don't care about the result for now // TODO: we should probably show an error message if it fails... } //----------------------------------------------------------------------------- void KMComposeWin::slotSelectCryptoModule( bool init ) { if ( !init ) setModified( true ); mComposerBase->attachmentModel()->setEncryptEnabled( canSignEncryptAttachments() ); mComposerBase->attachmentModel()->setSignEnabled( canSignEncryptAttachments() ); } //----------------------------------------------------------------------------- void KMComposeWin::slotUpdateFont() { kDebug(); if ( !mFixedFontAction ) { return; } mComposerBase->editor()->setFontForWholeText( mFixedFontAction->isChecked() ? mFixedFont : mBodyFont ); } QString KMComposeWin::smartQuote( const QString & msg ) { return MessageCore::StringUtil::smartQuote( msg, MessageComposer::MessageComposerSettings::self()->lineWrapWidth() ); } bool KMComposeWin::insertFromMimeData( const QMimeData *source, bool forceAttachment ) { // If this is a PNG image, either add it as an attachment or as an inline image if ( source->hasImage() && source->hasFormat( QLatin1String("image/png") ) ) { // Get the image data before showing the dialog, since that processes events which can delete // the QMimeData object behind our back const QByteArray imageData = source->data( QLatin1String("image/png") ); if ( imageData.isEmpty() ) { return true; } if ( !forceAttachment ) { if ( mComposerBase->editor()->textMode() == KRichTextEdit::Rich && mComposerBase->editor()->isEnableImageActions() ) { QImage image = qvariant_cast( source->imageData() ); QFileInfo fi( source->text() ); KMenu menu; const QAction *addAsInlineImageAction = menu.addAction( i18n("Add as &Inline Image") ); /*const QAction *addAsAttachmentAction = */menu.addAction( i18n("Add as &Attachment") ); const QAction *selectedAction = menu.exec( QCursor::pos() ); if ( selectedAction == addAsInlineImageAction ) { // Let the textedit from kdepimlibs handle inline images mComposerBase->editor()->insertImage( image, fi ); return true; } else if( !selectedAction ) { return true; } // else fall through } } // Ok, when we reached this point, the user wants to add the image as an attachment. // Ask for the filename first. bool ok; const QString attName = KInputDialog::getText( i18n("KMail"), i18n( "Name of the attachment:" ), QString(), &ok, this ); if ( !ok ) { return true; } addAttachment( attName, KMime::Headers::CEbase64, QString(), imageData, "image/png" ); return true; } // If this is a URL list, add those files as attachments or text const KUrl::List urlList = KUrl::List::fromMimeData( source ); if ( !urlList.isEmpty() ) { //Search if it's message items. Akonadi::Item::List items; Akonadi::Collection::List collections; bool allLocalURLs = true; foreach ( const KUrl &url, urlList ) { if ( !url.isLocalFile() ) { allLocalURLs = false; } const Akonadi::Item item = Akonadi::Item::fromUrl( url ); if ( item.isValid() ) { items << item; } else { const Akonadi::Collection collection = Akonadi::Collection::fromUrl( url ); if ( collection.isValid() ) collections << collection; } } if ( items.isEmpty() && collections.isEmpty() ) { if ( allLocalURLs || forceAttachment ) { foreach( const KUrl &url, urlList ) { addAttachment( url, QString() ); } } else { KMenu p; const QAction *addAsTextAction = p.addAction( i18np("Add URL into Message", "Add URLs into Message", urlList.size() ) ); const QAction *addAsAttachmentAction = p.addAction( i18np("Add File as &Attachment", "Add Files as &Attachment", urlList.size() ) ); const QAction *selectedAction = p.exec( QCursor::pos() ); if ( selectedAction == addAsTextAction ) { foreach( const KUrl &url, urlList ) { mComposerBase->editor()->insertLink(url.url()); } } else if ( selectedAction == addAsAttachmentAction ) { foreach( const KUrl &url, urlList ) { addAttachment( url, QString() ); } } } return true; } else { if ( !items.isEmpty() ){ Akonadi::ItemFetchJob *itemFetchJob = new Akonadi::ItemFetchJob( items, this ); itemFetchJob->fetchScope().fetchFullPayload( true ); itemFetchJob->fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); connect( itemFetchJob, SIGNAL(result(KJob*)), this, SLOT(slotFetchJob(KJob*)) ); } if ( !collections.isEmpty() ) { //TODO } return true; } } return false; } void KMComposeWin::slotPasteAsAttachment() { const QMimeData *mimeData = QApplication::clipboard()->mimeData(); if ( insertFromMimeData( mimeData, true ) ) return; if( mimeData->hasText() ) { bool ok; const QString attName = KInputDialog::getText( i18n( "Insert clipboard text as attachment" ), i18n( "Name of the attachment:" ), QString(), &ok, this ); if( ok ) { mComposerBase->addAttachment( attName, attName, QLatin1String("utf-8"), QApplication::clipboard()->text().toUtf8(), "text/plain" ); } return; } } void KMComposeWin::slotFetchJob(KJob*job) { if ( job->error() ) { if ( static_cast(job)->ui() ) static_cast(job)->ui()->showErrorMessage(); else kDebug()<<" job->errorString() :"<errorString(); return; } Akonadi::ItemFetchJob *fjob = dynamic_cast( job ); if ( !fjob ) return; const Akonadi::Item::List items = fjob->items(); if ( items.isEmpty() ) return; if ( items.first().mimeType() == KMime::Message::mimeType() ) { uint identity = 0; if ( items.at( 0 ).isValid() && items.at( 0 ).parentCollection().isValid() ) { QSharedPointer fd( MailCommon::FolderCollection::forCollection( items.at( 0 ).parentCollection(), false ) ); if ( fd ) identity = fd->identity(); } KMCommand *command = new KMForwardAttachedCommand( this, items,identity, this ); command->start(); } else { foreach ( const Akonadi::Item &item, items ) { QString attachmentName = QLatin1String( "attachment" ); if ( item.hasPayload() ) { const KABC::Addressee contact = item.payload(); attachmentName = contact.realName() + QLatin1String( ".vcf" ); //Workaround about broken kaddressbook fields. QByteArray data = item.payloadData(); data.replace("X-messaging/aim-All",("X-AIM")); data.replace("X-messaging/icq-All",("X-ICQ")); data.replace("X-messaging/xmpp-All",("X-JABBER")); data.replace("X-messaging/msn-All",("X-MSN")); data.replace("X-messaging/yahoo-All",("X-YAHOO")); data.replace("X-messaging/gadu-All",("X-GADUGADU")); data.replace("X-messaging/skype-All",("X-SKYPE")); data.replace("X-messaging/groupwise-All",("X-GROUPWISE")); data.replace(("X-messaging/sms-All"),("X-SMS")); data.replace(("X-messaging/meanwhile-All"),("X-MEANWHILE")); data.replace(("X-messaging/irc-All"),("X-IRC")); data.replace(("X-messaging/googletalk-All"),("X-GOOGLETALK")); addAttachment( attachmentName, KMime::Headers::CEbase64, QString(), data, item.mimeType().toLatin1() ); } else { addAttachment( attachmentName, KMime::Headers::CEbase64, QString(), item.payloadData(), item.mimeType().toLatin1() ); } } } } QString KMComposeWin::addQuotesToText( const QString &inputText ) const { QString answer( inputText ); const QString indentStr = mComposerBase->editor()->quotePrefixName(); answer.replace( QLatin1Char('\n'), QLatin1Char('\n') + indentStr ); answer.prepend( indentStr ); answer += QLatin1Char('\n'); return MessageCore::StringUtil::smartQuote( answer, MessageComposer::MessageComposerSettings::self()->lineWrapWidth() ); } //----------------------------------------------------------------------------- void KMComposeWin::slotUndo() { QWidget *fw = focusWidget(); if ( !fw ) { return; } if (::qobject_cast( fw )) { static_cast( fw )->undo(); }else if ( ::qobject_cast( fw ) ) { static_cast( fw )->undo(); } else if (::qobject_cast( fw )) { static_cast( fw )->undo(); } } void KMComposeWin::slotRedo() { QWidget *fw = focusWidget(); if ( !fw ) { return; } if (::qobject_cast( fw )) { static_cast( fw )->redo(); } else if ( ::qobject_cast( fw ) ) { static_cast( fw )->redo(); } else if (::qobject_cast( fw )) { static_cast( fw )->redo(); } } //----------------------------------------------------------------------------- void KMComposeWin::slotCut() { QWidget *fw = focusWidget(); if ( !fw ) { return; } if ( ::qobject_cast( fw ) ) { static_cast( fw )->cut(); } else if ( ::qobject_cast( fw ) ) { static_cast(fw)->cut(); } else if ( ::qobject_cast( fw ) ) { static_cast( fw )->cut(); } } //----------------------------------------------------------------------------- void KMComposeWin::slotCopy() { QWidget *fw = focusWidget(); if ( !fw ) { return; } if ( ::qobject_cast( fw ) ) { static_cast( fw )->copy(); } else if ( ::qobject_cast( fw ) ) { static_cast(fw)->copy(); } else if ( ::qobject_cast( fw ) ) { static_cast( fw )->copy(); } } //----------------------------------------------------------------------------- void KMComposeWin::slotPaste() { QWidget * const fw = focusWidget(); if ( !fw ) { return; } if ( ::qobject_cast( fw ) ) { static_cast( fw )->paste(); } else if ( ::qobject_cast( fw ) ) { static_cast(fw)->paste(); } else if ( ::qobject_cast( fw ) ) { static_cast( fw )->paste(); } } //----------------------------------------------------------------------------- void KMComposeWin::slotMarkAll() { QWidget *fw = focusWidget(); if ( !fw ) { return; } if (::qobject_cast( fw )) { static_cast( fw )->selectAll(); } else if ( ::qobject_cast( fw ) ) { static_cast( fw )->selectAll(); } else if (::qobject_cast( fw )) { static_cast( fw )->selectAll(); } } //----------------------------------------------------------------------------- void KMComposeWin::slotClose() { close(); } //----------------------------------------------------------------------------- void KMComposeWin::slotNewComposer() { KMComposeWin *win; KMime::Message::Ptr msg( new KMime::Message ); MessageHelper::initHeader( msg, KMKernel::self()->identityManager() ); win = new KMComposeWin( msg, false, false ); win->show(); } //----------------------------------------------------------------------------- void KMComposeWin::slotNewMailReader() { KMMainWin *kmmwin = new KMMainWin( 0 ); kmmwin->show(); } //----------------------------------------------------------------------------- void KMComposeWin::slotUpdWinTitle() { QString s( mEdtSubject->toPlainText() ); // Remove characters that show badly in most window decorations: // newlines tend to become boxes. if ( s.isEmpty() ) { setCaption( QLatin1Char('(') + i18n("unnamed") + QLatin1Char(')') ); } else { setCaption( s.replace( QLatin1Char('\n'), QLatin1Char(' ') ) ); } } //----------------------------------------------------------------------------- void KMComposeWin::slotEncryptToggled( bool on ) { setEncryption( on, true ); slotUpdateSignatureAndEncrypionStateIndicators(); } //----------------------------------------------------------------------------- void KMComposeWin::setEncryption( bool encrypt, bool setByUser ) { bool wasModified = isModified(); 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("

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.

" "

Please select the key(s) to use " "in the identity configuration.

" "
"), i18n("Undefined Encryption Key") ); setModified( wasModified ); } encrypt = false; } // make sure the mEncryptAction is in the right state mEncryptAction->setChecked( encrypt ); if(!setByUser) { slotUpdateSignatureAndEncrypionStateIndicators(); } // show the appropriate icon if ( encrypt ) { mEncryptAction->setIcon( KIcon( QLatin1String("document-encrypt") ) ); } else { mEncryptAction->setIcon( KIcon( QLatin1String("document-decrypt") ) ); } // mark the attachments for (no) encryption if( canSignEncryptAttachments() ) { mComposerBase->attachmentModel()->setEncryptSelected( encrypt ); } } //----------------------------------------------------------------------------- void KMComposeWin::slotSignToggled( bool on ) { setSigning( on, true ); slotUpdateSignatureAndEncrypionStateIndicators(); } //----------------------------------------------------------------------------- void KMComposeWin::setSigning( bool sign, bool setByUser ) { bool wasModified = isModified(); 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("

In order to be able to sign " "this message you first have to " "define the (OpenPGP or S/MIME) signing key " "to use.

" "

Please select the key to use " "in the identity configuration.

" "
"), i18n("Undefined Signing Key") ); setModified( wasModified ); } sign = false; } // make sure the mSignAction is in the right state mSignAction->setChecked( sign ); if(!setByUser) { slotUpdateSignatureAndEncrypionStateIndicators(); } // mark the attachments for (no) signing if ( canSignEncryptAttachments() ) { mComposerBase->attachmentModel()->setSignSelected( sign ); } } //----------------------------------------------------------------------------- void KMComposeWin::slotWordWrapToggled( bool on ) { if ( on ) mComposerBase->editor()->enableWordWrap( MessageComposer::MessageComposerSettings::self()->lineWrapWidth() ); else disableWordWrap(); } //----------------------------------------------------------------------------- void KMComposeWin::disableWordWrap() { mComposerBase->editor()->disableWordWrap(); } //----------------------------------------------------------------------------- void KMComposeWin::forceDisableHtml() { mForceDisableHtml = true; disableHtml( MessageComposer::ComposerViewBase::NoConfirmationNeeded ); markupAction->setEnabled( false ); // FIXME: Remove the toggle toolbar action somehow } void KMComposeWin::disableForgottenAttachmentsCheck() { mCheckForForgottenAttachments = false; } void KMComposeWin::ignoreStickyFields() { mIgnoreStickyFields = true; mBtnTransport->setChecked( false ); mBtnDictionary->setChecked( false ); mBtnIdentity->setChecked( false ); mBtnTransport->setEnabled( false ); mBtnDictionary->setEnabled( false ); mBtnIdentity->setEnabled( false ); } void KMComposeWin::slotPrint() { printComposer(false); } void KMComposeWin::slotPrintPreview() { printComposer(true); } void KMComposeWin::printComposer(bool preview) { MessageComposer::Composer* composer = createSimpleComposer(); mMiscComposers.append( composer ); composer->setProperty("preview",preview); connect( composer, SIGNAL(result(KJob*)), this, SLOT(slotPrintComposeResult(KJob*)) ); composer->start(); } void KMComposeWin::slotPrintComposeResult( KJob *job ) { const bool preview = job->property("preview").toBool(); printComposeResult( job, preview ); } void KMComposeWin::printComposeResult( KJob *job, bool preview ) { Q_ASSERT( dynamic_cast< MessageComposer::Composer* >( job ) ); MessageComposer::Composer* composer = dynamic_cast< MessageComposer::Composer* >( job ); Q_ASSERT( mMiscComposers.contains( composer ) ); mMiscComposers.removeAll( composer ); if( composer->error() == MessageComposer::Composer::NoError ) { Q_ASSERT( composer->resultMessages().size() == 1 ); Akonadi::Item printItem; printItem.setPayload( composer->resultMessages().first() ); const bool isHtml = ( mComposerBase->editor()->textMode() == KMeditor::Rich ); KMPrintCommand *command = new KMPrintCommand( this, printItem,0, 0, isHtml, isHtml ); command->setPrintPreview( preview ); command->start(); } else { if ( static_cast(job)->ui() ) { static_cast(job)->ui()->showErrorMessage(); } else { kWarning() << "Composer for printing failed:" << composer->errorString(); } } } //---------------------------------------------------------------------------- void KMComposeWin::doSend( MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn ) { if (mNumProgressUploadFile > 0) { KMessageBox::sorry( this, i18np( "There is %1 file upload in progress.", "There are %1 file uploads in progress.", mNumProgressUploadFile ) ); return; } // TODO integrate with MDA online status if ( method == MessageComposer::MessageSender::SendImmediate ) { if( !MessageComposer::Util::sendMailDispatcherIsOnline() ) { method = MessageComposer::MessageSender::SendLater; } } if ( saveIn == MessageComposer::MessageSender::SaveInNone ) { // don't save as draft or template, send immediately if ( KPIMUtils::firstEmailAddress( 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 ( mComposerBase->to().isEmpty() ) { if ( mComposerBase->cc().isEmpty() && mComposerBase->bcc().isEmpty() ) { KMessageBox::information( this, i18n("You must specify at least one receiver, " "either in the To: field or as CC or as BCC.") ); return; } else { int rc = KMessageBox::questionYesNo( this, i18n("To: field is empty. " "Send message anyway?"), i18n("No To: specified"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QLatin1String(":kmail_no_to_field_specified") ); if ( rc == KMessageBox::No ) { 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"), KGuiItem(i18n("S&end as Is")), KGuiItem(i18n("&Specify the Subject")), QLatin1String("no_subject_specified") ); if ( rc == KMessageBox::No ) { return; } } const MessageComposer::ComposerViewBase::MissingAttachment forgotAttachment = userForgotAttachment(); if ( (forgotAttachment == MessageComposer::ComposerViewBase::FoundMissingAttachmentAndAddedAttachment) || (forgotAttachment == MessageComposer::ComposerViewBase::FoundMissingAttachmentAndCancel) ) { return; } setEnabled( false ); // Validate the To:, CC: and BCC fields const QStringList recipients = QStringList() << mComposerBase->to().trimmed() << mComposerBase->cc().trimmed() << mComposerBase->bcc().trimmed(); AddressValidationJob *job = new AddressValidationJob( recipients.join( QLatin1String( ", ") ), this, this ); job->setProperty( "method", static_cast( method ) ); job->setProperty( "saveIn", static_cast( saveIn ) ); connect( job, SIGNAL(result(KJob*)), SLOT(slotDoDelayedSend(KJob*)) ); job->start(); // we'll call send from within slotDoDelaySend } else { if( saveIn == MessageComposer::MessageSender::SaveInDrafts && mEncryptAction->isChecked() && !GlobalSettings::self()->neverEncryptDrafts() && mComposerBase->to().isEmpty() && mComposerBase->cc().isEmpty() ) { KMessageBox::information( this, i18n("You must specify at least one receiver " "in order to be able to encrypt a draft.") ); return; } doDelayedSend( method, saveIn ); } } void KMComposeWin::slotDoDelayedSend( KJob *job ) { if ( job->error() ) { KMessageBox::error( this, job->errorText() ); setEnabled(true); return; } const AddressValidationJob *validateJob = qobject_cast( job ); // Abort sending if one of the recipient addresses is invalid ... if ( !validateJob->isValid() ) { setEnabled(true); return; } // ... otherwise continue as usual const MessageComposer::MessageSender::SendMethod method = static_cast( job->property( "method" ).toInt() ); const MessageComposer::MessageSender::SaveIn saveIn = static_cast( job->property( "saveIn" ).toInt() ); doDelayedSend( method, saveIn ); } void KMComposeWin::applyComposerSetting( MessageComposer::ComposerViewBase* mComposerBase ) { QList< QByteArray > charsets = mCodecAction->mimeCharsets(); if( !mOriginalPreferredCharset.isEmpty() ) { charsets.insert( 0, mOriginalPreferredCharset ); } mComposerBase->setFrom( from() ); mComposerBase->setReplyTo( replyTo() ); mComposerBase->setSubject( subject() ); mComposerBase->setCharsets( charsets ); mComposerBase->setUrgent( mUrgentAction->isChecked() ); mComposerBase->setMDNRequested( mRequestMDNAction->isChecked() ); } void KMComposeWin::doDelayedSend( MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn ) { #ifndef QT_NO_CURSOR MessageViewer::KCursorSaver busy( MessageViewer::KBusyPtr::busy() ); #endif applyComposerSetting( mComposerBase ); if ( mForceDisableHtml ) disableHtml( MessageComposer::ComposerViewBase::NoConfirmationNeeded ); bool sign = mSignAction->isChecked(); bool encrypt = mEncryptAction->isChecked(); mComposerBase->setCryptoOptions( sign, encrypt, cryptoMessageFormat(), ( ( saveIn != MessageComposer::MessageSender::SaveInNone && GlobalSettings::self()->neverEncryptDrafts() ) || mSigningAndEncryptionExplicitlyDisabled ) ); const int num = GlobalSettings::self()->customMessageHeadersCount(); QMap customHeader; for (int ix=0; ix extraCustomHeader(mExtraHeaders); while (extraCustomHeader.hasNext()) { extraCustomHeader.next(); customHeader.insert(extraCustomHeader.key(), extraCustomHeader.value() ); } mComposerBase->setCustomHeader( customHeader ); mComposerBase->send( method, saveIn, false ); } //---------------------------------------------------------------------------- void KMComposeWin::slotSendLater() { if ( !TransportManager::self()->showTransportCreationDialog( this, TransportManager::IfNoTransportExists ) ) return; if ( !checkRecipientNumber() ) return; if ( mComposerBase->editor()->checkExternalEditorFinished() ) { const bool wasRegistered = (SendLater::SendLaterUtil::sentLaterAgentWasRegistered() && SendLater::SendLaterUtil::sentLaterAgentEnabled()); if (wasRegistered) { SendLater::SendLaterInfo *info = 0; QPointer dlg = new SendLater::SendLaterDialog(info, this); if (dlg->exec()) { info = dlg->info(); const SendLater::SendLaterDialog::SendLaterAction action = dlg->action(); delete dlg; switch (action) { case SendLater::SendLaterDialog::Unknown: kDebug()<<"Sendlater action \"Unknown\": Need to fix it."; break; case SendLater::SendLaterDialog::Canceled: return; break; case SendLater::SendLaterDialog::PutInOutbox: doSend( MessageComposer::MessageSender::SendLater ); break; case SendLater::SendLaterDialog::SendDeliveryAtTime: { mComposerBase->setSendLaterInfo(info); if (info->isRecurrence()) { doSend( MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInTemplates ); } else { doSend( MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInDrafts ); } break; } } } else { delete dlg; } } else { doSend( MessageComposer::MessageSender::SendLater ); } } } //---------------------------------------------------------------------------- void KMComposeWin::slotSaveDraft() { if ( mComposerBase->editor()->checkExternalEditorFinished() ) { doSend( MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInDrafts ); } } //---------------------------------------------------------------------------- void KMComposeWin::slotSaveTemplate() { if ( mComposerBase->editor()->checkExternalEditorFinished() ) { doSend( MessageComposer::MessageSender::SendLater, MessageComposer::MessageSender::SaveInTemplates ); } } //---------------------------------------------------------------------------- void KMComposeWin::slotSendNowVia( QAction *item ) { const QList availTransports= TransportManager::self()->transportIds(); const int transport = item->data().toInt(); if ( availTransports.contains( transport ) ) { mComposerBase->transportComboBox()->setCurrentTransport( transport ); slotSendNow(); } } //---------------------------------------------------------------------------- void KMComposeWin::slotSendLaterVia( QAction *item ) { const QList availTransports= TransportManager::self()->transportIds(); const int transport = item->data().toInt(); if ( availTransports.contains( transport ) ) { mComposerBase->transportComboBox()->setCurrentTransport( transport ); slotSendLater(); } } //---------------------------------------------------------------------------- void KMComposeWin::slotSendNow() { if ( !mComposerBase->editor()->checkExternalEditorFinished() ) { return; } if ( !TransportManager::self()->showTransportCreationDialog( this, TransportManager::IfNoTransportExists ) ) return; if ( !checkRecipientNumber() ) return; if( GlobalSettings::self()->checkSpellingBeforeSend()) { mComposerBase->editor()->forceSpellChecking(); } else { slotCheckSendNow(); } } void KMComposeWin::slotCheckSendNow() { if ( GlobalSettings::self()->confirmBeforeSend() ) { const int rc = KMessageBox::warningYesNoCancel( mMainWidget, i18n("About to send email..."), i18n("Send Confirmation"), KGuiItem( i18n("&Send Now") ), KGuiItem( i18n("Send &Later") ) ); if ( rc == KMessageBox::Yes ) { doSend( MessageComposer::MessageSender::SendImmediate ); } else if ( rc == KMessageBox::No ) { doSend( MessageComposer::MessageSender::SendLater ); } } else { doSend( MessageComposer::MessageSender::SendImmediate ); } } //---------------------------------------------------------------------------- bool KMComposeWin::checkRecipientNumber() const { const int thresHold = GlobalSettings::self()->recipientThreshold(); if ( GlobalSettings::self()->tooManyRecipients() && mComposerBase->recipientsEditor()->recipients().count() > thresHold ) { if ( KMessageBox::questionYesNo( mMainWidget, i18n("You are trying to send the mail to more than %1 recipients. Send message anyway?", thresHold), i18n("Too many recipients"), KGuiItem( i18n("&Send as Is") ), KGuiItem( i18n("&Edit Recipients") ) ) == KMessageBox::No ) { return false; } } return true; } //----------------------------------------------------------------------------- void KMComposeWin::slotHelp() { KToolInvocation::invokeHelp(); } //----------------------------------------------------------------------------- void KMComposeWin::enableHtml() { if ( mForceDisableHtml ) { disableHtml( MessageComposer::ComposerViewBase::NoConfirmationNeeded ); return; } mComposerBase->editor()->enableRichTextMode(); if ( !toolBar( QLatin1String("htmlToolBar") )->isVisible() ) { // Use singleshot, as we we might actually be called from a slot that wanted to disable the // toolbar (but the messagebox in disableHtml() prevented that and called us). // The toolbar can't correctly deal with being enabled right in a slot called from the "disabled" // signal, so wait one event loop run for that. QTimer::singleShot( 0, toolBar( QLatin1String("htmlToolBar") ), SLOT(show()) ); } if ( !markupAction->isChecked() ) markupAction->setChecked( true ); mComposerBase->editor()->updateActionStates(); mComposerBase->editor()->setActionsEnabled( true ); } //----------------------------------------------------------------------------- void KMComposeWin::disableHtml( MessageComposer::ComposerViewBase::Confirmation confirmation ) { bool forcePlainTextMarkup = false; if ( confirmation == MessageComposer::ComposerViewBase::LetUserConfirm && mComposerBase->editor()->isFormattingUsed() && !mForceDisableHtml ) { int choice = KMessageBox::warningYesNoCancel( this, i18n( "Turning HTML mode off " "will cause the text to lose the formatting. Are you sure?" ), i18n( "Lose the formatting?" ), KGuiItem( i18n( "Lose Formatting" ) ), KGuiItem( i18n( "Add Markup Plain Text" ) ) , KStandardGuiItem::cancel(), QLatin1String("LoseFormattingWarning") ); switch(choice) { case KMessageBox::Cancel: enableHtml(); return; case KMessageBox::No: forcePlainTextMarkup = true; break; case KMessageBox::Yes: break; } } mComposerBase->editor()->forcePlainTextMarkup(forcePlainTextMarkup); mComposerBase->editor()->switchToPlainText(); mComposerBase->editor()->setActionsEnabled( false ); slotUpdateFont(); if ( toolBar( QLatin1String("htmlToolBar") )->isVisible() ) { // See the comment in enableHtml() why we use a singleshot timer, similar situation here. QTimer::singleShot( 0, toolBar( QLatin1String("htmlToolBar") ), SLOT(hide()) ); } if ( markupAction->isChecked() ) markupAction->setChecked( false ); } //----------------------------------------------------------------------------- void KMComposeWin::slotToggleMarkup() { htmlToolBarVisibilityChanged( markupAction->isChecked() ); } //----------------------------------------------------------------------------- void KMComposeWin::slotTextModeChanged( MessageComposer::KMeditor::Mode mode ) { if ( mode == KMeditor::Plain ) disableHtml( MessageComposer::ComposerViewBase::NoConfirmationNeeded ); // ### Can this happen at all? else enableHtml(); } //----------------------------------------------------------------------------- void KMComposeWin::htmlToolBarVisibilityChanged( bool visible ) { if ( visible ) enableHtml(); else disableHtml( MessageComposer::ComposerViewBase::LetUserConfirm ); } //----------------------------------------------------------------------------- void KMComposeWin::slotAutoSpellCheckingToggled( bool on ) { mAutoSpellCheckingAction->setChecked( on ); if ( on != mComposerBase->editor()->checkSpellingEnabled() ) mComposerBase->editor()->setCheckSpellingEnabled( on ); if ( on != mEdtSubject->checkSpellingEnabled() ) mEdtSubject->setCheckSpellingEnabled( on ); QString temp; if ( on ) { temp = i18n( "Spellcheck: on" ); } else { temp = i18n( "Spellcheck: off" ); } statusBar()->changeItem( temp, 3 ); } void KMComposeWin::slotSpellCheckingStatus(const QString & status) { statusBar()->changeItem( status, 0 ); QTimer::singleShot( 2000, this, SLOT(slotSpellcheckDoneClearStatus()) ); } void KMComposeWin::slotSpellcheckDoneClearStatus() { statusBar()->changeItem(QString(), 0); } //----------------------------------------------------------------------------- void KMComposeWin::slotIdentityChanged( uint uoid, bool initalChange ) { if( mMsg == 0 ) { kDebug() << "Trying to change identity but mMsg == 0!"; return; } const KPIMIdentities::Identity &ident = KMKernel::self()->identityManager()->identityForUoid( uoid ); if ( ident.isNull() ) { return; } bool wasModified(isModified()); emit identityChanged( identity() ); 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 ( KPIMUtils::firstEmailAddress( from() ).isEmpty() ) { mShowHeaders |= HDR_FROM; } if ( mEdtReplyTo ) { mEdtReplyTo->setText( ident.replyToAddr() ); } // remove BCC of old identity and add BCC of new identity (if they differ) const KPIMIdentities::Identity &oldIdentity = KMKernel::self()->identityManager()->identityForUoidOrDefault( mId ); if ( ident.organization().isEmpty() ) { mMsg->organization()->clear(); } else { KMime::Headers::Organization * const organization = new KMime::Headers::Organization( mMsg.get(), ident.organization(), "utf-8" ); mMsg->setHeader( organization ); } if ( !ident.isXFaceEnabled() || ident.xface().isEmpty() ) { mMsg->removeHeader( "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, QLatin1String("\n\t") ); } KMime::Headers::Generic *header = new KMime::Headers::Generic( "X-Face", mMsg.get(), xface, "utf-8" ); mMsg->setHeader( header ); } } // If the transport sticky checkbox is not checked, set the transport // from the new identity if ( !mBtnTransport->isChecked() && !mIgnoreStickyFields ) { const int transportId = ident.transport().isEmpty() ? -1 : ident.transport().toInt(); const Transport *transport = TransportManager::self()->transportById( transportId, true ); if ( !transport ) { mMsg->removeHeader( "X-KMail-Transport" ); mComposerBase->transportComboBox()->setCurrentTransport( TransportManager::self()->defaultTransportId() ); } else { KMime::Headers::Generic *header = new KMime::Headers::Generic( "X-KMail-Transport", mMsg.get(), QString::number( transport->id() ), "utf-8" ); mMsg->setHeader( header ); mComposerBase->transportComboBox()->setCurrentTransport( transport->id() ); } } const bool fccIsDisabled = ident.disabledFcc(); if (fccIsDisabled) { KMime::Headers::Generic *header = new KMime::Headers::Generic( "X-KMail-FccDisabled", mMsg.get(), QLatin1String("true"), "utf-8" ); mMsg->setHeader( header ); } else { mMsg->removeHeader( "X-KMail-FccDisabled" ); } mFccFolder->setEnabled(!fccIsDisabled); if ( !mBtnDictionary->isChecked() && !mIgnoreStickyFields ) { mDictionaryCombo->setCurrentByDictionaryName( ident.dictionary() ); } slotSpellCheckingLanguage( mDictionaryCombo->currentDictionary() ); if ( !mBtnFcc->isChecked() && !mPreventFccOverwrite ) { setFcc( ident.fcc() ); } // if unmodified, apply new template, if one is set if ( !wasModified && !( ident.templates().isEmpty() && mCustomTemplate.isEmpty() ) && !initalChange ) { applyTemplate( uoid, mId ); } else { mComposerBase->identityChanged( ident, oldIdentity, false ); mEdtSubject->setAutocorrectionLanguage(ident.autocorrectionLanguage()); } // 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(); // 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 ); } mCryptoModuleAction->setCurrentItem( format2cb( Kleo::stringToCryptoMessageFormat( ident.preferredCryptoMessageFormat() ) ) ); slotSelectCryptoModule( true ); mLastIdentityHasSigningKey = bNewIdentityHasSigningKey; mLastIdentityHasEncryptionKey = bNewIdentityHasEncryptionKey; mId = uoid; changeCryptoAction(); // make sure the From and BCC fields are shown if necessary rethinkFields( false ); setModified(wasModified); } //----------------------------------------------------------------------------- void KMComposeWin::slotSpellcheckConfig() { static_cast(mComposerBase->editor())->showSpellConfigDialog( QLatin1String("kmail2rc") ); } //----------------------------------------------------------------------------- void KMComposeWin::slotEditToolbars() { saveMainWindowSettings( KMKernel::self()->config()->group( "Composer") ); KEditToolBar dlg( guiFactory(), this ); connect( &dlg, SIGNAL(newToolBarConfig()), SLOT(slotUpdateToolbars()) ); dlg.exec(); } void KMComposeWin::slotUpdateToolbars() { createGUI( QLatin1String("kmcomposerui.rc") ); applyMainWindowSettings( KMKernel::self()->config()->group( "Composer") ); } void KMComposeWin::slotEditKeys() { KShortcutsDialog::configure( actionCollection(), KShortcutsEditor::LetterShortcutsDisallowed ); } void KMComposeWin::setFocusToEditor() { // The cursor position is already set by setMsg(), so we only need to set the // focus here. mComposerBase->editor()->setFocus(); } void KMComposeWin::setFocusToSubject() { mEdtSubject->setFocus(); } 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 ); mComposerBase->recipientsEditor()->setCompletionMode( mode ); } void KMComposeWin::slotConfigChanged() { readConfig( true /*reload*/); mComposerBase->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( const Akonadi::Collection & col ) { kDebug() << "you killed me."; // TODO: need to handle templates here? if ( ( mFolder.isValid() ) && ( col.id() == mFolder.id() ) ) { mFolder = CommonKernel->draftsCollectionFolder(); kDebug() << "restoring drafts to" << mFolder.id(); } } void KMComposeWin::slotOverwriteModeChanged() { mComposerBase->editor()->setCursorWidth( mComposerBase->editor()->overwriteMode () ? 5 : 1 ); statusBar()->changeItem( overwriteModeStr(), 4 ); } QString KMComposeWin::overwriteModeStr() const { return mComposerBase->editor()->overwriteMode () ? i18n("OVR") : i18n ("INS"); } void KMComposeWin::slotCursorPositionChanged() { // Change Line/Column info in status bar int col, line; QString temp; line = mComposerBase->editor()->linePosition(); col = mComposerBase->editor()->columnNumber(); temp = i18nc("Shows the linenumber of the cursor position.", " Line: %1 ", line + 1 ); statusBar()->changeItem( temp, 1 ); temp = i18n( " Column: %1 ", col + 1 ); statusBar()->changeItem( temp, 2 ); // Show link target in status bar if ( mComposerBase->editor()->textCursor().charFormat().isAnchor() ) { const QString text = mComposerBase->editor()->currentLinkText(); const QString url = mComposerBase->editor()->currentLinkUrl(); statusBar()->changeItem( text + QLatin1String(" -> ") + url, 0 ); } else { statusBar()->changeItem( QString(), 0 ); } } #if 0 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 ) { 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::auto_ptr job( chiasmus->specialJob( "x-obtain-keys", QMap() ) ); 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; } MessageViewer::ChiasmusKeySelector selectorDlg( this, i18n( "Chiasmus Encryption Key Selection" ), keys, GlobalSettings::chiasmusKey(), GlobalSettings::chiasmusOptions() ); if ( selectorDlg.exec() != KDialog::Accepted ) { return; } GlobalSettings::setChiasmusOptions( selectorDlg.options() ); GlobalSettings::setChiasmusKey( selectorDlg.key() ); assert( !GlobalSettings::chiasmusKey().isEmpty() ); resetter.disable(); } #endif void KMComposeWin::recipientEditorSizeHintChanged() { QTimer::singleShot( 1, this, SLOT(setMaximumHeaderSize()) ); } void KMComposeWin::setMaximumHeaderSize() { mHeadersArea->setMaximumHeight( mHeadersArea->sizeHint().height() ); } 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->setVisible( mSignAction->isChecked() ); mEncryptionStateIndicator->setVisible( mEncryptAction->isChecked() ); } } void KMComposeWin::slotLanguageChanged( const QString &language ) { mDictionaryCombo->setCurrentByDictionary( language ); } void KMComposeWin::slotFccFolderChanged(const Akonadi::Collection& collection) { mComposerBase->setFcc( collection ); } void KMComposeWin::insertSpecialCharacter() { if(!mSelectSpecialChar) { mSelectSpecialChar = new KPIMTextEdit::SelectSpecialChar(this); mSelectSpecialChar->setCaption(i18n("Insert Special Character")); mSelectSpecialChar->setOkButtonText(i18n("Insert")); connect(mSelectSpecialChar,SIGNAL(charSelected(QChar)),this,SLOT(charSelected(QChar))); } mSelectSpecialChar->show(); } void KMComposeWin::charSelected(const QChar& c) { mComposerBase->editor()->insertPlainText(c); } void KMComposeWin::slotSaveAsFile() { QPointer dlg = new KFileDialog(KUrl(),QString(),this); dlg->setOperationMode(KFileDialog::Saving); dlg->setConfirmOverwrite(true); if(mComposerBase->editor()->textMode() == KMeditor::Rich ) { dlg->setFilter( QString::fromLatin1("text/html text/plain application/vnd.oasis.opendocument.text") ); } else { dlg->setFilter( QString::fromLatin1("text/plain") ); } if(dlg->exec()) { QTextDocumentWriter writer; const QString filename = dlg->selectedUrl().path(); writer.setFileName(dlg->selectedUrl().path()); if (dlg->currentFilter() == QString::fromLatin1("text/plain") || filename.endsWith(QLatin1String(".txt"))) { writer.setFormat("plaintext"); } else if (dlg->currentFilter() == QString::fromLatin1("text/html")|| filename.endsWith(QLatin1String(".html"))) { writer.setFormat("HTML"); } else if (dlg->currentFilter() == QString::fromLatin1("application/vnd.oasis.opendocument.text") || filename.endsWith(QLatin1String(".odf"))) { writer.setFormat("ODF"); } else { writer.setFormat("plaintext"); } if (!writer.write(mComposerBase->editor()->document())) { qDebug()<<" Error during writing"; } } delete dlg; } void KMComposeWin::slotCreateAddressBookContact() { CreateNewContactJob *job = new CreateNewContactJob( this, this ); job->start(); } void KMComposeWin::slotAttachMissingFile() { mComposerBase->attachmentController()->showAddAttachmentDialog(); } void KMComposeWin::slotCloseAttachMissingFile() { if(m_verifyMissingAttachment) { m_verifyMissingAttachment->start(); } } void KMComposeWin::slotVerifyMissingAttachmentTimeout() { if( mComposerBase->hasMissingAttachments( GlobalSettings::self()->attachmentKeywords() )) { mAttachmentMissing->animatedShow(); } else { m_verifyMissingAttachment->start(); } } void KMComposeWin::slotExplicitClosedMissingAttachment() { if(m_verifyMissingAttachment) { m_verifyMissingAttachment->stop(); delete m_verifyMissingAttachment; m_verifyMissingAttachment = 0; } } void KMComposeWin::addExtraCustomHeaders( const QMap &headers) { mExtraHeaders = headers; } void KMComposeWin::slotUpperCase() { QTextCursor textCursor = mComposerBase->editor()->textCursor(); PimCommon::EditorUtil::lowerCase(textCursor); } void KMComposeWin::slotLowerCase() { QTextCursor textCursor = mComposerBase->editor()->textCursor(); PimCommon::EditorUtil::upperCase(textCursor); } void KMComposeWin::slotExternalEditorStarted() { mComposerBase->identityCombo()->setEnabled(false); mExternalEditorWarning->show(); } void KMComposeWin::slotExternalEditorClosed() { mComposerBase->identityCombo()->setEnabled(true); mExternalEditorWarning->hide(); } void KMComposeWin::slotInsertShortUrl(const QString &url) { mComposerBase->editor()->insertLink(url); } void KMComposeWin::slotUploadFileDone(const QString &serviceName, const QString &fileName) { Q_UNUSED(serviceName); Q_UNUSED(fileName); } void KMComposeWin::slotUploadFileFailed(const QString &serviceName, const QString &fileName) { Q_UNUSED(serviceName); Q_UNUSED(fileName); KMessageBox::error(this, i18n("An error occurred while sending the file."), i18n("Upload file")); --mNumProgressUploadFile; } void KMComposeWin::slotShareLinkDone(const QString &serviceName, const QString &link) { Q_UNUSED(serviceName); mComposerBase->editor()->insertShareLink(link); --mNumProgressUploadFile; } void KMComposeWin::slotUploadFileStart(PimCommon::StorageServiceAbstract *service) { Q_UNUSED(service); ++mNumProgressUploadFile; } void KMComposeWin::slotActionFailed(const QString &serviceName, const QString &error) { KMessageBox::error(this, i18n("%1 return an error '%2'", serviceName, error), i18n("Error")); --mNumProgressUploadFile; } diff --git a/kmail/kmcommands.cpp b/kmail/kmcommands.cpp index 85fbd6dc77..bf52513664 100644 --- a/kmail/kmcommands.cpp +++ b/kmail/kmcommands.cpp @@ -1,1598 +1,1597 @@ /* -*- mode: C++; c-file-style: "gnu" -*- This file is part of KMail, the KDE mail client. Copyright (c) 2002 Don Sanders Copyright (c) 2013 Laurent Montel 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" #include "widgets/collectionpane.h" #include "kernel/mailkernel.h" #include "util/mailutil.h" #include // link() #include #include #include #include #include #include #include #include #include #include // KIO headers #include #include #include #include #include #include #include #include "foldercollection.h" #include "messagecore/misc/mailinglist.h" #include "editor/composer.h" #include "mailcommon/filter/filteraction.h" #include "mailcommon/filter/filtermanager.h" #include "mailcommon/filter/mailfilter.h" #include "mailcommon/widgets/redirectdialog.h" #include "kmmainwidget.h" #include "undostack.h" #ifndef QT_NO_CURSOR #include "messageviewer/utils/kcursorsaver.h" #endif #include "messageviewer/viewer/objecttreeparser.h" #include "messageviewer/viewer/csshelper.h" #include "messageviewer/utils/util.h" #include "messageviewer/viewer/mailsourceviewer.h" #include "messageviewer/viewer/viewer.h" #include "messageviewer/header/headerstrategy.h" #include "messageviewer/header/headerstyle.h" #include "kmreadermainwin.h" #include "secondarywindow.h" using KMail::SecondaryWindow; #include "util.h" #include "misc/broadcaststatus.h" #include "settings/globalsettings.h" #include "utils/stringutil.h" #include "messageviewer/utils/autoqpointer.h" #include "messageviewer/settings/globalsettings.h" #include "messagecore/settings/globalsettings.h" #include #include #include #include #include #include #include #include #include #include using MailTransport::TransportManager; #include "messageviewer/viewer/nodehelper.h" #include "messageviewer/viewer/objecttreeemptysource.h" #include "messagecore/utils/stringutil.h" #include "messagecore/helpers/messagehelpers.h" #include "messagecomposer/sender/messagesender.h" #include "messagecomposer/helper/messagehelper.h" #include "messagecomposer/settings/messagecomposersettings.h" #include "messagecomposer/helper/messagefactory.h" using MessageComposer::MessageFactory; #include "progresswidget/progressmanager.h" using KPIM::ProgressManager; using KPIM::ProgressItem; #include using namespace KMime; #include "kleo/cryptobackend.h" #include "kleo/cryptobackendfactory.h" #include #include #include #include #include #include #include using namespace MailCommon; /// Small helper function to get the composer context from a reply static KMail::Composer::TemplateContext replyContext( MessageFactory::MessageReply reply ) { if ( reply.replyAll ) return KMail::Composer::ReplyToAll; else return KMail::Composer::Reply; } /// Helper to sanely show an error message for a job static void showJobError( KJob* job ) { assert(job); // we can be called from the KJob::kill, where we are no longer a KIO::Job // so better safe than sorry KIO::Job* kiojob = dynamic_cast(job); if( kiojob && kiojob->ui() ) kiojob->ui()->showErrorMessage(); else kWarning() << "There is no GUI delegate set for a kjob, and it failed with error:" << job->errorString(); } KMCommand::KMCommand( QWidget *parent ) : mCountMsgs(0), mResult( Undefined ), mDeletesItself( false ), mEmitsCompletedItself( false ), mParent( parent ) { } KMCommand::KMCommand( QWidget *parent, const Akonadi::Item &msg ) : mCountMsgs(0), mResult( Undefined ), mDeletesItself( false ), mEmitsCompletedItself( false ), mParent( parent ) { if ( msg.isValid() || msg.hasPayload() ) { mMsgList.append( msg ); } } KMCommand::KMCommand( QWidget *parent, const QList &msgList ) : mCountMsgs(0), mResult( Undefined ), mDeletesItself( false ), mEmitsCompletedItself( false ), mParent( parent ) { mMsgList = msgList; } KMCommand::~KMCommand() { } KMCommand::Result KMCommand::result() const { if ( mResult == Undefined ) { kDebug() << "mResult is Undefined"; } return mResult; } const QList KMCommand::retrievedMsgs() const { return mRetrievedMsgs; } Akonadi::Item KMCommand::retrievedMessage() const { if ( mRetrievedMsgs.isEmpty() ) return Akonadi::Item(); return *(mRetrievedMsgs.begin()); } QWidget *KMCommand::parentWidget() const { return mParent; } int KMCommand::mCountJobs = 0; void KMCommand::start() { connect( this, SIGNAL(messagesTransfered(KMCommand::Result)), this, SLOT(slotPostTransfer(KMCommand::Result)) ); if ( mMsgList.isEmpty() ) { emit messagesTransfered( OK ); return; } // Special case of operating on message that isn't in a folder const Akonadi::Item mb = mMsgList.first(); if ( ( mMsgList.count() == 1 ) && MessageCore::Util::isStandaloneMessage( mb ) ) { mRetrievedMsgs.append(mMsgList.takeFirst()); emit messagesTransfered( OK ); return; } // we can only retrieve items with a valid id foreach ( const Akonadi::Item &item, mMsgList ) { if ( !item.isValid() ) { emit messagesTransfered( Failed ); return; } } // 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; 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, i18n("Please wait"), i18np("Please wait while the message is transferred", "Please wait while the %1 messages are transferred", mMsgList.count())); mProgressDialog.data()->setModal(true); mProgressDialog.data()->setMinimumDuration(1000); } // TODO once the message list is based on ETM and we get the more advanced caching we need to make that check a bit more clever if ( !mFetchScope.isEmpty() ) { complete = false; ++KMCommand::mCountJobs; Akonadi::ItemFetchJob *fetch = new Akonadi::ItemFetchJob( mMsgList, this ); mFetchScope.fetchAttribute< MessageCore::MDNStateAttribute >(); fetch->setFetchScope( mFetchScope ); connect( fetch, SIGNAL(itemsReceived(Akonadi::Item::List)), SLOT(slotMsgTransfered(Akonadi::Item::List)) ); connect( fetch, SIGNAL(result(KJob*)), SLOT(slotJobFinished()) ); } else { // no need to fetch anything if ( !mMsgList.isEmpty() ) mRetrievedMsgs = mMsgList; } if ( complete ) { delete mProgressDialog.data(); mProgressDialog.clear(); emit messagesTransfered( OK ); } else { // wait for the transfer and tell the progressBar the necessary steps if ( mProgressDialog.data() ) { connect(mProgressDialog.data(), SIGNAL(cancelClicked()), this, SLOT(slotTransferCancelled())); mProgressDialog.data()->progressBar()->setMaximum(totalSize); } } } void KMCommand::slotMsgTransfered(const Akonadi::Item::List& msgs) { if ( mProgressDialog.data() && mProgressDialog.data()->wasCancelled() ) { emit messagesTransfered( Canceled ); return; } // save the complete messages mRetrievedMsgs.append( msgs ); } void KMCommand::slotJobFinished() { // the job is finished (with / without error) KMCommand::mCountJobs--; if ( mProgressDialog.data() && mProgressDialog.data()->wasCancelled() ) return; if ( mCountMsgs > mRetrievedMsgs.count() ) { // the message wasn't retrieved before => error if ( mProgressDialog.data() ) mProgressDialog.data()->hide(); slotTransferCancelled(); return; } // update the progressbar if ( mProgressDialog.data() ) { mProgressDialog.data()->setLabelText(i18np("Please wait while the message is transferred", "Please wait while the %1 messages are transferred", KMCommand::mCountJobs)); } if (KMCommand::mCountJobs == 0) { // all done delete mProgressDialog.data(); mProgressDialog.clear(); emit messagesTransfered( OK ); } } void KMCommand::slotTransferCancelled() { KMCommand::mCountJobs = 0; mCountMsgs = 0; mRetrievedMsgs.clear(); emit messagesTransfered( Canceled ); } KMMailtoComposeCommand::KMMailtoComposeCommand( const KUrl &url, const Akonadi::Item &msg ) :mUrl( url ), mMessage( msg ) { } KMCommand::Result KMMailtoComposeCommand::execute() { KMime::Message::Ptr msg( new KMime::Message ); uint id = 0; if ( mMessage.isValid() && mMessage.parentCollection().isValid() ) { QSharedPointer fd = FolderCollection::forCollection( mMessage.parentCollection(), false ); id = fd->identity(); } MessageHelper::initHeader( msg, KMKernel::self()->identityManager(),id ); msg->contentType()->setCharset("utf-8"); msg->to()->fromUnicodeString( KPIMUtils::decodeMailtoUrl( mUrl ), "utf-8" ); KMail::Composer * win = KMail::makeComposer( msg, false, false,KMail::Composer::New, id ); win->setFocusToSubject(); win->show(); return OK; } KMMailtoReplyCommand::KMMailtoReplyCommand( QWidget *parent, const KUrl &url, const Akonadi::Item &msg, const QString &selection ) :KMCommand( parent, msg ), mUrl( url ), mSelection( selection ) { fetchScope().fetchFullPayload( true ); fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMCommand::Result KMMailtoReplyCommand::execute() { Akonadi::Item item = retrievedMessage(); KMime::Message::Ptr msg = MessageCore::Util::message( item ); if ( !msg ) return Failed; MessageFactory factory( msg, item.id(), MailCommon::Util::updatedCollection(item.parentCollection()) ); factory.setIdentityManager( KMKernel::self()->identityManager() ); factory.setFolderIdentity( MailCommon::Util::folderIdentity( item ) ); factory.setMailingListAddresses( KMail::Util::mailingListsFromMessage( item ) ); factory.putRepliesInSameFolder( KMail::Util::putRepliesInSameFolder( item ) ); factory.setReplyStrategy( MessageComposer::ReplyNone ); factory.setSelection( mSelection ); KMime::Message::Ptr rmsg = factory.createReply().msg; rmsg->to()->fromUnicodeString( KPIMUtils::decodeMailtoUrl( mUrl ), "utf-8" ); //TODO Check the UTF-8 bool lastEncrypt = false; bool lastSign = false; KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg); KMail::Composer * win = KMail::makeComposer( rmsg, lastSign, lastEncrypt, KMail::Composer::Reply, 0, mSelection ); win->setFocusToEditor(); win->show(); return OK; } KMMailtoForwardCommand::KMMailtoForwardCommand( QWidget *parent, const KUrl &url, const Akonadi::Item &msg ) :KMCommand( parent, msg ), mUrl( url ) { fetchScope().fetchFullPayload( true ); fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMCommand::Result KMMailtoForwardCommand::execute() { //TODO : consider factoring createForward into this method. Akonadi::Item item = retrievedMessage(); KMime::Message::Ptr msg = MessageCore::Util::message( item ); if ( !msg ) return Failed; MessageFactory factory( msg, item.id(), MailCommon::Util::updatedCollection(item.parentCollection()) ); factory.setIdentityManager( KMKernel::self()->identityManager() ); factory.setFolderIdentity( MailCommon::Util::folderIdentity( item ) ); KMime::Message::Ptr fmsg = factory.createForward(); fmsg->to()->fromUnicodeString( KPIMUtils::decodeMailtoUrl( mUrl ).toLower(), "utf-8" ); //TODO check the utf-8 bool lastEncrypt = false; bool lastSign = false; KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg); KMail::Composer * win = KMail::makeComposer( fmsg, lastSign, lastEncrypt, KMail::Composer::Forward ); win->show(); return OK; } KMAddBookmarksCommand::KMAddBookmarksCommand( const KUrl &url, QWidget *parent ) : KMCommand( parent ), mUrl( url ) { } KMCommand::Result KMAddBookmarksCommand::execute() { const QString filename = KStandardDirs::locateLocal( "data", QString::fromLatin1("konqueror/bookmarks.xml") ); KBookmarkManager *bookManager = KBookmarkManager::managerForFile( filename, QLatin1String("konqueror") ); KBookmarkGroup group = bookManager->root(); group.addBookmark( mUrl.path(), KUrl( mUrl ) ); if( bookManager->save() ) { bookManager->emitChanged( group ); } return OK; } KMUrlSaveCommand::KMUrlSaveCommand( const KUrl &url, QWidget *parent ) : KMCommand( parent ), mUrl( url ) { } KMCommand::Result KMUrlSaveCommand::execute() { if ( mUrl.isEmpty() ) return OK; const KUrl saveUrl = KFileDialog::getSaveUrl(mUrl.fileName(), QString(), parentWidget() ); if ( saveUrl.isEmpty() ) return Canceled; if ( KIO::NetAccess::exists( saveUrl, KIO::NetAccess::DestinationSide, parentWidget() ) ) { if (KMessageBox::warningContinueCancel(0, i18nc("@info", "File %1 exists.Do you want to replace it?", saveUrl.pathOrUrl()), i18n("Save to File"), KGuiItem(i18n("&Replace"))) != KMessageBox::Continue) return Canceled; } KIO::Job *job = KIO::file_copy(mUrl, saveUrl, -1, KIO::Overwrite); connect(job, SIGNAL(result(KJob*)), SLOT(slotUrlSaveResult(KJob*))); setEmitsCompletedItself( true ); return OK; } void KMUrlSaveCommand::slotUrlSaveResult( KJob *job ) { if ( job->error() ) { showJobError(job); setResult( Failed ); emit completed( this ); } else { setResult( OK ); emit completed( this ); } } KMEditMessageCommand::KMEditMessageCommand( QWidget *parent, const KMime::Message::Ptr& msg ) :KMCommand( parent ), mMessage( msg ) { } KMCommand::Result KMEditMessageCommand::execute() { if ( !mMessage ) return Failed; KMail::Composer *win = KMail::makeComposer(); bool lastEncrypt = false; bool lastSign = false; KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, mMessage); - win->setMessage( mMessage, lastSign, lastEncrypt, false, false ); + win->setMessage( mMessage, lastSign, lastEncrypt, true, true ); win->show(); - win->setModified( true ); return OK; } KMEditItemCommand::KMEditItemCommand( QWidget *parent, const Akonadi::Item&msg, bool deleteFromSource ) :KMCommand( parent, msg ) , mDeleteFromSource( deleteFromSource ) { fetchScope().fetchFullPayload( true ); fetchScope().fetchAttribute(); fetchScope().fetchAttribute(); fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMEditItemCommand::~KMEditItemCommand() { } KMCommand::Result KMEditItemCommand::execute() { Akonadi::Item item = retrievedMessage(); if (!item.isValid() || !item.parentCollection().isValid() ) { return Failed; } KMime::Message::Ptr msg = MessageCore::Util::message( item ); if ( !msg ) { return Failed; } if ( mDeleteFromSource ) { setDeletesItself( true ); Akonadi::ItemDeleteJob *job = new Akonadi::ItemDeleteJob( item ); connect( job, SIGNAL(result(KJob*)), this, SLOT(slotDeleteItem(KJob*)) ); } KMail::Composer *win = KMail::makeComposer(); bool lastEncrypt = false; bool lastSign = false; KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg); - win->setMessage( msg, lastSign, lastEncrypt, false, true ); + win->setMessage( msg, lastSign, lastEncrypt, true, true ); win->setFolder( item.parentCollection() ); const MailTransport::TransportAttribute *transportAttribute = item.attribute(); if ( transportAttribute ) { win->setCurrentTransport( transportAttribute->transportId() ); } else { int transportId = msg->headerByType( "X-KMail-Transport" ) ? msg->headerByType( "X-KMail-Transport" )->asUnicodeString().toInt() : -1; if(transportId!=-1) { win->setCurrentTransport( transportId ); } } if(msg->headerByType( "Reply-To" )) { const QString replyTo = msg->headerByType( "Reply-To" )->asUnicodeString(); win->setCurrentReplyTo(replyTo); } const MailTransport::SentBehaviourAttribute *sentAttribute = item.attribute(); if ( sentAttribute && ( sentAttribute->sentBehaviour() == MailTransport::SentBehaviourAttribute::MoveToCollection ) ) win->setFcc( QString::number( sentAttribute->moveToCollection().id() ) ); win->show(); if ( mDeleteFromSource ) win->setModified( true ); return OK; } void KMEditItemCommand::slotDeleteItem( KJob *job ) { if ( job->error() ) { showJobError(job); setResult( Failed ); emit completed( this ); } else { setResult( OK ); emit completed( this ); } deleteLater( ); } KMUseTemplateCommand::KMUseTemplateCommand( QWidget *parent, const Akonadi::Item &msg ) :KMCommand( parent, msg ) { fetchScope().fetchFullPayload( true ); fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMCommand::Result KMUseTemplateCommand::execute() { Akonadi::Item item = retrievedMessage(); if ( !item.isValid() || !item.parentCollection().isValid() || !CommonKernel->folderIsTemplates( item.parentCollection() ) ) { return Failed; } KMime::Message::Ptr msg = MessageCore::Util::message( item ); if ( !msg ) return Failed; KMime::Message::Ptr newMsg(new KMime::Message); newMsg->setContent( msg->encodedContent() ); newMsg->parse(); // these fields need to be regenerated for the new message newMsg->removeHeader("Date"); newMsg->removeHeader("Message-ID"); KMail::Composer *win = KMail::makeComposer(); win->setMessage( newMsg, false, false, false, true ); win->show(); return OK; } KMSaveMsgCommand::KMSaveMsgCommand( QWidget *parent, const QList &msgList ) : KMCommand( parent, msgList ) { if ( msgList.empty() ) return; fetchScope().fetchFullPayload( true ); // ### unless we call the corresponding KMCommand ctor, this has no effect } KMCommand::Result KMSaveMsgCommand::execute() { if ( !MessageViewer::Util::saveMessageInMbox( retrievedMsgs(), parentWidget()) ) return Failed; return OK; } //----------------------------------------------------------------------------- KMOpenMsgCommand::KMOpenMsgCommand( QWidget *parent, const KUrl & url, const QString & encoding, KMMainWidget *main ) : KMCommand( parent ), mUrl( url ), mJob( 0 ), mEncoding( encoding ), mMainWidget( main ) { } KMCommand::Result KMOpenMsgCommand::execute() { if ( mUrl.isEmpty() ) { mUrl = KFileDialog::getOpenUrl( KUrl( QLatin1String("kfiledialog:///OpenMessage") ), QLatin1String("message/rfc822 application/mbox"), parentWidget(), i18n("Open Message") ); } if ( mUrl.isEmpty() ) { return Canceled; } if(mMainWidget) { mMainWidget->addRecentFile(mUrl); } setDeletesItself( true ); mJob = KIO::get( mUrl, KIO::NoReload, KIO::HideProgressInfo ); connect( mJob, SIGNAL(data(KIO::Job*,QByteArray)), this, SLOT(slotDataArrived(KIO::Job*,QByteArray)) ); connect( mJob, SIGNAL(result(KJob*)), SLOT(slotResult(KJob*)) ); setEmitsCompletedItself( true ); return OK; } void KMOpenMsgCommand::slotDataArrived( KIO::Job *, const QByteArray & data ) { if ( data.isEmpty() ) return; mMsgString.append( QString::fromLatin1(data.data()) ); } void KMOpenMsgCommand::doesNotContainMessage() { 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(); } void KMOpenMsgCommand::slotResult( KJob *job ) { if ( job->error() ) { // handle errors showJobError(job); setResult( Failed ); emit completed( this ); } else { int startOfMessage = 0; if ( mMsgString.startsWith( QLatin1String( "From " ) ) ) { startOfMessage = mMsgString.indexOf( QLatin1Char('\n') ); if ( startOfMessage == -1 ) { doesNotContainMessage(); return; } startOfMessage += 1; // the message starts after the '\n' } // check for multiple messages in the file bool multipleMessages = true; int endOfMessage = mMsgString.indexOf( QLatin1String("\nFrom ") ); if ( endOfMessage == -1 ) { endOfMessage = mMsgString.length(); multipleMessages = false; } KMime::Message *msg = new KMime::Message; msg->setContent( KMime::CRLFtoLF( mMsgString.mid( startOfMessage,endOfMessage - startOfMessage ).toUtf8() ) ); msg->parse(); if ( !msg->hasContent() ) { delete msg; msg = 0; doesNotContainMessage(); return; } KMReaderMainWin *win = new KMReaderMainWin(); KMime::Message::Ptr mMsg( msg ); win->showMessage( mEncoding, mMsg ); 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(); } //----------------------------------------------------------------------------- KMReplyCommand::KMReplyCommand( QWidget *parent, const Akonadi::Item &msg, MessageComposer::ReplyStrategy replyStrategy, const QString &selection, bool noquote, const QString& templateName ) : KMCommand( parent, msg ), mSelection( selection ), mTemplate( templateName ), m_replyStrategy( replyStrategy ), mNoQuote(noquote) { if ( !noquote ) fetchScope().fetchFullPayload( true ); fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMCommand::Result KMReplyCommand::execute() { #ifndef QT_NO_CURSOR MessageViewer::KCursorSaver busy( MessageViewer::KBusyPtr::busy() ); #endif Akonadi::Item item = retrievedMessage(); KMime::Message::Ptr msg = MessageCore::Util::message( item ); if ( !msg ) return Failed; MessageFactory factory( msg, item.id(), MailCommon::Util::updatedCollection(item.parentCollection()) ); factory.setIdentityManager( KMKernel::self()->identityManager() ); factory.setFolderIdentity( MailCommon::Util::folderIdentity( item ) ); factory.setMailingListAddresses( KMail::Util::mailingListsFromMessage( item ) ); factory.putRepliesInSameFolder( KMail::Util::putRepliesInSameFolder( item ) ); factory.setReplyStrategy( m_replyStrategy ); factory.setSelection( mSelection ); if ( !mTemplate.isEmpty() ) factory.setTemplate( mTemplate ); if(mNoQuote) { factory.setQuote(false); } bool lastEncrypt = false; bool lastSign = false; KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg); MessageFactory::MessageReply reply = factory.createReply(); KMail::Composer * win = KMail::makeComposer( KMime::Message::Ptr( reply.msg ), lastSign, lastEncrypt, replyContext( reply ), 0, mSelection,mTemplate ); win->setFocusToEditor(); win->show(); return OK; } KMForwardCommand::KMForwardCommand( QWidget *parent, const QList &msgList, uint identity,const QString& templateName ) : KMCommand( parent, msgList ), mIdentity( identity ), mTemplate( templateName ) { fetchScope().fetchFullPayload( true ); fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMForwardCommand::KMForwardCommand( QWidget *parent, const Akonadi::Item &msg, uint identity, const QString& templateName ) : KMCommand( parent, msg ), mIdentity( identity ), mTemplate( templateName ) { fetchScope().fetchFullPayload( true ); fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMCommand::Result KMForwardCommand::createComposer(const Akonadi::Item& item) { KMime::Message::Ptr msg = MessageCore::Util::message( item ); if ( !msg ) return Failed; #ifndef QT_NO_CURSOR MessageViewer::KCursorSaver busy( MessageViewer::KBusyPtr::busy() ); #endif MessageFactory factory( msg, item.id(), MailCommon::Util::updatedCollection(item.parentCollection()) ); factory.setIdentityManager( KMKernel::self()->identityManager() ); factory.setFolderIdentity( MailCommon::Util::folderIdentity( item ) ); if ( !mTemplate.isEmpty() ) factory.setTemplate( mTemplate ); KMime::Message::Ptr fwdMsg = factory.createForward(); uint id = msg->headerByType( "X-KMail-Identity" ) ? msg->headerByType("X-KMail-Identity")->asUnicodeString().trimmed().toUInt() : 0; kDebug() << "mail" << msg->encodedContent(); bool lastEncrypt = false; bool lastSign = false; KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg); if ( id == 0 ) id = mIdentity; { KMail::Composer * win = KMail::makeComposer( fwdMsg, lastSign, lastEncrypt, KMail::Composer::Forward, id,QString(), mTemplate ); win->show(); } return OK; } KMCommand::Result KMForwardCommand::execute() { QList msgList = retrievedMsgs(); if (msgList.count() >= 2) { // ask if they want a mime digest forward int answer = KMessageBox::questionYesNoCancel( parentWidget(), i18n("Do you want to forward the selected messages as " "attachments in one message (as a MIME digest) or as " "individual messages?"), QString(), KGuiItem(i18n("Send As Digest")), KGuiItem(i18n("Send Individually")) ); if ( answer == KMessageBox::Yes ) { Akonadi::Item firstItem( msgList.first() ); MessageFactory factory( KMime::Message::Ptr( new KMime::Message ), firstItem.id(), MailCommon::Util::updatedCollection(firstItem.parentCollection()) ); factory.setIdentityManager( KMKernel::self()->identityManager() ); factory.setFolderIdentity( MailCommon::Util::folderIdentity( firstItem ) ); QPair< KMime::Message::Ptr, KMime::Content* > fwdMsg = factory.createForwardDigestMIME(msgList ); KMail::Composer * win = KMail::makeComposer( fwdMsg.first, false, false, KMail::Composer::Forward, mIdentity ); win->addAttach( fwdMsg.second ); win->show(); return OK; } else if ( answer == KMessageBox::No ) {// NO MIME DIGEST, Multiple forward QList::const_iterator it; QList::const_iterator end( msgList.constEnd() ); for ( it = msgList.constBegin(); it != end; ++it ) { if ( createComposer( *it ) == Failed ) return Failed; } return OK; } else { // user cancelled return OK; } } // forward a single message at most. Akonadi::Item item = msgList.first(); if ( createComposer( item ) == Failed ) return Failed; return OK; } KMForwardAttachedCommand::KMForwardAttachedCommand( QWidget *parent, const QList &msgList, uint identity, KMail::Composer *win ) : KMCommand( parent, msgList ), mIdentity( identity ), mWin( QPointer( win )) { fetchScope().fetchFullPayload( true ); fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMForwardAttachedCommand::KMForwardAttachedCommand( QWidget *parent, const Akonadi::Item & msg, uint identity, KMail::Composer *win ) : KMCommand( parent, msg ), mIdentity( identity ), mWin( QPointer< KMail::Composer >( win )) { fetchScope().fetchFullPayload( true ); fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMCommand::Result KMForwardAttachedCommand::execute() { QList msgList = retrievedMsgs(); Akonadi::Item firstItem( msgList.first() ); MessageFactory factory( KMime::Message::Ptr( new KMime::Message ), firstItem.id(), MailCommon::Util::updatedCollection(firstItem.parentCollection()) ); factory.setIdentityManager( KMKernel::self()->identityManager() ); factory.setFolderIdentity( MailCommon::Util::folderIdentity( firstItem ) ); QPair< KMime::Message::Ptr, QList< KMime::Content* > > fwdMsg = factory.createAttachedForward( msgList ); if ( !mWin ) { mWin = KMail::makeComposer( fwdMsg.first, false, false, KMail::Composer::Forward, mIdentity ); } foreach( KMime::Content* attach, fwdMsg.second ) { mWin->addAttach( attach ); } mWin->show(); return OK; } KMRedirectCommand::KMRedirectCommand( QWidget *parent, const QList &msgList ) : KMCommand( parent, msgList ) { fetchScope().fetchFullPayload( true ); fetchScope().fetchAttribute(); fetchScope().fetchAttribute(); fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMRedirectCommand::KMRedirectCommand( QWidget *parent, const Akonadi::Item &msg ) : KMCommand( parent, msg ) { fetchScope().fetchFullPayload( true ); fetchScope().fetchAttribute(); fetchScope().fetchAttribute(); fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMCommand::Result KMRedirectCommand::execute() { const MailCommon::RedirectDialog::SendMode sendMode = MessageComposer::MessageComposerSettings::self()->sendImmediate() ? MailCommon::RedirectDialog::SendNow : MailCommon::RedirectDialog::SendLater; MessageViewer::AutoQPointer dlg( new MailCommon::RedirectDialog( sendMode, parentWidget() ) ); dlg->setObjectName( QLatin1String("redirect") ); if ( dlg->exec() == QDialog::Rejected || !dlg ) { return Failed; } if ( !TransportManager::self()->showTransportCreationDialog( parentWidget(), TransportManager::IfNoTransportExists ) ) return Failed; //TODO use sendlateragent here too. const MessageComposer::MessageSender::SendMethod method = (dlg->sendMode() == MailCommon::RedirectDialog::SendNow) ? MessageComposer::MessageSender::SendImmediate : MessageComposer::MessageSender::SendLater; const int identity = dlg->identity(); int transportId = dlg->transportId(); const QString to = dlg->to(); foreach( const Akonadi::Item &item, retrievedMsgs() ) { const KMime::Message::Ptr msg = MessageCore::Util::message( item ); if ( !msg ) return Failed; MessageFactory factory( msg, item.id(), MailCommon::Util::updatedCollection(item.parentCollection()) ); factory.setIdentityManager( KMKernel::self()->identityManager() ); factory.setFolderIdentity( MailCommon::Util::folderIdentity( item ) ); if(transportId == -1) { const MailTransport::TransportAttribute *transportAttribute = item.attribute(); if ( transportAttribute ) { transportId = transportAttribute->transportId(); const MailTransport::Transport *transport = MailTransport::TransportManager::self()->transportById( transportId ); if ( !transport ) { transportId = -1; } } } const MailTransport::SentBehaviourAttribute *sentAttribute = item.attribute(); QString fcc; if ( sentAttribute && ( sentAttribute->sentBehaviour() == MailTransport::SentBehaviourAttribute::MoveToCollection ) ) fcc = QString::number( sentAttribute->moveToCollection().id() ); const KMime::Message::Ptr newMsg = factory.createRedirect( to, transportId, fcc, identity ); if ( !newMsg ) return Failed; MessageStatus status; status.setStatusFromFlags( item.flags() ); if ( !status.isRead() ) FilterAction::sendMDN( item, KMime::MDN::Dispatched ); if ( !kmkernel->msgSender()->send( newMsg, method ) ) { kDebug() << "KMRedirectCommand: could not redirect message (sending failed)"; return Failed; // error: couldn't send } } return OK; } KMPrintCommand::KMPrintCommand( QWidget *parent, const Akonadi::Item &msg, MessageViewer::HeaderStyle *headerStyle, MessageViewer::HeaderStrategy *headerStrategy, bool htmlOverride, bool htmlLoadExtOverride, bool useFixedFont, const QString & encoding ) : KMCommand( parent, msg ), mHeaderStyle( headerStyle ), mHeaderStrategy( headerStrategy ), mAttachmentStrategy( 0 ), mEncoding( encoding ), mHtmlOverride( htmlOverride ), mHtmlLoadExtOverride( htmlLoadExtOverride ), mUseFixedFont( useFixedFont ), mPrintPreview(false) { fetchScope().fetchFullPayload( true ); if ( MessageCore::GlobalSettings::useDefaultFonts() ) mOverrideFont = KGlobalSettings::generalFont(); else { mOverrideFont = MessageCore::GlobalSettings::self()->printFont(); } } void KMPrintCommand::setOverrideFont( const QFont& font ) { mOverrideFont = font; } void KMPrintCommand::setAttachmentStrategy( const MessageViewer::AttachmentStrategy *strategy ) { mAttachmentStrategy = strategy; } void KMPrintCommand::setPrintPreview( bool preview ) { mPrintPreview = preview; } KMCommand::Result KMPrintCommand::execute() { // the window will be deleted after printout is performed, in KMReaderWin::slotPrintMsg() KMReaderWin *printerWin = new KMReaderWin( 0, kmkernel->mainWin(), 0, 0 ); printerWin->setPrinting( true ); printerWin->readConfig(); if ( mHeaderStyle != 0 && mHeaderStrategy != 0 ) printerWin->setHeaderStyleAndStrategy( mHeaderStyle, mHeaderStrategy ); printerWin->setHtmlOverride( mHtmlOverride ); printerWin->setHtmlLoadExtOverride( mHtmlLoadExtOverride ); printerWin->setUseFixedFont( mUseFixedFont ); printerWin->setOverrideEncoding( mEncoding ); printerWin->cssHelper()->setPrintFont( mOverrideFont ); printerWin->setDecryptMessageOverwrite( true ); if ( mAttachmentStrategy != 0 ) printerWin->setAttachmentStrategy( mAttachmentStrategy ); if(mPrintPreview) printerWin->viewer()->printPreviewMessage( retrievedMessage() ); else printerWin->viewer()->printMessage(retrievedMessage()); return OK; } KMSetStatusCommand::KMSetStatusCommand( const MessageStatus& status, const Akonadi::Item::List &items, bool invert ) : KMCommand( 0, items ), mStatus( status ), mInvertMark( invert ) { setDeletesItself(true); } KMCommand::Result KMSetStatusCommand::execute() { bool parentStatus = false; // Toggle actions on threads toggle the whole thread // depending on the state of the parent. if ( mInvertMark ) { const Akonadi::Item first = retrievedMsgs().first(); MessageStatus pStatus; pStatus.setStatusFromFlags( first.flags() ); if ( pStatus & mStatus ) parentStatus = true; else parentStatus = false; } Akonadi::Item::List itemsToModify; foreach( const Akonadi::Item &it, retrievedMsgs() ) { if ( mInvertMark ) { //kDebug()<<" item ::"<disableRevisionCheck(); modifyJob->setIgnorePayload( true ); connect( modifyJob, SIGNAL(result(KJob*)), this, SLOT(slotModifyItemDone(KJob*)) ); } return OK; } void KMSetStatusCommand::slotModifyItemDone( KJob * job ) { if ( job && job->error() ) { kWarning() << " Error trying to set item status:" << job->errorText(); } deleteLater(); } KMSetTagCommand::KMSetTagCommand( const Akonadi::Tag::List &tags, const QList &item, SetTagMode mode ) : mTags( tags ) , mItem( item ) , mMode( mode ) { setDeletesItself(true); } KMCommand::Result KMSetTagCommand::execute() { Q_FOREACH( const Akonadi::Tag &tag, mTags ) { if ( !tag.isValid() ) { Akonadi::TagCreateJob *createJob = new Akonadi::TagCreateJob(tag, this); connect( createJob, SIGNAL(result(KJob*)), this, SLOT(slotModifyItemDone(KJob*)) ); } else { mCreatedTags << tag; } } if ( mCreatedTags.size() == mTags.size() ) { setTags(); } else { deleteLater(); } return OK; } void KMSetTagCommand::slotTagCreateDone(KJob* job) { if ( job && job->error() ) { kWarning() << " Error trying to create tag:" << job->errorText(); deleteLater(); return; } Akonadi::TagCreateJob* createJob = static_cast(job); mCreatedTags << createJob->tag(); if ( mCreatedTags.size() == mTags.size() ) { setTags(); } else { deleteLater(); } } void KMSetTagCommand::setTags() { Akonadi::Item::List itemsToModify; Q_FOREACH( const Akonadi::Item& i, mItem ) { Akonadi::Item item(i); if ( mMode == CleanExistingAndAddNew ){ item.clearTags(); } if (mMode == KMSetTagCommand::Toggle) { Q_FOREACH( const Akonadi::Tag &tag, mCreatedTags ) { if ( item.hasTag(tag) ) { item.clearTag(tag); } else { item.setTag(tag); } } } else { item.setTags(mCreatedTags); } itemsToModify << item; } Akonadi::ItemModifyJob *modifyJob = new Akonadi::ItemModifyJob( itemsToModify, this ); modifyJob->disableRevisionCheck(); modifyJob->setIgnorePayload( true ); connect( modifyJob, SIGNAL(result(KJob*)), this, SLOT(slotModifyItemDone(KJob*)) ); if(!mCreatedTags.isEmpty()) { KMKernel::self()->updatePaneTagComboBox(); } } void KMSetTagCommand::slotModifyItemDone( KJob * job ) { if ( job && job->error() ) { kWarning() << " Error trying to set item status:" << job->errorText(); } deleteLater(); } KMFilterActionCommand::KMFilterActionCommand( QWidget *parent, const QVector &msgListId, const QString &filterId) : KMCommand( parent ), mMsgListId(msgListId), mFilterId( filterId ) { } KMCommand::Result KMFilterActionCommand::execute() { #ifndef QT_NO_CURSOR MessageViewer::KCursorSaver busy( MessageViewer::KBusyPtr::busy() ); #endif int msgCount = 0; const int msgCountToFilter = mMsgListId.count(); ProgressItem* progressItem = ProgressManager::createProgressItem ( QLatin1String("filter")+ProgressManager::getUniqueID(), i18n( "Filtering messages" ), QString(),true, KPIM::ProgressItem::Unknown ); progressItem->setTotalItems( msgCountToFilter ); foreach ( const qlonglong &id, mMsgListId ) { int diff = msgCountToFilter - ++msgCount; if ( diff < 10 || !( msgCount % 10 ) || msgCount <= 10 ) { progressItem->updateProgress(); const QString statusMsg = i18n( "Filtering message %1 of %2", msgCount, msgCountToFilter ); KPIM::BroadcastStatus::instance()->setStatusMsg( statusMsg ); qApp->processEvents( QEventLoop::ExcludeUserInputEvents, 50 ); } MailCommon::FilterManager::instance()->filter( Akonadi::Item(id), mFilterId, QString() ); progressItem->incCompletedItems(); } progressItem->setComplete(); progressItem = 0; return OK; } KMMetaFilterActionCommand::KMMetaFilterActionCommand( const QString &filterId, KMMainWidget *main ) : QObject( main ), mFilterId( filterId ), mMainWidget( main ) { } void KMMetaFilterActionCommand::start() { KMCommand *filterCommand = new KMFilterActionCommand( mMainWidget, mMainWidget->messageListPane()->selectionAsMessageItemListId() , mFilterId); filterCommand->start(); } KMMailingListFilterCommand::KMMailingListFilterCommand( QWidget *parent, const Akonadi::Item &msg ) : KMCommand( parent, msg ) { } KMCommand::Result KMMailingListFilterCommand::execute() { QByteArray name; QString value; Akonadi::Item item = retrievedMessage(); KMime::Message::Ptr msg = MessageCore::Util::message( item ); if ( !msg ) return Failed; if ( !MailingList::name( msg, name, value ).isEmpty() ) { FilterIf->openFilterDialog( false ); FilterIf->createFilter( name, value ); return OK; } else { return Failed; } } KMCopyCommand::KMCopyCommand( const Akonadi::Collection& destFolder, const QList &msgList) :KMCommand( 0, msgList ), mDestFolder( destFolder ) { } KMCopyCommand::KMCopyCommand( const Akonadi::Collection& destFolder, const Akonadi::Item& msg) :KMCommand( 0,msg ), mDestFolder( destFolder ) { } KMCommand::Result KMCopyCommand::execute() { setDeletesItself( true ); QList listItem = retrievedMsgs(); Akonadi::ItemCopyJob *job = new Akonadi::ItemCopyJob( listItem, Akonadi::Collection(mDestFolder.id()),this ); connect( job, SIGNAL(result(KJob*)), this, SLOT(slotCopyResult(KJob*)) ); return OK; } void KMCopyCommand::slotCopyResult( KJob * job ) { if ( job->error() ) { // handle errors showJobError(job); setResult( Failed ); } deleteLater(); } KMMoveCommand::KMMoveCommand( const Akonadi::Collection& destFolder, const QList &msgList, MessageList::Core::MessageItemSetReference ref) : KMCommand( 0, msgList ), mDestFolder( destFolder ), mProgressItem( 0 ), mRef( ref ) { fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMMoveCommand::KMMoveCommand( const Akonadi::Collection& destFolder, const Akonadi::Item& msg , MessageList::Core::MessageItemSetReference ref) : KMCommand( 0, msg ), mDestFolder( destFolder ), mProgressItem( 0 ), mRef( ref ) { fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } void KMMoveCommand::slotMoveResult( KJob * job ) { if ( job->error() ) { // handle errors showJobError(job); completeMove( Failed ); } else completeMove( OK ); } KMCommand::Result KMMoveCommand::execute() { #ifndef QT_NO_CURSOR MessageViewer::KCursorSaver busy( MessageViewer::KBusyPtr::busy() ); #endif setEmitsCompletedItself( true ); setDeletesItself( true ); if ( mDestFolder.isValid() ) { Akonadi::ItemMoveJob *job = new Akonadi::ItemMoveJob( retrievedMsgs(), mDestFolder, this ); connect( job, SIGNAL(result(KJob*)), this, SLOT(slotMoveResult(KJob*)) ); // group by source folder for undo Akonadi::Item::List items = retrievedMsgs(); std::sort( items.begin(), items.end(), boost::bind( &Akonadi::Item::storageCollectionId, _1 ) < boost::bind( &Akonadi::Item::storageCollectionId, _2 ) ); Akonadi::Collection parent; int undoId = -1; foreach ( const Akonadi::Item &item, items ) { if ( item.storageCollectionId() <= 0 ) continue; if ( parent.id() != item.storageCollectionId() ) { parent = Akonadi::Collection( item.storageCollectionId() ); undoId = kmkernel->undoStack()->newUndoAction( parent, mDestFolder ); } kmkernel->undoStack()->addMsgToAction( undoId, item ); } } else { const QList retrievedList = retrievedMsgs(); if ( !retrievedList.isEmpty() ) { Akonadi::ItemDeleteJob *job = new Akonadi::ItemDeleteJob( retrievedList, this ); connect( job, SIGNAL(result(KJob*)), this, SLOT(slotMoveResult(KJob*)) ); } } // TODO set SSL state according to source and destfolder connection? Q_ASSERT( !mProgressItem ); mProgressItem = ProgressManager::createProgressItem (QLatin1String("move")+ProgressManager::getUniqueID(), mDestFolder.isValid() ? i18n( "Moving messages" ) : i18n( "Deleting messages" ), QString(), true, KPIM::ProgressItem::Unknown ); mProgressItem->setUsesBusyIndicator( true ); connect( mProgressItem, SIGNAL(progressItemCanceled(KPIM::ProgressItem*)), this, SLOT(slotMoveCanceled()) ); return OK; } void KMMoveCommand::completeMove( Result result ) { if ( mProgressItem ) { mProgressItem->setComplete(); mProgressItem = 0; } setResult( result ); emit moveDone(this); emit completed( this ); deleteLater(); } void KMMoveCommand::slotMoveCanceled() { completeMove( Canceled ); } // srcFolder doesn't make much sense for searchFolders KMTrashMsgCommand::KMTrashMsgCommand( const Akonadi::Collection& srcFolder, const QList &msgList,MessageList::Core::MessageItemSetReference ref ) :KMMoveCommand( findTrashFolder( srcFolder ), msgList, ref) { } KMTrashMsgCommand::KMTrashMsgCommand( const Akonadi::Collection& srcFolder, const Akonadi::Item & msg,MessageList::Core::MessageItemSetReference ref ) :KMMoveCommand( findTrashFolder( srcFolder ), msg, ref) { } Akonadi::Collection KMTrashMsgCommand::findTrashFolder( const Akonadi::Collection& folder ) { Akonadi::Collection col = CommonKernel->trashCollectionFromResource( folder ); if ( !col.isValid() ) { col = CommonKernel->trashCollectionFolder(); } if ( folder != col ) return col; return Akonadi::Collection(); } KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( QWidget *parent, const Akonadi::Item& msg ) : KMCommand( parent, msg ) { fetchScope().fetchFullPayload( true ); } KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( QWidget *parent, const QList& msgs ) : KMCommand( parent, msgs ) { fetchScope().fetchFullPayload( true ); } KMCommand::Result KMSaveAttachmentsCommand::execute() { QList contentsToSave; foreach( const Akonadi::Item &item, retrievedMsgs() ) { if ( item.hasPayload() ) { contentsToSave += MessageViewer::Util::extractAttachments( item.payload().get() ); } else { kWarning() << "Retrieved item has no payload? Ignoring for saving the attachments"; } } if ( MessageViewer::Util::saveAttachments( contentsToSave, parentWidget() ) ) return OK; return Failed; } KMResendMessageCommand::KMResendMessageCommand( QWidget *parent, const Akonadi::Item &msg ) :KMCommand( parent, msg ) { fetchScope().fetchFullPayload( true ); fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMCommand::Result KMResendMessageCommand::execute() { Akonadi::Item item = retrievedMessage(); KMime::Message::Ptr msg = MessageCore::Util::message( item ); if ( !msg ) return Failed; MessageFactory factory( msg, item.id(), MailCommon::Util::updatedCollection(item.parentCollection()) ); factory.setIdentityManager( KMKernel::self()->identityManager() ); factory.setFolderIdentity( MailCommon::Util::folderIdentity( item ) ); KMime::Message::Ptr newMsg = factory.createResend(); newMsg->contentType()->setCharset( MessageViewer::NodeHelper::charset( msg.get() ) ); KMail::Composer * win = KMail::makeComposer(); if(msg->headerByType( "Reply-To" )) { const QString replyTo = msg->headerByType( "Reply-To" )->asUnicodeString(); win->setCurrentReplyTo(replyTo); } bool lastEncrypt = false; bool lastSign = false; KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg); - win->setMessage( newMsg, lastSign, lastEncrypt, false, true ); + win->setMessage( newMsg, lastSign, lastEncrypt, true, true ); win->show(); return OK; } KMShareImageCommand::KMShareImageCommand(const KUrl &url, QWidget *parent) : KMCommand( parent ), mUrl(url) { } KMCommand::Result KMShareImageCommand::execute() { KMime::Message::Ptr msg( new KMime::Message ); uint id = 0; MessageHelper::initHeader( msg, KMKernel::self()->identityManager(),id ); msg->contentType()->setCharset("utf-8"); KMail::Composer * win = KMail::makeComposer(msg, false, false,KMail::Composer::New, id); win->setFocusToSubject(); win->addAttachment(mUrl, i18n("Image")); win->show(); return OK; } diff --git a/messagecomposer/composer/composerviewbase.cpp b/messagecomposer/composer/composerviewbase.cpp index b81a5512f4..bd4200264c 100644 --- a/messagecomposer/composer/composerviewbase.cpp +++ b/messagecomposer/composer/composerviewbase.cpp @@ -1,1894 +1,1910 @@ /* Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Leo Franchi 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 "composerviewbase.h" #include "attachment/attachmentcontrollerbase.h" #include "attachment/attachmentmodel.h" #include "composer/signaturecontroller.h" #include "composer/kmeditor.h" #include "emailaddressresolvejob.h" #include "keyresolver.h" #include "part/globalpart.h" #include "kleo_util.h" #include "part/infopart.h" #include "composer.h" #include "utils/util.h" #include "imagescaling/imagescalingutils.h" #include "agents/sendlateragent/sendlaterinfo.h" #include "agents/sendlateragent/sendlaterutil.h" #include #include "helper/messagehelper.h" #include #include "settings/messagecomposersettings.h" #include #include #ifndef QT_NO_CURSOR #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static QStringList encodeIdn( const QStringList &emails ) { QStringList encoded; foreach ( const QString &email, emails ) encoded << KPIMUtils::normalizeAddressesAndEncodeIdn( email ); return encoded; } MessageComposer::ComposerViewBase::ComposerViewBase ( QObject* parent, QWidget *parentGui) : QObject ( parent ) , m_msg( KMime::Message::Ptr( new KMime::Message ) ) , m_attachmentController( 0 ) , m_attachmentModel( 0 ) , m_signatureController( 0 ) , m_recipientsEditor( 0 ) , m_identityCombo( 0 ) , m_identMan( 0 ) , m_editor( 0 ) , m_transport( 0 ) , m_fccCombo( 0 ) , m_parentWidget( parentGui ) , m_sign( false ) , m_encrypt( false ) , m_neverEncrypt( false ) , m_mdnRequested( false ) , m_urgent( false ) , m_cryptoMessageFormat( Kleo::AutoFormat ) , m_pendingQueueJobs( 0 ) , m_autoSaveTimer( 0 ) , m_autoSaveErrorShown( false ) , m_autoSaveInterval( 1 * 1000 * 60 ) // default of 1 min , mSendLaterInfo (0) { m_charsets << "utf-8"; // default, so we have a backup in case client code forgot to set. initAutoSave(); } MessageComposer::ComposerViewBase::~ComposerViewBase() { delete mSendLaterInfo; } bool MessageComposer::ComposerViewBase::isComposing() const { return !m_composers.isEmpty(); } -void MessageComposer::ComposerViewBase::setMessage ( const KMime::Message::Ptr& msg ) +void MessageComposer::ComposerViewBase::setMessage(const KMime::Message::Ptr &newMsg) +{ + setMessage(newMsg, false); // Do not decrypt message by default (old behaviour) +} + +void MessageComposer::ComposerViewBase::setMessage ( const KMime::Message::Ptr& msg, bool allowDecryption ) { foreach( MessageCore::AttachmentPart::Ptr attachment, m_attachmentModel->attachments() ) m_attachmentModel->removeAttachment( attachment ); m_msg = msg; m_recipientsEditor->clear(); m_recipientsEditor->setRecipientString( m_msg->to()->mailboxes(), MessageComposer::Recipient::To ); m_recipientsEditor->setRecipientString( m_msg->cc()->mailboxes(), MessageComposer::Recipient::Cc ); m_recipientsEditor->setRecipientString( m_msg->bcc()->mailboxes(), MessageComposer::Recipient::Bcc ); m_recipientsEditor->setFocusBottom(); // If we are loading from a draft, load unexpanded aliases as well if( m_msg->hasHeader( "X-KMail-UnExpanded-To" ) ) { const QStringList spl = m_msg->headerByType( "X-KMail-UnExpanded-To" )->asUnicodeString().split( QLatin1String( "," ) ); foreach( const QString& addr, spl ) m_recipientsEditor->addRecipient( addr, MessageComposer::Recipient::To ); } if( m_msg->hasHeader( "X-KMail-UnExpanded-CC" ) ) { const QStringList spl = m_msg->headerByType( "X-KMail-UnExpanded-CC" )->asUnicodeString().split( QLatin1String( "," ) ); foreach( const QString& addr, spl ) m_recipientsEditor->addRecipient( addr, MessageComposer::Recipient::Cc ); } if( m_msg->hasHeader( "X-KMail-UnExpanded-BCC" ) ) { const QStringList spl = m_msg->headerByType( "X-KMail-UnExpanded-BCC" )->asUnicodeString().split( QLatin1String( "," ) ); foreach( const QString& addr, spl ) m_recipientsEditor->addRecipient( addr, MessageComposer::Recipient::Bcc ); } // First, we copy the message and then parse it to the object tree parser. // The otp gets the message text out of it, in textualContent(), and also decrypts // the message if necessary. KMime::Content *msgContent = new KMime::Content; msgContent->setContent( m_msg->encodedContent() ); msgContent->parse(); MessageViewer::EmptySource emptySource; MessageViewer::ObjectTreeParser otp( &emptySource );//All default are ok + emptySource.setAllowDecryption(allowDecryption); otp.parseObjectTree( msgContent ); // Load the attachments MessageCore::AttachmentCollector ac; ac.collectAttachmentsFrom( msgContent ); std::vector::const_iterator end( ac.attachments().end() ); for ( std::vector::const_iterator it = ac.attachments().begin(); it != end ; ++it ) { - addAttachmentPart( *it ); + if (otp.nodeHelper()->partMetaData((*it)->parent()).isEncrypted) { // gpg mime has a msg.asc as subelement, that is recognized as attachment + MessageCore::AttachmentCollector acEnc; + acEnc.collectAttachmentsFrom(otp.nodeHelper()->decryptedNodeForContent((*it)->parent())); + std::vector::const_iterator endEnc( acEnc.attachments().end() ); + for ( std::vector::const_iterator itEnc = acEnc.attachments().begin(); + itEnc != endEnc ; ++itEnc ) { + addAttachmentPart( *itEnc ); + } + } else { + addAttachmentPart( *it ); + } } int transportId = -1; if ( m_msg->headerByType( "X-KMail-Transport" ) ) transportId = m_msg->headerByType( "X-KMail-Transport" )->asUnicodeString().toInt(); const MailTransport::Transport *transport = MailTransport::TransportManager::self()->transportById( transportId ); if ( transport ) m_transport->setCurrentTransport( transport->id() ); // Set the HTML text and collect HTML images if ( !otp.htmlContent().isEmpty() ) { m_editor->setHtml( otp.htmlContent() ); emit enableHtml(); collectImages( m_msg.get() ); } else { m_editor->setPlainText( otp.plainTextContent() ); } if ( m_msg->headerByType( "X-KMail-CursorPos" ) ) { m_editor->setCursorPositionFromStart( m_msg->headerByType( "X-KMail-CursorPos" )->asUnicodeString().toInt() ); } delete msgContent; } void MessageComposer::ComposerViewBase::updateTemplate ( const KMime::Message::Ptr& msg ) { // First, we copy the message and then parse it to the object tree parser. // The otp gets the message text out of it, in textualContent(), and also decrypts // the message if necessary. KMime::Content *msgContent = new KMime::Content; msgContent->setContent( msg->encodedContent() ); msgContent->parse(); MessageViewer::EmptySource emptySource; MessageViewer::ObjectTreeParser otp( &emptySource );//All default are ok otp.parseObjectTree( msgContent ); // Set the HTML text and collect HTML images if ( !otp.htmlContent().isEmpty() ) { m_editor->setHtml( otp.htmlContent() ); emit enableHtml(); collectImages( msg.get() ); } else { m_editor->setPlainText( otp.plainTextContent() ); } if ( msg->headerByType( "X-KMail-CursorPos" ) ) { m_editor->setCursorPositionFromStart( m_msg->headerByType( "X-KMail-CursorPos" )->asUnicodeString().toInt() ); } delete msgContent; } void MessageComposer::ComposerViewBase::send ( MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn, bool checkMailDispatcher ) { mSendMethod = method; mSaveIn = saveIn; #ifndef QT_NO_CURSOR MessageViewer::KCursorSaver busy( MessageViewer::KBusyPtr::busy() ); #endif const KPIMIdentities::Identity identity = identityManager()->identityForUoid( m_identityCombo->currentIdentity() ); if(identity.attachVcard() && m_attachmentController->attachOwnVcard()) { const QString vcardFileName = identity.vCardFile(); if(!vcardFileName.isEmpty()) { m_attachmentController->addAttachmentUrlSync(KUrl(vcardFileName)); } } m_msg->setHeader( new KMime::Headers::Generic( "X-KMail-Transport", m_msg.get(), QString::number(m_transport->currentTransportId()), "utf-8" ) ); m_msg->setHeader( new KMime::Headers::Generic( "X-KMail-Fcc", m_msg.get(), QString::number( m_fccCollection.id() ) , "utf-8" ) ); m_msg->setHeader( new KMime::Headers::Generic( "X-KMail-Identity", m_msg.get(), QString::number( identity.uoid() ), "utf-8" )); // Save the quote prefix which is used for this message. Each message can have // a different quote prefix, for example depending on the original sender. if ( m_editor->quotePrefixName().isEmpty() ) m_msg->removeHeader( "X-KMail-QuotePrefix" ); else m_msg->setHeader( new KMime::Headers::Generic("X-KMail-QuotePrefix", m_msg.get(), m_editor->quotePrefixName(), "utf-8" ) ); if ( m_editor->isFormattingUsed() ) { kDebug() << "Html mode"; m_msg->setHeader( new KMime::Headers::Generic("X-KMail-Markup", m_msg.get(), QLatin1String( "true" ), "utf-8" ) ); } else { m_msg->removeHeader( "X-KMail-Markup" ); kDebug() << "Plain text"; } if ( m_editor->isFormattingUsed() && inlineSigningEncryptionSelected() ) { QString keepBtnText = m_encrypt ? m_sign ? i18n( "&Keep markup, do not sign/encrypt" ) : i18n( "&Keep markup, do not encrypt" ) : i18n( "&Keep markup, do not sign" ); QString yesBtnText = m_encrypt ? m_sign ? i18n("Sign/Encrypt (delete markup)") : i18n( "Encrypt (delete markup)" ) : i18n( "Sign (delete markup)" ); int ret = KMessageBox::warningYesNoCancel( m_parentWidget, i18n("

Inline signing/encrypting of HTML messages is not possible;

" "

do you want to delete your markup?

"), i18n("Sign/Encrypt Message?"), KGuiItem( yesBtnText ), KGuiItem( keepBtnText ) ); if ( KMessageBox::Cancel == ret ) { return; } if ( KMessageBox::No == ret ) { m_encrypt = false; m_sign = false; } else { emit disableHtml( NoConfirmationNeeded ); } } if ( m_neverEncrypt && saveIn != MessageComposer::MessageSender::SaveInNone ) { // we can't use the state of the mail itself, to remember the // signing and encryption state, so let's add a header instead m_msg->setHeader( new KMime::Headers::Generic( "X-KMail-SignatureActionEnabled", m_msg.get(), m_sign ? QLatin1String( "true" ): QLatin1String( "false" ), "utf-8" ) ); m_msg->setHeader( new KMime::Headers::Generic( "X-KMail-EncryptActionEnabled", m_msg.get(), m_encrypt ? QLatin1String( "true" ) : QLatin1String( "false" ), "utf-8" ) ); m_msg->setHeader( new KMime::Headers::Generic( "X-KMail-CryptoMessageFormat", m_msg.get(), QString::number( m_cryptoMessageFormat ), "utf-8" ) ); } else { m_msg->removeHeader( "X-KMail-SignatureActionEnabled" ); m_msg->removeHeader( "X-KMail-EncryptActionEnabled" ); m_msg->removeHeader( "X-KMail-CryptoMessageFormat" ); } if( mSendMethod == MessageComposer::MessageSender::SendImmediate && checkMailDispatcher) MessageComposer::Util::sendMailDispatcherIsOnline( m_parentWidget ); readyForSending(); } void MessageComposer::ComposerViewBase::setCustomHeader( const QMap&customHeader ) { m_customHeader = customHeader; } void MessageComposer::ComposerViewBase::readyForSending() { kDebug() << "Entering readyForSending"; if( !m_msg ) { kDebug() << "m_msg == 0!"; return; } if( !m_composers.isEmpty() ) { // This may happen if e.g. the autosave timer calls applyChanges. kDebug() << "Called while composer active; ignoring."; return; } // first, expand all addresses MessageComposer::EmailAddressResolveJob *job = new MessageComposer::EmailAddressResolveJob( this ); job->setFrom( from() ); job->setTo( m_recipientsEditor->recipientStringList( MessageComposer::Recipient::To ) ); job->setCc( m_recipientsEditor->recipientStringList( MessageComposer::Recipient::Cc ) ); job->setBcc( m_recipientsEditor->recipientStringList( MessageComposer::Recipient::Bcc ) ); connect( job, SIGNAL(result(KJob*)), SLOT(slotEmailAddressResolved(KJob*)) ); job->start(); } void MessageComposer::ComposerViewBase::slotEmailAddressResolved ( KJob* job ) { if ( job->error() ) { qWarning() << "An error occurred while resolving the email addresses:" << job->errorString(); // This error could be caused by a broken search infrastructure, so we ignore it for now // to not block sending emails completely. } bool autoresizeImage = MessageComposer::MessageComposerSettings::self()->autoResizeImageEnabled(); const MessageComposer::EmailAddressResolveJob *resolveJob = qobject_cast( job ); if( mSaveIn == MessageComposer::MessageSender::SaveInNone ) { mExpandedFrom = resolveJob->expandedFrom(); mExpandedTo = resolveJob->expandedTo(); mExpandedCc = resolveJob->expandedCc(); mExpandedBcc = resolveJob->expandedBcc(); if (autoresizeImage) { QStringList listEmails; listEmails<< mExpandedFrom; listEmails<< mExpandedTo; listEmails<< mExpandedCc; listEmails<< mExpandedBcc; autoresizeImage = MessageComposer::Utils::filterRecipients(listEmails); } } else { // saved to draft, so keep the old values, not very nice. mExpandedFrom = from(); foreach( const MessageComposer::Recipient::Ptr &r, m_recipientsEditor->recipients() ) { switch( r->type() ) { case MessageComposer::Recipient::To: mExpandedTo << r->email(); break; case MessageComposer::Recipient::Cc: mExpandedCc << r->email(); break; case MessageComposer::Recipient::Bcc: mExpandedBcc << r->email(); break; case MessageComposer::Recipient::Undefined: Q_ASSERT( !"Unknown recpient type!" ); break; } } QStringList unExpandedTo, unExpandedCc, unExpandedBcc; foreach( const QString &exp, resolveJob->expandedTo() ) { if( !mExpandedTo.contains( exp ) ) // this address was expanded, so save it explicitly unExpandedTo << exp; } foreach( const QString& exp, resolveJob->expandedCc() ) { if( !mExpandedCc.contains( exp ) ) unExpandedCc << exp; } foreach( const QString& exp, resolveJob->expandedBcc() ) { if( !mExpandedBcc.contains( exp ) ) // this address was expanded, so save it explicitly unExpandedBcc << exp; } m_msg->setHeader( new KMime::Headers::Generic( "X-KMail-UnExpanded-To", m_msg.get(), unExpandedTo.join( QLatin1String( ", " ) ).toLatin1() ) ); m_msg->setHeader( new KMime::Headers::Generic( "X-KMail-UnExpanded-CC", m_msg.get(), unExpandedCc.join( QLatin1String( ", " ) ).toLatin1() ) ); m_msg->setHeader( new KMime::Headers::Generic( "X-KMail-UnExpanded-BCC", m_msg.get(), unExpandedBcc.join( QLatin1String( ", " ) ).toLatin1() ) ); autoresizeImage = false; } Q_ASSERT(m_composers.isEmpty()); //composers should be empty. The caller of this function //checks for emptyness before calling it //so just ensure it actually is empty //and document it // we first figure out if we need to create multiple messages with different crypto formats // if so, we create a composer per format // if we aren't signing or encrypting, this just returns a single empty message if( m_neverEncrypt && mSaveIn != MessageComposer::MessageSender::SaveInNone ) { MessageComposer::Composer* composer = new MessageComposer::Composer; composer->setNoCrypto( true ); m_composers.append( composer ); } else { m_composers = generateCryptoMessages(); } if( m_composers.isEmpty() ) { emit failed( i18n( "It was not possible to create a message composer." ) ); return; } if (autoresizeImage) { if (MessageComposer::MessageComposerSettings::self()->askBeforeResizing()) { if (MessageComposer::Utils::containsImage(m_attachmentModel->attachments())) { const int rc = KMessageBox::warningYesNo( m_parentWidget,i18n("Do you want to resize images?"), i18n("Auto Resize Images"), KStandardGuiItem::yes(), KStandardGuiItem::no()); if (rc == KMessageBox::Yes) { autoresizeImage = true; } else { autoresizeImage = false; } } else { autoresizeImage = false; } } } // Compose each message and prepare it for queueing, sending, or storing foreach( MessageComposer::Composer* composer, m_composers ) { fillGlobalPart( composer->globalPart() ); m_editor->fillComposerTextPart( composer->textPart() ); fillInfoPart( composer->infoPart(), UseExpandedRecipients ); composer->addAttachmentParts( m_attachmentModel->attachments(), autoresizeImage ); connect( composer, SIGNAL(result(KJob*)), this, SLOT(slotSendComposeResult(KJob*)) ); composer->start(); kDebug() << "Started a composer for sending!"; } } namespace { // helper methods for reading encryption settings inline int encryptKeyNearExpiryWarningThresholdInDays() { if ( ! MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire() ) { return -1; } const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrKeyNearExpiryThresholdDays(); return qMax( 1, num ); } inline int signingKeyNearExpiryWarningThresholdInDays() { if ( ! MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire() ) { return -1; } const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnSignKeyNearExpiryThresholdDays(); return qMax( 1, num ); } inline int encryptRootCertNearExpiryWarningThresholdInDays() { if ( ! MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire() ) { return -1; } const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrRootNearExpiryThresholdDays(); return qMax( 1, num ); } inline int signingRootCertNearExpiryWarningThresholdInDays() { if ( ! MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire() ) { return -1; } const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnSignRootNearExpiryThresholdDays(); return qMax( 1, num ); } inline int encryptChainCertNearExpiryWarningThresholdInDays() { if ( ! MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire() ) { return -1; } const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrChaincertNearExpiryThresholdDays(); return qMax( 1, num ); } inline int signingChainCertNearExpiryWarningThresholdInDays() { if ( ! MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire() ) { return -1; } const int num = MessageComposer::MessageComposerSettings::self()->cryptoWarnSignChaincertNearExpiryThresholdDays();; return qMax( 1, num ); } inline bool encryptToSelf() { // return !Kpgp::Module::getKpgp() || Kpgp::Module::getKpgp()->encryptToSelf(); return MessageComposer::MessageComposerSettings::self()->cryptoEncryptToSelf(); } inline bool showKeyApprovalDialog() { return MessageComposer::MessageComposerSettings::self()->cryptoShowKeysForApproval(); } } // nameless namespace QList< MessageComposer::Composer* > MessageComposer::ComposerViewBase::generateCryptoMessages () { kDebug() << "filling crypto info"; Kleo::KeyResolver* keyResolver = new Kleo::KeyResolver( encryptToSelf(), showKeyApprovalDialog(), MessageComposer::MessageComposerSettings::self()->pgpAutoEncrypt(), m_cryptoMessageFormat, encryptKeyNearExpiryWarningThresholdInDays(), signingKeyNearExpiryWarningThresholdInDays(), encryptRootCertNearExpiryWarningThresholdInDays(), signingRootCertNearExpiryWarningThresholdInDays(), encryptChainCertNearExpiryWarningThresholdInDays(), signingChainCertNearExpiryWarningThresholdInDays() ); const KPIMIdentities::Identity &id = m_identMan->identityForUoidOrDefault( m_identityCombo->currentIdentity() ); QStringList encryptToSelfKeys; QStringList signKeys; bool signSomething = m_sign; bool doSignCompletely = m_sign; bool encryptSomething = m_encrypt; bool doEncryptCompletely = m_encrypt; //Add encryptionkeys from id to keyResolver kDebug() << id.pgpEncryptionKey().isEmpty() << id.smimeEncryptionKey().isEmpty(); if ( !id.pgpEncryptionKey().isEmpty() ) encryptToSelfKeys.push_back( QLatin1String( id.pgpEncryptionKey() ) ); if ( !id.smimeEncryptionKey().isEmpty() ) encryptToSelfKeys.push_back( QLatin1String( id.smimeEncryptionKey() ) ); if ( keyResolver->setEncryptToSelfKeys( encryptToSelfKeys ) != Kpgp::Ok ) { kDebug() << "Failed to set encryptoToSelf keys!"; return QList< MessageComposer::Composer* >(); } //Add signingkeys from id to keyResolver if ( !id.pgpSigningKey().isEmpty() ) signKeys.push_back( QLatin1String( id.pgpSigningKey() ) ); if ( !id.smimeSigningKey().isEmpty() ) signKeys.push_back( QLatin1String( id.smimeSigningKey() ) ); if ( keyResolver->setSigningKeys( signKeys ) != Kpgp::Ok ) { kDebug() << "Failed to set signing keys!"; return QList< MessageComposer::Composer* >(); } foreach( MessageCore::AttachmentPart::Ptr attachment, m_attachmentModel->attachments() ) { if( attachment->isSigned() ) { signSomething = true; } else { doEncryptCompletely = false; } if( attachment->isEncrypted() ) { encryptSomething = true; } else { doSignCompletely = false; } } QStringList recipients( mExpandedTo ), bcc( mExpandedBcc ); recipients.append( mExpandedCc ); keyResolver->setPrimaryRecipients( recipients ); keyResolver->setSecondaryRecipients( bcc ); bool result = true; signSomething = determineWhetherToSign( doSignCompletely, keyResolver,signSomething, result ); if(!result) { /// TODO handle failure kDebug() << "determineWhetherToSign: failed to resolve keys! oh noes"; emit failed( i18n( "Failed to resolve keys. Please report a bug." ) ); return QList< MessageComposer::Composer*>(); } encryptSomething = determineWhetherToEncrypt( doEncryptCompletely,keyResolver,encryptSomething, signSomething, result ); if(!result) { /// TODO handle failure kDebug() << "determineWhetherToEncrypt: failed to resolve keys! oh noes"; emit failed( i18n( "Failed to resolve keys. Please report a bug." ) ); return QList< MessageComposer::Composer*>(); } //No encryption or signing is needed if( !signSomething && !encryptSomething ) { return QList< MessageComposer::Composer* >() << new MessageComposer::Composer(); } const Kpgp::Result kpgpResult = keyResolver->resolveAllKeys( signSomething, encryptSomething ); if ( kpgpResult == Kpgp::Canceled ) { kDebug() << "resolveAllKeys: one key resolution canceled by user"; return QList< MessageComposer::Composer*>(); } else if ( kpgpResult != Kpgp::Ok ) { // TODO handle failure kDebug() << "resolveAllKeys: failed to resolve keys! oh noes"; emit failed( i18n( "Failed to resolve keys. Please report a bug." ) ); return QList< MessageComposer::Composer*>(); } kDebug() << "done resolving keys:"; QList< MessageComposer::Composer* > composers; if( encryptSomething || signSomething ) { Kleo::CryptoMessageFormat concreteFormat = Kleo::AutoFormat; for ( unsigned int i = 0 ; i < numConcreteCryptoMessageFormats ; ++i ) { concreteFormat = concreteCryptoMessageFormats[i]; if ( keyResolver->encryptionItems( concreteFormat ).empty() ) continue; if ( !(concreteFormat & m_cryptoMessageFormat) ) continue; MessageComposer::Composer* composer = new MessageComposer::Composer; if ( encryptSomething ) { std::vector encData = keyResolver->encryptionItems( concreteFormat ); std::vector::iterator it; std::vector::iterator end( encData.end() ); QList > > data; for( it = encData.begin(); it != end; ++it ) { QPair > p( it->recipients, it->keys ); data.append( p ); kDebug() << "got resolved keys for:" << it->recipients; } composer->setEncryptionKeys( data ); } if( signSomething ) { // find signing keys for this format std::vector signingKeys = keyResolver->signingKeys( concreteFormat ); composer->setSigningKeys( signingKeys ); } composer->setMessageCryptoFormat( concreteFormat ); composer->setSignAndEncrypt( signSomething, encryptSomething ); composers.append( composer ); } } else { MessageComposer::Composer* composer = new MessageComposer::Composer; composers.append( composer ); //If we canceled sign or encrypt be sure to change status in attachment. markAllAttachmentsForSigning(false); markAllAttachmentsForSigning(false); } if( composers.isEmpty() && ( signSomething || encryptSomething ) ) Q_ASSERT_X( false, "ComposerViewBase::fillCryptoInfo" , "No concrete sign or encrypt method selected"); return composers; } void MessageComposer::ComposerViewBase::fillGlobalPart ( MessageComposer::GlobalPart* globalPart ) { globalPart->setParentWidgetForGui( m_parentWidget ); globalPart->setCharsets( m_charsets ); globalPart->setMDNRequested( m_mdnRequested ); } void MessageComposer::ComposerViewBase::fillInfoPart ( MessageComposer::InfoPart* infoPart, MessageComposer::ComposerViewBase::RecipientExpansion expansion ) { // TODO splitAddressList and expandAliases ugliness should be handled by a // special AddressListEdit widget... (later: see RecipientsEditor) if ( m_fccCombo ) { infoPart->setFcc( QString::number( m_fccCombo->currentCollection().id() ) ); } else { if ( m_fccCollection.isValid() ) { infoPart->setFcc( QString::number( m_fccCollection.id() ) ); } } infoPart->setTransportId( m_transport->currentTransportId() ); infoPart->setReplyTo( replyTo() ); if ( expansion == UseExpandedRecipients ) { infoPart->setFrom( mExpandedFrom ); infoPart->setTo( mExpandedTo ); infoPart->setCc( mExpandedCc ); infoPart->setBcc( mExpandedBcc ); } else { infoPart->setFrom( from() ); infoPart->setTo( m_recipientsEditor->recipientStringList( MessageComposer::Recipient::To ) ); infoPart->setCc( m_recipientsEditor->recipientStringList( MessageComposer::Recipient::Cc ) ); infoPart->setBcc( m_recipientsEditor->recipientStringList( MessageComposer::Recipient::Bcc ) ); } infoPart->setSubject( subject() ); infoPart->setUserAgent( QLatin1String( "KMail" ) ); infoPart->setUrgent( m_urgent ); if ( m_msg->inReplyTo() ) infoPart->setInReplyTo( m_msg->inReplyTo()->asUnicodeString() ); if ( m_msg->references() ) infoPart->setReferences( m_msg->references()->asUnicodeString() ); KMime::Headers::Base::List extras; if( m_msg->headerByType( "X-KMail-SignatureActionEnabled" ) ) extras << m_msg->headerByType( "X-KMail-SignatureActionEnabled" ); if( m_msg->headerByType( "X-KMail-EncryptActionEnabled" ) ) extras << m_msg->headerByType( "X-KMail-EncryptActionEnabled" ); if( m_msg->headerByType( "X-KMail-CryptoMessageFormat" ) ) extras << m_msg->headerByType( "X-KMail-CryptoMessageFormat" ); if( m_msg->headerByType( "X-KMail-UnExpanded-To" ) ) extras << m_msg->headerByType( "X-KMail-UnExpanded-To" ); if( m_msg->headerByType( "X-KMail-UnExpanded-CC" ) ) extras << m_msg->headerByType( "X-KMail-UnExpanded-CC" ); if( m_msg->headerByType( "X-KMail-UnExpanded-BCC" ) ) extras << m_msg->headerByType( "X-KMail-UnExpanded-BCC" ); if( m_msg->headerByType( "Organization" ) ) extras << m_msg->headerByType( "Organization" ); if( m_msg->headerByType( "X-KMail-Identity" ) ) extras << m_msg->headerByType( "X-KMail-Identity" ); if( m_msg->headerByType( "X-KMail-Transport" ) ) extras << m_msg->headerByType( "X-KMail-Transport" ); if( m_msg->headerByType( "X-KMail-Fcc" ) ) extras << m_msg->headerByType( "X-KMail-Fcc" ); if( m_msg->headerByType( "X-KMail-Drafts" ) ) extras << m_msg->headerByType( "X-KMail-Drafts" ); if( m_msg->headerByType( "X-KMail-Templates" ) ) extras << m_msg->headerByType( "X-KMail-Templates" ); if( m_msg->headerByType( "X-KMail-Link-Message" ) ) extras << m_msg->headerByType( "X-KMail-Link-Message" ); if( m_msg->headerByType( "X-KMail-Link-Type" ) ) extras << m_msg->headerByType( "X-KMail-Link-Type" ); if( m_msg->headerByType( "X-Face" ) ) extras << m_msg->headerByType( "X-Face" ); if( m_msg->headerByType( "X-KMail-FccDisabled") ) extras << m_msg->headerByType( "X-KMail-FccDisabled"); infoPart->setExtraHeaders( extras ); } void MessageComposer::ComposerViewBase::slotSendComposeResult( KJob* job ) { kDebug() << "compose job might have error error" << job->error() << "errorString" << job->errorString(); Q_ASSERT( dynamic_cast< MessageComposer::Composer* >( job ) ); MessageComposer::Composer* composer = static_cast< MessageComposer::Composer* >( job ); if( composer->error() == MessageComposer::Composer::NoError ) { Q_ASSERT( m_composers.contains( composer ) ); // The messages were composed successfully. kDebug() << "NoError."; const int numberOfMessage( composer->resultMessages().size() ); for( int i = 0; i < numberOfMessage; ++i ) { if ( mSaveIn == MessageComposer::MessageSender::SaveInNone ) { queueMessage( composer->resultMessages().at( i ), composer ); } else { saveMessage( composer->resultMessages().at( i ), mSaveIn ); } } saveRecentAddresses( composer->resultMessages().at( 0 ) ); } else if( composer->error() == MessageComposer::Composer::UserCancelledError ) { // The job warned the user about something, and the user chose to return // to the message. Nothing to do. kDebug() << "UserCancelledError."; emit failed( i18n( "Job cancelled by the user" ) ); } else { kDebug() << "other Error."; QString msg; if( composer->error() == MessageComposer::Composer::BugError ) { msg = i18n( "Could not compose message: %1 \n Please report this bug.", job->errorString() ); } else { msg = i18n( "Could not compose message: %1", job->errorString() ); } emit failed( msg ); } m_composers.removeAll( composer ); } void MessageComposer::ComposerViewBase::saveRecentAddresses( KMime::Message::Ptr msg ) { foreach( const QByteArray& address, msg->to()->addresses() ) KPIM::RecentAddresses::self( MessageComposer::MessageComposerSettings::self()->config() )->add( QLatin1String( address ) ); foreach( const QByteArray& address, msg->cc()->addresses() ) KPIM::RecentAddresses::self( MessageComposer::MessageComposerSettings::self()->config() )->add( QLatin1String( address ) ); foreach( const QByteArray& address, msg->bcc()->addresses() ) KPIM::RecentAddresses::self( MessageComposer::MessageComposerSettings::self()->config() )->add( QLatin1String( address ) ); } void MessageComposer::ComposerViewBase::queueMessage( KMime::Message::Ptr message, MessageComposer::Composer* composer ) { const MessageComposer::InfoPart *infoPart = composer->infoPart(); MailTransport::MessageQueueJob *qjob = new MailTransport::MessageQueueJob( this ); qjob->setMessage( message ); qjob->transportAttribute().setTransportId( infoPart->transportId() ); if( mSendMethod == MessageComposer::MessageSender::SendLater ) qjob->dispatchModeAttribute().setDispatchMode( MailTransport::DispatchModeAttribute::Manual ); if( message->hasHeader( "X-KMail-FccDisabled" ) ) { qjob->sentBehaviourAttribute().setSentBehaviour( MailTransport::SentBehaviourAttribute::Delete ); } else if ( !infoPart->fcc().isEmpty() ) { qjob->sentBehaviourAttribute().setSentBehaviour( MailTransport::SentBehaviourAttribute::MoveToCollection ); const Akonadi::Collection sentCollection( infoPart->fcc().toLongLong() ); qjob->sentBehaviourAttribute().setMoveToCollection( sentCollection ); } else { qjob->sentBehaviourAttribute().setSentBehaviour( MailTransport::SentBehaviourAttribute::MoveToDefaultSentCollection ); } MessageComposer::Util::addSendReplyForwardAction(message, qjob); fillQueueJobHeaders( qjob, message, infoPart ); MessageCore::StringUtil::removePrivateHeaderFields( message, false ); QMapIterator customHeader(m_customHeader); while (customHeader.hasNext()) { customHeader.next(); message->setHeader( new KMime::Headers::Generic( customHeader.key(), message.get(), customHeader.value(),"utf-8") ); } message->assemble(); connect( qjob, SIGNAL(result(KJob*)), this, SLOT(slotQueueResult(KJob*)) ); m_pendingQueueJobs++; qjob->start(); kDebug() << "Queued a message."; } void MessageComposer::ComposerViewBase::slotQueueResult( KJob *job ) { m_pendingQueueJobs--; kDebug() << "mPendingQueueJobs" << m_pendingQueueJobs; Q_ASSERT( m_pendingQueueJobs >= 0 ); if( job->error() ) { kDebug() << "Failed to queue a message:" << job->errorString(); // There is not much we can do now, since all the MessageQueueJobs have been // started. So just wait for them to finish. // TODO show a message box or something QString msg = i18n( "There were problems trying to queue the message for sending: %1", job->errorString() ); if( m_pendingQueueJobs == 0 ) { emit failed( msg ); return; } } if( m_pendingQueueJobs == 0 ) { emit sentSuccessfully(); } } void MessageComposer::ComposerViewBase::fillQueueJobHeaders( MailTransport::MessageQueueJob* qjob, KMime::Message::Ptr message, const MessageComposer::InfoPart* infoPart ) { MailTransport::Transport *transport = MailTransport::TransportManager::self()->transportById( infoPart->transportId() ); if ( transport && transport->specifySenderOverwriteAddress() ) qjob->addressAttribute().setFrom( KPIMUtils::extractEmailAddress( KPIMUtils::normalizeAddressesAndEncodeIdn( transport->senderOverwriteAddress() ) ) ); else qjob->addressAttribute().setFrom( KPIMUtils::extractEmailAddress( KPIMUtils::normalizeAddressesAndEncodeIdn( infoPart->from() ) ) ); // if this header is not empty, it contains the real recipient of the message, either the primary or one of the // secondary recipients. so we set that to the transport job, while leaving the message itself alone. if( message->hasHeader( "X-KMail-EncBccRecipients" ) ) { KMime::Headers::Base* realTo = message->headerByType( "X-KMail-EncBccRecipients" ); qjob->addressAttribute().setTo( cleanEmailList( encodeIdn( realTo->asUnicodeString().split( QLatin1Char( '%' ) ) ) ) ); message->removeHeader( "X-KMail-EncBccRecipients" ); message->assemble(); kDebug() << "sending with-bcc encr mail to a/n recipient:" << qjob->addressAttribute().to(); } else { qjob->addressAttribute().setTo( cleanEmailList( encodeIdn( infoPart->to() ) ) ); qjob->addressAttribute().setCc( cleanEmailList( encodeIdn( infoPart->cc() ) ) ); qjob->addressAttribute().setBcc( cleanEmailList( encodeIdn( infoPart->bcc() ) ) ); } } void MessageComposer::ComposerViewBase::initAutoSave() { kDebug() << "initalising autosave"; // Ensure that the autosave directory exists. QDir dataDirectory( KStandardDirs::locateLocal( "data", QLatin1String( "kmail2/" ) ) ); if( !dataDirectory.exists( QLatin1String( "autosave" ) ) ) { kDebug() << "Creating autosave directory."; dataDirectory.mkdir( QLatin1String( "autosave" ) ); } // Construct a file name if ( m_autoSaveUUID.isEmpty() ) { m_autoSaveUUID = QUuid::createUuid().toString(); } updateAutoSave(); } void MessageComposer::ComposerViewBase::updateAutoSave() { if ( m_autoSaveInterval == 0 ) { delete m_autoSaveTimer; m_autoSaveTimer = 0; } else { if ( !m_autoSaveTimer ) { m_autoSaveTimer = new QTimer( this ); if ( m_parentWidget ) connect( m_autoSaveTimer, SIGNAL(timeout()), m_parentWidget, SLOT(autoSaveMessage()) ); else connect( m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(autoSaveMessage()) ); } m_autoSaveTimer->start( m_autoSaveInterval ); } } void MessageComposer::ComposerViewBase::cleanupAutoSave() { delete m_autoSaveTimer; m_autoSaveTimer = 0; if ( !m_autoSaveUUID.isEmpty() ) { kDebug() << "deleting autosave files" << m_autoSaveUUID; // Delete the autosave files QDir autoSaveDir( KStandardDirs::locateLocal( "data", QLatin1String( "kmail2/" ) ) + QLatin1String( "autosave" ) ); // Filter out only this composer window's autosave files QStringList autoSaveFilter; autoSaveFilter << m_autoSaveUUID + QLatin1String( "*" ); autoSaveDir.setNameFilters( autoSaveFilter ); // Return the files to be removed QStringList autoSaveFiles = autoSaveDir.entryList(); kDebug() << "There are" << autoSaveFiles.count() << "to be deleted."; // Delete each file foreach( const QString &file, autoSaveFiles ) { autoSaveDir.remove( file ); } m_autoSaveUUID.clear(); } } //----------------------------------------------------------------------------- void MessageComposer::ComposerViewBase::autoSaveMessage() { kDebug() << "Autosaving message"; if ( m_autoSaveTimer ) { m_autoSaveTimer->stop(); } if( !m_composers.isEmpty() ) { // This may happen if e.g. the autosave timer calls applyChanges. kDebug() << "Called while composer active; ignoring."; return; } MessageComposer::Composer * const composer = createSimpleComposer(); composer->setAutoSave( true ); m_composers.append( composer ); connect( composer, SIGNAL(result(KJob*)), this, SLOT(slotAutoSaveComposeResult(KJob*)) ); composer->start(); } void MessageComposer::ComposerViewBase::setAutoSaveFileName( const QString &fileName ) { m_autoSaveUUID = fileName; emit modified( true ); } void MessageComposer::ComposerViewBase::slotAutoSaveComposeResult( KJob *job ) { using MessageComposer::Composer; Q_ASSERT( dynamic_cast< Composer* >( job ) ); Composer* composer = static_cast< Composer* >( job ); if( composer->error() == Composer::NoError ) { Q_ASSERT( m_composers.contains( composer ) ); // The messages were composed successfully. Only save the first message, there should // only be one anyway, since crypto is disabled. kDebug() << "NoError."; writeAutoSaveToDisk( composer->resultMessages().first() ); Q_ASSERT( composer->resultMessages().size() == 1 ); if( m_autoSaveInterval > 0 ) { updateAutoSave(); } } else if( composer->error() == MessageComposer::Composer::UserCancelledError ) { // The job warned the user about something, and the user chose to return // to the message. Nothing to do. kDebug() << "UserCancelledError."; emit failed( i18n( "Job cancelled by the user" ), AutoSave ); } else { kDebug() << "other Error."; emit failed( i18n( "Could not autosave message: %1", job->errorString() ), AutoSave ); } m_composers.removeAll( composer ); } void MessageComposer::ComposerViewBase::writeAutoSaveToDisk( const KMime::Message::Ptr& message ) { const QString filename = KStandardDirs::locateLocal( "data", QLatin1String( "kmail2/" ) ) + QLatin1String( "autosave/" ) + m_autoSaveUUID; KSaveFile file( filename ); QString errorMessage; kDebug() << "Writing message to disk as" << filename; if( file.open() ) { file.setPermissions( QFile::ReadUser | QFile::WriteUser ); if( file.write( message->encodedContent() ) != static_cast( message->encodedContent().size() ) ) { errorMessage = i18n( "Could not write all data to file." ); } else { if( !file.finalize() ) { errorMessage = i18n( "Could not finalize the file." ); } } } else { errorMessage = i18n( "Could not open file." ); } if ( !errorMessage.isEmpty() ) { kWarning() << "Auto saving failed:" << errorMessage << file.errorString(); if ( !m_autoSaveErrorShown ) { KMessageBox::sorry( m_parentWidget, i18n( "Autosaving the message as %1 failed.\n" "%2\n" "Reason: %3", filename, errorMessage, file.errorString() ), i18n( "Autosaving Message Failed" ) ); // Error dialog shown, hide the errors the next time m_autoSaveErrorShown = true; } } else { // No error occurred, the next error should be shown again m_autoSaveErrorShown = false; } } void MessageComposer::ComposerViewBase::saveMessage( KMime::Message::Ptr message, MessageComposer::MessageSender::SaveIn saveIn ) { Akonadi::Collection target; const KPIMIdentities::Identity identity = identityManager()->identityForUoid( m_identityCombo->currentIdentity() ); message->date()->setDateTime( KDateTime::currentLocalDateTime() ); message->assemble(); Akonadi::Item item; item.setMimeType( QLatin1String( "message/rfc822" ) ); item.setPayload( message ); if ( !identity.isNull() ) { // we have a valid identity if ( saveIn == MessageComposer::MessageSender::SaveInTemplates ) { if ( !identity.templates().isEmpty() ) { // the user has specified a custom templates collection target = Akonadi::Collection( identity.templates().toLongLong() ); } } else { if ( !identity.drafts().isEmpty() ) { // the user has specified a custom drafts collection target = Akonadi::Collection( identity.drafts().toLongLong() ); } } Akonadi::CollectionFetchJob *saveMessageJob = new Akonadi::CollectionFetchJob( target, Akonadi::CollectionFetchJob::Base ); saveMessageJob->setProperty( "Akonadi::Item" , QVariant::fromValue( item ) ); QObject::connect( saveMessageJob, SIGNAL(result(KJob*)), this, SLOT(slotSaveMessage(KJob*)) ); } else { // preinitialize with the default collections if ( saveIn == MessageComposer::MessageSender::SaveInTemplates ) { target = Akonadi::SpecialMailCollections::self()->defaultCollection( Akonadi::SpecialMailCollections::Templates ); } else { target = Akonadi::SpecialMailCollections::self()->defaultCollection( Akonadi::SpecialMailCollections::Drafts ); } Akonadi::ItemCreateJob *create = new Akonadi::ItemCreateJob( item, target, this ); connect( create, SIGNAL(result(KJob*)), this, SLOT(slotCreateItemResult(KJob*)) ); m_pendingQueueJobs++; } } void MessageComposer::ComposerViewBase::slotSaveMessage( KJob* job ) { Akonadi::Collection target; Akonadi::Item item = job->property( "Akonadi::Item" ).value(); if( job->error() ) { target = defaultSpecialTarget(); } else { const Akonadi::CollectionFetchJob *fetchJob = qobject_cast( job ); if(fetchJob->collections().isEmpty()) target = defaultSpecialTarget(); else target = fetchJob->collections().first(); } Akonadi::ItemCreateJob *create = new Akonadi::ItemCreateJob( item, target, this ); connect( create, SIGNAL(result(KJob*)), this, SLOT(slotCreateItemResult(KJob*)) ); m_pendingQueueJobs++; } Akonadi::Collection MessageComposer::ComposerViewBase::defaultSpecialTarget() const { Akonadi::Collection target; if ( mSaveIn == MessageComposer::MessageSender::SaveInTemplates ) { target = Akonadi::SpecialMailCollections::self()->defaultCollection( Akonadi::SpecialMailCollections::Templates ); } else { target = Akonadi::SpecialMailCollections::self()->defaultCollection( Akonadi::SpecialMailCollections::Drafts ); } return target; } void MessageComposer::ComposerViewBase::slotCreateItemResult( KJob *job ) { m_pendingQueueJobs--; kDebug() << "mPendingCreateItemJobs" << m_pendingQueueJobs; Q_ASSERT( m_pendingQueueJobs >= 0 ); if( job->error() ) { kWarning() << "Failed to save a message:" << job->errorString(); emit failed( i18n( "Failed to save the message: %1", job->errorString() ) ); return; } if (mSendLaterInfo) { Akonadi::ItemCreateJob *createJob = static_cast(job); const Akonadi::Item item = createJob->item(); if (item.isValid()) { mSendLaterInfo->setItemId(item.id()); SendLater::SendLaterUtil::writeSendLaterInfo(mSendLaterInfo); delete mSendLaterInfo; mSendLaterInfo = 0; } } if( m_pendingQueueJobs == 0 ) { emit sentSuccessfully(); } } void MessageComposer::ComposerViewBase::addAttachment ( const KUrl& url, const QString& comment ) { Q_UNUSED( comment ); kDebug() << "adding attachment with url:" << url; m_attachmentController->addAttachment( url ); } void MessageComposer::ComposerViewBase::addAttachmentUrlSync ( const KUrl& url, const QString& comment ) { Q_UNUSED( comment ); kDebug() << "adding attachment with url:" << url; m_attachmentController->addAttachmentUrlSync( url ); } void MessageComposer::ComposerViewBase::addAttachment ( const QString& name, const QString& filename, const QString& charset, const QByteArray& data, const QByteArray& mimeType ) { MessageCore::AttachmentPart::Ptr attachment = MessageCore::AttachmentPart::Ptr( new MessageCore::AttachmentPart() ); if( !data.isEmpty() ) { attachment->setName( name ); attachment->setFileName( filename ); attachment->setData( data ); attachment->setCharset( charset.toLatin1() ); attachment->setMimeType( mimeType ); // TODO what about the other fields? m_attachmentController->addAttachment( attachment); } } void MessageComposer::ComposerViewBase::addAttachmentPart ( KMime::Content* partToAttach ) { MessageCore::AttachmentPart::Ptr part( new MessageCore::AttachmentPart ); if( partToAttach->contentType()->mimeType() == "multipart/digest" || partToAttach->contentType()->mimeType() == "message/rfc822" ) { // if it is a digest or a full message, use the encodedContent() of the attachment, // which already has the proper headers part->setData( partToAttach->encodedContent() ); } else { part->setData( partToAttach->decodedContent() ); } part->setMimeType( partToAttach->contentType()->mimeType() ); if ( partToAttach->contentDescription( false ) ) { part->setDescription( partToAttach->contentDescription()->asUnicodeString() ); } if ( partToAttach->contentType( false ) ) { if ( partToAttach->contentType()->hasParameter( QLatin1String( "name" ) ) ) { part->setName( partToAttach->contentType()->parameter( QLatin1String( "name" ) ) ); } } if ( partToAttach->contentDisposition( false ) ) { part->setFileName( partToAttach->contentDisposition()->filename() ); part->setInline( partToAttach->contentDisposition()->disposition() == KMime::Headers::CDinline ); } if ( part->name().isEmpty() && !part->fileName().isEmpty() ) { part->setName( part->fileName() ); } if ( part->fileName().isEmpty() && !part->name().isEmpty() ) { part->setFileName( part->name() ); } m_attachmentController->addAttachment( part ); } MessageComposer::Composer* MessageComposer::ComposerViewBase::createSimpleComposer() { MessageComposer::Composer* composer = new MessageComposer::Composer; fillGlobalPart( composer->globalPart() ); m_editor->fillComposerTextPart( composer->textPart() ); fillInfoPart( composer->infoPart(), UseUnExpandedRecipients ); composer->addAttachmentParts( m_attachmentModel->attachments() ); return composer; } //----------------------------------------------------------------------------- QString MessageComposer::ComposerViewBase::to() const { return MessageComposer::Util::cleanedUpHeaderString( m_recipientsEditor->recipientString( MessageComposer::Recipient::To ) ); } //----------------------------------------------------------------------------- QString MessageComposer::ComposerViewBase::cc() const { return MessageComposer::Util::cleanedUpHeaderString( m_recipientsEditor->recipientString( MessageComposer::Recipient::Cc ) ); } //----------------------------------------------------------------------------- QString MessageComposer::ComposerViewBase::bcc() const { return MessageComposer::Util::cleanedUpHeaderString( m_recipientsEditor->recipientString( MessageComposer::Recipient::Bcc ) ); } QString MessageComposer::ComposerViewBase::from() const { return MessageComposer::Util::cleanedUpHeaderString( m_from ); } QString MessageComposer::ComposerViewBase::replyTo() const { return MessageComposer::Util::cleanedUpHeaderString( m_replyTo ); } QString MessageComposer::ComposerViewBase::subject() const { return MessageComposer::Util::cleanedUpHeaderString( m_subject ); } void MessageComposer::ComposerViewBase::setParentWidgetForGui ( QWidget* w ) { m_parentWidget = w; } void MessageComposer::ComposerViewBase::setAttachmentController( MessageComposer::AttachmentControllerBase* controller ) { m_attachmentController = controller; } MessageComposer::AttachmentControllerBase* MessageComposer::ComposerViewBase::attachmentController() { return m_attachmentController; } void MessageComposer::ComposerViewBase::setAttachmentModel( MessageComposer::AttachmentModel* model ) { m_attachmentModel = model; } MessageComposer::AttachmentModel* MessageComposer::ComposerViewBase::attachmentModel() { return m_attachmentModel; } void MessageComposer::ComposerViewBase::setRecipientsEditor ( MessageComposer::RecipientsEditor* recEditor ) { m_recipientsEditor = recEditor; } MessageComposer::RecipientsEditor* MessageComposer::ComposerViewBase::recipientsEditor() { return m_recipientsEditor; } void MessageComposer::ComposerViewBase::setSignatureController(MessageComposer::SignatureController* sigController) { m_signatureController = sigController; } MessageComposer::SignatureController* MessageComposer::ComposerViewBase::signatureController() { return m_signatureController; } void MessageComposer::ComposerViewBase::setIdentityCombo ( KPIMIdentities::IdentityCombo* identCombo ) { m_identityCombo = identCombo; } KPIMIdentities::IdentityCombo* MessageComposer::ComposerViewBase::identityCombo() { return m_identityCombo; } void MessageComposer::ComposerViewBase::updateRecipients( const KPIMIdentities::Identity &ident, const KPIMIdentities::Identity &oldIdent, MessageComposer::Recipient::Type type ) { QString oldIdentList; QString newIdentList; if ( type == MessageComposer::Recipient::Bcc ) { oldIdentList = oldIdent.bcc(); newIdentList = ident.bcc(); } else if ( type == MessageComposer::Recipient::Cc ) { oldIdentList = oldIdent.cc(); newIdentList = ident.cc(); } else { return; } if ( oldIdentList != newIdentList ) { const KMime::Types::Mailbox::List oldRecipients = MessageCore::StringUtil::mailboxListFromUnicodeString( oldIdentList ); foreach ( const KMime::Types::Mailbox &recipient, oldRecipients ) { m_recipientsEditor->removeRecipient( MessageCore::StringUtil::mailboxListToUnicodeString( KMime::Types::Mailbox::List() << recipient ), type ); } const KMime::Types::Mailbox::List newRecipients = MessageCore::StringUtil::mailboxListFromUnicodeString( newIdentList ); foreach ( const KMime::Types::Mailbox &recipient, newRecipients ) { m_recipientsEditor->addRecipient( MessageCore::StringUtil::mailboxListToUnicodeString( KMime::Types::Mailbox::List() << recipient ), type ); } m_recipientsEditor->setFocusBottom(); } } void MessageComposer::ComposerViewBase::identityChanged ( const KPIMIdentities::Identity &ident, const KPIMIdentities::Identity &oldIdent, bool msgCleared ) { updateRecipients( ident, oldIdent, MessageComposer::Recipient::Bcc ); updateRecipients( ident, oldIdent, MessageComposer::Recipient::Cc ); KPIMIdentities::Signature oldSig = const_cast ( oldIdent ).signature(); KPIMIdentities::Signature newSig = const_cast ( ident ).signature(); //replace existing signatures const bool replaced = editor()->replaceSignature( oldSig, newSig ); // Just append the signature if there was no old signature if ( !replaced && ( msgCleared || oldSig.rawText().isEmpty() ) ) { signatureController()->applySignature( newSig ); } const QString vcardFileName = ident.vCardFile(); attachmentController()->setIdentityHasOwnVcard(!vcardFileName.isEmpty()); attachmentController()->setAttachOwnVcard(ident.attachVcard()); m_editor->setAutocorrectionLanguage(ident.autocorrectionLanguage()); } void MessageComposer::ComposerViewBase::setEditor ( MessageComposer::KMeditor* editor ) { m_editor = editor; m_editor->setRichTextSupport( KRichTextWidget::FullTextFormattingSupport | KRichTextWidget::FullListSupport | KRichTextWidget::SupportDirection | KRichTextWidget::SupportAlignment | KRichTextWidget::SupportRuleLine | KRichTextWidget::SupportHyperlinks ); m_editor->enableImageActions(); m_editor->enableEmoticonActions(); m_editor->enableInsertHtmlActions(); m_editor->enableInsertTableActions(); m_editor->document()->setModified( false ); } MessageComposer::KMeditor* MessageComposer::ComposerViewBase::editor() { return m_editor; } void MessageComposer::ComposerViewBase::setTransportCombo ( MailTransport::TransportComboBox* transpCombo ) { m_transport = transpCombo; } MailTransport::TransportComboBox* MessageComposer::ComposerViewBase::transportComboBox() { return m_transport; } void MessageComposer::ComposerViewBase::setIdentityManager ( KPIMIdentities::IdentityManager* identMan ) { m_identMan = identMan; } KPIMIdentities::IdentityManager* MessageComposer::ComposerViewBase::identityManager() { return m_identMan; } void MessageComposer::ComposerViewBase::setFcc ( const Akonadi::Collection& fccCollection ) { if ( m_fccCombo ) { m_fccCombo->setDefaultCollection( fccCollection ); } else { m_fccCollection = fccCollection; } Akonadi::CollectionFetchJob * const checkFccCollectionJob = new Akonadi::CollectionFetchJob( fccCollection, Akonadi::CollectionFetchJob::Base ); connect( checkFccCollectionJob, SIGNAL(result(KJob*)), SLOT(slotFccCollectionCheckResult(KJob*)) ); } void MessageComposer::ComposerViewBase::slotFccCollectionCheckResult( KJob* job ) { if( job->error() ) { const Akonadi::Collection sentMailCol = Akonadi::SpecialMailCollections::self()->defaultCollection( Akonadi::SpecialMailCollections::SentMail ); if ( m_fccCombo ) { m_fccCombo->setDefaultCollection( sentMailCol ); } else { m_fccCollection = sentMailCol; } } } void MessageComposer::ComposerViewBase::setFccCombo ( Akonadi::CollectionComboBox* fcc ) { m_fccCombo = fcc; } Akonadi::CollectionComboBox* MessageComposer::ComposerViewBase::fccCombo() { return m_fccCombo; } void MessageComposer::ComposerViewBase::setFrom(const QString& from) { m_from = from; } void MessageComposer::ComposerViewBase::setReplyTo(const QString& replyTo) { m_replyTo = replyTo; } void MessageComposer::ComposerViewBase::setSubject(const QString& subject) { m_subject = subject; if (mSendLaterInfo) { mSendLaterInfo->setSubject(m_subject); mSendLaterInfo->setTo(to()); } } void MessageComposer::ComposerViewBase::setAutoSaveInterval( int interval ) { m_autoSaveInterval = interval; } void MessageComposer::ComposerViewBase::setCryptoOptions ( bool sign, bool encrypt, Kleo::CryptoMessageFormat format, bool neverEncryptDrafts ) { m_sign = sign; m_encrypt = encrypt; m_cryptoMessageFormat = format; m_neverEncrypt = neverEncryptDrafts; } void MessageComposer::ComposerViewBase::setCharsets( const QList< QByteArray >& charsets ) { m_charsets = charsets; } void MessageComposer::ComposerViewBase::setMDNRequested( bool mdnRequested ) { m_mdnRequested = mdnRequested; } void MessageComposer::ComposerViewBase::setUrgent( bool urgent ) { m_urgent = urgent; } QStringList MessageComposer::ComposerViewBase::cleanEmailList(const QStringList& emails) { QStringList clean; foreach( const QString& email, emails ) clean << KPIMUtils::extractEmailAddress( email ); return clean; } int MessageComposer::ComposerViewBase::autoSaveInterval() const { return m_autoSaveInterval; } //----------------------------------------------------------------------------- void MessageComposer::ComposerViewBase::collectImages( KMime::Content *root ) { if ( KMime::Content * n = MessageViewer::ObjectTreeParser::findType( root, "multipart/alternative", true, true ) ) { KMime::Content *parentnode = n->parent(); if ( parentnode && parentnode->contentType()->isMultipart() && parentnode->contentType()->subType() == "related" ) { KMime::Content *node = MessageCore::NodeHelper::nextSibling( n ); while ( node ) { if ( node->contentType()->isImage() ) { kDebug() << "found image in multipart/related : " << node->contentType()->name(); QImage img; img.loadFromData( node->decodedContent() ); m_editor->loadImage( img, QString::fromLatin1( QByteArray(QByteArray("cid:") + node->contentID()->identifier()) ), node->contentType()->name() ); } node = MessageCore::NodeHelper::nextSibling( node ); } } } } //----------------------------------------------------------------------------- bool MessageComposer::ComposerViewBase::inlineSigningEncryptionSelected() { if ( !m_sign && !m_encrypt ) { return false; } return m_cryptoMessageFormat == Kleo::InlineOpenPGPFormat; } bool MessageComposer::ComposerViewBase::hasMissingAttachments( const QStringList& attachmentKeywords ) { if ( attachmentKeywords.isEmpty() ) return false; if ( m_attachmentModel->rowCount() > 0 ) { return false; } QStringList attachWordsList = attachmentKeywords; QRegExp rx ( QString::fromLatin1("\\b") + attachWordsList.join( QString::fromLatin1("\\b|\\b") ) + QString::fromLatin1("\\b") ); rx.setCaseSensitivity( Qt::CaseInsensitive ); // check whether the subject contains one of the attachment key words // unless the message is a reply or a forwarded message const QString subj = subject(); bool gotMatch = ( MessageHelper::stripOffPrefixes( subj ) == subj ) && ( rx.indexIn( subj ) >= 0 ); if ( !gotMatch ) { // check whether the non-quoted text contains one of the attachment key // words QRegExp quotationRx( QString::fromLatin1("^([ \\t]*([|>:}#]|[A-Za-z]+>))+") ); QTextDocument *doc = m_editor->document(); QTextBlock end( doc->end() ); for ( QTextBlock it = doc->begin(); it != end; it = it.next() ) { const QString line = it.text(); gotMatch = ( quotationRx.indexIn( line ) < 0 ) && ( rx.indexIn( line ) >= 0 ); if ( gotMatch ) { break; } } } if ( !gotMatch ) return false; return true; } MessageComposer::ComposerViewBase::MissingAttachment MessageComposer::ComposerViewBase::checkForMissingAttachments( const QStringList& attachmentKeywords ) { if(!hasMissingAttachments( attachmentKeywords )) { return NoMissingAttachmentFound; } int rc = KMessageBox::warningYesNoCancel( m_editor, 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"), KGuiItem(i18n("&Attach File...")), KGuiItem(i18n("&Send as Is")) ); if ( rc == KMessageBox::Cancel ) return FoundMissingAttachmentAndCancel; if ( rc == KMessageBox::Yes ) { m_attachmentController->showAddAttachmentDialog(); return FoundMissingAttachmentAndAddedAttachment; } return FoundMissingAttachmentAndSending; } void MessageComposer::ComposerViewBase::markAllAttachmentsForSigning(bool sign) { foreach( MessageCore::AttachmentPart::Ptr attachment, m_attachmentModel->attachments() ) { if( attachment->isSigned() ) { attachment->setSigned(sign); } } } void MessageComposer::ComposerViewBase::markAllAttachmentsForEncryption(bool encrypt) { foreach( MessageCore::AttachmentPart::Ptr attachment, m_attachmentModel->attachments() ) { if( attachment->isEncrypted() ) { attachment->setEncrypted(encrypt); } } } bool MessageComposer::ComposerViewBase::determineWhetherToSign( bool doSignCompletely, Kleo::KeyResolver* keyResolver, bool signSomething, bool & result ) { bool sign = false; switch ( keyResolver->checkSigningPreferences( signSomething ) ) { case Kleo::DoIt: if ( !signSomething ) { 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 #ifndef QT_NO_CURSOR MessageViewer::KCursorSaver busy( MessageViewer::KBusyPtr::busy() ); #endif 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( m_parentWidget, msg, i18n("Sign Message?"), KGuiItem( i18nc("to sign","&Sign") ), KGuiItem( i18n("Do &Not Sign") ) ) ) { case KMessageBox::Cancel: result = 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 #ifndef QT_NO_CURSOR MessageViewer::KCursorSaver busy( MessageViewer::KBusyPtr::busy() ); #endif const QString msg = i18n("There are conflicting signing preferences " "for these recipients.\n" "Sign this message?"); switch ( KMessageBox::warningYesNoCancel( m_parentWidget, msg, i18n("Sign Message?"), KGuiItem( i18nc("to sign","&Sign") ), KGuiItem( i18n("Do &Not Sign") ) ) ) { case KMessageBox::Cancel: result = false; return false; case KMessageBox::Yes: markAllAttachmentsForSigning( true ); return true; case KMessageBox::No: markAllAttachmentsForSigning( false ); return false; } } break; case Kleo::Impossible: { #ifndef QT_NO_CURSOR MessageViewer::KCursorSaver busy( MessageViewer::KBusyPtr::busy() ); #endif 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( m_parentWidget, msg, i18n("Send Unsigned?"), KGuiItem( i18n("Send &Unsigned") ) ) == KMessageBox::Cancel ) { result = false; return false; } else { markAllAttachmentsForSigning( false ); return false; } } } if ( !sign || !doSignCompletely ) { if ( MessageComposer::MessageComposerSettings::self()->cryptoWarningUnsigned() ) { #ifndef QT_NO_CURSOR MessageViewer::KCursorSaver busy( MessageViewer::KBusyPtr::busy() ); #endif 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( m_parentWidget, msg, i18n("Unsigned-Message Warning"), KGuiItem( buttonText ), KGuiItem( i18n("Send &As Is") ) ) ) { case KMessageBox::Cancel: result = false; return false; case KMessageBox::Yes: markAllAttachmentsForSigning( true ); return true; case KMessageBox::No: return sign || doSignCompletely; } } } return sign || doSignCompletely; } bool MessageComposer::ComposerViewBase::determineWhetherToEncrypt( bool doEncryptCompletely, Kleo::KeyResolver* keyResolver, bool encryptSomething, bool signSomething, bool & result ) { bool encrypt = false; bool opportunistic = false; switch ( keyResolver->checkEncryptionPreferences( encryptSomething ) ) { case Kleo::DoIt: if ( !encryptSomething ) { 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 #ifndef QT_NO_CURSOR MessageViewer::KCursorSaver busy( MessageViewer::KBusyPtr::busy() ); #endif 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( m_parentWidget, msg, i18n("Encrypt Message?"), KGuiItem( signSomething ? i18n("Sign && &Encrypt") : i18n("&Encrypt") ), KGuiItem( signSomething ? i18n("&Sign Only") : i18n("&Send As-Is") ) ) ) { case KMessageBox::Cancel: result = 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 #ifndef QT_NO_CURSOR MessageViewer::KCursorSaver busy( MessageViewer::KBusyPtr::busy() ); #endif const QString msg = i18n("There are conflicting encryption preferences " "for these recipients.\n" "Encrypt this message?"); switch ( KMessageBox::warningYesNoCancel( m_parentWidget, msg, i18n("Encrypt Message?"), KGuiItem( i18n("&Encrypt") ), KGuiItem( i18n("Do &Not Encrypt")) ) ) { case KMessageBox::Cancel: result = false; return false; case KMessageBox::Yes: markAllAttachmentsForEncryption( true ); return true; case KMessageBox::No: markAllAttachmentsForEncryption( false ); return false; } } break; case Kleo::Impossible: { #ifndef QT_NO_CURSOR MessageViewer::KCursorSaver busy( MessageViewer::KBusyPtr::busy() ); #endif 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( m_parentWidget, msg, i18n("Send Unencrypted?"), KGuiItem( i18n("Send &Unencrypted") ) ) == KMessageBox::Cancel ) { result = false; return false; } else { markAllAttachmentsForEncryption( false ); return false; } } } if ( !encrypt || !doEncryptCompletely ) { if ( MessageComposer::MessageComposerSettings::self()->cryptoWarningUnencrypted() ) { #ifndef QT_NO_CURSOR MessageViewer::KCursorSaver busy( MessageViewer::KBusyPtr::busy() ); #endif 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( m_parentWidget, msg, i18n("Unencrypted Message Warning"), KGuiItem( buttonText ), KGuiItem( signSomething ? i18n("&Sign Only") : i18n("&Send As-Is")) ) ) { case KMessageBox::Cancel: result = false; return false; case KMessageBox::Yes: markAllAttachmentsForEncryption( true ); return true; case KMessageBox::No: return encrypt || doEncryptCompletely; } } } return encrypt || doEncryptCompletely; } void MessageComposer::ComposerViewBase::setSendLaterInfo( SendLater::SendLaterInfo *info) { delete mSendLaterInfo; mSendLaterInfo = info; } SendLater::SendLaterInfo *MessageComposer::ComposerViewBase::sendLaterInfo() { return mSendLaterInfo; } diff --git a/messagecomposer/composer/composerviewbase.h b/messagecomposer/composer/composerviewbase.h index bcc085ad97..4cc0d02596 100644 --- a/messagecomposer/composer/composerviewbase.h +++ b/messagecomposer/composer/composerviewbase.h @@ -1,345 +1,346 @@ /* Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Leo Franchi 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 COMPOSER_VIEW_BASE_H #define COMPOSER_VIEW_BASE_H #include "messagecomposer_export.h" #include "sender/messagesender.h" #include "messagecomposer/recipient/recipient.h" #include "agents/sendlateragent/sendlaterdialog.h" #include #include #include #include #include class QTimer; class KJob; class QWidget; namespace SendLater { class SendLaterInfo; } namespace Akonadi { class CollectionComboBox; } namespace MailTransport { class TransportComboBox; class MessageQueueJob; } namespace KPIMIdentities { class IdentityCombo; class Identity; class IdentityManager; } namespace Kleo { class KeyResolver; } namespace MessageComposer { class RecipientsEditor; class KMeditor; class InfoPart; class GlobalPart; class Composer; class AttachmentControllerBase; class AttachmentModel; class SignatureController; /** * */ class MESSAGECOMPOSER_EXPORT ComposerViewBase : public QObject { Q_OBJECT public: explicit ComposerViewBase ( QObject* parent = 0, QWidget *parentGui = 0 ); virtual ~ComposerViewBase(); enum Confirmation { LetUserConfirm, NoConfirmationNeeded }; enum MissingAttachment { NoMissingAttachmentFound, FoundMissingAttachmentAndSending, FoundMissingAttachmentAndAddedAttachment, FoundMissingAttachmentAndCancel }; enum FailedType { Sending, AutoSave }; /** * Set the message to be opened in the composer window, and set the internal data structures to * keep track of it. */ void setMessage( const KMime::Message::Ptr& newMsg ); + void setMessage( const KMime::Message::Ptr& newMsg, bool allowDecryption ); void updateTemplate ( const KMime::Message::Ptr& msg ); /** * Send the message with the specified method, saving it in the specified folder. */ void send( MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn, bool checkMailDispatcher = true); /** * Returns true if there is at least one composer job running. */ bool isComposing() const; /** * Add the given attachment to the message. */ void addAttachment( const KUrl &url, const QString &comment ); void addAttachmentUrlSync ( const KUrl& url, const QString& comment ); void addAttachment ( const QString& name, const QString& filename, const QString& charset, const QByteArray& data, const QByteArray& mimeType ); void addAttachmentPart( KMime::Content* part ); MessageComposer::Composer* createSimpleComposer(); /** * Header fields in recipients editor. */ QString to() const; QString cc() const; QString bcc() const; QString from() const; QString replyTo() const; QString subject() const; /** * The following are for setting the various options and widgets in the * composer. */ void setAttachmentModel( MessageComposer::AttachmentModel* model ); MessageComposer::AttachmentModel* attachmentModel(); void setAttachmentController( MessageComposer::AttachmentControllerBase* controller ); MessageComposer::AttachmentControllerBase* attachmentController(); void setRecipientsEditor( MessageComposer::RecipientsEditor* recEditor ); MessageComposer::RecipientsEditor* recipientsEditor(); void setSignatureController( MessageComposer::SignatureController* sigController ); MessageComposer::SignatureController* signatureController(); void setIdentityCombo( KPIMIdentities::IdentityCombo* identCombo ); KPIMIdentities::IdentityCombo* identityCombo(); void setIdentityManager( KPIMIdentities::IdentityManager* identMan ); KPIMIdentities::IdentityManager* identityManager(); void setEditor( MessageComposer::KMeditor* editor ); MessageComposer::KMeditor* editor(); void setTransportCombo( MailTransport::TransportComboBox* transpCombo ); MailTransport::TransportComboBox* transportComboBox(); void setFccCombo( Akonadi::CollectionComboBox* fcc ); Akonadi::CollectionComboBox* fccCombo(); void setFcc( const Akonadi::Collection& id ); /** * Widgets for editing differ in client classes, so * values are set before sending. */ void setFrom( const QString& from ); void setReplyTo( const QString& replyTo ); void setSubject( const QString& subject ); /** * The following are various settings the user can modify when composing a message. If they are not set, * the default values will be used. */ void setCryptoOptions( bool sign, bool encrypt, Kleo::CryptoMessageFormat format, bool neverEncryptDrafts = false ); void setCharsets( const QList< QByteArray >& charsets ); void setMDNRequested( bool mdnRequested ); void setUrgent( bool urgent ); void setAutoSaveInterval( int interval ); void setCustomHeader( const QMap&customHeader ); /** * Enables/disables autosaving depending on the value of the autosave * interval. */ void updateAutoSave(); /** * Sets the filename to use when autosaving something. This is used when the client recovers * the autosave files: It calls this method, so that the composer uses the same filename again. * That way, the recovered autosave file is properly cleaned up in cleanupAutoSave(): */ void setAutoSaveFileName( const QString &fileName ); /** * Stop autosaving and delete the autosaved message. */ void cleanupAutoSave(); void setParentWidgetForGui( QWidget* ); /** * Check if the mail has references to attachments, but no attachments are added to it. * If missing attachments are found, a dialog to add new attachments is shown. * @param attachmentKeywords a list with the keywords that indicate an attachment should be present * @return NoMissingAttachmentFound, if there is attachment in email * FoundMissingAttachmentAndCancelSending, if mail might miss attachment but sending * FoundMissingAttachmentAndAddedAttachment, if mail might miss attachment and we added an attachment * FoundMissingAttachmentAndCancel, if mail might miss attachment and cancel sending */ ComposerViewBase::MissingAttachment checkForMissingAttachments( const QStringList &attachmentKeywords ) ; bool hasMissingAttachments( const QStringList& attachmentKeywords ); void setSendLaterInfo( SendLater::SendLaterInfo *info); SendLater::SendLaterInfo *sendLaterInfo(); public slots: void identityChanged( const KPIMIdentities::Identity &ident, const KPIMIdentities::Identity &oldIdent, bool msgCleared = false); /** * Save the message. */ void autoSaveMessage(); signals: /** * Message sending completed successfully. */ void sentSuccessfully(); /** * Message sending failed with given error message. */ void failed( const QString& errorMessage, MessageComposer::ComposerViewBase::FailedType type = Sending ); /** * The composer was modified. This can happen behind the users' back * when, for example, and autosaved message was recovered. */ void modified( bool isModified ); /** * Enabling or disabling HTML in the editor is affected * by various client options, so when that would otherwise happen, * hand it off to the client to enact it for real. */ void disableHtml( MessageComposer::ComposerViewBase::Confirmation ); void enableHtml(); private slots: void slotEmailAddressResolved( KJob* ); void slotSendComposeResult( KJob* ); void slotQueueResult( KJob *job ); void slotCreateItemResult( KJob * ); void slotAutoSaveComposeResult( KJob *job ); void slotFccCollectionCheckResult( KJob *job ); void slotSaveMessage( KJob *job ); private: Akonadi::Collection defaultSpecialTarget() const; /** * Searches the mime tree, where root is the root node, for embedded images, * extracts them froom the body and adds them to the editor. */ void collectImages( KMime::Content* root ); bool inlineSigningEncryptionSelected(); /** * Applies the user changes to the message object of the composer * and signs/encrypts the message if activated. * Disables the controls of the composer window. */ void readyForSending(); enum RecipientExpansion { UseExpandedRecipients, UseUnExpandedRecipients }; QList< MessageComposer::Composer* > generateCryptoMessages(); void fillGlobalPart( MessageComposer::GlobalPart *globalPart ); void fillInfoPart( MessageComposer::InfoPart *part, RecipientExpansion expansion ); void queueMessage( KMime::Message::Ptr message, MessageComposer::Composer* composer ); void saveMessage( KMime::Message::Ptr message, MessageComposer::MessageSender::SaveIn saveIn ); void fillQueueJobHeaders( MailTransport::MessageQueueJob* qjob, KMime::Message::Ptr message, const MessageComposer::InfoPart* infoPart ); QStringList cleanEmailList( const QStringList& emails ); void saveRecentAddresses( KMime::Message::Ptr ptr ); void updateRecipients( const KPIMIdentities::Identity &ident, const KPIMIdentities::Identity &oldIdent, MessageComposer::Recipient::Type type ); void markAllAttachmentsForSigning(bool sign); void markAllAttachmentsForEncryption(bool encrypt); bool determineWhetherToSign(bool doSignCompletely , Kleo::KeyResolver *keyResolver, bool signSomething, bool & result); bool determineWhetherToEncrypt(bool doEncryptCompletely , Kleo::KeyResolver *keyResolver, bool encryptSomething, bool signSomething, bool & result); /** * Writes out autosave data to the disk from the KMime::Message message. * Also appends the msgNum to the filename as a message can have a number of * KMime::Messages */ void writeAutoSaveToDisk( const KMime::Message::Ptr& message ); /** * Returns the autosave interval in milliseconds (as needed for QTimer). */ int autoSaveInterval() const; /** * Initialize autosaving (timer and filename). */ void initAutoSave(); KMime::Message::Ptr m_msg; MessageComposer::AttachmentControllerBase* m_attachmentController; MessageComposer::AttachmentModel* m_attachmentModel; MessageComposer::SignatureController* m_signatureController; MessageComposer::RecipientsEditor * m_recipientsEditor; KPIMIdentities::IdentityCombo *m_identityCombo; KPIMIdentities::IdentityManager* m_identMan; MessageComposer::KMeditor* m_editor; MailTransport::TransportComboBox* m_transport; Akonadi::CollectionComboBox* m_fccCombo; Akonadi::Collection m_fccCollection; QWidget* m_parentWidget; // List of active composer jobs. For example, saving as draft, autosaving and printing // all create a composer, which is added to this list as long as it is active. // Used mainly to prevent closing the window if a composer is active QList< MessageComposer::Composer* > m_composers; bool m_sign, m_encrypt, m_neverEncrypt, m_mdnRequested, m_urgent; Kleo::CryptoMessageFormat m_cryptoMessageFormat; QString mExpandedFrom, m_from, m_replyTo, m_subject; QStringList mExpandedTo, mExpandedCc, mExpandedBcc; QList< QByteArray > m_charsets; QMap m_customHeader; int m_pendingQueueJobs; QTimer *m_autoSaveTimer; QString m_autoSaveUUID; bool m_autoSaveErrorShown; // Stops an error message being shown every time autosave is executed. int m_autoSaveInterval; MessageComposer::MessageSender::SendMethod mSendMethod; MessageComposer::MessageSender::SaveIn mSaveIn; SendLater::SendLaterInfo *mSendLaterInfo; }; } // namespace #endif diff --git a/messagecomposer/tests/cryptocomposertest.cpp b/messagecomposer/tests/cryptocomposertest.cpp index d5f75fa51f..e37acdee86 100644 --- a/messagecomposer/tests/cryptocomposertest.cpp +++ b/messagecomposer/tests/cryptocomposertest.cpp @@ -1,488 +1,623 @@ /* Copyright (c) 2009 Constantin Berzan Copyright (c) 2009 Leo Franchi 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 "cryptocomposertest.h" #include "qtest_messagecomposer.h" #include "cryptofunctions.h" #include "testhtmlwriter.h" #include "testcsshelper.h" #include #include #include #include using namespace KMime; #include +#include +#include #include #include #include +#include +#include +#include using namespace MessageComposer; #include #include #include #include #include using MessageCore::AttachmentPart; #include +Q_DECLARE_METATYPE(MessageCore::AttachmentPart) + QTEST_KDEMAIN( CryptoComposerTest, GUI ) void CryptoComposerTest::initTestCase() { MessageCore::Test::setupEnv(); } Q_DECLARE_METATYPE( Headers::contentEncoding ) // openpgp void CryptoComposerTest::testOpenPGPMime_data() { QTest::addColumn( "data" ); QTest::addColumn( "sign" ); QTest::addColumn( "encrypt" ); QTest::addColumn( "cte" ); QString data( QString::fromLatin1( "All happy families are alike; each unhappy family is unhappy in its own way." ) ); QTest::newRow( "SignOpenPGPMime" ) << data << true << false << Headers::CE7Bit; QTest::newRow( "EncryptOpenPGPMime" ) << data << false << true << Headers::CE7Bit; QTest::newRow( "SignEncryptOpenPGPMime" ) << data << true << true << Headers::CE7Bit; } void CryptoComposerTest::testOpenPGPMime() { QFETCH( QString, data ); QFETCH( bool, sign ); QFETCH( bool, encrypt ); QFETCH( Headers::contentEncoding, cte ); Composer *composer = new Composer; fillComposerData( composer, data ); fillComposerCryptoData( composer ); composer->setSignAndEncrypt( sign, encrypt ); composer->setMessageCryptoFormat( Kleo::OpenPGPMIMEFormat ); VERIFYEXEC( composer ); QCOMPARE( composer->resultMessages().size(), 1 ); KMime::Message::Ptr message = composer->resultMessages().first(); delete composer; composer = 0; //kDebug()<< "message:" << message.get()->encodedContent(); ComposerTestUtil::verify( sign, encrypt, message.get(), data.toUtf8(), Kleo::OpenPGPMIMEFormat, cte ); QCOMPARE( message->from()->asUnicodeString(), QString::fromLocal8Bit( "me@me.me" ) ); QCOMPARE( message->to()->asUnicodeString(), QString::fromLocal8Bit( "you@you.you" ) ); } // the following will do for s-mime as well, as the same sign/enc jobs are used void CryptoComposerTest::testEncryptSameAttachments_data() { QTest::addColumn( "format" ); QTest::newRow( "OpenPGPMime" ) << (int) Kleo::OpenPGPMIMEFormat; //TODO: fix Inline PGP with encrypted attachments //QTest::newRow( "InlineOpenPGP" ) << (int) Kleo::InlineOpenPGPFormat; } void CryptoComposerTest::testEncryptSameAttachments() { QFETCH( int, format ); Composer *composer = new Composer; QString data( QString::fromLatin1( "All happy families are alike; each unhappy family is unhappy in its own way." ) ); fillComposerData( composer, data ); fillComposerCryptoData( composer ); AttachmentPart::Ptr attachment = AttachmentPart::Ptr( new AttachmentPart ); attachment->setData( "abc" ); attachment->setMimeType( "x-some/x-type" ); attachment->setFileName( QString::fromLocal8Bit( "anattachment.txt" ) ); attachment->setEncrypted( true ); attachment->setSigned( false ); composer->addAttachmentPart( attachment ); composer->setSignAndEncrypt( false, true ); composer->setMessageCryptoFormat( (Kleo::CryptoMessageFormat) format ); VERIFYEXEC( composer ); QCOMPARE( composer->resultMessages().size(), 1 ); KMime::Message::Ptr message = composer->resultMessages().first(); delete composer; composer = 0; //kDebug()<< "message:" << message.get()->encodedContent(); ComposerTestUtil::verifyEncryption( message.get(), data.toUtf8(), (Kleo::CryptoMessageFormat) format , true ); QCOMPARE( message->from()->asUnicodeString(), QString::fromLocal8Bit( "me@me.me" ) ); QCOMPARE( message->to()->asUnicodeString(), QString::fromLocal8Bit( "you@you.you" ) ); TestHtmlWriter testWriter; TestCSSHelper testCSSHelper; MessageCore::Test::TestObjectTreeSource testSource( &testWriter, &testCSSHelper ); testSource.setAllowDecryption( true ); MessageViewer::NodeHelper* nh = new MessageViewer::NodeHelper; MessageViewer::ObjectTreeParser otp( &testSource, nh ); MessageViewer::ProcessResult pResult( nh ); otp.parseObjectTree( message.get() ); KMime::Message::Ptr unencrypted = nh->unencryptedMessage( message ); KMime::Content* testAttachment = MessageViewer::ObjectTreeParser::findType( unencrypted.get() , "x-some", "x-type", true, true ); QCOMPARE( testAttachment->body(), QString::fromLatin1( "abc" ).toUtf8() ); QCOMPARE( testAttachment->contentDisposition()->filename(), QString::fromLatin1( "anattachment.txt" ) ); } +void CryptoComposerTest::testEditEncryptAttachments_data() +{ + QTest::addColumn( "format" ); + QTest::newRow( "OpenPGPMime" ) << (int) Kleo::OpenPGPMIMEFormat; + //TODO: SMIME should also be tested +} + +void CryptoComposerTest::testEditEncryptAttachments() +{ + QFETCH( int, format ); + Composer *composer = new Composer; + QString data( QString::fromLatin1( "All happy families are alike; each unhappy family is unhappy in its own way." ) ); + fillComposerData( composer, data ); + fillComposerCryptoData( composer ); + + AttachmentPart::Ptr attachment = AttachmentPart::Ptr( new AttachmentPart ); + const QString fileName = QLatin1String( "anattachment.txt" ); + const QByteArray fileData = "abc"; + attachment->setData( fileData ); + attachment->setMimeType( "x-some/x-type" ); + attachment->setFileName( fileName ); + attachment->setEncrypted( true ); + attachment->setSigned( false ); + composer->addAttachmentPart( attachment ); + + composer->setSignAndEncrypt( false, true ); + composer->setMessageCryptoFormat( (Kleo::CryptoMessageFormat) format ); + + VERIFYEXEC( composer ); + QCOMPARE( composer->resultMessages().size(), 1 ); + + KMime::Message::Ptr message = composer->resultMessages().first(); + delete composer; + composer = 0; + + // setup a viewer + ComposerViewBase view(this, 0); + AttachmentModel model(this); + AttachmentControllerBase controller(&model,0,0); + RecipientsEditor recipientEditor; + KMeditor editor; + view.setAttachmentModel(&model); + view.setAttachmentController(&controller); + view.setRecipientsEditor(&recipientEditor); + view.setEditor(&editor); + + // Let's load the email to the viewer + view.setMessage(message, true); + + QModelIndex index = model.index(0,0); + QCOMPARE (editor.toPlainText(), data); + QCOMPARE(model.rowCount(), 1); + QCOMPARE(model.data(index, AttachmentModel::NameRole).toString(), fileName); + AttachmentPart::Ptr part = model.attachments()[0]; + QCOMPARE(part->data(), fileData); + QCOMPARE(part->fileName(), fileName); +} + +void CryptoComposerTest::testEditEncryptAndLateAttachments_data() +{ + QTest::addColumn( "format" ); + QTest::newRow( "OpenPGPMime" ) << (int) Kleo::OpenPGPMIMEFormat; + //TODO: SMIME should also be tested +} + +void CryptoComposerTest::testEditEncryptAndLateAttachments() +{ + QFETCH( int, format ); + Composer *composer = new Composer; + QString data( QString::fromLatin1( "All happy families are alike; each unhappy family is unhappy in its own way." ) ); + fillComposerData( composer, data ); + fillComposerCryptoData( composer ); + + AttachmentPart::Ptr attachment = AttachmentPart::Ptr( new AttachmentPart ); + const QString fileName = QLatin1String( "anattachment.txt" ); + const QByteArray fileData = "abc"; + const QString fileName2 = QLatin1String( "nonencrypt.txt" ); + const QByteArray fileData2 = "readable"; + attachment->setData( fileData ); + attachment->setMimeType( "x-some/x-type" ); + attachment->setFileName( fileName ); + attachment->setEncrypted( true ); + attachment->setSigned( false ); + composer->addAttachmentPart( attachment ); + + attachment = AttachmentPart::Ptr( new AttachmentPart ); + attachment->setData( fileData2 ); + attachment->setMimeType( "x-some/x-type2" ); + attachment->setFileName( fileName2 ); + attachment->setEncrypted( false ); + attachment->setSigned( false ); + composer->addAttachmentPart( attachment ); + + composer->setSignAndEncrypt( false, true ); + composer->setMessageCryptoFormat( (Kleo::CryptoMessageFormat) format ); + + VERIFYEXEC( composer ); + QCOMPARE( composer->resultMessages().size(), 1 ); + + KMime::Message::Ptr message = composer->resultMessages().first(); + delete composer; + composer = 0; + + // setup a viewer + ComposerViewBase view(this, 0); + AttachmentModel model(this); + AttachmentControllerBase controller(&model,0,0); + RecipientsEditor recipientEditor; + KMeditor editor; + view.setAttachmentModel(&model); + view.setAttachmentController(&controller); + view.setRecipientsEditor(&recipientEditor); + view.setEditor(&editor); + + // Let's load the email to the viewer + view.setMessage(message, true); + + QModelIndex index = model.index(0,0); + QCOMPARE (editor.toPlainText(), data); + QCOMPARE(model.rowCount(), 2); + AttachmentPart::Ptr part = model.attachments()[0]; + QCOMPARE(part->fileName(), fileName); + QCOMPARE(part->data(), fileData); + part = model.attachments()[1]; + QCOMPARE(part->fileName(), fileName2); + QCOMPARE(part->data(), fileData2); +} + void CryptoComposerTest::testSignEncryptLateAttachments_data() { QTest::addColumn( "format" ); QTest::newRow( "OpenPGPMime" ) << (int) Kleo::OpenPGPMIMEFormat; QTest::newRow( "InlineOpenPGP" ) << (int) Kleo::InlineOpenPGPFormat; } void CryptoComposerTest::testSignEncryptLateAttachments() { QFETCH( int, format ); Composer *composer = new Composer; QString data( QString::fromLatin1( "All happy families are alike; each unhappy family is unhappy in its own way." ) ); fillComposerData( composer, data ); fillComposerCryptoData( composer ); AttachmentPart::Ptr attachment = AttachmentPart::Ptr( new AttachmentPart ); attachment->setData( "abc" ); attachment->setMimeType( "x-some/x-type" ); attachment->setFileName( QString::fromLocal8Bit( "anattachment.txt" ) ); attachment->setEncrypted( false ); attachment->setSigned( false ); composer->addAttachmentPart( attachment ); composer->setSignAndEncrypt( true, true ); composer->setMessageCryptoFormat( (Kleo::CryptoMessageFormat) format ); VERIFYEXEC( composer ); QCOMPARE( composer->resultMessages().size(), 1 ); KMime::Message::Ptr message = composer->resultMessages().first(); delete composer; composer = 0; // as we have an additional attachment, just ignore it when checking for sign/encrypt KMime::Content * b = MessageCore::NodeHelper::firstChild( message.get() ); ComposerTestUtil::verifySignatureAndEncryption( b, data.toUtf8(), (Kleo::CryptoMessageFormat) format, true ); QCOMPARE( message->from()->asUnicodeString(), QString::fromLocal8Bit( "me@me.me" ) ); QCOMPARE( message->to()->asUnicodeString(), QString::fromLocal8Bit( "you@you.you" ) ); // now check the attachment separately - QCOMPARE( QString::fromAscii( MessageCore::NodeHelper::nextSibling( MessageCore::NodeHelper::firstChild( message.get() ) )->body() ), QString::fromAscii( "abc" ) ); + QCOMPARE( QString::fromAscii( MessageCore::NodeHelper::nextSibling( b )->body() ), QString::fromAscii( "abc" ) ); } void CryptoComposerTest::testBCCEncrypt_data() { QTest::addColumn( "format" ); QTest::newRow( "OpenPGPMime" ) << (int) Kleo::OpenPGPMIMEFormat; QTest::newRow( "InlineOpenPGP" ) << (int) Kleo::InlineOpenPGPFormat; } // secondary recipients void CryptoComposerTest::testBCCEncrypt() { QFETCH( int, format ); Composer *composer = new Composer; QString data( QString::fromLatin1( "All happy families are alike; each unhappy family is unhappy in its own way." ) ); fillComposerData( composer, data ); composer->infoPart()->setBcc( QStringList( QString::fromLatin1( "bcc@bcc.org" ) ) ); std::vector keys = MessageCore::Test::getKeys(); QStringList primRecipients; primRecipients << QString::fromLocal8Bit( "you@you.you" ); std::vector< GpgME::Key > pkeys; pkeys.push_back( keys[1] ); QStringList secondRecipients; secondRecipients << QString::fromLocal8Bit( "bcc@bcc.org" ); std::vector< GpgME::Key > skeys; skeys.push_back( keys[2] ); QList > > encKeys; encKeys.append( QPair >( primRecipients, pkeys ) ); encKeys.append( QPair >( secondRecipients, skeys ) ); composer->setSignAndEncrypt( true, true ); composer->setMessageCryptoFormat( (Kleo::CryptoMessageFormat) format ); composer->setEncryptionKeys( encKeys ); composer->setSigningKeys( keys ); VERIFYEXEC( composer ); QCOMPARE( composer->resultMessages().size(), 2 ); KMime::Message::Ptr primMessage = composer->resultMessages().first(); KMime::Message::Ptr secMessage = composer->resultMessages()[1]; delete composer; composer = 0; ComposerTestUtil::verifySignatureAndEncryption( primMessage.get(), data.toUtf8(), (Kleo::CryptoMessageFormat) format ); QCOMPARE( primMessage->from()->asUnicodeString(), QString::fromLocal8Bit( "me@me.me" ) ); QCOMPARE( primMessage->to()->asUnicodeString(), QString::fromLocal8Bit( "you@you.you" ) ); ComposerTestUtil::verifySignatureAndEncryption( secMessage.get(), data.toUtf8(), (Kleo::CryptoMessageFormat) format ); QCOMPARE( secMessage->from()->asUnicodeString(), QString::fromLocal8Bit( "me@me.me" ) ); QCOMPARE( secMessage->to()->asUnicodeString(), QString::fromLocal8Bit( "you@you.you" ) ); } // inline pgp void CryptoComposerTest::testOpenPGPInline_data() { QTest::addColumn( "data" ); QTest::addColumn( "sign" ); QTest::addColumn( "encrypt" ); QTest::addColumn( "cte" ); QString data( QString::fromLatin1( "All happy families are alike; each unhappy family is unhappy in its own way." ) ); QTest::newRow( "SignOpenPGPInline" ) << data << true << false << Headers::CE7Bit; QTest::newRow( "EncryptOpenPGPInline" ) << data << false << true << Headers::CE7Bit; QTest::newRow( "SignEncryptOpenPGPInline" ) << data << true << true << Headers::CE7Bit; } void CryptoComposerTest::testOpenPGPInline() { QFETCH( QString, data ); QFETCH( bool, sign ); QFETCH( bool, encrypt ); QFETCH( Headers::contentEncoding, cte ); Composer *composer = new Composer; fillComposerData( composer, data ); fillComposerCryptoData( composer ); composer->setSignAndEncrypt( sign, encrypt ); composer->setMessageCryptoFormat( Kleo::InlineOpenPGPFormat ); VERIFYEXEC( composer ); QCOMPARE( composer->resultMessages().size(), 1 ); KMime::Message::Ptr message = composer->resultMessages().first(); delete composer; composer = 0; if ( sign && !encrypt ) data += QString::fromLatin1( "\n" ); kDebug() << "message:" << message->encodedContent(); ComposerTestUtil::verify( sign, encrypt, message.get(), data.toUtf8(), Kleo::InlineOpenPGPFormat, cte ); QCOMPARE( message->from()->asUnicodeString(), QString::fromLocal8Bit( "me@me.me" ) ); QCOMPARE( message->to()->asUnicodeString(), QString::fromLocal8Bit( "you@you.you" ) ); } // s-mime void CryptoComposerTest::testSMIME_data() { QTest::addColumn( "data" ); QTest::addColumn( "sign" ); QTest::addColumn( "encrypt" ); QTest::addColumn( "cte" ); QString data( QString::fromLatin1( "All happy families are alike; each unhappy family is unhappy in its own way." ) ); QTest::newRow( "SignSMIME" ) << data << true << false << Headers::CE7Bit; QTest::newRow( "EncryptSMIME" ) << data << false << true << Headers::CE7Bit; QTest::newRow( "SignEncryptSMIME" ) << data << true << true << Headers::CE7Bit; } void CryptoComposerTest::testSMIME() { QFETCH( bool, sign ); QFETCH( bool, encrypt ); runSMIMETest( sign, encrypt, false ); } void CryptoComposerTest::testSMIMEOpaque_data() { QTest::addColumn( "data" ); QTest::addColumn( "sign" ); QTest::addColumn( "encrypt" ); QTest::addColumn( "cte" ); QString data( QString::fromLatin1( "All happy families are alike; each unhappy family is unhappy in its own way." ) ); QTest::newRow( "SignSMIMEOpaque" ) << data << true << false << Headers::CE7Bit; QTest::newRow( "EncryptSMIMEOpaque" ) << data << false << true << Headers::CE7Bit; QTest::newRow( "SignEncryptSMIMEOpaque" ) << data << true << true << Headers::CE7Bit; } void CryptoComposerTest::testSMIMEOpaque() { QFETCH( bool, sign ); QFETCH( bool, encrypt ); runSMIMETest( sign, encrypt, true ); } // contentTransferEncoding void CryptoComposerTest::testCTEquPr_data() { QTest::addColumn( "data" ); QTest::addColumn( "sign" ); QTest::addColumn( "encrypt" ); QTest::addColumn( "cte" ); QString data( QString::fromLatin1( "All happy families are alike; each unhappy family is unhappy in its own way. [ä]" ) ); QTest::newRow( "CTEquPr:Sign" ) << data << true << false << Headers::CEquPr; QTest::newRow( "CTEquPr:Encrypt" ) << data << false << true << Headers::CE7Bit; QTest::newRow( "CTEquPr:SignEncrypt" ) << data << true << true << Headers::CE7Bit; data = QString::fromUtf8( "All happy families are alike;\n\n\n\neach unhappy family is unhappy in its own way.\n--\n hallloasdfasdfsadfsdf asdf sadfasdf sdf sdf sdf sadfasdf sdaf daf sdf asdf sadf asdf asdf [ä]" ); QTest::newRow( "CTEquPr:Sign:Newline" ) << data << true << false << Headers::CEquPr; QTest::newRow( "CTEquPr:Encrypt:Newline" ) << data << false << true << Headers::CE7Bit; QTest::newRow( "CTEquPr:SignEncrypt:Newline" ) << data << true << true << Headers::CE7Bit; } void CryptoComposerTest::testCTEquPr() { testSMIME(); testSMIMEOpaque(); testOpenPGPMime(); testOpenPGPInline(); } void CryptoComposerTest::testCTEbase64_data() { QTest::addColumn( "data" ); QTest::addColumn( "sign" ); QTest::addColumn( "encrypt" ); QTest::addColumn( "cte" ); QString data( QString::fromUtf8( "[ääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääääää]" ) ); QTest::newRow( "CTEbase64:Sign" ) << data << true << false << Headers::CEbase64; QTest::newRow( "CTEbase64:Encrypt" ) << data << false << true << Headers::CE7Bit; QTest::newRow( "CTEbase64:SignEncrypt" ) << data << true << true << Headers::CE7Bit; } void CryptoComposerTest::testCTEbase64() { testSMIME(); testSMIMEOpaque(); testOpenPGPMime(); testOpenPGPInline(); } // Helper methods void CryptoComposerTest::fillComposerData( Composer* composer, QString data ) { composer->globalPart()->setFallbackCharsetEnabled( true ); composer->infoPart()->setFrom( QString::fromLatin1( "me@me.me" ) ); composer->infoPart()->setTo( QStringList( QString::fromLatin1( "you@you.you" ) ) ); composer->textPart()->setWrappedPlainText( data ); } void CryptoComposerTest::fillComposerCryptoData( Composer* composer ) { std::vector keys = MessageCore::Test::getKeys(); kDebug() << "got num of keys:" << keys.size(); QStringList recipients; recipients << QString::fromLocal8Bit( "you@you.you" ); QList > > encKeys; encKeys.append( QPair >( recipients, keys ) ); composer->setEncryptionKeys( encKeys ); composer->setSigningKeys( keys ); } void CryptoComposerTest::runSMIMETest( bool sign, bool enc, bool opaque ) { QFETCH( QString, data ); QFETCH( Headers::contentEncoding, cte ); Composer *composer = new Composer; fillComposerData( composer, data ); composer->infoPart()->setFrom( QString::fromLatin1( "test@example.com" ) ); std::vector keys = MessageCore::Test::getKeys( true ); QStringList recipients; recipients << QString::fromLocal8Bit( "you@you.you" ); QList > > encKeys; encKeys.append( QPair >( recipients, keys ) ); composer->setEncryptionKeys( encKeys ); composer->setSigningKeys( keys ); composer->setSignAndEncrypt( sign, enc ); Kleo::CryptoMessageFormat f; if( opaque ) { f = Kleo::SMIMEOpaqueFormat; } else { f = Kleo::SMIMEFormat; } composer->setMessageCryptoFormat( f ); const bool result = composer->exec(); //QEXPECT_FAIL("", "GPG setup problems", Continue); QVERIFY( result ); if ( result ) { QCOMPARE( composer->resultMessages().size(), 1 ); KMime::Message::Ptr message = composer->resultMessages().first(); delete composer; composer = 0; kDebug() << "message:" << message->encodedContent(); ComposerTestUtil::verify( sign, enc, message.get(), data.toUtf8(), f, cte ); QCOMPARE( message->from()->asUnicodeString(), QString::fromLocal8Bit( "test@example.com" ) ); QCOMPARE( message->to()->asUnicodeString(), QString::fromLocal8Bit( "you@you.you" ) ); } } diff --git a/messagecomposer/tests/cryptocomposertest.h b/messagecomposer/tests/cryptocomposertest.h index 71976b01dc..c928d38184 100644 --- a/messagecomposer/tests/cryptocomposertest.h +++ b/messagecomposer/tests/cryptocomposertest.h @@ -1,77 +1,83 @@ /* Copyright (c) 2009 Constantin Berzan Copyright (c) 2009 Leo Franchi 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 CRYPTOCOMPOSERTEST_H #define CRYPTOCOMPOSERTEST_H #include namespace MessageComposer { class Composer; } class CryptoComposerTest : public QObject { Q_OBJECT public Q_SLOTS: void initTestCase(); private Q_SLOTS: // openpgp void testOpenPGPMime(); void testOpenPGPMime_data(); // the following will do for s-mime as well, as the same sign/enc jobs are used void testEncryptSameAttachments(); void testEncryptSameAttachments_data(); void testSignEncryptLateAttachments(); void testSignEncryptLateAttachments_data(); + void testEditEncryptAttachments(); + void testEditEncryptAttachments_data(); + + void testEditEncryptAndLateAttachments(); + void testEditEncryptAndLateAttachments_data(); + // secondary recipients void testBCCEncrypt(); void testBCCEncrypt_data(); // inline pgp void testOpenPGPInline_data(); void testOpenPGPInline(); // s-mime void testSMIME_data(); void testSMIME(); void testSMIMEOpaque_data(); void testSMIMEOpaque(); // contentTransferEncoding void testCTEquPr_data(); void testCTEquPr(); void testCTEbase64_data(); void testCTEbase64(); // TODO test the code for autodetecting the charset of a text attachment. private: void fillComposerData( MessageComposer::Composer* composer, QString data ); void fillComposerCryptoData( MessageComposer::Composer* composer ); // convenience, shared code void runSMIMETest( bool sign, bool enc, bool opaque ); }; #endif