kmail Library API Documentation

kmcomposewin.cpp

00001 // -*- mode: C++; c-file-style: "gnu" -*-
00002 // kmcomposewin.cpp
00003 // Author: Markus Wuebben <markus.wuebben@kde.org>
00004 // This code is published under the GPL.
00005 
00006 // keep this in sync with the define in configuredialog.h
00007 #define DEFAULT_EDITOR_STR "kate %f"
00008 
00009 #undef GrayScale
00010 #undef Color
00011 #include <config.h>
00012 
00013 #include "kmcomposewin.h"
00014 
00015 #include "globalsettings.h"
00016 #include "kmglobalns.h" //for text completion mode struct
00017 
00018 #include "kmmainwin.h"
00019 #include "kmreaderwin.h"
00020 #include "kmreadermainwin.h"
00021 #include "messagesender.h"
00022 #include "kmmsgpartdlg.h"
00023 #include <kpgpblock.h>
00024 #include <kaddrbook.h>
00025 #include "kmaddrbook.h"
00026 #include "kmmsgdict.h"
00027 #include "kmfolderimap.h"
00028 #include "kmfoldermaildir.h"
00029 #include "kmfoldermgr.h"
00030 #include "kmfoldercombobox.h"
00031 #include "kmtransport.h"
00032 #include "kmcommands.h"
00033 #include "kcursorsaver.h"
00034 #include "kmkernel.h"
00035 #include "partNode.h"
00036 #include "attachmentlistview.h"
00037 using KMail::AttachmentListView;
00038 #include "dictionarycombobox.h"
00039 using KMail::DictionaryComboBox;
00040 #include "addressesdialog.h"
00041 using KPIM::AddressesDialog;
00042 #include <maillistdrag.h>
00043 using KPIM::MailListDrag;
00044 #include "recentaddresses.h"
00045 #include "completionordereditor.h"
00046 #include "ldapclient.h"
00047 
00048 using KRecentAddress::RecentAddresses;
00049 #include "kleo_util.h"
00050 #include "stl_util.h"
00051 
00052 #include "attachmentcollector.h"
00053 #include "objecttreeparser.h"
00054 
00055 #include <libkpimidentities/identitymanager.h>
00056 #include <libkpimidentities/identitycombo.h>
00057 #include <libkpimidentities/identity.h>
00058 #include <libkdepim/kfileio.h>
00059 #include <libkdepim/email.h>
00060 #include <kleo/cryptobackendfactory.h>
00061 #include <kleo/exportjob.h>
00062 #include <kleo/specialjob.h>
00063 #include <ui/progressdialog.h>
00064 #include <ui/keyselectiondialog.h>
00065 
00066 #include <gpgmepp/context.h>
00067 #include <gpgmepp/key.h>
00068 
00069 #include "klistboxdialog.h"
00070 
00071 #include "messagecomposer.h"
00072 
00073 #include <kcharsets.h>
00074 #include <kcompletionbox.h>
00075 #include <kcursor.h>
00076 #include <kcombobox.h>
00077 #include <kstdaccel.h>
00078 #include <kpopupmenu.h>
00079 #include <kedittoolbar.h>
00080 #include <kkeydialog.h>
00081 #include <kdebug.h>
00082 #include <kfiledialog.h>
00083 #include <kwin.h>
00084 #include <kinputdialog.h>
00085 #include <kmessagebox.h>
00086 #include <kurldrag.h>
00087 #include <kio/scheduler.h>
00088 #include <ktempfile.h>
00089 #include <klocale.h>
00090 #include <kapplication.h>
00091 #include <kstatusbar.h>
00092 #include <kaction.h>
00093 #include <kdirwatch.h>
00094 #include <kstdguiitem.h>
00095 #include <kiconloader.h>
00096 #include <kpushbutton.h>
00097 #include <kmenubar.h>
00098 #include <ktoolbarbutton.h>
00099 #include <kuserprofile.h> // for KServiceTypeProfile
00100 #include <krun.h>
00101 //#include <keditlistbox.h>
00102 
00103 #include <kspell.h>
00104 #include <kspelldlg.h>
00105 #include <spellingfilter.h>
00106 #include <ksyntaxhighlighter.h>
00107 #include <kcolordialog.h>
00108 #include <ksavefile.h>
00109 
00110 #include <qtabdialog.h>
00111 #include <qregexp.h>
00112 #include <qbuffer.h>
00113 #include <qtooltip.h>
00114 #include <qtextcodec.h>
00115 #include <qheader.h>
00116 #include <qwhatsthis.h>
00117 #include <qfontdatabase.h>
00118 
00119 #include <mimelib/mimepp.h>
00120 
00121 #include <algorithm>
00122 #include <memory>
00123 
00124 #include <sys/stat.h>
00125 #include <sys/types.h>
00126 #include <stdlib.h>
00127 #include <unistd.h>
00128 #include <errno.h>
00129 #include <fcntl.h>
00130 #include <assert.h>
00131 
00132 #include "kmcomposewin.moc"
00133 
00134 //-----------------------------------------------------------------------------
00135 KMComposeWin::KMComposeWin( KMMessage *aMsg, uint id  )
00136   : MailComposerIface(), KMail::SecondaryWindow( "kmail-composer#" ),
00137     mSpellCheckInProgress( false ),
00138     mDone( false ),
00139     mAtmModified( false ),
00140     mMsg( 0 ),
00141     mAttachMenu( 0 ),
00142     mSigningAndEncryptionExplicitlyDisabled( false ),
00143     mAutoRequestMDN( false ),
00144     mFolder( 0 ),
00145     mUseHTMLEditor( false ),
00146     mId( id ),
00147     mAttachPK( 0 ), mAttachMPK( 0 ),
00148     mAttachRemoveAction( 0 ), mAttachSaveAction( 0 ), mAttachPropertiesAction( 0 ),
00149     mAppendSignatureAction( 0 ), mPrependSignatureAction( 0 ), mInsertSignatureAction( 0 ),
00150     mSignAction( 0 ), mEncryptAction( 0 ), mRequestMDNAction( 0 ),
00151     mUrgentAction( 0 ), mAllFieldsAction( 0 ), mFromAction( 0 ),
00152     mReplyToAction( 0 ), mToAction( 0 ), mCcAction( 0 ), mBccAction( 0 ),
00153     mSubjectAction( 0 ),
00154     mIdentityAction( 0 ), mTransportAction( 0 ), mFccAction( 0 ),
00155     mWordWrapAction( 0 ), mFixedFontAction( 0 ), mAutoSpellCheckingAction( 0 ),
00156     mDictionaryAction( 0 ),
00157     mEncodingAction( 0 ),
00158     mCryptoModuleAction( 0 ),
00159     mTextCompletionAction( 0 ),
00160 #ifdef KLEO_CHIASMUS
00161     mEncryptChiasmusAction( 0 ),
00162     mEncryptBodyChiasmusAction( 0 ),
00163     mEncryptWithChiasmus( false ),
00164     mEncryptBodyWithChiasmus( false ),
00165 #endif
00166     mComposer( 0 ),
00167     mAutoSaveTimer( 0 ), mLastAutoSaveErrno( 0 )
00168 {
00169   // Set this to be the group leader for all subdialogs - this means
00170   // modal subdialogs will only affect this dialog, not the other windows
00171   setWFlags( getWFlags() | WGroupLeader );
00172 
00173   mSubjectTextWasSpellChecked = false;
00174   if (kmkernel->xmlGuiInstance())
00175     setInstance( kmkernel->xmlGuiInstance() );
00176   mMainWidget = new QWidget(this);
00177   mIdentity = new KPIM::IdentityCombo(kmkernel->identityManager(), mMainWidget);
00178   mDictionaryCombo = new DictionaryComboBox( mMainWidget );
00179   mFcc = new KMFolderComboBox(mMainWidget);
00180   mFcc->showOutboxFolder( FALSE );
00181   mTransport = new QComboBox(true, mMainWidget);
00182   mEdtFrom = new KMLineEdit(this,false,mMainWidget);
00183   mEdtReplyTo = new KMLineEdit(this,false,mMainWidget);
00184   mEdtTo = new KMLineEdit(this,true,mMainWidget);
00185   mEdtCc = new KMLineEdit(this,true,mMainWidget);
00186   mEdtBcc = new KMLineEdit(this,true,mMainWidget);
00187   mEdtSubject = new KMLineEditSpell(this,false,mMainWidget, "subjectLine");
00188   mLblIdentity = new QLabel(mMainWidget);
00189   mDictionaryLabel = new QLabel( mMainWidget );
00190   mLblFcc = new QLabel(mMainWidget);
00191   mLblTransport = new QLabel(mMainWidget);
00192   mLblFrom = new QLabel(mMainWidget);
00193   mLblReplyTo = new QLabel(mMainWidget);
00194   mLblTo = new QLabel(mMainWidget);
00195   mLblCc = new QLabel(mMainWidget);
00196   mLblBcc = new QLabel(mMainWidget);
00197   mLblSubject = new QLabel(mMainWidget);
00198   QString sticky = i18n("Sticky");
00199   mBtnIdentity = new QCheckBox(sticky,mMainWidget);
00200   mBtnFcc = new QCheckBox(sticky,mMainWidget);
00201   mBtnTransport = new QCheckBox(sticky,mMainWidget);
00202   mBtnTo = new QPushButton("...",mMainWidget);
00203   mBtnCc = new QPushButton("...",mMainWidget);
00204   mBtnBcc = new QPushButton("...",mMainWidget);
00205   //mBtnFrom = new QPushButton("...",mMainWidget);
00206   mBtnReplyTo = new QPushButton("...",mMainWidget);
00207 
00208   //setWFlags( WType_TopLevel | WStyle_Dialog );
00209   mDone = false;
00210   mGrid = 0;
00211   mAtmListView = 0;
00212   mAtmList.setAutoDelete(TRUE);
00213   mAtmTempList.setAutoDelete(TRUE);
00214   mAtmModified = FALSE;
00215   mAutoDeleteMsg = FALSE;
00216   mFolder = 0;
00217   mAutoCharset = TRUE;
00218   mFixedFontAction = 0;
00219   mEditor = new KMEdit( mMainWidget, this, mDictionaryCombo->spellConfig() );
00220   mEditor->setTextFormat(Qt::PlainText);
00221   mEditor->setAcceptDrops( true );
00222 
00223   QString tip = i18n("Select email address(es)");
00224   QToolTip::add( mBtnTo, tip );
00225   QToolTip::add( mBtnCc, tip );
00226   QToolTip::add( mBtnReplyTo, tip );
00227 
00228   QWhatsThis::add( mBtnIdentity, i18n("Remember this identity, so that it "
00229     "will be used in future composer windows as well."));
00230   QWhatsThis::add( mBtnFcc, i18n("Remember this folder for sent items, so "
00231     "that it will be used in future composer windows as well."));
00232   QWhatsThis::add( mBtnTransport, i18n("Remember this mail transport, so "
00233     "that it will be used in future composer windows as well."));
00234 
00235   mSpellCheckInProgress=FALSE;
00236 
00237   setCaption( i18n("Composer") );
00238   setMinimumSize(200,200);
00239 
00240   mBtnIdentity->setFocusPolicy(QWidget::NoFocus);
00241   mBtnFcc->setFocusPolicy(QWidget::NoFocus);
00242   mBtnTransport->setFocusPolicy(QWidget::NoFocus);
00243   mBtnTo->setFocusPolicy(QWidget::NoFocus);
00244   mBtnCc->setFocusPolicy(QWidget::NoFocus);
00245   mBtnBcc->setFocusPolicy(QWidget::NoFocus);
00246   //mBtnFrom->setFocusPolicy(QWidget::NoFocus);
00247   mBtnReplyTo->setFocusPolicy(QWidget::NoFocus);
00248 
00249   mAtmListView = new AttachmentListView( this, mMainWidget,
00250                                          "attachment list view" );
00251   mAtmListView->setSelectionMode( QListView::Extended );
00252   mAtmListView->setFocusPolicy( QWidget::NoFocus );
00253   mAtmListView->addColumn( i18n("Name"), 200 );
00254   mAtmListView->addColumn( i18n("Size"), 80 );
00255   mAtmListView->addColumn( i18n("Encoding"), 120 );
00256   int atmColType = mAtmListView->addColumn( i18n("Type"), 120 );
00257   // Stretch "Type".
00258   mAtmListView->header()->setStretchEnabled( true, atmColType );
00259   mAtmEncryptColWidth = 80;
00260   mAtmSignColWidth = 80;
00261   mAtmColEncrypt = mAtmListView->addColumn( i18n("Encrypt"),
00262                                             mAtmEncryptColWidth );
00263   mAtmColSign    = mAtmListView->addColumn( i18n("Sign"),
00264                                             mAtmSignColWidth );
00265   mAtmListView->setColumnWidth( mAtmColEncrypt, 0 );
00266   mAtmListView->setColumnWidth( mAtmColSign,    0 );
00267   mAtmListView->setAllColumnsShowFocus( true );
00268 
00269   connect( mAtmListView,
00270            SIGNAL( doubleClicked( QListViewItem* ) ),
00271            SLOT( slotAttachOpen() ) );
00272   connect( mAtmListView,
00273            SIGNAL( rightButtonPressed( QListViewItem*, const QPoint&, int ) ),
00274            SLOT( slotAttachPopupMenu( QListViewItem*, const QPoint&, int ) ) );
00275   connect( mAtmListView,
00276            SIGNAL( selectionChanged() ),
00277            SLOT( slotUpdateAttachActions() ) );
00278   mAttachMenu = 0;
00279 
00280   readConfig();
00281   setupStatusBar();
00282   setupEditor();
00283   if( !aMsg || aMsg->headerField("X-KMail-CryptoFormat").isEmpty() )
00284   setupActions();
00285   else
00286     setupActions( aMsg->headerField("X-KMail-CryptoFormat").stripWhiteSpace().toInt() );
00287 
00288   applyMainWindowSettings(KMKernel::config(), "Composer");
00289 
00290   connect( mEdtSubject, SIGNAL( subjectTextSpellChecked() ),
00291            SLOT( slotSubjectTextSpellChecked() ) );
00292   connect(mEdtSubject,SIGNAL(textChanged(const QString&)),
00293           SLOT(slotUpdWinTitle(const QString&)));
00294   connect(mBtnTo,SIGNAL(clicked()),SLOT(slotAddrBookTo()));
00295   connect(mBtnCc,SIGNAL(clicked()),SLOT(slotAddrBookTo()));
00296   connect(mBtnBcc,SIGNAL(clicked()),SLOT(slotAddrBookTo()));
00297   connect(mBtnReplyTo,SIGNAL(clicked()),SLOT(slotAddrBookReplyTo()));
00298   //connect(mBtnFrom,SIGNAL(clicked()),SLOT(slotAddrBookFrom()));
00299   connect(mIdentity,SIGNAL(identityChanged(uint)),
00300           SLOT(slotIdentityChanged(uint)));
00301   connect( kmkernel->identityManager(), SIGNAL(changed(uint)),
00302           SLOT(slotIdentityChanged(uint)));
00303 
00304   connect(mEdtTo,SIGNAL(completionModeChanged(KGlobalSettings::Completion)),
00305           SLOT(slotCompletionModeChanged(KGlobalSettings::Completion)));
00306   connect(mEdtCc,SIGNAL(completionModeChanged(KGlobalSettings::Completion)),
00307           SLOT(slotCompletionModeChanged(KGlobalSettings::Completion)));
00308   connect(mEdtBcc,SIGNAL(completionModeChanged(KGlobalSettings::Completion)),
00309           SLOT(slotCompletionModeChanged(KGlobalSettings::Completion)));
00310   connect(mEdtReplyTo,SIGNAL(completionModeChanged(KGlobalSettings::Completion)),
00311           SLOT(slotCompletionModeChanged(KGlobalSettings::Completion)));
00312   connect(mEdtFrom,SIGNAL(completionModeChanged(KGlobalSettings::Completion)),
00313           SLOT(slotCompletionModeChanged(KGlobalSettings::Completion)));
00314         connect(kmkernel->folderMgr(),SIGNAL(folderRemoved(KMFolder*)),
00315                                         SLOT(slotFolderRemoved(KMFolder*)));
00316         connect(kmkernel->imapFolderMgr(),SIGNAL(folderRemoved(KMFolder*)),
00317                                         SLOT(slotFolderRemoved(KMFolder*)));
00318         connect(kmkernel->dimapFolderMgr(),SIGNAL(folderRemoved(KMFolder*)),
00319                                         SLOT(slotFolderRemoved(KMFolder*)));
00320   connect( kmkernel, SIGNAL( configChanged() ),
00321            this, SLOT( slotConfigChanged() ) );
00322 
00323   connect (mEditor, SIGNAL (spellcheck_done(int)),
00324     this, SLOT (slotSpellcheckDone (int)));
00325 
00326   mMainWidget->resize(480,510);
00327   setCentralWidget(mMainWidget);
00328   rethinkFields();
00329 
00330   if (mUseExtEditor) {
00331     mEditor->setUseExternalEditor(true);
00332     mEditor->setExternalEditorPath(mExtEditor);
00333   }
00334 
00335   initAutoSave();
00336 
00337   slotUpdateSignatureActions();
00338 
00339   mMsg = 0;
00340   if (aMsg)
00341     setMsg(aMsg);
00342 
00343   mEdtTo->setFocus();
00344 
00345   mDone = true;
00346 }
00347 
00348 //-----------------------------------------------------------------------------
00349 KMComposeWin::~KMComposeWin()
00350 {
00351   writeConfig();
00352   if (mFolder && mMsg)
00353   {
00354     mAutoDeleteMsg = FALSE;
00355     mFolder->addMsg(mMsg);
00356     // Ensure that the message is correctly and fully parsed
00357     mFolder->unGetMsg( mFolder->count() - 1 );
00358   }
00359   if (mAutoDeleteMsg) {
00360     delete mMsg;
00361     mMsg = 0;
00362   }
00363   QMap<KIO::Job*, atmLoadData>::Iterator it = mMapAtmLoadData.begin();
00364   while ( it != mMapAtmLoadData.end() )
00365   {
00366     KIO::Job *job = it.key();
00367     mMapAtmLoadData.remove( it );
00368     job->kill();
00369     it = mMapAtmLoadData.begin();
00370   }
00371   deleteAll( mComposedMessages );
00372 }
00373 
00374 void KMComposeWin::setAutoDeleteWindow( bool f )
00375 {
00376   if ( f )
00377     setWFlags( getWFlags() | WDestructiveClose );
00378   else
00379     setWFlags( getWFlags() & ~WDestructiveClose );
00380 }
00381 
00382 //-----------------------------------------------------------------------------
00383 void KMComposeWin::send(int how)
00384 {
00385   switch (how) {
00386     case 1:
00387       slotSendNow();
00388       break;
00389     default:
00390     case 0:
00391       // TODO: find out, what the default send method is and send it this way
00392     case 2:
00393       slotSendLater();
00394       break;
00395   }
00396 }
00397 
00398 //-----------------------------------------------------------------------------
00399 void KMComposeWin::addAttachment(KURL url,QString /*comment*/)
00400 {
00401   addAttach(url);
00402 }
00403 
00404 //-----------------------------------------------------------------------------
00405 void KMComposeWin::addAttachment(const QString &name,
00406                                  const QCString &/*cte*/,
00407                                  const QByteArray &data,
00408                                  const QCString &type,
00409                                  const QCString &subType,
00410                                  const QCString &paramAttr,
00411                                  const QString &paramValue,
00412                                  const QCString &contDisp)
00413 {
00414   if (!data.isEmpty()) {
00415     KMMessagePart *msgPart = new KMMessagePart;
00416     msgPart->setName(name);
00417     if ( type == "message" && subType == "rfc822" ) {
00418       msgPart->setMessageBody( data );
00419     } else {
00420       QValueList<int> dummy;
00421       msgPart->setBodyAndGuessCte(data, dummy,
00422               kmkernel->msgSender()->sendQuotedPrintable());
00423     }
00424     msgPart->setTypeStr(type);
00425     msgPart->setSubtypeStr(subType);
00426     msgPart->setParameter(paramAttr,paramValue);
00427     msgPart->setContentDisposition(contDisp);
00428     addAttach(msgPart);
00429   }
00430 }
00431 
00432 //-----------------------------------------------------------------------------
00433 void KMComposeWin::setBody(QString body)
00434 {
00435   mEditor->setText(body);
00436 }
00437 
00438 //-----------------------------------------------------------------------------
00439 bool KMComposeWin::event(QEvent *e)
00440 {
00441   if (e->type() == QEvent::ApplicationPaletteChange)
00442   {
00443      readColorConfig();
00444   }
00445   return KMail::SecondaryWindow::event(e);
00446 }
00447 
00448 
00449 //-----------------------------------------------------------------------------
00450 void KMComposeWin::readColorConfig(void)
00451 {
00452   KConfig *config = KMKernel::config();
00453   KConfigGroupSaver saver(config, "Reader");
00454   QColor c1=QColor(kapp->palette().active().text());
00455   QColor c4=QColor(kapp->palette().active().base());
00456 
00457   if (!config->readBoolEntry("defaultColors",TRUE)) {
00458     mForeColor = config->readColorEntry("ForegroundColor",&c1);
00459     mBackColor = config->readColorEntry("BackgroundColor",&c4);
00460   }
00461   else {
00462     mForeColor = c1;
00463     mBackColor = c4;
00464   }
00465 
00466   // Color setup
00467   mPalette = kapp->palette();
00468   QColorGroup cgrp  = mPalette.active();
00469   cgrp.setColor( QColorGroup::Base, mBackColor);
00470   cgrp.setColor( QColorGroup::Text, mForeColor);
00471   mPalette.setActive(cgrp);
00472   mPalette.setInactive(cgrp);
00473 
00474   mEdtTo->setPalette(mPalette);
00475   mEdtFrom->setPalette(mPalette);
00476   mEdtCc->setPalette(mPalette);
00477   mEdtSubject->setPalette(mPalette);
00478   mEdtReplyTo->setPalette(mPalette);
00479   mEdtBcc->setPalette(mPalette);
00480   mTransport->setPalette(mPalette);
00481   mEditor->setPalette(mPalette);
00482   mFcc->setPalette(mPalette);
00483 }
00484 
00485 //-----------------------------------------------------------------------------
00486 void KMComposeWin::readConfig(void)
00487 {
00488   KConfig *config = KMKernel::config();
00489   QCString str;
00490   //  int w, h,
00491   int maxTransportItems;
00492 
00493   KConfigGroupSaver saver(config, "Composer");
00494 
00495   mDefCharset = KMMessage::defaultCharset();
00496   mForceReplyCharset = config->readBoolEntry("force-reply-charset", false );
00497   mAutoSign = config->readEntry("signature","auto") == "auto";
00498   mShowHeaders = config->readNumEntry("headers", HDR_STANDARD);
00499   mWordWrap = config->readBoolEntry("word-wrap", true);
00500   mUseFixedFont = config->readBoolEntry("use-fixed-font", false);
00501   mLineBreak = config->readNumEntry("break-at", 78);
00502   mBtnIdentity->setChecked(config->readBoolEntry("sticky-identity", false));
00503   if (mBtnIdentity->isChecked())
00504     mId = config->readUnsignedNumEntry("previous-identity", mId );
00505   mBtnFcc->setChecked(config->readBoolEntry("sticky-fcc", false));
00506   QString previousFcc = kmkernel->sentFolder()->idString();
00507   if (mBtnFcc->isChecked())
00508     previousFcc = config->readEntry("previous-fcc", previousFcc );
00509   mBtnTransport->setChecked(config->readBoolEntry("sticky-transport", false));
00510   mTransportHistory = config->readListEntry("transport-history");
00511   QString currentTransport = config->readEntry("current-transport");
00512   maxTransportItems = config->readNumEntry("max-transport-items",10);
00513 
00514   if ((mLineBreak == 0) || (mLineBreak > 78))
00515     mLineBreak = 78;
00516   if (mLineBreak < 30)
00517     mLineBreak = 30;
00518   mOutlookCompatible = config->readBoolEntry( "outlook-compatible-attachments", false );
00519   mAutoPgpSign = config->readBoolEntry("pgp-auto-sign", false);
00520   mAutoPgpEncrypt = config->readBoolEntry("pgp-auto-encrypt", false);
00521   mNeverEncryptWhenSavingInDrafts = config->readBoolEntry("never-encrypt-drafts", true);
00522   mConfirmSend = config->readBoolEntry("confirm-before-send", false);
00523   mAutoRequestMDN = config->readBoolEntry("request-mdn", false);
00524 #ifdef KLEO_CHIASMUS
00525   mChiasmusKey = config->readEntry( "chiasmus-key" );
00526   mChiasmusOptions = config->readEntry( "chiasmus-options" );
00527 #endif
00528 
00529   int mode = config->readNumEntry("Completion Mode",
00530                                   KGlobalSettings::completionMode() );
00531   mEdtFrom->setCompletionMode( (KGlobalSettings::Completion) mode );
00532   mEdtReplyTo->setCompletionMode( (KGlobalSettings::Completion) mode );
00533   mEdtTo->setCompletionMode( (KGlobalSettings::Completion) mode );
00534   mEdtCc->setCompletionMode( (KGlobalSettings::Completion) mode );
00535   mEdtBcc->setCompletionMode( (KGlobalSettings::Completion) mode );
00536 
00537   readColorConfig();
00538 
00539   { // area for config group "General"
00540     KConfigGroupSaver saver(config, "General");
00541     mExtEditor = config->readPathEntry("external-editor", DEFAULT_EDITOR_STR);
00542     mUseExtEditor = config->readBoolEntry("use-external-editor", FALSE);
00543 
00544     int headerCount = config->readNumEntry("mime-header-count", 0);
00545     mCustHeaders.clear();
00546     mCustHeaders.setAutoDelete(true);
00547     for (int i = 0; i < headerCount; i++) {
00548       QString thisGroup;
00549       _StringPair *thisItem = new _StringPair;
00550       thisGroup.sprintf("Mime #%d", i);
00551       KConfigGroupSaver saver(config, thisGroup);
00552       thisItem->name = config->readEntry("name");
00553       if ((thisItem->name).length() > 0) {
00554         thisItem->value = config->readEntry("value");
00555         mCustHeaders.append(thisItem);
00556       } else {
00557         delete thisItem;
00558         thisItem = 0;
00559       }
00560     }
00561   }
00562 
00563   { // area fo config group "Fonts"
00564     KConfigGroupSaver saver(config, "Fonts");
00565     mBodyFont = KGlobalSettings::generalFont();
00566     mFixedFont = KGlobalSettings::fixedFont();
00567     if (!config->readBoolEntry("defaultFonts",TRUE)) {
00568       mBodyFont = config->readFontEntry("composer-font", &mBodyFont);
00569       mFixedFont = config->readFontEntry("fixed-font", &mFixedFont);
00570     }
00571     slotUpdateFont();
00572     mEdtFrom->setFont(mBodyFont);
00573     mEdtReplyTo->setFont(mBodyFont);
00574     mEdtTo->setFont(mBodyFont);
00575     mEdtCc->setFont(mBodyFont);
00576     mEdtBcc->setFont(mBodyFont);
00577     mEdtSubject->setFont(mBodyFont);
00578   }
00579 
00580   { // area fo config group "Fonts"
00581     KConfigGroupSaver saver(config, "Geometry");
00582     QSize defaultSize(480,510);
00583     QSize siz = config->readSizeEntry("composer", &defaultSize);
00584     if (siz.width() < 200) siz.setWidth(200);
00585     if (siz.height() < 200) siz.setHeight(200);
00586     resize(siz);
00587   }
00588 
00589   mIdentity->setCurrentIdentity( mId );
00590 
00591   kdDebug(5006) << "KMComposeWin::readConfig. " << mIdentity->currentIdentityName() << endl;
00592   const KPIM::Identity & ident =
00593     kmkernel->identityManager()->identityForUoid( mIdentity->currentIdentity() );
00594 
00595   mDictionaryCombo->setCurrentByDictionary( ident.dictionary() );
00596 
00597   mTransport->clear();
00598   mTransport->insertStringList( KMTransportInfo::availableTransports() );
00599   while (mTransportHistory.count() > (uint)maxTransportItems)
00600     mTransportHistory.remove( mTransportHistory.last() );
00601   mTransport->insertStringList( mTransportHistory );
00602   if (mBtnTransport->isChecked() && !currentTransport.isEmpty())
00603   {
00604     for (int i = 0; i < mTransport->count(); i++)
00605       if (mTransport->text(i) == currentTransport)
00606         mTransport->setCurrentItem(i);
00607     mTransport->setEditText( currentTransport );
00608   }
00609 
00610   if ( !mBtnFcc->isChecked() )
00611   {
00612       kdDebug(5006) << "KMComposeWin::readConfig: identity.fcc()='"
00613                     << ident.fcc() << "'" << endl;
00614       if ( ident.fcc().isEmpty() )
00615         previousFcc = kmkernel->sentFolder()->idString();
00616       else
00617         previousFcc = ident.fcc();
00618       kdDebug(5006) << "KMComposeWin::readConfig: previousFcc="
00619                 << previousFcc <<  endl;
00620   }
00621 
00622   setFcc( previousFcc );
00623 }
00624 
00625 //-----------------------------------------------------------------------------
00626 void KMComposeWin::writeConfig(void)
00627 {
00628   KConfig *config = KMKernel::config();
00629   QString str;
00630 
00631   {
00632     KConfigGroupSaver saver(config, "Composer");
00633     config->writeEntry("signature", mAutoSign?"auto":"manual");
00634     config->writeEntry("headers", mShowHeaders);
00635     config->writeEntry("sticky-transport", mBtnTransport->isChecked());
00636     config->writeEntry("sticky-identity", mBtnIdentity->isChecked());
00637     config->writeEntry("sticky-fcc", mBtnFcc->isChecked());
00638     config->writeEntry("previous-identity", mIdentity->currentIdentity() );
00639     config->writeEntry("current-transport", mTransport->currentText());
00640     config->writeEntry("previous-fcc", mFcc->getFolder()->idString() );
00641     config->writeEntry( "autoSpellChecking",
00642                         mAutoSpellCheckingAction->isChecked() );
00643     mTransportHistory.remove(mTransport->currentText());
00644     if (KMTransportInfo::availableTransports().findIndex(mTransport
00645       ->currentText()) == -1)
00646         mTransportHistory.prepend(mTransport->currentText());
00647     config->writeEntry("transport-history", mTransportHistory );
00648     config->writeEntry("use-fixed-font", mUseFixedFont );
00649 #ifdef KLEO_CHIASMUS
00650     config->writeEntry( "chiasmus-key", mChiasmusKey );
00651     config->writeEntry( "chiasmus-options", mChiasmusOptions );
00652 #endif
00653   }
00654 
00655   {
00656     KConfigGroupSaver saver(config, "Geometry");
00657     config->writeEntry("composer", size());
00658 
00659     saveMainWindowSettings(config, "Composer");
00660     config->sync();
00661   }
00662 }
00663 
00664 
00665 //-----------------------------------------------------------------------------
00666 void KMComposeWin::autoSaveMessage()
00667 {
00668   kdDebug(5006) << k_funcinfo << endl;
00669   if ( !mMsg || mComposer || mAutoSaveFilename.isEmpty() )
00670     return;
00671   kdDebug(5006) << k_funcinfo << "autosaving message" << endl;
00672 
00673   if ( mAutoSaveTimer )
00674     mAutoSaveTimer->stop();
00675   connect( this, SIGNAL( applyChangesDone( bool ) ),
00676            this, SLOT( slotContinueAutoSave( bool ) ) );
00677   // This method is called when KMail crashed, so don't try signing/encryption
00678   // and don't disable controls because it is also called from a timer and
00679   // then the disabling is distracting.
00680   applyChanges( true, true );
00681 
00682   // Don't continue before the applyChanges is done!
00683   qApp->enter_loop();
00684 
00685   // Ok, it's done now - continue dead letter saving
00686   if ( mComposedMessages.isEmpty() ) {
00687     kdDebug(5006) << "Composing the message failed." << endl;
00688     return;
00689   }
00690   KMMessage *msg = mComposedMessages.first();
00691   if ( !msg ) // a bit of extra defensiveness
00692     return;
00693 
00694   kdDebug(5006) << k_funcinfo << "opening autoSaveFile " << mAutoSaveFilename
00695                 << endl;
00696   const QString filename =
00697     KMKernel::localDataPath() + "autosave/cur/" + mAutoSaveFilename;
00698   KSaveFile autoSaveFile( filename, 0600 );
00699   int status = autoSaveFile.status();
00700   kdDebug(5006) << k_funcinfo << "autoSaveFile.status() = " << status << endl;
00701   if ( status == 0 ) { // no error
00702     kdDebug(5006) << "autosaving message in " << filename << endl;
00703     int fd = autoSaveFile.handle();
00704     QCString msgStr = msg->asString();
00705     if ( ::write( fd, msgStr, msgStr.length() ) == -1 )
00706       status = errno;
00707   }
00708   if ( status == 0 ) {
00709     kdDebug(5006) << k_funcinfo << "closing autoSaveFile" << endl;
00710     autoSaveFile.close();
00711     mLastAutoSaveErrno = 0;
00712   }
00713   else {
00714     kdDebug(5006) << k_funcinfo << "autosaving failed" << endl;
00715     autoSaveFile.abort();
00716     if ( status != mLastAutoSaveErrno ) {
00717       // don't show the same error message twice
00718       KMessageBox::queuedMessageBox( 0, KMessageBox::Sorry,
00719                                      i18n("Autosaving the message as %1 "
00720                                           "failed.\n"
00721                                           "Reason: %2" )
00722                                      .arg( filename, strerror( status ) ),
00723                                      i18n("Autosaving Failed") );
00724       mLastAutoSaveErrno = status;
00725     }
00726   }
00727 
00728   if ( autoSaveInterval() > 0 )
00729     mAutoSaveTimer->start( autoSaveInterval() );
00730 }
00731 
00732 void KMComposeWin::slotContinueAutoSave( bool )
00733 {
00734   disconnect( this, SIGNAL( applyChangesDone( bool ) ),
00735               this, SLOT( slotContinueAutoSave( bool ) ) );
00736   qApp->exit_loop();
00737 }
00738 
00739 
00740 //-----------------------------------------------------------------------------
00741 void KMComposeWin::slotView(void)
00742 {
00743   if (!mDone)
00744     return; // otherwise called from rethinkFields during the construction
00745             // which is not the intended behavior
00746   int id;
00747 
00748   //This sucks awfully, but no, I cannot get an activated(int id) from
00749   // actionContainer()
00750   if (!sender()->isA("KToggleAction"))
00751     return;
00752   KToggleAction *act = (KToggleAction *) sender();
00753 
00754   if (act == mAllFieldsAction)
00755     id = 0;
00756   else if (act == mIdentityAction)
00757     id = HDR_IDENTITY;
00758   else if (act == mTransportAction)
00759     id = HDR_TRANSPORT;
00760   else if (act == mFromAction)
00761     id = HDR_FROM;
00762   else if (act == mReplyToAction)
00763     id = HDR_REPLY_TO;
00764   else if (act == mToAction)
00765     id = HDR_TO;
00766   else if (act == mCcAction)
00767     id = HDR_CC;
00768   else  if (act == mBccAction)
00769     id = HDR_BCC;
00770   else if (act == mSubjectAction)
00771     id = HDR_SUBJECT;
00772   else if (act == mFccAction)
00773     id = HDR_FCC;
00774   else if ( act == mDictionaryAction )
00775     id = HDR_DICTIONARY;
00776   else
00777    {
00778      id = 0;
00779      kdDebug(5006) << "Something is wrong (Oh, yeah?)" << endl;
00780      return;
00781    }
00782 
00783   // sanders There's a bug here this logic doesn't work if no
00784   // fields are shown and then show all fields is selected.
00785   // Instead of all fields being shown none are.
00786   if (!act->isChecked())
00787   {
00788     // hide header
00789     if (id > 0) mShowHeaders = mShowHeaders & ~id;
00790     else mShowHeaders = abs(mShowHeaders);
00791   }
00792   else
00793   {
00794     // show header
00795     if (id > 0) mShowHeaders |= id;
00796     else mShowHeaders = -abs(mShowHeaders);
00797   }
00798   rethinkFields(true);
00799 
00800 }
00801 
00802 void KMComposeWin::rethinkFields(bool fromSlot)
00803 {
00804   //This sucks even more but again no ids. sorry (sven)
00805   int mask, row, numRows;
00806   long showHeaders;
00807 
00808   if (mShowHeaders < 0)
00809     showHeaders = HDR_ALL;
00810   else
00811     showHeaders = mShowHeaders;
00812 
00813   for (mask=1,mNumHeaders=0; mask<=showHeaders; mask<<=1)
00814     if ((showHeaders&mask) != 0) mNumHeaders++;
00815 
00816   numRows = mNumHeaders + 2;
00817 
00818   delete mGrid;
00819   mGrid = new QGridLayout(mMainWidget, numRows, 3, 4, 4);
00820   mGrid->setColStretch(0, 1);
00821   mGrid->setColStretch(1, 100);
00822   mGrid->setColStretch(2, 1);
00823   mGrid->setRowStretch(mNumHeaders, 100);
00824 
00825   mEdtList.clear();
00826   row = 0;
00827   kdDebug(5006) << "KMComposeWin::rethinkFields" << endl;
00828   if (!fromSlot) mAllFieldsAction->setChecked(showHeaders==HDR_ALL);
00829 
00830   if (!fromSlot) mIdentityAction->setChecked(abs(mShowHeaders)&HDR_IDENTITY);
00831   rethinkHeaderLine(showHeaders,HDR_IDENTITY, row, i18n("&Identity:"),
00832                     mLblIdentity, mIdentity, mBtnIdentity);
00833   if (!fromSlot) mDictionaryAction->setChecked(abs(mShowHeaders)&HDR_DICTIONARY);
00834   rethinkHeaderLine(showHeaders,HDR_DICTIONARY, row, i18n("&Dictionary:"),
00835                     mDictionaryLabel, mDictionaryCombo, 0 );
00836   if (!fromSlot) mFccAction->setChecked(abs(mShowHeaders)&HDR_FCC);
00837   rethinkHeaderLine(showHeaders,HDR_FCC, row, i18n("Se&nt-Mail folder:"),
00838                     mLblFcc, mFcc, mBtnFcc);
00839   if (!fromSlot) mTransportAction->setChecked(abs(mShowHeaders)&HDR_TRANSPORT);
00840   rethinkHeaderLine(showHeaders,HDR_TRANSPORT, row, i18n("Mai&l transport:"),
00841                     mLblTransport, mTransport, mBtnTransport);
00842   if (!fromSlot) mFromAction->setChecked(abs(mShowHeaders)&HDR_FROM);
00843   rethinkHeaderLine(showHeaders,HDR_FROM, row, i18n("&From:"),
00844                     mLblFrom, mEdtFrom /*, mBtnFrom */ );
00845   if (!fromSlot) mReplyToAction->setChecked(abs(mShowHeaders)&HDR_REPLY_TO);
00846   rethinkHeaderLine(showHeaders,HDR_REPLY_TO,row,i18n("&Reply to:"),
00847                     mLblReplyTo, mEdtReplyTo, mBtnReplyTo);
00848   if (!fromSlot) mToAction->setChecked(abs(mShowHeaders)&HDR_TO);
00849   rethinkHeaderLine(showHeaders, HDR_TO, row, i18n("To:"),
00850                     mLblTo, mEdtTo, mBtnTo,
00851                     i18n("Primary Recipients"),
00852                     i18n("<qt>The email addresses you put "
00853                          "in this field receive a copy of the email.</qt>"));
00854   if (!fromSlot) mCcAction->setChecked(abs(mShowHeaders)&HDR_CC);
00855   rethinkHeaderLine(showHeaders, HDR_CC, row, i18n("&Copy to (CC):"),
00856                     mLblCc, mEdtCc, mBtnCc,
00857                     i18n("Additional Recipients"),
00858                     i18n("<qt>The email addresses you put "
00859                          "in this field receive a copy of the email. "
00860                          "Technically it is the same thing as putting all the "
00861                          "addresses in the <b>To:</b> field but differs in "
00862                          "that it usually symbolises the receiver of the "
00863                          "Carbon Copy (CC) is a listener, not the main "
00864                          "recipient.</qt>"));
00865   if (!fromSlot) mBccAction->setChecked(abs(mShowHeaders)&HDR_BCC);
00866   rethinkHeaderLine(showHeaders,HDR_BCC, row, i18n("&Blind copy to (BCC):"),
00867                     mLblBcc, mEdtBcc, mBtnBcc,
00868                     i18n("Hidden Recipients"),
00869                     i18n("<qt>Essentially the same thing "
00870                          "as the <b>Copy To:</b> field but differs in that "
00871                          "all other recipients do not see who receives a "
00872                          "blind copy.</qt>"));
00873   if (!fromSlot) mSubjectAction->setChecked(abs(mShowHeaders)&HDR_SUBJECT);
00874   rethinkHeaderLine(showHeaders,HDR_SUBJECT, row, i18n("S&ubject:"),
00875                     mLblSubject, mEdtSubject);
00876   assert(row<=mNumHeaders);
00877 
00878   mGrid->addMultiCellWidget(mEditor, row, mNumHeaders, 0, 2);
00879   mGrid->addMultiCellWidget(mAtmListView, mNumHeaders+1, mNumHeaders+1, 0, 2);
00880 
00881   if( !mAtmList.isEmpty() )
00882     mAtmListView->show();
00883   else
00884     mAtmListView->hide();
00885   resize(this->size());
00886   repaint();
00887 
00888   mGrid->activate();
00889 
00890   slotUpdateAttachActions();
00891   mIdentityAction->setEnabled(!mAllFieldsAction->isChecked());
00892   mDictionaryAction->setEnabled( !mAllFieldsAction->isChecked() );
00893   mTransportAction->setEnabled(!mAllFieldsAction->isChecked());
00894   mFromAction->setEnabled(!mAllFieldsAction->isChecked());
00895   mReplyToAction->setEnabled(!mAllFieldsAction->isChecked());
00896   mToAction->setEnabled(!mAllFieldsAction->isChecked());
00897   mCcAction->setEnabled(!mAllFieldsAction->isChecked());
00898   mBccAction->setEnabled(!mAllFieldsAction->isChecked());
00899   mFccAction->setEnabled(!mAllFieldsAction->isChecked());
00900   mSubjectAction->setEnabled(!mAllFieldsAction->isChecked());
00901 }
00902 
00903 
00904 //-----------------------------------------------------------------------------
00905 void KMComposeWin::rethinkHeaderLine(int aValue, int aMask, int& aRow,
00906                                      const QString &aLabelStr, QLabel* aLbl,
00907                                      QLineEdit* aEdt, QPushButton* aBtn,
00908                                      const QString &toolTip, const QString &whatsThis )
00909 {
00910   if (aValue & aMask)
00911   {
00912     aLbl->setText(aLabelStr);
00913     if ( !toolTip.isEmpty() )
00914       QToolTip::add( aLbl, toolTip );
00915     if ( !whatsThis.isEmpty() )
00916       QWhatsThis::add( aLbl, whatsThis );
00917     aLbl->adjustSize();
00918     aLbl->resize((int)aLbl->sizeHint().width(),aLbl->sizeHint().height() + 6);
00919     aLbl->setMinimumSize(aLbl->size());
00920     aLbl->show();
00921     aLbl->setBuddy(aEdt);
00922     mGrid->addWidget(aLbl, aRow, 0);
00923 
00924     aEdt->setBackgroundColor( mBackColor );
00925     aEdt->show();
00926     aEdt->setMinimumSize(100, aLbl->height()+2);
00927     mEdtList.append(aEdt);
00928 
00929     mGrid->addWidget(aEdt, aRow, 1);
00930     if (aBtn)
00931     {
00932       mGrid->addWidget(aBtn, aRow, 2);
00933       aBtn->setFixedSize(aBtn->sizeHint().width(), aLbl->height());
00934       aBtn->show();
00935     }
00936     aRow++;
00937   }
00938   else
00939   {
00940     aLbl->hide();
00941     aEdt->hide();
00942     if (aBtn) aBtn->hide();
00943   }
00944 }
00945 
00946 //-----------------------------------------------------------------------------
00947 void KMComposeWin::rethinkHeaderLine(int aValue, int aMask, int& aRow,
00948                                      const QString &aLabelStr, QLabel* aLbl,
00949                                      QComboBox* aCbx, QCheckBox* aChk)
00950 {
00951   if (aValue & aMask)
00952   {
00953     aLbl->setText(aLabelStr);
00954     aLbl->adjustSize();
00955     aLbl->resize((int)aLbl->sizeHint().width(),aLbl->sizeHint().height() + 6);
00956     aLbl->setMinimumSize(aLbl->size());
00957     aLbl->show();
00958     aLbl->setBuddy(aCbx);
00959     mGrid->addWidget(aLbl, aRow, 0);
00960 
00961     //    aCbx->setBackgroundColor( mBackColor );
00962     aCbx->show();
00963     aCbx->setMinimumSize(100, aLbl->height()+2);
00964 
00965     mGrid->addWidget(aCbx, aRow, 1);
00966     if ( aChk ) {
00967       mGrid->addWidget(aChk, aRow, 2);
00968       aChk->setFixedSize(aChk->sizeHint().width(), aLbl->height());
00969       aChk->show();
00970     }
00971     aRow++;
00972   }
00973   else
00974   {
00975     aLbl->hide();
00976     aCbx->hide();
00977     if ( aChk )
00978       aChk->hide();
00979   }
00980 }
00981 
00982 namespace {
00983 
00984 #if KDE_IS_VERSION( 3, 3, 0 )
00985   // KDELIBS 3.2 misses setCheckedState(), so we provide our own
00986   typedef KToggleAction K33ToggleAction;
00987 #else
00988   class K33ToggleAction : public KToggleAction {
00989     KGuiItem * m_checkedGuiItem;
00990   public:
00991     K33ToggleAction( const QString & text, const QString & pix, const KShortcut & cut,
00992                      QObject * parent, const char * name=0 )
00993       : KToggleAction( text, pix, cut, parent, name ),
00994         m_checkedGuiItem( 0 )
00995     {
00996 
00997     }
00998     ~K33ToggleAction() {
00999       delete m_checkedGuiItem; m_checkedGuiItem = 0;
01000     }
01001 
01002     // This is inspired from some kdelibs version beyond 3.3..
01003     void updateChecked( int id ) {
01004       if ( !m_checkedGuiItem ) {
01005         KToggleAction::updateChecked( id );
01006         return;
01007       }
01008 
01009       QWidget *w = container( id );
01010 
01011       if ( ::qt_cast<QPopupMenu *>( w ) ) {
01012         QPopupMenu* pm = static_cast<QPopupMenu*>(w);
01013         int itemId_ = itemId( id );
01014         if ( isChecked() ) {
01015           if ( m_checkedGuiItem->hasIcon() )
01016             pm->changeItem( itemId_, m_checkedGuiItem->iconSet( KIcon::Small ), m_checkedGuiItem->text() );
01017           else
01018             pm->changeItem( itemId_, m_checkedGuiItem->text() );
01019           if ( !m_checkedGuiItem->whatsThis().isEmpty() ) // if empty, we keep the initial one
01020             pm->setWhatsThis( itemId_, m_checkedGuiItem->whatsThis() );
01021         } else {
01022           if ( hasIcon() )
01023             pm->changeItem( itemId_, iconSet( KIcon::Small ), text() );
01024           else
01025             pm->changeItem( itemId_, text() );
01026           pm->setWhatsThis( itemId_, whatsThis() );
01027         }
01028         pm->setItemChecked( itemId_, isChecked() );
01029         updateShortcut( pm, itemId_ );
01030       }
01031       else if ( ::qt_cast<QMenuBar *>( w ) ) // not handled in plug...
01032         static_cast<QMenuBar*>(w)->setItemChecked( itemId( id ), isChecked() );
01033       else if ( ::qt_cast<KToolBar *>( w ) ) {
01034         QWidget* r = static_cast<KToolBar*>( w )->getButton( itemId( id ) );
01035         if ( r && ::qt_cast<KToolBarButton *>( r ) ) {
01036           static_cast<KToolBar*>( w )->setButton( itemId( id ), isChecked() );
01037           if ( m_checkedGuiItem->hasIcon() ) {
01038             const QIconSet iconSet = isChecked()
01039               ? m_checkedGuiItem->iconSet( KIcon::Toolbar )
01040               : this->iconSet( KIcon::Toolbar );
01041             static_cast<KToolBar*>( w )->setButtonIconSet( itemId( id ), iconSet );
01042           }
01043         }
01044       }
01045     }
01046 
01047     void setCheckedState( const KGuiItem & item ) {
01048       delete m_checkedGuiItem;
01049       m_checkedGuiItem = new KGuiItem( item );
01050     }
01051 
01052     QString toolTip() const {
01053       if ( m_checkedGuiItem && isChecked() )
01054         return m_checkedGuiItem->toolTip();
01055       else
01056         return KToggleAction::toolTip();
01057     }
01058   };
01059 
01060 #endif
01061 
01062 }
01063 
01064 //-----------------------------------------------------------------------------
01065 void KMComposeWin::setupActions(int aCryptoMessageFormat)
01066 {
01067   if (kmkernel->msgSender()->sendImmediate()) //default == send now?
01068   {
01069     //default = send now, alternative = queue
01070     (void) new KAction (i18n("&Send"), "mail_send", CTRL+Key_Return,
01071                         this, SLOT(slotSendNow()), actionCollection(),
01072                         "send_default");
01073     (void) new KAction (i18n("&Queue"), "queue", 0,
01074                         this, SLOT(slotSendLater()),
01075                         actionCollection(), "send_alternative");
01076   }
01077   else //no, default = send later
01078   {
01079     //default = queue, alternative = send now
01080     (void) new KAction (i18n("&Queue"), "queue",
01081                         CTRL+Key_Return,
01082                         this, SLOT(slotSendLater()), actionCollection(),
01083                         "send_default");
01084     (void) new KAction (i18n("&Send Now"), "mail_send", 0,
01085                         this, SLOT(slotSendNow()),
01086                         actionCollection(), "send_alternative");
01087   }
01088 
01089   (void) new KAction (i18n("Save in &Drafts Folder"), "filesave", 0,
01090                       this, SLOT(slotSaveDraft()),
01091                       actionCollection(), "save_in_drafts");
01092   (void) new KAction (i18n("&Insert File..."), "fileopen", 0,
01093                       this,  SLOT(slotInsertFile()),
01094                       actionCollection(), "insert_file");
01095   (void) new KAction (i18n("&Address Book"), "contents",0,
01096                       this, SLOT(slotAddrBook()),
01097                       actionCollection(), "addressbook");
01098   (void) new KAction (i18n("&New Composer"), "mail_new",
01099                       KStdAccel::shortcut(KStdAccel::New),
01100                       this, SLOT(slotNewComposer()),
01101                       actionCollection(), "new_composer");
01102   (void) new KAction (i18n("New Main &Window"), "window_new", 0,
01103                       this, SLOT(slotNewMailReader()),
01104                       actionCollection(), "open_mailreader");
01105 
01106 
01107   //KStdAction::save(this, SLOT(), actionCollection(), "save_message");
01108   KStdAction::print (this, SLOT(slotPrint()), actionCollection());
01109   KStdAction::close (this, SLOT(slotClose()), actionCollection());
01110 
01111   KStdAction::undo (this, SLOT(slotUndo()), actionCollection());
01112   KStdAction::redo (this, SLOT(slotRedo()), actionCollection());
01113   KStdAction::cut (this, SLOT(slotCut()), actionCollection());
01114   KStdAction::copy (this, SLOT(slotCopy()), actionCollection());
01115   KStdAction::pasteText (this, SLOT(slotPaste()), actionCollection());
01116   KStdAction::selectAll (this, SLOT(slotMarkAll()), actionCollection());
01117 
01118   KStdAction::find (this, SLOT(slotFind()), actionCollection());
01119   KStdAction::findNext(this, SLOT(slotSearchAgain()), actionCollection());
01120 
01121   KStdAction::replace (this, SLOT(slotReplace()), actionCollection());
01122   KStdAction::spelling (this, SLOT(slotSpellcheck()), actionCollection(), "spellcheck");
01123 
01124   (void) new KAction (i18n("Pa&ste as Quotation"),0,this,SLOT( slotPasteAsQuotation()),
01125                       actionCollection(), "paste_quoted");
01126 
01127   (void) new KAction(i18n("Add &Quote Characters"), 0, this,
01128               SLOT(slotAddQuotes()), actionCollection(), "tools_quote");
01129 
01130   (void) new KAction(i18n("Re&move Quote Characters"), 0, this,
01131               SLOT(slotRemoveQuotes()), actionCollection(), "tools_unquote");
01132 
01133 
01134   (void) new KAction (i18n("Cl&ean Spaces"), 0, this, SLOT(slotCleanSpace()),
01135                       actionCollection(), "clean_spaces");
01136 
01137   mFixedFontAction = new KToggleAction( i18n("Use Fi&xed Font"), 0, this,
01138                       SLOT(slotUpdateFont()), actionCollection(), "toggle_fixedfont" );
01139   mFixedFontAction->setChecked(mUseFixedFont);
01140 
01141   //these are checkable!!!
01142   mUrgentAction = new KToggleAction (i18n("&Urgent"), 0,
01143                                     actionCollection(),
01144                                     "urgent");
01145   mRequestMDNAction = new KToggleAction ( i18n("&Request Disposition Notification"), 0,
01146                                          actionCollection(),
01147                                          "options_request_mdn");
01148   mRequestMDNAction->setChecked(mAutoRequestMDN);
01149   //----- Message-Encoding Submenu
01150   mEncodingAction = new KSelectAction( i18n( "Se&t Encoding" ), "charset",
01151                                       0, this, SLOT(slotSetCharset() ),
01152                                       actionCollection(), "charsets" );
01153   mWordWrapAction = new KToggleAction (i18n("&Wordwrap"), 0,
01154                       actionCollection(), "wordwrap");
01155   mWordWrapAction->setChecked(mWordWrap);
01156   connect(mWordWrapAction, SIGNAL(toggled(bool)), SLOT(slotWordWrapToggled(bool)));
01157 
01158   mAutoSpellCheckingAction =
01159     new KToggleAction( i18n( "&Automatic Spellchecking" ), "spellcheck", 0,
01160                        actionCollection(), "options_auto_spellchecking" );
01161   KConfigGroup composerConfig( KMKernel::config(), "Composer" );
01162   const bool spellChecking =
01163     composerConfig.readBoolEntry( "autoSpellChecking", true );
01164   mAutoSpellCheckingAction->setEnabled( !mUseExtEditor );
01165   mAutoSpellCheckingAction->setChecked( !mUseExtEditor && spellChecking );
01166   slotAutoSpellCheckingToggled( !mUseExtEditor && spellChecking );
01167   connect( mAutoSpellCheckingAction, SIGNAL( toggled( bool ) ),
01168            this, SLOT( slotAutoSpellCheckingToggled( bool ) ) );
01169 
01170   QStringList encodings = KMMsgBase::supportedEncodings(TRUE);
01171   encodings.prepend( i18n("Auto-Detect"));
01172   mEncodingAction->setItems( encodings );
01173   mEncodingAction->setCurrentItem( -1 );
01174 
01175   //these are checkable!!!
01176   markupAction = new KToggleAction (i18n("Formatting (HTML)"), 0, this,
01177                                     SLOT(slotToggleMarkup()),
01178                       actionCollection(), "html");
01179   markupAction->setChecked(mUseHTMLEditor);
01180 
01181   mAllFieldsAction = new KToggleAction (i18n("&All Fields"), 0, this,
01182                                        SLOT(slotView()),
01183                                        actionCollection(), "show_all_fields");
01184   mIdentityAction = new KToggleAction (i18n("&Identity"), 0, this,
01185                                       SLOT(slotView()),
01186                                       actionCollection(), "show_identity");
01187   mDictionaryAction = new KToggleAction (i18n("&Dictionary"), 0, this,
01188                                          SLOT(slotView()),
01189                                          actionCollection(), "show_dictionary");
01190   mFccAction = new KToggleAction (i18n("Sent-Mail F&older"), 0, this,
01191                                  SLOT(slotView()),
01192                                  actionCollection(), "show_fcc");
01193   mTransportAction = new KToggleAction (i18n("&Mail Transport"), 0, this,
01194                                       SLOT(slotView()),
01195                                       actionCollection(), "show_transport");
01196   mFromAction = new KToggleAction (i18n("&From"), 0, this,
01197                                   SLOT(slotView()),
01198                                   actionCollection(), "show_from");
01199   mReplyToAction = new KToggleAction (i18n("&Reply To"), 0, this,
01200                                      SLOT(slotView()),
01201                                      actionCollection(), "show_reply_to");
01202   mToAction = new KToggleAction (i18n("&To"), 0, this,
01203                                 SLOT(slotView()),
01204                                 actionCollection(), "show_to");
01205   mCcAction = new KToggleAction (i18n("&CC"), 0, this,
01206                                 SLOT(slotView()),
01207                                 actionCollection(), "show_cc");
01208   mBccAction = new KToggleAction (i18n("&BCC"), 0, this,
01209                                  SLOT(slotView()),
01210                                  actionCollection(), "show_bcc");
01211   mSubjectAction = new KToggleAction (i18n("&Subject"), 0, this,
01212                                      SLOT(slotView()),
01213                                      actionCollection(), "show_subject");
01214   //end of checkable
01215 
01216   mAppendSignatureAction =  new KAction (i18n("Append S&ignature"), 0, this,
01217                       SLOT(slotAppendSignature()),
01218                       actionCollection(), "append_signature");
01219 
01220   mPrependSignatureAction =  new KAction (i18n("Prepend S&ignature"), 0, this,
01221                       SLOT(slotPrependSignature()),
01222                       actionCollection(), "prepend_signature");
01223 
01224   mInsertSignatureAction =  new KAction (i18n("Insert Signature At C&ursor Position"), "edit", 0, this,
01225                       SLOT(slotInsertSignatureAtCursor()),
01226                       actionCollection(), "insert_signature_at_cursor_position");
01227 
01228   mAttachPK  = new KAction (i18n("Attach &Public Key..."), 0, this,
01229                            SLOT(slotInsertPublicKey()),
01230                            actionCollection(), "attach_public_key");
01231   mAttachMPK = new KAction (i18n("Attach &My Public Key"), 0, this,
01232                            SLOT(slotInsertMyPublicKey()),
01233                            actionCollection(), "attach_my_public_key");
01234   (void) new KAction (i18n("&Attach File..."), "attach",
01235                       0, this, SLOT(slotAttachFile()),
01236                       actionCollection(), "attach");
01237   mAttachRemoveAction = new KAction (i18n("&Remove Attachment"), 0, this,
01238                       SLOT(slotAttachRemove()),
01239                       actionCollection(), "remove");
01240   mAttachSaveAction = new KAction (i18n("&Save Attachment As..."), "filesave",0,
01241                       this, SLOT(slotAttachSave()),
01242                       actionCollection(), "attach_save");
01243   mAttachPropertiesAction = new KAction (i18n("Attachment Pr&operties"), 0, this,
01244                       SLOT(slotAttachProperties()),
01245                       actionCollection(), "attach_properties");
01246 
01247   setStandardToolBarMenuEnabled(true);
01248 
01249   KStdAction::keyBindings(this, SLOT(slotEditKeys()), actionCollection());
01250   KStdAction::configureToolbars(this, SLOT(slotEditToolbars()), actionCollection());
01251   KStdAction::preferences(kmkernel, SLOT(slotShowConfigurationDialog()), actionCollection());
01252 
01253   (void) new KAction (i18n("Configure &Spellchecker..."), 0, this, SLOT(slotSpellcheckConfig()),
01254                       actionCollection(), "setup_spellchecker");
01255 
01256   mTextCompletionAction =  new   KSelectAction (i18n("&Text Completion"), 0,
01257                       this, SLOT(slotMenuTextCompletion()),
01258                       actionCollection(), "setup_text_completion");
01259 
01260 
01261   //Adding the Menu Items to the "Text Completion" submenu (Settings)
01262   QStringList menuItems;
01263   for ( int i=0; i < KMGlobalNS::numCompletionModes; ++i )
01264     menuItems <<  i18n( KMGlobalNS::completionModes[i].displayName );
01265 
01266   mTextCompletionAction->setItems( menuItems );
01267 
01268   //Setting the menu to last selected option.
01269   KConfigGroup composer( KMKernel::config(), "Composer" );
01270   const int mode = composer.readNumEntry("Completion Mode", KGlobalSettings::completionMode() );
01271   for ( int i=0; i < KMGlobalNS::numCompletionModes; ++i ) {
01272     if ( KMGlobalNS::completionModes[i].mode == mode )
01273       mTextCompletionAction->setCurrentItem( i );
01274   }
01275 
01276   (void) new KAction (i18n("Configure Completion &Order..."), 0,
01277                       this, SLOT(slotConfigureAddressCompletion()),
01278                       actionCollection(), "setup_completion_order");
01279 
01280   (void) new KAction ( i18n("Configure &Recent Addresses..." ), 0,
01281                        mEdtTo, SLOT( editRecentAddresses() ),
01282                        actionCollection(), "setup_recent_addresses" );
01283 
01284 #ifdef KLEO_CHIASMUS
01285   if ( Kleo::CryptoBackendFactory::instance()->protocol( "Chiasmus" ) ) {
01286     K33ToggleAction * a = new K33ToggleAction( i18n( "Encrypt Message with Chiasmus..." ),
01287                                                "chidecrypted", 0, actionCollection(),
01288                                                "encrypt_message_chiasmus" );
01289     a->setCheckedState( KGuiItem( i18n( "Encrypt Message with Chiasmus..." ), "chiencrypted" ) );
01290     mEncryptChiasmusAction = a;
01291     a = new K33ToggleAction( i18n( "Encrypt Message Body with Chiasmus..." ),
01292                                                "chidecrypted", 0, actionCollection(),
01293                                                "encrypt_message_body_chiasmus" );
01294     a->setCheckedState( KGuiItem( i18n( "Encrypt Message Body with Chiasmus..." ), "chiencrypted" ) );
01295     mEncryptBodyChiasmusAction = a;
01296     mEncryptBodyChiasmusAction->setEnabled( false );
01297 
01298     connect( mEncryptChiasmusAction, SIGNAL(toggled(bool)),
01299              this, SLOT(slotEncryptChiasmusToggled(bool)) );
01300     connect( mEncryptBodyChiasmusAction, SIGNAL(toggled(bool)),
01301              this, SLOT(slotEncryptBodyChiasmusToggled(bool)) );
01302 
01303   } else {
01304     mEncryptChiasmusAction = 0;
01305     mEncryptBodyChiasmusAction = 0;
01306   }
01307 #endif // KLEO_CHIASMUS
01308 
01309   mEncryptAction = new KToggleAction (i18n("&Encrypt Message"),
01310                                      "decrypted", 0,
01311                                      actionCollection(), "encrypt_message");
01312   mSignAction = new KToggleAction (i18n("&Sign Message"),
01313                                   "signature", 0,
01314                                   actionCollection(), "sign_message");
01315   // get PGP user id for the chosen identity
01316   const KPIM::Identity & ident =
01317     kmkernel->identityManager()->identityForUoidOrDefault( mIdentity->currentIdentity() );
01318   // PENDING(marc): check the uses of this member and split it into
01319   // smime/openpgp and or enc/sign, if necessary:
01320   mLastIdentityHasSigningKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty();
01321   mLastIdentityHasEncryptionKey = !ident.pgpEncryptionKey().isEmpty() || !ident.smimeEncryptionKey().isEmpty();
01322 
01323   mLastEncryptActionState = false;
01324   mLastSignActionState = mAutoPgpSign;
01325 
01326   // "Attach public key" is only possible if OpenPGP support is available:
01327   mAttachPK->setEnabled( Kleo::CryptoBackendFactory::instance()->openpgp() );
01328 
01329   // "Attach my public key" is only possible if OpenPGP support is
01330   // available and the user specified his key for the current identity:
01331   mAttachMPK->setEnabled( Kleo::CryptoBackendFactory::instance()->openpgp() &&
01332               !ident.pgpEncryptionKey().isEmpty() );
01333 
01334   if ( !Kleo::CryptoBackendFactory::instance()->openpgp() && !Kleo::CryptoBackendFactory::instance()->smime() ) {
01335     // no crypto whatsoever
01336     mEncryptAction->setEnabled( false );
01337     setEncryption( false );
01338     mSignAction->setEnabled( false );
01339     setSigning( false );
01340   } else {
01341     const bool canOpenPGPSign = Kleo::CryptoBackendFactory::instance()->openpgp()
01342       && !ident.pgpSigningKey().isEmpty();
01343     const bool canSMIMESign = Kleo::CryptoBackendFactory::instance()->smime()
01344       && !ident.smimeSigningKey().isEmpty();
01345 
01346     setEncryption( false );
01347     setSigning( ( canOpenPGPSign || canSMIMESign ) && mAutoPgpSign );
01348   }
01349 
01350   connect(mEncryptAction, SIGNAL(toggled(bool)),
01351                          SLOT(slotEncryptToggled( bool )));
01352   connect(mSignAction,    SIGNAL(toggled(bool)),
01353                          SLOT(slotSignToggled(    bool )));
01354 
01355   QStringList l;
01356   for ( int i = 0 ; i < numCryptoMessageFormats ; ++i )
01357     l.push_back( Kleo::cryptoMessageFormatToLabel( cryptoMessageFormats[i] ) );
01358 
01359   mCryptoModuleAction = new KSelectAction( i18n( "&Cryptographic Message Format" ), 0,
01360                        this, SLOT(slotSelectCryptoModule()),
01361                        actionCollection(), "options_select_crypto" );
01362   mCryptoModuleAction->setItems( l );
01363   mCryptoModuleAction->setCurrentItem(
01364     format2cb(   (0 <= aCryptoMessageFormat)
01365                ? (Kleo::CryptoMessageFormat)aCryptoMessageFormat
01366                : ident.preferredCryptoMessageFormat() ) );
01367 
01368   slotSelectCryptoModule();
01369 
01370   QStringList styleItems;
01371   styleItems << i18n( "Standard" );
01372   styleItems << i18n( "Bulleted List (Disc)" );
01373   styleItems << i18n( "Bulleted List (Circle)" );
01374   styleItems << i18n( "Bulleted List (Square)" );
01375   styleItems << i18n( "Ordered List (Decimal)" );
01376   styleItems << i18n( "Ordered List (Alpha lower)" );
01377   styleItems << i18n( "Ordered List (Alpha upper)" );
01378 
01379   listAction = new KSelectAction( i18n( "Select Style" ), 0, actionCollection(),
01380                                  "text_list" );
01381   listAction->setItems( styleItems );
01382   connect( listAction, SIGNAL( activated( const QString& ) ),
01383            SLOT( slotListAction( const QString& ) ) );
01384   fontAction = new KFontAction( "Select Font", 0, actionCollection(),
01385                                "text_font" );
01386   connect( fontAction, SIGNAL( activated( const QString& ) ),
01387            SLOT( slotFontAction( const QString& ) ) );
01388   fontSizeAction = new KFontSizeAction( "Select Size", 0, actionCollection(),
01389                                        "text_size" );
01390   connect( fontSizeAction, SIGNAL( fontSizeChanged( int ) ),
01391            SLOT( slotSizeAction( int ) ) );
01392 
01393   alignLeftAction = new KToggleAction (i18n("Align Left"), "text_left", 0,
01394                       this, SLOT(slotAlignLeft()), actionCollection(),
01395                       "align_left");
01396   alignLeftAction->setChecked( TRUE );
01397   alignRightAction = new KToggleAction (i18n("Align Right"), "text_right", 0,
01398                       this, SLOT(slotAlignRight()), actionCollection(),
01399                       "align_right");
01400   alignCenterAction = new KToggleAction (i18n("Align Center"), "text_center", 0,
01401                        this, SLOT(slotAlignCenter()), actionCollection(),
01402                        "align_center");
01403   textBoldAction = new KToggleAction (i18n("&Bold"), "text_bold", 0,
01404                                      this, SLOT(slotTextBold()),
01405                                      actionCollection(), "text_bold");
01406   textItalicAction = new KToggleAction (i18n("&Italic"), "text_italic", 0,
01407                                        this, SLOT(slotTextItalic()),
01408                                        actionCollection(), "text_italic");
01409   textUnderAction = new KToggleAction (i18n("&Underline"), "text_under", 0,
01410                                      this, SLOT(slotTextUnder()),
01411                                      actionCollection(), "text_under");
01412   actionFormatReset = new KAction( i18n( "Reset Font Settings" ), "eraser", 0,
01413                                      this, SLOT( slotFormatReset() ),
01414                                      actionCollection(), "format_reset");
01415   actionFormatColor = new KAction( i18n( "Text Color..." ), "colorize", 0,
01416                                      this, SLOT( slotTextColor() ),
01417                                      actionCollection(), "format_color");
01418 
01419 
01420   createGUI("kmcomposerui.rc");
01421   // In Kontact, this entry would read "Configure Kontact", but bring
01422   // up KMail's config dialog. That's sensible, though, so fix the label.
01423   KAction* configureAction = actionCollection()->action("options_configure" );
01424   if ( configureAction )
01425     configureAction->setText( i18n("Configure KMail" ) );
01426 }
01427 
01428 //-----------------------------------------------------------------------------
01429 void KMComposeWin::setupStatusBar(void)
01430 {
01431   statusBar()->insertItem("", 0, 1);
01432   statusBar()->setItemAlignment(0, AlignLeft | AlignVCenter);
01433 
01434   statusBar()->insertItem(i18n(" Column: %1 ").arg("     "),2,0,true);
01435   statusBar()->insertItem(i18n(" Line: %1 ").arg("     "),1,0,true);
01436 }
01437 
01438 
01439 //-----------------------------------------------------------------------------
01440 void KMComposeWin::updateCursorPosition()
01441 {
01442   int col,line;
01443   QString temp;
01444   line = mEditor->currentLine();
01445   col = mEditor->currentColumn();
01446   temp = i18n(" Line: %1 ").arg(line+1);
01447   statusBar()->changeItem(temp,1);
01448   temp = i18n(" Column: %1 ").arg(col+1);
01449   statusBar()->changeItem(temp,2);
01450 }
01451 
01452 
01453 //-----------------------------------------------------------------------------
01454 void KMComposeWin::setupEditor(void)
01455 {
01456   //QPopupMenu* menu;
01457   mEditor->setModified(FALSE);
01458   QFontMetrics fm(mBodyFont);
01459   mEditor->setTabStopWidth(fm.width(QChar(' ')) * 8);
01460   //mEditor->setFocusPolicy(QWidget::ClickFocus);
01461 
01462   if (mWordWrap)
01463   {
01464     mEditor->setWordWrap( QMultiLineEdit::FixedColumnWidth );
01465     mEditor->setWrapColumnOrWidth(mLineBreak);
01466   }
01467   else
01468   {
01469     mEditor->setWordWrap( QMultiLineEdit::NoWrap );
01470   }
01471 
01472   // Font setup
01473   slotUpdateFont();
01474 
01475   /* installRBPopup() is broken in kdelibs, we should wait for
01476           the new klibtextedit (dnaber, 2002-01-01)
01477   menu = new QPopupMenu(this);
01478   //#ifdef BROKEN
01479   menu->insertItem(i18n("Undo"),mEditor,
01480                    SLOT(undo()), KStdAccel::shortcut(KStdAccel::Undo));
01481   menu->insertItem(i18n("Redo"),mEditor,
01482                    SLOT(redo()), KStdAccel::shortcut(KStdAccel::Redo));
01483   menu->insertSeparator();
01484   //#endif //BROKEN
01485   menu->insertItem(i18n("Cut"), this, SLOT(slotCut()));
01486   menu->insertItem(i18n("Copy"), this, SLOT(slotCopy()));
01487   menu->insertItem(i18n("Paste"), this, SLOT(slotPaste()));
01488   menu->insertItem(i18n("Mark All"),this, SLOT(slotMarkAll()));
01489   menu->insertSeparator();
01490   menu->insertItem(i18n("Find..."), this, SLOT(slotFind()));
01491   menu->insertItem(i18n("Replace..."), this, SLOT(slotReplace()));
01492   menu->insertSeparator();
01493   menu->insertItem(i18n("Fixed Font Widths"), this, SLOT(slotUpdateFont()));
01494   mEditor->installRBPopup(menu);
01495   */
01496   updateCursorPosition();
01497   connect(mEditor,SIGNAL(CursorPositionChanged()),SLOT(updateCursorPosition()));
01498   connect( mEditor, SIGNAL( currentFontChanged( const QFont & ) ),
01499           this, SLOT( fontChanged( const QFont & ) ) );
01500   connect( mEditor, SIGNAL( currentAlignmentChanged( int ) ),
01501           this, SLOT( alignmentChanged( int ) ) );
01502 
01503 }
01504 
01505 
01506 //-----------------------------------------------------------------------------
01507 static QString cleanedUpHeaderString( const QString & s )
01508 {
01509   // remove invalid characters from the header strings
01510   QString res( s );
01511   res.replace( '\r', "" );
01512   res.replace( '\n', " " );
01513   return res.stripWhiteSpace();
01514 }
01515 
01516 static QString sanitizedSeparators( const QString& s )
01517 {
01518     const bool allowSemicolon = GlobalSettings::allowSemicolonAsAddressSeparator();
01519     return KPIM::splitEmailAddrList( s, allowSemicolon ).join( "," );
01520 }
01521 
01522 //-----------------------------------------------------------------------------
01523 QString KMComposeWin::subject() const
01524 {
01525   return cleanedUpHeaderString( mEdtSubject->text() );
01526 }
01527 
01528 //-----------------------------------------------------------------------------
01529 QString KMComposeWin::to() const
01530 {
01531     kdDebug() << "TO: " << kdBacktrace() << endl;
01532   return sanitizedSeparators( cleanedUpHeaderString( mEdtTo->text() ) );
01533 }
01534 
01535 //-----------------------------------------------------------------------------
01536 QString KMComposeWin::cc() const
01537 {
01538   if ( mEdtCc->isHidden() )
01539     return QString::null;
01540   else
01541     return sanitizedSeparators( cleanedUpHeaderString( mEdtCc->text() ) );
01542 }
01543 
01544 //-----------------------------------------------------------------------------
01545 QString KMComposeWin::bcc() const
01546 {
01547   if ( mEdtBcc->isHidden() )
01548     return QString::null;
01549   else
01550     return sanitizedSeparators( cleanedUpHeaderString( mEdtBcc->text() ) );
01551 }
01552 
01553 //-----------------------------------------------------------------------------
01554 QString KMComposeWin::from() const
01555 {
01556   return cleanedUpHeaderString( mEdtFrom->text() );
01557 }
01558 
01559 //-----------------------------------------------------------------------------
01560 QString KMComposeWin::replyTo() const
01561 {
01562   return cleanedUpHeaderString( mEdtReplyTo->text() );
01563 }
01564 
01565 //-----------------------------------------------------------------------------
01566 void KMComposeWin::verifyWordWrapLengthIsAdequate(const QString &body)
01567 {
01568   int maxLineLength = 0;
01569   int curPos;
01570   int oldPos = 0;
01571   if (mEditor->QMultiLineEdit::wordWrap() == QMultiLineEdit::FixedColumnWidth) {
01572     for (curPos = 0; curPos < (int)body.length(); ++curPos)
01573         if (body[curPos] == '\n') {
01574           if ((curPos - oldPos) > maxLineLength)
01575             maxLineLength = curPos - oldPos;
01576           oldPos = curPos;
01577         }
01578     if ((curPos - oldPos) > maxLineLength)
01579       maxLineLength = curPos - oldPos;
01580     if (mEditor->wrapColumnOrWidth() < maxLineLength) // column
01581       mEditor->setWrapColumnOrWidth(maxLineLength);
01582   }
01583 }
01584 
01585 //-----------------------------------------------------------------------------
01586 void KMComposeWin::decryptOrStripOffCleartextSignature( QCString& body )
01587 {
01588   QPtrList<Kpgp::Block> pgpBlocks;
01589   QStrList nonPgpBlocks;
01590   if( Kpgp::Module::prepareMessageForDecryption( body,
01591                                                  pgpBlocks, nonPgpBlocks ) )
01592   {
01593     // Only decrypt/strip off the signature if there is only one OpenPGP
01594     // block in the message
01595     if( pgpBlocks.count() == 1 )
01596     {
01597       Kpgp::Block* block = pgpBlocks.first();
01598       if( ( block->type() == Kpgp::PgpMessageBlock ) ||
01599           ( block->type() == Kpgp::ClearsignedBlock ) )
01600       {
01601         if( block->type() == Kpgp::PgpMessageBlock )
01602           // try to decrypt this OpenPGP block
01603           block->decrypt();
01604         else
01605           // strip off the signature
01606           block->verify();
01607 
01608         body = nonPgpBlocks.first()
01609              + block->text()
01610              + nonPgpBlocks.last();
01611       }
01612     }
01613   }
01614 }
01615 
01616 //-----------------------------------------------------------------------------
01617 void KMComposeWin::setMsg(KMMessage* newMsg, bool mayAutoSign,
01618                           bool allowDecryption, bool isModified)
01619 {
01620   //assert(newMsg!=0);
01621   if(!newMsg)
01622     {
01623       kdDebug(5006) << "KMComposeWin::setMsg() : newMsg == 0!" << endl;
01624       return;
01625     }
01626   mMsg = newMsg;
01627 
01628   mEdtTo->setText(mMsg->to());
01629   mEdtFrom->setText(mMsg->from());
01630   mEdtCc->setText(mMsg->cc());
01631   mEdtSubject->setText(mMsg->subject());
01632   mEdtReplyTo->setText(mMsg->replyTo());
01633   mEdtBcc->setText(mMsg->bcc());
01634 
01635   if (!mBtnIdentity->isChecked() && !newMsg->headerField("X-KMail-Identity").isEmpty())
01636     mId = newMsg->headerField("X-KMail-Identity").stripWhiteSpace().toUInt();
01637 
01638   // don't overwrite the header values with identity specific values
01639   // unless the identity is sticky
01640   if ( !mBtnIdentity->isChecked() ) {
01641     disconnect(mIdentity,SIGNAL(identityChanged(uint)),
01642                this, SLOT(slotIdentityChanged(uint)));
01643   }
01644    mIdentity->setCurrentIdentity( mId );
01645   if ( !mBtnIdentity->isChecked() ) {
01646     connect(mIdentity,SIGNAL(identityChanged(uint)),
01647             this, SLOT(slotIdentityChanged(uint)));
01648   }
01649   else {
01650     // make sure the header values are overwritten with the values of the
01651     // sticky identity (the slot isn't called by the signal for new messages
01652     // since the identity has already been set before the signal was connected)
01653     slotIdentityChanged( mId );
01654   }
01655 
01656   KPIM::IdentityManager * im = kmkernel->identityManager();
01657 
01658   const KPIM::Identity & ident = im->identityForUoid( mIdentity->currentIdentity() );
01659 
01660   mOldSigText = GlobalSettings::prependSignatures()? ident.signature().rawText() : ident.signatureText();
01661 
01662   // check for the presence of a DNT header, indicating that MDN's were
01663   // requested
01664   QString mdnAddr = newMsg->headerField("Disposition-Notification-To");
01665   mRequestMDNAction->setChecked( ( !mdnAddr.isEmpty() &&
01666                                   im->thatIsMe( mdnAddr ) ) || mAutoRequestMDN );
01667 
01668   // check for presence of a priority header, indicating urgent mail:
01669   mUrgentAction->setChecked( newMsg->isUrgent() );
01670 
01671   // enable/disable encryption if the message was/wasn't encrypted
01672   switch ( mMsg->encryptionState() ) {
01673     case KMMsgFullyEncrypted: // fall through
01674     case KMMsgPartiallyEncrypted:
01675       mLastEncryptActionState = true;
01676       break;
01677     case KMMsgNotEncrypted:
01678       mLastEncryptActionState = false;
01679       break;
01680     default: // nothing
01681       break;
01682   }
01683 
01684   // enable/disable signing if the message was/wasn't signed
01685   switch ( mMsg->signatureState() ) {
01686     case KMMsgFullySigned: // fall through
01687     case KMMsgPartiallySigned:
01688       mLastSignActionState = true;
01689       break;
01690     case KMMsgNotSigned:
01691       mLastSignActionState = false;
01692       break;
01693     default: // nothing
01694       break;
01695   }
01696 
01697   // if these headers are present, the state of the message should be overruled
01698   if ( mMsg->headers().FindField( "X-KMail-SignatureActionEnabled" ) )
01699     mLastSignActionState = (mMsg->headerField( "X-KMail-SignatureActionEnabled" ) == "true");
01700   if ( mMsg->headers().FindField( "X-KMail-EncryptActionEnabled" ) )
01701     mLastEncryptActionState = (mMsg->headerField( "X-KMail-EncryptActionEnabled" ) == "true");
01702 
01703   mLastIdentityHasSigningKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty();
01704   mLastIdentityHasEncryptionKey = !ident.pgpEncryptionKey().isEmpty() || !ident.smimeEncryptionKey().isEmpty();
01705 
01706   if( !mMsg->headerField("X-KMail-CryptoFormat").isEmpty() ){
01707     const int format = mMsg->headerField("X-KMail-CryptoFormat").stripWhiteSpace().toInt();
01708     if( 0 <= format ){
01709       mCryptoModuleAction->setCurrentItem(
01710         format2cb( (Kleo::CryptoMessageFormat)format ) );
01711       slotSelectCryptoModule();
01712     }
01713   }
01714 
01715   if ( Kleo::CryptoBackendFactory::instance()->openpgp() || Kleo::CryptoBackendFactory::instance()->smime() ) {
01716     const bool canOpenPGPSign = Kleo::CryptoBackendFactory::instance()->openpgp()
01717       && !ident.pgpSigningKey().isEmpty();
01718     const bool canSMIMESign = Kleo::CryptoBackendFactory::instance()->smime()
01719       && !ident.smimeSigningKey().isEmpty();
01720 
01721     setEncryption( mLastEncryptActionState );
01722     setSigning( ( canOpenPGPSign || canSMIMESign ) && mLastSignActionState );
01723   }
01724 
01725   // "Attach my public key" is only possible if the user uses OpenPGP
01726   // support and he specified his key:
01727   mAttachMPK->setEnabled( Kleo::CryptoBackendFactory::instance()->openpgp() &&
01728               !ident.pgpEncryptionKey().isEmpty() );
01729 
01730   QString transport = newMsg->headerField("X-KMail-Transport");
01731   if (!mBtnTransport->isChecked() && !transport.isEmpty())
01732   {
01733     for (int i = 0; i < mTransport->count(); i++)
01734       if (mTransport->text(i) == transport)
01735         mTransport->setCurrentItem(i);
01736     mTransport->setEditText( transport );
01737   }
01738 
01739   if (!mBtnFcc->isChecked())
01740   {
01741     if (!mMsg->fcc().isEmpty())
01742       setFcc(mMsg->fcc());
01743     else
01744       setFcc(ident.fcc());
01745   }
01746 
01747   mDictionaryCombo->setCurrentByDictionary( ident.dictionary() );
01748 
01749   partNode * root = partNode::fromMessage( mMsg );
01750 
01751   KMail::ObjectTreeParser otp; // all defaults are ok
01752   otp.parseObjectTree( root );
01753 
01754   KMail::AttachmentCollector ac;
01755   ac.setDiveIntoEncryptions( true );
01756   ac.setDiveIntoSignatures( true );
01757   ac.setDiveIntoMessages( false );
01758 
01759   ac.collectAttachmentsFrom( root );
01760 
01761   for ( std::vector<partNode*>::const_iterator it = ac.attachments().begin() ; it != ac.attachments().end() ; ++it )
01762     addAttach( new KMMessagePart( (*it)->msgPart() ) );
01763 
01764   mEditor->setText( otp.textualContent() );
01765   mCharset = otp.textualContentCharset();
01766   if ( mCharset.isEmpty() )
01767     mCharset = mMsg->charset();
01768   if ( mCharset.isEmpty() )
01769     mCharset = mDefCharset;
01770   setCharset( mCharset );
01771 
01772   if ( partNode * n = root->findType( DwMime::kTypeText, DwMime::kSubtypeHtml ) )
01773     if ( partNode * p = n->parentNode() )
01774       if ( p->hasType( DwMime::kTypeMultipart ) &&
01775            p->hasSubType( DwMime::kSubtypeAlternative ) )
01776         if ( mMsg->headerField( "X-KMail-Markup" ) == "true" )
01777           toggleMarkup( true );
01778 
01779   /* Handle the special case of non-mime mails */
01780   if ( mMsg->numBodyParts() == 0 && otp.textualContent().isEmpty() ) {
01781     mCharset=mMsg->charset();
01782     if ( mCharset.isEmpty() ||  mCharset == "default" )
01783       mCharset = mDefCharset;
01784 
01785     QCString bodyDecoded = mMsg->bodyDecoded();
01786 
01787     if( allowDecryption )
01788       decryptOrStripOffCleartextSignature( bodyDecoded );
01789 
01790     const QTextCodec *codec = KMMsgBase::codecForName(mCharset);
01791     if (codec) {
01792       mEditor->setText(codec->toUnicode(bodyDecoded));
01793     } else
01794       mEditor->setText(QString::fromLocal8Bit(bodyDecoded));
01795   }
01796 
01797 
01798 #ifdef BROKEN_FOR_OPAQUE_SIGNED_OR_ENCRYPTED_MAILS
01799   const int num = mMsg->numBodyParts();
01800   kdDebug(5006) << "KMComposeWin::setMsg() mMsg->numBodyParts="
01801                 << mMsg->numBodyParts() << endl;
01802 
01803   if ( num > 0 ) {
01804     KMMessagePart bodyPart;
01805     int firstAttachment = 0;
01806 
01807     mMsg->bodyPart(1, &bodyPart);
01808     if ( bodyPart.typeStr().lower() == "text" &&
01809          bodyPart.subtypeStr().lower() == "html" ) {
01810       // check whether we are inside a mp/al body part
01811       partNode *root = partNode::fromMessage( mMsg );
01812       partNode *node = root->findType( DwMime::kTypeText,
01813                                        DwMime::kSubtypeHtml );
01814       if ( node && node->parentNode() &&
01815            node->parentNode()->hasType( DwMime::kTypeMultipart ) &&
01816            node->parentNode()->hasSubType( DwMime::kSubtypeAlternative ) ) {
01817         // we have a mp/al body part with a text and an html body
01818       kdDebug(5006) << "KMComposeWin::setMsg() : text/html found" << endl;
01819       firstAttachment = 2;
01820         if ( mMsg->headerField( "X-KMail-Markup" ) == "true" )
01821           toggleMarkup( true );
01822       }
01823       delete root; root = 0;
01824     }
01825     if ( firstAttachment == 0 ) {
01826         mMsg->bodyPart(0, &bodyPart);
01827         if ( bodyPart.typeStr().lower() == "text" ) {
01828           // we have a mp/mx body with a text body
01829         kdDebug(5006) << "KMComposeWin::setMsg() : text/* found" << endl;
01830           firstAttachment = 1;
01831         }
01832       }
01833 
01834     if ( firstAttachment != 0 ) // there's text to show
01835     {
01836       mCharset = bodyPart.charset();
01837       if ( mCharset.isEmpty() || mCharset == "default" )
01838         mCharset = mDefCharset;
01839 
01840       QCString bodyDecoded = bodyPart.bodyDecoded();
01841 
01842       if( allowDecryption )
01843         decryptOrStripOffCleartextSignature( bodyDecoded );
01844 
01845       // As nobody seems to know the purpose of the following line and
01846       // as it breaks word wrapping of long lines if drafts with attachments
01847       // are opened for editting in the composer (cf. Bug#41102) I comment it
01848       // out. Ingo, 2002-04-21
01849       //verifyWordWrapLengthIsAdequate(bodyDecoded);
01850 
01851       const QTextCodec *codec = KMMsgBase::codecForName(mCharset);
01852       if (codec)
01853         mEditor->setText(codec->toUnicode(bodyDecoded));
01854       else
01855         mEditor->setText(QString::fromLocal8Bit(bodyDecoded));
01856       //mEditor->insertLine("\n", -1); <-- why ?
01857     } else mEditor->setText("");
01858     for( int i = firstAttachment; i < num; ++i )
01859     {
01860       KMMessagePart *msgPart = new KMMessagePart;
01861       mMsg->bodyPart(i, msgPart);
01862       QCString mimeType = msgPart->typeStr().lower() + '/'
01863                         + msgPart->subtypeStr().lower();
01864       // don't add the detached signature as attachment when editting a
01865       // PGP/MIME signed message
01866       if( mimeType != "application/pgp-signature" ) {
01867         addAttach(msgPart);
01868       }
01869     }
01870   } else{
01871     mCharset=mMsg->charset();
01872     if ( mCharset.isEmpty() ||  mCharset == "default" )
01873       mCharset = mDefCharset;
01874 
01875     QCString bodyDecoded = mMsg->bodyDecoded();
01876 
01877     if( allowDecryption )
01878       decryptOrStripOffCleartextSignature( bodyDecoded );
01879 
01880     const QTextCodec *codec = KMMsgBase::codecForName(mCharset);
01881     if (codec) {
01882       mEditor->setText(codec->toUnicode(bodyDecoded));
01883     } else
01884       mEditor->setText(QString::fromLocal8Bit(bodyDecoded));
01885   }
01886 
01887   setCharset(mCharset);
01888 #endif // BROKEN_FOR_OPAQUE_SIGNED_OR_ENCRYPTED_MAILS
01889 
01890   if( mAutoSign && mayAutoSign ) {
01891     //
01892     // Espen 2000-05-16
01893     // Delay the signature appending. It may start a fileseletor.
01894     // Not user friendy if this modal fileseletor opens before the
01895     // composer.
01896     //
01897       if ( GlobalSettings::prependSignatures() ) {
01898         QTimer::singleShot( 0, this, SLOT(slotPrependSignature()) );
01899       } else {
01900         QTimer::singleShot( 0, this, SLOT(slotAppendSignature()) );
01901       }
01902 
01903   }
01904   mEditor->setModified(isModified);
01905 }
01906 
01907 
01908 //-----------------------------------------------------------------------------
01909 void KMComposeWin::setFcc( const QString &idString )
01910 {
01911   // check if the sent-mail folder still exists
01912   KMFolder *folder = kmkernel->findFolderById( idString );
01913   if ( folder )
01914     mFcc->setFolder( idString );
01915   else
01916     mFcc->setFolder( kmkernel->sentFolder() );
01917 }
01918 
01919 
01920 //-----------------------------------------------------------------------------
01921 bool KMComposeWin::queryClose ()
01922 {
01923   if ( !mEditor->checkExternalEditorFinished() )
01924     return false;
01925   if (kmkernel->shuttingDown() || kapp->sessionSaving())
01926     return true;
01927 
01928   if(mEditor->isModified() || mEdtFrom->edited() || mEdtReplyTo->edited() ||
01929      mEdtTo->edited() || mEdtCc->edited() || mEdtBcc->edited() ||
01930      mEdtSubject->edited() || mAtmModified ||
01931      (mTransport->lineEdit() && mTransport->lineEdit()->edited()))
01932   {
01933     const int rc = KMessageBox::warningYesNoCancel(this,
01934            i18n("Do you want to save the message for later or discard it?"),
01935            i18n("Close Composer"),
01936            i18n("&Save as Draft"),
01937            KStdGuiItem::discard() );
01938     if (rc == KMessageBox::Cancel)
01939       return false;
01940     else if (rc == KMessageBox::Yes) {
01941       // doSend will close the window. Just return false from this method
01942       slotSaveDraft();
01943       return false;
01944     }
01945   }
01946   cleanupAutoSave();
01947   return true;
01948 }
01949 
01950 //-----------------------------------------------------------------------------
01951 bool KMComposeWin::userForgotAttachment()
01952 {
01953   KConfigGroup composer( KMKernel::config(), "Composer" );
01954   bool checkForForgottenAttachments =
01955     composer.readBoolEntry( "showForgottenAttachmentWarning", true );
01956 
01957   if ( !checkForForgottenAttachments || ( mAtmList.count() > 0 ) )
01958     return false;
01959 
01960 
01961   QStringList attachWordsList =
01962     composer.readListEntry( "attachment-keywords" );
01963 
01964   if ( attachWordsList.isEmpty() ) {
01965     // default value (FIXME: this is duplicated in configuredialog.cpp)
01966     attachWordsList << QString::fromLatin1("attachment")
01967                     << QString::fromLatin1("attached");
01968     if ( QString::fromLatin1("attachment") != i18n("attachment") )
01969       attachWordsList << i18n("attachment");
01970     if ( QString::fromLatin1("attached") != i18n("attached") )
01971       attachWordsList << i18n("attached");
01972   }
01973 
01974   QRegExp rx ( QString::fromLatin1("\\b") +
01975                attachWordsList.join("\\b|\\b") +
01976                QString::fromLatin1("\\b") );
01977   rx.setCaseSensitive( false );
01978 
01979   bool gotMatch = false;
01980 
01981   // check whether the subject contains one of the attachment key words
01982   // unless the message is a reply or a forwarded message
01983   QString subj = subject();
01984   gotMatch =    ( KMMessage::stripOffPrefixes( subj ) == subj )
01985              && ( rx.search( subj ) >= 0 );
01986 
01987   if ( !gotMatch ) {
01988     // check whether the non-quoted text contains one of the attachment key
01989     // words
01990     QRegExp quotationRx ("^([ \\t]*([|>:}#]|[A-Za-z]+>))+");
01991     for ( int i = 0; i < mEditor->numLines(); ++i ) {
01992       QString line = mEditor->textLine( i );
01993       gotMatch =    ( quotationRx.search( line ) < 0 )
01994                  && ( rx.search( line ) >= 0 );
01995       if ( gotMatch )
01996         break;
01997     }
01998   }
01999 
02000   if ( !gotMatch )
02001     return false;
02002 
02003   int rc = KMessageBox::warningYesNoCancel( this,
02004              i18n("The message you have composed seems to refer to an "
02005                   "attached file but you have not attached anything.\n"
02006                   "Do you want to attach a file to your message?"),
02007              i18n("File Attachment Reminder"),
02008              i18n("&Attach File..."),
02009              i18n("&Send as Is") );
02010   if ( rc == KMessageBox::Cancel )
02011     return true;
02012   if ( rc == KMessageBox::Yes ) {
02013     slotAttachFile();
02014     //preceed with editing
02015     return true;
02016   }
02017   return false;
02018 }
02019 
02020 //-----------------------------------------------------------------------------
02021 void KMComposeWin::applyChanges( bool dontSignNorEncrypt, bool dontDisable )
02022 {
02023 #ifdef DEBUG
02024   kdDebug(5006) << "entering KMComposeWin::applyChanges" << endl;
02025 #endif
02026 
02027   if(!mMsg) {
02028     kdDebug(5006) << "KMComposeWin::applyChanges() : mMsg == 0!\n" << endl;
02029     emit applyChangesDone( false );
02030     return;
02031   }
02032 
02033   if( mComposer ) {
02034     kdDebug(5006) << "KMComposeWin::applyChanges() : applyChanges called twice"
02035                   << endl;
02036     return;
02037   }
02038 
02039   // Make new job and execute it
02040   mComposer = new MessageComposer( this );
02041   connect( mComposer, SIGNAL( done( bool ) ),
02042            this, SLOT( slotComposerDone( bool ) ) );
02043 
02044   // TODO: Add a cancel button for this operation
02045   if ( !dontDisable )
02046     setEnabled( false );
02047 
02048   mComposer->setDisableBreaking( mDisableBreaking );
02049   mComposer->applyChanges( dontSignNorEncrypt );
02050 }
02051 
02052 void KMComposeWin::slotComposerDone( bool rc )
02053 {
02054   deleteAll( mComposedMessages );
02055   mComposedMessages = mComposer->composedMessageList();
02056   emit applyChangesDone( rc );
02057   delete mComposer;
02058   mComposer = 0;
02059 }
02060 
02061 const KPIM::Identity & KMComposeWin::identity() const {
02062   return kmkernel->identityManager()->identityForUoidOrDefault( mIdentity->currentIdentity() );
02063 }
02064 
02065 Kleo::CryptoMessageFormat KMComposeWin::cryptoMessageFormat() const {
02066   if ( !mCryptoModuleAction )
02067     return Kleo::AutoFormat;
02068   return cb2format( mCryptoModuleAction->currentItem() );
02069 }
02070 
02071 bool KMComposeWin::encryptToSelf() const {
02072   return !Kpgp::Module::getKpgp() || Kpgp::Module::getKpgp()->encryptToSelf();
02073 }
02074 
02075 bool KMComposeWin::queryExit ()
02076 {
02077   return true;
02078 }
02079 
02080 //-----------------------------------------------------------------------------
02081 void KMComposeWin::addAttach(const KURL aUrl)
02082 {
02083   if ( !aUrl.isValid() ) {
02084     KMessageBox::sorry( this, i18n( "<qt><p>KMail could not recognize the location of the attachment (%1);</p>"
02085                                  "<p>you have to specify the full path if you wish to attach a file.</p></qt>" )
02086                         .arg( aUrl.prettyURL() ) );
02087     return;
02088   }
02089   KIO::TransferJob *job = KIO::get(aUrl);
02090   KIO::Scheduler::scheduleJob( job );
02091   atmLoadData ld;
02092   ld.url = aUrl;
02093   ld.data = QByteArray();
02094   ld.insert = false;
02095   if( !aUrl.fileEncoding().isEmpty() )
02096     ld.encoding = aUrl.fileEncoding().latin1();
02097 
02098   mMapAtmLoadData.insert(job, ld);
02099   connect(job, SIGNAL(result(KIO::Job *)),
02100           this, SLOT(slotAttachFileResult(KIO::Job *)));
02101   connect(job, SIGNAL(data(KIO::Job *, const QByteArray &)),
02102           this, SLOT(slotAttachFileData(KIO::Job *, const QByteArray &)));
02103 }
02104 
02105 
02106 //-----------------------------------------------------------------------------
02107 void KMComposeWin::addAttach(const KMMessagePart* msgPart)
02108 {
02109   mAtmList.append(msgPart);
02110 
02111   // show the attachment listbox if it does not up to now
02112   if (mAtmList.count()==1)
02113   {
02114     mGrid->setRowStretch(mNumHeaders+1, 50);
02115     mAtmListView->setMinimumSize(100, 80);
02116     mAtmListView->setMaximumHeight( 100 );
02117     mAtmListView->show();
02118     resize(size());
02119   }
02120 
02121   // add a line in the attachment listbox
02122   KMAtmListViewItem *lvi = new KMAtmListViewItem( mAtmListView );
02123   msgPartToItem(msgPart, lvi);
02124   mAtmItemList.append(lvi);
02125 
02126   slotUpdateAttachActions();
02127 }
02128 
02129 
02130 //-----------------------------------------------------------------------------
02131 void KMComposeWin::slotUpdateAttachActions()
02132 {
02133   int selectedCount = 0;
02134   for ( QPtrListIterator<QListViewItem> it(mAtmItemList); *it; ++it ) {
02135     if ( (*it)->isSelected() ) {
02136       ++selectedCount;
02137     }
02138   }
02139 
02140   mAttachRemoveAction->setEnabled( selectedCount >= 1 );
02141   mAttachSaveAction->setEnabled( selectedCount == 1 );
02142   mAttachPropertiesAction->setEnabled( selectedCount == 1 );
02143 }
02144 
02145 
02146 //-----------------------------------------------------------------------------
02147 
02148 QString KMComposeWin::prettyMimeType( const QString& type )
02149 {
02150   QString t = type.lower();
02151   KServiceType::Ptr st = KServiceType::serviceType( t );
02152   return st ? st->comment() : t;
02153 }
02154 
02155 void KMComposeWin::msgPartToItem(const KMMessagePart* msgPart,
02156                                  KMAtmListViewItem *lvi)
02157 {
02158   assert(msgPart != 0);
02159 
02160   if (!msgPart->fileName().isEmpty())
02161     lvi->setText(0, msgPart->fileName());
02162   else
02163     lvi->setText(0, msgPart->name());
02164   lvi->setText(1, KIO::convertSize( msgPart->decodedSize()));
02165   lvi->setText(2, msgPart->contentTransferEncodingStr());
02166   lvi->setText(3, prettyMimeType(msgPart->typeStr() + "/" + msgPart->subtypeStr()));
02167   if( canSignEncryptAttachments() ) {
02168     lvi->enableCryptoCBs( true );
02169     lvi->setEncrypt( mEncryptAction->isChecked() );
02170     lvi->setSign(    mSignAction->isChecked() );
02171   } else {
02172     lvi->enableCryptoCBs( false );
02173   }
02174 }
02175 
02176 
02177 //-----------------------------------------------------------------------------
02178 void KMComposeWin::removeAttach(const QString &aUrl)
02179 {
02180   int idx;
02181   KMMessagePart* msgPart;
02182   for(idx=0,msgPart=mAtmList.first(); msgPart;
02183       msgPart=mAtmList.next(),idx++) {
02184     if (msgPart->name() == aUrl) {
02185       removeAttach(idx);
02186       return;
02187     }
02188   }
02189 }
02190 
02191 
02192 //-----------------------------------------------------------------------------
02193 void KMComposeWin::removeAttach(int idx)
02194 {
02195   mAtmModified = TRUE;
02196   mAtmList.remove(idx);
02197   delete mAtmItemList.take(idx);
02198 
02199   if( mAtmList.isEmpty() )
02200   {
02201     mAtmListView->hide();
02202     mGrid->setRowStretch(mNumHeaders+1, 0);
02203     mAtmListView->setMinimumSize(0, 0);
02204     resize(size());
02205   }
02206 }
02207 
02208 
02209 //-----------------------------------------------------------------------------
02210 bool KMComposeWin::encryptFlagOfAttachment(int idx)
02211 {
02212   return (int)(mAtmItemList.count()) > idx
02213     ? ((KMAtmListViewItem*)(mAtmItemList.at( idx )))->isEncrypt()
02214     : false;
02215 }
02216 
02217 
02218 //-----------------------------------------------------------------------------
02219 bool KMComposeWin::signFlagOfAttachment(int idx)
02220 {
02221   return (int)(mAtmItemList.count()) > idx
02222     ? ((KMAtmListViewItem*)(mAtmItemList.at( idx )))->isSign()
02223     : false;
02224 }
02225 
02226 
02227 //-----------------------------------------------------------------------------
02228 void KMComposeWin::addrBookSelInto()
02229 {
02230   AddressesDialog dlg( this );
02231   QString txt;
02232   QStringList lst;
02233 
02234   txt = to();
02235   if ( !txt.isEmpty() ) {
02236       lst = KPIM::splitEmailAddrList( txt );
02237       dlg.setSelectedTo( lst );
02238   }
02239 
02240   txt = cc();
02241   if ( !txt.isEmpty() ) {
02242       lst = KPIM::splitEmailAddrList( txt );
02243       dlg.setSelectedCC( lst );
02244   }
02245 
02246   txt = bcc();
02247   if ( !txt.isEmpty() ) {
02248       lst = KPIM::splitEmailAddrList( txt );
02249       dlg.setSelectedBCC( lst );
02250   }
02251 
02252   dlg.setRecentAddresses( RecentAddresses::self( KMKernel::config() )->kabcAddresses() );
02253 
02254   if (dlg.exec()==QDialog::Rejected) return;
02255 
02256   mEdtTo->setText( dlg.to().join(", ") );
02257   mEdtTo->setEdited( true );
02258 
02259   mEdtCc->setText( dlg.cc().join(", ") );
02260   mEdtCc->setEdited( true );
02261 
02262   mEdtBcc->setText( dlg.bcc().join(", ") );
02263   mEdtBcc->setEdited( true );
02264 }
02265 
02266 
02267 //-----------------------------------------------------------------------------
02268 void KMComposeWin::setCharset(const QCString& aCharset, bool forceDefault)
02269 {
02270   if ((forceDefault && mForceReplyCharset) || aCharset.isEmpty())
02271     mCharset = mDefCharset;
02272   else
02273     mCharset = aCharset.lower();
02274 
02275   if ( mCharset.isEmpty() || mCharset == "default" )
02276      mCharset = mDefCharset;
02277 
02278   if (mAutoCharset)
02279   {
02280     mEncodingAction->setCurrentItem( 0 );
02281     return;
02282   }
02283 
02284   QStringList encodings = mEncodingAction->items();
02285   int i = 0;
02286   bool charsetFound = FALSE;
02287   for ( QStringList::Iterator it = encodings.begin(); it != encodings.end();
02288      ++it, i++ )
02289   {
02290     if (i > 0 && ((mCharset == "us-ascii" && i == 1) ||
02291      (i != 1 && KGlobal::charsets()->codecForName(
02292       KGlobal::charsets()->encodingForName(*it))
02293       == KGlobal::charsets()->codecForName(mCharset))))
02294     {
02295       mEncodingAction->setCurrentItem( i );
02296       slotSetCharset();
02297       charsetFound = TRUE;
02298       break;
02299     }
02300   }
02301   if (!aCharset.isEmpty() && !charsetFound) setCharset("", TRUE);
02302 }
02303 
02304 
02305 //-----------------------------------------------------------------------------
02306 void KMComposeWin::slotAddrBook()
02307 {
02308   KAddrBookExternal::openAddressBook(this);
02309 }
02310 
02311 
02312 //-----------------------------------------------------------------------------
02313 void KMComposeWin::slotAddrBookFrom()
02314 {
02315   addrBookSelInto();
02316 }
02317 
02318 
02319 //-----------------------------------------------------------------------------
02320 void KMComposeWin::slotAddrBookReplyTo()
02321 {
02322   addrBookSelInto();
02323 }
02324 
02325 
02326 //-----------------------------------------------------------------------------
02327 void KMComposeWin::slotAddrBookTo()
02328 {
02329   addrBookSelInto();
02330 }
02331 
02332 //-----------------------------------------------------------------------------
02333 void KMComposeWin::slotAttachFile()
02334 {
02335   // Create File Dialog and return selected file(s)
02336   // We will not care about any permissions, existence or whatsoever in
02337   // this function.
02338 
02339   KFileDialog fdlg(QString::null, QString::null, this, 0, TRUE);
02340   fdlg.setOperationMode( KFileDialog::Other );
02341   fdlg.setCaption(i18n("Attach File"));
02342   fdlg.okButton()->setGuiItem(KGuiItem(i18n("&Attach"),"fileopen"));
02343   fdlg.setMode(KFile::Files);
02344   fdlg.exec();
02345   KURL::List files = fdlg.selectedURLs();
02346 
02347   for (KURL::List::Iterator it = files.begin(); it != files.end(); ++it)
02348     addAttach(*it);
02349 }
02350 
02351 
02352 //-----------------------------------------------------------------------------
02353 void KMComposeWin::slotAttachFileData(KIO::Job *job, const QByteArray &data)
02354 {
02355   QMap<KIO::Job*, atmLoadData>::Iterator it = mMapAtmLoadData.find(job);
02356   assert(it != mMapAtmLoadData.end());
02357   QBuffer buff((*it).data);
02358   buff.open(IO_WriteOnly | IO_Append);
02359   buff.writeBlock(data.data(), data.size());
02360   buff.close();
02361 }
02362 
02363 
02364 //-----------------------------------------------------------------------------
02365 void KMComposeWin::slotAttachFileResult(KIO::Job *job)
02366 {
02367   QMap<KIO::Job*, atmLoadData>::Iterator it = mMapAtmLoadData.find(job);
02368   assert(it != mMapAtmLoadData.end());
02369   if (job->error())
02370   {
02371     mMapAtmLoadData.remove(it);
02372     job->showErrorDialog();
02373     return;
02374   }
02375   if ((*it).insert)
02376   {
02377     (*it).data.resize((*it).data.size() + 1);
02378     (*it).data[(*it).data.size() - 1] = '\0';
02379     if ( const QTextCodec * codec = KGlobal::charsets()->codecForName((*it).encoding) )
02380       mEditor->insert( codec->toUnicode( (*it).data ) );
02381     else
02382       mEditor->insert( QString::fromLocal8Bit( (*it).data ) );
02383     mMapAtmLoadData.remove(it);
02384     return;
02385   }
02386   const QCString partCharset = (*it).url.fileEncoding().isEmpty()
02387                              ? mCharset
02388                              : QCString((*it).url.fileEncoding().latin1());
02389 
02390   KMMessagePart* msgPart;
02391 
02392   KCursorSaver busy(KBusyPtr::busy());
02393   QString name( (*it).url.fileName() );
02394   // ask the job for the mime type of the file
02395   QString mimeType = static_cast<KIO::MimetypeJob*>(job)->mimetype();
02396 
02397   if ( name.isEmpty() ) {
02398     // URL ends with '/' (e.g. http://www.kde.org/)
02399     // guess a reasonable filename
02400     if( mimeType == "text/html" )
02401       name = "index.html";
02402     else {
02403       // try to determine a reasonable extension
02404       QStringList patterns( KMimeType::mimeType( mimeType )->patterns() );
02405       QString ext;
02406       if( !patterns.isEmpty() ) {
02407         ext = patterns[0];
02408         int i = ext.findRev( '.' );
02409         if( i == -1 )
02410           ext.prepend( '.' );
02411         else if( i > 0 )
02412           ext = ext.mid( i );
02413       }
02414       name = QString("unknown") += ext;
02415     }
02416   }
02417 
02418   name.truncate( 256 ); // is this needed?
02419 
02420   QCString encoding = KMMsgBase::autoDetectCharset(partCharset,
02421     KMMessage::preferredCharsets(), name);
02422   if (encoding.isEmpty()) encoding = "utf-8";
02423 
02424   QCString encName;
02425   if ( mOutlookCompatible )
02426     encName = KMMsgBase::encodeRFC2047String( name, encoding );
02427   else
02428     encName = KMMsgBase::encodeRFC2231String( name, encoding );
02429   bool RFC2231encoded = false;
02430   if ( !mOutlookCompatible )
02431     RFC2231encoded = name != QString( encName );
02432 
02433   // create message part
02434   msgPart = new KMMessagePart;
02435   msgPart->setName(name);
02436   QValueList<int> allowedCTEs;
02437   if ( mimeType == "message/rfc822" ) {
02438     msgPart->setMessageBody( (*it).data );
02439     allowedCTEs << DwMime::kCte7bit;
02440     allowedCTEs << DwMime::kCte8bit;
02441   } else {
02442     msgPart->setBodyAndGuessCte((*it).data, allowedCTEs,
02443                               !kmkernel->msgSender()->sendQuotedPrintable());
02444     kdDebug(5006) << "autodetected cte: " << msgPart->cteStr() << endl;
02445   }
02446   int slash = mimeType.find( '/' );
02447   if( slash == -1 )
02448     slash = mimeType.length();
02449   msgPart->setTypeStr( mimeType.left( slash ).latin1() );
02450   msgPart->setSubtypeStr( mimeType.mid( slash + 1 ).latin1() );
02451   msgPart->setContentDisposition(QCString("attachment;\n\tfilename")
02452     + ( RFC2231encoded ? "*=" + encName : "=\"" + encName + '"' ));
02453 
02454   mMapAtmLoadData.remove(it);
02455 
02456   msgPart->setCharset(partCharset);
02457 
02458   // show message part dialog, if not configured away (default):
02459   KConfigGroup composer(KMKernel::config(), "Composer");
02460   if (!composer.hasKey("showMessagePartDialogOnAttach"))
02461     // make it visible in the config file:
02462     composer.writeEntry("showMessagePartDialogOnAttach", false);
02463   if (composer.readBoolEntry("showMessagePartDialogOnAttach", false)) {
02464     KMMsgPartDialogCompat dlg;
02465     int encodings = 0;
02466     for ( QValueListConstIterator<int> it = allowedCTEs.begin() ;
02467           it != allowedCTEs.end() ; ++it )
02468       switch ( *it ) {
02469       case DwMime::kCteBase64: encodings |= KMMsgPartDialog::Base64; break;
02470       case DwMime::kCteQp: encodings |= KMMsgPartDialog::QuotedPrintable; break;
02471       case DwMime::kCte7bit: encodings |= KMMsgPartDialog::SevenBit; break;
02472       case DwMime::kCte8bit: encodings |= KMMsgPartDialog::EightBit; break;
02473       default: ;
02474       }
02475     dlg.setShownEncodings( encodings );
02476     dlg.setMsgPart(msgPart);
02477     if (!dlg.exec()) {
02478       delete msgPart;
02479       msgPart = 0;
02480       return;
02481     }
02482   }
02483   mAtmModified = TRUE;
02484   if (msgPart->typeStr().lower() != "text") msgPart->setCharset(QCString());
02485 
02486   // add the new attachment to the list
02487   addAttach(msgPart);
02488 }
02489 
02490 
02491 //-----------------------------------------------------------------------------
02492 void KMComposeWin::slotInsertFile()
02493 {
02494   KFileDialog fdlg(QString::null, QString::null, this, 0, TRUE);
02495   fdlg.setOperationMode( KFileDialog::Opening );
02496   fdlg.okButton()->setText(i18n("&Insert"));
02497   fdlg.setCaption(i18n("Insert File"));
02498   fdlg.toolBar()->insertCombo(KMMsgBase::supportedEncodings(FALSE), 4711,
02499     false, 0, 0, 0);
02500   KComboBox *combo = fdlg.toolBar()->getCombo(4711);
02501   for (int i = 0; i < combo->count(); i++)
02502     if (KGlobal::charsets()->codecForName(KGlobal::charsets()->
02503       encodingForName(combo->text(i)))
02504       == QTextCodec::codecForLocale()) combo->setCurrentItem(i);
02505   if (!fdlg.exec()) return;
02506 
02507   KURL u = fdlg.selectedURL();
02508 
02509   if (u.fileName().isEmpty()) return;
02510 
02511   KIO::Job *job = KIO::get(u);
02512   atmLoadData ld;
02513   ld.url = u;
02514   ld.data = QByteArray();
02515   ld.insert = true;
02516   ld.encoding = KGlobal::charsets()->encodingForName(
02517     combo->currentText()).latin1();
02518   mMapAtmLoadData.insert(job, ld);
02519   connect(job, SIGNAL(result(KIO::Job *)),
02520           this, SLOT(slotAttachFileResult(KIO::Job *)));
02521   connect(job, SIGNAL(data(KIO::Job *, const QByteArray &)),
02522           this, SLOT(slotAttachFileData(KIO::Job *, const QByteArray &)));
02523 }
02524 
02525 
02526 //-----------------------------------------------------------------------------
02527 void KMComposeWin::slotSetCharset()
02528 {
02529   if (mEncodingAction->currentItem() == 0)
02530   {
02531     mAutoCharset = true;
02532     return;
02533   }
02534   mAutoCharset = false;
02535 
02536   mCharset = KGlobal::charsets()->encodingForName( mEncodingAction->
02537     currentText() ).latin1();
02538 }
02539 
02540 
02541 //-----------------------------------------------------------------------------
02542 void KMComposeWin::slotSelectCryptoModule()
02543 {
02544   if( canSignEncryptAttachments() ) {
02545     // if the encrypt/sign columns are hidden then show them
02546     if( 0 == mAtmListView->columnWidth( mAtmColEncrypt ) ) {
02547       // set/unset signing/encryption for all attachments according to the
02548       // state of the global sign/encrypt action
02549       if( !mAtmList.isEmpty() ) {
02550         for( KMAtmListViewItem* lvi = (KMAtmListViewItem*)mAtmItemList.first();
02551              lvi;
02552              lvi = (KMAtmListViewItem*)mAtmItemList.next() ) {
02553           lvi->setSign( mSignAction->isChecked() );
02554           lvi->setEncrypt( mEncryptAction->isChecked() );
02555         }
02556       }
02557       int totalWidth = 0;
02558       // determine the total width of the columns
02559       for( int col=0; col < mAtmColEncrypt; col++ )
02560         totalWidth += mAtmListView->columnWidth( col );
02561       int reducedTotalWidth = totalWidth - mAtmEncryptColWidth
02562                                          - mAtmSignColWidth;
02563       // reduce the width of all columns so that the encrypt and sign column
02564       // fit
02565       int usedWidth = 0;
02566       for( int col=0; col < mAtmColEncrypt-1; col++ ) {
02567         int newWidth = mAtmListView->columnWidth( col ) * reducedTotalWidth
02568                                                        / totalWidth;
02569         mAtmListView->setColumnWidth( col, newWidth );
02570         usedWidth += newWidth;
02571       }
02572       // the last column before the encrypt column gets the remaining space
02573       // (because of rounding errors the width of this column isn't calculated
02574       // the same way as the width of the other columns)
02575       mAtmListView->setColumnWidth( mAtmColEncrypt-1,
02576                                     reducedTotalWidth - usedWidth );
02577       mAtmListView->setColumnWidth( mAtmColEncrypt, mAtmEncryptColWidth );
02578       mAtmListView->setColumnWidth( mAtmColSign,    mAtmSignColWidth );
02579       for( KMAtmListViewItem* lvi = (KMAtmListViewItem*)mAtmItemList.first();
02580            lvi;
02581            lvi = (KMAtmListViewItem*)mAtmItemList.next() ) {
02582         lvi->enableCryptoCBs( true );
02583       }
02584     }
02585   } else {
02586     // if the encrypt/sign columns are visible then hide them
02587     if( 0 != mAtmListView->columnWidth( mAtmColEncrypt ) ) {
02588       mAtmEncryptColWidth = mAtmListView->columnWidth( mAtmColEncrypt );
02589       mAtmSignColWidth = mAtmListView->columnWidth( mAtmColSign );
02590       int totalWidth = 0;
02591       // determine the total width of the columns
02592       for( int col=0; col < mAtmListView->columns(); col++ )
02593         totalWidth += mAtmListView->columnWidth( col );
02594       int reducedTotalWidth = totalWidth - mAtmEncryptColWidth
02595                                          - mAtmSignColWidth;
02596       // increase the width of all columns so that the visible columns take
02597       // up the whole space
02598       int usedWidth = 0;
02599       for( int col=0; col < mAtmColEncrypt-1; col++ ) {
02600         int newWidth = mAtmListView->columnWidth( col ) * totalWidth
02601                                                        / reducedTotalWidth;
02602         mAtmListView->setColumnWidth( col, newWidth );
02603         usedWidth += newWidth;
02604       }
02605       // the last column before the encrypt column gets the remaining space
02606       // (because of rounding errors the width of this column isn't calculated
02607       // the same way as the width of the other columns)
02608       mAtmListView->setColumnWidth( mAtmColEncrypt-1, totalWidth - usedWidth );
02609       mAtmListView->setColumnWidth( mAtmColEncrypt, 0 );
02610       mAtmListView->setColumnWidth( mAtmColSign,    0 );
02611       for( KMAtmListViewItem* lvi = (KMAtmListViewItem*)mAtmItemList.first();
02612            lvi;
02613            lvi = (KMAtmListViewItem*)mAtmItemList.next() ) {
02614         lvi->enableCryptoCBs( false );
02615       }
02616     }
02617   }
02618 }
02619 
02620 static void showExportError( QWidget * w, const GpgME::Error & err ) {
02621   assert( err );
02622   const QString msg = i18n("<qt><p>An error occurred while trying to export "
02623                "the key from the backend:</p>"
02624                "<p><b>%1</b></p></qt>")
02625     .arg( QString::fromLocal8Bit( err.asString() ) );
02626   KMessageBox::error( w, msg, i18n("Key Export Failed") );
02627 }
02628 
02629 
02630 //-----------------------------------------------------------------------------
02631 void KMComposeWin::slotInsertMyPublicKey()
02632 {
02633   // get PGP user id for the chosen identity
02634   mFingerprint =
02635     kmkernel->identityManager()->identityForUoidOrDefault( mIdentity->currentIdentity() ).pgpEncryptionKey();
02636   if ( !mFingerprint.isEmpty() )
02637     startPublicKeyExport();
02638 }
02639 
02640 void KMComposeWin::startPublicKeyExport() {
02641   if ( mFingerprint.isEmpty() )
02642     return;
02643   Kleo::ExportJob * job = Kleo::CryptoBackendFactory::instance()->openpgp()->publicKeyExportJob( true );
02644   assert( job );
02645 
02646   connect( job, SIGNAL(result(const GpgME::Error&,const QByteArray&)),
02647        this, SLOT(slotPublicKeyExportResult(const GpgME::Error&,const QByteArray&)) );
02648 
02649   const GpgME::Error err = job->start( mFingerprint );
02650   if ( err )
02651     showExportError( this, err );
02652   else
02653     (void)new Kleo::ProgressDialog( job, i18n("Exporting key..."), this );
02654 }
02655 
02656 void KMComposeWin::slotPublicKeyExportResult( const GpgME::Error & err, const QByteArray & keydata ) {
02657   if ( err ) {
02658     showExportError( this, err );
02659     return;
02660   }
02661 
02662   // create message part
02663   KMMessagePart * msgPart = new KMMessagePart();
02664   msgPart->setName( i18n("OpenPGP key 0x%1").arg( mFingerprint ) );
02665   msgPart->setTypeStr("application");
02666   msgPart->setSubtypeStr("pgp-keys");
02667   QValueList<int> dummy;
02668   msgPart->setBodyAndGuessCte(keydata, dummy, false);
02669   msgPart->setContentDisposition( "attachment;\n\tfilename=0x" + QCString( mFingerprint.latin1() ) + ".asc" );
02670 
02671   // add the new attachment to the list
02672   addAttach(msgPart);
02673   rethinkFields(); //work around initial-size bug in Qt-1.32
02674 }
02675 
02676 //-----------------------------------------------------------------------------
02677 void KMComposeWin::slotInsertPublicKey()
02678 {
02679   Kleo::KeySelectionDialog dlg( i18n("Attach Public OpenPGP Key"),
02680                                 i18n("Select the public key which should "
02681                                      "be attached."),
02682                 std::vector<GpgME::Key>(),
02683                 Kleo::KeySelectionDialog::PublicKeys|Kleo::KeySelectionDialog::OpenPGPKeys,
02684                 false /* no multi selection */,
02685                 false /* no remember choice box */,
02686                 this, "attach public key selection dialog" );
02687   if ( dlg.exec() != QDialog::Accepted )
02688     return;
02689 
02690   mFingerprint = dlg.fingerprint();
02691   startPublicKeyExport();
02692 }
02693 
02694 
02695 //-----------------------------------------------------------------------------
02696 void KMComposeWin::slotAttachPopupMenu(QListViewItem *, const QPoint &, int)
02697 {
02698   if (!mAttachMenu)
02699   {
02700      mAttachMenu = new QPopupMenu(this);
02701 
02702      mOpenId = mAttachMenu->insertItem(SmallIcon("fileopen"), i18n("to open", "Open"), this,
02703                              SLOT(slotAttachOpen()));
02704      mOpenWithId = mAttachMenu->insertItem(i18n("to open with", "Open with..."), this,
02705                              SLOT(slotAttachOpenWith()));
02706      mViewId = mAttachMenu->insertItem(i18n("to view", "View"), this,
02707                              SLOT(slotAttachView()));
02708      mRemoveId = mAttachMenu->insertItem(i18n("Remove"), this, SLOT(slotAttachRemove()));
02709      mSaveAsId = mAttachMenu->insertItem( SmallIcon("filesaveas"), i18n("Save As..."), this,
02710                                           SLOT( slotAttachSave() ) );
02711      mPropertiesId = mAttachMenu->insertItem( i18n("Properties"), this,
02712                                               SLOT( slotAttachProperties() ) );
02713      mAttachMenu->insertSeparator();
02714      mAttachMenu->insertItem(i18n("Add Attachment..."), this, SLOT(slotAttachFile()));
02715   }
02716 
02717   int selectedCount = 0;
02718   for ( QPtrListIterator<QListViewItem> it(mAtmItemList); *it; ++it ) {
02719     if ( (*it)->isSelected() ) {
02720       ++selectedCount;
02721     }
02722   }
02723 
02724   mAttachMenu->setItemEnabled( mOpenId, selectedCount > 0 );
02725   mAttachMenu->setItemEnabled( mOpenWithId, selectedCount > 0 );
02726   mAttachMenu->setItemEnabled( mViewId, selectedCount > 0 );
02727   mAttachMenu->setItemEnabled( mRemoveId, selectedCount > 0 );
02728   mAttachMenu->setItemEnabled( mSaveAsId, selectedCount == 1 );
02729   mAttachMenu->setItemEnabled( mPropertiesId, selectedCount == 1 );
02730 
02731   mAttachMenu->popup(QCursor::pos());
02732 }
02733 
02734 //-----------------------------------------------------------------------------
02735 int KMComposeWin::currentAttachmentNum()
02736 {
02737   int i = 0;
02738   for ( QPtrListIterator<QListViewItem> it(mAtmItemList); *it; ++it, ++i )
02739     if ( *it == mAtmListView->currentItem() )
02740       return i;
02741   return -1;
02742 }
02743 
02744 //-----------------------------------------------------------------------------
02745 void KMComposeWin::slotAttachProperties()
02746 {
02747   int idx = currentAttachmentNum();
02748 
02749   if (idx < 0) return;
02750 
02751   KMMessagePart* msgPart = mAtmList.at(idx);
02752   msgPart->setCharset(mCharset);
02753 
02754   KMMsgPartDialogCompat dlg;
02755   dlg.setMsgPart(msgPart);
02756   KMAtmListViewItem* listItem = (KMAtmListViewItem*)(mAtmItemList.at(idx));
02757   if( canSignEncryptAttachments() && listItem ) {
02758     dlg.setCanSign(    true );
02759     dlg.setCanEncrypt( true );
02760     dlg.setSigned(    listItem->isSign()    );
02761     dlg.setEncrypted( listItem->isEncrypt() );
02762   } else {
02763     dlg.setCanSign(    false );
02764     dlg.setCanEncrypt( false );
02765   }
02766   if (dlg.exec())
02767   {
02768     mAtmModified = TRUE;
02769     // values may have changed, so recreate the listbox line
02770     if( listItem ) {
02771       msgPartToItem(msgPart, listItem);
02772       if( canSignEncryptAttachments() ) {
02773         listItem->setSign(    dlg.isSigned()    );
02774         listItem->setEncrypt( dlg.isEncrypted() );
02775       }
02776     }
02777   }
02778   if (msgPart->typeStr().lower() != "text") msgPart->setCharset(QCString());
02779 }
02780 
02781 
02782 //-----------------------------------------------------------------------------
02783 void KMComposeWin::slotAttachView()
02784 {
02785   int i = 0;
02786   for ( QPtrListIterator<QListViewItem> it(mAtmItemList); *it; ++it, ++i ) {
02787     if ( (*it)->isSelected() ) {
02788       viewAttach( i );
02789     }
02790   }
02791 }
02792 
02793 //-----------------------------------------------------------------------------
02794 void KMComposeWin::slotAttachOpen()
02795 {
02796   int i = 0;
02797   for ( QPtrListIterator<QListViewItem> it(mAtmItemList); *it; ++it, ++i ) {
02798     if ( (*it)->isSelected() ) {
02799       openAttach( i, false );
02800     }
02801   }
02802 }
02803 
02804 //-----------------------------------------------------------------------------
02805 void KMComposeWin::slotAttachOpenWith()
02806 {
02807   int i = 0;
02808   for ( QPtrListIterator<QListViewItem> it(mAtmItemList); *it; ++it, ++i ) {
02809     if ( (*it)->isSelected() ) {
02810       openAttach( i, true );
02811     }
02812   }
02813 }
02814 
02815 //-----------------------------------------------------------------------------
02816 bool KMComposeWin::inlineSigningEncryptionSelected() {
02817   if ( !mSignAction->isChecked() && !mEncryptAction->isChecked() )
02818     return false;
02819   return cryptoMessageFormat() == Kleo::InlineOpenPGPFormat;
02820 }
02821 
02822 //-----------------------------------------------------------------------------
02823 void KMComposeWin::viewAttach( int index )
02824 {
02825   QString str, pname;
02826   KMMessagePart* msgPart;
02827   msgPart = mAtmList.at(index);
02828   pname = msgPart->name().stripWhiteSpace();
02829   if (pname.isEmpty()) pname=msgPart->contentDescription();
02830   if (pname.isEmpty()) pname="unnamed";
02831 
02832   KTempFile* atmTempFile = new KTempFile();
02833   mAtmTempList.append( atmTempFile );
02834   atmTempFile->setAutoDelete( true );
02835   KPIM::kByteArrayToFile(msgPart->bodyDecodedBinary(), atmTempFile->name(), false, false,
02836     false);
02837   KMReaderMainWin *win = new KMReaderMainWin(msgPart, false,
02838     atmTempFile->name(), pname, mCharset );
02839   win->show();
02840 }
02841 
02842 //-----------------------------------------------------------------------------
02843 void KMComposeWin::openAttach( int index, bool with )
02844 {
02845   KMMessagePart* msgPart = mAtmList.at(index);
02846   const QString contentTypeStr =
02847     ( msgPart->typeStr() + '/' + msgPart->subtypeStr() ).lower();
02848 
02849   KMimeType::Ptr mimetype;
02850   mimetype = KMimeType::mimeType( contentTypeStr );
02851 
02852   KTempFile* atmTempFile = new KTempFile();
02853   mAtmTempList.append( atmTempFile );
02854   const bool autoDelete = true;
02855   atmTempFile->setAutoDelete( autoDelete );
02856 
02857   KURL url;
02858   url.setPath( atmTempFile->name() );
02859 
02860   KPIM::kByteArrayToFile( msgPart->bodyDecodedBinary(), atmTempFile->name(), false, false,
02861     false );
02862   if ( ::chmod( QFile::encodeName( atmTempFile->name() ), S_IRUSR ) != 0) {
02863     QFile::remove(url.path());
02864     return;
02865   }
02866 
02867   KService::Ptr offer =
02868     KServiceTypeProfile::preferredService( mimetype->name(), "Application" );
02869 
02870   if ( with || !offer || mimetype->name() == "application/octet-stream" ) {
02871     if ( ( !KRun::displayOpenWithDialog( url, autoDelete ) ) && autoDelete ) {
02872       QFile::remove(url.path());
02873     }
02874   }
02875   else {
02876     if ( ( !KRun::run( *offer, url, autoDelete ) ) && autoDelete ) {
02877         QFile::remove( url.path() );
02878     }
02879   }
02880 
02881 }
02882 
02883 //-----------------------------------------------------------------------------
02884 void KMComposeWin::slotAttachSave()
02885 {
02886   KMMessagePart* msgPart;
02887   QString fileName, pname;
02888   int idx = currentAttachmentNum();
02889 
02890   if (idx < 0) return;
02891 
02892   msgPart = mAtmList.at(idx);
02893   pname = msgPart->name();
02894   if (pname.isEmpty()) pname="unnamed";
02895 
02896   KURL url = KFileDialog::getSaveURL(QString::null, QString::null, 0, i18n("Save Attachment As"));
02897 
02898   if( url.isEmpty() )
02899     return;
02900 
02901   kmkernel->byteArrayToRemoteFile(msgPart->bodyDecodedBinary(), url);
02902 }
02903 
02904 
02905 //-----------------------------------------------------------------------------
02906 void KMComposeWin::slotAttachRemove()
02907 {
02908   bool attachmentRemoved = false;
02909   int i = 0;
02910   for ( QPtrListIterator<QListViewItem> it(mAtmItemList); *it; ) {
02911     if ( (*it)->isSelected() ) {
02912       removeAttach( i );
02913       attachmentRemoved = true;
02914     }
02915     else {
02916       ++it;
02917       ++i;
02918     }
02919   }
02920 
02921   if ( attachmentRemoved ) {
02922     mEditor->setModified( true );
02923     slotUpdateAttachActions();
02924   }
02925 }
02926 
02927 //-----------------------------------------------------------------------------
02928 void KMComposeWin::slotFind()
02929 {
02930   mEditor->search();
02931 }
02932 
02933 void KMComposeWin::slotSearchAgain()
02934 {
02935   mEditor->repeatSearch();
02936 }
02937 
02938 //-----------------------------------------------------------------------------
02939 void KMComposeWin::slotReplace()
02940 {
02941   mEditor->replace();
02942 }
02943 
02944 //-----------------------------------------------------------------------------
02945 void KMComposeWin::slotUpdateFont()
02946 {
02947   if ( mFixedFontAction ) {
02948     mUseFixedFont = mFixedFontAction->isChecked();
02949   }
02950   mEditor->setFont( mUseFixedFont ? mFixedFont : mBodyFont );
02951 }
02952 
02953 QString KMComposeWin::quotePrefixName() const
02954 {
02955     if ( !msg() )
02956         return QString::null;
02957 
02958     KConfig *config=KMKernel::config();
02959     KConfigGroupSaver saver(config, "General");
02960 
02961     int languageNr = config->readNumEntry("reply-current-language",0);
02962     config->setGroup( QString("KMMessage #%1").arg(languageNr) );
02963 
02964     QString quotePrefix = config->readEntry("indent-prefix", ">%_");
02965     quotePrefix = msg()->formatString(quotePrefix);
02966     return quotePrefix;
02967 }
02968 
02969 void KMComposeWin::slotPasteAsQuotation()
02970 {
02971     if( mEditor->hasFocus() && msg() )
02972     {
02973         QString quotePrefix = quotePrefixName();
02974         QString s = QApplication::clipboard()->text();
02975         if (!s.isEmpty()) {
02976             for (int i=0; (uint)i<s.length(); i++) {
02977                 if ( s[i] < ' ' && s[i] != '\n' && s[i] != '\t' )
02978                     s[i] = ' ';
02979             }
02980             s.prepend(quotePrefix);
02981             s.replace("\n","\n"+quotePrefix);
02982             mEditor->insert(s);
02983         }
02984     }
02985 }
02986 
02987 
02988 void KMComposeWin::slotAddQuotes()
02989 {
02990     if( mEditor->hasFocus() && msg() )
02991     {
02992         if ( mEditor->hasMarkedText()) {
02993             QString s =  mEditor->markedText();
02994             QString quotePrefix = quotePrefixName();
02995             s.prepend(quotePrefix);
02996             s.replace("\n", "\n"+quotePrefix);
02997             mEditor->insert(s);
02998         } else {
02999             int l =  mEditor->currentLine();
03000             int c =  mEditor->currentColumn();
03001             QString s =  mEditor->textLine(l);
03002             s.prepend("> ");
03003             mEditor->insertLine(s,l);
03004             mEditor->removeLine(l+1);
03005             mEditor->setCursorPosition(l,c+2);
03006         }
03007     }
03008 }
03009 
03010 
03011 void KMComposeWin::slotRemoveQuotes()
03012 {
03013     if( mEditor->hasFocus() && msg() )
03014     {
03015         QString quotePrefix = quotePrefixName();
03016         if (mEditor->hasMarkedText()) {
03017             QString s = mEditor->markedText();
03018             QString quotePrefix = quotePrefixName();
03019             if (s.left(2) == quotePrefix )
03020                 s.remove(0,2);
03021             s.replace("\n"+quotePrefix,"\n");
03022             mEditor->insert(s);
03023         } else {
03024             int l = mEditor->currentLine();
03025             int c = mEditor->currentColumn();
03026             QString s = mEditor->textLine(l);
03027             if (s.left(2) == quotePrefix) {
03028                 s.remove(0,2);
03029                 mEditor->insertLine(s,l);
03030                 mEditor->removeLine(l+1);
03031                 mEditor->setCursorPosition(l,c-2);
03032             }
03033         }
03034     }
03035 }
03036 
03037 
03038 //-----------------------------------------------------------------------------
03039 void KMComposeWin::slotUndo()
03040 {
03041   QWidget* fw = focusWidget();
03042   if (!fw) return;
03043 
03044   if ( ::qt_cast<KEdit*>(fw) )
03045       static_cast<QMultiLineEdit*>(fw)->undo();
03046   else if (::qt_cast<QLineEdit*>(fw))
03047       static_cast<QLineEdit*>(fw)->undo();
03048 }
03049 
03050 void KMComposeWin::slotRedo()
03051 {
03052   QWidget* fw = focusWidget();
03053   if (!fw) return;
03054 
03055   if (::qt_cast<KEdit*>(fw))
03056       static_cast<KEdit*>(fw)->redo();
03057   else if (::qt_cast<QLineEdit*>(fw))
03058       static_cast<QLineEdit*>(fw)->redo();
03059 }
03060 
03061 //-----------------------------------------------------------------------------
03062 void KMComposeWin::slotCut()
03063 {
03064   QWidget* fw = focusWidget();
03065   if (!fw) return;
03066 
03067   if (::qt_cast<KEdit*>(fw))
03068       static_cast<KEdit*>(fw)->cut();
03069   else if (::qt_cast<QLineEdit*>(fw))
03070       static_cast<QLineEdit*>(fw)->cut();
03071 }
03072 
03073 
03074 //-----------------------------------------------------------------------------
03075 void KMComposeWin::slotCopy()
03076 {
03077   QWidget* fw = focusWidget();
03078   if (!fw) return;
03079 
03080 #ifdef KeyPress
03081 #undef KeyPress
03082 #endif
03083 
03084   QKeyEvent k(QEvent::KeyPress, Key_C , 0 , ControlButton);
03085   kapp->notify(fw, &k);
03086 }
03087 
03088 
03089 //-----------------------------------------------------------------------------
03090 void KMComposeWin::slotPaste()
03091 {
03092   QWidget* fw = focusWidget();
03093   if (!fw) return;
03094 
03095 #ifdef KeyPress
03096 #undef KeyPress
03097 #endif
03098 
03099   QKeyEvent k(QEvent::KeyPress, Key_V , 0 , ControlButton);
03100   kapp->notify(fw, &k);
03101 }
03102 
03103 
03104 //-----------------------------------------------------------------------------
03105 void KMComposeWin::slotMarkAll()
03106 {
03107   QWidget* fw = focusWidget();
03108   if (!fw) return;
03109 
03110   if (::qt_cast<QLineEdit*>(fw))
03111       static_cast<QLineEdit*>(fw)->selectAll();
03112   else if (::qt_cast<KEdit*>(fw))
03113       static_cast<KEdit*>(fw)->selectAll();
03114 }
03115 
03116 
03117 //-----------------------------------------------------------------------------
03118 void KMComposeWin::slotClose()
03119 {
03120   close(FALSE);
03121 }
03122 
03123 
03124 //-----------------------------------------------------------------------------
03125 void KMComposeWin::slotNewComposer()
03126 {
03127   KMComposeWin* win;
03128   KMMessage* msg = new KMMessage;
03129 
03130   msg->initHeader();
03131   win = new KMComposeWin(msg);
03132   win->show();
03133 }
03134 
03135 
03136 //-----------------------------------------------------------------------------
03137 void KMComposeWin::slotNewMailReader()
03138 {
03139   KMMainWin *kmmwin = new KMMainWin(0);
03140   kmmwin->show();
03141   //d->resize(d->size());
03142 }
03143 
03144 
03145 //-----------------------------------------------------------------------------
03146 void KMComposeWin::slotUpdWinTitle(const QString& text)
03147 {
03148   if (text.isEmpty())
03149        setCaption("("+i18n("unnamed")+")");
03150   else setCaption(text);
03151 }
03152 
03153 
03154 //-----------------------------------------------------------------------------
03155 void KMComposeWin::slotEncryptToggled(bool on)
03156 {
03157 #ifdef KLEO_CHIASMUS
03158   if ( on && mEncryptBodyWithChiasmus ) {
03159     int ret = KMessageBox::warningContinueCancel( this,
03160         i18n("You have chosen to encrypt the body of your message using "
03161              "chiasmus. Additionally encrypting such messages by other means "
03162              "is currently not supported. Do you want to continue, which "
03163              "will disable encryption of the body (but not of attachments ) "
03164              "using chiasmus, or cancel, which will disable additional "
03165              "(non-chiasmus) encryption?" ),
03166         i18n("Chiasmus Body Encryption Conflict") );
03167     if ( ret == KMessageBox::Cancel ) {
03168       mEncryptAction->setChecked( false );
03169       return;
03170     }
03171     slotEncryptBodyChiasmusToggled( false );
03172     mEncryptBodyChiasmusAction->setChecked( false );
03173   }
03174 #endif
03175   setEncryption( on, true /* set by the user */ );
03176 }
03177 
03178 
03179 //-----------------------------------------------------------------------------
03180 void KMComposeWin::setEncryption( bool encrypt, bool setByUser )
03181 {
03182   if ( !mEncryptAction->isEnabled() )
03183     encrypt = false;
03184   // check if the user wants to encrypt messages to himself and if he defined
03185   // an encryption key for the current identity
03186   else if ( encrypt && encryptToSelf() && !mLastIdentityHasEncryptionKey ) {
03187     if ( setByUser )
03188       KMessageBox::sorry( this,
03189                           i18n("<qt><p>You have requested that messages be "
03190                    "encrypted to yourself, but the currently selected "
03191                    "identity does not define an (OpenPGP or S/MIME) "
03192                    "encryption key to use for this.</p>"
03193                                "<p>Please select the key(s) to use "
03194                                "in the identity configuration.</p>"
03195                                "</qt>"),
03196                           i18n("Undefined Encryption Key") );
03197     encrypt = false;
03198   }
03199 
03200   // make sure the mEncryptAction is in the right state
03201   mEncryptAction->setChecked( encrypt );
03202 
03203   // show the appropriate icon
03204   if ( encrypt )
03205     mEncryptAction->setIcon("encrypted");
03206   else
03207     mEncryptAction->setIcon("decrypted");
03208 
03209   // mark the attachments for (no) encryption
03210   if ( canSignEncryptAttachments() ) {
03211     for ( KMAtmListViewItem* entry =
03212             static_cast<KMAtmListViewItem*>( mAtmItemList.first() );
03213           entry;
03214           entry = static_cast<KMAtmListViewItem*>( mAtmItemList.next() ) )
03215       entry->setEncrypt( encrypt );
03216   }
03217 }
03218 
03219 
03220 //-----------------------------------------------------------------------------
03221 void KMComposeWin::slotSignToggled(bool on)
03222 {
03223 #ifdef KLEO_CHIASMUS
03224   if ( on && mEncryptBodyWithChiasmus ) {
03225     int ret = KMessageBox::warningContinueCancel( this,
03226         i18n("You have chosen to encrypt the body of your message using "
03227              "chiasmus. Signing such messages is currently not supported. Do you "
03228              "want to continue, which will disable encryption of the body (but not "
03229              "of attachments ) using chiasmus, or cancel, which will disable signing?"),
03230           i18n("Chiasmus Body Encryption Conflict" ) );
03231     if ( ret == KMessageBox::Cancel ) {
03232       mSignAction->setChecked( false );
03233       return;
03234     }
03235     slotEncryptBodyChiasmusToggled( false );
03236     mEncryptBodyChiasmusAction->setChecked( false );
03237   }
03238 #endif
03239   setSigning( on, true /* set by the user */ );
03240 }
03241 
03242 
03243 //-----------------------------------------------------------------------------
03244 void KMComposeWin::setSigning( bool sign, bool setByUser )
03245 {
03246   if ( !mSignAction->isEnabled() )
03247     sign = false;
03248 
03249   // check if the user defined a signing key for the current identity
03250   if ( sign && !mLastIdentityHasSigningKey ) {
03251     if ( setByUser )
03252       KMessageBox::sorry( this,
03253                           i18n("<qt><p>In order to be able to sign "
03254                                "this message you first have to "
03255                                "define the (OpenPGP or S/MIME) signing key "
03256                    "to use.</p>"
03257                                "<p>Please select the key to use "
03258                                "in the identity configuration.</p>"
03259                                "</qt>"),
03260                           i18n("Undefined Signing Key") );
03261     sign = false;
03262   }
03263 
03264   // make sure the mSignAction is in the right state
03265   mSignAction->setChecked( sign );
03266 
03267   // mark the attachments for (no) signing
03268   if ( canSignEncryptAttachments() ) {
03269     for ( KMAtmListViewItem* entry =
03270             static_cast<KMAtmListViewItem*>( mAtmItemList.first() );
03271           entry;
03272           entry = static_cast<KMAtmListViewItem*>( mAtmItemList.next() ) )
03273       entry->setSign( sign );
03274   }
03275 }
03276 
03277 
03278 //-----------------------------------------------------------------------------
03279 void KMComposeWin::slotWordWrapToggled(bool on)
03280 {
03281   if (on)
03282   {
03283     mEditor->setWordWrap( QMultiLineEdit::FixedColumnWidth );
03284     mEditor->setWrapColumnOrWidth(mLineBreak);
03285   }
03286   else
03287   {
03288     mEditor->setWordWrap( QMultiLineEdit::NoWrap );
03289   }
03290 }
03291 
03292 
03293 //-----------------------------------------------------------------------------
03294 void KMComposeWin::slotPrint()
03295 {
03296   mMessageWasModified = ( mEditor->isModified() || mEdtFrom->edited() ||
03297                           mEdtReplyTo->edited() || mEdtTo->edited() ||
03298                           mEdtCc->edited() || mEdtBcc->edited() ||
03299                           mEdtSubject->edited() || mAtmModified ||
03300                           ( mTransport->lineEdit() &&
03301                             mTransport->lineEdit()->edited() ) );
03302   connect( this, SIGNAL( applyChangesDone( bool ) ),
03303            this, SLOT( slotContinuePrint( bool ) ) );
03304   applyChanges( true );
03305 }
03306 
03307 void KMComposeWin::slotContinuePrint( bool rc )
03308 {
03309   disconnect( this, SIGNAL( applyChangesDone( bool ) ),
03310               this, SLOT( slotContinuePrint( bool ) ) );
03311 
03312   if( rc ) {
03313     if ( mComposedMessages.isEmpty() ) {
03314       kdDebug(5006) << "Composing the message failed." << endl;
03315       return;
03316     }
03317     KMCommand *command = new KMPrintCommand( this, mComposedMessages.first() );
03318     command->start();
03319     mEditor->setModified( mMessageWasModified );
03320   }
03321 }
03322 
03323 
03324 //----------------------------------------------------------------------------
03325 void KMComposeWin::doSend(int aSendNow, bool saveInDrafts)
03326 {
03327   if ( aSendNow && kmkernel->isOffline() ) {
03328     KMessageBox::information( this,
03329                               i18n("KMail is currently in offline mode,"
03330                                    "your messages will be kept in the outbox until you go online."),
03331                               i18n("Online/Offline"), "kmailIsOffline" );
03332     mSendNow = false;
03333   } else {
03334     mSendNow = aSendNow;
03335   }
03336 
03337   mSaveInDrafts = saveInDrafts;
03338 
03339   if (!saveInDrafts)
03340   {
03341     if ( from().isEmpty() ) {
03342       if ( !( mShowHeaders & HDR_FROM ) ) {
03343         mShowHeaders |= HDR_FROM;
03344         rethinkFields( false );
03345       }
03346       mEdtFrom->setFocus();
03347       KMessageBox::sorry( this,
03348                           i18n("You must enter your email address in the "
03349                                "From: field. You should also set your email "
03350                                "address for all identities, so that you do "
03351                                "not have to enter it for each message.") );
03352       return;
03353     }
03354     if ( to().isEmpty() )
03355     {
03356         if (  cc().isEmpty() && bcc().isEmpty()) {
03357           if ( mEdtTo ) mEdtTo->setFocus();
03358           KMessageBox::information( this,
03359                                 i18n("You must specify at least one receiver,"
03360                                      "either in the To: field or as CC or as BCC.") );
03361           return;
03362         }
03363         else {
03364                 if ( mEdtTo ) mEdtTo->setFocus();
03365                 int rc =
03366                             KMessageBox::questionYesNo( this,
03367                                                         i18n("To field is missing."
03368                                                               "Send message anyway?"),
03369                                                         i18n("No To: specified") );
03370                 if ( rc == KMessageBox::No ){
03371                    return;
03372                 }
03373         }
03374     }
03375 
03376     if (subject().isEmpty())
03377     {
03378         mEdtSubject->setFocus();
03379         int rc =
03380           KMessageBox::questionYesNo( this,
03381                                       i18n("You did not specify a subject. "
03382                                            "Send message anyway?"),
03383                                       i18n("No Subject Specified"),
03384                                       i18n("S&end as Is"),
03385                                       i18n("&Specify the Subject"),
03386                                       "no_subject_specified" );
03387         if( rc == KMessageBox::No )
03388         {
03389            return;
03390         }
03391     }
03392 
03393     if ( userForgotAttachment() )
03394       return;
03395   }
03396 
03397   KCursorSaver busy(KBusyPtr::busy());
03398   mMsg->setDateToday();
03399 
03400   // If a user sets up their outgoing messages preferences wrong and then
03401   // sends mail that gets 'stuck' in their outbox, they should be able to
03402   // rectify the problem by editing their outgoing preferences and
03403   // resending.
03404   // Hence this following conditional
03405   QString hf = mMsg->headerField("X-KMail-Transport");
03406   if ((mTransport->currentText() != mTransport->text(0)) ||
03407       (!hf.isEmpty() && (hf != mTransport->text(0))))
03408     mMsg->setHeaderField("X-KMail-Transport", mTransport->currentText());
03409 
03410   if( saveInDrafts )
03411     mMsg->setHeaderField("X-KMail-CryptoFormat", QString::number(cryptoMessageFormat()));
03412 
03413   mDisableBreaking = saveInDrafts;
03414 
03415   const bool neverEncrypt = ( saveInDrafts && mNeverEncryptWhenSavingInDrafts )
03416                            || mSigningAndEncryptionExplicitlyDisabled;
03417   connect( this, SIGNAL( applyChangesDone( bool ) ),
03418            SLOT( slotContinueDoSend( bool ) ) );
03419 
03420   if ( mEditor->textFormat() == Qt::RichText )
03421     mMsg->setHeaderField( "X-KMail-Markup", "true" );
03422   else
03423     mMsg->removeHeaderField( "X-KMail-Markup" );
03424   if ( mEditor->textFormat() == Qt::RichText && inlineSigningEncryptionSelected() ) {
03425     QString keepBtnText = mEncryptAction->isChecked() ?
03426       mSignAction->isChecked() ? i18n( "&Keep markup, do not sign/encrypt" )
03427                                : i18n( "&Keep markup, do not encrypt" )
03428       : i18n( "&Keep markup, do not sign" );
03429     QString yesBtnText = mEncryptAction->isChecked() ?
03430       mSignAction->isChecked() ? i18n("Sign/Encrypt (delete markup)")
03431       : i18n( "Encrypt (delete markup)" )
03432       : i18n( "Sign (delete markup)" );
03433     int ret = KMessageBox::warningYesNoCancel(this,
03434                                       i18n("<qt><p>Inline signing/encrypting of HTML messages is not possible;</p>"
03435                                            "<p>do you want to delete your markup?</p></qt>"),
03436                                            i18n("Sign/Encrypt Message?"),
03437                                            KGuiItem( yesBtnText ),
03438                                            KGuiItem( keepBtnText ) );
03439     if ( KMessageBox::Cancel == ret )
03440       return;
03441     if ( KMessageBox::No == ret ) {
03442       mEncryptAction->setChecked(false);
03443       mSignAction->setChecked(false);
03444     }
03445     else {
03446       toggleMarkup(false);
03447     }
03448   }
03449 
03450   if ( neverEncrypt && saveInDrafts ) {
03451       // we can't use the state of the mail itself, to remember the 
03452       // signing and encryption state, so let's add a header instead
03453     mMsg->setHeaderField( "X-KMail-SignatureActionEnabled", mSignAction->isChecked()? "true":"false" );
03454     mMsg->setHeaderField( "X-KMail-EncryptActionEnabled", mEncryptAction->isChecked()? "true":"false"  );
03455   } else {
03456     mMsg->removeHeaderField( "X-KMail-SignatureActionEnabled" );
03457     mMsg->removeHeaderField( "X-KMail-EncryptActionEnabled" );
03458   }
03459 
03460 
03461   kdDebug(5006) << "KMComposeWin::doSend() - calling applyChanges()"
03462                 << endl;
03463   applyChanges( neverEncrypt );
03464 }
03465 
03466 void KMComposeWin::slotContinueDoSend( bool sentOk )
03467 {
03468   kdDebug(5006) << "KMComposeWin::slotContinueDoSend( " << sentOk << " )"
03469                 << endl;
03470   disconnect( this, SIGNAL( applyChangesDone( bool ) ),
03471               this, SLOT( slotContinueDoSend( bool ) ) );
03472 
03473   if ( !sentOk ) {
03474     mDisableBreaking = false;
03475     return;
03476   }
03477   if (!mAutoDeleteMsg) mEditor->setModified(FALSE);
03478   mEdtFrom->setEdited(FALSE);
03479   mEdtReplyTo->setEdited(FALSE);
03480   mEdtTo->setEdited(FALSE);
03481   mEdtCc->setEdited(FALSE);
03482   mEdtBcc->setEdited(FALSE);
03483   mEdtSubject->setEdited(FALSE);
03484   if (mTransport->lineEdit())
03485     mTransport->lineEdit()->setEdited(FALSE);
03486   mAtmModified = FALSE;
03487 
03488   for ( QValueVector<KMMessage*>::iterator it = mComposedMessages.begin() ; it != mComposedMessages.end() ; ++it ) {
03489 
03490     // remove fields that contain no data (e.g. an empty Cc: or Bcc:)
03491     (*it)->cleanupHeader();
03492 
03493     // needed for imap
03494     (*it)->setComplete( true );
03495 
03496     if (mSaveInDrafts) {
03497       KMFolder* draftsFolder = 0, *imapDraftsFolder = 0;
03498       // get the draftsFolder
03499       if ( !(*it)->drafts().isEmpty() ) {
03500     draftsFolder = kmkernel->folderMgr()->findIdString( (*it)->drafts() );
03501     if ( draftsFolder == 0 )
03502       // This is *NOT* supposed to be "imapDraftsFolder", because a
03503       // dIMAP folder works like a normal folder
03504       draftsFolder = kmkernel->dimapFolderMgr()->findIdString( (*it)->drafts() );
03505     if ( draftsFolder == 0 )
03506       imapDraftsFolder = kmkernel->imapFolderMgr()->findIdString( (*it)->drafts() );
03507     if ( !draftsFolder && !imapDraftsFolder ) {
03508       const KPIM::Identity & id = kmkernel->identityManager()
03509         ->identityForUoidOrDefault( (*it)->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt() );
03510       KMessageBox::information(0, i18n("The custom drafts folder for identity "
03511                        "\"%1\" does not exist (anymore); "
03512                        "therefore, the default drafts folder "
03513                        "will be used.")
03514                    .arg( id.identityName() ) );
03515     }
03516       }
03517       if (imapDraftsFolder && imapDraftsFolder->noContent())
03518     imapDraftsFolder = 0;
03519 
03520       if ( draftsFolder == 0 ) {
03521     draftsFolder = kmkernel->draftsFolder();
03522       } else {
03523     draftsFolder->open();
03524       }
03525       kdDebug(5006) << "saveindrafts: drafts=" << draftsFolder->name() << endl;
03526       if (imapDraftsFolder)
03527     kdDebug(5006) << "saveindrafts: imapdrafts="
03528               << imapDraftsFolder->name() << endl;
03529 
03530       sentOk = !(draftsFolder->addMsg((*it)));
03531 
03532       //Ensure the drafts message is correctly and fully parsed
03533       draftsFolder->unGetMsg(draftsFolder->count() - 1);
03534       (*it) = draftsFolder->getMsg(draftsFolder->count() - 1);
03535 
03536       if (imapDraftsFolder) {
03537     // move the message to the imap-folder and highlight it
03538     imapDraftsFolder->moveMsg((*it));
03539     (static_cast<KMFolderImap*>(imapDraftsFolder->storage()))->getFolder();
03540       }
03541 
03542     } else {
03543       (*it)->setTo( KMMessage::expandAliases( to() ));
03544       (*it)->setCc( KMMessage::expandAliases( cc() ));
03545       if( !mComposer->originalBCC().isEmpty() )
03546     (*it)->setBcc( KMMessage::expandAliases( mComposer->originalBCC() ));
03547       QString recips = (*it)->headerField( "X-KMail-Recipients" );
03548       if( !recips.isEmpty() ) {
03549     (*it)->setHeaderField( "X-KMail-Recipients", KMMessage::expandAliases( recips ) );
03550       }
03551       (*it)->cleanupHeader();
03552       sentOk = kmkernel->msgSender()->send((*it), mSendNow);
03553     }
03554 
03555     if (!sentOk)
03556       return;
03557 
03558     *it = 0; // don't kill it later...
03559   }
03560 
03561   RecentAddresses::self( KMKernel::config() )->add( bcc() );
03562   RecentAddresses::self( KMKernel::config() )->add( cc() );
03563   RecentAddresses::self( KMKernel::config() )->add( to() );
03564 
03565   mAutoDeleteMsg = FALSE;
03566   mFolder = 0;
03567   cleanupAutoSave();
03568   close();
03569   return;
03570 }
03571 
03572 
03573 
03574 //----------------------------------------------------------------------------
03575 void KMComposeWin::slotSendLater()
03576 {
03577   if ( mEditor->checkExternalEditorFinished() )
03578     doSend( false );
03579 }
03580 
03581 
03582 //----------------------------------------------------------------------------
03583 void KMComposeWin::slotSaveDraft() {
03584   if ( mEditor->checkExternalEditorFinished() )
03585     doSend( false, true );
03586 }
03587 
03588 
03589 //----------------------------------------------------------------------------
03590 void KMComposeWin::slotSendNow() {
03591   if ( !mEditor->checkExternalEditorFinished() )
03592     return;
03593   if (mConfirmSend) {
03594     switch(KMessageBox::warningYesNoCancel(mMainWidget,
03595                                     i18n("About to send email..."),
03596                                     i18n("Send Confirmation"),
03597                                     i18n("Send &Now"),
03598                                     i18n("Send &Later"))) {
03599     case KMessageBox::Yes:        // send now
03600         doSend(TRUE);
03601       break;
03602     case KMessageBox::No:        // send later
03603         doSend(FALSE);
03604       break;
03605     case KMessageBox::Cancel:        // cancel
03606       break;
03607     default:
03608       ;    // whoa something weird happened here!
03609     }
03610     return;
03611   }
03612 
03613   doSend(TRUE);
03614 }
03615 
03616 
03617 //----------------------------------------------------------------------------
03618 void KMComposeWin::slotAppendSignature()
03619 {
03620     insertSignature();
03621 }
03622 
03623 //----------------------------------------------------------------------------
03624 void KMComposeWin::slotPrependSignature()
03625 {
03626     insertSignature( false );
03627 }
03628 
03629 //----------------------------------------------------------------------------
03630 void KMComposeWin::slotInsertSignatureAtCursor()
03631 {
03632     insertSignature( false, mEditor->currentLine() );
03633 }
03634 
03635 
03636 //----------------------------------------------------------------------------
03637 void KMComposeWin::insertSignature( bool append, int pos )
03638 {
03639   bool mod = mEditor->isModified();
03640 
03641   const KPIM::Identity & ident =
03642     kmkernel->identityManager()->identityForUoidOrDefault( mIdentity->currentIdentity() );
03643   mOldSigText = append? ident.signatureText() : ident.signature().rawText();
03644   if( !mOldSigText.isEmpty() )
03645   {
03646     mEditor->sync();
03647     if ( append ) {
03648        mEditor->append(mOldSigText);
03649     } else {
03650        mEditor->insertAt(mOldSigText, pos, 0);
03651     }
03652     mEditor->update();
03653     mEditor->setModified(mod);
03654     // for append and prepend, move the cursor to 0,0, for insertAt,
03655     // keep it in the same row, but move to first column
03656     mEditor->setCursorPosition( pos, 0 );
03657     if ( !append && pos == 0 )
03658       mEditor->setContentsPos( 0, 0 );
03659   }
03660 }
03661 
03662 
03663 //-----------------------------------------------------------------------------
03664 void KMComposeWin::slotHelp()
03665 {
03666   kapp->invokeHelp();
03667 }
03668 
03669 //-----------------------------------------------------------------------------
03670 void KMComposeWin::slotCleanSpace()
03671 {
03672   mEditor->cleanWhiteSpace();
03673 }
03674 
03675 //-----------------------------------------------------------------------------
03676 void KMComposeWin::slotToggleMarkup()
03677 {
03678  if ( markupAction->isChecked() ) {
03679     toolBar("htmlToolBar")->show();
03680    // markup will be toggled as soon as markup is actually used
03681    fontChanged( mEditor->currentFont().family() ); // set buttons in correct position
03682    fontAction->setFont( mEditor->currentFont().family() );
03683    fontSizeAction->setFontSize( mEditor->currentFont().pointSize() );
03684    mSaveFont = mEditor->currentFont();
03685  }
03686  else
03687    toggleMarkup(false);
03688 
03689 }
03690 //-----------------------------------------------------------------------------
03691 void KMComposeWin::toggleMarkup(bool markup)
03692 {
03693   if ( markup ) {
03694     if ( !mUseHTMLEditor ) {
03695     kdDebug(5006) << "setting RichText editor" << endl;
03696     mUseHTMLEditor = true; // set it directly to true. setColor hits another toggleMarkup
03697 
03698     // set all highlighted text caused by spelling back to black
03699     int paraFrom, indexFrom, paraTo, indexTo;
03700     mEditor->getSelection ( &paraFrom, &indexFrom, &paraTo, &indexTo);
03701     mEditor->selectAll();
03702     // save the buttonstates because setColor calls fontChanged
03703     bool _bold = textBoldAction->isChecked();
03704     bool _italic = textItalicAction->isChecked();
03705     mEditor->setColor(QColor(0,0,0));
03706     textBoldAction->setChecked(_bold);
03707     textItalicAction->setChecked(_italic);
03708     mEditor->setSelection ( paraFrom, indexFrom, paraTo, indexTo);
03709 
03710     mEditor->setTextFormat(Qt::RichText);
03711     mEditor->setModified(true);
03712     markupAction->setChecked(true);
03713     toolBar( "htmlToolBar" )->show();
03714     mEditor->deleteAutoSpellChecking();
03715     mAutoSpellCheckingAction->setChecked(false);
03716     slotAutoSpellCheckingToggled(false);
03717    }
03718   }
03719   else if ( mUseHTMLEditor ) {
03720     kdDebug(5006) << "setting PlainText editor" << endl;
03721     mUseHTMLEditor = false;
03722     mEditor->setTextFormat(Qt::PlainText);
03723     QString text = mEditor->text();
03724     mEditor->setText(text); // otherwise the text still looks formatted
03725     mEditor->setModified(true);
03726     toolBar("htmlToolBar")->hide();
03727     mEditor->initializeAutoSpellChecking( mDictionaryCombo->spellConfig());
03728     slotAutoSpellCheckingToggled(true);
03729   }
03730   else if ( !markup && !mUseHTMLEditor )
03731     {
03732       toolBar("htmlToolBar")->hide();
03733     }
03734 }
03735 
03736 void KMComposeWin::slotSubjectTextSpellChecked()
03737 {
03738   mSubjectTextWasSpellChecked = true;
03739 }
03740 
03741 //-----------------------------------------------------------------------------
03742 void KMComposeWin::slotAutoSpellCheckingToggled( bool on )
03743 {
03744   if ( mEditor->autoSpellChecking(on) == -1 )
03745     mAutoSpellCheckingAction->setChecked(false); // set it to false again
03746 }
03747 //-----------------------------------------------------------------------------
03748 void KMComposeWin::slotSpellcheck()
03749 {
03750   if (mSpellCheckInProgress) return;
03751   mSubjectTextWasSpellChecked = false;
03752   mSpellCheckInProgress=TRUE;
03753   /*
03754     connect (mEditor, SIGNAL (spellcheck_progress (unsigned)),
03755     this, SLOT (spell_progress (unsigned)));
03756     */
03757 
03758   mEditor->spellcheck();
03759 }
03760 
03761 
03762 //-----------------------------------------------------------------------------
03763 void KMComposeWin::slotSpellcheckDone(int result)
03764 {
03765   kdDebug(5006) << "spell check complete: result = " << result << endl;
03766   mSpellCheckInProgress=FALSE;
03767 
03768   switch( result )
03769   {
03770     case KS_CANCEL:
03771       statusBar()->changeItem(i18n(" Spell check canceled."),0);
03772       break;
03773     case KS_STOP:
03774       statusBar()->changeItem(i18n(" Spell check stopped."),0);
03775       break;
03776     default:
03777       statusBar()->changeItem(i18n(" Spell check complete."),0);
03778       break;
03779   }
03780   QTimer::singleShot( 2000, this, SLOT(slotSpellcheckDoneClearStatus()) );
03781 }
03782 
03783 void KMComposeWin::slotSpellcheckDoneClearStatus()
03784 {
03785   statusBar()->changeItem("", 0);
03786 }
03787 
03788 
03789 //-----------------------------------------------------------------------------
03790 void KMComposeWin::focusNextPrevEdit(const QWidget* aCur, bool aNext)
03791 {
03792   QWidget* cur;
03793 
03794   if (!aCur)
03795   {
03796     cur=mEdtList.last();
03797   }
03798   else
03799   {
03800     for (cur=mEdtList.first(); aCur!=cur && cur; cur=mEdtList.next())
03801       ;
03802     if (!cur) return;
03803     if (aNext) cur = mEdtList.next();
03804     else cur = mEdtList.prev();
03805   }
03806   if (cur) cur->setFocus();
03807   else if (aNext) mEditor->setFocus(); //Key up from first doea nothing (sven)
03808 }
03809 
03810 //-----------------------------------------------------------------------------
03811 void KMComposeWin::slotIdentityChanged(uint uoid)
03812 {
03813   const KPIM::Identity & ident =
03814     kmkernel->identityManager()->identityForUoid( uoid );
03815   if ( ident.isNull() ) return;
03816 
03817   //Turn on/off signature actions if identity has no signature.
03818   slotUpdateSignatureActions();
03819 
03820   if(!ident.fullEmailAddr().isNull())
03821     mEdtFrom->setText(ident.fullEmailAddr());
03822   // make sure the From field is shown if it's empty
03823   if ( from().isEmpty() )
03824     mShowHeaders |= HDR_FROM;
03825   mEdtReplyTo->setText(ident.replyToAddr());
03826   // don't overwrite the BCC field when the user has edited it and the
03827   // BCC field of the new identity is empty
03828   if( !mEdtBcc->edited() || !ident.bcc().isEmpty() )
03829     mEdtBcc->setText(ident.bcc());
03830   // make sure the BCC field is shown because else it's ignored
03831   if (! ident.bcc().isEmpty()) {
03832     mShowHeaders |= HDR_BCC;
03833   }
03834   if (ident.organization().isEmpty())
03835     mMsg->removeHeaderField("Organization");
03836   else
03837     mMsg->setHeaderField("Organization", ident.organization());
03838 
03839   if (!mBtnTransport->isChecked()) {
03840     QString transp = ident.transport();
03841     if (transp.isEmpty())
03842     {
03843       mMsg->removeHeaderField("X-KMail-Transport");
03844       transp = mTransport->text(0);
03845     }
03846     else
03847       mMsg->setHeaderField("X-KMail-Transport", transp);
03848     bool found = false;
03849     int i;
03850     for (i = 0; i < mTransport->count(); i++) {
03851       if (mTransport->text(i) == transp) {
03852         found = true;
03853         mTransport->setCurrentItem(i);
03854         break;
03855       }
03856     }
03857     if (found == false) {
03858       if (i == mTransport->maxCount()) mTransport->setMaxCount(i + 1);
03859       mTransport->insertItem(transp,i);
03860       mTransport->setCurrentItem(i);
03861     }
03862   }
03863 
03864   mDictionaryCombo->setCurrentByDictionary( ident.dictionary() );
03865 
03866   if ( !mBtnFcc->isChecked() )
03867   {
03868     if ( ident.fcc().isEmpty() )
03869       mFcc->setFolder( kmkernel->sentFolder() );
03870     else
03871       setFcc( ident.fcc() );
03872   }
03873 
03874   QString edtText = mEditor->text();
03875   bool appendNewSig = true;
03876   const bool prepended = GlobalSettings::prependSignatures();
03877   // try to truncate the old sig
03878   if( !mOldSigText.isEmpty() )
03879   {
03880     if( !prepended && edtText.endsWith( mOldSigText ) )
03881       edtText.truncate( edtText.length() - mOldSigText.length() );
03882     else if ( prepended && edtText.find( mOldSigText ) != -1 ) {
03883       edtText.remove(  mOldSigText );
03884     }
03885     else
03886       appendNewSig = false;
03887   }
03888   // now append the new sig
03889   mOldSigText = prepended ? ident.signature().rawText() : ident.signatureText();
03890   if( appendNewSig )
03891   {
03892     if( !mOldSigText.isEmpty() && mAutoSign ) {
03893       if ( prepended ) {
03894         edtText.prepend( mOldSigText );
03895       } else {
03896         edtText.append( mOldSigText );
03897       }
03898     }
03899     mEditor->setText( edtText );
03900   }
03901 
03902   // disable certain actions if there is no PGP user identity set
03903   // for this profile
03904   bool bNewIdentityHasSigningKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty();
03905   bool bNewIdentityHasEncryptionKey = !ident.pgpSigningKey().isEmpty() || !ident.smimeSigningKey().isEmpty();
03906   mAttachMPK->setEnabled( Kleo::CryptoBackendFactory::instance()->openpgp() &&
03907               !ident.pgpEncryptionKey().isEmpty() );
03908   // save the state of the sign and encrypt button
03909   if ( !bNewIdentityHasEncryptionKey && mLastIdentityHasEncryptionKey ) {
03910     mLastEncryptActionState = mEncryptAction->isChecked();
03911     setEncryption( false );
03912   }
03913   if ( !bNewIdentityHasSigningKey && mLastIdentityHasSigningKey ) {
03914     mLastSignActionState = mSignAction->isChecked();
03915     setSigning( false );
03916   }
03917   // restore the last state of the sign and encrypt button
03918   if ( bNewIdentityHasEncryptionKey && !mLastIdentityHasEncryptionKey )
03919       setEncryption( mLastEncryptActionState );
03920   if ( bNewIdentityHasSigningKey && !mLastIdentityHasSigningKey )
03921     setSigning( mLastSignActionState );
03922 
03923   mLastIdentityHasSigningKey = bNewIdentityHasSigningKey;
03924   mLastIdentityHasEncryptionKey = bNewIdentityHasEncryptionKey;
03925 
03926   mEditor->setModified(TRUE);
03927   mId = uoid;
03928 
03929   // make sure the From and BCC fields are shown if necessary
03930   rethinkFields( false );
03931 }
03932 
03933 //-----------------------------------------------------------------------------
03934 void KMComposeWin::slotSpellcheckConfig()
03935 {
03936   KWin kwin;
03937   QTabDialog qtd (this, "tabdialog", true);
03938   KSpellConfig mKSpellConfig (&qtd);
03939 
03940   qtd.addTab (&mKSpellConfig, i18n("Spellchecker"));
03941   qtd.setCancelButton ();
03942 
03943   kwin.setIcons (qtd.winId(), kapp->icon(), kapp->miniIcon());
03944   qtd.setCancelButton(KStdGuiItem::cancel().text());
03945   qtd.setOkButton(KStdGuiItem::ok().text());
03946 
03947   if (qtd.exec())
03948     mKSpellConfig.writeGlobalSettings();
03949 }
03950 
03951 //----------------------------------------------------------------------------
03952 void KMComposeWin::slotMenuTextCompletion()
03953 {
03954    //Get the currently selected item in the sub menu
03955    int nItem = mTextCompletionAction->currentItem();
03956 
03957    //Set proper value for that item (NOTE the coversion, very important).
03958    KConfig *config = KMKernel::config();
03959    KConfigGroup composer( config, "Composer" );
03960    composer.writeEntry( "Completion Mode", KMGlobalNS::completionModes[ nItem ].mode );
03961    config->sync();
03962 
03963    //trigger this to read config and set the lineEdits automatically.
03964    slotConfigChanged();
03965 
03966 }
03967 
03968 //-----------------------------------------------------------------------------
03969 void KMComposeWin::slotConfigureAddressCompletion()
03970 {
03971   kdDebug(5006) << k_funcinfo << endl;
03972   KPIM::LdapSearch ls;
03973   KPIM::CompletionOrderEditor editor( &ls, this );
03974   editor.exec();
03975 }
03976 
03977 //-----------------------------------------------------------------------------
03978 void KMComposeWin::slotStatusMessage(const QString &message)
03979 {
03980     statusBar()->changeItem( message, 0 );
03981 }
03982 
03983 void KMComposeWin::slotEditToolbars()
03984 {
03985   saveMainWindowSettings(KMKernel::config(), "Composer");
03986   KEditToolbar dlg(guiFactory(), this);
03987 
03988   connect( &dlg, SIGNAL(newToolbarConfig()),
03989            SLOT(slotUpdateToolbars()) );
03990 
03991   dlg.exec();
03992 }
03993 
03994 void KMComposeWin::slotUpdateToolbars()
03995 {
03996   createGUI("kmcomposerui.rc");
03997   applyMainWindowSettings(KMKernel::config(), "Composer");
03998 }
03999 
04000 void KMComposeWin::slotEditKeys()
04001 {
04002   KKeyDialog::configure( actionCollection(),
04003                          false /*don't allow one-letter shortcuts*/
04004                          );
04005 }
04006 
04007 void KMComposeWin::setReplyFocus( bool hasMessage )
04008 {
04009   mEditor->setFocus();
04010   if ( hasMessage )
04011     mEditor->setCursorPosition( 1, 0 );
04012 }
04013 
04014 void KMComposeWin::setFocusToSubject()
04015 {
04016   mEdtSubject->setFocus();
04017 }
04018 
04019 int KMComposeWin::autoSaveInterval() const
04020 {
04021   return GlobalSettings::self()->autosaveInterval() * 1000 * 60;
04022 }
04023 
04024 void KMComposeWin::initAutoSave()
04025 {
04026   kdDebug(5006) << k_funcinfo << endl;
04027   // make sure the autosave folder exists
04028   KMFolderMaildir::createMaildirFolders( KMKernel::localDataPath() + "autosave" );
04029   if ( mAutoSaveFilename.isEmpty() ) {
04030     mAutoSaveFilename = KMFolderMaildir::constructValidFileName();
04031   }
04032 
04033   updateAutoSave();
04034 }
04035 
04036 void KMComposeWin::updateAutoSave()
04037 {
04038   if ( autoSaveInterval() == 0 ) {
04039     delete mAutoSaveTimer; mAutoSaveTimer = 0;
04040   }
04041   else {
04042     if ( !mAutoSaveTimer ) {
04043       mAutoSaveTimer = new QTimer( this );
04044       connect( mAutoSaveTimer, SIGNAL( timeout() ),
04045                this, SLOT( autoSaveMessage() ) );
04046     }
04047     mAutoSaveTimer->start( autoSaveInterval() );
04048   }
04049 }
04050 
04051 void KMComposeWin::setAutoSaveFilename( const QString & filename )
04052 {
04053   if ( !mAutoSaveFilename.isEmpty() )
04054     KMFolderMaildir::removeFile( KMKernel::localDataPath() + "autosave",
04055                                  mAutoSaveFilename );
04056   mAutoSaveFilename = filename;
04057 }
04058 
04059 void KMComposeWin::cleanupAutoSave()
04060 {
04061   delete mAutoSaveTimer; mAutoSaveTimer = 0;
04062   if ( !mAutoSaveFilename.isEmpty() ) {
04063     kdDebug(5006) << k_funcinfo << "deleting autosave file "
04064                   << mAutoSaveFilename << endl;
04065     KMFolderMaildir::removeFile( KMKernel::localDataPath() + "autosave",
04066                                  mAutoSaveFilename );
04067     mAutoSaveFilename = QString();
04068   }
04069 }
04070 
04071 
04072 
04073 void KMComposeWin::slotCompletionModeChanged( KGlobalSettings::Completion mode)
04074 {
04075     KConfig *config = KMKernel::config();
04076     KConfigGroupSaver cs( config, "Composer" );
04077     config->writeEntry( "Completion Mode", (int) mode );
04078     config->sync(); // maybe not?
04079 
04080     // sync all the lineedits to the same completion mode
04081     mEdtFrom->setCompletionMode( mode );
04082     mEdtReplyTo->setCompletionMode( mode );
04083     mEdtTo->setCompletionMode( mode );
04084     mEdtCc->setCompletionMode( mode );
04085     mEdtBcc->setCompletionMode( mode );
04086 
04087     // sync the Text Completion submenu if lineEdits are changed.
04088     for ( int i=0; i < KMGlobalNS::numCompletionModes; ++i ) {
04089       if ( KMGlobalNS::completionModes[i].mode == mode )
04090        mTextCompletionAction->setCurrentItem( i );
04091     }
04092 
04093 }
04094 
04095 void KMComposeWin::slotConfigChanged()
04096 {
04097     readConfig();
04098     updateAutoSave();
04099 }
04100 
04101 /*
04102 * checks if the drafts-folder has been deleted
04103 * that is not nice so we set the system-drafts-folder
04104 */
04105 void KMComposeWin::slotFolderRemoved(KMFolder* folder)
04106 {
04107         if ( (mFolder) && (folder->idString() == mFolder->idString()) )
04108         {
04109                 mFolder = kmkernel->draftsFolder();
04110                 kdDebug(5006) << "restoring drafts to " << mFolder->idString() << endl;
04111         }
04112         if (mMsg) mMsg->setParent(0);
04113 }
04114 
04115 
04116 
04117 void KMComposeWin::slotSetAlwaysSend( bool bAlways )
04118 {
04119     mAlwaysSend = bAlways;
04120 }
04121 
04122 void KMComposeWin::slotListAction( const QString& style )
04123 {
04124     toggleMarkup(true);
04125     if ( style == i18n( "Standard" ) )
04126        mEditor->setParagType( QStyleSheetItem::DisplayBlock, QStyleSheetItem::ListDisc );
04127     else if ( style == i18n( "Bulleted List (Disc)" ) )
04128        mEditor->setParagType( QStyleSheetItem::DisplayListItem, QStyleSheetItem::ListDisc );
04129     else if ( style == i18n( "Bulleted List (Circle)" ) )
04130        mEditor->setParagType( QStyleSheetItem::DisplayListItem, QStyleSheetItem::ListCircle );
04131     else if ( style == i18n( "Bulleted List (Square)" ) )
04132        mEditor->setParagType( QStyleSheetItem::DisplayListItem, QStyleSheetItem::ListSquare );
04133     else if ( style == i18n( "Ordered List (Decimal)" ))
04134        mEditor->setParagType( QStyleSheetItem::DisplayListItem, QStyleSheetItem::ListDecimal );
04135     else if ( style == i18n( "Ordered List (Alpha lower)" ) )
04136        mEditor->setParagType( QStyleSheetItem::DisplayListItem, QStyleSheetItem::ListLowerAlpha );
04137     else if ( style == i18n( "Ordered List (Alpha upper)" ) )
04138        mEditor->setParagType( QStyleSheetItem::DisplayListItem, QStyleSheetItem::ListUpperAlpha );
04139     mEditor->viewport()->setFocus();
04140 }
04141 
04142 void KMComposeWin::slotFontAction( const QString& font)
04143 {
04144     toggleMarkup(true);
04145     mEditor->QTextEdit::setFamily( font );
04146     mEditor->viewport()->setFocus();
04147 }
04148 
04149 void KMComposeWin::slotSizeAction( int size )
04150 {
04151     toggleMarkup(true);
04152     mEditor->setPointSize( size );
04153     mEditor->viewport()->setFocus();
04154 }
04155 
04156 void KMComposeWin::slotAlignLeft()
04157 {
04158     toggleMarkup(true);
04159     mEditor->QTextEdit::setAlignment( AlignLeft );
04160 }
04161 
04162 void KMComposeWin::slotAlignCenter()
04163 {
04164     toggleMarkup(true);
04165     mEditor->QTextEdit::setAlignment( AlignHCenter );
04166 }
04167 
04168 void KMComposeWin::slotAlignRight()
04169 {
04170     toggleMarkup(true);
04171     mEditor->QTextEdit::setAlignment( AlignRight );
04172 }
04173 
04174 void KMComposeWin::slotTextBold()
04175 {
04176     toggleMarkup(true);
04177     mEditor->QTextEdit::setBold( textBoldAction->isChecked() );
04178 }
04179 
04180 void KMComposeWin::slotTextItalic()
04181 {
04182     toggleMarkup(true);
04183     mEditor->QTextEdit::setItalic( textItalicAction->isChecked() );
04184 }
04185 
04186 void KMComposeWin::slotTextUnder()
04187 {
04188     toggleMarkup(true);
04189     mEditor->QTextEdit::setUnderline( textUnderAction->isChecked() );
04190 }
04191 
04192 void KMComposeWin::slotFormatReset()
04193 {
04194   mEditor->setColor(mForeColor);
04195   mEditor->setCurrentFont( mSaveFont ); // fontChanged is called now
04196 }
04197 void KMComposeWin::slotTextColor()
04198 {
04199   QColor color = mEditor->color();
04200 
04201   if ( KColorDialog::getColor( color, this ) ) {
04202     toggleMarkup(true);
04203     mEditor->setColor( color );
04204   }
04205 }
04206 
04207 void KMComposeWin::fontChanged( const QFont &f )
04208 {
04209   QFontDatabase *fontdb = new QFontDatabase();
04210 
04211   if ( fontdb->bold(f.family(), "Bold") ) {
04212     textBoldAction->setChecked( f.bold() );
04213     textBoldAction->setEnabled(true);
04214   }
04215   else
04216     textBoldAction->setEnabled(false);
04217 
04218   if ( fontdb->italic(f.family(), "Italic") ) {
04219     textItalicAction->setChecked( f.italic() );
04220     textItalicAction->setEnabled(true);
04221   }
04222   else
04223     textItalicAction->setEnabled(false);
04224 
04225   textUnderAction->setChecked( f.underline() );
04226 
04227   fontAction->setFont( f.family() );
04228   fontSizeAction->setFontSize( f.pointSize() );
04229   delete fontdb;
04230 }
04231 
04232 void KMComposeWin::alignmentChanged( int a )
04233 {
04234     //toggleMarkup();
04235     alignLeftAction->setChecked( ( a == AlignAuto ) || ( a & AlignLeft ) );
04236     alignCenterAction->setChecked( ( a & AlignHCenter ) );
04237     alignRightAction->setChecked( ( a & AlignRight ) );
04238 }
04239 
04240 namespace {
04241   class KToggleActionResetter {
04242     KToggleAction * mAction;
04243     bool mOn;
04244   public:
04245     KToggleActionResetter( KToggleAction * action, bool on )
04246       : mAction( action ),  mOn( on ) {}
04247     ~KToggleActionResetter() {
04248       if ( mAction )
04249         mAction->setChecked( mOn );
04250     }
04251     void disable() { mAction = 0; }
04252   };
04253 }
04254 
04255 
04256 void KMComposeWin::slotEncryptBodyChiasmusToggled( bool on ) {
04257 #ifdef KLEO_CHIASMUS
04258     Q_ASSERT( mEncryptChiasmusAction->isEnabled() );
04259     if ( on ) {
04260       // FIXME remove once body encryption is fixed
04261       // user wants to enable body chiamsus encryption, which means
04262       // we currently need to disable signing and encryption using the
04263       // other mechanisms. Warn the user.
04264       if ( mEncryptAction->isChecked() || mSignAction->isChecked() ) {
04265         int ret = KMessageBox::warningContinueCancel( this,
04266             i18n("It is currently not possible to use chiasmus body encryption "
04267               "with signed or otherwise encrypted messages. Do you want to "
04268               "continue with chiasmus body encryption and disable other signature "
04269               "and encryption methods, or cancel, which will not encrypt the body "
04270               "using chiasmus?" ),
04271             i18n("Chiasmus body encryption not possible") );
04272         if ( ret == KMessageBox::Continue ) {
04273           setSigning( false, true );
04274           setEncryption( false, true );
04275         } else {
04276           mEncryptBodyChiasmusAction->setEnabled( false ); // reset
04277           return;
04278         }
04279 
04280       }
04281     }
04282     mEncryptBodyWithChiasmus = on;
04283 #endif
04284 }
04285 
04286 void KMComposeWin::slotEncryptChiasmusToggled( bool on ) {
04287 #ifdef KLEO_CHIASMUS
04288     mEncryptWithChiasmus = false;
04289     mEncryptBodyChiasmusAction->setEnabled( on );
04290 #endif
04291 
04292   if ( !on )
04293     return;
04294 
04295 #ifdef KLEO_CHIASMUS
04296   KToggleActionResetter resetter( mEncryptChiasmusAction, false );
04297 #endif
04298 
04299 #ifndef KLEO_CHIASMUS
04300 
04301   KMessageBox::information( this, i18n( "This version of KMail was not compiled "
04302                                         "with support for Chiasmus. You might want "
04303                                         "to recompile KMail with --enable-chiasmus." ),
04304                             i18n( "Missing Chiasmus Support" ) );
04305   return;
04306 
04307 #else
04308 
04309   const Kleo::CryptoBackend::Protocol * chiasmus =
04310     Kleo::CryptoBackendFactory::instance()->protocol( "Chiasmus" );
04311 
04312   if ( !chiasmus ) {
04313     const QString msg = Kleo::CryptoBackendFactory::instance()->knowsAboutProtocol( "Chiasmus" )
04314       ? i18n( "Please configure a Crypto Backend to use for "
04315               "Chiasmus encryption first.\n"
04316               "You can do this in the Crypto Backends tab of "
04317               "the configure dialog's Security page." )
04318       : i18n( "It looks as though libkleopatra was compiled without "
04319               "Chiasmus support. You might want to recompile "
04320               "libkleopatra with --enable-chiasmus.");
04321     KMessageBox::information( this, msg, i18n("No Chiasmus Backend Configured" ) );
04322     return;
04323   }
04324 
04325   std::auto_ptr<Kleo::SpecialJob> job( chiasmus->specialJob( "x-obtain-keys", QMap<QString,QVariant>() ) );
04326   if ( !job.get() ) {
04327     const QString msg = i18n( "Chiasmus backend does not offer the "
04328                               "\"x-obtain-keys\" function. Please report this bug." );
04329     KMessageBox::error( this, msg, i18n( "Chiasmus Backend Error" ) );
04330     return;
04331   }
04332 
04333   if ( job->exec() ) {
04334     job->showErrorDialog( this, i18n( "Chiasmus Backend Error" ) );
04335     return;
04336   }
04337 
04338   const QVariant result = job->property( "result" );
04339   if ( result.type() != QVariant::StringList ) {
04340     const QString msg = i18n( "Unexpected return value from Chiasmus backend: "
04341                               "The \"x-obtain-keys\" function did not return a "
04342                               "string list. Please report this bug." );
04343     KMessageBox::error( this, msg, i18n( "Chiasmus Backend Error" ) );
04344     return;
04345   }
04346 
04347   const QStringList keys = result.toStringList();
04348   if ( keys.empty() ) {
04349     const QString msg = i18n( "No keys have been found. Please check that a "
04350                               "valid key path has been set in the Chiasmus "
04351                               "configuration." );
04352     KMessageBox::information( this, msg, i18n( "No Chiasmus Keys Found" ) );
04353     return;
04354   }
04355 
04356   ChiasmusKeySelector selectorDlg( this, i18n( "Chiasmus Encryption Key Selection" ),
04357                                    keys, mChiasmusKey, mChiasmusOptions );
04358   if ( selectorDlg.exec() != QDialog::Accepted )
04359     return;
04360 
04361   mChiasmusOptions = selectorDlg.options();
04362   mChiasmusKey = selectorDlg.key();
04363   assert( !mChiasmusKey.isEmpty() );
04364   mEncryptWithChiasmus = true;
04365   resetter.disable();
04366 #endif // KLEO_CHIASMUS
04367 }
04368 
04369 
04370 void KMComposeWin::slotUpdateSignatureActions()
04371 {
04372   //Check if an identity has signature or not and turn on/off actions in the
04373   //edit menu accordingly.
04374   const KPIM::Identity & ident =
04375     kmkernel->identityManager()->identityForUoidOrDefault( mIdentity->currentIdentity() );
04376   QString sig = ident.signatureText();
04377 
04378   if ( sig.isEmpty() ) {
04379      mAppendSignatureAction->setEnabled( false );
04380      mPrependSignatureAction->setEnabled( false );
04381      mInsertSignatureAction->setEnabled( false );
04382   }
04383   else {
04384       mAppendSignatureAction->setEnabled( true );
04385       mPrependSignatureAction->setEnabled( true );
04386       mInsertSignatureAction->setEnabled( true );
04387   }
04388 }
04389 
04390 void KMEdit::contentsDragEnterEvent(QDragEnterEvent *e)
04391 {
04392     if (e->provides(MailListDrag::format()))
04393         e->accept(true);
04394     else
04395         return KEdit::contentsDragEnterEvent(e);
04396 }
04397 
04398 void KMEdit::contentsDragMoveEvent(QDragMoveEvent *e)
04399 {
04400     if (e->provides(MailListDrag::format()))
04401         e->accept();
04402     else
04403         return KEdit::contentsDragMoveEvent(e);
04404 }
04405 
04406 void KMEdit::keyPressEvent( QKeyEvent* e )
04407 {
04408     if( e->key() == Key_Return ) {
04409         int line, col;
04410         getCursorPosition( &line, &col );
04411         QString lineText = text( line );
04412         // returns line with additional trailing space (bug in Qt?), cut it off
04413         lineText.truncate( lineText.length() - 1 );
04414         // special treatment of quoted lines only if the cursor is neither at
04415         // the begin nor at the end of the line
04416         if( ( col > 0 ) && ( col < int( lineText.length() ) ) ) {
04417             bool isQuotedLine = false;
04418             uint bot = 0; // bot = begin of text after quote indicators
04419             while( bot < lineText.length() ) {
04420                 if( ( lineText[bot] == '>' ) || ( lineText[bot] == '|' ) ) {
04421                     isQuotedLine = true;
04422                     ++bot;
04423                 }
04424                 else if( lineText[bot].isSpace() ) {
04425                     ++bot;
04426                 }
04427                 else {
04428                     break;
04429                 }
04430             }
04431 
04432             KEdit::keyPressEvent( e );
04433 
04434             // duplicate quote indicators of the previous line before the new
04435             // line if the line actually contained text (apart from the quote
04436             // indicators) and the cursor is behind the quote indicators
04437             if( isQuotedLine
04438                 && ( bot != lineText.length() )
04439                 && ( col >= int( bot ) ) ) {
04440 
04441         // The cursor position might have changed unpredictably if there was selected
04442         // text which got replaced by a new line, so we query it again:
04443         getCursorPosition( &line, &col );
04444                 QString newLine = text( line );
04445                 // remove leading white space from the new line and instead
04446                 // add the quote indicators of the previous line
04447                 unsigned int leadingWhiteSpaceCount = 0;
04448                 while( ( leadingWhiteSpaceCount < newLine.length() )
04449                        && newLine[leadingWhiteSpaceCount].isSpace() ) {
04450                     ++leadingWhiteSpaceCount;
04451                 }
04452                 newLine = newLine.replace( 0, leadingWhiteSpaceCount,
04453                                            lineText.left( bot ) );
04454                 removeParagraph( line );
04455                 insertParagraph( newLine, line );
04456                 // place the cursor at the begin of the new line since
04457                 // we assume that the user split the quoted line in order
04458                 // to add a comment to the first part of the quoted line
04459                 setCursorPosition( line, 0 );
04460             }
04461         }
04462         else
04463             KEdit::keyPressEvent( e );
04464     }
04465     else
04466         KEdit::keyPressEvent( e );
04467 }
04468 
04469 void KMEdit::contentsDropEvent(QDropEvent *e)
04470 {
04471     if (e->provides(MailListDrag::format())) {
04472         // Decode the list of serial numbers stored as the drag data
04473         QByteArray serNums;
04474         MailListDrag::decode( e, serNums );
04475         QBuffer serNumBuffer(serNums);
04476         serNumBuffer.open(IO_ReadOnly);
04477         QDataStream serNumStream(&serNumBuffer);
04478         unsigned long serNum;
04479         KMFolder *folder = 0;
04480         int idx;
04481         QPtrList<KMMsgBase> messageList;
04482         while (!serNumStream.atEnd()) {
04483             KMMsgBase *msgBase = 0;
04484             serNumStream >> serNum;
04485             kmkernel->msgDict()->getLocation(serNum, &folder, &idx);
04486             if (folder)
04487                 msgBase = folder->getMsgBase(idx);
04488             if (msgBase)
04489                 messageList.append( msgBase );
04490         }
04491         serNumBuffer.close();
04492         uint identity = folder ? folder->identity() : 0;
04493         KMCommand *command =
04494             new KMForwardAttachedCommand(mComposer, messageList,
04495                                          identity, mComposer);
04496         command->start();
04497     }
04498     else if( KURLDrag::canDecode( e ) ) {
04499         KURL::List urlList;
04500         if( KURLDrag::decode( e, urlList ) ) {
04501             for( KURL::List::Iterator it = urlList.begin();
04502                  it != urlList.end(); ++it ) {
04503                 mComposer->addAttach( *it );
04504             }
04505         }
04506     }
04507     else {
04508         return KEdit::contentsDropEvent(e);
04509     }
04510 }
04511 
04512 //=============================================================================
04513 //
04514 //   Class  KMAtmListViewItem
04515 //
04516 //=============================================================================
04517 
04518 KMAtmListViewItem::KMAtmListViewItem(QListView *parent) :
04519   QObject(), QListViewItem( parent )
04520 {
04521 
04522   mCBSignEnabled = false;
04523   mCBEncryptEnabled = false;
04524 
04525   mListview = parent;
04526   mCBEncrypt = new QCheckBox(mListview->viewport());
04527   mCBSign = new QCheckBox(mListview->viewport());
04528 
04529   mCBEncrypt->hide();
04530   mCBSign->hide();
04531 }
04532 
04533 KMAtmListViewItem::~KMAtmListViewItem()
04534 {
04535   delete mCBEncrypt;
04536   mCBEncrypt = 0;
04537   delete mCBSign;
04538   mCBSign = 0;
04539 }
04540 
04541 void KMAtmListViewItem::paintCell( QPainter * p, const QColorGroup & cg,
04542                                   int column, int width, int align )
04543 {
04544   // this is also called for the encrypt/sign columns to assure that the
04545   // background is cleared
04546   QListViewItem::paintCell( p, cg, column, width, align );
04547   if( 4 == column || 5 == column ) {
04548     QRect r = mListview->itemRect( this );
04549     if ( !r.size().isValid() ) {
04550         mListview->ensureItemVisible( this );
04551         mListview->repaintContents( FALSE );
04552         r = mListview->itemRect( this );
04553     }
04554     int colWidth = mListview->header()->sectionSize( column );
04555     r.setX( mListview->header()->sectionPos( column )
04556             - mListview->header()->offset()
04557             + colWidth / 2
04558             - r.height() / 2
04559             - 1 );
04560     r.setY( r.y() + 1 );
04561     r.setWidth(  r.height() - 2 );
04562     r.setHeight( r.height() - 2 );
04563     r = QRect( mListview->viewportToContents( r.topLeft() ), r.size() );
04564 
04565     QCheckBox* cb = (4 == column) ? mCBEncrypt : mCBSign;
04566     cb->resize( r.size() );
04567     mListview->moveChild( cb, r.x(), r.y() );
04568 
04569     QColor bg;
04570     if (isSelected())
04571       bg = cg.highlight();
04572     else
04573       bg = cg.base();
04574 
04575     bool enabled = (4 == column) ? mCBEncryptEnabled : mCBSignEnabled;
04576     cb->setPaletteBackgroundColor(bg);
04577     if (enabled) cb->show();
04578   }
04579 }
04580 
04581 void KMAtmListViewItem::enableCryptoCBs(bool on)
04582 {
04583   if( mCBEncrypt ) {
04584     mCBEncryptEnabled = on;
04585     mCBEncrypt->setEnabled( on );
04586     mCBEncrypt->setShown( on );
04587   }
04588   if( mCBSign ) {
04589     mCBSignEnabled = on;
04590     mCBSign->setEnabled( on );
04591     mCBSign->setShown( on );
04592   }
04593 }
04594 
04595 void KMAtmListViewItem::setEncrypt(bool on)
04596 {
04597   if( mCBEncrypt )
04598     mCBEncrypt->setChecked( on );
04599 }
04600 
04601 bool KMAtmListViewItem::isEncrypt()
04602 {
04603   if( mCBEncrypt )
04604     return mCBEncrypt->isChecked();
04605   else
04606     return false;
04607 }
04608 
04609 void KMAtmListViewItem::setSign(bool on)
04610 {
04611   if( mCBSign )
04612     mCBSign->setChecked( on );
04613 }
04614 
04615 bool KMAtmListViewItem::isSign()
04616 {
04617   if( mCBSign )
04618     return mCBSign->isChecked();
04619   else
04620     return false;
04621 }
04622 
04623 
04624 
04625 //=============================================================================
04626 //
04627 //   Class  KMLineEdit
04628 //
04629 //=============================================================================
04630 
04631 KMLineEdit::KMLineEdit(KMComposeWin* composer, bool useCompletion,
04632                        QWidget *parent, const char *name)
04633     : KPIM::AddresseeLineEdit(parent,useCompletion,name), mComposer(composer)
04634 {
04635   allowSemiColonAsSeparator( GlobalSettings::allowSemicolonAsAddressSeparator() );
04636 }
04637 
04638 
04639 //-----------------------------------------------------------------------------
04640 void KMLineEdit::keyPressEvent(QKeyEvent *e)
04641 {
04642     // ---sven's Return is same Tab and arrow key navigation start ---
04643     if ((e->key() == Key_Enter || e->key() == Key_Return) &&
04644         !completionBox()->isVisible())
04645     {
04646       mComposer->focusNextPrevEdit(this,TRUE);
04647       return;
04648     }
04649     if (e->key() == Key_Up)
04650     {
04651       mComposer->focusNextPrevEdit(this,FALSE); // Go up
04652       return;
04653     }
04654     if (e->key() == Key_Down)
04655     {
04656       mComposer->focusNextPrevEdit(this,TRUE); // Go down
04657       return;
04658     }
04659     // ---sven's Return is same Tab and arrow key navigation end ---
04660   AddresseeLineEdit::keyPressEvent(e);
04661 }
04662 
04663 QPopupMenu *KMLineEdit::createPopupMenu()
04664 {
04665     QPopupMenu *menu = KPIM::AddresseeLineEdit::createPopupMenu();
04666     if ( !menu )
04667         return 0;
04668 
04669     menu->insertSeparator();
04670     menu->insertItem( i18n( "Edit Recent Addresses..." ),
04671                       this, SLOT( editRecentAddresses() ) );
04672 
04673     return menu;
04674 }
04675 
04676 void KMLineEdit::editRecentAddresses()
04677 {
04678   KRecentAddress::RecentAddressDialog dlg( this );
04679   dlg.setAddresses( RecentAddresses::self( KMKernel::config() )->addresses() );
04680   if ( dlg.exec() ) {
04681     RecentAddresses::self( KMKernel::config() )->clear();
04682     QStringList addrList = dlg.addresses();
04683     QStringList::Iterator it;
04684     for ( it = addrList.begin(); it != addrList.end(); ++it )
04685       RecentAddresses::self( KMKernel::config() )->add( *it );
04686 
04687     loadContacts();
04688   }
04689 }
04690 
04691 
04692 //-----------------------------------------------------------------------------
04693 void KMLineEdit::loadContacts()
04694 {
04695     // was: KABC::AddressLineEdit::loadAddresses()
04696     AddresseeLineEdit::loadContacts();
04697 
04698     QStringList recent =
04699       RecentAddresses::self( KMKernel::config() )->addresses();
04700     QStringList::Iterator it = recent.begin();
04701     QString name, email;
04702     int idx = addCompletionSource( i18n( "Recent Addresses" ) );
04703     for ( ; it != recent.end(); ++it ) {
04704       //kdDebug(5006) << "KMLineEdit::loadContacts() found: \"" << *it << "\"" << endl;
04705       KABC::Addressee addr;
04706       KPIM::getNameAndMail(*it, name, email);
04707       addr.setNameFromString( name );
04708       addr.insertEmail( email, true );
04709       addContact( addr, 120, idx ); // more weight than kabc entries and more than ldap results
04710     }
04711 }
04712 
04713 
04714 KMLineEditSpell::KMLineEditSpell(KMComposeWin* composer, bool useCompletion,
04715                        QWidget *parent, const char *name)
04716     : KMLineEdit(composer,useCompletion,parent,name)
04717 {
04718 }
04719 
04720 
04721 void KMLineEditSpell::highLightWord( unsigned int length, unsigned int pos )
04722 {
04723     setSelection ( pos, length );
04724 }
04725 
04726 void KMLineEditSpell::spellCheckDone( const QString &s )
04727 {
04728     if( s != text() )
04729         setText( s );
04730 }
04731 
04732 void KMLineEditSpell::spellCheckerMisspelling( const QString &_text, const QStringList&, unsigned int pos)
04733 {
04734      highLightWord( _text.length(),pos );
04735 }
04736 
04737 void KMLineEditSpell::spellCheckerCorrected( const QString &old, const QString &corr, unsigned int pos)
04738 {
04739     if( old!= corr )
04740     {
04741         setSelection ( pos, old.length() );
04742         insert( corr );
04743         setSelection ( pos, corr.length() );
04744         emit subjectTextSpellChecked();
04745     }
04746 }
04747 
04748 
04749 //=============================================================================
04750 //
04751 //   Class  KMEdit
04752 //
04753 //=============================================================================
04754 KMEdit::KMEdit(QWidget *parent, KMComposeWin* composer,
04755                KSpellConfig* autoSpellConfig,
04756                const char *name)
04757   : KEdit( parent, name ),
04758     mComposer( composer ),
04759     mKSpell( 0 ),
04760     mSpellingFilter( 0 ),
04761     mExtEditorTempFile( 0 ),
04762     mExtEditorTempFileWatcher( 0 ),
04763     mExtEditorProcess( 0 ),
04764     mUseExtEditor( false ),
04765     mWasModifiedBeforeSpellCheck( false ),
04766     mSpellChecker( 0 ),
04767     mSpellLineEdit( false )
04768 {
04769   installEventFilter(this);
04770   KCursor::setAutoHideCursor( this, true, true );
04771   setOverwriteEnabled( true );
04772 
04773   initializeAutoSpellChecking( autoSpellConfig );
04774 }
04775 
04776 //-----------------------------------------------------------------------------
04777 void KMEdit::initializeAutoSpellChecking( KSpellConfig* autoSpellConfig )
04778 {
04779   if ( mSpellChecker )
04780     return; // already initialized
04781   KConfigGroup readerConfig( KMKernel::config(), "Reader" );
04782   QColor defaultColor1( 0x00, 0x80, 0x00 ); // defaults from kmreaderwin.cpp
04783   QColor defaultColor2( 0x00, 0x70, 0x00 );
04784   QColor defaultColor3( 0x00, 0x60, 0x00 );
04785   QColor defaultForeground( kapp->palette().active().text() );
04786   QColor col1;
04787   if (!readerConfig.readBoolEntry("defaultColors",TRUE))
04788     col1 = readerConfig.readColorEntry( "ForegroundColor", &defaultForeground );
04789   else
04790     col1 = defaultForeground;
04791   QColor col2 = readerConfig.readColorEntry( "QuotedText3", &defaultColor3 );
04792   QColor col3 = readerConfig.readColorEntry( "QuotedText2", &defaultColor2 );
04793   QColor col4 = readerConfig.readColorEntry( "QuotedText1", &defaultColor1 );
04794   QColor c = Qt::red;
04795   QColor misspelled = readerConfig.readColorEntry( "MisspelledColor", &c );
04796 
04797   mSpellChecker = new KDictSpellingHighlighter( this, /*active*/ true,
04798                                                 /*autoEnabled*/ false,
04799                                                 /*spellColor*/ misspelled,
04800                                                 /*colorQuoting*/ true,
04801                                                 col1, col2, col3, col4,
04802                                                 autoSpellConfig );
04803   connect( mSpellChecker, SIGNAL(activeChanged(const QString &)),
04804            mComposer, SLOT(slotStatusMessage(const QString &)));
04805   connect( mSpellChecker, SIGNAL(newSuggestions(const QString&, const QStringList&, unsigned int)),
04806            this, SLOT(addSuggestion(const QString&, const QStringList&, unsigned int)) );
04807 }
04808 
04809 //-----------------------------------------------------------------------------
04810 void KMEdit::deleteAutoSpellChecking()
04811 { // because the highlighter doesn't support RichText, delete its instance.
04812   delete mSpellChecker;
04813   mSpellChecker =0;
04814 }
04815 //-----------------------------------------------------------------------------
04816 void KMEdit::addSuggestion(const QString& text, const QStringList& lst, unsigned int )
04817 {
04818   mReplacements[text] = lst;
04819 }
04820 
04821 void KMEdit::setSpellCheckingActive(bool spellCheckingActive)
04822 {
04823   if ( mSpellChecker ) {
04824     mSpellChecker->setActive(spellCheckingActive);
04825   }
04826 }
04827 
04828 //-----------------------------------------------------------------------------
04829 KMEdit::~KMEdit()
04830 {
04831   removeEventFilter(this);
04832 
04833   delete mKSpell;
04834   delete mSpellChecker;
04835   mSpellChecker = 0;
04836 
04837 }
04838 
04839 
04840 //-----------------------------------------------------------------------------
04841 QString KMEdit::brokenText()
04842 {
04843   QString temp, line;
04844 
04845   int num_lines = numLines();
04846   for (int i = 0; i < num_lines; ++i)
04847   {
04848     int lastLine = 0;
04849     line = textLine(i);
04850     for (int j = 0; j < (int)line.length(); ++j)
04851     {
04852       if (lineOfChar(i, j) > lastLine)
04853       {
04854         lastLine = lineOfChar(i, j);
04855         temp += '\n';
04856       }
04857       temp += line[j];
04858     }
04859     if (i + 1 < num_lines) temp += '\n';
04860   }
04861 
04862   return temp;
04863 }
04864 
04865 //-----------------------------------------------------------------------------
04866 bool KMEdit::eventFilter(QObject*o, QEvent* e)
04867 {
04868   if (o == this)
04869     KCursor::autoHideEventFilter(o, e);
04870 
04871   if (e->type() == QEvent::KeyPress)
04872   {
04873     QKeyEvent *k = (QKeyEvent*)e;
04874 
04875     if (mUseExtEditor) {
04876       if (k->key() == Key_Up)
04877       {
04878         mComposer->focusNextPrevEdit(0, false); //take me up
04879         return TRUE;
04880       }
04881 
04882       // ignore modifier keys (cf. bug 48841)
04883       if ( (k->key() == Key_Shift) || (k->key() == Key_Control) ||
04884            (k->key() == Key_Meta) || (k->key() == Key_Alt) )
04885         return true;
04886       if (mExtEditorTempFile) return TRUE;
04887       QString sysLine = mExtEditor;
04888       mExtEditorTempFile = new KTempFile();
04889 
04890       mExtEditorTempFile->setAutoDelete(true);
04891 
04892       (*mExtEditorTempFile->textStream()) << text();
04893 
04894       mExtEditorTempFile->close();
04895       // replace %f in the system line
04896       sysLine.replace( "%f", mExtEditorTempFile->name() );
04897       mExtEditorProcess = new KProcess();
04898       sysLine += " ";
04899       while (!sysLine.isEmpty())
04900       {
04901         *mExtEditorProcess << sysLine.left(sysLine.find(" ")).local8Bit();
04902         sysLine.remove(0, sysLine.find(" ") + 1);
04903       }
04904       connect(mExtEditorProcess, SIGNAL(processExited(KProcess*)),
04905               SLOT(slotExternalEditorDone(KProcess*)));
04906       if (!mExtEditorProcess->start())
04907       {
04908         KMessageBox::error( topLevelWidget(),
04909                             i18n("Unable to start external editor.") );
04910         killExternalEditor();
04911       } else {
04912         mExtEditorTempFileWatcher = new KDirWatch( this, "mExtEditorTempFileWatcher" );
04913         connect( mExtEditorTempFileWatcher, SIGNAL(dirty(const QString&)),
04914                  SLOT(slotExternalEditorTempFileChanged(const QString&)) );
04915         mExtEditorTempFileWatcher->addFile( mExtEditorTempFile->name() );
04916       }
04917       return TRUE;
04918     } else {
04919     // ---sven's Arrow key navigation start ---
04920     // Key Up in first line takes you to Subject line.
04921     if (k->key() == Key_Up && k->state() != ShiftButton && currentLine() == 0
04922       && lineOfChar(0, currentColumn()) == 0)
04923     {
04924       deselect();
04925       mComposer->focusNextPrevEdit(0, false); //take me up
04926       return TRUE;
04927     }
04928     // ---sven's Arrow key navigation end ---
04929 
04930     if (k->key() == Key_Backtab && k->state() == ShiftButton)
04931     {
04932       deselect();
04933       mComposer->focusNextPrevEdit(0, false);
04934       return TRUE;
04935     }
04936 
04937     }
04938   } else if ( e->type() == QEvent::ContextMenu ) {
04939     QContextMenuEvent *event = (QContextMenuEvent*) e;
04940 
04941     int para = 1, charPos, firstSpace, lastSpace;
04942 
04943     //Get the character at the position of the click
04944     charPos = charAt( viewportToContents(event->pos()), &para );
04945     QString paraText = text( para );
04946 
04947     if( !paraText.at(charPos).isSpace() )
04948     {
04949       //Get word right clicked on
04950       const QRegExp wordBoundary( "[\\s\\W]" );
04951       firstSpace = paraText.findRev( wordBoundary, charPos ) + 1;
04952       lastSpace = paraText.find( wordBoundary, charPos );
04953       if( lastSpace == -1 )
04954         lastSpace = paraText.length();
04955       QString word = paraText.mid( firstSpace, lastSpace - firstSpace );
04956       //Continue if this word was misspelled
04957       if( !word.isEmpty() && mReplacements.contains( word ) )
04958       {
04959         KPopupMenu p;
04960         p.insertTitle( i18n("Suggestions") );
04961 
04962         //Add the suggestions to the popup menu
04963         QStringList reps = mReplacements[word];
04964         if( reps.count() > 0 )
04965         {
04966           int listPos = 0;
04967           for ( QStringList::Iterator it = reps.begin(); it != reps.end(); ++it ) {
04968             p.insertItem( *it, listPos );
04969             listPos++;
04970           }
04971         }
04972         else
04973         {
04974           p.insertItem( QString::fromLatin1("No Suggestions"), -2 );
04975         }
04976 
04977         //Execute the popup inline
04978         int id = p.exec( mapToGlobal( event->pos() ) );
04979 
04980         if( id > -1 )
04981         {
04982           //Save the cursor position
04983           int parIdx = 1, txtIdx = 1;
04984           getCursorPosition(&parIdx, &txtIdx);
04985           setSelection(para, firstSpace, para, lastSpace);
04986           insert(mReplacements[word][id]);
04987           // Restore the cursor position; if the cursor was behind the
04988           // misspelled word then adjust the cursor position
04989           if ( para == parIdx && txtIdx >= lastSpace )
04990             txtIdx += mReplacements[word][id].length() - word.length();
04991           setCursorPosition(parIdx, txtIdx);
04992         }
04993         //Cancel original event
04994         return true;
04995       }
04996     }
04997   }
04998 
04999   return KEdit::eventFilter(o, e);
05000 }
05001 
05002 
05003 //-----------------------------------------------------------------------------
05004 int KMEdit::autoSpellChecking( bool on )
05005 {
05006   if ( textFormat() == Qt::RichText ) {
05007      // syntax highlighter doesn't support extended text properties
05008      if ( on )
05009        KMessageBox::sorry(this, i18n("Automatic spellchecking is not possible on text with markup."));
05010      return -1;
05011   }
05012 
05013   // don't autoEnable spell checking if the user turned spell checking off
05014   mSpellChecker->setAutomatic( on );
05015   mSpellChecker->setActive( on );
05016   return 1;
05017 }
05018 
05019 
05020 //-----------------------------------------------------------------------------
05021 void KMEdit::slotExternalEditorTempFileChanged( const QString & fileName ) {
05022   if ( !mExtEditorTempFile )
05023     return;
05024   if ( fileName != mExtEditorTempFile->name() )
05025     return;
05026   // read data back in from file
05027   setAutoUpdate(false);
05028   clear();
05029 
05030   insertLine(QString::fromLocal8Bit(KPIM::kFileToString( fileName, true, false )), -1);
05031   setAutoUpdate(true);
05032   repaint();
05033 }
05034 
05035 void KMEdit::slotExternalEditorDone( KProcess * proc ) {
05036   assert(proc == mExtEditorProcess);
05037   // make sure, we update even when KDirWatcher is too slow:
05038   slotExternalEditorTempFileChanged( mExtEditorTempFile->name() );
05039   killExternalEditor();
05040 }
05041 
05042 void KMEdit::killExternalEditor() {
05043   delete mExtEditorTempFileWatcher; mExtEditorTempFileWatcher = 0;
05044   delete mExtEditorTempFile; mExtEditorTempFile = 0;
05045   delete mExtEditorProcess; mExtEditorProcess = 0;
05046 }
05047 
05048 
05049 bool KMEdit::checkExternalEditorFinished() {
05050   if ( !mExtEditorProcess )
05051     return true;
05052   switch ( KMessageBox::warningYesNoCancel( topLevelWidget(),
05053            i18n("The external editor is still running.\n"
05054                 "Abort the external editor or leave it open?"),
05055            i18n("External Editor"),
05056            i18n("Abort Editor"), i18n("Leave Editor Open") ) ) {
05057   case KMessageBox::Yes:
05058     killExternalEditor();
05059     return true;
05060   case KMessageBox::No:
05061     return true;
05062   default:
05063     return false;
05064   }
05065 }
05066 
05067 //-----------------------------------------------------------------------------
05068 void KMEdit::spellcheck()
05069 {
05070   if ( mKSpell )
05071     return;
05072   mWasModifiedBeforeSpellCheck = isModified();
05073   mSpellLineEdit = !mSpellLineEdit;
05074 //  maybe for later, for now plaintext is given to KSpell
05075 //  if (textFormat() == Qt::RichText ) {
05076 //    kdDebug(5006) << "KMEdit::spellcheck, spellchecking for RichText" << endl;
05077 //    mKSpell = new KSpell(this, i18n("Spellcheck - KMail"), this,
05078 //                    SLOT(slotSpellcheck2(KSpell*)),0,true,false,KSpell::HTML);
05079 //  }
05080 //  else {
05081     mKSpell = new KSpell(this, i18n("Spellcheck - KMail"), this,
05082                       SLOT(slotSpellcheck2(KSpell*)));
05083 //  }
05084 
05085   QStringList l = KSpellingHighlighter::personalWords();
05086   for ( QStringList::Iterator it = l.begin(); it != l.end(); ++it ) {
05087       mKSpell->addPersonal( *it );
05088   }
05089   connect (mKSpell, SIGNAL( death()),
05090           this, SLOT (slotSpellDone()));
05091   connect (mKSpell, SIGNAL (misspelling (const QString &, const QStringList &, unsigned int)),
05092           this, SLOT (slotMisspelling (const QString &, const QStringList &, unsigned int)));
05093   connect (mKSpell, SIGNAL (corrected (const QString &, const QString &, unsigned int)),
05094           this, SLOT (slotCorrected (const QString &, const QString &, unsigned int)));
05095   connect (mKSpell, SIGNAL (done(const QString &)),
05096           this, SLOT (slotSpellResult (const QString&)));
05097 }
05098 
05099 void KMEdit::cut()
05100 {
05101   KEdit::cut();
05102   if ( textFormat() != Qt::RichText )
05103     mSpellChecker->restartBackgroundSpellCheck();
05104 }
05105 
05106 void KMEdit::clear()
05107 {
05108   KEdit::clear();
05109   if ( textFormat() != Qt::RichText )
05110     mSpellChecker->restartBackgroundSpellCheck();
05111 }
05112 
05113 void KMEdit::del()
05114 {
05115   KEdit::del();
05116   if ( textFormat() != Qt::RichText )
05117     mSpellChecker->restartBackgroundSpellCheck();
05118 }
05119 
05120 
05121 void KMEdit::slotMisspelling(const QString &text, const QStringList &lst, unsigned int pos)
05122 {
05123     kdDebug(5006)<<"void KMEdit::slotMisspelling(const QString &text, const QStringList &lst, unsigned int pos) : "<<text <<endl;
05124     if( mSpellLineEdit )
05125         mComposer->sujectLineWidget()->spellCheckerMisspelling( text, lst, pos);
05126     else
05127         misspelling(text, lst, pos);
05128 }
05129 
05130 void KMEdit::slotCorrected (const QString &oldWord, const QString &newWord, unsigned int pos)
05131 {
05132     kdDebug(5006)<<"slotCorrected (const QString &oldWord, const QString &newWord, unsigned int pos) : "<<oldWord<<endl;
05133     if( mSpellLineEdit )
05134         mComposer->sujectLineWidget()->spellCheckerCorrected( oldWord, newWord, pos);
05135     else {
05136         unsigned int l = 0;
05137         unsigned int cnt = 0;
05138         bool _bold,_underline,_italic;
05139         QColor _color;
05140         QFont _font;
05141         posToRowCol (pos, l, cnt);
05142         setCursorPosition(l, cnt+1); // the new word will get the same markup now as the first character of the word
05143         _bold = bold();
05144         _underline = underline();
05145         _italic = italic();
05146         _color = color();
05147         _font = currentFont();
05148         corrected(oldWord, newWord, pos);
05149         setSelection (l, cnt, l, cnt+newWord.length());
05150         setBold(_bold);
05151         setItalic(_italic);
05152         setUnderline(_underline);
05153         setColor(_color);
05154         setCurrentFont(_font);
05155     }
05156 
05157 }
05158 
05159 //-----------------------------------------------------------------------------
05160 void KMEdit::slotSpellcheck2(KSpell*)
05161 {
05162     if( !mSpellLineEdit)
05163     {
05164         spellcheck_start();
05165 
05166         QString quotePrefix;
05167         if(mComposer && mComposer->msg())
05168         {
05169             // read the quote indicator from the preferences
05170             KConfig *config=KMKernel::config();
05171             KConfigGroupSaver saver(config, "General");
05172 
05173             int languageNr = config->readNumEntry("reply-current-language",0);
05174             config->setGroup( QString("KMMessage #%1").arg(languageNr) );
05175 
05176             quotePrefix = config->readEntry("indent-prefix", ">%_");
05177             quotePrefix = mComposer->msg()->formatString(quotePrefix);
05178         }
05179 
05180         kdDebug(5006) << "spelling: new SpellingFilter with prefix=\"" << quotePrefix << "\"" << endl;
05181         QTextEdit plaintext;
05182         plaintext.setText(text());
05183         plaintext.setTextFormat(Qt::PlainText);
05184         mSpellingFilter = new SpellingFilter(plaintext.text(), quotePrefix, SpellingFilter::FilterUrls,
05185                                              SpellingFilter::FilterEmailAddresses);
05186 
05187         mKSpell->check(mSpellingFilter->filteredText());
05188     }
05189     else if( mComposer )
05190         mKSpell->check( mComposer->sujectLineWidget()->text());
05191 }
05192 
05193 //-----------------------------------------------------------------------------
05194 void KMEdit::slotSpellResult(const QString &s)
05195 {
05196     if( !mSpellLineEdit)
05197         spellcheck_stop();
05198 
05199   int dlgResult = mKSpell->dlgResult();
05200   if ( dlgResult == KS_CANCEL )
05201   {
05202       if( mSpellLineEdit)
05203       {
05204           //stop spell check
05205           mSpellLineEdit = false;
05206           QString tmpText( s );
05207           tmpText =  tmpText.remove('\n');
05208 
05209           if( tmpText != mComposer->sujectLineWidget()->text() )
05210               mComposer->sujectLineWidget()->setText( tmpText );
05211       }
05212       else
05213       {
05214           setModified(true);
05215       }
05216   }
05217   mKSpell->cleanUp();
05218   KDictSpellingHighlighter::dictionaryChanged();
05219 
05220   emit spellcheck_done( dlgResult );
05221 }
05222 
05223 //-----------------------------------------------------------------------------
05224 void KMEdit::slotSpellDone()
05225 {
05226   kdDebug(5006)<<" void KMEdit::slotSpellDone()\n";
05227   KSpell::spellStatus status = mKSpell->status();
05228   delete mKSpell;
05229   mKSpell = 0;
05230 
05231   kdDebug(5006) << "spelling: delete SpellingFilter" << endl;
05232   delete mSpellingFilter;
05233   mSpellingFilter = 0;
05234   mComposer->sujectLineWidget()->deselect();
05235   if (status == KSpell::Error)
05236   {
05237      KMessageBox::sorry( topLevelWidget(),
05238                          i18n("ISpell/Aspell could not be started. Please "
05239                               "make sure you have ISpell or Aspell properly "
05240                               "configured and in your PATH.") );
05241      emit spellcheck_done( KS_CANCEL );
05242   }
05243   else if (status == KSpell::Crashed)
05244   {
05245      spellcheck_stop();
05246      KMessageBox::sorry( topLevelWidget(),
05247                          i18n("ISpell/Aspell seems to have crashed.") );
05248      emit spellcheck_done( KS_CANCEL );
05249   }
05250   else
05251   {
05252       if( mSpellLineEdit )
05253           spellcheck();
05254       else if( !mComposer->subjectTextWasSpellChecked() && status == KSpell::FinishedNoMisspellingsEncountered )
05255           KMessageBox::information( topLevelWidget(),
05256                                     i18n("No misspellings encountered.") );
05257   }
05258 }
KDE Logo
This file is part of the documentation for kmail Library Version 3.3.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Fri Dec 21 14:24:50 2007 by doxygen 1.4.2 written by Dimitri van Heesch, © 1997-2003