• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • kdepim
  • Sitemap
  • Contact Us
 

kmail

kmcommands.cpp

Go to the documentation of this file.
00001 /* -*- mode: C++; c-file-style: "gnu" -*-
00002     This file is part of KMail, the KDE mail client.
00003     Copyright (c) 2002 Don Sanders <sanders@kde.org>
00004 
00005     KMail is free software; you can redistribute it and/or modify it
00006     under the terms of the GNU General Public License, version 2, as
00007     published by the Free Software Foundation.
00008 
00009     KMail is distributed in the hope that it will be useful, but
00010     WITHOUT ANY WARRANTY; without even the implied warranty of
00011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012     General Public License for more details.
00013 
00014     You should have received a copy of the GNU General Public License
00015     along with this program; if not, write to the Free Software
00016     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
00017 */
00018 
00019 //
00020 // This file implements various "command" classes. These command classes
00021 // are based on the command design pattern.
00022 //
00023 // Historically various operations were implemented as slots of KMMainWin.
00024 // This proved inadequate as KMail has multiple top level windows
00025 // (KMMainWin, KMReaderMainWin, SearchWindow, KMComposeWin) that may
00026 // benefit from using these operations. It is desirable that these
00027 // classes can operate without depending on or altering the state of
00028 // a KMMainWin, in fact it is possible no KMMainWin object even exists.
00029 //
00030 // Now these operations have been rewritten as KMCommand based classes,
00031 // making them independent of KMMainWin.
00032 //
00033 // The base command class KMCommand is async, which is a difference
00034 // from the conventional command pattern. As normal derived classes implement
00035 // the execute method, but client classes call start() instead of
00036 // calling execute() directly. start() initiates async operations,
00037 // and on completion of these operations calls execute() and then deletes
00038 // the command. (So the client must not construct commands on the stack).
00039 //
00040 // The type of async operation supported by KMCommand is retrieval
00041 // of messages from an IMAP server.
00042 
00043 #include "kmcommands.h"
00044 
00045 #include <unistd.h> // link()
00046 #include <errno.h>
00047 #include <mimelib/enum.h>
00048 #include <mimelib/field.h>
00049 #include <mimelib/mimepp.h>
00050 #include <mimelib/string.h>
00051 
00052 //Added by qt3to4:
00053 #include <QMenu>
00054 #include <QByteArray>
00055 #include <kprogressdialog.h>
00056 
00057 #include <QApplication>
00058 #include <QDesktopWidget>
00059 #include <QList>
00060 #include <QTextCodec>
00061 #include <QProgressBar>
00062 
00063 #include <kpimutils/email.h>
00064 #include <kdbusservicestarter.h>
00065 #include <kdebug.h>
00066 #include <kfiledialog.h>
00067 #include <kabc/stdaddressbook.h>
00068 #include <kabc/addresseelist.h>
00069 #include <klocale.h>
00070 #include <kmessagebox.h>
00071 #include <kmimetypetrader.h>
00072 #include <kparts/browserextension.h>
00073 #include <krun.h>
00074 #include <kbookmarkmanager.h>
00075 #include <kstandarddirs.h>
00076 #include <ktemporaryfile.h>
00077 // KIO headers
00078 #include <kio/job.h>
00079 #include <kio/jobuidelegate.h>
00080 #include <kio/netaccess.h>
00081 
00082 #include <kpimidentities/identitymanager.h>
00083 
00084 #include "actionscheduler.h"
00085 using KMail::ActionScheduler;
00086 #include "mailinglist-magic.h"
00087 #include "kmaddrbook.h"
00088 #include <kaddrbookexternal.h>
00089 #include "composer.h"
00090 #include "kmfiltermgr.h"
00091 #include "kmfoldermbox.h"
00092 #include "kmfolderimap.h"
00093 #include "kmfoldermgr.h"
00094 #include "kmheaders.h"
00095 #include "headeritem.h"
00096 #include "kmmainwidget.h"
00097 #include "kmmsgdict.h"
00098 #include "messagesender.h"
00099 #include "kmmsgpartdlg.h"
00100 #include "undostack.h"
00101 #include "kcursorsaver.h"
00102 #include "partNode.h"
00103 #include "objecttreeparser.h"
00104 using KMail::ObjectTreeParser;
00105 using KMail::FolderJob;
00106 #include "chiasmuskeyselector.h"
00107 #include "mailsourceviewer.h"
00108 using KMail::MailSourceViewer;
00109 #include "kmreadermainwin.h"
00110 #include "secondarywindow.h"
00111 using KMail::SecondaryWindow;
00112 #include "redirectdialog.h"
00113 using KMail::RedirectDialog;
00114 #include "util.h"
00115 #include "templateparser.h"
00116 using KMail::TemplateParser;
00117 #include "editorwatcher.h"
00118 #include "korghelper.h"
00119 
00120 #include "broadcaststatus.h"
00121 #include "globalsettings.h"
00122 
00123 #include <kpimutils/kfileio.h>
00124 #include "calendarinterface.h"
00125 #include "interfaces/htmlwriter.h"
00126 
00127 #include "progressmanager.h"
00128 using KPIM::ProgressManager;
00129 using KPIM::ProgressItem;
00130 #include <kmime/kmime_mdn.h>
00131 using namespace KMime;
00132 
00133 #include "kleo/specialjob.h"
00134 #include "kleo/cryptobackend.h"
00135 #include "kleo/cryptobackendfactory.h"
00136 
00137 #include <gpgme++/error.h>
00138 
00139 #include <QClipboard>
00140 #include <QDBusMessage>
00141 #include <QDBusConnection>
00142 
00143 #include <memory>
00144 
00145 class LaterDeleterWithCommandCompletion : public KMail::Util::LaterDeleter
00146 {
00147 public:
00148   LaterDeleterWithCommandCompletion( KMCommand* command )
00149     :LaterDeleter( command ), m_result( KMCommand::Failed )
00150   {
00151   }
00152   ~LaterDeleterWithCommandCompletion()
00153   {
00154     setResult( m_result );
00155     KMCommand *command = static_cast<KMCommand*>( m_object );
00156     emit command->completed( command );
00157   }
00158   void setResult( KMCommand::Result v ) { m_result = v; }
00159 private:
00160   KMCommand::Result m_result;
00161 };
00162 
00163 
00164 KMCommand::KMCommand( QWidget *parent )
00165   : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
00166     mEmitsCompletedItself( false ), mParent( parent )
00167 {
00168 }
00169 
00170 KMCommand::KMCommand( QWidget *parent, const QList<KMMsgBase*> &msgList )
00171   : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
00172     mEmitsCompletedItself( false ), mParent( parent ), mMsgList( msgList )
00173 {
00174 }
00175 
00176 KMCommand::KMCommand( QWidget *parent, KMMsgBase *msgBase )
00177   : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
00178     mEmitsCompletedItself( false ), mParent( parent )
00179 {
00180   mMsgList.append( msgBase );
00181 }
00182 
00183 KMCommand::KMCommand( QWidget *parent, KMMessage *msg )
00184   : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
00185     mEmitsCompletedItself( false ), mParent( parent )
00186 {
00187   if ( msg ) {
00188     mMsgList.append( &msg->toMsgBase() );
00189   }
00190 }
00191 
00192 KMCommand::~KMCommand()
00193 {
00194   QList<QPointer<KMFolder> >::Iterator fit;
00195   for ( fit = mFolders.begin(); fit != mFolders.end(); ++fit ) {
00196     if ( !(*fit) ) {
00197       continue;
00198     }
00199     (*fit)->close( "kmcommand" );
00200   }
00201 }
00202 
00203 KMCommand::Result KMCommand::result() const
00204 {
00205   if ( mResult == Undefined ) {
00206     kDebug(5006) <<"mResult is Undefined";
00207   }
00208   return mResult;
00209 }
00210 
00211 void KMCommand::start()
00212 {
00213   QTimer::singleShot( 0, this, SLOT( slotStart() ) );
00214 }
00215 
00216 
00217 const QList<KMMessage*> KMCommand::retrievedMsgs() const
00218 {
00219   return mRetrievedMsgs;
00220 }
00221 
00222 KMMessage *KMCommand::retrievedMessage() const
00223 {
00224   return *(mRetrievedMsgs.begin());
00225 }
00226 
00227 QWidget *KMCommand::parentWidget() const
00228 {
00229   return mParent;
00230 }
00231 
00232 int KMCommand::mCountJobs = 0;
00233 
00234 void KMCommand::slotStart()
00235 {
00236   connect( this, SIGNAL( messagesTransfered( KMCommand::Result ) ),
00237            this, SLOT( slotPostTransfer( KMCommand::Result ) ) );
00238   kmkernel->filterMgr()->ref();
00239 
00240   if ( mMsgList.contains(0) ) {
00241       emit messagesTransfered( Failed );
00242       return;
00243   }
00244 
00245   KMMsgBase *mb = *(mMsgList.begin());
00246   if ( (mMsgList.count() == 1) && ( mb->isMessage() ) &&
00247       ( mb->parent() == 0 ) )
00248   {
00249     // Special case of operating on message that isn't in a folder
00250     mRetrievedMsgs.append((KMMessage*)mMsgList.takeFirst());
00251     emit messagesTransfered( OK );
00252     return;
00253   }
00254 
00255   QList<KMMsgBase*>::const_iterator it;
00256   for ( it = mMsgList.begin(); it != mMsgList.end(); ++it )
00257     if ( !( (*it)->parent() ) ) {
00258       emit messagesTransfered( Failed );
00259       return;
00260     } else {
00261       keepFolderOpen( (*it)->parent() );
00262     }
00263 
00264   // transfer the selected messages first
00265   transferSelectedMsgs();
00266 }
00267 
00268 void KMCommand::slotPostTransfer( KMCommand::Result result )
00269 {
00270   disconnect( this, SIGNAL( messagesTransfered( KMCommand::Result ) ),
00271               this, SLOT( slotPostTransfer( KMCommand::Result ) ) );
00272   if ( result == OK )
00273     result = execute();
00274   mResult = result;
00275   KMMessage* msg;
00276   foreach( msg, mRetrievedMsgs ) {
00277     if ( msg && msg->parent() )
00278       msg->setTransferInProgress(false);
00279   }
00280   kmkernel->filterMgr()->deref();
00281   if ( !emitsCompletedItself() )
00282     emit completed( this );
00283   if ( !deletesItself() )
00284     deleteLater();
00285 }
00286 
00287 void KMCommand::transferSelectedMsgs()
00288 {
00289   // make sure no other transfer is active
00290   if (KMCommand::mCountJobs > 0) {
00291     emit messagesTransfered( Failed );
00292     return;
00293   }
00294 
00295   bool complete = true;
00296   KMCommand::mCountJobs = 0;
00297   mCountMsgs = 0;
00298   mRetrievedMsgs.clear();
00299   mCountMsgs = mMsgList.count();
00300   uint totalSize = 0;
00301   // the KProgressDialog for the user-feedback. Only enable it if it's needed.
00302   // For some commands like KMSetStatusCommand it's not needed. Note, that
00303   // for some reason the KProgressDialog eats the MouseReleaseEvent (if a
00304   // command is executed after the MousePressEvent), cf. bug #71761.
00305   if ( mCountMsgs > 0 ) {
00306     mProgressDialog = new KProgressDialog(mParent,
00307       i18n("Please wait"),
00308       i18np("Please wait while the message is transferred",
00309         "Please wait while the %1 messages are transferred", mMsgList.count()));
00310     mProgressDialog->setModal(true);
00311     mProgressDialog->setMinimumDuration(1000);
00312   }
00313   QList<KMMsgBase*>::const_iterator it;
00314   for ( it = mMsgList.begin(); it != mMsgList.end(); ++it )
00315   {
00316     KMMsgBase *mb = (*it);
00317     // check if all messages are complete
00318     KMMessage *thisMsg = 0;
00319     if ( mb->isMessage() )
00320       thisMsg = static_cast<KMMessage*>(mb);
00321     else
00322     {
00323       KMFolder *folder = mb->parent();
00324       int idx = folder->find(mb);
00325       if (idx < 0) continue;
00326       thisMsg = folder->getMsg(idx);
00327     }
00328     if (!thisMsg) continue;
00329     if ( thisMsg->transferInProgress() &&
00330          thisMsg->parent()->folderType() == KMFolderTypeImap )
00331     {
00332       thisMsg->setTransferInProgress( false, true );
00333       thisMsg->parent()->ignoreJobsForMessage( thisMsg );
00334     }
00335 
00336     if ( thisMsg->parent() && !thisMsg->isComplete() &&
00337          ( !mProgressDialog || !mProgressDialog->wasCancelled() ) )
00338     {
00339       kDebug(5006)<<"### INCOMPLETE";
00340       // the message needs to be transferred first
00341       complete = false;
00342       KMCommand::mCountJobs++;
00343       FolderJob *job = thisMsg->parent()->createJob(thisMsg);
00344       job->setCancellable( false );
00345       totalSize += thisMsg->msgSizeServer();
00346       // emitted when the message was transferred successfully
00347       connect(job, SIGNAL(messageRetrieved(KMMessage*)),
00348               this, SLOT(slotMsgTransfered(KMMessage*)));
00349       // emitted when the job is destroyed
00350       connect(job, SIGNAL(finished()),
00351               this, SLOT(slotJobFinished()));
00352       connect(job, SIGNAL(progress(unsigned long, unsigned long)),
00353               this, SLOT(slotProgress(unsigned long, unsigned long)));
00354       // msg musn't be deleted
00355       thisMsg->setTransferInProgress(true);
00356       job->start();
00357     } else {
00358       thisMsg->setTransferInProgress(true);
00359       mRetrievedMsgs.append(thisMsg);
00360     }
00361   }
00362 
00363   if (complete)
00364   {
00365     delete mProgressDialog;
00366     mProgressDialog = 0;
00367     emit messagesTransfered( OK );
00368   } else {
00369     // wait for the transfer and tell the progressBar the necessary steps
00370     if ( mProgressDialog ) {
00371       connect(mProgressDialog, SIGNAL(cancelClicked()),
00372               this, SLOT(slotTransferCancelled()));
00373       mProgressDialog->progressBar()->setMaximum(totalSize);
00374     }
00375   }
00376 }
00377 
00378 void KMCommand::slotMsgTransfered(KMMessage* msg)
00379 {
00380   if ( mProgressDialog && mProgressDialog->wasCancelled() ) {
00381     emit messagesTransfered( Canceled );
00382     return;
00383   }
00384 
00385   // save the complete messages
00386   mRetrievedMsgs.append(msg);
00387 }
00388 
00389 void KMCommand::slotProgress( unsigned long done, unsigned long /*total*/ )
00390 {
00391   mProgressDialog->progressBar()->setValue( done );
00392 }
00393 
00394 void KMCommand::slotJobFinished()
00395 {
00396   // the job is finished (with / without error)
00397   KMCommand::mCountJobs--;
00398 
00399   if ( mProgressDialog && mProgressDialog->wasCancelled() ) return;
00400 
00401   if ( (mCountMsgs - static_cast<int>(mRetrievedMsgs.count())) > KMCommand::mCountJobs )
00402   {
00403     // the message wasn't retrieved before => error
00404     if ( mProgressDialog )
00405       mProgressDialog->hide();
00406     slotTransferCancelled();
00407     return;
00408   }
00409   // update the progressbar
00410   if ( mProgressDialog ) {
00411     mProgressDialog->setLabelText(i18np("Please wait while the message is transferred",
00412           "Please wait while the %1 messages are transferred", KMCommand::mCountJobs));
00413   }
00414   if (KMCommand::mCountJobs == 0)
00415   {
00416     // all done
00417     delete mProgressDialog;
00418     mProgressDialog = 0;
00419     emit messagesTransfered( OK );
00420   }
00421 }
00422 
00423 void KMCommand::slotTransferCancelled()
00424 {
00425   // kill the pending jobs
00426   QList<QPointer<KMFolder> >::Iterator fit;
00427   for ( fit = mFolders.begin(); fit != mFolders.end(); ++fit ) {
00428     if (!(*fit))
00429       continue;
00430     KMFolder *folder = *fit;
00431     KMFolderImap *imapFolder = dynamic_cast<KMFolderImap*>(folder);
00432     if (imapFolder && imapFolder->account()) {
00433       imapFolder->account()->killAllJobs();
00434     }
00435   }
00436 
00437   KMCommand::mCountJobs = 0;
00438   mCountMsgs = 0;
00439   // unget the transfered messages
00440   QList<KMMessage*>::const_iterator it;
00441   for ( it = mRetrievedMsgs.constBegin(); it != mRetrievedMsgs.constEnd(); ++it ) {
00442     KMMessage* msg = (*it);
00443     KMFolder *folder = msg->parent();
00444     ++it;
00445     if (!folder)
00446       continue;
00447     msg->setTransferInProgress(false);
00448     int idx = folder->find(msg);
00449     if (idx > 0) folder->unGetMsg(idx);
00450   }
00451   mRetrievedMsgs.clear();
00452   emit messagesTransfered( Canceled );
00453 }
00454 
00455 void KMCommand::keepFolderOpen( KMFolder *folder )
00456 {
00457   folder->open( "kmcommand" );
00458   mFolders.append( folder );
00459 }
00460 
00461 KMMailtoComposeCommand::KMMailtoComposeCommand( const KUrl &url,
00462                                                 KMMessage *msg )
00463   :mUrl( url ), mMessage( msg )
00464 {
00465 }
00466 
00467 KMCommand::Result KMMailtoComposeCommand::execute()
00468 {
00469   KMMessage *msg = new KMMessage;
00470   uint id = 0;
00471 
00472   if ( mMessage && mMessage->parent() )
00473     id = mMessage->parent()->identity();
00474 
00475   msg->initHeader(id);
00476   msg->setCharset("utf-8");
00477   msg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );
00478 
00479   KMail::Composer * win = KMail::makeComposer( msg, id );
00480   win->setCharset("", true);
00481   win->setFocusToSubject();
00482   win->show();
00483 
00484   return OK;
00485 }
00486 
00487 
00488 KMMailtoReplyCommand::KMMailtoReplyCommand( QWidget *parent,
00489    const KUrl &url, KMMessage *msg, const QString &selection )
00490   :KMCommand( parent, msg ), mUrl( url ), mSelection( selection  )
00491 {
00492 }
00493 
00494 KMCommand::Result KMMailtoReplyCommand::execute()
00495 {
00496   //TODO : consider factoring createReply into this method.
00497   KMMessage *msg = retrievedMessage();
00498   if ( !msg || !msg->codec() ) {
00499     return Failed;
00500   }
00501   KMMessage *rmsg = msg->createReply( KMail::ReplyNone, mSelection );
00502   rmsg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );
00503 
00504   KMail::Composer * win = KMail::makeComposer( rmsg, 0 );
00505   win->setCharset( msg->codec()->name(), true );
00506   win->setReplyFocus();
00507   win->show();
00508 
00509   return OK;
00510 }
00511 
00512 
00513 KMMailtoForwardCommand::KMMailtoForwardCommand( QWidget *parent,
00514    const KUrl &url, KMMessage *msg )
00515   :KMCommand( parent, msg ), mUrl( url )
00516 {
00517 }
00518 
00519 KMCommand::Result KMMailtoForwardCommand::execute()
00520 {
00521   //TODO : consider factoring createForward into this method.
00522   KMMessage *msg = retrievedMessage();
00523   if ( !msg || !msg->codec() ) {
00524     return Failed;
00525   }
00526   KMMessage *fmsg = msg->createForward();
00527   fmsg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );
00528 
00529   KMail::Composer * win = KMail::makeComposer( fmsg );
00530   win->setCharset( msg->codec()->name(), true );
00531   win->show();
00532 
00533   return OK;
00534 }
00535 
00536 
00537 KMAddBookmarksCommand::KMAddBookmarksCommand( const KUrl &url, QWidget *parent )
00538   : KMCommand( parent ), mUrl( url )
00539 {
00540 }
00541 
00542 KMCommand::Result KMAddBookmarksCommand::execute()
00543 {
00544   QString filename = KStandardDirs::locateLocal( "data", QString::fromLatin1("konqueror/bookmarks.xml") );
00545   KBookmarkManager *bookManager = KBookmarkManager::managerForFile( filename, "konqueror" );
00546   KBookmarkGroup group = bookManager->root();
00547   group.addBookmark( mUrl.path(), KUrl( mUrl ) );
00548   if( bookManager->save() ) {
00549     bookManager->emitChanged( group );
00550   }
00551 
00552   return OK;
00553 }
00554 
00555 KMMailtoAddAddrBookCommand::KMMailtoAddAddrBookCommand( const KUrl &url,
00556    QWidget *parent )
00557   : KMCommand( parent ), mUrl( url )
00558 {
00559 }
00560 
00561 KMCommand::Result KMMailtoAddAddrBookCommand::execute()
00562 {
00563   KPIM::KAddrBookExternal::addEmail( KMMessage::decodeMailtoUrl( mUrl.path() ),
00564                                      parentWidget() );
00565 
00566   return OK;
00567 }
00568 
00569 
00570 KMMailtoOpenAddrBookCommand::KMMailtoOpenAddrBookCommand( const KUrl &url,
00571    QWidget *parent )
00572   : KMCommand( parent ), mUrl( url )
00573 {
00574 }
00575 
00576 KMCommand::Result KMMailtoOpenAddrBookCommand::execute()
00577 {
00578   QString addr = KMMessage::decodeMailtoUrl( mUrl.path() );
00579   KPIM::KAddrBookExternal::openEmail( KPIMUtils::extractEmailAddress(addr),
00580                                       addr, parentWidget() );
00581 
00582   return OK;
00583 }
00584 
00585 
00586 KMUrlCopyCommand::KMUrlCopyCommand( const KUrl &url, KMMainWidget *mainWidget )
00587   :mUrl( url ), mMainWidget( mainWidget )
00588 {
00589 }
00590 
00591 KMCommand::Result KMUrlCopyCommand::execute()
00592 {
00593   QClipboard* clip = QApplication::clipboard();
00594 
00595   if (mUrl.protocol() == "mailto") {
00596     // put the url into the mouse selection and the clipboard
00597     QString address = KMMessage::decodeMailtoUrl( mUrl.path() );
00598     clip->setText( address, QClipboard::Clipboard );
00599     clip->setText( address, QClipboard::Selection );
00600     KPIM::BroadcastStatus::instance()->setStatusMsg( i18n( "Address copied to clipboard." ));
00601   } else {
00602     // put the url into the mouse selection and the clipboard
00603     clip->setText( mUrl.url(), QClipboard::Clipboard );
00604     clip->setText( mUrl.url(), QClipboard::Selection );
00605     KPIM::BroadcastStatus::instance()->setStatusMsg( i18n( "URL copied to clipboard." ));
00606   }
00607 
00608   return OK;
00609 }
00610 
00611 
00612 KMUrlOpenCommand::KMUrlOpenCommand( const KUrl &url, KMReaderWin *readerWin )
00613   :mUrl( url ), mReaderWin( readerWin )
00614 {
00615 }
00616 
00617 KMCommand::Result KMUrlOpenCommand::execute()
00618 {
00619   if ( !mUrl.isEmpty() )
00620     mReaderWin->slotUrlOpen( mUrl, KParts::OpenUrlArguments(), KParts::BrowserArguments() );
00621 
00622   return OK;
00623 }
00624 
00625 
00626 KMUrlSaveCommand::KMUrlSaveCommand( const KUrl &url, QWidget *parent )
00627   : KMCommand( parent ), mUrl( url )
00628 {
00629 }
00630 
00631 KMCommand::Result KMUrlSaveCommand::execute()
00632 {
00633   if ( mUrl.isEmpty() )
00634     return OK;
00635   KUrl saveUrl = KFileDialog::getSaveUrl(mUrl.fileName(), QString(),
00636                                          parentWidget() );
00637   if ( saveUrl.isEmpty() )
00638     return Canceled;
00639   if ( KIO::NetAccess::exists( saveUrl, KIO::NetAccess::DestinationSide, parentWidget() ) )
00640   {
00641     if (KMessageBox::warningContinueCancel(0,
00642         i18nc("@info", "File <filename>%1</filename> exists.<nl/>Do you want to replace it?",
00643          saveUrl.pathOrUrl()), i18n("Save to File"), KGuiItem(i18n("&Replace")))
00644         != KMessageBox::Continue)
00645       return Canceled;
00646   }
00647   KIO::Job *job = KIO::file_copy(mUrl, saveUrl, -1, KIO::Overwrite);
00648   connect(job, SIGNAL(result(KJob*)), SLOT(slotUrlSaveResult(KJob*)));
00649   setEmitsCompletedItself( true );
00650   return OK;
00651 }
00652 
00653 void KMUrlSaveCommand::slotUrlSaveResult( KJob *job )
00654 {
00655   if ( job->error() ) {
00656     static_cast<KIO::Job*>(job)->ui()->showErrorMessage();
00657     setResult( Failed );
00658     emit completed( this );
00659   }
00660   else {
00661     setResult( OK );
00662     emit completed( this );
00663   }
00664 }
00665 
00666 
00667 KMEditMsgCommand::KMEditMsgCommand( QWidget *parent, KMMessage *msg )
00668   :KMCommand( parent, msg )
00669 {
00670 }
00671 
00672 KMCommand::Result KMEditMsgCommand::execute()
00673 {
00674   KMMessage *msg = retrievedMessage();
00675   if (!msg || !msg->parent() ||
00676       ( !kmkernel->folderIsDraftOrOutbox( msg->parent() ) &&
00677         !kmkernel->folderIsTemplates( msg->parent() ) ) ) {
00678     return Failed;
00679   }
00680 
00681   // Remember the old parent, we need it a bit further down to be able
00682   // to put the unchanged messsage back in the original folder if the nth
00683   // edit is discarded, for n > 1.
00684   KMFolder *parent = msg->parent();
00685   if ( parent ) {
00686     parent->take( parent->find( msg ) );
00687   }
00688 
00689   KMail::Composer *win = KMail::makeComposer();
00690   msg->setTransferInProgress( false ); // From here on on, the composer owns the message.
00691   win->setMsg( msg, false, true );
00692   win->setFolder( parent );
00693   win->show();
00694 
00695   return OK;
00696 }
00697 
00698 KMUseTemplateCommand::KMUseTemplateCommand( QWidget *parent, KMMessage *msg )
00699   :KMCommand( parent, msg )
00700 {
00701 }
00702 
00703 KMCommand::Result KMUseTemplateCommand::execute()
00704 {
00705   KMMessage *msg = retrievedMessage();
00706   if ( !msg || !msg->parent() ||
00707        !kmkernel->folderIsTemplates( msg->parent() ) ) {
00708     return Failed;
00709   }
00710 
00711   // Take a copy of the original message, which remains unchanged.
00712   KMMessage *newMsg = new KMMessage( new DwMessage( *msg->asDwMessage() ) );
00713   newMsg->setComplete( msg->isComplete() );
00714 
00715   // these fields need to be regenerated for the new message
00716   newMsg->removeHeaderField("Date");
00717   newMsg->removeHeaderField("Message-ID");
00718 
00719   KMail::Composer *win = KMail::makeComposer();
00720   newMsg->setTransferInProgress( false ); // From here on on, the composer owns the message.
00721   win->setMsg( newMsg, false, true );
00722   win->show();
00723 
00724   return OK;
00725 }
00726 
00727 KMShowMsgSrcCommand::KMShowMsgSrcCommand( QWidget *parent,
00728                                           KMMessage *msg, bool fixedFont )
00729   :KMCommand( parent, msg ), mFixedFont( fixedFont )
00730 {
00731   // remember complete state
00732   mMsgWasComplete = msg->isComplete();
00733 }
00734 
00735 KMCommand::Result KMShowMsgSrcCommand::execute()
00736 {
00737   KMMessage *msg = retrievedMessage();
00738   if ( !msg ) {
00739     return Failed;
00740   }
00741   if ( msg->isComplete() && !mMsgWasComplete ) {
00742     msg->notify(); // notify observers as msg was transfered
00743   }
00744   QString str = msg->codec()->toUnicode( msg->asString() );
00745 
00746   MailSourceViewer *viewer = new MailSourceViewer(); // deletes itself upon close
00747   viewer->setWindowTitle( i18n("Message as Plain Text") );
00748   viewer->setText( str );
00749   if( mFixedFont ) {
00750     viewer->setFont( KGlobalSettings::fixedFont() );
00751   }
00752 
00753   // Well, there is no widget to be seen here, so we have to use QCursor::pos()
00754   // Update: (GS) I'm not going to make this code behave according to Xinerama
00755   //         configuration because this is quite the hack.
00756   if ( QApplication::desktop()->isVirtualDesktop() ) {
00757     int scnum = QApplication::desktop()->screenNumber( QCursor::pos() );
00758     viewer->resize( QApplication::desktop()->screenGeometry( scnum ).width()/2,
00759                     2 * QApplication::desktop()->screenGeometry( scnum ).height()/3);
00760   } else {
00761     viewer->resize( QApplication::desktop()->geometry().width()/2,
00762                     2 * QApplication::desktop()->geometry().height()/3);
00763   }
00764   viewer->show();
00765 
00766   return OK;
00767 }
00768 
00769 static KUrl subjectToUrl( const QString &subject ) {
00770 
00771   return KFileDialog::getSaveUrl( KUrl::fromPath( subject.trimmed()
00772                                   .replace( QDir::separator(), '_' ) ),
00773                                   "*.mbox" );
00774 }
00775 
00776 KMSaveMsgCommand::KMSaveMsgCommand( QWidget *parent, KMMessage *msg )
00777   : KMCommand( parent ),
00778     mMsgListIndex( 0 ),
00779     mStandAloneMessage( 0 ),
00780     mOffset( 0 ),
00781     mTotalSize( msg ? msg->msgSize() : 0 )
00782 {
00783   if ( !msg ) {
00784     return;
00785   }
00786   setDeletesItself( true );
00787   // If the mail has a serial number, operate on sernums, if it does not
00788   // we need to work with the pointer, but can be reasonably sure it won't
00789   // go away, since it'll be an encapsulated message or one that was opened
00790   // from an .eml file.
00791   if ( msg->getMsgSerNum() != 0 ) {
00792     mMsgList.append( msg->getMsgSerNum() );
00793   } else {
00794     mStandAloneMessage = msg;
00795   }
00796   mUrl = subjectToUrl( msg->cleanSubject() );
00797 }
00798 
00799 KMSaveMsgCommand::KMSaveMsgCommand( QWidget *parent,
00800                                     const QList<KMMsgBase*> &msgList )
00801   : KMCommand( parent ),
00802     mMsgListIndex( 0 ),
00803     mStandAloneMessage( 0 ),
00804     mOffset( 0 ),
00805     mTotalSize( 0 )
00806 {
00807   if ( msgList.empty() ) {
00808     return;
00809   }
00810   setDeletesItself( true );
00811   // We operate on serNums and not the KMMsgBase pointers, as those can
00812   // change, or become invalid when changing the current message, switching
00813   // folders, etc.
00814   QList<KMMsgBase*>::const_iterator it;
00815   for ( it = msgList.begin(); it != msgList.end(); ++it ) {
00816     mMsgList.append( (*it)->getMsgSerNum() );
00817     mTotalSize += (*it)->msgSize();
00818     if ( (*it)->parent() != 0 ) {
00819       (*it)->parent()->open( "kmcommand" );
00820     }
00821   }
00822   mMsgListIndex = 0;
00823   KMMsgBase *msgBase = *(msgList.begin());
00824   mUrl = subjectToUrl( msgBase->cleanSubject() );
00825 }
00826 
00827 KUrl KMSaveMsgCommand::url()
00828 {
00829   return mUrl;
00830 }
00831 
00832 KMCommand::Result KMSaveMsgCommand::execute()
00833 {
00834   mJob = KIO::put( mUrl, S_IRUSR|S_IWUSR );
00835 #ifdef __GNUC__
00836 #warning Port me!
00837 #endif
00838 //  mJob->slotTotalSize( mTotalSize );
00839   mJob->setAsyncDataEnabled( true );
00840   mJob->setReportDataSent( true );
00841   connect(mJob, SIGNAL(dataReq(KIO::Job*, QByteArray &)),
00842     SLOT(slotSaveDataReq()));
00843   connect(mJob, SIGNAL(result(KJob*)),
00844     SLOT(slotSaveResult(KJob*)));
00845   setEmitsCompletedItself( true );
00846   return OK;
00847 }
00848 
00849 void KMSaveMsgCommand::slotSaveDataReq()
00850 {
00851   int remainingBytes = mData.size() - mOffset;
00852   if ( remainingBytes > 0 ) {
00853     // eat leftovers first
00854     if ( remainingBytes > MAX_CHUNK_SIZE )
00855       remainingBytes = MAX_CHUNK_SIZE;
00856 
00857     QByteArray data;
00858     data = QByteArray( mData.data() + mOffset, remainingBytes );
00859     mJob->sendAsyncData( data );
00860     mOffset += remainingBytes;
00861     return;
00862   }
00863   // No leftovers, process next message.
00864   if ( mMsgListIndex < static_cast<unsigned int>( mMsgList.size() ) ) {
00865     KMMessage *msg = 0;
00866     int idx = -1;
00867     KMFolder * p = 0;
00868     KMMsgDict::instance()->getLocation( mMsgList[mMsgListIndex], &p, &idx );
00869     assert( p );
00870     assert( idx >= 0 );
00871     msg = p->getMsg(idx);
00872 
00873     if ( msg ) {
00874       if ( msg->transferInProgress() ) {
00875         QByteArray data = QByteArray();
00876         mJob->sendAsyncData( data );
00877       }
00878       msg->setTransferInProgress( true );
00879       if ( msg->isComplete() ) {
00880         slotMessageRetrievedForSaving( msg );
00881       } else {
00882         // retrieve Message first
00883         if ( msg->parent()  && !msg->isComplete() ) {
00884           FolderJob *job = msg->parent()->createJob( msg );
00885           job->setCancellable( false );
00886           connect(job, SIGNAL( messageRetrieved( KMMessage* ) ),
00887                   this, SLOT( slotMessageRetrievedForSaving( KMMessage* ) ) );
00888           job->start();
00889         }
00890       }
00891     } else {
00892       mJob->slotError( KIO::ERR_ABORTED,
00893                        i18n("The message was removed while saving it. "
00894                             "It has not been saved.") );
00895     }
00896   } else {
00897     if ( mStandAloneMessage ) {
00898       // do the special case of a standalone message
00899       slotMessageRetrievedForSaving( mStandAloneMessage );
00900       mStandAloneMessage = 0;
00901     } else {
00902       // No more messages. Tell the putjob we are done.
00903       QByteArray data = QByteArray();
00904       mJob->sendAsyncData( data );
00905     }
00906   }
00907 }
00908 
00909 void KMSaveMsgCommand::slotMessageRetrievedForSaving(KMMessage *msg)
00910 {
00911   if ( msg ) {
00912     QByteArray str( msg->mboxMessageSeparator() );
00913     str += KMFolderMbox::escapeFrom( msg->asDwString() );
00914     str += '\n';
00915     msg->setTransferInProgress(false);
00916 
00917     mData = str;
00918     mData.resize(mData.size() - 1);
00919     mOffset = 0;
00920     QByteArray data;
00921     int size;
00922     // Unless it is great than 64 k send the whole message. kio buffers for us.
00923     if( mData.size() >  MAX_CHUNK_SIZE )
00924       size = MAX_CHUNK_SIZE;
00925     else
00926       size = mData.size();
00927 
00928     data = QByteArray( mData, size );
00929     mJob->sendAsyncData( data );
00930     mOffset += size;
00931   }
00932   ++mMsgListIndex;
00933   // Get rid of the message.
00934   if ( msg && msg->parent() && msg->getMsgSerNum() ) {
00935     int idx = -1;
00936     KMFolder * p = 0;
00937     KMMsgDict::instance()->getLocation( msg, &p, &idx );
00938     assert( p == msg->parent() ); assert( idx >= 0 );
00939     p->unGetMsg( idx );
00940     p->close( "kmcommand" );
00941   }
00942 }
00943 
00944 void KMSaveMsgCommand::slotSaveResult(KJob *job)
00945 {
00946   if (job->error())
00947   {
00948     if (job->error() == KIO::ERR_FILE_ALREADY_EXIST)
00949     {
00950       if (KMessageBox::warningContinueCancel(0,
00951         i18n("File %1 exists.\nDo you want to replace it?",
00952          mUrl.prettyUrl()), i18n("Save to File"), KGuiItem(i18n("&Replace")))
00953         == KMessageBox::Continue) {
00954         mOffset = 0;
00955 
00956         mJob = KIO::put( mUrl, S_IRUSR|S_IWUSR, KIO::Overwrite );
00957 #ifdef __GNUC__
00958 #warning Port me!
00959 #endif
00960 //        mJob->slotTotalSize( mTotalSize );
00961         mJob->setAsyncDataEnabled( true );
00962         mJob->setReportDataSent( true );
00963         connect(mJob, SIGNAL(dataReq(KIO::Job*, QByteArray &)),
00964             SLOT(slotSaveDataReq()));
00965         connect(mJob, SIGNAL(result(KJob*)),
00966             SLOT(slotSaveResult(KJob*)));
00967       }
00968     }
00969     else
00970     {
00971       static_cast<KIO::Job*>(job)->ui()->showErrorMessage();
00972       setResult( Failed );
00973       emit completed( this );
00974       deleteLater();
00975     }
00976   } else {
00977     setResult( OK );
00978     emit completed( this );
00979     deleteLater();
00980   }
00981 }
00982 
00983 //-----------------------------------------------------------------------------
00984 
00985 KMOpenMsgCommand::KMOpenMsgCommand( QWidget *parent, const KUrl & url,
00986                                     const QString & encoding )
00987   : KMCommand( parent ),
00988     mUrl( url ),
00989     mEncoding( encoding )
00990 {
00991   setDeletesItself( true );
00992 }
00993 
00994 KMCommand::Result KMOpenMsgCommand::execute()
00995 {
00996   if ( mUrl.isEmpty() ) {
00997     mUrl = KFileDialog::getOpenUrl( KUrl( ":OpenMessage" ),
00998                                     "message/rfc822 application/mbox",
00999