You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

3105 lines
116 KiB

/*
* This file is part of KMail.
* Copyright (c) 2011 Laurent Montel <montel@kde.org>
*
* Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
*
* Based on KMail code by:
* Copyright (c) 1997 Markus Wuebben <markus.wuebben@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kmcomposewin.h"
// KMail includes
#include "addressvalidationjob.h"
#include "attachmentcontroller.h"
#include "messagecomposer/attachmentmodel.h"
#include "attachmentview.h"
#include "codecaction.h"
#include <messagecomposer/emailaddressresolvejob.h>
#include "kleo_util.h"
#include "kmcommands.h"
#include "kmcomposereditor.h"
#include "kmkernel.h"
#include "globalsettings.h"
#include "kmmainwin.h"
#include "kmmainwidget.h"
#include "mailcomposeradaptor.h" // TODO port all D-Bus stuff...
#include "messageviewer/stl_util.h"
#include "messageviewer/util.h"
#include "messagecore/stringutil.h"
#include "messagecore/attachmentcollector.h"
#include "util.h"
#include "snippetwidget.h"
#include <messagecomposer/keyresolver.h>
#include "templatesconfiguration_kfg.h"
#include "foldercollectionmonitor.h"
#include "mailkernel.h"
#include "custommimeheader.h"
#include <messagecomposer/kmsubjectlineedit.h>
// KDEPIM includes
#include <libkpgp/kpgpblock.h>
#include <libkleo/ui/progressdialog.h>
#include <libkleo/ui/keyselectiondialog.h>
#include "kleo/cryptobackendfactory.h"
#include "kleo/exportjob.h"
#include "kleo/specialjob.h"
#include <messageviewer/objecttreeemptysource.h>
#ifndef QT_NO_CURSOR
#include <messageviewer/kcursorsaver.h>
#endif
#include <messageviewer/objecttreeparser.h>
#include <messageviewer/nodehelper.h>
#include "messageviewer/chiasmuskeyselector.h"
#include <messageviewer/globalsettings.h>
#include <messagecomposer/composer.h>
#include <messagecomposer/globalpart.h>
#include <messagecomposer/infopart.h>
#include <messagecomposer/textpart.h>
#include <messagecomposer/messagehelper.h>
#include <messagecomposersettings.h>
#include <messagecomposer/messagehelper.h>
#include <messagecomposer/signaturecontroller.h>
#include <messagecomposer/inserttextfilejob.h>
#include <messagecomposer/composerlineedit.h>
#include <messagecomposer/recipientseditor.h>
#include <messagecore/attachmentpart.h>
#include "messagecore/globalsettings.h"
#include <templateparser/templateparser.h>
#include <templatesconfiguration.h>
#include "messagecore/nodehelper.h"
#include <akonadi/kmime/messagestatus.h>
#include "messagecore/messagehelpers.h"
#include "mailcommon/folderrequester.h"
// LIBKDEPIM includes
#include <libkdepim/nepomukwarning.h>
#include <libkdepim/recentaddresses.h>
// KDEPIMLIBS includes
#include <akonadi/changerecorder.h>
#include <akonadi/itemcreatejob.h>
#include <akonadi/entitymimetypefiltermodel.h>
#include <akonadi/itemfetchjob.h>
#include <kpimutils/email.h>
#include <kpimidentities/identitymanager.h>
#include <kpimidentities/identitycombo.h>
#include <kpimidentities/identity.h>
#include <mailtransport/transportcombobox.h>
#include <mailtransport/transportmanager.h>
#include <mailtransport/transport.h>
#include <kmime/kmime_codecs.h>
#include <kmime/kmime_message.h>
// KDELIBS includes
#include <kactioncollection.h>
#include <kactionmenu.h>
#include <kapplication.h>
#include <kcharsets.h>
#include <kdebug.h>
#include <kdescendantsproxymodel.h>
#include <kedittoolbar.h>
#include <kinputdialog.h>
#include <kmenu.h>
#include <kmimetype.h>
#include <kmessagebox.h>
#include <krecentfilesaction.h>
#include <kshortcutsdialog.h>
#include <kstandarddirs.h>
#include <kstandardshortcut.h>
#include <kstatusbar.h>
#include <ktempdir.h>
#include <ktoggleaction.h>
#include <ktoolbar.h>
#include <ktoolinvocation.h>
#include <sonnet/dictionarycombobox.h>
#include <krun.h>
// Qt includes
#include <QClipboard>
#include <QEvent>
#include <QSplitter>
#include <QUuid>
#include <QDir>
#include <QMimeData>
#include <QKeyEvent>
// System includes
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <memory>
#include <boost/shared_ptr.hpp>
// MOC
#include "kmcomposewin.moc"
using Sonnet::DictionaryComboBox;
using MailTransport::TransportManager;
using MailTransport::Transport;
using KPIM::RecentAddresses;
using Message::KMeditor;
KMail::Composer *KMail::makeComposer( const KMime::Message::Ptr &msg, Composer::TemplateContext context,
uint identity, const QString & textSelection,
const QString & customTemplate ) {
return KMComposeWin::create( msg, context, identity, textSelection, customTemplate );
}
KMail::Composer *KMComposeWin::create( const KMime::Message::Ptr &msg, Composer::TemplateContext context,
uint identity, const QString & textSelection,
const QString & customTemplate ) {
return new KMComposeWin( msg, context, identity, textSelection, customTemplate );
}
int KMComposeWin::s_composerNumber = 0;
//-----------------------------------------------------------------------------
KMComposeWin::KMComposeWin( const KMime::Message::Ptr &aMsg, 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 ),
mCodecAction( 0 ),
mCryptoModuleAction( 0 ),
mEncryptChiasmusAction( 0 ),
mDummyComposer( 0 ),
mLabelWidth( 0 ),
mComposerBase( 0 ),
mSignatureStateIndicator( 0 ), mEncryptionStateIndicator( 0 ),
mPreventFccOverwrite( false ),
mCheckForForgottenAttachments( true ),
mIgnoreStickyFields( false )
{
mComposerBase = new Message::ComposerViewBase( this );
mComposerBase->setIdentityManager( kmkernel->identityManager() );
mComposerBase->setParentWidgetForGui( this );
connect( mComposerBase, SIGNAL(disableHtml(Message::ComposerViewBase::Confirmation)),
this, SLOT(disableHtml(Message::ComposerViewBase::Confirmation)) );
connect( mComposerBase, SIGNAL(enableHtml()),
this, SLOT(enableHtml()) );
connect( mComposerBase, SIGNAL(failed(QString)), this, SLOT(slotSendFailed(QString)) );
connect( mComposerBase, SIGNAL(sentSuccessfully()), this, SLOT(slotSendSuccessful()) );
connect( mComposerBase, SIGNAL(modified(bool)), this, SLOT(setModified(bool)) );
(void) new MailcomposerAdaptor( this );
mdbusObjectPath = "/Composer_" + QString::number( ++s_composerNumber );
QDBusConnection::sessionBus().registerObject( mdbusObjectPath, this );
Message::SignatureController* sigController = new Message::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( "mHeadersToEditorSplitter" );
mHeadersToEditorSplitter->setChildrenCollapsible( false );
mHeadersArea = new QWidget( mHeadersToEditorSplitter );
mHeadersArea->setSizePolicy( mHeadersToEditorSplitter->sizePolicy().horizontalPolicy(),
QSizePolicy::Expanding );
mHeadersToEditorSplitter->addWidget( mHeadersArea );
QList<int> defaultSizes;
defaultSizes << 0;
mHeadersToEditorSplitter->setSizes( defaultSizes );
QVBoxLayout *v = new QVBoxLayout( mMainWidget );
KPIM::NepomukWarning *nepomukWarning = new KPIM::NepomukWarning( "kmail-composer" );
nepomukWarning->setMissingFeatures( QStringList() << i18n("Recipient auto-completion") << i18n("Distribution lists") << i18n("Per-contact crypto preferences") );
v->addWidget( nepomukWarning );
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( "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( "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 Message::KMSubjectLineEdit( mHeadersArea, QLatin1String( "kmail2rc" ) );
mEdtSubject->setToolTip( i18n( "Set a subject for this message" ) );
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( "mSplitter" );
mSplitter->setChildrenCollapsible( false );
mSnippetSplitter = new QSplitter( Qt::Horizontal, mSplitter );
mSnippetSplitter->setObjectName( "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 );
KConfigGroup reader( KMKernel::self()->config(), "Reader" );
// 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( mDictionaryCombo, SIGNAL(dictionaryChanged(QString)),
editor, SLOT(setSpellCheckingLanguage(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()) );
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 );
Message::AttachmentModel* attachmentModel = new Message::AttachmentModel( this );
KMail::AttachmentView *attachmentView = new KMail::AttachmentView( attachmentModel, mSplitter );
attachmentView->hideIfEmpty();
KMail::AttachmentController* attachmentController = new KMail::AttachmentController( attachmentModel, attachmentView, this );
mComposerBase->setAttachmentModel( attachmentModel );
mComposerBase->setAttachmentController( attachmentController );
readConfig();
setupStatusBar();
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( Message::ComposerViewBase::LetUserConfirm );
if ( GlobalSettings::self()->useExternalEditor() ) {
editor->setUseExternalEditor( true );
editor->setExternalEditorPath( GlobalSettings::self()->externalEditor() );
}
if ( aMsg ) {
setMsg( aMsg );
}
mComposerBase->recipientsEditor()->setFocus();
editor->updateActionStates(); // set toolbar buttons to correct values
mDone = true;
mDummyComposer = new Message::Composer( this );
mDummyComposer->globalPart()->setParentWidgetForGui( this );
}
//-----------------------------------------------------------------------------
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 ) {
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;
}
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 ) {
addAttachment( urls[i], comment );
}
send( how );
}
//-----------------------------------------------------------------------------
void KMComposeWin::addAttachment( const KUrl &url, const QString &comment )
{
Q_UNUSED( comment );
mComposerBase->addAttachment( url, comment );
}
void KMComposeWin::addAttachment( const QString& name,
KMime::Headers::contentEncoding cte,
const QString& charset,
const QByteArray& data,
const QByteArray& mimeType )
{
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<int> 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();
}
Message::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
}
int id;
//This sucks awfully, but no, I cannot get an activated(int id) from
// actionContainer()
KToggleAction *act = ::qobject_cast<KToggleAction *>( sender() );
if ( !act ) {
return;
}
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, numRows;
long showHeaders;
if ( mShowHeaders < 0 ) {
showHeaders = HDR_ALL;
} else {
showHeaders = mShowHeaders;
}
for ( mask=1, mNumHeaders=0; mask<=showHeaders; mask<<=1 ) {
if ( ( showHeaders & mask ) != 0 ) {
mNumHeaders++;
}
}
numRows = mNumHeaders + 1;
delete mGrid;
mGrid = new QGridLayout( mHeadersArea );
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 )
{
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() );
if ( !mCustomTemplate.isEmpty() )
parser.process( mCustomTemplate, KMime::Message::Ptr() );
else
parser.processWithIdentity( uoid, KMime::Message::Ptr() );
return;
}
if ( mMsg->headerByType( "X-KMail-Link-Message" ) ) {
Akonadi::Item::List items;
foreach( const QString& serNumStr, mMsg->headerByType( "X-KMail-Link-Message" )->asUnicodeString().split( ',' ) )
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 );
connect( job, SIGNAL(result(KJob*)), SLOT(slotDelayedApplyTemplate(KJob*)) );
}
}
void KMComposeWin::slotDelayedApplyTemplate( KJob *job )
{
const Akonadi::ItemFetchJob *fetchJob = qobject_cast<Akonadi::ItemFetchJob*>( job );
const Akonadi::Item::List items = fetchJob->items();
const TemplateParser::TemplateParser::Mode mode = static_cast<TemplateParser::TemplateParser::Mode>( fetchJob->property( "mode" ).toInt() );
const uint uoid = fetchJob->property( "uoid" ).toUInt();
TemplateParser::TemplateParser parser( mMsg, mode );
parser.setSelection( mTextSelection );
parser.setAllowDecryption( MessageViewer::GlobalSettings::self()->automaticDecrypt() );
foreach ( const Akonadi::Item &item, items ) {
if ( !mCustomTemplate.isEmpty() )
parser.process( mCustomTemplate, MessageCore::Util::message( item ) );
else
parser.processWithIdentity( uoid, MessageCore::Util::message( item ) );
}
}
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 )
::Templates quoteTemplate( 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<Transport*> transports = TransportManager::self()->transports();
foreach ( Transport *transport, transports ) {
const QString name = transport->name().replace( '&', "&&" );
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("mail-send"), i18n("&Send Mail"), this);
actionCollection()->addAction("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 exits.
actActionNowMenu = new KActionMenu( KIcon( "mail-send" ), i18n("&Send Mail Via"), this );
actActionNowMenu->setIconText( i18n( "Send" ) );
actionCollection()->addAction( "send_default_via", actActionNowMenu );
action = new KAction( KIcon( "mail-queue" ), i18n("Send &Later"), this );
actionCollection()->addAction( "send_alternative", action );
connect( action, SIGNAL(triggered(bool)), SLOT(slotSendLater()) );
actActionLaterMenu = new KActionMenu( KIcon( "mail-queue" ), i18n("Send &Later Via"), this );
actActionLaterMenu->setIconText( i18nc( "Queue the message for sending at a later date", "Queue" ) );
actionCollection()->addAction( "send_alternative_via", actActionLaterMenu );
} else {
//default = queue, alternative = send now
QAction *action = new KAction( KIcon( "mail-queue" ), i18n("Send &Later"), this );
actionCollection()->addAction( "send_default", action );
connect( action, SIGNAL(triggered(bool)), SLOT(slotSendLater()) );
action->setShortcut( QKeySequence( Qt::CTRL + Qt::Key_Return ) );
actActionLaterMenu = new KActionMenu( KIcon( "mail-queue" ), i18n("Send &Later Via"), this );
actionCollection()->addAction( "send_default_via", actActionLaterMenu );
action = new KAction( KIcon( "mail-send" ), i18n("&Send Mail"), this );
actionCollection()->addAction( "send_alternative", action );
connect( action, SIGNAL(triggered(bool)), SLOT(slotSendNow()) );
// FIXME: change to mail_send_via icon when this exits.
actActionNowMenu = new KActionMenu( KIcon( "mail-send" ), i18n("&Send Mail Via"), this );
actionCollection()->addAction( "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( "document-save" ), i18n("Save as &Draft"), this );
actionCollection()->addAction("save_in_drafts", action );
connect( action, SIGNAL(triggered(bool)), SLOT(slotSaveDraft()) );
action = new KAction( KIcon( "document-save" ), i18n("Save as &Template"), this );
actionCollection()->addAction( "save_in_templates", action );
connect( action, SIGNAL(triggered(bool)), SLOT(slotSaveTemplate()) );
action = new KAction(KIcon("document-open"), i18n("&Insert Text File..."), this);
actionCollection()->addAction("insert_file", action );
connect(action, SIGNAL(triggered(bool)), SLOT(slotInsertFile()));
mRecentAction = new KRecentFilesAction( KIcon( "document-open" ),
i18n( "&Insert Recent Text File" ), this );
actionCollection()->addAction("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("x-office-address-book"), i18n("&Address Book"), this);
actionCollection()->addAction("addressbook", action );
connect(action, SIGNAL(triggered(bool)), SLOT(slotAddrBook()));
action = new KAction(KIcon("mail-message-new"), i18n("&New Composer"), this);
actionCollection()->addAction("new_composer", action );
connect(action, SIGNAL(triggered(bool)), SLOT(slotNewComposer()));
action->setShortcuts( KStandardShortcut::shortcut( KStandardShortcut::New ) );
action = new KAction( KIcon( "window-new" ), i18n("New Main &Window"), this );
actionCollection()->addAction( "open_mailreader", action );
connect( action, SIGNAL(triggered(bool)), SLOT(slotNewMailReader()) );
action = new KAction( i18n("Select &Recipients..."), this );
actionCollection()->addAction( "select_recipients", action );
connect( action, SIGNAL(triggered(bool)),
mComposerBase->recipientsEditor(), SLOT(selectRecipients()) );
action = new KAction( i18n("Save &Distribution List..."), this );
actionCollection()->addAction( "save_distribution_list", action );
connect( action, SIGNAL(triggered(bool)),
mComposerBase->recipientsEditor(), SLOT(saveDistributionList()) );
KStandardAction::print( this, SLOT(slotPrint()), 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, "spellcheck",
mComposerBase->editor(), SLOT(checkSpelling()) );
action = new KAction( i18n("Paste as Attac&hment"), this );
actionCollection()->addAction( "paste_att", action );
connect( action, SIGNAL(triggered(bool)), SLOT(slotPasteAsAttachment()) );
mCleanSpace = new KAction( i18n("Cl&ean Spaces"), this );
actionCollection()->addAction( "clean_spaces", mCleanSpace );
connect( mCleanSpace, SIGNAL(triggered(bool)), mComposerBase->signatureController(), SLOT(cleanSpace()) );
mFixedFontAction = new KToggleAction( i18n("Use Fi&xed Font"), this );
actionCollection()->addAction( "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( "urgent", mUrgentAction );
mRequestMDNAction = new KToggleAction( i18n("&Request Disposition Notification"), this );
actionCollection()->addAction("options_request_mdn", mRequestMDNAction );
mRequestMDNAction->setChecked(GlobalSettings::self()->requestMDN());
//----- Message-Encoding Submenu
mCodecAction = new CodecAction( CodecAction::ComposerMode, this );
actionCollection()->addAction( "charsets", mCodecAction );
mWordWrapAction = new KToggleAction( i18n( "&Wordwrap" ), this );
actionCollection()->addAction( "wordwrap", mWordWrapAction );
mWordWrapAction->setChecked( MessageComposer::MessageComposerSettings::self()->wordWrap() );
connect( mWordWrapAction, SIGNAL(toggled(bool)), SLOT(slotWordWrapToggled(bool)) );
mSnippetAction = new KToggleAction( i18n("&Snippets"), this );
actionCollection()->addAction( "snippets", mSnippetAction );
connect( mSnippetAction, SIGNAL(toggled(bool)),
mSnippetWidget, SLOT(setVisible(bool)) );
mSnippetAction->setChecked( GlobalSettings::self()->showSnippetManager() );
mAutoSpellCheckingAction = new KToggleAction( KIcon( "tools-check-spelling" ),
i18n("&Automatic Spellchecking"),
this );
actionCollection()->addAction( "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)) );
//these are checkable!!!
markupAction = new KToggleAction( i18n("Formatting (HTML)"), this );
markupAction->setIconText( i18n("HTML") );
actionCollection()->addAction( "html", markupAction );
connect( markupAction, SIGNAL(triggered(bool)), SLOT(slotToggleMarkup()) );
mAllFieldsAction = new KToggleAction( i18n("&All Fields"), this);
actionCollection()->addAction( "show_all_fields", mAllFieldsAction );
connect( mAllFieldsAction, SIGNAL(triggered(bool)), SLOT(slotView()));
mIdentityAction = new KToggleAction(i18n("&Identity"), this);
actionCollection()->addAction("show_identity", mIdentityAction );
connect( mIdentityAction, SIGNAL(triggered(bool)), SLOT(slotView()));
mDictionaryAction = new KToggleAction(i18n("&Dictionary"), this);
actionCollection()->addAction("show_dictionary", mDictionaryAction );
connect( mDictionaryAction, SIGNAL(triggered(bool)), SLOT(slotView()));
mFccAction = new KToggleAction(i18n("&Sent-Mail Folder"), this);
actionCollection()->addAction("show_fcc", mFccAction );
connect( mFccAction, SIGNAL(triggered(bool)), SLOT(slotView()));
mTransportAction = new KToggleAction(i18n("&Mail Transport"), this);
actionCollection()->addAction("show_transport", mTransportAction );
connect( mTransportAction, SIGNAL(triggered(bool)), SLOT(slotView()));
mFromAction = new KToggleAction(i18n("&From"), this);
actionCollection()->addAction("show_from", mFromAction );
connect( mFromAction, SIGNAL(triggered(bool)), SLOT(slotView()));
mReplyToAction = new KToggleAction(i18n("&Reply To"), this);
actionCollection()->addAction("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("show_subject", mSubjectAction );
connect(mSubjectAction, SIGNAL(triggered(bool)), SLOT(slotView()));
//end of checkable
action = new KAction( i18n("Append S&ignature"), this );
actionCollection()->addAction( "append_signature", action );
connect( action, SIGNAL(triggered(bool)), mComposerBase->signatureController(), SLOT(appendSignature()));
action = new KAction( i18n("Pr&epend Signature"), this );
actionCollection()->addAction( "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( "insert_signature_at_cursor_position", action );
connect( action, SIGNAL(triggered(bool)), mComposerBase->signatureController(), SLOT(insertSignatureAtCursor()) );
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( "setup_spellchecker", action );
connect( action, SIGNAL(triggered(bool)), SLOT(slotSpellcheckConfig()) );
if ( Kleo::CryptoBackendFactory::instance()->protocol( "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;
}
mEncryptAction = new KToggleAction(KIcon("document-encrypt"), i18n("&Encrypt Message"), this);
mEncryptAction->setIconText( i18n( "Encrypt" ) );
actionCollection()->addAction("encrypt_message", mEncryptAction );
mSignAction = new KToggleAction(KIcon("document-sign"), i18n("&Sign Message"), this);
mSignAction->setIconText( i18n( "Sign" ) );
actionCollection()->addAction("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 = GlobalSettings::self()->pgpAutoSign();
changeCryptoAction();
connect( mEncryptAction, SIGNAL(toggled(bool)),
SLOT(slotEncryptToggled(bool)) );
connect( mSignAction, SIGNAL(toggled(bool)),
SLOT(slotSignToggled(bool)) );
QStringList l;
for ( int i=0 ; i<numCryptoMessageFormats ; ++i ) {
l.push_back( Kleo::cryptoMessageFormatToLabel( cryptoMessageFormats[i] ) );
}
mCryptoModuleAction = new KSelectAction(i18n("&Cryptographic Message Format"), this);
actionCollection()->addAction("options_select_crypto", mCryptoModuleAction );
connect(mCryptoModuleAction, SIGNAL(triggered(int)), SLOT(slotSelectCryptoModule()));
mCryptoModuleAction->setItems( l );
mCryptoModuleAction->setToolTip( i18n( "Select a cryptographic format for this message" ) );
actionFormatReset = new KAction( KIcon( "draw-eraser" ), i18n("Reset Font Settings"), this );
actionFormatReset->setIconText( i18n("Reset Font") );
actionCollection()->addAction( "format_reset", actionFormatReset );
connect( actionFormatReset, SIGNAL(triggered(bool)), SLOT(slotFormatReset()) );
mComposerBase->editor()->createActions( actionCollection() );
createGUI( "kmcomposerui.rc" );
connect( toolBar( "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( "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 ) && GlobalSettings::self()->pgpAutoSign() );
}
}
//-----------------------------------------------------------------------------
void KMComposeWin::setupStatusBar( void )
{
statusBar()->insertItem( "", 0, 1 );
statusBar()->setItemAlignment( 0, Qt::AlignLeft | Qt::AlignVCenter );
statusBar()->insertPermanentItem( overwriteModeStr(), 4,0 );
statusBar()->insertPermanentItem( i18n(" Spellcheck: %1 ", QString( " " )), 3, 0) ;
statusBar()->insertPermanentItem( i18n(" Column: %1 ", QString( " " ) ), 2, 0 );
statusBar()->insertPermanentItem(
i18nc("Shows the linenumber of the cursor position.", " Line: %1 "
, QString( " " ) ), 1, 0 );
}
//-----------------------------------------------------------------------------
void KMComposeWin::setupEditor( void )
{
QFontMetrics fm( mBodyFont );
mComposerBase->editor()->setTabStopWidth( fm.width( QChar(' ') ) * 8 );
slotWordWrapToggled( MessageComposer::MessageComposerSettings::self()->wordWrap() );
// Font setup
slotUpdateFont();
connect( mComposerBase->editor(), SIGNAL(cursorPositionChanged()),
this, SLOT(slotCursorPositionChanged()) );
slotCursorPositionChanged();
}
//-----------------------------------------------------------------------------
QString KMComposeWin::subject() const
{
return Message::Util::cleanedUpHeaderString( mEdtSubject->toPlainText() );
}
//-----------------------------------------------------------------------------
QString KMComposeWin::from() const
{
return Message::Util::cleanedUpHeaderString( mEdtFrom->text() );
}
//-----------------------------------------------------------------------------
QString KMComposeWin::replyTo() const
{
if ( mEdtReplyTo ) {
return Message::Util::cleanedUpHeaderString( mEdtReplyTo->text() );
} else {
return QString();
}
}
//-----------------------------------------------------------------------------
void KMComposeWin::decryptOrStripOffCleartextSignature( QByteArray &body )
{
QList<Kpgp::Block> pgpBlocks;
QList<QByteArray> 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::setMsg( const KMime::Message::Ptr &newMsg, bool mayAutoSign,
bool allowDecryption, bool isModified )
{
if ( !newMsg ) {
kDebug() << "newMsg == 0!";
return;
}
mComposerBase->setMessage( newMsg );
mMsg = newMsg;
KPIMIdentities::IdentityManager * im = KMKernel::self()->identityManager();
mEdtFrom->setText( mMsg->from()->asUnicodeString() );
mEdtReplyTo->setText( mMsg->replyTo()->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, "\n\t" );
}
mMsg->setHeader( new KMime::Headers::Generic( "X-Face", mMsg.get(), xface.toUtf8(), "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<Kleo::CryptoMessageFormat>(
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() );
}
KMime::Content *msgContent = new KMime::Content;
msgContent->setContent( mMsg->encodedContent() );
msgContent->parse();
MessageViewer::EmptySource emptySource;
MessageViewer::ObjectTreeParser otp( &emptySource );//All default are ok
otp.parseObjectTree( msgContent );
bool shouldSetCharset = false;
if ( ( mContext == Reply || mContext == ReplyToAll || mContext == Forward ) && MessageComposer::MessageComposerSettings::forceReplyCharset() )
shouldSetCharset = true;
if ( shouldSetCharset && !otp.textualContentCharset().isEmpty() )
mOriginalPreferredCharset = otp.textualContentCharset();
// always set auto charset, but prefer original when composing if force reply is set.
setAutoCharset();
#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()=="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()) );
}
}
setModified( isModified );
// honor "keep reply in this folder" setting even when the identity is changed later on
mPreventFccOverwrite = ( !kmailFcc.isEmpty() && ident.fcc() != kmailFcc );
}
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::isModified() const
{
return ( mComposerBase->editor()->document()->isModified() ||
mEdtFrom->isModified() ||
( mEdtReplyTo && mEdtReplyTo->isModified() ) ||
mComposerBase->recipientsEditor()->isModified() ||
mEdtSubject->document()->isModified() );
}
//-----------------------------------------------------------------------------
void KMComposeWin::setModified( 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() ) {
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, "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;
}
//-----------------------------------------------------------------------------
bool KMComposeWin::userForgotAttachment()
{
bool checkForForgottenAttachments = mCheckForForgottenAttachments && GlobalSettings::self()->showForgottenAttachmentWarning();
if ( !checkForForgottenAttachments )
return false;
mComposerBase->setSubject( subject() ); //be sure the composer knows the subject
bool missingAttachments = mComposerBase->checkForMissingAttachments( GlobalSettings::self()->attachmentKeywords() );
return missingAttachments;
}
void KMComposeWin::autoSaveMessage()
{
applyComposerSetting( mComposerBase );
mComposerBase->autoSaveMessage();
}
bool KMComposeWin::encryptToSelf()
{
// return !Kpgp::Module::getKpgp() || Kpgp::Module::getKpgp()->encryptToSelf();
return GlobalSettings::self()->cryptoEncryptToSelf();
}
bool KMComposeWin::showKeyApprovalDialog()
{
return GlobalSettings::self()->cryptoShowKeysForApproval();
}
int KMComposeWin::encryptKeyNearExpiryWarningThresholdInDays() {
if ( ! MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire() ) {
return -1;
}
const int num =
MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrKeyNearExpiryThresholdDays();
return qMax( 1, num );
}
int KMComposeWin::signingKeyNearExpiryWarningThresholdInDays()
{
if ( ! MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire() ) {
return -1;
}
const int num =
MessageComposer::MessageComposerSettings::self()->cryptoWarnSignKeyNearExpiryThresholdDays();
return qMax( 1, num );
}
int KMComposeWin::encryptRootCertNearExpiryWarningThresholdInDays()
{
if ( ! MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire() ) {
return -1;
}
const int num =
MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrRootNearExpiryThresholdDays();
return qMax( 1, num );
}
int KMComposeWin::signingRootCertNearExpiryWarningThresholdInDays() {
if ( ! MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire() ) {
return -1;
}
const int num =
MessageComposer::MessageComposerSettings::self()->cryptoWarnSignRootNearExpiryThresholdDays();
return qMax( 1, num );
}
int KMComposeWin::encryptChainCertNearExpiryWarningThresholdInDays()
{
if ( ! MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire() ) {
return -1;
}
const int num =
MessageComposer::MessageComposerSettings::self()->cryptoWarnEncrChaincertNearExpiryThresholdDays();
return qMax( 1, num );
}
int KMComposeWin::signingChainCertNearExpiryWarningThresholdInDays()
{
if ( ! MessageComposer::MessageComposerSettings::self()->cryptoWarnWhenNearExpire() ) {
return -1;
}
const int num =
MessageComposer::MessageComposerSettings::self()->cryptoWarnSignChaincertNearExpiryThresholdDays();;
return qMax( 1, num );
}
void KMComposeWin::slotSendFailed( const QString& msg )
{
// setModified( false );
setEnabled( true );
KMessageBox::sorry( mMainWidget, msg,
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() );
}
bool KMComposeWin::queryExit ()
{
return true;
}
//-----------------------------------------------------------------------------
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;
}
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<KSelectAction *>( 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, charset );
if ( codecNameToSet.isEmpty() ) {
kWarning() << "Could not find charset" << charset;
setAutoCharset();
}
else
mCodecAction->setCurrentCodec( codecNameToSet );
}
//-----------------------------------------------------------------------------
void KMComposeWin::slotAddrBook()
{
KRun::runCommand("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
{
QString encoding = MessageViewer::NodeHelper::encodingForName( u.fileEncoding() ).toLatin1();
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
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 ];
}
Message::InsertTextFileJob *job = new Message::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() );
}
void KMComposeWin::slotPasteAsAttachment()
{
const QMimeData *mimeData = QApplication::clipboard()->mimeData();
if( mimeData->hasUrls() ) {
// If the clipboard contains a list of URL, attach each file.
const KUrl::List urls = KUrl::List::fromMimeData( mimeData );
foreach( const KUrl &url, urls ) {
mComposerBase->attachmentController()->addAttachment( url );
}
} else 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, "utf-8", QApplication::clipboard()->text().toUtf8(), "text/plain" );
}
}
}
QString KMComposeWin::addQuotesToText( const QString &inputText ) const
{
QString answer = QString( inputText );
QString indentStr = mComposerBase->editor()->quotePrefixName();
answer.replace( '\n', '\n' + indentStr );
answer.prepend( indentStr );
answer += '\n';
return MessageCore::StringUtil::smartQuote( answer, MessageComposer::MessageComposerSettings::self()->lineWrapWidth() );
}
//-----------------------------------------------------------------------------
void KMComposeWin::slotUndo()
{
QWidget *fw = focusWidget();
if ( !fw ) {
return;
}
if (::qobject_cast<Message::KMSubjectLineEdit*>( fw )) {
static_cast<Message::KMSubjectLineEdit*>( fw )->undo();
}else if ( ::qobject_cast<KMComposerEditor*>( fw ) ) {
static_cast<KTextEdit*>( fw )->undo();
} else if (::qobject_cast<KLineEdit*>( fw )) {
static_cast<KLineEdit*>( fw )->undo();
}
}
void KMComposeWin::slotRedo()
{
QWidget *fw = focusWidget();
if ( !fw ) {
return;
}
if (::qobject_cast<Message::KMSubjectLineEdit*>( fw )) {
static_cast<Message::KMSubjectLineEdit*>( fw )->redo();
} else if ( ::qobject_cast<KMComposerEditor*>( fw ) ) {
static_cast<KTextEdit*>( fw )->redo();
} else if (::qobject_cast<KLineEdit*>( fw )) {
static_cast<KLineEdit*>( fw )->redo();
}
}
//-----------------------------------------------------------------------------
void KMComposeWin::slotCut()
{
QWidget *fw = focusWidget();
if ( !fw ) {
return;
}
if ( ::qobject_cast<Message::KMSubjectLineEdit*>( fw ) ) {
static_cast<Message::KMSubjectLineEdit*>( fw )->cut();
} else if ( ::qobject_cast<KMComposerEditor*>( fw ) ) {
static_cast<KTextEdit*>(fw)->cut();
} else if ( ::qobject_cast<KLineEdit*>( fw ) ) {
static_cast<KLineEdit*>( fw )->cut();
}
}
//-----------------------------------------------------------------------------
void KMComposeWin::slotCopy()
{
QWidget *fw = focusWidget();
if ( !fw ) {
return;
}
if ( ::qobject_cast<Message::KMSubjectLineEdit*>( fw ) ) {
static_cast<Message::KMSubjectLineEdit*>( fw )->copy();
} else if ( ::qobject_cast<KMComposerEditor*>( fw ) ) {
static_cast<KTextEdit*>(fw)->copy();
} else if ( ::qobject_cast<KLineEdit*>( fw ) ) {
static_cast<KLineEdit*>( fw )->copy();
}
}
//-----------------------------------------------------------------------------
void KMComposeWin::slotPaste()
{
QWidget * const fw = focusWidget();
if ( !fw ) {
return;
}
if ( ::qobject_cast<Message::KMSubjectLineEdit*>( fw ) ) {
static_cast<Message::KMSubjectLineEdit*>( fw )->paste();
} else if ( ::qobject_cast<KMComposerEditor*>( fw ) ) {
static_cast<KTextEdit*>(fw)->paste();
} else if ( ::qobject_cast<KLineEdit*>( fw ) ) {
static_cast<KLineEdit*>( fw )->paste();
}
}
//-----------------------------------------------------------------------------
void KMComposeWin::slotMarkAll()
{
QWidget *fw = focusWidget();
if ( !fw ) {
return;
}
if (::qobject_cast<Message::KMSubjectLineEdit*>( fw )) {
static_cast<Message::KMSubjectLineEdit*>( fw )->selectAll();
} else if ( ::qobject_cast<KLineEdit*>( fw ) ) {
static_cast<KLineEdit*>( fw )->selectAll();
} else if (::qobject_cast<KMComposerEditor*>( fw )) {
static_cast<KTextEdit*>( 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 );
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( '(' + i18n("unnamed") + ')' );
} else {
setCaption( s.replace( QChar('\n'), ' ' ) );
}
}
//-----------------------------------------------------------------------------
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("<qt><p>You have requested that messages be "
"encrypted to yourself, but the currently selected "
"identity does not define an (OpenPGP or S/MIME) "
"encryption key to use for this.</p>"
"<p>Please select the key(s) to use "
"in the identity configuration.</p>"
"</qt>"),
i18n("Undefined Encryption Key") );
setModified( wasModified );
}
encrypt = false;
}
// make sure the mEncryptAction is in the right state
mEncryptAction->setChecked( encrypt );
// show the appropriate icon
if ( encrypt ) {
mEncryptAction->setIcon( KIcon( "document-encrypt" ) );
} else {
mEncryptAction->setIcon( KIcon( "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("<qt><p>In order to be able to sign "
"this message you first have to "
"define the (OpenPGP or S/MIME) signing key "
"to use.</p>"
"<p>Please select the key to use "
"in the identity configuration.</p>"
"</qt>"),
i18n("Undefined Signing Key") );
setModified( wasModified );
}
sign = false;
}
// make sure the mSignAction is in the right state
mSignAction->setChecked( sign );
// 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()->setWordWrapMode( QTextOption::NoWrap );
}
//-----------------------------------------------------------------------------
void KMComposeWin::forceDisableHtml()
{
mForceDisableHtml = true;
disableHtml( Message::ComposerViewBase::NoConfirmationNeeded );
markupAction->setEnabled( false );
// FIXME: Remove the toggle toolbar action somehow
}
void KMComposeWin::disableRecipientNumberCheck()
{
// mCheckForRecipients = false; // TODO composer...
}
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()
{
Message::Composer* composer = createSimpleComposer();
mMiscComposers.append( composer );
connect( composer, SIGNAL(result(KJob*)),
this, SLOT(slotPrintComposeResult(KJob*)) );
composer->start();
}
void KMComposeWin::slotPrintComposeResult( KJob *job )
{
Q_ASSERT( dynamic_cast< Message::Composer* >( job ) );
Message::Composer* composer = dynamic_cast< Message::Composer* >( job );
Q_ASSERT( mMiscComposers.contains( composer ) );
mMiscComposers.removeAll( composer );
if( composer->error() == Message::Composer::NoError ) {
Q_ASSERT( composer->resultMessages().size() == 1 );
Akonadi::Item printItem;
printItem.setPayload<KMime::Message::Ptr>( composer->resultMessages().first() );
KMCommand *command = new KMPrintCommand( this, printItem,0,
0, ( mComposerBase->editor()->textMode() == KMeditor::Rich ) );
command->start();
} else {
// TODO: error reporting to the user
kWarning() << "Composer for printing failed:" << composer->errorString();
}
}
//----------------------------------------------------------------------------
void KMComposeWin::doSend( MessageSender::SendMethod method,
MessageSender::SaveIn saveIn )
{
// TODO integrate with MDA online status
if ( method != MessageSender::SendLater && KMKernel::self()->isOffline() ) {
KMessageBox::information( this,
i18n("KMail is currently in offline mode. "
"Your messages will be kept in the outbox until you go online."),
i18n("Online/Offline"), "kmailIsOffline" );
method = MessageSender::SendLater;
}
if ( saveIn == 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(),
":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")),
"no_subject_specified" );
if ( rc == KMessageBox::No ) {
return;
}
}
if ( userForgotAttachment() ) {
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<int>( method ) );
job->setProperty( "saveIn", static_cast<int>( saveIn ) );
connect( job, SIGNAL(result(KJob*)), SLOT(slotDoDelayedSend(KJob*)) );
job->start();
// we'll call send from within slotDoDelaySend
} else {
if( saveIn == 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() );
return;
}
const AddressValidationJob *validateJob = qobject_cast<AddressValidationJob*>( job );
// Abort sending if one of the recipient addresses is invalid ...
if ( !validateJob->isValid() )
return;
// ... otherwise continue as usual
const MessageSender::SendMethod method = static_cast<MessageSender::SendMethod>( job->property( "method" ).toInt() );
const MessageSender::SaveIn saveIn = static_cast<MessageSender::SaveIn>( job->property( "saveIn" ).toInt() );
doDelayedSend( method, saveIn );
}
void KMComposeWin::applyComposerSetting( Message::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( MessageSender::SendMethod method, MessageSender::SaveIn saveIn )
{
#ifndef QT_NO_CURSOR
MessageViewer::KCursorSaver busy( MessageViewer::KBusyPtr::busy() );
#endif
applyComposerSetting( mComposerBase );
if ( mForceDisableHtml )
disableHtml( Message::ComposerViewBase::NoConfirmationNeeded );
bool sign = mSignAction->isChecked();
bool encrypt = mEncryptAction->isChecked();
mComposerBase->setCryptoOptions( sign, encrypt, cryptoMessageFormat(),
( ( saveIn != MessageSender::SaveInNone && GlobalSettings::self()->neverEncryptDrafts() )
|| mSigningAndEncryptionExplicitlyDisabled ) );
const int num = GlobalSettings::self()->customMessageHeadersCount();
QMap<QByteArray, QString> customHeader;
for(int ix=0; ix<num; ++ix) {
CustomMimeHeader customMimeHeader( QString::number(ix) );
customMimeHeader.readConfig();
customHeader.insert(customMimeHeader.custHeaderName().toLatin1(), customMimeHeader.custHeaderValue() );
}
mComposerBase->setCustomHeader( customHeader );
mComposerBase->send( method, saveIn );
}
//----------------------------------------------------------------------------
void KMComposeWin::slotSendLater()
{
if ( !TransportManager::self()->showTransportCreationDialog( this, TransportManager::IfNoTransportExists ) )
return;
if ( !checkRecipientNumber() )
return;
if ( mComposerBase->editor()->checkExternalEditorFinished() ) {
doSend( MessageSender::SendLater );
}
}
//----------------------------------------------------------------------------
void KMComposeWin::slotSaveDraft()
{
if ( mComposerBase->editor()->checkExternalEditorFinished() ) {
doSend( MessageSender::SendLater, MessageSender::SaveInDrafts );
}
}
//----------------------------------------------------------------------------
void KMComposeWin::slotSaveTemplate()
{
if ( mComposerBase->editor()->checkExternalEditorFinished() ) {
doSend( MessageSender::SendLater, MessageSender::SaveInTemplates );
}
}
//----------------------------------------------------------------------------
void KMComposeWin::slotSendNowVia( QAction *item )
{
const QList<int> 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<int> 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()->confirmBeforeSend() ) {
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( MessageSender::SendImmediate );
} else if ( rc == KMessageBox::No ) {
doSend( MessageSender::SendLater );
}
} else {
doSend( 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( Message::ComposerViewBase::NoConfirmationNeeded );;
return;
}
mComposerBase->editor()->enableRichTextMode();
if ( !toolBar( "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( "htmlToolBar" ), SLOT(show()) );
}
if ( !markupAction->isChecked() )
markupAction->setChecked( true );
mSaveFont = mComposerBase->editor()->currentFont();
mComposerBase->editor()->updateActionStates();
mComposerBase->editor()->setActionsEnabled( true );
}
//-----------------------------------------------------------------------------
void KMComposeWin::disableHtml( Message::ComposerViewBase::Confirmation confirmation )
{
if ( confirmation == Message::ComposerViewBase::LetUserConfirm && mComposerBase->editor()->isFormattingUsed() && !mForceDisableHtml ) {
int choice = KMessageBox::warningContinueCancel( 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" ) ), KStandardGuiItem::cancel(),
"LoseFormattingWarning" );
if ( choice != KMessageBox::Continue ) {
enableHtml();
return;
}
}
mComposerBase->editor()->switchToPlainText();
mComposerBase->editor()->setActionsEnabled( false );
slotUpdateFont();
if ( toolBar( "htmlToolBar" )->isVisible() ) {
// See the comment in enableHtml() why we use a singleshot timer, similar situation here.
QTimer::singleShot( 0, toolBar( "htmlToolBar" ), SLOT(hide()) );
}
if ( markupAction->isChecked() )
markupAction->setChecked( false );
}
//-----------------------------------------------------------------------------
void KMComposeWin::slotToggleMarkup()
{
htmlToolBarVisibilityChanged( markupAction->isChecked() );
}
//-----------------------------------------------------------------------------
void KMComposeWin::slotTextModeChanged( Message::KMeditor::Mode mode )
{
if ( mode == KMeditor::Plain )
disableHtml( Message::ComposerViewBase::NoConfirmationNeeded ); // ### Can this happen at all?
else
enableHtml();
}
//-----------------------------------------------------------------------------
void KMComposeWin::htmlToolBarVisibilityChanged( bool visible )
{
if ( visible )
enableHtml();
else
disableHtml( Message::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("", 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;
}
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 );
mComposerBase->identityChanged( ident, oldIdentity );
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, "\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() );
}
}
if ( !mBtnDictionary->isChecked() && !mIgnoreStickyFields ) {
mDictionaryCombo->setCurrentByDictionaryName( ident.dictionary() );
}
mComposerBase->editor()->setSpellCheckingLanguage( mDictionaryCombo->currentDictionary() );
mEdtSubject->setSpellCheckingLanguage( mDictionaryCombo->currentDictionary() );
if ( !mBtnFcc->isChecked() && !mPreventFccOverwrite ) {
setFcc( ident.fcc() );
}
// if unmodified, apply new template, if one is set
bool msgCleared = false;
if ( !isModified() && !( ident.templates().isEmpty() && mCustomTemplate.isEmpty() ) &&
!initalChange ) {
applyTemplate( uoid );
msgCleared = true;
}
// 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 );
}
//-----------------------------------------------------------------------------
void KMComposeWin::slotSpellcheckConfig()
{
mComposerBase->editor()->showSpellConfigDialog( "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( "kmcomposerui.rc" );
applyMainWindowSettings( KMKernel::self()->config()->group( "Composer") );
}
void KMComposeWin::slotEditKeys()
{
KShortcutsDialog::configure( actionCollection(),
KShortcutsEditor::LetterShortcutsDisallowed );
}
void KMComposeWin::setReplyFocus( bool hasMessage )
{
Q_UNUSED( hasMessage );
// 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::slotSetAlwaysSend( bool bAlways )
{
//Never implemented in kmail 4.x or 4.8... => comment it for the moment it's a virtual function in composer.
//mAlwaysSend = bAlways;
}
void KMComposeWin::slotFormatReset()
{
mComposerBase->editor()->setTextForegroundColor( palette().text().color() );
mComposerBase->editor()->setFont( mSaveFont );
}
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 + " -> " + url, 0 );
}
else {
statusBar()->changeItem( QString(), 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<Kleo::SpecialJob> job( chiasmus->specialJob( "x-obtain-keys", QMap<QString,QVariant>() ) );
if ( !job.get() ) {
const QString msg = i18n( "Chiasmus backend does not offer the "
"\"x-obtain-keys\" function. Please report this bug." );
KMessageBox::error( this, msg, i18n( "Chiasmus Backend Error" ) );
return;
}
if ( job->exec() ) {
job->showErrorDialog( this, i18n( "Chiasmus Backend Error" ) );
return;
}
const QVariant result = job->property( "result" );
if ( result.type() != QVariant::StringList ) {
const QString msg = i18n( "Unexpected return value from Chiasmus backend: "
"The \"x-obtain-keys\" function did not return a "
"string list. Please report this bug." );
KMessageBox::error( this, msg, i18n( "Chiasmus Backend Error" ) );
return;
}
const QStringList keys = result.toStringList();
if ( keys.empty() ) {
const QString msg = i18n( "No keys have been found. Please check that a "
"valid key path has been set in the Chiasmus "
"configuration." );
KMessageBox::information( this, msg, i18n( "No Chiasmus Keys Found" ) );
return;
}
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();
}
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 );
}