/* -*- mode: C++; c-file-style: "gnu" -*-
    This file is part of KMail, the KDE mail client.
    Copyright (c) 2002 Don Sanders <sanders@kde.org>

    KMail is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License, version 2, as
    published by the Free Software Foundation.

    KMail is distributed in the hope that it will be useful, but
    WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

//
// This file implements various "command" classes. These command classes
// are based on the command design pattern.
//
// Historically various operations were implemented as slots of KMMainWin.
// This proved inadequate as KMail has multiple top level windows
// (KMMainWin, KMReaderMainWin, SearchWindow, KMComposeWin) that may
// benefit from using these operations. It is desirable that these
// classes can operate without depending on or altering the state of
// a KMMainWin, in fact it is possible no KMMainWin object even exists.
//
// Now these operations have been rewritten as KMCommand based classes,
// making them independent of KMMainWin.
//
// The base command class KMCommand is async, which is a difference
// from the conventional command pattern. As normal derived classes implement
// the execute method, but client classes call start() instead of
// calling execute() directly. start() initiates async operations,
// and on completion of these operations calls execute() and then deletes
// the command. (So the client must not construct commands on the stack).
//
// The type of async operation supported by KMCommand is retrieval
// of messages from an IMAP server.

#include "kmcommands.h"

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <errno.h>
#include <mimelib/enum.h>
#include <mimelib/field.h>
#include <mimelib/mimepp.h>
#include <mimelib/string.h>
#include <tdeapplication.h>
#include <dcopclient.h>

#include <tqtextcodec.h>
#include <tqpopupmenu.h>
#include <tqeventloop.h>

#include <libemailfunctions/email.h>
#include <kdcopservicestarter.h>
#include <kdebug.h>
#include <tdefiledialog.h>
#include <tdeabc/stdaddressbook.h>
#include <tdeabc/addresseelist.h>
#include <kdirselectdialog.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <tdeparts/browserextension.h>
#include <kprogress.h>
#include <krun.h>
#include <kbookmarkmanager.h>
#include <kstandarddirs.h>
#include <tdetempfile.h>
#include <tdeimproxy.h>
#include <kuserprofile.h>
// TDEIO headers
#include <tdeio/job.h>
#include <tdeio/netaccess.h>

#include <libkpimidentities/identitymanager.h>

#include "actionscheduler.h"
using KMail::ActionScheduler;
#include "mailinglist-magic.h"
#include "kmaddrbook.h"
#include <kaddrbook.h>
#include "composer.h"
#include "kmfiltermgr.h"
#include "kmfoldermbox.h"
#include "kmfolderimap.h"
#include "kmfoldermgr.h"
#include "kmheaders.h"
#include "headeritem.h"
#include "kmmainwidget.h"
#include "kmmsgdict.h"
#include "messagesender.h"
#include "kmmsgpartdlg.h"
#include "undostack.h"
#include "kcursorsaver.h"
#include "partNode.h"
#include "objecttreeparser.h"
#include "csshelper.h"
using KMail::ObjectTreeParser;
using KMail::FolderJob;
#include "chiasmuskeyselector.h"
#include "mailsourceviewer.h"
using KMail::MailSourceViewer;
#include "kmreadermainwin.h"
#include "secondarywindow.h"
using KMail::SecondaryWindow;
#include "redirectdialog.h"
using KMail::RedirectDialog;
#include "util.h"
#include "templateparser.h"
#include "editorwatcher.h"
#include "korghelper.h"

#include "broadcaststatus.h"
#include "globalsettings.h"

#include <libtdepim/tdefileio.h>
#include "kcalendariface_stub.h"

#include "progressmanager.h"
using KPIM::ProgressManager;
using KPIM::ProgressItem;
#include <kmime_mdn.h>
using namespace KMime;

#include <kleo/specialjob.h>
#include <kleo/cryptobackend.h>
#include <kleo/cryptobackendfactory.h>

#include <tqclipboard.h>

#include <memory>

class LaterDeleterWithCommandCompletion : public KMail::Util::LaterDeleter
{
public:
  LaterDeleterWithCommandCompletion( KMCommand* command )
    :LaterDeleter( command ), m_result( KMCommand::Failed )
  {
  }
  ~LaterDeleterWithCommandCompletion()
  {
    setResult( m_result );
    KMCommand *command = static_cast<KMCommand*>( m_object );
    emit command->completed( command );
  }
  void setResult( KMCommand::Result v ) { m_result = v; }
private:
  KMCommand::Result m_result;
};


KMCommand::KMCommand( TQWidget *parent )
  : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
    mEmitsCompletedItself( false ), mParent( parent )
{
}

KMCommand::KMCommand( TQWidget *parent, const TQPtrList<KMMsgBase> &msgList )
  : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
    mEmitsCompletedItself( false ), mParent( parent ), mMsgList( msgList )
{
}

KMCommand::KMCommand( TQWidget *parent, KMMsgBase *msgBase )
  : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
    mEmitsCompletedItself( false ), mParent( parent )
{
  mMsgList.append( msgBase );
}

KMCommand::KMCommand( TQWidget *parent, KMMessage *msg )
  : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
    mEmitsCompletedItself( false ), mParent( parent )
{
  if (msg)
    mMsgList.append( &msg->toMsgBase() );
}

KMCommand::~KMCommand()
{
  TQValueListIterator<TQGuardedPtr<KMFolder> > fit;
  for ( fit = mFolders.begin(); fit != mFolders.end(); ++fit ) {
    if (!(*fit))
      continue;
    (*fit)->close("kmcommand");
  }
}

KMCommand::Result KMCommand::result()
{
  if ( mResult == Undefined )
    kdDebug(5006) << k_funcinfo << "mResult is Undefined" << endl;
  return mResult;
}

void KMCommand::start()
{
  TQTimer::singleShot( 0, this, TQT_SLOT( slotStart() ) );
}


const TQPtrList<KMMessage> KMCommand::retrievedMsgs() const
{
  return mRetrievedMsgs;
}

KMMessage *KMCommand::retrievedMessage() const
{
  return mRetrievedMsgs.getFirst();
}

TQWidget *KMCommand::parentWidget() const
{
  return mParent;
}

int KMCommand::mCountJobs = 0;

void KMCommand::slotStart()
{
  connect( this, TQT_SIGNAL( messagesTransfered( KMCommand::Result ) ),
           this, TQT_SLOT( slotPostTransfer( KMCommand::Result ) ) );
  kmkernel->filterMgr()->ref();

  if (mMsgList.find(0) != -1) {
      emit messagesTransfered( Failed );
      return;
  }

  if ((mMsgList.count() == 1) &&
      (mMsgList.getFirst()->isMessage()) &&
      (mMsgList.getFirst()->parent() == 0))
  {
    // Special case of operating on message that isn't in a folder
    mRetrievedMsgs.append((KMMessage*)mMsgList.getFirst());
    emit messagesTransfered( OK );
    return;
  }

  for ( KMMsgBase *mb = mMsgList.first(); mb; mb = mMsgList.next() ) {
    if ( mb ) {
      if ( !mb->parent() ) {
        emit messagesTransfered( Failed );
        return;
      } else {
        keepFolderOpen( mb->parent() );
      }
    }
  }

  // transfer the selected messages first
  transferSelectedMsgs();
}

void KMCommand::slotPostTransfer( KMCommand::Result result )
{
  disconnect( this, TQT_SIGNAL( messagesTransfered( KMCommand::Result ) ),
              this, TQT_SLOT( slotPostTransfer( KMCommand::Result ) ) );
  if ( result == OK )
    result = execute();
  mResult = result;
  TQPtrListIterator<KMMessage> it( mRetrievedMsgs );
  KMMessage* msg;
  while ( (msg = it.current()) != 0 )
  {
    ++it;
    if (msg->parent())
      msg->setTransferInProgress(false);
  }
  kmkernel->filterMgr()->deref();
  if ( !emitsCompletedItself() )
    emit completed( this );
  if ( !deletesItself() )
    deleteLater();
}

void KMCommand::transferSelectedMsgs()
{
  // make sure no other transfer is active
  if (KMCommand::mCountJobs > 0) {
    emit messagesTransfered( Failed );
    return;
  }

  bool complete = true;
  KMCommand::mCountJobs = 0;
  mCountMsgs = 0;
  mRetrievedMsgs.clear();
  mCountMsgs = mMsgList.count();
  uint totalSize = 0;
  // the KProgressDialog for the user-feedback. Only enable it if it's needed.
  // For some commands like KMSeStatusCommand it's not needed. Note, that
  // for some reason the KProgressDialog eats the MouseReleaseEvent (if a
  // command is executed after the MousePressEvent), cf. bug #71761.
  if ( mCountMsgs > 0 ) {
    mProgressDialog = new KProgressDialog(mParent, "transferProgress",
      i18n("Please wait"),
      i18n("Please wait while the message is transferred",
        "Please wait while the %n messages are transferred", mMsgList.count()),
      true);
    mProgressDialog->setMinimumDuration(1000);
  }
  for (KMMsgBase *mb = mMsgList.first(); mb; mb = mMsgList.next())
  {
    // check if all messages are complete
    KMMessage *thisMsg = 0;
    if ( mb->isMessage() )
      thisMsg = static_cast<KMMessage*>(mb);
    else
    {
      KMFolder *folder = mb->parent();
      int idx = folder->find(mb);
      if (idx < 0) continue;
      thisMsg = folder->getMsg(idx);
    }
    if (!thisMsg) continue;
    if ( thisMsg->transferInProgress() &&
         thisMsg->parent()->folderType() == KMFolderTypeImap )
    {
      thisMsg->setTransferInProgress( false, true );
      thisMsg->parent()->ignoreJobsForMessage( thisMsg );
    }

    if ( thisMsg->parent() && !thisMsg->isComplete() &&
         ( !mProgressDialog || !mProgressDialog->wasCancelled() ) )
    {
      kdDebug(5006)<<"### INCOMPLETE\n";
      // the message needs to be transferred first
      complete = false;
      KMCommand::mCountJobs++;
      FolderJob *job = thisMsg->parent()->createJob(thisMsg);
      job->setCancellable( false );
      totalSize += thisMsg->msgSizeServer();
      // emitted when the message was transferred successfully
      connect(job, TQT_SIGNAL(messageRetrieved(KMMessage*)),
              this, TQT_SLOT(slotMsgTransfered(KMMessage*)));
      // emitted when the job is destroyed
      connect(job, TQT_SIGNAL(finished()),
              this, TQT_SLOT(slotJobFinished()));
      connect(job, TQT_SIGNAL(progress(unsigned long, unsigned long)),
              this, TQT_SLOT(slotProgress(unsigned long, unsigned long)));
      // msg musn't be deleted
      thisMsg->setTransferInProgress(true);
      job->start();
    } else {
      thisMsg->setTransferInProgress(true);
      mRetrievedMsgs.append(thisMsg);
    }
  }

  if (complete)
  {
    delete mProgressDialog;
    mProgressDialog = 0;
    emit messagesTransfered( OK );
  } else {
    // wait for the transfer and tell the progressBar the necessary steps
    if ( mProgressDialog ) {
      connect(mProgressDialog, TQT_SIGNAL(cancelClicked()),
              this, TQT_SLOT(slotTransferCancelled()));
      mProgressDialog->progressBar()->setTotalSteps(totalSize);
    }
  }
}

void KMCommand::slotMsgTransfered(KMMessage* msg)
{
  if ( mProgressDialog && mProgressDialog->wasCancelled() ) {
    emit messagesTransfered( Canceled );
    return;
  }

  // save the complete messages
  mRetrievedMsgs.append(msg);
}

void KMCommand::slotProgress( unsigned long done, unsigned long /*total*/ )
{
  mProgressDialog->progressBar()->setProgress( done );
}

void KMCommand::slotJobFinished()
{
  // the job is finished (with / without error)
  KMCommand::mCountJobs--;

  if ( mProgressDialog && mProgressDialog->wasCancelled() ) return;

  if ( (mCountMsgs - static_cast<int>(mRetrievedMsgs.count())) > KMCommand::mCountJobs )
  {
    // the message wasn't retrieved before => error
    if ( mProgressDialog )
      mProgressDialog->hide();
    slotTransferCancelled();
    return;
  }
  // update the progressbar
  if ( mProgressDialog ) {
    mProgressDialog->setLabel(i18n("Please wait while the message is transferred",
          "Please wait while the %n messages are transferred", KMCommand::mCountJobs));
  }
  if (KMCommand::mCountJobs == 0)
  {
    // all done
    delete mProgressDialog;
    mProgressDialog = 0;
    emit messagesTransfered( OK );
  }
}

void KMCommand::slotTransferCancelled()
{
  // kill the pending jobs
  TQValueListIterator<TQGuardedPtr<KMFolder> > fit;
  for ( fit = mFolders.begin(); fit != mFolders.end(); ++fit ) {
    if (!(*fit))
      continue;
    KMFolder *folder = *fit;
    KMFolderImap *imapFolder = dynamic_cast<KMFolderImap*>(folder);
    if (imapFolder && imapFolder->account()) {
      imapFolder->account()->killAllJobs();
    }
  }

  KMCommand::mCountJobs = 0;
  mCountMsgs = 0;
  // unget the transfered messages
  TQPtrListIterator<KMMessage> it( mRetrievedMsgs );
  KMMessage* msg;
  while ( (msg = it.current()) != 0 )
  {
    KMFolder *folder = msg->parent();
    ++it;
    if (!folder)
      continue;
    msg->setTransferInProgress(false);
    int idx = folder->find(msg);
    if (idx > 0) folder->unGetMsg(idx);
  }
  mRetrievedMsgs.clear();
  emit messagesTransfered( Canceled );
}

void KMCommand::keepFolderOpen( KMFolder *folder )
{
  folder->open( "kmcommand" );
  mFolders.append( folder );
}

KMMailtoComposeCommand::KMMailtoComposeCommand( const KURL &url,
                                                KMMessage *msg )
  :mUrl( url ), mMessage( msg )
{
}

KMCommand::Result KMMailtoComposeCommand::execute()
{
  KMMessage *msg = new KMMessage;
  uint id = 0;

  if ( mMessage && mMessage->parent() )
    id = mMessage->parent()->identity();

  msg->initHeader(id);
  msg->setCharset("utf-8");
  msg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );

  KMail::Composer * win = KMail::makeComposer( msg, id );
  win->setCharset("", true);
  win->setFocusToSubject();
  win->show();

  return OK;
}


KMMailtoReplyCommand::KMMailtoReplyCommand( TQWidget *parent,
   const KURL &url, KMMessage *msg, const TQString &selection )
  :KMCommand( parent, msg ), mUrl( url ), mSelection( selection  )
{
}

KMCommand::Result KMMailtoReplyCommand::execute()
{
  //TODO : consider factoring createReply into this method.
  KMMessage *msg = retrievedMessage();
  if ( !msg || !msg->codec() ) {
    return Failed;
  }
  KMMessage *rmsg = msg->createReply( KMail::ReplyNone, mSelection );
  rmsg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );

  KMail::Composer * win = KMail::makeComposer( rmsg, 0 );
  win->setCharset(msg->codec()->mimeName(), true);
  win->setReplyFocus();
  win->show();

  return OK;
}


KMMailtoForwardCommand::KMMailtoForwardCommand( TQWidget *parent,
   const KURL &url, KMMessage *msg )
  :KMCommand( parent, msg ), mUrl( url )
{
}

KMCommand::Result KMMailtoForwardCommand::execute()
{
  //TODO : consider factoring createForward into this method.
  KMMessage *msg = retrievedMessage();
  if ( !msg || !msg->codec() ) {
    return Failed;
  }
  KMMessage *fmsg = msg->createForward();
  fmsg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );

  KMail::Composer * win = KMail::makeComposer( fmsg );
  win->setCharset(msg->codec()->mimeName(), true);
  win->show();

  return OK;
}


KMAddBookmarksCommand::KMAddBookmarksCommand( const KURL &url, TQWidget *parent )
  : KMCommand( parent ), mUrl( url )
{
}

KMCommand::Result KMAddBookmarksCommand::execute()
{
  TQString filename = locateLocal( "data", TQString::fromLatin1("konqueror/bookmarks.xml") );
  KBookmarkManager *bookManager = KBookmarkManager::managerForFile( filename,
                                                                    false );
  KBookmarkGroup group = bookManager->root();
  group.addBookmark( bookManager, mUrl.path(), KURL( mUrl ) );
  if( bookManager->save() ) {
    bookManager->emitChanged( group );
  }

  return OK;
}

KMMailtoAddAddrBookCommand::KMMailtoAddAddrBookCommand( const KURL &url,
   TQWidget *parent )
  : KMCommand( parent ), mUrl( url )
{
}

KMCommand::Result KMMailtoAddAddrBookCommand::execute()
{
  KAddrBookExternal::addEmail( KMMessage::decodeMailtoUrl( mUrl.path() ),
                               parentWidget() );

  return OK;
}


KMMailtoOpenAddrBookCommand::KMMailtoOpenAddrBookCommand( const KURL &url,
   TQWidget *parent )
  : KMCommand( parent ), mUrl( url )
{
}

KMCommand::Result KMMailtoOpenAddrBookCommand::execute()
{
  KAddrBookExternal::openEmail( KMMessage::decodeMailtoUrl( mUrl.path() ),
                                parentWidget() );

  return OK;
}


KMUrlCopyCommand::KMUrlCopyCommand( const KURL &url, KMMainWidget *mainWidget )
  :mUrl( url ), mMainWidget( mainWidget )
{
}

KMCommand::Result KMUrlCopyCommand::execute()
{
  TQClipboard* clip = TQApplication::clipboard();

  if (mUrl.protocol() == "mailto") {
    // put the url into the mouse selection and the clipboard
    TQString address = KMMessage::decodeMailtoUrl( mUrl.path() );
    clip->setSelectionMode( true );
    clip->setText( address );
    clip->setSelectionMode( false );
    clip->setText( address );
    KPIM::BroadcastStatus::instance()->setStatusMsg( i18n( "Address copied to clipboard." ));
  } else {
    // put the url into the mouse selection and the clipboard
    clip->setSelectionMode( true );
    clip->setText( mUrl.url() );
    clip->setSelectionMode( false );
    clip->setText( mUrl.url() );
    KPIM::BroadcastStatus::instance()->setStatusMsg( i18n( "URL copied to clipboard." ));
  }

  return OK;
}


KMUrlOpenCommand::KMUrlOpenCommand( const KURL &url, KMReaderWin *readerWin )
  :mUrl( url ), mReaderWin( readerWin )
{
}

KMCommand::Result KMUrlOpenCommand::execute()
{
  if ( !mUrl.isEmpty() )
    mReaderWin->slotUrlOpen( mUrl, KParts::URLArgs() );

  return OK;
}


KMUrlSaveCommand::KMUrlSaveCommand( const KURL &url, TQWidget *parent )
  : KMCommand( parent ), mUrl( url )
{
}

KMCommand::Result KMUrlSaveCommand::execute()
{
  if ( mUrl.isEmpty() )
    return OK;
  KURL saveUrl = KFileDialog::getSaveURL(mUrl.fileName(), TQString(),
                                         parentWidget() );
  if ( saveUrl.isEmpty() )
    return Canceled;
  if ( TDEIO::NetAccess::exists( saveUrl, false, parentWidget() ) )
  {
    if (KMessageBox::warningContinueCancel(0,
        i18n("<qt>File <b>%1</b> exists.<br>Do you want to replace it?</qt>")
        .arg(saveUrl.prettyURL()), i18n("Save to File"), i18n("&Replace"))
        != KMessageBox::Continue)
      return Canceled;
  }
  TDEIO::Job *job = TDEIO::file_copy(mUrl, saveUrl, -1, true);
  connect(job, TQT_SIGNAL(result(TDEIO::Job*)), TQT_SLOT(slotUrlSaveResult(TDEIO::Job*)));
  setEmitsCompletedItself( true );
  return OK;
}

void KMUrlSaveCommand::slotUrlSaveResult( TDEIO::Job *job )
{
  if ( job->error() ) {
    job->showErrorDialog();
    setResult( Failed );
    emit completed( this );
  }
  else {
    setResult( OK );
    emit completed( this );
  }
}


KMEditMsgCommand::KMEditMsgCommand( TQWidget *parent, KMMessage *msg )
  :KMCommand( parent, msg )
{
}

KMCommand::Result KMEditMsgCommand::execute()
{
  KMMessage *msg = retrievedMessage();
  if ( !msg || !msg->parent() ||
       ( !kmkernel->folderIsDraftOrOutbox( msg->parent() ) &&
         !kmkernel->folderIsTemplates( msg->parent() ) ) )
    return Failed;

  // Remember the old parent, we need it a bit further down to be able
  // to put the unchanged messsage back in the original folder if the nth
  // edit is discarded, for n > 1.
  KMFolder *parent = msg->parent();
  if ( parent )
    parent->take( parent->find( msg ) );

  KMail::Composer * win = KMail::makeComposer();
  msg->setTransferInProgress(false); // From here on on, the composer owns the message.
  win->setMsg(msg, false, true);
  win->setFolder( parent );
  win->show();

  return OK;
}

KMUseTemplateCommand::KMUseTemplateCommand( TQWidget *parent, KMMessage *msg )
  :KMCommand( parent, msg )
{
}

KMCommand::Result KMUseTemplateCommand::execute()
{
  KMMessage *msg = retrievedMessage();
  if ( !msg || !msg->parent() ||
       !kmkernel->folderIsTemplates( msg->parent() ) )
    return Failed;

  // Take a copy of the original message, which remains unchanged.
  KMMessage *newMsg = new KMMessage( new DwMessage( *msg->asDwMessage() ) );
  newMsg->setComplete( msg->isComplete() );

  // these fields need to be regenerated for the new message
  newMsg->removeHeaderField("Date");
  newMsg->removeHeaderField("Message-ID");

  KMail::Composer *win = KMail::makeComposer();
  newMsg->setTransferInProgress( false ); // From here on on, the composer owns the message.
  win->setMsg( newMsg, false, true );
  win->show();

  return OK;
}

KMShowMsgSrcCommand::KMShowMsgSrcCommand( TQWidget *parent,
  KMMessage *msg, bool fixedFont )
  :KMCommand( parent, msg ), mFixedFont( fixedFont )
{
  // remember complete state
  mMsgWasComplete = msg->isComplete();
}

KMCommand::Result KMShowMsgSrcCommand::execute()
{
  KMMessage *msg = retrievedMessage();
  if ( !msg || !msg->codec() ) {
    return Failed;
  }
  if ( msg->isComplete() && !mMsgWasComplete )
    msg->notify(); // notify observers as msg was transfered
  TQString str = msg->codec()->toUnicode( msg->asString() );

  MailSourceViewer *viewer = new MailSourceViewer(); // deletes itself upon close
  viewer->setCaption( i18n("Message as Plain Text") );
  viewer->setText(str);
  if( mFixedFont )
    viewer->setFont(TDEGlobalSettings::fixedFont());

  // Well, there is no widget to be seen here, so we have to use TQCursor::pos()
  // Update: (GS) I'm not going to make this code behave according to Xinerama
  //         configuration because this is quite the hack.
  if (TQApplication::desktop()->isVirtualDesktop()) {
    int scnum = TQApplication::desktop()->screenNumber(TQCursor::pos());
    viewer->resize(TQApplication::desktop()->screenGeometry(scnum).width()/2,
                  2*TQApplication::desktop()->screenGeometry(scnum).height()/3);
  } else {
    viewer->resize(TQApplication::desktop()->geometry().width()/2,
                  2*TQApplication::desktop()->geometry().height()/3);
  }
  viewer->show();

  return OK;
}

static KURL subjectToUrl( const TQString & subject )
{
  // We need to replace colons with underscores since those cause problems with KFileDialog (bug
  // in KFileDialog though) and also on Windows filesystems.
  // We also look at the special case of ": ", since converting that to "_ " would look strange,
  // simply "_" looks better.
  // We also don't allow filenames starting with a dot, since then the file is hidden and the poor
  // user can't find it anymore.
  // Don't allow filenames starting with a tilde either, since that will cause the file dialog to
  // discard the filename entirely.
  // https://issues.kolab.org/issue3805
  const TQString filter = i18n( "*.mbox|email messages (*.mbox)\n*|all files (*)" );
  TQString cleanSubject = subject.stripWhiteSpace()
                                  .replace( TQDir::separator(), '_' )
                                  .replace( ": ", "_" )
                                  .replace( ':', '_' )
                                  .replace( '.', '_' )
                                  .replace( '~', '_' );
  return KFileDialog::getSaveURL( cleanSubject, filter );
}

KMSaveMsgCommand::KMSaveMsgCommand( TQWidget *parent, KMMessage * msg )
  : KMCommand( parent ),
    mMsgListIndex( 0 ),
    mStandAloneMessage( 0 ),
    mOffset( 0 ),
    mTotalSize( msg ? msg->msgSize() : 0 )
{
  if ( !msg ) return;
  setDeletesItself( true );
  // If the mail has a serial number, operate on sernums, if it does not
  // we need to work with the pointer, but can be reasonably sure it won't
  // go away, since it'll be an encapsulated message or one that was opened
  // from an .eml file.
  if ( msg->getMsgSerNum() != 0 ) {
    mMsgList.append( msg->getMsgSerNum() );
    if ( msg->parent() ) {
      msg->parent()->open( "kmsavemsgcommand" );
    }
  } else {
    mStandAloneMessage = msg;
  }
  mUrl = subjectToUrl( msg->cleanSubject() );
}

KMSaveMsgCommand::KMSaveMsgCommand( TQWidget *parent,
                                    const TQPtrList<KMMsgBase> &msgList )
  : KMCommand( parent ),
    mMsgListIndex( 0 ),
    mStandAloneMessage( 0 ),
    mOffset( 0 ),
    mTotalSize( 0 )
{
  if (!msgList.getFirst())
    return;
  setDeletesItself( true );
  KMMsgBase *msgBase = msgList.getFirst();

  // We operate on serNums and not the KMMsgBase pointers, as those can
  // change, or become invalid when changing the current message, switching
  // folders, etc.
  TQPtrListIterator<KMMsgBase> it(msgList);
  while ( it.current() ) {
    mMsgList.append( (*it)->getMsgSerNum() );
    mTotalSize += (*it)->msgSize();
    if ((*it)->parent() != 0)
      (*it)->parent()->open("kmcommand");
    ++it;
  }
  mMsgListIndex = 0;
  mUrl = subjectToUrl( msgBase->cleanSubject() );
}

KURL KMSaveMsgCommand::url()
{
  return mUrl;
}

KMCommand::Result KMSaveMsgCommand::execute()
{
  mJob = TDEIO::put( mUrl, S_IRUSR|S_IWUSR, false, false );
  mJob->slotTotalSize( mTotalSize );
  mJob->setAsyncDataEnabled( true );
  mJob->setReportDataSent( true );
  connect(mJob, TQT_SIGNAL(dataReq(TDEIO::Job*, TQByteArray &)),
    TQT_SLOT(slotSaveDataReq()));
  connect(mJob, TQT_SIGNAL(result(TDEIO::Job*)),
    TQT_SLOT(slotSaveResult(TDEIO::Job*)));
  setEmitsCompletedItself( true );
  return OK;
}

void KMSaveMsgCommand::slotSaveDataReq()
{
  int remainingBytes = mData.size() - mOffset;
  if ( remainingBytes > 0 ) {
    // eat leftovers first
    if ( remainingBytes > MAX_CHUNK_SIZE )
      remainingBytes = MAX_CHUNK_SIZE;

    TQByteArray data;
    data.duplicate( mData.data() + mOffset, remainingBytes );
    mJob->sendAsyncData( data );
    mOffset += remainingBytes;
    return;
  }
  // No leftovers, process next message.
  if ( mMsgListIndex < mMsgList.size() ) {
    KMMessage *msg = 0;
    int idx = -1;
    KMFolder * p = 0;
    KMMsgDict::instance()->getLocation( mMsgList[mMsgListIndex], &p, &idx );
    assert( p );
    assert( idx >= 0 );
    //kdDebug() << "SERNUM: " << mMsgList[mMsgListIndex] << " idx: " << idx << " folder: " << p->prettyURL() << endl;

    const bool alreadyGot = p->isMessage( idx );

    msg = p->getMsg(idx);

    if ( msg ) {
      // Only unGet the message if it isn't already got.
      if ( !alreadyGot ) {
        mUngetMsgs.append( msg );
      }
      if ( msg->transferInProgress() ) {
        TQByteArray data = TQByteArray();
        mJob->sendAsyncData( data );
      }
      msg->setTransferInProgress( true );
      if ( msg->isComplete() ) {
        slotMessageRetrievedForSaving( msg );
      } else {
        // retrieve Message first
        if ( msg->parent()  && !msg->isComplete() ) {
          FolderJob *job = msg->parent()->createJob( msg );
          job->setCancellable( false );
          connect(job, TQT_SIGNAL( messageRetrieved( KMMessage* ) ),
                  this, TQT_SLOT( slotMessageRetrievedForSaving( KMMessage* ) ) );
          job->start();
        }
      }
    } else {
      mJob->slotError( TDEIO::ERR_ABORTED,
                       i18n("The message was removed while saving it. "
                            "It has not been saved.") );
    }
  } else {
    if ( mStandAloneMessage ) {
      // do the special case of a standalone message
      slotMessageRetrievedForSaving( mStandAloneMessage );
      mStandAloneMessage = 0;
    } else {
      // No more messages. Tell the putjob we are done.
      TQByteArray data = TQByteArray();
      mJob->sendAsyncData( data );
    }
  }
}

void KMSaveMsgCommand::slotMessageRetrievedForSaving(KMMessage *msg)
{
  if ( msg ) {
    mData = KMFolderMbox::escapeFrom( msg->asDwString() );
    KMail::Util::insert( mData, 0, msg->mboxMessageSeparator() );
    KMail::Util::append( mData, "\n" );
    msg->setTransferInProgress(false);

    mOffset = 0;
    TQByteArray data;
    int size;
    // Unless it is great than 64 k send the whole message. tdeio buffers for us.
    if( mData.size() > (unsigned int) MAX_CHUNK_SIZE )
      size = MAX_CHUNK_SIZE;
    else
      size = mData.size();

    data.duplicate( mData, size );
    mJob->sendAsyncData( data );
    mOffset += size;
  }
  ++mMsgListIndex;
  // Get rid of the message.
  if ( msg && msg->parent() && msg->getMsgSerNum() &&
       mUngetMsgs.contains( msg ) ) {
    int idx = -1;
    KMFolder * p = 0;
    KMMsgDict::instance()->getLocation( msg, &p, &idx );
    assert( p == msg->parent() ); assert( idx >= 0 );
    p->unGetMsg( idx );
    p->close("kmcommand");
  }
}

void KMSaveMsgCommand::slotSaveResult(TDEIO::Job *job)
{
  if (job->error())
  {
    if (job->error() == TDEIO::ERR_FILE_ALREADY_EXIST)
    {
      if (KMessageBox::warningContinueCancel(0,
        i18n("File %1 exists.\nDo you want to replace it?")
        .arg(mUrl.prettyURL()), i18n("Save to File"), i18n("&Replace"))
        == KMessageBox::Continue) {
        mOffset = 0;

        mJob = TDEIO::put( mUrl, S_IRUSR|S_IWUSR, true, false );
        mJob->slotTotalSize( mTotalSize );
        mJob->setAsyncDataEnabled( true );
        mJob->setReportDataSent( true );
        connect(mJob, TQT_SIGNAL(dataReq(TDEIO::Job*, TQByteArray &)),
            TQT_SLOT(slotSaveDataReq()));
        connect(mJob, TQT_SIGNAL(result(TDEIO::Job*)),
            TQT_SLOT(slotSaveResult(TDEIO::Job*)));
      }
    }
    else
    {
      job->showErrorDialog();
      setResult( Failed );
      emit completed( this );
      deleteLater();
    }
  } else {
    setResult( OK );
    emit completed( this );
    deleteLater();
  }
}

//-----------------------------------------------------------------------------

KMOpenMsgCommand::KMOpenMsgCommand( TQWidget *parent, const KURL & url,
                                    const TQString & encoding )
  : KMCommand( parent ),
    mUrl( url ),
    mEncoding( encoding )
{
  setDeletesItself( true );
}

KMCommand::Result KMOpenMsgCommand::execute()
{
  if ( mUrl.isEmpty() ) {
    mUrl = KFileDialog::getOpenURL( ":OpenMessage", "message/rfc822 application/mbox",
                                    parentWidget(), i18n("Open Message") );
  }
  if ( mUrl.isEmpty() ) {
    setDeletesItself( false );
    return Canceled;
  }
  mJob = TDEIO::get( mUrl, false, false );
  mJob->setReportDataSent( true );
  connect( mJob, TQT_SIGNAL( data( TDEIO::Job *, const TQByteArray & ) ),
           this, TQT_SLOT( slotDataArrived( TDEIO::Job*, const TQByteArray & ) ) );
  connect( mJob, TQT_SIGNAL( result( TDEIO::Job * ) ),
           TQT_SLOT( slotResult( TDEIO::Job * ) ) );
  setEmitsCompletedItself( true );
  return OK;
}

void KMOpenMsgCommand::slotDataArrived( TDEIO::Job *, const TQByteArray & data )
{
  if ( data.isEmpty() )
    return;

  mMsgString.append( data.data(), data.size() );
}

void KMOpenMsgCommand::slotResult( TDEIO::Job *job )
{
  if ( job->error() ) {
    // handle errors
    job->showErrorDialog();
    setResult( Failed );
    emit completed( this );
  }
  else {
    int startOfMessage = 0;
    if ( mMsgString.compare( 0, 5, "From ", 5 ) == 0 ) {
      startOfMessage = mMsgString.find( '\n' );
      if ( startOfMessage == -1 ) {
        KMessageBox::sorry( parentWidget(),
                            i18n( "The file does not contain a message." ) );
        setResult( Failed );
        emit completed( this );
        // Emulate closing of a secondary window so that KMail exits in case it
        // was started with the --view command line option. Otherwise an
        // invisible KMail would keep running.
        SecondaryWindow *win = new SecondaryWindow();
        win->close();
        win->deleteLater();
        deleteLater();
        return;
      }
      startOfMessage += 1; // the message starts after the '\n'
    }
    // check for multiple messages in the file
    bool multipleMessages = true;
    int endOfMessage = mMsgString.find( "\nFrom " );
    if ( endOfMessage == -1 ) {
      endOfMessage = mMsgString.length();
      multipleMessages = false;
    }
    DwMessage *dwMsg = new DwMessage;
    dwMsg->FromString( mMsgString.substr( startOfMessage,
                                          endOfMessage - startOfMessage ) );
    dwMsg->Parse();
    // check whether we have a message ( no headers => this isn't a message )
    if ( dwMsg->Headers().NumFields() == 0 ) {
      KMessageBox::sorry( parentWidget(),
                          i18n( "The file does not contain a message." ) );
      delete dwMsg; dwMsg = 0;
      setResult( Failed );
      emit completed( this );
      // Emulate closing of a secondary window (see above).
      SecondaryWindow *win = new SecondaryWindow();
      win->close();
      win->deleteLater();
      deleteLater();
      return;
    }
    KMMessage *msg = new KMMessage( dwMsg );
    msg->setReadyToShow( true );
    KMReaderMainWin *win = new KMReaderMainWin();
    win->showMsg( mEncoding, msg );
    win->show();
    if ( multipleMessages )
      KMessageBox::information( win,
                                i18n( "The file contains multiple messages. "
                                      "Only the first message is shown." ) );
    setResult( OK );
    emit completed( this );
  }
  deleteLater();
}

//-----------------------------------------------------------------------------

//TODO: ReplyTo, NoQuoteReplyTo, ReplyList, ReplyToAll, ReplyAuthor
//      are all similar and should be factored
KMReplyToCommand::KMReplyToCommand( TQWidget *parent, KMMessage *msg,
                                    const TQString &selection )
  : KMCommand( parent, msg ), mSelection( selection )
{
}

KMCommand::Result KMReplyToCommand::execute()
{
  KCursorSaver busy(KBusyPtr::busy());
  KMMessage *msg = retrievedMessage();
  if ( !msg || !msg->codec() ) {
    return Failed;
  }

  // Find the account that held the original message
  TQString accountName;
  KMFolder* parentFolder = msg->parent();
  if (parentFolder) {
    KMFolderDir* parentFolderDir = parentFolder->parent();
    while (parentFolderDir) {
      TQString prettyURL = parentFolderDir->prettyURL();
      if (prettyURL != "") {
        accountName = prettyURL;
      }
      parentFolderDir = parentFolderDir->parent();
    }
  }

  KMMessage *reply = msg->createReply( KMail::ReplySmart, mSelection, false, true, TQString(), accountName );
  KMail::Composer * win = KMail::makeComposer( reply );
  win->setCharset( msg->codec()->mimeName(), true );
  win->setReplyFocus();
  win->show();

  return OK;
}


KMNoQuoteReplyToCommand::KMNoQuoteReplyToCommand( TQWidget *parent,
                                                  KMMessage *msg )
  : KMCommand( parent, msg )
{
}

KMCommand::Result KMNoQuoteReplyToCommand::execute()
{
  KCursorSaver busy(KBusyPtr::busy());
  KMMessage *msg = retrievedMessage();
  if ( !msg || !msg->codec() ) {
    return Failed;
  }
  KMMessage *reply = msg->createReply( KMail::ReplySmart, "", true);
  KMail::Composer * win = KMail::makeComposer( reply );
  win->setCharset(msg->codec()->mimeName(), true);
  win->setReplyFocus(false);
  win->show();

  return OK;
}


KMReplyListCommand::KMReplyListCommand( TQWidget *parent,
  KMMessage *msg, const TQString &selection )
 : KMCommand( parent, msg ), mSelection( selection )
{
}

KMCommand::Result KMReplyListCommand::execute()
{
  KCursorSaver busy(KBusyPtr::busy());
  KMMessage *msg = retrievedMessage();
  if ( !msg || !msg->codec() ) {
    return Failed;
  }
  KMMessage *reply = msg->createReply( KMail::ReplyList, mSelection);
  KMail::Composer * win = KMail::makeComposer( reply );
  win->setCharset(msg->codec()->mimeName(), true);
  win->setReplyFocus(false);
  win->show();

  return OK;
}


KMReplyToAllCommand::KMReplyToAllCommand( TQWidget *parent,
  KMMessage *msg, const TQString &selection )
  :KMCommand( parent, msg ), mSelection( selection )
{
}

KMCommand::Result KMReplyToAllCommand::execute()
{
  KCursorSaver busy(KBusyPtr::busy());
  KMMessage *msg = retrievedMessage();
  if ( !msg || !msg->codec() ) {
    return Failed;
  }
  KMMessage *reply = msg->createReply( KMail::ReplyAll, mSelection );
  KMail::Composer * win = KMail::makeComposer( reply );
  win->setCharset( msg->codec()->mimeName(), true );
  win->setReplyFocus();
  win->show();

  return OK;
}


KMReplyAuthorCommand::KMReplyAuthorCommand( TQWidget *parent, KMMessage *msg,
                                            const TQString &selection )
  : KMCommand( parent, msg ), mSelection( selection )
{
}

KMCommand::Result KMReplyAuthorCommand::execute()
{
  KCursorSaver busy(KBusyPtr::busy());
  KMMessage *msg = retrievedMessage();
  if ( !msg || !msg->codec() ) {
    return Failed;
  }
  KMMessage *reply = msg->createReply( KMail::ReplyAuthor, mSelection );
  KMail::Composer * win = KMail::makeComposer( reply );
  win->setCharset( msg->codec()->mimeName(), true );
  win->setReplyFocus();
  win->show();

  return OK;
}


KMForwardInlineCommand::KMForwardInlineCommand( TQWidget *parent,
  const TQPtrList<KMMsgBase> &msgList, uint identity )
  : KMCommand( parent, msgList ),
    mIdentity( identity )
{
}

KMForwardInlineCommand::KMForwardInlineCommand( TQWidget *parent,
  KMMessage *msg, uint identity )
  : KMCommand( parent, msg ),
    mIdentity( identity )
{
}

KMCommand::Result KMForwardInlineCommand::execute()
{
  TQPtrList<KMMessage> msgList = retrievedMsgs();

  if (msgList.count() >= 2) { // Multiple forward

    uint id = 0;
    TQPtrList<KMMessage> linklist;
    for ( KMMessage *msg = msgList.first(); msg; msg = msgList.next() ) {
      // set the identity
      if (id == 0)
        id = msg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt();

      // msgText += msg->createForwardBody();
      linklist.append( msg );
    }
    if ( id == 0 )
      id = mIdentity; // use folder identity if no message had an id set
    KMMessage *fwdMsg = new KMMessage;
    fwdMsg->initHeader( id );
    fwdMsg->setAutomaticFields( true );
    fwdMsg->setCharset( "utf-8" );
    // fwdMsg->setBody( msgText );

    for ( KMMessage *msg = linklist.first(); msg; msg = linklist.next() ) {
      TemplateParser parser( fwdMsg, TemplateParser::Forward );
      parser.setSelection( msg->body() ); // FIXME: Why is this needed?
      parser.process( msg, 0, true );

      fwdMsg->link( msg, KMMsgStatusForwarded );
    }

    KCursorSaver busy( KBusyPtr::busy() );
    KMail::Composer * win = KMail::makeComposer( fwdMsg, id );
    win->setCharset("");
    win->show();

  } else { // forward a single message at most

    KMMessage *msg = msgList.getFirst();
    if ( !msg || !msg->codec() )
      return Failed;

    KCursorSaver busy( KBusyPtr::busy() );
    KMMessage *fwdMsg = msg->createForward();

    uint id = msg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt();
    if ( id == 0 )
      id = mIdentity;
    {
      KMail::Composer * win = KMail::makeComposer( fwdMsg, id );
      win->setCharset( fwdMsg->codec()->mimeName(), true );
      win->show();
    }
  }
  return OK;
}


KMForwardAttachedCommand::KMForwardAttachedCommand( TQWidget *parent,
           const TQPtrList<KMMsgBase> &msgList, uint identity, KMail::Composer *win )
  : KMCommand( parent, msgList ), mIdentity( identity ),
    mWin( TQGuardedPtr<KMail::Composer>( win ))
{
}

KMForwardAttachedCommand::KMForwardAttachedCommand( TQWidget *parent,
           KMMessage * msg, uint identity, KMail::Composer *win )
  : KMCommand( parent, msg ), mIdentity( identity ),
    mWin( TQGuardedPtr< KMail::Composer >( win ))
{
}

KMCommand::Result KMForwardAttachedCommand::execute()
{
  TQPtrList<KMMessage> msgList = retrievedMsgs();
  KMMessage *fwdMsg = new KMMessage;

  if (msgList.count() >= 2) {
    // don't respect X-KMail-Identity headers because they might differ for
    // the selected mails
    fwdMsg->initHeader(mIdentity);
  }
  else if (msgList.count() == 1) {
    KMMessage *msg = msgList.getFirst();
    fwdMsg->initFromMessage(msg);
    fwdMsg->setSubject( msg->forwardSubject() );
  }

  fwdMsg->setAutomaticFields(true);

  KCursorSaver busy(KBusyPtr::busy());
  if (!mWin)
    mWin = KMail::makeComposer(fwdMsg, mIdentity);

  // iterate through all the messages to be forwarded
  for (KMMessage *msg = msgList.first(); msg; msg = msgList.next()) {
    // remove headers that shouldn't be forwarded
    msg->removePrivateHeaderFields();
    msg->removeHeaderField("BCC");
    // set the part
    KMMessagePart *msgPart = new KMMessagePart;
    msgPart->setTypeStr("message");
    msgPart->setSubtypeStr("rfc822");
    msgPart->setName("forwarded message");
    msgPart->setContentDescription(msg->from()+": "+msg->subject());
    msgPart->setContentDisposition( "inline" );
    msgPart->setMessageBody( KMail::Util::ByteArray( msg->asDwString() ) );

    fwdMsg->link(msg, KMMsgStatusForwarded);
    mWin->addAttach(msgPart);
  }

  mWin->show();

  return OK;
}


KMForwardDigestCommand::KMForwardDigestCommand( TQWidget *parent,
           const TQPtrList<KMMsgBase> &msgList, uint identity, KMail::Composer *win )
  : KMCommand( parent, msgList ), mIdentity( identity ),
    mWin( TQGuardedPtr<KMail::Composer>( win ))
{
}

KMForwardDigestCommand::KMForwardDigestCommand( TQWidget *parent,
           KMMessage * msg, uint identity, KMail::Composer *win )
  : KMCommand( parent, msg ), mIdentity( identity ),
    mWin( TQGuardedPtr< KMail::Composer >( win ))
{
}

KMCommand::Result KMForwardDigestCommand::execute()
{
  TQPtrList<KMMessage> msgList = retrievedMsgs();

  if ( msgList.count() < 2 )
    return Undefined; // must have more than 1 for a digest

  uint id = 0;
  KMMessage *fwdMsg = new KMMessage;
  KMMessagePart *msgPart = new KMMessagePart;
  TQString msgPartText;
  int msgCnt = 0; // incase there are some we can't forward for some reason

  // dummy header initialization; initialization with the correct identity
  // is done below
  fwdMsg->initHeader( id );
  fwdMsg->setAutomaticFields( true );
  fwdMsg->mMsg->Headers().ContentType().CreateBoundary( 1 );
  TQCString boundary( fwdMsg->mMsg->Headers().ContentType().Boundary().c_str() );
  msgPartText = i18n("\nThis is a MIME digest forward. The content of the"
                     " message is contained in the attachment(s).\n\n\n");
  // iterate through all the messages to be forwarded
  for ( KMMessage *msg = msgList.first(); msg; msg = msgList.next() ) {
    // set the identity
    if ( id == 0 )
      id = msg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt();
    // set the part header
    msgPartText += "--";
    msgPartText += TQString::fromLatin1( boundary );
    msgPartText += "\nContent-Type: MESSAGE/RFC822";
    msgPartText += TQString( "; CHARSET=%1" ).arg( TQString(msg->charset()) );
    msgPartText += '\n';
    DwHeaders dwh;
    dwh.MessageId().CreateDefault();
    msgPartText += TQString( "Content-ID: %1\n" ).arg( dwh.MessageId().AsString().c_str() );
    msgPartText += TQString( "Content-Description: %1" ).arg( msg->subject() );
    if ( !msg->subject().contains( "(fwd)" ) )
      msgPartText += " (fwd)";
    msgPartText += "\n\n";
    // remove headers that shouldn't be forwarded
    msg->removePrivateHeaderFields();
    msg->removeHeaderField( "BCC" );
    // set the part
    msgPartText += msg->headerAsString();
    msgPartText += '\n';
    msgPartText += msg->body();
    msgPartText += '\n';     // eot
    msgCnt++;
    fwdMsg->link( msg, KMMsgStatusForwarded );
  }

  if ( id == 0 )
    id = mIdentity; // use folder identity if no message had an id set
  fwdMsg->initHeader( id );
  msgPartText += "--";
  msgPartText += TQString::fromLatin1( boundary );
  msgPartText += "--\n";
  TQCString tmp;
  msgPart->setTypeStr( "MULTIPART" );
  tmp.sprintf( "Digest; boundary=\"%s\"", boundary.data() );
  msgPart->setSubtypeStr( tmp );
  msgPart->setName( "unnamed" );
  msgPart->setCte( DwMime::kCte7bit );   // does it have to be 7bit?
  msgPart->setContentDescription( TQString( "Digest of %1 messages." ).arg( msgCnt ) );
  // THIS HAS TO BE AFTER setCte()!!!!
  msgPart->setBodyEncoded( TQCString( msgPartText.ascii() ) );
  KCursorSaver busy( KBusyPtr::busy() );
  KMail::Composer * win = KMail::makeComposer( fwdMsg, id );
  win->addAttach( msgPart );
  win->show();
  return OK;
}

KMRedirectCommand::KMRedirectCommand( TQWidget *parent,
                                      KMMessage *msg )
  : KMCommand( parent, msg )
{
}

KMCommand::Result KMRedirectCommand::execute()
{
  KMMessage *msg = retrievedMessage();
  if ( !msg || !msg->codec() )
    return Failed;

  RedirectDialog dlg( parentWidget(), "redirect", true,
                      kmkernel->msgSender()->sendImmediate() );
  if (dlg.exec()==TQDialog::Rejected) return Failed;

  KMMessage *newMsg = msg->createRedirect( dlg.to() );
  KMFilterAction::sendMDN( msg, KMime::MDN::Dispatched );

  const KMail::MessageSender::SendMethod method = dlg.sendImmediate()
    ? KMail::MessageSender::SendImmediate
    : KMail::MessageSender::SendLater;
  if ( !kmkernel->msgSender()->send( newMsg, method ) ) {
    kdDebug(5006) << "KMRedirectCommand: could not redirect message (sending failed)" << endl;
    return Failed; // error: couldn't send
  }
  return OK;
}


KMCustomReplyToCommand::KMCustomReplyToCommand( TQWidget *parent, KMMessage *msg,
                                                const TQString &selection,
                                                const TQString &tmpl )
  : KMCommand( parent, msg ), mSelection( selection ), mTemplate( tmpl )
{
}

KMCommand::Result KMCustomReplyToCommand::execute()
{
  KCursorSaver busy(KBusyPtr::busy());
  KMMessage *msg = retrievedMessage();
  if ( !msg || !msg->codec() ) {
    return Failed;
  }
  KMMessage *reply = msg->createReply( KMail::ReplySmart, mSelection,
                                       false, true, mTemplate );
  KMail::Composer * win = KMail::makeComposer( reply );
  win->setCharset( msg->codec()->mimeName(), true );
  win->setReplyFocus();
  win->show();

  return OK;
}


KMCustomReplyAllToCommand::KMCustomReplyAllToCommand( TQWidget *parent, KMMessage *msg,
                                                      const TQString &selection,
                                                      const TQString &tmpl )
  : KMCommand( parent, msg ), mSelection( selection ), mTemplate( tmpl )
{
}

KMCommand::Result KMCustomReplyAllToCommand::execute()
{
  KCursorSaver busy(KBusyPtr::busy());
  KMMessage *msg = retrievedMessage();
  if ( !msg || !msg->codec() ) {
    return Failed;
  }
  KMMessage *reply = msg->createReply( KMail::ReplyAll, mSelection,
                                       false, true, mTemplate );
  KMail::Composer * win = KMail::makeComposer( reply );
  win->setCharset( msg->codec()->mimeName(), true );
  win->setReplyFocus();
  win->show();

  return OK;
}


KMCustomForwardCommand::KMCustomForwardCommand( TQWidget *parent,
  const TQPtrList<KMMsgBase> &msgList, uint identity, const TQString &tmpl )
  : KMCommand( parent, msgList ),
    mIdentity( identity ), mTemplate( tmpl )
{
}

KMCustomForwardCommand::KMCustomForwardCommand( TQWidget *parent,
  KMMessage *msg, uint identity, const TQString &tmpl )
  : KMCommand( parent, msg ),
    mIdentity( identity ), mTemplate( tmpl )
{
}

KMCommand::Result KMCustomForwardCommand::execute()
{
  TQPtrList<KMMessage> msgList = retrievedMsgs();

  if (msgList.count() >= 2) { // Multiple forward

    uint id = 0;
    TQPtrList<KMMessage> linklist;
    for ( KMMessage *msg = msgList.first(); msg; msg = msgList.next() ) {
      // set the identity
      if (id == 0)
        id = msg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt();

      // msgText += msg->createForwardBody();
      linklist.append( msg );
    }
    if ( id == 0 )
      id = mIdentity; // use folder identity if no message had an id set
    KMMessage *fwdMsg = new KMMessage;
    fwdMsg->initHeader( id );
    fwdMsg->setAutomaticFields( true );
    fwdMsg->setCharset( "utf-8" );
    // fwdMsg->setBody( msgText );

    for ( KMMessage *msg = linklist.first(); msg; msg = linklist.next() ) {
      TemplateParser parser( fwdMsg, TemplateParser::Forward );
      parser.setSelection( msg->body() ); // FIXME: Why is this needed?
      parser.process( msg, 0, true );

      fwdMsg->link( msg, KMMsgStatusForwarded );
    }

    KCursorSaver busy( KBusyPtr::busy() );
    KMail::Composer * win = KMail::makeComposer( fwdMsg, id );
    win->setCharset("");
    win->show();

  } else { // forward a single message at most

    KMMessage *msg = msgList.getFirst();
    if ( !msg || !msg->codec() )
      return Failed;

    KCursorSaver busy( KBusyPtr::busy() );
    KMMessage *fwdMsg = msg->createForward( mTemplate );

    uint id = msg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt();
    if ( id == 0 )
      id = mIdentity;
    {
      KMail::Composer * win = KMail::makeComposer( fwdMsg, id );
      win->setCharset( fwdMsg->codec()->mimeName(), true );
      win->show();
    }
  }
  return OK;
}


KMPrintCommand::KMPrintCommand( TQWidget *parent, KMMessage *msg,
                                const KMail::HeaderStyle *headerStyle,
                                const KMail::HeaderStrategy *headerStrategy,
                                bool htmlOverride, bool htmlLoadExtOverride,
                                bool useFixedFont, const TQString & encoding )
  : KMCommand( parent, msg ),
    mHeaderStyle( headerStyle ), mHeaderStrategy( headerStrategy ),
    mHtmlOverride( htmlOverride ),
    mHtmlLoadExtOverride( htmlLoadExtOverride ),
    mUseFixedFont( useFixedFont ), mEncoding( encoding )
{
  if ( GlobalSettings::useDefaultFonts() )
    mOverrideFont = TDEGlobalSettings::generalFont();
  else {
    TDEConfigGroup fonts( KMKernel::config(), "Fonts" );
    TQString tmp = fonts.readEntry( "print-font", TDEGlobalSettings::generalFont().toString() );
    mOverrideFont.fromString( tmp );
  }
}


void KMPrintCommand::setOverrideFont( const TQFont& font )
{
  mOverrideFont = font;
}

KMCommand::Result KMPrintCommand::execute()
{
  KMReaderWin printWin( 0, 0, 0 );
  printWin.setPrinting( true );
  printWin.readConfig();
  if ( mHeaderStyle != 0 && mHeaderStrategy != 0 )
    printWin.setHeaderStyleAndStrategy( mHeaderStyle, mHeaderStrategy );
  printWin.setHtmlOverride( mHtmlOverride );
  printWin.setHtmlLoadExtOverride( mHtmlLoadExtOverride );
  printWin.setUseFixedFont( mUseFixedFont );
  printWin.setOverrideEncoding( mEncoding );
  printWin.cssHelper()->setPrintFont( mOverrideFont );
  printWin.setDecryptMessageOverwrite( true );
  printWin.setMsg( retrievedMessage(), true );
  printWin.printMsg();

  return OK;
}


KMSeStatusCommand::KMSeStatusCommand( KMMsgStatus status,
  const TQValueList<TQ_UINT32> &serNums, bool toggle )
  : mStatus( status ), mSerNums( serNums ), mToggle( toggle )
{
}

KMCommand::Result KMSeStatusCommand::execute()
{
  TQValueListIterator<TQ_UINT32> it;
  int idx = -1;
  KMFolder *folder = 0;
  bool parenStatus = false;

  // Toggle actions on threads toggle the whole thread
  // depending on the state of the parent.
  if (mToggle) {
    KMMsgBase *msg;
    KMMsgDict::instance()->getLocation( *mSerNums.begin(), &folder, &idx );
    if (folder) {
      msg = folder->getMsgBase(idx);
      if (msg && (msg->status()&mStatus))
        parenStatus = true;
      else
        parenStatus = false;
    }
  }
  TQMap< KMFolder*, TQValueList<int> > folderMap;
  for ( it = mSerNums.begin(); it != mSerNums.end(); ++it ) {
    KMMsgDict::instance()->getLocation( *it, &folder, &idx );
    if (folder) {
      if (mToggle) {
        KMMsgBase *msg = folder->getMsgBase(idx);
        // check if we are already at the target toggle state
        if (msg) {
          bool myStatus;
          if (msg->status()&mStatus)
            myStatus = true;
          else
            myStatus = false;
          if (myStatus != parenStatus)
            continue;
        }
      }
      /* Collect the ids for each folder in a separate list and
         send them off in one go at the end. */
      folderMap[folder].append(idx);
    }
  }
  TQMapIterator< KMFolder*, TQValueList<int> > it2 = folderMap.begin();
  while ( it2 != folderMap.end() ) {
     KMFolder *f = it2.key();
     f->setStatus( (*it2), mStatus, mToggle );
     ++it2;
  }
  //kapp->dcopClient()->emitDCOPSignal( "unreadCountChanged()", TQByteArray() );

  return OK;
}


KMFilterCommand::KMFilterCommand( const TQCString &field, const TQString &value )
  : mField( field ), mValue( value )
{
}

KMCommand::Result KMFilterCommand::execute()
{
  kmkernel->filterMgr()->createFilter( mField, mValue );

  return OK;
}


KMFilterActionCommand::KMFilterActionCommand( TQWidget *parent,
                                              const TQPtrList<KMMsgBase> &msgList,
                                              KMFilter *filter )
  : KMCommand( parent, msgList ), mFilter( filter  )
{
  TQPtrListIterator<KMMsgBase> it(msgList);
  while ( it.current() ) {
    serNumList.append( (*it)->getMsgSerNum() );
    ++it;
  }
}

KMCommand::Result KMFilterActionCommand::execute()
{
  KCursorSaver busy( KBusyPtr::busy() );

  int msgCount = 0;
  int msgCountToFilter = serNumList.count();
  ProgressItem* progressItem =
    ProgressManager::createProgressItem ( "filter"+ProgressManager::getUniqueID(),
                                          i18n( "Filtering messages" ) );
  progressItem->setTotalItems( msgCountToFilter );
  TQValueList<TQ_UINT32>::const_iterator it;
  for ( it = serNumList.begin(); it != serNumList.end(); it++ ) {
    TQ_UINT32 serNum = *it;
    int diff = msgCountToFilter - ++msgCount;
    if ( diff < 10 || !( msgCount % 20 ) || msgCount <= 10 ) {
      progressItem->updateProgress();
      TQString statusMsg = i18n("Filtering message %1 of %2");
      statusMsg = statusMsg.arg( msgCount ).arg( msgCountToFilter );
      KPIM::BroadcastStatus::instance()->setStatusMsg( statusMsg );
      TDEApplication::kApplication()->eventLoop()->processEvents( TQEventLoop::ExcludeUserInput, 50 );
    }

    int filterResult = kmkernel->filterMgr()->process( serNum, mFilter );
    if (filterResult == 2) {
      // something went horribly wrong (out of space?)
      perror("Critical error");
      kmkernel->emergencyExit( i18n("Not enough free disk space?" ));
    }
    progressItem->incCompletedItems();
  }

  progressItem->setComplete();
  progressItem = 0;
  return OK;
}


KMMetaFilterActionCommand::KMMetaFilterActionCommand( KMFilter *filter,
                                                      KMHeaders *headers,
                                                      KMMainWidget *main )
    : TQObject( main ),
      mFilter( filter ), mHeaders( headers ), mMainWidget( main )
{
}

void KMMetaFilterActionCommand::start()
{
  if (ActionScheduler::isEnabled() ) {
    // use action scheduler
    KMFilterMgr::FilterSet set = KMFilterMgr::All;
    TQValueList<KMFilter*> filters;
    filters.append( mFilter );
    ActionScheduler *scheduler = new ActionScheduler( set, filters, mHeaders );
    scheduler->setAlwaysMatch( true );
    scheduler->setAutoDestruct( true );

    int contentX, contentY;
    HeaderItem *nextItem = mHeaders->prepareMove( &contentX, &contentY );
    TQPtrList<KMMsgBase> msgList = *mHeaders->selectedMsgs(true);
    mHeaders->finalizeMove( nextItem, contentX, contentY );

    for (KMMsgBase *msg = msgList.first(); msg; msg = msgList.next())
      scheduler->execFilters( msg );
  } else {
    KMCommand *filterCommand =
      new KMFilterActionCommand( mMainWidget,
                                 *mHeaders->selectedMsgs(), mFilter );
    filterCommand->start();
    int contentX, contentY;
    HeaderItem *item = mHeaders->prepareMove( &contentX, &contentY );
    mHeaders->finalizeMove( item, contentX, contentY );
  }
}

FolderShortcutCommand::FolderShortcutCommand( KMMainWidget *mainwidget,
                                              KMFolder *folder )
    : mMainWidget( mainwidget ), mFolder( folder ), mAction( 0 )
{
}


FolderShortcutCommand::~FolderShortcutCommand()
{
  if ( mAction ) mAction->unplugAll();
  delete mAction;
}

void FolderShortcutCommand::start()
{
  mMainWidget->slotSelectFolder( mFolder );
}

void FolderShortcutCommand::setAction( TDEAction* action )
{
  mAction = action;
}

KMMailingListFilterCommand::KMMailingListFilterCommand( TQWidget *parent,
                                                        KMMessage *msg )
  : KMCommand( parent, msg )
{
}

KMCommand::Result KMMailingListFilterCommand::execute()
{
  TQCString name;
  TQString value;
  KMMessage *msg = retrievedMessage();
  if (!msg)
    return Failed;

  if ( !MailingList::name( msg, name, value ).isEmpty() ) {
    kmkernel->filterMgr()->createFilter( name, value );
    return OK;
  }
  else
    return Failed;
}


void KMMenuCommand::folderToPopupMenu(bool move,
  TQObject *receiver, KMMenuToFolder *aMenuToFolder, TQPopupMenu *menu )
{
  while ( menu->count() )
  {
    TQPopupMenu *popup = menu->findItem( menu->idAt( 0 ) )->popup();
    if (popup)
      delete popup;
    else
      menu->removeItemAt( 0 );
  }

  if (!kmkernel->imapFolderMgr()->dir().first() &&
      !kmkernel->dimapFolderMgr()->dir().first())
  { // only local folders
    makeFolderMenu( &kmkernel->folderMgr()->dir(), move,
                    receiver, aMenuToFolder, menu );
  } else {
    // operate on top-level items
    TQPopupMenu* subMenu = new TQPopupMenu(menu);
    makeFolderMenu( &kmkernel->folderMgr()->dir(),
                    move, receiver, aMenuToFolder, subMenu );
    menu->insertItem( i18n( "Local Folders" ), subMenu );
    KMFolderDir* fdir = &kmkernel->imapFolderMgr()->dir();
    for (KMFolderNode *node = fdir->first(); node; node = fdir->next()) {
      if (node->isDir())
        continue;
      subMenu = new TQPopupMenu(menu);
      makeFolderMenu( node, move, receiver, aMenuToFolder, subMenu );
      menu->insertItem( node->label(), subMenu );
    }
    fdir = &kmkernel->dimapFolderMgr()->dir();
    for (KMFolderNode *node = fdir->first(); node; node = fdir->next()) {
      if (node->isDir())
        continue;
      subMenu = new TQPopupMenu(menu);
      makeFolderMenu( node, move, receiver, aMenuToFolder, subMenu );
      menu->insertItem( node->label(), subMenu );
    }
  }
}

void KMMenuCommand::makeFolderMenu(KMFolderNode* node, bool move,
  TQObject *receiver, KMMenuToFolder *aMenuToFolder, TQPopupMenu *menu )
{
  // connect the signals
  if (move)
  {
    disconnect(menu, TQT_SIGNAL(activated(int)), receiver,
           TQT_SLOT(moveSelectedToFolder(int)));
    connect(menu, TQT_SIGNAL(activated(int)), receiver,
             TQT_SLOT(moveSelectedToFolder(int)));
  } else {
    disconnect(menu, TQT_SIGNAL(activated(int)), receiver,
           TQT_SLOT(copySelectedToFolder(int)));
    connect(menu, TQT_SIGNAL(activated(int)), receiver,
             TQT_SLOT(copySelectedToFolder(int)));
  }

  KMFolder *folder = 0;
  KMFolderDir *folderDir = 0;
  if (node->isDir()) {
    folderDir = static_cast<KMFolderDir*>(node);
  } else {
    folder = static_cast<KMFolder*>(node);
    folderDir = folder->child();
  }

  if (folder && !folder->noContent())
  {
    int menuId;
    if (move)
      menuId = menu->insertItem(i18n("Move to This Folder"));
    else
      menuId = menu->insertItem(i18n("Copy to This Folder"));
    aMenuToFolder->insert( menuId, folder );
    menu->setItemEnabled( menuId, !folder->isReadOnly() );
    menu->insertSeparator();
  }

  if (!folderDir)
    return;

  for (KMFolderNode *it = folderDir->first(); it; it = folderDir->next() ) {
    if (it->isDir())
      continue;
    KMFolder *child = static_cast<KMFolder*>(it);
    TQString label = child->label();
    label.replace("&","&&");
    if (child->child() && child->child()->first()) {
      // descend
      TQPopupMenu *subMenu = new TQPopupMenu(menu, "subMenu");
      makeFolderMenu( child, move, receiver,
                      aMenuToFolder, subMenu );
      menu->insertItem( label, subMenu );
    } else {
      // insert an item
      int menuId = menu->insertItem( label );
      aMenuToFolder->insert( menuId, child );
      menu->setItemEnabled( menuId, !child->isReadOnly() );
    }
  }
  return;
}


KMCopyCommand::KMCopyCommand( KMFolder* destFolder,
                              const TQPtrList<KMMsgBase> &msgList )
:mDestFolder( destFolder ), mMsgList( msgList )
{
  setDeletesItself( true );
}

KMCopyCommand::KMCopyCommand( KMFolder* destFolder, KMMessage * msg )
  :mDestFolder( destFolder )
{
  setDeletesItself( true );
  mMsgList.append( &msg->toMsgBase() );
}

KMCommand::Result KMCopyCommand::execute()
{
  KMMsgBase *msgBase;
  KMMessage *msg, *newMsg;
  int idx = -1;
  bool isMessage;
  TQPtrList<KMMessage> list;
  TQPtrList<KMMessage> localList;

  if (mDestFolder && mDestFolder->open("kmcommand") != 0)
  {
    deleteLater();
    return Failed;
  }

  setEmitsCompletedItself( true );
  KCursorSaver busy(KBusyPtr::busy());

  for (msgBase = mMsgList.first(); msgBase; msgBase = mMsgList.next() )
  {
    KMFolder *srcFolder = msgBase->parent();
    if (( isMessage = msgBase->isMessage() ))
    {
      msg = static_cast<KMMessage*>(msgBase);
    } else {
      idx = srcFolder->find(msgBase);
      assert(idx != -1);
      msg = srcFolder->getMsg(idx);
      // corrupt IMAP cache, see FolderStorage::getMsg()
      if ( msg == 0 ) {
        KMessageBox::error( parentWidget(), i18n("Corrupt IMAP cache detected in folder %1. "
            "Copying of messages aborted.").arg( srcFolder->prettyURL() ) );
        deleteLater();
        return Failed;
      }
    }

    if (srcFolder && mDestFolder &&
        (srcFolder->folderType()== KMFolderTypeImap) &&
        (mDestFolder->folderType() == KMFolderTypeImap) &&
        (static_cast<KMFolderImap*>(srcFolder->storage())->account() ==
         static_cast<KMFolderImap*>(mDestFolder->storage())->account()))
    {
      // imap => imap with same account
      list.append(msg);
    } else {
      newMsg = new KMMessage( new DwMessage( *msg->asDwMessage() ) );
      newMsg->setComplete(msg->isComplete());
      // make sure the attachment state is only calculated when it's complete
      if (!newMsg->isComplete())
        newMsg->setReadyToShow(false);
      newMsg->setStatus(msg->status());

      if (srcFolder && !newMsg->isComplete())
      {
        // imap => others
        newMsg->setParent(msg->parent());
        FolderJob *job = srcFolder->createJob(newMsg);
        job->setCancellable( false );
        mPendingJobs << job;
        connect(job, TQT_SIGNAL(messageRetrieved(KMMessage*)),
                mDestFolder, TQT_SLOT(reallyAddCopyOfMsg(KMMessage*)));
        connect( job, TQT_SIGNAL(result(KMail::FolderJob*)),
                 this, TQT_SLOT(slotJobFinished(KMail::FolderJob*)) );
        job->start();
      } else {
        // local => others
        localList.append(newMsg);
      }
    }

    if (srcFolder && !isMessage && list.isEmpty())
    {
      assert(idx != -1);
      srcFolder->unGetMsg( idx );
    }

  } // end for

  bool deleteNow = false;
  if (!localList.isEmpty())
  {
    TQValueList<int> index;
    mDestFolder->addMsg( localList, index );
    for ( TQValueListIterator<int> it = index.begin(); it != index.end(); ++it ) {
      mDestFolder->unGetMsg( *it );
    }
    if ( mDestFolder->folderType() == KMFolderTypeImap ) {
      if ( mPendingJobs.isEmpty() ) {
        // wait for the end of the copy before closing the folder
        KMFolderImap *imapDestFolder = static_cast<KMFolderImap*>(mDestFolder->storage());
        connect( imapDestFolder, TQT_SIGNAL( folderComplete( KMFolderImap*, bool ) ),
            this, TQT_SLOT( slotFolderComplete( KMFolderImap*, bool ) ) );
      }
    } else {
      deleteNow = list.isEmpty() && mPendingJobs.isEmpty(); // we're done if there are no other mails we need to fetch
    }
  }

//TODO: Get rid of the other cases just use this one for all types of folder
//TODO: requires adding copyMsg and getFolder methods to KMFolder.h
  if (!list.isEmpty())
  {
    // copy the message(s); note: the list is empty afterwards!
    KMFolderImap *imapDestFolder = static_cast<KMFolderImap*>(mDestFolder->storage());
    connect( imapDestFolder, TQT_SIGNAL( folderComplete( KMFolderImap*, bool ) ),
        this, TQT_SLOT( slotFolderComplete( KMFolderImap*, bool ) ) );
    imapDestFolder->copyMsg(list);
    imapDestFolder->getFolder();
  }

  // only close the folder and delete the job if we're done
  // otherwise this is done in slotMsgAdded or slotFolderComplete
  if ( deleteNow )
  {
    mDestFolder->close("kmcommand");
    setResult( OK );
    emit completed( this );
    deleteLater();
  }

  return OK;
}

void KMCopyCommand::slotJobFinished(KMail::FolderJob * job)
{
  mPendingJobs.remove( job );
  if ( job->error() ) {
    kdDebug(5006) << k_funcinfo << "folder job failed: " << job->error() << endl;
    // kill all pending jobs
    for ( TQValueList<KMail::FolderJob*>::Iterator it = mPendingJobs.begin(); it != mPendingJobs.end(); ++it ) {
      disconnect( (*it), TQT_SIGNAL(result(KMail::FolderJob*)),
                  this, TQT_SLOT(slotJobFinished(KMail::FolderJob*)) );
      (*it)->kill();
    }
    mPendingJobs.clear();
    setResult( Failed );
  }

  if ( mPendingJobs.isEmpty() )
  {
    mDestFolder->close("kmcommand");
    emit completed( this );
    deleteLater();
  }
}

void KMCopyCommand::slotFolderComplete( KMFolderImap*, bool success )
{
  kdDebug(5006) << k_funcinfo << success << endl;
  if ( !success )
    setResult( Failed );
  mDestFolder->close( "kmcommand" );
  emit completed( this );
  deleteLater();
}


KMMoveCommand::KMMoveCommand( KMFolder* destFolder,
                              const TQPtrList<KMMsgBase> &msgList)
  : mDestFolder( destFolder ), mProgressItem( 0 )
{
  TQPtrList<KMMsgBase> tmp = msgList;
  for ( KMMsgBase *msgBase = tmp.first(); msgBase; msgBase = tmp.next() )
    mSerNumList.append( msgBase->getMsgSerNum() );
}

KMMoveCommand::KMMoveCommand( KMFolder* destFolder,
                              KMMessage *msg )
  : mDestFolder( destFolder ), mProgressItem( 0 )
{
  mSerNumList.append( msg->getMsgSerNum() );
}

KMMoveCommand::KMMoveCommand( KMFolder* destFolder,
                              KMMsgBase *msgBase )
  : mDestFolder( destFolder ), mProgressItem( 0 )
{
  mSerNumList.append( msgBase->getMsgSerNum() );
}

KMMoveCommand::KMMoveCommand( TQ_UINT32 )
  : mProgressItem( 0 )
{
}

KMCommand::Result KMMoveCommand::execute()
{
  setEmitsCompletedItself( true );
  setDeletesItself( true );
  typedef TQMap< KMFolder*, TQPtrList<KMMessage>* > FolderToMessageListMap;
  FolderToMessageListMap folderDeleteList;

  if (mDestFolder && mDestFolder->open("kmcommand") != 0) {
    completeMove( Failed );
    return Failed;
  }
  KCursorSaver busy(KBusyPtr::busy());

  // TODO set SSL state according to source and destfolder connection?
  Q_ASSERT( !mProgressItem );
  mProgressItem =
     ProgressManager::createProgressItem (
         "move"+ProgressManager::getUniqueID(),
         mDestFolder ? i18n( "Moving messages" ) : i18n( "Deleting messages" ) );
  connect( mProgressItem, TQT_SIGNAL( progressItemCanceled( KPIM::ProgressItem* ) ),
           this, TQT_SLOT( slotMoveCanceled() ) );

  KMMessage *msg;
  int rc = 0;
  int index;
  TQPtrList<KMMessage> list;
  int undoId = -1;
  mCompleteWithAddedMsg = false;

  if (mDestFolder) {
    connect (mDestFolder, TQT_SIGNAL(msgAdded(KMFolder*, TQ_UINT32)),
             this, TQT_SLOT(slotMsgAddedToDestFolder(KMFolder*, TQ_UINT32)));
    mLostBoys = mSerNumList;
  }
  mProgressItem->setTotalItems( mSerNumList.count() );

  for ( TQValueList<TQ_UINT32>::ConstIterator it = mSerNumList.constBegin(); it != mSerNumList.constEnd(); ++it ) {
    if ( *it == 0 ) {
      kdDebug(5006) << k_funcinfo << "serial number == 0!" << endl;
      continue; // invalid message
    }
    KMFolder *srcFolder = 0;
    int idx = -1;
    KMMsgDict::instance()->getLocation( *it, &srcFolder, &idx );
    if (srcFolder == mDestFolder)
      continue;
    assert(srcFolder);
    assert(idx != -1);
    if ( !srcFolder->isOpened() ) {
      srcFolder->open( "kmmovecommand" );
      mOpenedFolders.append( srcFolder );
    }
    msg = srcFolder->getMsg(idx);
    if ( !msg ) {
      kdDebug(5006) << k_funcinfo << "No message found for serial number " << *it << endl;
      continue;
    }
    bool undo = msg->enableUndo();

    if ( msg && msg->transferInProgress() &&
         srcFolder->folderType() == KMFolderTypeImap )
    {
      // cancel the download
      msg->setTransferInProgress( false, true );
      static_cast<KMFolderImap*>(srcFolder->storage())->ignoreJobsForMessage( msg );
    }

    if (mDestFolder) {
      if (mDestFolder->folderType() == KMFolderTypeImap) {
        /* If we are moving to an imap folder, connect to it's completed
         * signal so we notice when all the mails should have showed up in it
         * but haven't for some reason. */
        KMFolderImap *imapFolder = static_cast<KMFolderImap*> ( mDestFolder->storage() );
        disconnect (imapFolder, TQT_SIGNAL(folderComplete( KMFolderImap*, bool )),
                 this, TQT_SLOT(slotImapFolderCompleted( KMFolderImap*, bool )));

        connect (imapFolder, TQT_SIGNAL(folderComplete( KMFolderImap*, bool )),
                 this, TQT_SLOT(slotImapFolderCompleted( KMFolderImap*, bool )));
        list.append(msg);
      } else {
        // We are moving to a local folder.
        if ( srcFolder->folderType() == KMFolderTypeImap )
        {
          // do not complete here but wait until all messages are transferred
          mCompleteWithAddedMsg = true;
        }
        rc = mDestFolder->moveMsg(msg, &index);
        if (rc == 0 && index != -1) {
          KMMsgBase *mb = mDestFolder->unGetMsg( mDestFolder->count() - 1 );
          if (undo && mb)
          {
            if ( undoId == -1 )
              undoId = kmkernel->undoStack()->newUndoAction( srcFolder, mDestFolder );
            kmkernel->undoStack()->addMsgToAction( undoId, mb->getMsgSerNum() );
          }
        } else if (rc != 0) {
          // Something  went wrong. Stop processing here, it is likely that the
          // other moves would fail as well.
          completeMove( Failed );
          return Failed;
        }
      }
    } else {
      // really delete messages that are already in the trash folder or if
      // we are really, really deleting, not just moving to trash
      if (srcFolder->folderType() == KMFolderTypeImap) {
        if (!folderDeleteList[srcFolder])
          folderDeleteList[srcFolder] = new TQPtrList<KMMessage>;
        folderDeleteList[srcFolder]->append( msg );
      } else {
        srcFolder->removeMsg(idx);
        delete msg;
      }
    }
  }
  if (!list.isEmpty() && mDestFolder) {
    // will be completed with folderComplete signal
    mDestFolder->moveMsg(list, &index);
  } else {
    FolderToMessageListMap::Iterator it;
    for ( it = folderDeleteList.begin(); it != folderDeleteList.end(); ++it ) {
      it.key()->removeMsg(*it.data());
      delete it.data();
    }
    if ( !mCompleteWithAddedMsg ) {
      // imap folders will be completed in slotMsgAddedToDestFolder
      completeMove( OK );
    }
  }

  return OK;
}

void KMMoveCommand::slotImapFolderCompleted(KMFolderImap* imapFolder, bool success)
{
  disconnect (imapFolder, TQT_SIGNAL(folderComplete( KMFolderImap*, bool )),
      this, TQT_SLOT(slotImapFolderCompleted( KMFolderImap*, bool )));
  if ( success ) {
    // the folder was checked successfully but we were still called, so check
    // if we are still waiting for messages to show up. If so, uidValidity
    // changed, or something else went wrong. Clean up.

    /* Unfortunately older UW imap servers change uid validity for each put job.
     * Yes, it is really that broken. *sigh* So we cannot report error here, I guess. */
    if ( !mLostBoys.isEmpty() ) {
      kdDebug(5006) <<  "### Not all moved messages reported back that they were " << endl
                    <<  "### added to the target folder. Did uidValidity change? " << endl;
    }
    completeMove( OK );
  } else {
    // Should we inform the user here or leave that to the caller?
    completeMove( Failed );
  }
}

void KMMoveCommand::slotMsgAddedToDestFolder(KMFolder *folder, TQ_UINT32 serNum)
{
  if ( folder != mDestFolder || mLostBoys.find( serNum ) == mLostBoys.end() ) {
    //kdDebug(5006) << "KMMoveCommand::msgAddedToDestFolder different "
    //                 "folder or invalid serial number." << endl;
    return;
  }
  mLostBoys.remove(serNum);
  if ( mLostBoys.isEmpty() ) {
    // we are done. All messages transferred to the host succesfully
    disconnect (mDestFolder, TQT_SIGNAL(msgAdded(KMFolder*, TQ_UINT32)),
             this, TQT_SLOT(slotMsgAddedToDestFolder(KMFolder*, TQ_UINT32)));
    if (mDestFolder && mDestFolder->folderType() != KMFolderTypeImap) {
      mDestFolder->sync();
    }
    if ( mCompleteWithAddedMsg ) {
      completeMove( OK );
    }
  } else {
    if ( mProgressItem ) {
      mProgressItem->incCompletedItems();
      mProgressItem->updateProgress();
    }
  }
}

void KMMoveCommand::completeMove( Result result )
{
  if ( mDestFolder )
    mDestFolder->close("kmcommand");
  while ( !mOpenedFolders.empty() ) {
    KMFolder *folder = mOpenedFolders.back();
    mOpenedFolders.pop_back();
    folder->close("kmcommand");
  }
  if ( mProgressItem ) {
    mProgressItem->setComplete();
    mProgressItem = 0;
  }
  setResult( result );
  emit completed( this );
  deleteLater();
}

void KMMoveCommand::slotMoveCanceled()
{
  completeMove( Canceled );
}

// srcFolder doesn't make much sense for searchFolders
KMDeleteMsgCommand::KMDeleteMsgCommand( KMFolder* srcFolder,
  const TQPtrList<KMMsgBase> &msgList )
:KMMoveCommand( findTrashFolder( srcFolder ), msgList)
{
  srcFolder->open("kmcommand");
  mOpenedFolders.push_back( srcFolder );
}

KMDeleteMsgCommand::KMDeleteMsgCommand( KMFolder* srcFolder, KMMessage * msg )
:KMMoveCommand( findTrashFolder( srcFolder ), msg)
{
  srcFolder->open("kmcommand");
  mOpenedFolders.push_back( srcFolder );
}

KMDeleteMsgCommand::KMDeleteMsgCommand( TQ_UINT32 sernum )
:KMMoveCommand( sernum )
{
  if ( !sernum ) {
    setDestFolder( 0 );
    return;
  }

  KMFolder *srcFolder = 0;
  int idx;
  KMMsgDict::instance()->getLocation( sernum, &srcFolder, &idx );
  if ( srcFolder ) {
    KMMsgBase *msg = srcFolder->getMsgBase( idx );
    srcFolder->open("kmcommand");
    mOpenedFolders.push_back( srcFolder );
    addMsg( msg );
  }
  setDestFolder( findTrashFolder( srcFolder ) );
}

KMFolder * KMDeleteMsgCommand::findTrashFolder( KMFolder * folder )
{
  KMFolder* trash = folder->trashFolder();
  if( !trash )
    trash = kmkernel->trashFolder();
  if( trash != folder )
    return trash;
  return 0;
}


KMUrlClickedCommand::KMUrlClickedCommand( const KURL &url, uint identity,
  KMReaderWin *readerWin, bool htmlPref, KMMainWidget *mainWidget )
  :mUrl( url ), mIdentity( identity ), mReaderWin( readerWin ),
   mHtmlPref( htmlPref ), mMainWidget( mainWidget )
{
}

KMCommand::Result KMUrlClickedCommand::execute()
{
  KMMessage* msg;

  if (mUrl.protocol() == "mailto")
  {
    msg = new KMMessage;
    msg->initHeader(mIdentity);
    msg->setCharset("utf-8");
    msg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );
    TQString query=mUrl.query();
    while (!query.isEmpty()) {
      TQString queryPart;
      int secondQuery = query.find('?',1);
      if (secondQuery != -1)
        queryPart = query.left(secondQuery);
      else
        queryPart = query;
      query = query.mid(queryPart.length());

      if (queryPart.left(9) == "?subject=")
        msg->setSubject( KURL::decode_string(queryPart.mid(9)) );
      else if (queryPart.left(6) == "?body=")
        // It is correct to convert to latin1() as URL should not contain
        // anything except ascii.
        msg->setBody( KURL::decode_string(queryPart.mid(6)).latin1() );
      else if (queryPart.left(4) == "?cc=")
        msg->setCc( KURL::decode_string(queryPart.mid(4)) );
    }

    KMail::Composer * win = KMail::makeComposer( msg, mIdentity );
    win->setCharset("", true);
    win->show();
  }
  else if ( mUrl.protocol() == "im" )
  {
    kmkernel->imProxy()->chatWithContact( mUrl.path() );
  }
  else if ((mUrl.protocol() == "http") || (mUrl.protocol() == "https") ||
           (mUrl.protocol() == "ftp")  || (mUrl.protocol() == "file")  ||
           (mUrl.protocol() == "ftps") || (mUrl.protocol() == "sftp" ) ||
           (mUrl.protocol() == "help") || (mUrl.protocol() == "vnc")   ||
           (mUrl.protocol() == "smb")  || (mUrl.protocol() == "fish")  ||
           (mUrl.protocol() == "news"))
  {
    KPIM::BroadcastStatus::instance()->setStatusMsg( i18n("Opening URL..."));
    KMimeType::Ptr mime = KMimeType::findByURL( mUrl );
    if (mime->name() == "application/x-desktop" ||
        mime->name() == "application/x-executable" ||
        mime->name() == "application/x-msdos-program" ||
        mime->name() == "application/x-shellscript" )
    {
      if (KMessageBox::warningYesNo( 0, i18n( "<qt>Do you really want to execute <b>%1</b>?</qt>" )
        .arg( mUrl.prettyURL() ), TQString(), i18n("Execute"), KStdGuiItem::cancel() ) != KMessageBox::Yes)
        return Canceled;
    }
    KRun * runner = new KRun( mUrl );
    runner->setRunExecutables( false );
  }
  else
    return Failed;

  return OK;
}

KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( TQWidget *parent, KMMessage *msg )
  : KMCommand( parent, msg ), mImplicitAttachments( true ), mEncoded( false )
{
}

KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( TQWidget *parent, const TQPtrList<KMMsgBase>& msgs )
  : KMCommand( parent, msgs ), mImplicitAttachments( true ), mEncoded( false )
{
}

KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( TQWidget *parent, TQPtrList<partNode>& attachments,
                                                    KMMessage *msg, bool encoded )
  : KMCommand( parent ), mImplicitAttachments( false ), mEncoded( encoded )
{
  for ( TQPtrListIterator<partNode> it( attachments ); it.current(); ++it ) {
    mAttachmentMap.insert( it.current(), msg );
  }
}

KMCommand::Result KMSaveAttachmentsCommand::execute()
{
  setEmitsCompletedItself( true );
  if ( mImplicitAttachments ) {
    TQPtrList<KMMessage> msgList = retrievedMsgs();
    KMMessage *msg;
    for ( TQPtrListIterator<KMMessage> itr( msgList );
          ( msg = itr.current() );
          ++itr ) {
      partNode *rootNode = partNode::fromMessage( msg );
      for ( partNode *child = rootNode; child;
            child = child->firstChild() ) {
        for ( partNode *node = child; node; node = node->nextSibling() ) {
          if ( node->type() != DwMime::kTypeMultipart )
            mAttachmentMap.insert( node, msg );
        }
      }
    }
  }
  setDeletesItself( true );
  // load all parts
  KMLoadPartsCommand *command = new KMLoadPartsCommand( mAttachmentMap );
  connect( command, TQT_SIGNAL( partsRetrieved() ),
           this, TQT_SLOT( slotSaveAll() ) );
  command->start();

  return OK;
}

void KMSaveAttachmentsCommand::slotSaveAll()
{
  // now that all message parts have been retrieved, remove all parts which
  // don't represent an attachment if they were not explicitely passed in the
  // c'tor
  if ( mImplicitAttachments ) {
    for ( PartNodeMessageMap::iterator it = mAttachmentMap.begin();
          it != mAttachmentMap.end(); ) {
      // only body parts which have a filename or a name parameter (except for
      // the root node for which name is set to the message's subject) are
      // considered attachments
      if ( it.key()->msgPart().fileName().stripWhiteSpace().isEmpty() &&
           ( it.key()->msgPart().name().stripWhiteSpace().isEmpty() ||
             !it.key()->parentNode() ) ) {
        PartNodeMessageMap::iterator delIt = it;
        ++it;
        mAttachmentMap.remove( delIt );
      }
      else
        ++it;
    }
    if ( mAttachmentMap.isEmpty() ) {
      KMessageBox::information( 0, i18n("Found no attachments to save.") );
      setResult( OK ); // The user has already been informed.
      emit completed( this );
      deleteLater();
      return;
    }
  }

  KURL url, dirUrl;
  if ( mAttachmentMap.count() > 1 ) {
    // get the dir
    dirUrl = KDirSelectDialog::selectDirectory( TQString(), false,
                                                parentWidget(),
                                                i18n("Save Attachments To") );
    if ( !dirUrl.isValid() ) {
      setResult( Canceled );
      emit completed( this );
      deleteLater();
      return;
    }

    // we may not get a slash-terminated url out of KDirSelectDialog
    dirUrl.adjustPath( 1 );
  }
  else {
    // only one item, get the desired filename
    partNode *node = mAttachmentMap.begin().key();
    // replace all ':' with '_' because ':' isn't allowed on FAT volumes
    TQString s =
      node->msgPart().fileName().stripWhiteSpace().replace( ':', '_' );
    if ( s.isEmpty() )
      s = node->msgPart().name().stripWhiteSpace().replace( ':', '_' );
    if ( s.isEmpty() )
      s = i18n("filename for an unnamed attachment", "attachment.1");
    url = KFileDialog::getSaveURL( s, TQString(), parentWidget(),
                                   TQString() );
    if ( url.isEmpty() ) {
      setResult( Canceled );
      emit completed( this );
      deleteLater();
      return;
    }
  }

  TQMap< TQString, int > renameNumbering;

  Result globalResult = OK;
  int unnamedAtmCount = 0;
  for ( PartNodeMessageMap::const_iterator it = mAttachmentMap.begin();
        it != mAttachmentMap.end();
        ++it ) {
    KURL curUrl;
    if ( !dirUrl.isEmpty() ) {
      curUrl = dirUrl;
      TQString s =
        it.key()->msgPart().fileName().stripWhiteSpace().replace( ':', '_' );
      if ( s.isEmpty() )
        s = it.key()->msgPart().name().stripWhiteSpace().replace( ':', '_' );
      if ( s.isEmpty() ) {
        ++unnamedAtmCount;
        s = i18n("filename for the %1-th unnamed attachment",
                 "attachment.%1")
            .arg( unnamedAtmCount );
      }
      curUrl.setFileName( s );
    } else {
      curUrl = url;
    }

    if ( !curUrl.isEmpty() ) {

     // Rename the file if we have already saved one with the same name:
     // try appending a number before extension (e.g. "pic.jpg" => "pic_2.jpg")
     TQString origFile = curUrl.fileName();
     TQString file = origFile;

     while ( renameNumbering.contains(file) ) {
       file = origFile;
       int num = renameNumbering[file] + 1;
       int dotIdx = file.findRev('.');
       file = file.insert( (dotIdx>=0) ? dotIdx : file.length(), TQString("_") + TQString::number(num) );
     }
     curUrl.setFileName(file);

     // Increment the counter for both the old and the new filename
     if ( !renameNumbering.contains(origFile))
         renameNumbering[origFile] = 1;
     else
         renameNumbering[origFile]++;

     if ( file != origFile ) {
        if ( !renameNumbering.contains(file))
            renameNumbering[file] = 1;
        else
            renameNumbering[file]++;
     }


      if ( TDEIO::NetAccess::exists( curUrl, false, parentWidget() ) ) {
        if ( KMessageBox::warningContinueCancel( parentWidget(),
              i18n( "A file named %1 already exists. Do you want to overwrite it?" )
              .arg( curUrl.fileName() ),
              i18n( "File Already Exists" ), i18n("&Overwrite") ) == KMessageBox::Cancel) {
          continue;
        }
      }
      // save
      const Result result = saveItem( it.key(), curUrl );
      if ( result != OK )
        globalResult = result;
    }
  }
  setResult( globalResult );
  emit completed( this );
  deleteLater();
}

KMCommand::Result KMSaveAttachmentsCommand::saveItem( partNode *node,
                                                      const KURL& url )
{
  bool bSaveEncrypted = false;
  bool bEncryptedParts = node->encryptionState() != KMMsgNotEncrypted;
  if( bEncryptedParts )
    if( KMessageBox::questionYesNo( parentWidget(),
          i18n( "The part %1 of the message is encrypted. Do you want to keep the encryption when saving?" ).
          arg( url.fileName() ),
          i18n( "KMail Question" ), i18n("Keep Encryption"), i18n("Do Not Keep") ) ==
        KMessageBox::Yes )
      bSaveEncrypted = true;

  bool bSaveWithSig = true;
  if( node->signatureState() != KMMsgNotSigned )
    if( KMessageBox::questionYesNo( parentWidget(),
          i18n( "The part %1 of the message is signed. Do you want to keep the signature when saving?" ).
          arg( url.fileName() ),
          i18n( "KMail Question" ), i18n("Keep Signature"), i18n("Do Not Keep") ) !=
        KMessageBox::Yes )
      bSaveWithSig = false;

  TQByteArray data;
  if ( mEncoded )
  {
    // This does not decode the Message Content-Transfer-Encoding
    // but saves the _original_ content of the message part
    data = KMail::Util::ByteArray( node->msgPart().dwBody() );
  }
  else
  {
    if( bSaveEncrypted || !bEncryptedParts) {
      partNode *dataNode = node;
      TQCString rawReplyString;
      bool gotRawReplyString = false;
      if( !bSaveWithSig ) {
        if( DwMime::kTypeMultipart == node->type() &&
            DwMime::kSubtypeSigned == node->subType() ){
          // carefully look for the part that is *not* the signature part:
          if( node->findType( DwMime::kTypeApplication,
                DwMime::kSubtypePgpSignature,
                true, false ) ){
            dataNode = node->findTypeNot( DwMime::kTypeApplication,
                DwMime::kSubtypePgpSignature,
                true, false );
          }else if( node->findType( DwMime::kTypeApplication,
                DwMime::kSubtypePkcs7Mime,
                true, false ) ){
            dataNode = node->findTypeNot( DwMime::kTypeApplication,
                DwMime::kSubtypePkcs7Mime,
                true, false );
          }else{
            dataNode = node->findTypeNot( DwMime::kTypeMultipart,
                DwMime::kSubtypeUnknown,
                true, false );
          }
	}else{
	  ObjectTreeParser otp( 0, 0, false, false, false );

	  // process this node and all it's siblings and descendants
	  dataNode->setProcessed( false, true );
	  otp.parseObjectTree( dataNode );

	  rawReplyString = otp.rawReplyString();
	  gotRawReplyString = true;
        }
      }
      TQByteArray cstr = gotRawReplyString
                         ? rawReplyString
                         : dataNode->msgPart().bodyDecodedBinary();
      data = cstr;
      size_t size = cstr.size();
      if ( dataNode->msgPart().type() == DwMime::kTypeText ) {
        // convert CRLF to LF before writing text attachments to disk
        size = KMail::Util::crlf2lf( cstr.data(), size );
      }
      data.resize( size );
    }
  }
  TQDataStream ds;
  TQFile file;
  KTempFile tf;
  tf.setAutoDelete( true );
  if ( url.isLocalFile() )
  {
    // save directly
    file.setName( url.path() );
    if ( !file.open( IO_WriteOnly ) )
    {
      KMessageBox::error( parentWidget(),
          i18n( "%2 is detailed error description",
            "Could not write the file %1:\n%2" )
          .arg( file.name() )
          .arg( TQString::fromLocal8Bit( strerror( errno ) ) ),
          i18n( "KMail Error" ) );
      return Failed;
    }

    // #79685 by default use the umask the user defined, but let it be configurable
    if ( GlobalSettings::self()->disregardUmask() )
      fchmod( file.handle(), S_IRUSR | S_IWUSR );

    ds.setDevice( &file );
  } else
  {
    // tmp file for upload
    ds.setDevice( tf.file() );
  }

  ds.writeRawBytes( data.data(), data.size() );
  if ( !url.isLocalFile() )
  {
    tf.close();
    if ( !TDEIO::NetAccess::upload( tf.name(), url, parentWidget() ) )
    {
      KMessageBox::error( parentWidget(),
          i18n( "Could not write the file %1." )
          .arg( url.path() ),
          i18n( "KMail Error" ) );
      return Failed;
    }
  } else
    file.close();
  return OK;
}

KMLoadPartsCommand::KMLoadPartsCommand( TQPtrList<partNode>& parts, KMMessage *msg )
  : mNeedsRetrieval( 0 )
{
  for ( TQPtrListIterator<partNode> it( parts ); it.current(); ++it ) {
    mPartMap.insert( it.current(), msg );
  }
}

KMLoadPartsCommand::KMLoadPartsCommand( partNode *node, KMMessage *msg )
  : mNeedsRetrieval( 0 )
{
  mPartMap.insert( node, msg );
}

KMLoadPartsCommand::KMLoadPartsCommand( PartNodeMessageMap& partMap )
  : mNeedsRetrieval( 0 ), mPartMap( partMap )
{
}

void KMLoadPartsCommand::slotStart()
{
  for ( PartNodeMessageMap::const_iterator it = mPartMap.begin();
        it != mPartMap.end();
        ++it ) {
    if ( !it.key()->msgPart().isComplete() &&
         !it.key()->msgPart().partSpecifier().isEmpty() ) {
      // incomplete part, so retrieve it first
      ++mNeedsRetrieval;
      KMFolder* curFolder = it.data()->parent();
      if ( curFolder ) {
        FolderJob *job =
          curFolder->createJob( it.data(), FolderJob::tGetMessage,
                                0, it.key()->msgPart().partSpecifier() );
        job->setCancellable( false );
        connect( job, TQT_SIGNAL(messageUpdated(KMMessage*, TQString)),
                 this, TQT_SLOT(slotPartRetrieved(KMMessage*, TQString)) );
        job->start();
      } else
        kdWarning(5006) << "KMLoadPartsCommand - msg has no parent" << endl;
    }
  }
  if ( mNeedsRetrieval == 0 )
    execute();
}

void KMLoadPartsCommand::slotPartRetrieved( KMMessage *msg,
                                            TQString partSpecifier )
{
  DwBodyPart *part =
    msg->findDwBodyPart( msg->getFirstDwBodyPart(), partSpecifier );
  if ( part ) {
    // update the DwBodyPart in the partNode
    for ( PartNodeMessageMap::const_iterator it = mPartMap.begin();
          it != mPartMap.end();
          ++it ) {
      if ( it.key()->dwPart()->partId() == part->partId() )
        it.key()->setDwPart( part );
    }
  } else
    kdWarning(5006) << "KMLoadPartsCommand::slotPartRetrieved - could not find bodypart!" << endl;
  --mNeedsRetrieval;
  if ( mNeedsRetrieval == 0 )
    execute();
}

KMCommand::Result KMLoadPartsCommand::execute()
{
  emit partsRetrieved();
  setResult( OK );
  emit completed( this );
  deleteLater();
  return OK;
}

KMResendMessageCommand::KMResendMessageCommand( TQWidget *parent,
   KMMessage *msg )
  :KMCommand( parent, msg )
{
}

KMCommand::Result KMResendMessageCommand::execute()
{
   KMMessage *msg = retrievedMessage();
   if ( !msg || !msg->codec() ) {
     return Failed;
   }
   KMMessage *newMsg = new KMMessage(*msg);

   TQStringList whiteList;
   whiteList << "To" << "Cc" << "Bcc" << "Subject";
   newMsg->sanitizeHeaders( whiteList );

   if( newMsg->type() == DwMime::kTypeText) {
     newMsg->setCharset(msg->codec()->mimeName());
   }
   newMsg->setParent( 0 );

   // make sure we have an identity set, default, if necessary
   newMsg->setHeaderField("X-KMail-Identity", TQString::number( newMsg->identityUoid() ));
   newMsg->applyIdentity( newMsg->identityUoid() );

   KMail::Composer * win = KMail::makeComposer();
   win->setMsg(newMsg, false, true);
   win->show();

   return OK;
}

KMMailingListCommand::KMMailingListCommand( TQWidget *parent, KMFolder *folder )
  : KMCommand( parent ), mFolder( folder )
{
}

KMCommand::Result KMMailingListCommand::execute()
{
  KURL::List lst = urls();
  TQString handler = ( mFolder->mailingList().handler() == MailingList::KMail )
    ? "mailto" : "https";

  KMCommand *command = 0;
  for ( KURL::List::Iterator itr = lst.begin(); itr != lst.end(); ++itr ) {
    if ( handler == (*itr).protocol() ) {
      command = new KMUrlClickedCommand( *itr, mFolder->identity(), 0, false );
    }
  }
  if ( !command && !lst.empty() ) {
    command =
      new KMUrlClickedCommand( lst.first(), mFolder->identity(), 0, false );
  }
  if ( command ) {
    connect( command, TQT_SIGNAL( completed( KMCommand * ) ),
             this, TQT_SLOT( commandCompleted( KMCommand * ) ) );
    setDeletesItself( true );
    setEmitsCompletedItself( true );
    command->start();
    return OK;
  }
  return Failed;
}

void KMMailingListCommand::commandCompleted( KMCommand *command )
{
  setResult( command->result() );
  emit completed( this );
  deleteLater();
}

KMMailingListPostCommand::KMMailingListPostCommand( TQWidget *parent, KMFolder *folder )
  : KMMailingListCommand( parent, folder )
{
}
KURL::List KMMailingListPostCommand::urls() const
{
  return mFolder->mailingList().postURLS();
}

KMMailingListSubscribeCommand::KMMailingListSubscribeCommand( TQWidget *parent, KMFolder *folder )
  : KMMailingListCommand( parent, folder )
{
}
KURL::List KMMailingListSubscribeCommand::urls() const
{
  return mFolder->mailingList().subscribeURLS();
}

KMMailingListUnsubscribeCommand::KMMailingListUnsubscribeCommand( TQWidget *parent, KMFolder *folder )
  : KMMailingListCommand( parent, folder )
{
}
KURL::List KMMailingListUnsubscribeCommand::urls() const
{
  return mFolder->mailingList().unsubscribeURLS();
}

KMMailingListArchivesCommand::KMMailingListArchivesCommand( TQWidget *parent, KMFolder *folder )
  : KMMailingListCommand( parent, folder )
{
}
KURL::List KMMailingListArchivesCommand::urls() const
{
  return mFolder->mailingList().archiveURLS();
}

KMMailingListHelpCommand::KMMailingListHelpCommand( TQWidget *parent, KMFolder *folder )
  : KMMailingListCommand( parent, folder )
{
}
KURL::List KMMailingListHelpCommand::urls() const
{
  return mFolder->mailingList().helpURLS();
}

KMIMChatCommand::KMIMChatCommand( const KURL &url, KMMessage *msg )
  :mUrl( url ), mMessage( msg )
{
}

KMCommand::Result KMIMChatCommand::execute()
{
  kdDebug( 5006 ) << k_funcinfo << " URL is: " << mUrl << endl;
  TQString addr = KMMessage::decodeMailtoUrl( mUrl.path() );
  // find UID for mail address
  TDEABC::AddressBook *addressBook = TDEABC::StdAddressBook::self( true );
  TDEABC::AddresseeList addressees = addressBook->findByEmail( KPIM::getEmailAddress( addr ) ) ;

  // start chat
  if( addressees.count() == 1 ) {
    kmkernel->imProxy()->chatWithContact( addressees[0].uid() );
    return OK;
  }
  else
  {
    kdDebug( 5006 ) << "Didn't find exactly one addressee, couldn't tell who to chat to for that email address.  Count = " << addressees.count() << endl;

    TQString apology;
    if ( addressees.isEmpty() )
      apology = i18n( "There is no Address Book entry for this email address. Add them to the Address Book and then add instant messaging addresses using your preferred messaging client." );
    else
    {
      apology = i18n( "More than one Address Book entry uses this email address:\n %1\n it is not possible to determine who to chat with." );
      TQStringList nameList;
      TDEABC::AddresseeList::const_iterator it = addressees.begin();
      TDEABC::AddresseeList::const_iterator end = addressees.end();
      for ( ; it != end; ++it )
      {
          nameList.append( (*it).realName() );
      }
      TQString names = nameList.join( TQString::fromLatin1( ",\n" ) );
      apology = apology.arg( names );
    }

    KMessageBox::sorry( parentWidget(), apology );
    return Failed;
  }
}

KMHandleAttachmentCommand::KMHandleAttachmentCommand( partNode* node,
     KMMessage* msg, int atmId, const TQString& atmName,
     AttachmentAction action, KService::Ptr offer, TQWidget* parent )
: KMCommand( parent ), mNode( node ), mMsg( msg ), mAtmId( atmId ), mAtmName( atmName ),
  mAction( action ), mOffer( offer ), mJob( 0 )
{
}

void KMHandleAttachmentCommand::slotStart()
{
  if ( !mNode->msgPart().isComplete() )
  {
    // load the part
    kdDebug(5006) << "load part" << endl;
    KMLoadPartsCommand *command = new KMLoadPartsCommand( mNode, mMsg );
    connect( command, TQT_SIGNAL( partsRetrieved() ),
        this, TQT_SLOT( slotPartComplete() ) );
    command->start();
  } else
  {
    execute();
  }
}

void KMHandleAttachmentCommand::slotPartComplete()
{
  execute();
}

KMCommand::Result KMHandleAttachmentCommand::execute()
{
  switch( mAction )
  {
    case Open:
      atmOpen();
      break;
    case OpenWith:
      atmOpenWith();
      break;
    case View:
      atmView();
      break;
    case Save:
      atmSave();
      break;
    case Properties:
      atmProperties();
      break;
    case ChiasmusEncrypt:
      atmEncryptWithChiasmus();
      return Undefined;
      break;
    default:
      kdDebug(5006) << "unknown action " << mAction << endl;
      break;
  }
  setResult( OK );
  emit completed( this );
  deleteLater();
  return OK;
}

TQString KMHandleAttachmentCommand::createAtmFileLink() const
{
  TQFileInfo atmFileInfo( mAtmName );

  if ( atmFileInfo.size() == 0 )
  {
    kdDebug(5006) << k_funcinfo << "rewriting attachment" << endl;
    // there is something wrong so write the file again
    TQByteArray data = mNode->msgPart().bodyDecodedBinary();
    size_t size = data.size();
    if ( mNode->msgPart().type() == DwMime::kTypeText && size) {
      // convert CRLF to LF before writing text attachments to disk
      size = KMail::Util::crlf2lf( data.data(), size );
    }
    KPIM::kBytesToFile( data.data(), size, mAtmName, false, false, false );
  }

  KTempFile *linkFile = new KTempFile( locateLocal("tmp", atmFileInfo.fileName() +"_["),
                          "]."+ atmFileInfo.extension() );

  linkFile->setAutoDelete(true);
  TQString linkName = linkFile->name();
  delete linkFile;

  if ( ::link(TQFile::encodeName( mAtmName ), TQFile::encodeName( linkName )) == 0 ) {
    return linkName; // success
  }
  return TQString();
}

KService::Ptr KMHandleAttachmentCommand::getServiceOffer()
{
  KMMessagePart& msgPart = mNode->msgPart();
  const TQString contentTypeStr =
    ( msgPart.typeStr() + '/' + msgPart.subtypeStr() ).lower();

  if ( contentTypeStr == "text/x-vcard" ) {
    atmView();
    return 0;
  }
  // determine the MIME type of the attachment
  KMimeType::Ptr mimetype;
  // prefer the value of the Content-Type header
  mimetype = KMimeType::mimeType( contentTypeStr );
  if ( mimetype->name() == "application/octet-stream" ) {
    // consider the filename if Content-Type is application/octet-stream
    mimetype = KMimeType::findByPath( mAtmName, 0, true /* no disk access */ );
  }
  if ( ( mimetype->name() == "application/octet-stream" )
       && msgPart.isComplete() ) {
    // consider the attachment's contents if neither the Content-Type header
    // nor the filename give us a clue
    mimetype = KMimeType::findByFileContent( mAtmName );
  }
  return KServiceTypeProfile::preferredService( mimetype->name(), "Application" );
}

void KMHandleAttachmentCommand::atmOpen()
{
  if ( !mOffer )
    mOffer = getServiceOffer();
  if ( !mOffer ) {
    kdDebug(5006) << k_funcinfo << "got no offer" << endl;
    return;
  }

  KURL::List lst;
  KURL url;
  bool autoDelete = true;
  TQString fname = createAtmFileLink();

  if ( fname.isNull() ) {
    autoDelete = false;
    fname = mAtmName;
  }

  url.setPath( fname );
  lst.append( url );
  if ( (KRun::run( *mOffer, lst, autoDelete ) <= 0) && autoDelete ) {
      TQFile::remove(url.path());
  }
}

void KMHandleAttachmentCommand::atmOpenWith()
{
  KURL::List lst;
  KURL url;
  bool autoDelete = true;
  TQString fname = createAtmFileLink();

  if ( fname.isNull() ) {
    autoDelete = false;
    fname = mAtmName;
  }

  url.setPath( fname );
  lst.append( url );
  if ( (! KRun::displayOpenWithDialog(lst, autoDelete)) && autoDelete ) {
    TQFile::remove( url.path() );
  }
}

void KMHandleAttachmentCommand::atmView()
{
  // we do not handle this ourself
  emit showAttachment( mAtmId, mAtmName );
}

void KMHandleAttachmentCommand::atmSave()
{
  TQPtrList<partNode> parts;
  parts.append( mNode );
  // save, do not leave encoded
  KMSaveAttachmentsCommand *command =
    new KMSaveAttachmentsCommand( parentWidget(), parts, mMsg, false );
  command->start();
}

void KMHandleAttachmentCommand::atmProperties()
{
  KMMsgPartDialogCompat dlg( parentWidget() , 0, true );
  KMMessagePart& msgPart = mNode->msgPart();
  dlg.setMsgPart( &msgPart );
  dlg.exec();
}

void KMHandleAttachmentCommand::atmEncryptWithChiasmus()
{
  const partNode * node = mNode;
  Q_ASSERT( node );
  if ( !node )
    return;

  // FIXME: better detection of mimetype??
  if ( !mAtmName.endsWith( ".xia", false ) )
    return;

  const Kleo::CryptoBackend::Protocol * chiasmus =
    Kleo::CryptoBackendFactory::instance()->protocol( "Chiasmus" );
  Q_ASSERT( chiasmus );
  if ( !chiasmus )
    return;

  const STD_NAMESPACE_PREFIX auto_ptr<Kleo::SpecialJob> listjob( chiasmus->specialJob( "x-obtain-keys", TQMap<TQString,TQVariant>() ) );
  if ( !listjob.get() ) {
    const TQString msg = i18n( "Chiasmus backend does not offer the "
                              "\"x-obtain-keys\" function. Please report this bug." );
    KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) );
    return;
  }

  if ( listjob->exec() ) {
    listjob->showErrorDialog( parentWidget(), i18n( "Chiasmus Backend Error" ) );
    return;
  }

  const TQVariant result = listjob->property( "result" );
  if ( result.type() != TQVariant::StringList ) {
    const TQString msg = i18n( "Unexpected return value from Chiasmus backend: "
                              "The \"x-obtain-keys\" function did not return a "
                              "string list. Please report this bug." );
    KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) );
    return;
  }

  const TQStringList keys = result.toStringList();
  if ( keys.empty() ) {
    const TQString msg = i18n( "No keys have been found. Please check that a "
                              "valid key path has been set in the Chiasmus "
                              "configuration." );
    KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) );
    return;
  }

  ChiasmusKeySelector selectorDlg( parentWidget(), i18n( "Chiasmus Decryption Key Selection" ),
                                   keys, GlobalSettings::chiasmusDecryptionKey(),
                                   GlobalSettings::chiasmusDecryptionOptions() );
  if ( selectorDlg.exec() != TQDialog::Accepted )
    return;

  GlobalSettings::setChiasmusDecryptionOptions( selectorDlg.options() );
  GlobalSettings::setChiasmusDecryptionKey( selectorDlg.key() );
  assert( !GlobalSettings::chiasmusDecryptionKey().isEmpty() );

  Kleo::SpecialJob * job = chiasmus->specialJob( "x-decrypt", TQMap<TQString,TQVariant>() );
  if ( !job ) {
    const TQString msg = i18n( "Chiasmus backend does not offer the "
                              "\"x-decrypt\" function. Please report this bug." );
    KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) );
    return;
  }

  const TQByteArray input = node->msgPart().bodyDecodedBinary();

  if ( !job->setProperty( "key", GlobalSettings::chiasmusDecryptionKey() ) ||
       !job->setProperty( "options", GlobalSettings::chiasmusDecryptionOptions() ) ||
       !job->setProperty( "input", input ) ) {
    const TQString msg = i18n( "The \"x-decrypt\" function does not accept "
                              "the expected parameters. Please report this bug." );
    KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) );
    return;
  }

  setDeletesItself( true ); // the job below is async, we have to cleanup ourselves
  if ( job->start() ) {
    job->showErrorDialog( parentWidget(), i18n( "Chiasmus Decryption Error" ) );
    return;
  }

  mJob = job;
  connect( job, TQT_SIGNAL(result(const GpgME::Error&,const TQVariant&)),
           this, TQT_SLOT(slotAtmDecryptWithChiasmusResult(const GpgME::Error&,const TQVariant&)) );
}

static const TQString chomp( const TQString & base, const TQString & suffix, bool cs ) {
  return base.endsWith( suffix, cs ) ? base.left( base.length() - suffix.length() ) : base ;
}

void KMHandleAttachmentCommand::slotAtmDecryptWithChiasmusResult( const GpgME::Error & err, const TQVariant & result )
{
  LaterDeleterWithCommandCompletion d( this );
  if ( !mJob )
    return;
  Q_ASSERT( mJob == sender() );
  if ( mJob != sender() )
    return;
  Kleo::Job * job = mJob;
  mJob = 0;
  if ( err.isCanceled() )
    return;
  if ( err ) {
    job->showErrorDialog( parentWidget(), i18n( "Chiasmus Decryption Error" ) );
    return;
  }

  if ( result.type() != TQVariant::ByteArray ) {
    const TQString msg = i18n( "Unexpected return value from Chiasmus backend: "
                              "The \"x-decrypt\" function did not return a "
                              "byte array. Please report this bug." );
    KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) );
    return;
  }

  const KURL url = KFileDialog::getSaveURL( chomp( mAtmName, ".xia", false ), TQString(), parentWidget() );
  if ( url.isEmpty() )
    return;

  bool overwrite = KMail::Util::checkOverwrite( url, parentWidget() );
  if ( !overwrite )
    return;

  d.setDisabled( true ); // we got this far, don't delete yet
  TDEIO::Job * uploadJob = TDEIO::storedPut( result.toByteArray(), url, -1, overwrite, false /*resume*/ );
  uploadJob->setWindow( parentWidget() );
  connect( uploadJob, TQT_SIGNAL(result(TDEIO::Job*)),
           this, TQT_SLOT(slotAtmDecryptWithChiasmusUploadResult(TDEIO::Job*)) );
}

void KMHandleAttachmentCommand::slotAtmDecryptWithChiasmusUploadResult( TDEIO::Job * job )
{
  if ( job->error() )
    job->showErrorDialog();
  LaterDeleterWithCommandCompletion d( this );
  d.setResult( OK );
}


AttachmentModifyCommand::AttachmentModifyCommand(partNode * node, KMMessage * msg, TQWidget * parent) :
    KMCommand( parent, msg ),
    mPartIndex( node->nodeId() ),
    mSernum( 0 )
{
}

AttachmentModifyCommand::AttachmentModifyCommand( int nodeId, KMMessage *msg, TQWidget *parent )
  : KMCommand( parent, msg ),
    mPartIndex( nodeId ),
    mSernum( 0 )
{
}

AttachmentModifyCommand::~ AttachmentModifyCommand()
{
}

KMCommand::Result AttachmentModifyCommand::execute()
{
  KMMessage *msg = retrievedMessage();
  if ( !msg )
    return Failed;
  mSernum = msg->getMsgSerNum();

  mFolder = msg->parent();
  if ( !mFolder || !mFolder->storage() )
    return Failed;

  Result res = doAttachmentModify();
  if ( res != OK )
    return res;

  setEmitsCompletedItself( true );
  setDeletesItself( true );
  return OK;
}

void AttachmentModifyCommand::storeChangedMessage(KMMessage * msg)
{
  if ( !mFolder || !mFolder->storage() ) {
    kdWarning(5006) << k_funcinfo << "We lost the folder!" << endl;
    setResult( Failed );
    emit completed( this );
    deleteLater();
  }
  int res = mFolder->addMsg( msg ) != 0;
  if ( mFolder->folderType() == KMFolderTypeImap ) {
    KMFolderImap *f = static_cast<KMFolderImap*>( mFolder->storage() );
    connect( f, TQT_SIGNAL(folderComplete(KMFolderImap*,bool)),
             TQT_SLOT(messageStoreResult(KMFolderImap*,bool)) );
  } else {
    messageStoreResult( 0, res == 0 );
  }
}

void AttachmentModifyCommand::messageStoreResult(KMFolderImap* folder, bool success )
{
  Q_UNUSED( folder );
  if ( success ) {
    KMCommand *delCmd = new KMDeleteMsgCommand( mSernum );
    connect( delCmd, TQT_SIGNAL(completed(KMCommand*)), TQT_SLOT(messageDeleteResult(KMCommand*)) );
    delCmd->start();
    return;
  }
  kdWarning(5006) << k_funcinfo << "Adding modified message failed." << endl;
  setResult( Failed );
  emit completed( this );
  deleteLater();
}

void AttachmentModifyCommand::messageDeleteResult(KMCommand * cmd)
{
  setResult( cmd->result() );
  emit completed( this );
  deleteLater();
}

KMDeleteAttachmentCommand::KMDeleteAttachmentCommand(partNode * node, KMMessage * msg, TQWidget * parent) :
    AttachmentModifyCommand( node, msg, parent )
{
  kdDebug(5006) << k_funcinfo << endl;
}

KMDeleteAttachmentCommand::KMDeleteAttachmentCommand( int nodeId, KMMessage *msg, TQWidget *parent )
  : AttachmentModifyCommand( nodeId, msg, parent )
{
  kdDebug(5006) << k_funcinfo << endl;
}

KMDeleteAttachmentCommand::~KMDeleteAttachmentCommand()
{
  kdDebug(5006) << k_funcinfo << endl;
}

KMCommand::Result KMDeleteAttachmentCommand::doAttachmentModify()
{
  KMMessage *msg = retrievedMessage();
  if ( !msg || !msg->deleteBodyPart( mPartIndex ) )
    return Failed;

  KMMessage *newMsg = new KMMessage();
  newMsg->fromDwString( msg->asDwString() );
  newMsg->setStatus( msg->status() );

  storeChangedMessage( newMsg );
  return OK;
}


KMEditAttachmentCommand::KMEditAttachmentCommand(partNode * node, KMMessage * msg, TQWidget * parent) :
    AttachmentModifyCommand( node, msg, parent )
{
  kdDebug(5006) << k_funcinfo << endl;
  mTempFile.setAutoDelete( true );
}

KMEditAttachmentCommand::KMEditAttachmentCommand( int nodeId, KMMessage *msg, TQWidget *parent )
  : AttachmentModifyCommand( nodeId, msg, parent )
{
  kdDebug(5006) << k_funcinfo << endl;
  mTempFile.setAutoDelete( true );
}

KMEditAttachmentCommand::~ KMEditAttachmentCommand()
{
}

KMCommand::Result KMEditAttachmentCommand::doAttachmentModify()
{
  KMMessage *msg = retrievedMessage();
  if ( !msg )
    return Failed;

  KMMessagePart part;
  DwBodyPart *dwpart = msg->findPart( mPartIndex );
  if ( !dwpart )
    return Failed;
  KMMessage::bodyPart( dwpart, &part, true );
  if ( !part.isComplete() )
     return Failed;

  if( !dynamic_cast<DwBody*>( dwpart->Parent() ) )
    return Failed;

  mTempFile.file()->writeBlock( part.bodyDecodedBinary() );
  mTempFile.file()->flush();

  KMail::EditorWatcher *watcher =
          new KMail::EditorWatcher( KURL( mTempFile.file()->name() ),
                                    part.typeStr() + "/" + part.subtypeStr(),
                                    false, this, parentWidget() );
  connect( watcher, TQT_SIGNAL(editDone(KMail::EditorWatcher*)), TQT_SLOT(editDone(KMail::EditorWatcher*)) );
  if ( !watcher->start() )
    return Failed;
  setEmitsCompletedItself( true );
  setDeletesItself( true );
  return OK;
}

void KMEditAttachmentCommand::editDone(KMail::EditorWatcher * watcher)
{
  kdDebug(5006) << k_funcinfo << endl;
  // anything changed?
  if ( !watcher->fileChanged() ) {
    kdDebug(5006) << k_funcinfo << "File has not been changed" << endl;
    setResult( Canceled );
    emit completed( this );
    deleteLater();
  }

  mTempFile.file()->reset();
  TQByteArray data = mTempFile.file()->readAll();

  // build the new message
  KMMessage *msg = retrievedMessage();
  KMMessagePart part;
  DwBodyPart *dwpart = msg->findPart( mPartIndex );
  KMMessage::bodyPart( dwpart, &part, true );

  DwBody *parentNode = dynamic_cast<DwBody*>( dwpart->Parent() );
  assert( parentNode );
  parentNode->RemoveBodyPart( dwpart );

  KMMessagePart att;
  att.duplicate( part );
  att.setBodyEncodedBinary( data );

  DwBodyPart* newDwPart = msg->createDWBodyPart( &att );
  parentNode->AddBodyPart( newDwPart );
  msg->getTopLevelPart()->Assemble();

  KMMessage *newMsg = new KMMessage();
  newMsg->fromDwString( msg->asDwString() );
  newMsg->setStatus( msg->status() );

  storeChangedMessage( newMsg );
}


CreateTodoCommand::CreateTodoCommand(TQWidget * parent, KMMessage * msg)
  : KMCommand( parent, msg )
{
}

KMCommand::Result CreateTodoCommand::execute()
{
  KMMessage *msg = retrievedMessage();
  if ( !msg || !msg->codec() ) {
    return Failed;
  }

  KMail::KorgHelper::ensureRunning();

  TQString txt = i18n("From: %1\nTo: %2\nSubject: %3").arg( msg->from() )
                .arg( msg->to() ).arg( msg->subject() );

  KTempFile tf;
  tf.setAutoDelete( true );
  TQString uri = "kmail:" + TQString::number( msg->getMsgSerNum() ) + "/" + msg->msgId();
  tf.file()->writeBlock( msg->asDwString().c_str(), msg->asDwString().length() );
  tf.close();

  KCalendarIface_stub *iface = new KCalendarIface_stub( kapp->dcopClient(), "korganizer", "CalendarIface" );
  iface->openTodoEditor( i18n("Mail: %1").arg( msg->subject() ), txt, uri,
                         tf.name(), TQStringList(), "message/rfc822", true );
  delete iface;

  return OK;
}

#include "kmcommands.moc"
