// kmcommands
// (c) 2002 Don Sanders <sanders@kde.org>
// License: GPL
//
// 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, KMFldSearch, 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 <mimelib/enum.h>
#include <mimelib/field.h>
#include <mimelib/mimepp.h>
#include <qptrlist.h>
#include <qregexp.h>
#include <qtextcodec.h>
#include <kfiledialog.h>
#include <kio/netaccess.h>
#include <kio/job.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kparts/browserextension.h>
#include <kprogress.h>
#include <krun.h>
#include <kdebug.h>

#include "kbusyptr.h"
#include "mailinglist-magic.h"
#include "kmaddrbook.h"
#include "kmcomposewin.h"
#include "kmfiltermgr.h"
#include "kmfolder.h"
#include "kmfolderimap.h"
#include "kmfoldermgr.h"
#include "kmheaders.h"
#include "kmkernel.h"
#include "kmmainwin.h"
#include "kmmessage.h"
#include "kmreaderwin.h"
#include "kmsender.h"
#include "kmundostack.h"

#include "kmcommands.h"
#include "kmcommands.moc"

#define IDENTITY_UOIDs

KMCommand::KMCommand( QWidget *parent )
  :mParent( parent ), mFolder( 0 )
{
}

KMCommand::KMCommand( QWidget *parent, const QPtrList<KMMsgBase> &msgList,
				KMFolder *folder )
  :mParent( parent ), mMsgList( msgList ), mFolder( folder )
{
}

KMCommand::KMCommand( QWidget *parent, KMMsgBase *msgBase )
  :mParent( parent ), mFolder( msgBase->parent() )
{
  QPtrList< KMMsgBase > msgList;
  msgList.append( msgBase );
  mMsgList = msgList;
}

KMCommand::~KMCommand()
{
}

void KMCommand::start()
{
  preTransfer();
}


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

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

int KMCommand::mCountJobs = 0;

void KMCommand::preTransfer()
{
  connect(this, SIGNAL(messagesTransfered(bool)),
	  this, SLOT(slotPostTransfer(bool)));
  if (!mFolder && (mMsgList.count() == 1) &&
      (mMsgList.getFirst()->isMessage())) {
    mRetrievedMsgs.append((KMMessage*)mMsgList.getFirst());
    emit messagesTransfered(true);
    return;
  }
  if ( (mMsgList.count() > 0) && !mFolder) {
    emit messagesTransfered(false);
    return;
  }

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

void KMCommand::slotPostTransfer(bool success)
{
  disconnect(this, SIGNAL(messagesTransfered(bool)),
	     this, SLOT(slotPostTransfer(bool)));
  if (success)
    execute();
  QPtrListIterator<KMMessage> it( mRetrievedMsgs );
  KMMessage* msg;
  while ( (msg = it.current()) != 0 )
  {
    ++it;
    msg->setTransferInProgress(false);
  }
  delete this;
}

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

  bool complete = true;
  KMCommand::mCountJobs = 0;
  mCountMsgs = 0;
  mRetrievedMsgs.clear();
  mCountMsgs = mMsgList.count();
  // the KProgressDialog for the user-feedback
  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
    int idx = mFolder->find(mb);
    if (idx < 0) continue;
    KMMessage *thisMsg = mFolder->getMsg(idx);
    if (!thisMsg) continue;

    if (thisMsg->parent() && thisMsg->parent()->protocol() == "imap" &&
        !thisMsg->isComplete() && !mProgressDialog->wasCancelled())
    {
      // the message needs to be transferred first
      complete = false;
      KMCommand::mCountJobs++;
      KMImapJob *imapJob = new KMImapJob(thisMsg);
      // emitted when the message was transferred successfully
      connect(imapJob, SIGNAL(messageRetrieved(KMMessage*)),
          this, SLOT(slotMsgTransfered(KMMessage*)));
      // emitted when the job is destroyed
      connect(imapJob, SIGNAL(finished()),
          this, SLOT(slotJobFinished()));
      // msg musn't be deleted
      thisMsg->setTransferInProgress(true);
    } else {
	if (thisMsg->parent() && thisMsg->parent()->protocol() == "imap")
	    thisMsg->setTransferInProgress(true);
      mRetrievedMsgs.append(thisMsg);
    }
  }

  if (complete)
  {
    delete mProgressDialog;
    emit messagesTransfered(true);
  } else {
    // wait for the transfer and tell the progressBar the necessary steps
    connect(mProgressDialog, SIGNAL(cancelClicked()),
        this, SLOT(slotTransferCancelled()));
    mProgressDialog->progressBar()->setTotalSteps(KMCommand::mCountJobs);
  }
}

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

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

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

  if (mProgressDialog->wasCancelled()) return;

  if ( (mCountMsgs - static_cast<int>(mRetrievedMsgs.count())) > KMCommand::mCountJobs )
  {
    // the message wasn't retrieved before => error
    mProgressDialog->hide();
    slotTransferCancelled();
    return;
  }
  // update the progressbar
  mProgressDialog->progressBar()->advance(1);
  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;
    emit messagesTransfered(true);
  }
}

void KMCommand::slotTransferCancelled()
{
  if (mFolder->protocol() != "imap") {
    emit messagesTransfered(false);
    return;
  }

  // kill the pending jobs
  KMAcctImap* acct = static_cast<KMFolderImap*>(mFolder)->account();
  if (acct)
  {
    acct->killAllJobs();
    acct->setIdle(true);
  }

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

KMMailtoComposeCommand::KMMailtoComposeCommand( const KURL &url,
						KMMsgBase *msgBase )
  :mUrl( url ), mMsgBase( msgBase )
{
}

void KMMailtoComposeCommand::execute()
{
  KMComposeWin *win;
  KMMessage *msg = new KMMessage;
#ifdef IDENTITY_UOIDs
  uint id = 0;
#else
  QString id = "";
#endif

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

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

  win = new KMComposeWin(msg, id);
  win->setCharset("", TRUE);
  win->show();
}


KMMailtoReplyCommand::KMMailtoReplyCommand( KMainWindow *parent,
   const KURL &url, KMMsgBase *msgBase, const QString &selection )
  :KMCommand( parent, msgBase ), mUrl( url ), mSelection( selection  )
{
}

void KMMailtoReplyCommand::execute()
{
  //TODO : consider factoring createReply into this method.
  KMMessage *msg = retrievedMessage();
  KMComposeWin *win;
  KMMessage *rmsg = msg->createReply(FALSE, FALSE, mSelection );
  rmsg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );

#ifdef IDENTITY_UOIDs
  win = new KMComposeWin(rmsg, 0);
#else
  win = new KMComposeWin(rmsg, QString::null);
#endif
  win->setCharset(msg->codec()->mimeName(), TRUE);
  win->setReplyFocus();
  win->show();
}


KMMailtoForwardCommand::KMMailtoForwardCommand( KMainWindow *parent,
   const KURL &url, KMMsgBase *msgBase )
  :KMCommand( parent, msgBase ), mUrl( url )
{
}

void KMMailtoForwardCommand::execute()
{
  //TODO : consider factoring createForward into this method.
  KMMessage *msg = retrievedMessage();
  KMComposeWin *win;
  KMMessage *fmsg = msg->createForward();
  fmsg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );

  win = new KMComposeWin(fmsg);
  win->setCharset(msg->codec()->mimeName(), TRUE);
  win->show();
}


KMMailtoAddAddrBookCommand::KMMailtoAddAddrBookCommand( const KURL &url,
   QWidget *parent )
  :mUrl( url ), mParent( parent )
{
}

void KMMailtoAddAddrBookCommand::execute()
{
  KMAddrBookExternal::addEmail( KMMessage::decodeMailtoUrl( mUrl.path() ), mParent );
}


KMMailtoOpenAddrBookCommand::KMMailtoOpenAddrBookCommand( const KURL &url,
   QWidget *parent )
  :mUrl( url ), mParent( parent )
{
}

void KMMailtoOpenAddrBookCommand::execute()
{
  KMAddrBookExternal::openEmail( KMMessage::decodeMailtoUrl( mUrl.path() ), mParent );
}


KMUrlCopyCommand::KMUrlCopyCommand( const KURL &url, KMMainWin *mainWin )
  :mUrl( url ), mMainWin( mainWin )
{
}

void KMUrlCopyCommand::execute()
{
  QClipboard* clip = QApplication::clipboard();

  if (mUrl.protocol() == "mailto") {
    // put the url into the mouse selection and the clipboard
    clip->setSelectionMode( true );
    clip->setText( KMMessage::decodeMailtoUrl( mUrl.path() ) );
    clip->setSelectionMode( false );
    clip->setText( KMMessage::decodeMailtoUrl( mUrl.path() ) );
    if (mMainWin)
      mMainWin->statusMsg( 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() );
    if ( mMainWin )
      mMainWin->statusMsg( i18n( "URL copied to clipboard." ));
  }
}


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

void KMUrlOpenCommand::execute()
{
  if (mUrl.isEmpty()) return;
  mReaderWin->slotUrlOpen( mUrl, KParts::URLArgs() );
}


KMUrlSaveCommand::KMUrlSaveCommand( const KURL &url, QWidget *parent )
  :mUrl( url ), mParent( parent )
{
}

void KMUrlSaveCommand::execute()
{
  if (mUrl.isEmpty()) return;
  KURL saveUrl = KFileDialog::getSaveURL(mUrl.fileName(), QString::null,
    mParent);
  if (saveUrl.isEmpty()) return;
  if (KIO::NetAccess::exists(saveUrl))
  {
    if (KMessageBox::warningContinueCancel(0,
        i18n("File %1 exists.\nDo you want to replace it?")
        .arg(saveUrl.prettyURL()), i18n("Save to File"), i18n("&Replace"))
        != KMessageBox::Continue)
      return;
  }
  KIO::Job *job = KIO::file_copy(mUrl, saveUrl, -1, true);
  connect(job, SIGNAL(result(KIO::Job*)), SLOT(slotUrlSaveResult(KIO::Job*)));
}

void KMUrlSaveCommand::slotUrlSaveResult( KIO::Job *job )
{
  if (job->error()) job->showErrorDialog();
}


KMEditMsgCommand::KMEditMsgCommand( KMainWindow *parent, KMMsgBase *msgBase )
  :KMCommand( parent, msgBase )
{
}

void KMEditMsgCommand::execute()
{
  KMMessage *msg = retrievedMessage();
  if (!msg || !msg->parent() ||
      !kernel->folderIsDraftOrOutbox( msg->parent() ))
    return;

  if (msg->parent() == kernel->outboxFolder() && msg->transferInProgress())
    return;

  msg->parent()->removeMsg(msg);
#if 0
  // Useful?
  mHeaders->setSelected(mHeaders->currentItem(), TRUE);
  mHeaders->highlightMessage(mHeaders->currentItem(), true);
#endif

  KMComposeWin *win = new KMComposeWin();
#if 0
  // FIXME: Poor solution, won't work for multiple readerwins should use kmkernel as an observer
  QObject::connect( win, SIGNAL( messageQueuedOrDrafted()),
		    this, SLOT( slotMessageQueuedOrDrafted()) );
#endif
  win->setMsg(msg, FALSE, TRUE);
  win->setFolder( msg->parent() );
  win->show();
}


KMShowMsgSrcCommand::KMShowMsgSrcCommand( KMainWindow *parent,
  KMMsgBase *msgBase, const QTextCodec *codec, bool fixedFont )
  :KMCommand( parent, msgBase ), mCodec( codec ), mFixedFont( fixedFont )
{
}

void KMShowMsgSrcCommand::execute()
{
  //TODO: move KMMessage::viewSource to here
  KMMessage *msg = retrievedMessage();
  if (!mCodec) //this is Auto setting
  {
    QCString cset = msg->charset();
    if (!cset.isEmpty())
      mCodec = KMMsgBase::codecForName( cset );
  }
  msg->viewSource( i18n("Message as Plain Text"), mCodec,
		   mFixedFont );
}

KMSaveMsgCommand::KMSaveMsgCommand( KMainWindow *parent,
  const QPtrList<KMMsgBase> &msgList, KMFolder *folder )
  :KMCommand( parent, msgList, folder )
{
  if (!msgList.getFirst())
    return;
  KMMsgBase *msgBase = msgList.getFirst();
  QString subject = msgBase->subject();
  while (subject.find(':') != -1)
    subject = subject.mid(subject.find(':') + 1).stripWhiteSpace();
  subject.replace(QRegExp(QChar(QDir::separator())), "_");
  mUrl = KFileDialog::getSaveURL(subject, QString::null);
}

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

void KMSaveMsgCommand::execute()
{
//TODO: Handle messages one by one
  QPtrList<KMMessage> msgList = retrievedMsgs();
  QCString str;

  for (KMMessage *msg = msgList.first(); msg; msg = msgList.next()) {
    str += "From " + msg->fromEmail() + " " + msg->dateShortStr() + "\n";
    str += msg->asString();
    str += "\n";
  }

  QByteArray ba = str;
  ba.resize(ba.size() - 1);
  kernel->byteArrayToRemoteFile(ba, mUrl);
}

//TODO: ReplyTo, NoQuoteReplyTo, ReplyList, ReplyToAll are all similar
//and should be factored
KMReplyToCommand::KMReplyToCommand( KMainWindow *parent, KMMsgBase *msgBase,
				    const QString &selection )
  : KMCommand( parent, msgBase ), mSelection( selection )
{
}

void KMReplyToCommand::execute()
{
  kernel->kbp()->busy();
  KMMessage *msg = retrievedMessage();
  KMMessage *reply = msg->createReply( FALSE, FALSE, mSelection );
  KMComposeWin *win = new KMComposeWin( reply );
  win->setCharset( msg->codec()->mimeName(), TRUE );
  win->setReplyFocus();
  win->show();
  kernel->kbp()->idle();
}


KMNoQuoteReplyToCommand::KMNoQuoteReplyToCommand( KMainWindow *parent,
						  KMMsgBase *msgBase )
  : KMCommand( parent, msgBase )
{
}

void KMNoQuoteReplyToCommand::execute()
{
  kernel->kbp()->busy();
  KMMessage *msg = retrievedMessage();
  KMMessage *reply = msg->createReply(FALSE, FALSE, "", TRUE);
  KMComposeWin *win = new KMComposeWin( reply );
  win->setCharset(msg->codec()->mimeName(), TRUE);
  win->setReplyFocus(false);
  win->show();
  kernel->kbp()->idle();
}


KMReplyListCommand::KMReplyListCommand( KMainWindow *parent,
  KMMsgBase *msgBase, const QString &selection )
 : KMCommand( parent, msgBase ), mSelection( selection )
{
}

void KMReplyListCommand::execute()
{
  kernel->kbp()->busy();
  KMMessage *msg = retrievedMessage();
  KMMessage *reply = msg->createReply(false, true, mSelection);
  KMComposeWin *win = new KMComposeWin( reply );
  win->setCharset(msg->codec()->mimeName(), TRUE);
  win->setReplyFocus(false);
  win->show();
  kernel->kbp()->idle();
}


KMReplyToAllCommand::KMReplyToAllCommand( KMainWindow *parent,
  KMMsgBase *msgBase, const QString &selection )
  :KMCommand( parent, msgBase ), mSelection( selection )
{
}

void KMReplyToAllCommand::execute()
{
  kernel->kbp()->busy();
  KMMessage *msg = retrievedMessage();
  KMMessage *reply = msg->createReply( TRUE, FALSE, mSelection );
  KMComposeWin *win = new KMComposeWin( reply );
  win->setCharset( msg->codec()->mimeName(), TRUE );
  win->setReplyFocus();
  win->show();
  kernel->kbp()->idle();
}


KMForwardCommand::KMForwardCommand( KMainWindow *parent,
  const QPtrList<KMMsgBase> &msgList, KMFolder *folder )
  : KMCommand( parent, msgList, folder ),
    mParent( parent ), mFolder( folder )
{
}

void KMForwardCommand::execute()
{
  KMComposeWin *win;
  QPtrList<KMMessage> msgList = retrievedMsgs();

  if (msgList.count() >= 2) {
    // ask if they want a mime digest forward

    if (KMessageBox::questionYesNo(mParent, i18n("Forward selected messages as"
						 " a MIME digest?"))
	== KMessageBox::Yes) {
      // we default to the first identity to save prompting the user
      // (the messages could have different identities)
      uint id = 0;
      KMMessage *fwdMsg = new KMMessage;
      KMMessagePart *msgPart = new KMMessagePart;
      QString msgPartText;
      int msgCnt = 0; // incase there are some we can't forward for some reason

      fwdMsg->initHeader(id);
      fwdMsg->setAutomaticFields(true);
      fwdMsg->mMsg->Headers().ContentType().CreateBoundary(1);
      QCString boundary( fwdMsg->mMsg->Headers().ContentType().Boundary().c_str() );
      msgPartText = i18n("\nThis is a MIME digest forward. The content of the"
                         " message is contained in the attachment(s).\n\n\n");
      // iterate through all the messages to be forwarded
      for (KMMessage *msg = msgList.first(); msg; msg = msgList.next()) {
        // set the identity
        if (id == 0)
          id = msg->headerField("X-KMail-Identity").stripWhiteSpace().toUInt();
        // set the part header
        msgPartText += "--";
        msgPartText += QString::fromLatin1( boundary );
        msgPartText += "\nContent-Type: MESSAGE/RFC822";
        msgPartText += QString("; CHARSET=%1").arg(msg->charset());
        msgPartText += "\n";
        DwHeaders dwh;
        dwh.MessageId().CreateDefault();
        msgPartText += QString("Content-ID: %1\n").arg(dwh.MessageId().AsString().c_str());
        msgPartText += QString("Content-Description: %1").arg(msg->subject());
        if (!msg->subject().contains("(fwd)"))
          msgPartText += " (fwd)";
        msgPartText += "\n\n";
        // set the part
        msgPartText += msg->headerAsString();
        msgPartText += "\n";
        msgPartText += msg->body();
        msgPartText += "\n";     // eot
        msgCnt++;
        fwdMsg->link(msg, KMMsgStatusForwarded);
      }
      msgPartText += "--";
      msgPartText += QString::fromLatin1( boundary );
      msgPartText += "--\n";
      QCString tmp;
      msgPart->setTypeStr("MULTIPART");
      tmp.sprintf( "Digest; boundary=\"%s\"", boundary.data() );
      msgPart->setSubtypeStr( tmp );
      msgPart->setName("unnamed");
      msgPart->setCte(DwMime::kCte7bit);   // does it have to be 7bit?
      msgPart->setContentDescription(QString("Digest of %1 messages.").arg(msgCnt));
      // THIS HAS TO BE AFTER setCte()!!!!
      msgPart->setBodyEncoded(QCString(msgPartText.ascii()));
      kernel->kbp()->busy();
      win = new KMComposeWin(fwdMsg, id);
      win->addAttach(msgPart);
      win->show();
      kernel->kbp()->idle();
      return;
    } else {            // NO MIME DIGEST, Multiple forward
      uint id = 0;
      QCString msgText = "";
      QPtrList<KMMessage> linklist;
      for (KMMessage *msg = msgList.first(); msg; msg = msgList.next()) {
        // set the identity
        if (id == 0)
          id = msg->headerField("X-KMail-Identity").stripWhiteSpace().toUInt();

        msgText += msg->createForwardBody();
        linklist.append(msg);
      }

      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())
        fwdMsg->link(msg, KMMsgStatusForwarded);

      kernel->kbp()->busy();
      win = new KMComposeWin(fwdMsg, id);
      win->setCharset("");
      win->show();
      kernel->kbp()->idle();
      return;
    }
  }

  // forward a single message at most.

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

  kernel->kbp()->busy();
  win = new KMComposeWin(msg->createForward());
  win->setCharset(msg->codec()->mimeName(), TRUE);
  win->show();
  kernel->kbp()->idle();
}


KMForwardAttachedCommand::KMForwardAttachedCommand( KMainWindow *parent,
  const QPtrList<KMMsgBase> &msgList, KMFolder *folder )
  : KMCommand( parent, msgList, folder ), mFolder( folder )
{
}

void KMForwardAttachedCommand::execute()
{
  QPtrList<KMMessage> msgList = retrievedMsgs();
  KMComposeWin *win;
  uint id = 0;
  KMMessage *fwdMsg = new KMMessage;

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

  fwdMsg->setAutomaticFields(true);

  kernel->kbp()->busy();
  win = new KMComposeWin(fwdMsg, id);

  // iterate through all the messages to be forwarded
  for (KMMessage *msg = msgList.first(); msg; msg = msgList.next()) {
    // set the part
    KMMessagePart *msgPart = new KMMessagePart;
    msgPart->setTypeStr("message");
    msgPart->setSubtypeStr("rfc822");
    msgPart->setCharset(msg->charset());
    msgPart->setName("forwarded message");
    msgPart->setContentDescription(msg->from()+": "+msg->subject());
    QValueList<int> dummy;
    msgPart->setBodyAndGuessCte(msg->asString(), dummy, true);
    msgPart->setCharset("");

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

  win->show();
  kernel->kbp()->idle();
}


KMRedirectCommand::KMRedirectCommand( KMainWindow *parent,
  KMMsgBase *msgBase )
  : KMCommand( parent, msgBase )
{
}

void KMRedirectCommand::execute()
{
  //TODO: move KMMessage::createRedirect to here
  KMComposeWin *win;
  KMMessage *msg = retrievedMessage();
  if (!msg || !msg->codec()) return;

  kernel->kbp()->busy();
  win = new KMComposeWin();
  win->setMsg(msg->createRedirect(), FALSE);
  win->setCharset(msg->codec()->mimeName());
  win->show();
  kernel->kbp()->idle();
}


KMBounceCommand::KMBounceCommand( KMainWindow *parent,
  KMMsgBase *msgBase )
  : KMCommand( parent, msgBase )
{
}

void KMBounceCommand::execute()
{
  KMMessage *msg = retrievedMessage();
  KMMessage *newMsg = msg->createBounce( TRUE /* with UI */);
  if (newMsg)
    kernel->msgSender()->send(newMsg, kernel->msgSender()->sendImmediate());
}


KMToggleFixedCommand::KMToggleFixedCommand( KMReaderWin *readerWin )
  : KMCommand(), mReaderWin( readerWin )
{
}

void KMToggleFixedCommand::execute()
{
  mReaderWin->slotToggleFixedFont();
}


KMPrintCommand::KMPrintCommand( KMainWindow *parent,
  KMMsgBase *msgBase, bool htmlOverride )
  : KMCommand( parent, msgBase ), mHtmlOverride( htmlOverride )
{
}

void KMPrintCommand::execute()
{
  KMReaderWin printWin;
  printWin.setPrinting(TRUE);
  printWin.readConfig();
  printWin.setHtmlOverride( mHtmlOverride );
  printWin.setMsg(retrievedMessage(), TRUE);
  printWin.printMsg();
}


//TODO: Use serial numbers
KMSetStatusCommand::KMSetStatusCommand( KMMsgStatus status,
  const QValueList<int> &ids, KMFolder *folder )
  : mStatus( status ), mIds( ids ), mFolder( folder )
{
}

void KMSetStatusCommand::execute()
{
   mFolder->setStatus( mIds, mStatus );
}


KMFilterCommand::KMFilterCommand( const QCString &field, const QString &value )
  : mField( field ), mValue( value )
{
}

void KMFilterCommand::execute()
{
  kernel->filterMgr()->createFilter( mField, mValue );
}


KMMailingListFilterCommand::KMMailingListFilterCommand( KMainWindow *parent,
  KMMsgBase *msgBase )
  : KMCommand( parent, msgBase )
{
}

void KMMailingListFilterCommand::execute()
{
  QCString name;
  QString value;
  KMMessage *msg = retrievedMessage();
  if (!msg)
    return;

  if (!KMMLInfo::name( msg, name, value ).isNull())
    kernel->filterMgr()->createFilter( name, value );
}


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

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

  return menu;
}

QPopupMenu* KMMenuCommand::makeFolderMenu(KMFolderNode* node, bool move,
  QObject *receiver, KMMenuToFolder *aMenuToFolder, QPopupMenu *menu )
{
  // connect the signals
  if (move)
  {
    disconnect(menu, SIGNAL(activated(int)), receiver,
           SLOT(moveSelectedToFolder(int)));
    connect(menu, SIGNAL(activated(int)), receiver,
             SLOT(moveSelectedToFolder(int)));
  } else {
    disconnect(menu, SIGNAL(activated(int)), receiver,
           SLOT(copySelectedToFolder(int)));
    connect(menu, SIGNAL(activated(int)), receiver,
             SLOT(copySelectedToFolder(int)));
  }

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

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

  if (!folderDir)
    return menu;

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


KMCopyCommand::KMCopyCommand( KMFolder* srcFolder, KMFolder* destFolder,
			      const QPtrList<KMMsgBase> &msgList )
  :mSrcFolder( srcFolder ), mDestFolder( destFolder ), mMsgList( msgList )
{
}

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

  kernel->kbp()->busy();
  mDestFolder->open();

  for (msgBase = mMsgList.first(); msgBase; msgBase = mMsgList.next() )
  {
    if (isMessage = msgBase->isMessage())
    {
      msg = static_cast<KMMessage*>(msgBase);
    } else {
      idx = mSrcFolder->find(msgBase);
      assert(idx != -1);
      msg = mSrcFolder->getMsg(idx);
    }

    if (mSrcFolder &&
	(mSrcFolder->protocol() == "imap") &&
	(mDestFolder->protocol() == "imap") &&
	(static_cast<KMFolderImap*>(mSrcFolder)->account() ==
	 static_cast<KMFolderImap*>(mDestFolder)->account()))
    {
      list.append(msg);
    } else {
      newMsg = new KMMessage;
      newMsg->fromString(msg->asString());
      newMsg->setStatus(msg->status());
      newMsg->setComplete(msg->isComplete());

      if (mSrcFolder && (mSrcFolder->protocol() == "imap") &&
	  !newMsg->isComplete())
      {
        kernel->filterMgr()->tempOpenFolder(mDestFolder);
	newMsg->setParent(msg->parent());
        KMImapJob *imapJob = new KMImapJob(newMsg);
        connect(imapJob, SIGNAL(messageRetrieved(KMMessage*)),
		mDestFolder, SLOT(reallyAddCopyOfMsg(KMMessage*)));
      } else {
	int rc, index;
	rc = mDestFolder->addMsg(newMsg, &index);
	if (rc == 0 && index != -1)
	  mDestFolder->unGetMsg( mDestFolder->count() - 1 );
      }
    }

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

  } // end for

//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);
    imapDestFolder->copyMsg(list);
    imapDestFolder->getFolder();
  }

  mDestFolder->close();
  kernel->kbp()->idle();
}


KMMoveCommand::KMMoveCommand( KMFolder* srcFolder, KMFolder* destFolder,
			      const QPtrList<KMMsgBase> &msgList,
			      KMHeaders *headers )
  :mSrcFolder( srcFolder ), mDestFolder( destFolder ), mMsgList( msgList ),
   mHeaders( headers )
{
// TODO: mHeaders is optional ...
}

void KMMoveCommand::execute()
{
  if (mDestFolder == mSrcFolder)
    return;

  if (mDestFolder && mDestFolder->open() != 0)
    return;
  kernel->kbp()->busy();

  KMMsgBase *curMsg = 0;
  int contentX, contentY;
  if (mHeaders)
    mHeaders->prepareMove( &curMsg, &contentX, &contentY );

  KMMessage *msg;
  KMMsgBase *msgBase;
  int rc = 0;
  int index;
  QPtrList<KMMessage> list;
  for (msgBase=mMsgList.first(); msgBase && !rc; msgBase=mMsgList.next())
  {
    bool undo = msgBase->enableUndo();
    int idx = mSrcFolder->find(msgBase);
    assert(idx != -1);
    msg = mSrcFolder->getMsg(idx);
    if (msg->transferInProgress()) continue;

    if (mDestFolder) {
      if (mDestFolder->protocol() == "imap")
        list.append(msg);
      else
      {
        rc = mDestFolder->moveMsg(msg, &index);
        if (rc == 0 && index != -1) {
          KMMsgBase *mb = mDestFolder->unGetMsg( mDestFolder->count() - 1 );
          if (undo)
          {
            kernel->undoStack()->pushAction( mb->getMsgSerNum(), mSrcFolder,
                mDestFolder );
          }
        }
      }
    }
    else
    {
      // really delete messages that are already in the trash folder
      if (mSrcFolder->protocol() == "imap")
        list.append(msg);
      else
      {
        mSrcFolder->removeMsg(msg);
        delete msg;
      }
    }
  }
  if (!list.isEmpty())
  {
    if (mDestFolder)
      mDestFolder->moveMsg(list, &index);
    else
      mSrcFolder->removeMsg(list);
  }

  if (mHeaders)
    mHeaders->finalizeMove( curMsg, contentX, contentY );

  if (mDestFolder) {
     mDestFolder->sync();
     mDestFolder->close();
  }
  kernel->kbp()->idle();
}


KMDeleteMsgCommand::KMDeleteMsgCommand( KMFolder* srcFolder,
  const QPtrList<KMMsgBase> &msgList, KMHeaders *headers )
{
  KMFolder* folder = 0;
  if (srcFolder->protocol() == "imap")
  {
    KMFolderImap* fi = static_cast<KMFolderImap*> (srcFolder);
    QString trashStr = fi->account()->trash();
    KMFolder* trash = kernel->imapFolderMgr()->findIdString( trashStr );
    if (!trash) trash = kernel->trashFolder();
    if (srcFolder != trash)
    {
      folder = trash;
    } else {
      // delete msg
      headers->moveMsgToFolder(0);
    }
  } else {
    if (srcFolder != kernel->trashFolder())
    {
      // move to trash folder
      folder = kernel->trashFolder();
    } else {
      // delete msg
      headers->moveMsgToFolder(0);
    }
  }

  if (folder) {
    KMCommand *command = new KMMoveCommand( srcFolder, folder, msgList,
					    headers );
    command->start();
  }
}

void KMDeleteMsgCommand::execute()
{
}


KMUrlClickedCommand::KMUrlClickedCommand( const KURL &url, KMFolder* folder,
  KMReaderWin *readerWin, bool htmlPref, KMMainWin *mainWin )
  :mUrl( url ), mFolder( folder ), mReaderWin( readerWin ),
   mHtmlPref( htmlPref ), mMainWin( mainWin )
{
}

void KMUrlClickedCommand::execute()
{
  KMComposeWin *win;
  KMMessage* msg;

  if (mUrl.protocol() == "mailto")
  {
#ifdef IDENTITY_UOIDs
    uint id = 0;
#else
    QString id = "";
#endif
    if ( mFolder )
      id = mFolder->identity();

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

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

    win = new KMComposeWin(msg, id);
    win->setCharset("", TRUE);
    win->show();
  }
  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"))
  {
    if (mMainWin)
      mMainWin->statusMsg(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( "Do you really want to execute"
        " '%1'? " ).arg( mUrl.prettyURL() ) ) != KMessageBox::Yes) return;
    }
    (void) new KRun( mUrl );
  }
}
