kmail

kmcommands.cpp

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 #ifdef HAVE_CONFIG_H
00046 #include <config.h>
00047 #endif
00048 
00049 #include <errno.h>
00050 #include <mimelib/enum.h>
00051 #include <mimelib/field.h>
00052 #include <mimelib/mimepp.h>
00053 #include <mimelib/string.h>
00054 #include <kapplication.h>
00055 #include <dcopclient.h>
00056 
00057 #include <qtextcodec.h>
00058 #include <qpopupmenu.h>
00059 #include <qeventloop.h>
00060 
00061 #include <libemailfunctions/email.h>
00062 #include <kdebug.h>
00063 #include <kfiledialog.h>
00064 #include <kabc/stdaddressbook.h>
00065 #include <kabc/addresseelist.h>
00066 #include <kdirselectdialog.h>
00067 #include <klocale.h>
00068 #include <kmessagebox.h>
00069 #include <kparts/browserextension.h>
00070 #include <kprogress.h>
00071 #include <krun.h>
00072 #include <kbookmarkmanager.h>
00073 #include <kstandarddirs.h>
00074 #include <ktempfile.h>
00075 #include <kimproxy.h>
00076 #include <kuserprofile.h>
00077 // KIO headers
00078 #include <kio/job.h>
00079 #include <kio/netaccess.h>
00080 
00081 #include "actionscheduler.h"
00082 using KMail::ActionScheduler;
00083 #include "mailinglist-magic.h"
00084 #include "kmaddrbook.h"
00085 #include <kaddrbook.h>
00086 #include "composer.h"
00087 #include "kmfiltermgr.h"
00088 #include "kmfoldermbox.h"
00089 #include "kmfolderimap.h"
00090 #include "kmfoldermgr.h"
00091 #include "kmheaders.h"
00092 #include "headeritem.h"
00093 #include "kmmainwidget.h"
00094 #include "kmmsgdict.h"
00095 #include "messagesender.h"
00096 #include "kmmsgpartdlg.h"
00097 #include "undostack.h"
00098 #include "kcursorsaver.h"
00099 #include "partNode.h"
00100 #include "objecttreeparser.h"
00101 using KMail::ObjectTreeParser;
00102 using KMail::FolderJob;
00103 #include "chiasmuskeyselector.h"
00104 #include "mailsourceviewer.h"
00105 using KMail::MailSourceViewer;
00106 #include "kmreadermainwin.h"
00107 #include "secondarywindow.h"
00108 using KMail::SecondaryWindow;
00109 #include "redirectdialog.h"
00110 using KMail::RedirectDialog;
00111 #include "util.h"
00112 
00113 #include "broadcaststatus.h"
00114 #include "globalsettings.h"
00115 
00116 #include <libkdepim/kfileio.h>
00117 
00118 #include "progressmanager.h"
00119 using KPIM::ProgressManager;
00120 using KPIM::ProgressItem;
00121 #include <kmime_mdn.h>
00122 using namespace KMime;
00123 
00124 #include <kleo/specialjob.h>
00125 #include <kleo/cryptobackend.h>
00126 #include <kleo/cryptobackendfactory.h>
00127 
00128 #include <qclipboard.h>
00129 
00130 #include <memory>
00131 
00132 class LaterDeleterWithCommandCompletion : public KMail::Util::LaterDeleter
00133 {
00134 public:
00135   LaterDeleterWithCommandCompletion( KMCommand* command )
00136     :LaterDeleter( command ), m_result( KMCommand::Failed )
00137   {
00138   }
00139   ~LaterDeleterWithCommandCompletion()
00140   {
00141     setResult( m_result );
00142     KMCommand *command = static_cast<KMCommand*>( m_object );
00143     emit command->completed( command );
00144   }
00145   void setResult( KMCommand::Result v ) { m_result = v; }
00146 private:
00147   KMCommand::Result m_result;
00148 };
00149 
00150 
00151 KMCommand::KMCommand( QWidget *parent )
00152   : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
00153     mEmitsCompletedItself( false ), mParent( parent )
00154 {
00155 }
00156 
00157 KMCommand::KMCommand( QWidget *parent, const QPtrList<KMMsgBase> &msgList )
00158   : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
00159     mEmitsCompletedItself( false ), mParent( parent ), mMsgList( msgList )
00160 {
00161 }
00162 
00163 KMCommand::KMCommand( QWidget *parent, KMMsgBase *msgBase )
00164   : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
00165     mEmitsCompletedItself( false ), mParent( parent )
00166 {
00167   mMsgList.append( msgBase );
00168 }
00169 
00170 KMCommand::KMCommand( QWidget *parent, KMMessage *msg )
00171   : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
00172     mEmitsCompletedItself( false ), mParent( parent )
00173 {
00174   if (msg)
00175     mMsgList.append( &msg->toMsgBase() );
00176 }
00177 
00178 KMCommand::~KMCommand()
00179 {
00180   QValueListIterator<QGuardedPtr<KMFolder> > fit;
00181   for ( fit = mFolders.begin(); fit != mFolders.end(); ++fit ) {
00182     if (!(*fit))
00183       continue;
00184     (*fit)->close();
00185   }
00186 }
00187 
00188 KMCommand::Result KMCommand::result()
00189 {
00190   if ( mResult == Undefined )
00191     kdDebug(5006) << k_funcinfo << "mResult is Undefined" << endl;
00192   return mResult;
00193 }
00194 
00195 void KMCommand::start()
00196 {
00197   QTimer::singleShot( 0, this, SLOT( slotStart() ) );
00198 }
00199 
00200 
00201 const QPtrList<KMMessage> KMCommand::retrievedMsgs() const
00202 {
00203   return mRetrievedMsgs;
00204 }
00205 
00206 KMMessage *KMCommand::retrievedMessage() const
00207 {
00208   return mRetrievedMsgs.getFirst();
00209 }
00210 
00211 QWidget *KMCommand::parentWidget() const
00212 {
00213   return mParent;
00214 }
00215 
00216 int KMCommand::mCountJobs = 0;
00217 
00218 void KMCommand::slotStart()
00219 {
00220   connect( this, SIGNAL( messagesTransfered( KMCommand::Result ) ),
00221            this, SLOT( slotPostTransfer( KMCommand::Result ) ) );
00222   kmkernel->filterMgr()->ref();
00223 
00224   if (mMsgList.find(0) != -1) {
00225       emit messagesTransfered( Failed );
00226       return;
00227   }
00228 
00229   if ((mMsgList.count() == 1) &&
00230       (mMsgList.getFirst()->isMessage()) &&
00231       (mMsgList.getFirst()->parent() == 0))
00232   {
00233     // Special case of operating on message that isn't in a folder
00234     mRetrievedMsgs.append((KMMessage*)mMsgList.getFirst());
00235     emit messagesTransfered( OK );
00236     return;
00237   }
00238 
00239   for (KMMsgBase *mb = mMsgList.first(); mb; mb = mMsgList.next())
00240     if (!mb->parent()) {
00241       emit messagesTransfered( Failed );
00242       return;
00243     } else {
00244       keepFolderOpen( mb->parent() );
00245     }
00246 
00247   // transfer the selected messages first
00248   transferSelectedMsgs();
00249 }
00250 
00251 void KMCommand::slotPostTransfer( KMCommand::Result result )
00252 {
00253   disconnect( this, SIGNAL( messagesTransfered( KMCommand::Result ) ),
00254               this, SLOT( slotPostTransfer( KMCommand::Result ) ) );
00255   if ( result == OK )
00256     result = execute();
00257   mResult = result;
00258   QPtrListIterator<KMMessage> it( mRetrievedMsgs );
00259   KMMessage* msg;
00260   while ( (msg = it.current()) != 0 )
00261   {
00262     ++it;
00263     if (msg->parent())
00264       msg->setTransferInProgress(false);
00265   }
00266   kmkernel->filterMgr()->deref();
00267   if ( !emitsCompletedItself() )
00268     emit completed( this );
00269   if ( !deletesItself() )
00270     deleteLater();
00271 }
00272 
00273 void KMCommand::transferSelectedMsgs()
00274 {
00275   // make sure no other transfer is active
00276   if (KMCommand::mCountJobs > 0) {
00277     emit messagesTransfered( Failed );
00278     return;
00279   }
00280 
00281   bool complete = true;
00282   KMCommand::mCountJobs = 0;
00283   mCountMsgs = 0;
00284   mRetrievedMsgs.clear();
00285   mCountMsgs = mMsgList.count();
00286   uint totalSize = 0;
00287   // the KProgressDialog for the user-feedback. Only enable it if it's needed.
00288   // For some commands like KMSetStatusCommand it's not needed. Note, that
00289   // for some reason the KProgressDialog eats the MouseReleaseEvent (if a
00290   // command is executed after the MousePressEvent), cf. bug #71761.
00291   if ( mCountMsgs > 0 ) {
00292     mProgressDialog = new KProgressDialog(mParent, "transferProgress",
00293       i18n("Please wait"),
00294       i18n("Please wait while the message is transferred",
00295         "Please wait while the %n messages are transferred", mMsgList.count()),
00296       true);
00297     mProgressDialog->setMinimumDuration(1000);
00298   }
00299   for (KMMsgBase *mb = mMsgList.first(); mb; mb = mMsgList.next())
00300   {
00301     // check if all messages are complete
00302     KMMessage *thisMsg = 0;
00303     if ( mb->isMessage() )
00304       thisMsg = static_cast<KMMessage*>(mb);
00305     else
00306     {
00307       KMFolder *folder = mb->parent();
00308       int idx = folder->find(mb);
00309       if (idx < 0) continue;
00310       thisMsg = folder->getMsg(idx);
00311     }
00312     if (!thisMsg) continue;
00313     if ( thisMsg->transferInProgress() &&
00314          thisMsg->parent()->folderType() == KMFolderTypeImap )
00315     {
00316       thisMsg->setTransferInProgress( false, true );
00317       thisMsg->parent()->ignoreJobsForMessage( thisMsg );
00318     }
00319 
00320     if ( thisMsg->parent() && !thisMsg->isComplete() &&
00321          ( !mProgressDialog || !mProgressDialog->wasCancelled() ) )
00322     {
00323       kdDebug(5006)<<"### INCOMPLETE\n";
00324       // the message needs to be transferred first
00325       complete = false;
00326       KMCommand::mCountJobs++;
00327       FolderJob *job = thisMsg->parent()->createJob(thisMsg);
00328       job->setCancellable( false );
00329       totalSize += thisMsg->msgSizeServer();
00330       // emitted when the message was transferred successfully
00331       connect(job, SIGNAL(messageRetrieved(KMMessage*)),
00332               this, SLOT(slotMsgTransfered(KMMessage*)));
00333       // emitted when the job is destroyed
00334       connect(job, SIGNAL(finished()),
00335               this, SLOT(slotJobFinished()));
00336       connect(job, SIGNAL(progress(unsigned long, unsigned long)),
00337               this, SLOT(slotProgress(unsigned long, unsigned long)));
00338       // msg musn't be deleted
00339       thisMsg->setTransferInProgress(true);
00340       job->start();
00341     } else {
00342       thisMsg->setTransferInProgress(true);
00343       mRetrievedMsgs.append(thisMsg);
00344     }
00345   }
00346 
00347   if (complete)
00348   {
00349     delete mProgressDialog;
00350     mProgressDialog = 0;
00351     emit messagesTransfered( OK );
00352   } else {
00353     // wait for the transfer and tell the progressBar the necessary steps
00354     if ( mProgressDialog ) {
00355       connect(mProgressDialog, SIGNAL(cancelClicked()),
00356               this, SLOT(slotTransferCancelled()));
00357       mProgressDialog->progressBar()->setTotalSteps(totalSize);
00358     }
00359   }
00360 }
00361 
00362 void KMCommand::slotMsgTransfered(KMMessage* msg)
00363 {
00364   if ( mProgressDialog && mProgressDialog->wasCancelled() ) {
00365     emit messagesTransfered( Canceled );
00366     return;
00367   }
00368 
00369   // save the complete messages
00370   mRetrievedMsgs.append(msg);
00371 }
00372 
00373 void KMCommand::slotProgress( unsigned long done, unsigned long /*total*/ )
00374 {
00375   mProgressDialog->progressBar()->setProgress( done );
00376 }
00377 
00378 void KMCommand::slotJobFinished()
00379 {
00380   // the job is finished (with / without error)
00381   KMCommand::mCountJobs--;
00382 
00383   if ( mProgressDialog && mProgressDialog->wasCancelled() ) return;
00384 
00385   if ( (mCountMsgs - static_cast<int>(mRetrievedMsgs.count())) > KMCommand::mCountJobs )
00386   {
00387     // the message wasn't retrieved before => error
00388     if ( mProgressDialog )
00389       mProgressDialog->hide();
00390     slotTransferCancelled();
00391     return;
00392   }
00393   // update the progressbar
00394   if ( mProgressDialog ) {
00395     mProgressDialog->setLabel(i18n("Please wait while the message is transferred",
00396           "Please wait while the %n messages are transferred", KMCommand::mCountJobs));
00397   }
00398   if (KMCommand::mCountJobs == 0)
00399   {
00400     // all done
00401     delete mProgressDialog;
00402     mProgressDialog = 0;
00403     emit messagesTransfered( OK );
00404   }
00405 }
00406 
00407 void KMCommand::slotTransferCancelled()
00408 {
00409   // kill the pending jobs
00410   QValueListIterator<QGuardedPtr<KMFolder> > fit;
00411   for ( fit = mFolders.begin(); fit != mFolders.end(); ++fit ) {
00412     if (!(*fit))
00413       continue;
00414     KMFolder *folder = *fit;
00415     KMFolderImap *imapFolder = dynamic_cast<KMFolderImap*>(folder);
00416     if (imapFolder && imapFolder->account()) {
00417       imapFolder->account()->killAllJobs();
00418     }
00419   }
00420 
00421   KMCommand::mCountJobs = 0;
00422   mCountMsgs = 0;
00423   // unget the transfered messages
00424   QPtrListIterator<KMMessage> it( mRetrievedMsgs );
00425   KMMessage* msg;
00426   while ( (msg = it.current()) != 0 )
00427   {
00428     KMFolder *folder = msg->parent();
00429     ++it;
00430     if (!folder)
00431       continue;
00432     msg->setTransferInProgress(false);
00433     int idx = folder->find(msg);
00434     if (idx > 0) folder->unGetMsg(idx);
00435   }
00436   mRetrievedMsgs.clear();
00437   emit messagesTransfered( Canceled );
00438 }
00439 
00440 void KMCommand::keepFolderOpen( KMFolder *folder )
00441 {
00442   folder->open();
00443   mFolders.append( folder );
00444 }
00445 
00446 KMMailtoComposeCommand::KMMailtoComposeCommand( const KURL &url,
00447                                                 KMMessage *msg )
00448   :mUrl( url ), mMessage( msg )
00449 {
00450 }
00451 
00452 KMCommand::Result KMMailtoComposeCommand::execute()
00453 {
00454   KMMessage *msg = new KMMessage;
00455   uint id = 0;
00456 
00457   if ( mMessage && mMessage->parent() )
00458     id = mMessage->parent()->identity();
00459 
00460   msg->initHeader(id);
00461   msg->setCharset("utf-8");
00462   msg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );
00463 
00464   KMail::Composer * win = KMail::makeComposer( msg, id );
00465   win->setCharset("", TRUE);
00466   win->setFocusToSubject();
00467   win->show();
00468 
00469   return OK;
00470 }
00471 
00472 
00473 KMMailtoReplyCommand::KMMailtoReplyCommand( QWidget *parent,
00474    const KURL &url, KMMessage *msg, const QString &selection )
00475   :KMCommand( parent, msg ), mUrl( url ), mSelection( selection  )
00476 {
00477 }
00478 
00479 KMCommand::Result KMMailtoReplyCommand::execute()
00480 {
00481   //TODO : consider factoring createReply into this method.
00482   KMMessage *msg = retrievedMessage();
00483   KMMessage *rmsg = msg->createReply( KMail::ReplyNone, mSelection );
00484   rmsg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );
00485 
00486   KMail::Composer * win = KMail::makeComposer( rmsg, 0 );
00487   win->setCharset(msg->codec()->mimeName(), TRUE);
00488   win->setReplyFocus();
00489   win->show();
00490 
00491   return OK;
00492 }
00493 
00494 
00495 KMMailtoForwardCommand::KMMailtoForwardCommand( QWidget *parent,
00496    const KURL &url, KMMessage *msg )
00497   :KMCommand( parent, msg ), mUrl( url )
00498 {
00499 }
00500 
00501 KMCommand::Result KMMailtoForwardCommand::execute()
00502 {
00503   //TODO : consider factoring createForward into this method.
00504   KMMessage *msg = retrievedMessage();
00505   KMMessage *fmsg = msg->createForward();
00506   fmsg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );
00507 
00508   KMail::Composer * win = KMail::makeComposer( fmsg );
00509   win->setCharset(msg->codec()->mimeName(), TRUE);
00510   win->show();
00511 
00512   return OK;
00513 }
00514 
00515 
00516 KMAddBookmarksCommand::KMAddBookmarksCommand( const KURL &url, QWidget *parent )
00517   : KMCommand( parent ), mUrl( url )
00518 {
00519 }
00520 
00521 KMCommand::Result KMAddBookmarksCommand::execute()
00522 {
00523   QString filename = locateLocal( "data", QString::fromLatin1("konqueror/bookmarks.xml") );
00524   KBookmarkManager *bookManager = KBookmarkManager::managerForFile( filename,
00525                                                                     false );
00526   KBookmarkGroup group = bookManager->root();
00527   group.addBookmark( bookManager, mUrl.path(), KURL( mUrl ) );
00528   if( bookManager->save() ) {
00529     bookManager->emitChanged( group );
00530   }
00531 
00532   return OK;
00533 }
00534 
00535 KMMailtoAddAddrBookCommand::KMMailtoAddAddrBookCommand( const KURL &url,
00536    QWidget *parent )
00537   : KMCommand( parent ), mUrl( url )
00538 {
00539 }
00540 
00541 KMCommand::Result KMMailtoAddAddrBookCommand::execute()
00542 {
00543   KAddrBookExternal::addEmail( KMMessage::decodeMailtoUrl( mUrl.path() ),
00544                                parentWidget() );
00545 
00546   return OK;
00547 }
00548 
00549 
00550 KMMailtoOpenAddrBookCommand::KMMailtoOpenAddrBookCommand( const KURL &url,
00551    QWidget *parent )
00552   : KMCommand( parent ), mUrl( url )
00553 {
00554 }
00555 
00556 KMCommand::Result KMMailtoOpenAddrBookCommand::execute()
00557 {
00558   QString addr = KMMessage::decodeMailtoUrl( mUrl.path() );
00559   KAddrBookExternal::openEmail( KPIM::getEmailAddress(addr), addr ,
00560                                 parentWidget() );
00561 
00562   return OK;
00563 }
00564 
00565 
00566 KMUrlCopyCommand::KMUrlCopyCommand( const KURL &url, KMMainWidget *mainWidget )
00567   :mUrl( url ), mMainWidget( mainWidget )
00568 {
00569 }
00570 
00571 KMCommand::Result KMUrlCopyCommand::execute()
00572 {
00573   QClipboard* clip = QApplication::clipboard();
00574 
00575   if (mUrl.protocol() == "mailto") {
00576     // put the url into the mouse selection and the clipboard
00577     QString address = KMMessage::decodeMailtoUrl( mUrl.path() );
00578     clip->setSelectionMode( true );
00579     clip->setText( address );
00580     clip->setSelectionMode( false );
00581     clip->setText( address );
00582     KPIM::BroadcastStatus::instance()->setStatusMsg( i18n( "Address copied to clipboard." ));
00583   } else {
00584     // put the url into the mouse selection and the clipboard
00585     clip->setSelectionMode( true );
00586     clip->setText( mUrl.url() );
00587     clip->setSelectionMode( false );
00588     clip->setText( mUrl.url() );
00589     KPIM::BroadcastStatus::instance()->setStatusMsg( i18n( "URL copied to clipboard." ));
00590   }
00591 
00592   return OK;
00593 }
00594 
00595 
00596 KMUrlOpenCommand::KMUrlOpenCommand( const KURL &url, KMReaderWin *readerWin )
00597   :mUrl( url ), mReaderWin( readerWin )
00598 {
00599 }
00600 
00601 KMCommand::Result KMUrlOpenCommand::execute()
00602 {
00603   if ( !mUrl.isEmpty() )
00604     mReaderWin->slotUrlOpen( mUrl, KParts::URLArgs() );
00605 
00606   return OK;
00607 }
00608 
00609 
00610 KMUrlSaveCommand::KMUrlSaveCommand( const KURL &url, QWidget *parent )
00611   : KMCommand( parent ), mUrl( url )
00612 {
00613 }
00614 
00615 KMCommand::Result KMUrlSaveCommand::execute()
00616 {
00617   if ( mUrl.isEmpty() )
00618     return OK;
00619   KURL saveUrl = KFileDialog::getSaveURL(mUrl.fileName(), QString::null,
00620                                          parentWidget() );
00621   if ( saveUrl.isEmpty() )
00622     return Canceled;
00623   if ( KIO::NetAccess::exists( saveUrl, false, parentWidget() ) )
00624   {
00625     if (KMessageBox::warningContinueCancel(0,
00626         i18n("<qt>File <b>%1</b> exists.<br>Do you want to replace it?</qt>")
00627         .arg(saveUrl.prettyURL()), i18n("Save to File"), i18n("&Replace"))
00628         != KMessageBox::Continue)
00629       return Canceled;
00630   }
00631   KIO::Job *job = KIO::file_copy(mUrl, saveUrl, -1, true);
00632   connect(job, SIGNAL(result(KIO::Job*)), SLOT(slotUrlSaveResult(KIO::Job*)));
00633   setEmitsCompletedItself( true );
00634   return OK;
00635 }
00636 
00637 void KMUrlSaveCommand::slotUrlSaveResult( KIO::Job *job )
00638 {
00639   if ( job->error() ) {
00640     job->showErrorDialog();
00641     setResult( Failed );
00642     emit completed( this );
00643   }
00644   else {
00645     setResult( OK );
00646     emit completed( this );
00647   }
00648 }
00649 
00650 
00651 KMEditMsgCommand::KMEditMsgCommand( QWidget *parent, KMMessage *msg )
00652   :KMCommand( parent, msg )
00653 {
00654 }
00655 
00656 KMCommand::Result KMEditMsgCommand::execute()
00657 {
00658   KMMessage *msg = retrievedMessage();
00659   if (!msg || !msg->parent() ||
00660       !kmkernel->folderIsDraftOrOutbox( msg->parent() ))
00661     return Failed;
00662 
00663   // Remember the old parent, we need it a bit further down to be able
00664   // to put the unchanged messsage back in the drafts folder if the nth
00665   // edit is discarded, for n > 1.
00666   KMFolder *parent = msg->parent();
00667   if ( parent )
00668     parent->take( parent->find( msg ) );
00669 
00670   KMail::Composer * win = KMail::makeComposer();
00671   msg->setTransferInProgress(false); // From here on on, the composer owns the message.
00672   win->setMsg(msg, FALSE, TRUE);
00673   win->setFolder( parent );
00674   win->show();
00675 
00676   return OK;
00677 }
00678 
00679 
00680 KMShowMsgSrcCommand::KMShowMsgSrcCommand( QWidget *parent,
00681   KMMessage *msg, bool fixedFont )
00682   :KMCommand( parent, msg ), mFixedFont( fixedFont )
00683 {
00684   // remember complete state
00685   mMsgWasComplete = msg->isComplete();
00686 }
00687 
00688 KMCommand::Result KMShowMsgSrcCommand::execute()
00689 {
00690   KMMessage *msg = retrievedMessage();
00691   if ( msg->isComplete() && !mMsgWasComplete )
00692     msg->notify(); // notify observers as msg was transfered
00693   QString str = msg->codec()->toUnicode( msg->asString() );
00694 
00695   MailSourceViewer *viewer = new MailSourceViewer(); // deletes itself upon close
00696   viewer->setCaption( i18n("Message as Plain Text") );
00697   viewer->setText(str);
00698   if( mFixedFont )
00699     viewer->setFont(KGlobalSettings::fixedFont());
00700 
00701   // Well, there is no widget to be seen here, so we have to use QCursor::pos()
00702   // Update: (GS) I'm not going to make this code behave according to Xinerama
00703   //         configuration because this is quite the hack.
00704   if (QApplication::desktop()->isVirtualDesktop()) {
00705     int scnum = QApplication::desktop()->screenNumber(QCursor::pos());
00706     viewer->resize(QApplication::desktop()->screenGeometry(scnum).width()/2,
00707                   2*QApplication::desktop()->screenGeometry(scnum).height()/3);
00708   } else {
00709     viewer->resize(QApplication::desktop()->geometry().width()/2,
00710                   2*QApplication::desktop()->geometry().height()/3);
00711   }
00712   viewer->show();
00713 
00714   return OK;
00715 }
00716 
00717 static KURL subjectToUrl( const QString & subject ) {
00718     return KFileDialog::getSaveURL( subject.stripWhiteSpace()
00719                                            .replace( QDir::separator(), '_' ),
00720                                     QString::null );
00721 }
00722 
00723 KMSaveMsgCommand::KMSaveMsgCommand( QWidget *parent, KMMessage * msg )
00724   : KMCommand( parent ),
00725     mMsgListIndex( 0 ),
00726     mStandAloneMessage( 0 ),
00727     mOffset( 0 ),
00728     mTotalSize( msg ? msg->msgSize() : 0 )
00729 {
00730   if ( !msg ) return;
00731   setDeletesItself( true );
00732   // If the mail has a serial number, operate on sernums, if it does not
00733   // we need to work with the pointer, but can be reasonably sure it won't
00734   // go away, since it'll be an encapsulated message or one that was opened
00735   // from an .eml file.
00736   if ( msg->getMsgSerNum() != 0 ) {
00737     mMsgList.append( msg->getMsgSerNum() );
00738   } else {
00739     mStandAloneMessage = msg;
00740   }
00741   mUrl = subjectToUrl( msg->cleanSubject() );
00742 }
00743 
00744 KMSaveMsgCommand::KMSaveMsgCommand( QWidget *parent,
00745                                     const QPtrList<KMMsgBase> &msgList )
00746   : KMCommand( parent ),
00747     mMsgListIndex( 0 ),
00748     mStandAloneMessage( 0 ),
00749     mOffset( 0 ),
00750     mTotalSize( 0 )
00751 {
00752   if (!msgList.getFirst())
00753     return;
00754   setDeletesItself( true );
00755   KMMsgBase *msgBase = msgList.getFirst();
00756 
00757   // We operate on serNums and not the KMMsgBase pointers, as those can
00758   // change, or become invalid when changing the current message, switching
00759   // folders, etc.
00760   QPtrListIterator<KMMsgBase> it(msgList);
00761   while ( it.current() ) {
00762     mMsgList.append( (*it)->getMsgSerNum() );
00763     mTotalSize += (*it)->msgSize();
00764     if ((*it)->parent() != 0)
00765       (*it)->parent()->open();
00766     ++it;
00767   }
00768   mMsgListIndex = 0;
00769   mUrl = subjectToUrl( msgBase->cleanSubject() );
00770 }
00771 
00772 KURL KMSaveMsgCommand::url()
00773 {
00774   return mUrl;
00775 }
00776 
00777 KMCommand::Result KMSaveMsgCommand::execute()
00778 {
00779   mJob = KIO::put( mUrl, S_IRUSR|S_IWUSR, false, false );
00780   mJob->slotTotalSize( mTotalSize );
00781   mJob->setAsyncDataEnabled( true );
00782   mJob->setReportDataSent( true );
00783   connect(mJob, SIGNAL(dataReq(KIO::Job*, QByteArray &)),
00784     SLOT(slotSaveDataReq()));
00785   connect(mJob, SIGNAL(result(KIO::Job*)),
00786     SLOT(slotSaveResult(KIO::Job*)));
00787   setEmitsCompletedItself( true );
00788   return OK;
00789 }
00790 
00791 void KMSaveMsgCommand::slotSaveDataReq()
00792 {
00793   int remainingBytes = mData.size() - mOffset;
00794   if ( remainingBytes > 0 ) {
00795     // eat leftovers first
00796     if ( remainingBytes > MAX_CHUNK_SIZE )
00797       remainingBytes = MAX_CHUNK_SIZE;
00798 
00799     QByteArray data;
00800     data.duplicate( mData.data() + mOffset, remainingBytes );
00801     mJob->sendAsyncData( data );
00802     mOffset += remainingBytes;
00803     return;
00804   }
00805   // No leftovers, process next message.
00806   if ( mMsgListIndex < mMsgList.size() ) {
00807     KMMessage *msg = 0;
00808     int idx = -1;
00809     KMFolder * p = 0;
00810     KMMsgDict::instance()->getLocation( mMsgList[mMsgListIndex], &p, &idx );
00811     assert( p );
00812     assert( idx >= 0 );
00813     msg = p->getMsg(idx);
00814 
00815     if (msg->transferInProgress()) {
00816       QByteArray data = QByteArray();
00817       mJob->sendAsyncData( data );
00818     }
00819     msg->setTransferInProgress( true );
00820     if (msg->isComplete() ) {
00821       slotMessageRetrievedForSaving(msg);
00822     } else {
00823       // retrieve Message first
00824       if (msg->parent()  && !msg->isComplete() ) {
00825         FolderJob *job = msg->parent()->createJob(msg);
00826         job->setCancellable( false );
00827         connect(job, SIGNAL(messageRetrieved(KMMessage*)),
00828             this, SLOT(slotMessageRetrievedForSaving(KMMessage*)));
00829         job->start();
00830       }
00831     }
00832   } else {
00833     if ( mStandAloneMessage ) {
00834       // do the special case of a standalone message
00835       slotMessageRetrievedForSaving( mStandAloneMessage );
00836       mStandAloneMessage = 0;
00837     } else {
00838       // No more messages. Tell the putjob we are done.
00839       QByteArray data = QByteArray();
00840       mJob->sendAsyncData( data );
00841     }
00842   }
00843 }
00844 
00845 void KMSaveMsgCommand::slotMessageRetrievedForSaving(KMMessage *msg)
00846 {
00847   if ( msg ) {
00848     QCString str( msg->mboxMessageSeparator() );
00849     str += KMFolderMbox::escapeFrom( msg->asString() );
00850     str += "\n";
00851     msg->setTransferInProgress(false);
00852 
00853     mData = str;
00854     mData.resize(mData.size() - 1);
00855     mOffset = 0;
00856     QByteArray data;
00857     int size;
00858     // Unless it is great than 64 k send the whole message. kio buffers for us.
00859     if( mData.size() > (unsigned int) MAX_CHUNK_SIZE )
00860       size = MAX_CHUNK_SIZE;
00861     else
00862       size = mData.size();
00863 
00864     data.duplicate( mData, size );
00865     mJob->sendAsyncData( data );
00866     mOffset += size;
00867   }
00868   ++mMsgListIndex;
00869   // Get rid of the message.
00870   if ( msg && msg->parent() && msg->getMsgSerNum() ) {
00871     int idx = -1;
00872     KMFolder * p = 0;
00873     KMMsgDict::instance()->getLocation( msg, &p, &idx );
00874     assert( p == msg->parent() ); assert( idx >= 0 );
00875     p->unGetMsg( idx );
00876     p->close();
00877   }
00878 }
00879 
00880 void KMSaveMsgCommand::slotSaveResult(KIO::Job *job)
00881 {
00882   if (job->error())
00883   {
00884     if (job->error() == KIO::ERR_FILE_ALREADY_EXIST)
00885     {
00886       if (KMessageBox::warningContinueCancel(0,
00887         i18n("File %1 exists.\nDo you want to replace it?")
00888         .arg(mUrl.prettyURL()), i18n("Save to File"), i18n("&Replace"))
00889         == KMessageBox::Continue) {
00890         mOffset = 0;
00891 
00892         mJob = KIO::put( mUrl, S_IRUSR|S_IWUSR, true, false );
00893         mJob->slotTotalSize( mTotalSize );
00894         mJob->setAsyncDataEnabled( true );
00895         mJob->setReportDataSent( true );
00896         connect(mJob, SIGNAL(dataReq(KIO::Job*, QByteArray &)),
00897             SLOT(slotSaveDataReq()));
00898         connect(mJob, SIGNAL(result(KIO::Job*)),
00899             SLOT(slotSaveResult(KIO::Job*)));
00900       }
00901     }
00902     else
00903     {
00904       job->showErrorDialog();
00905       setResult( Failed );
00906       emit completed( this );
00907       deleteLater();
00908     }
00909   } else {
00910     setResult( OK );
00911     emit completed( this );
00912     deleteLater();
00913   }
00914 }
00915 
00916 //-----------------------------------------------------------------------------
00917 
00918 KMOpenMsgCommand::KMOpenMsgCommand( QWidget *parent, const KURL & url,
00919                                     const QString & encoding )
00920   : KMCommand( parent ),
00921     mUrl( url ),
00922     mEncoding( encoding )
00923 {
00924   setDeletesItself( true );
00925 }
00926 
00927 KMCommand::Result KMOpenMsgCommand::execute()
00928 {
00929   if ( mUrl.isEmpty() ) {
00930     mUrl = KFileDialog::getOpenURL( ":OpenMessage", "message/rfc822",
00931                                     parentWidget(), i18n("Open Message") );
00932   }
00933   if ( mUrl.isEmpty() ) {
00934     setDeletesItself( false );
00935     return Canceled;
00936   }
00937   mJob = KIO::get( mUrl, false, false );
00938   mJob->setReportDataSent( true );
00939   connect( mJob, SIGNAL( data( KIO::Job *, const QByteArray & ) ),
00940            this, SLOT( slotDataArrived( KIO::Job*, const QByteArray & ) ) );
00941   connect( mJob, SIGNAL( result( KIO::Job * ) ),
00942            SLOT( slotResult( KIO::Job * ) ) );
00943   setEmitsCompletedItself( true );
00944   return OK;
00945 }
00946 
00947 void KMOpenMsgCommand::slotDataArrived( KIO::Job *, const QByteArray & data )
00948 {
00949   if ( data.isEmpty() )
00950     return;
00951 
00952   mMsgString.append( data.data(), data.size() );
00953 }
00954 
00955 void KMOpenMsgCommand::slotResult( KIO::Job *job )
00956 {
00957   if ( job->error() ) {
00958     // handle errors
00959     job->showErrorDialog();
00960     setResult( Failed );
00961     emit completed( this );
00962   }
00963   else {
00964     int startOfMessage = 0;
00965     if ( mMsgString.compare( 0, 5, "From ", 5 ) == 0 ) {
00966       startOfMessage = mMsgString.find( '\n' );
00967       if ( startOfMessage == -1 ) {
00968         KMessageBox::sorry( parentWidget(),
00969                             i18n( "The file does not contain a message." ) );
00970         setResult( Failed );
00971         emit completed( this );
00972         // Emulate closing of a secondary window so that KMail exits in case it
00973         // was started with the --view command line option. Otherwise an
00974         // invisible KMail would keep running.
00975         SecondaryWindow *win = new SecondaryWindow();
00976         win->close();
00977         win->deleteLater();
00978         deleteLater();
00979         return;
00980       }
00981       startOfMessage += 1; // the message starts after the '\n'
00982     }
00983     // check for multiple messages in the file
00984     bool multipleMessages = true;
00985     int endOfMessage = mMsgString.find( "\nFrom " );
00986     if ( endOfMessage == -1 ) {
00987       endOfMessage = mMsgString.length();
00988       multipleMessages = false;
00989     }
00990     DwMessage *dwMsg = new DwMessage;
00991     dwMsg->FromString( mMsgString.substr( startOfMessage,
00992                                           endOfMessage - startOfMessage ) );
00993     dwMsg->Parse();
00994     // check whether we have a message ( no headers => this isn't a message )
00995     if ( dwMsg->Headers().NumFields() == 0 ) {
00996       KMessageBox::sorry( parentWidget(),
00997                           i18n( "The file does not contain a message." ) );
00998       delete dwMsg; dwMsg = 0;
00999       setResult( Failed );
01000       emit completed( this );
01001       // Emulate closing of a secondary window (see above).
01002       SecondaryWindow *win = new SecondaryWindow();
01003       win->close();
01004       win->deleteLater();
01005       deleteLater();
01006       return;
01007     }
01008     KMMessage *msg = new KMMessage( dwMsg );
01009     msg->setReadyToShow( true );
01010     KMReaderMainWin *win = new KMReaderMainWin();
01011     win->showMsg( mEncoding, msg );
01012     win->show();
01013     if ( multipleMessages )
01014       KMessageBox::information( win,
01015                                 i18n( "The file contains multiple messages. "
01016                                       "Only the first message is shown." ) );
01017     setResult( OK );
01018     emit completed( this );
01019   }
01020   deleteLater();
01021 }
01022 
01023 //-----------------------------------------------------------------------------
01024 
01025 //TODO: ReplyTo, NoQuoteReplyTo, ReplyList, ReplyToAll, ReplyAuthor
01026 //      are all similar and should be factored
01027 KMReplyToCommand::KMReplyToCommand( QWidget *parent, KMMessage *msg,
01028                                     const QString &selection )
01029   : KMCommand( parent, msg ), mSelection( selection )
01030 {
01031 }
01032 
01033 KMCommand::Result KMReplyToCommand::execute()
01034 {
01035   KCursorSaver busy(KBusyPtr::busy());
01036   KMMessage *msg = retrievedMessage();
01037   KMMessage *reply = msg->createReply( KMail::ReplySmart, mSelection );
01038   KMail::Composer * win = KMail::makeComposer( reply );
01039   win->setCharset( msg->codec()->mimeName(), TRUE );
01040   win->setReplyFocus();
01041   win->show();
01042 
01043   return OK;
01044 }
01045 
01046 
01047 KMNoQuoteReplyToCommand::KMNoQuoteReplyToCommand( QWidget *parent,
01048                                                   KMMessage *msg )
01049   : KMCommand( parent, msg )
01050 {
01051 }
01052 
01053 KMCommand::Result KMNoQuoteReplyToCommand::execute()
01054 {
01055   KCursorSaver busy(KBusyPtr::busy());
01056   KMMessage *msg = retrievedMessage();
01057   KMMessage *reply = msg->createReply( KMail::ReplySmart, "", TRUE);
01058   KMail::Composer * win = KMail::makeComposer( reply );
01059   win->setCharset(msg->codec()->mimeName(), TRUE);
01060   win->setReplyFocus(false);
01061   win->show();
01062 
01063   return OK;
01064 }
01065 
01066 
01067 KMReplyListCommand::KMReplyListCommand( QWidget *parent,
01068   KMMessage *msg, const QString &selection )
01069  : KMCommand( parent, msg ), mSelection( selection )
01070 {
01071 }
01072 
01073 KMCommand::Result KMReplyListCommand::execute()
01074 {
01075   KCursorSaver busy(KBusyPtr::busy());
01076   KMMessage *msg = retrievedMessage();
01077   KMMessage *reply = msg->createReply( KMail::ReplyList, mSelection);
01078   KMail::Composer * win = KMail::makeComposer( reply );
01079   win->setCharset(msg->codec()->mimeName(), TRUE);
01080   win->setReplyFocus(false);
01081   win->show();
01082 
01083   return OK;
01084 }
01085 
01086 
01087 KMReplyToAllCommand::KMReplyToAllCommand( QWidget *parent,
01088   KMMessage *msg, const QString &selection )
01089   :KMCommand( parent, msg ), mSelection( selection )
01090 {
01091 }
01092 
01093 KMCommand::Result KMReplyToAllCommand::execute()
01094 {
01095   KCursorSaver busy(KBusyPtr::busy());
01096   KMMessage *msg = retrievedMessage();
01097   KMMessage *reply = msg->createReply( KMail::ReplyAll, mSelection );
01098   KMail::Composer * win = KMail::makeComposer( reply );
01099   win->setCharset( msg->codec()->mimeName(), TRUE );
01100   win->setReplyFocus();
01101   win->show();
01102 
01103   return OK;
01104 }
01105 
01106 
01107 KMReplyAuthorCommand::KMReplyAuthorCommand( QWidget *parent, KMMessage *msg,
01108                                             const QString &selection )
01109   : KMCommand( parent, msg ), mSelection( selection )
01110 {
01111 }
01112 
01113 KMCommand::Result KMReplyAuthorCommand::execute()
01114 {
01115   KCursorSaver busy(KBusyPtr::busy());
01116   KMMessage *msg = retrievedMessage();
01117   KMMessage *reply = msg->createReply( KMail::ReplyAuthor, mSelection );
01118   KMail::Composer * win = KMail::makeComposer( reply );
01119   win->setCharset( msg->codec()->mimeName(), TRUE );
01120   win->setReplyFocus();
01121   win->show();
01122 
01123   return OK;
01124 }
01125 
01126 
01127 KMForwardCommand::KMForwardCommand( QWidget *parent,
01128   const QPtrList<KMMsgBase> &msgList, uint identity )
01129   : KMCommand( parent, msgList ),
01130     mIdentity( identity )
01131 {
01132 }
01133 
01134 KMForwardCommand::KMForwardCommand( QWidget *parent, KMMessage *msg,
01135                                     uint identity )
01136   : KMCommand( parent, msg ),
01137     mIdentity( identity )
01138 {
01139 }
01140 
01141 KMCommand::Result KMForwardCommand::execute()
01142 {
01143   QPtrList<KMMessage> msgList = retrievedMsgs();
01144 
01145   if (msgList.count() >= 2) {
01146     // ask if they want a mime digest forward
01147 
01148     if (KMessageBox::questionYesNo( parentWidget(),
01149                                     i18n("Forward selected messages as "
01150                                          "a MIME digest?"), QString::null, i18n("Send Digest"), i18n("Send") )
01151         == KMessageBox::Yes) {
01152       uint id = 0;
01153       KMMessage *fwdMsg = new KMMessage;
01154       KMMessagePart *msgPart = new KMMessagePart;
01155       QString msgPartText;
01156       int msgCnt = 0; // incase there are some we can't forward for some reason
01157 
01158       // dummy header initialization; initialization with the correct identity
01159       // is done below
01160       fwdMsg->initHeader(id);
01161       fwdMsg->setAutomaticFields(true);
01162       fwdMsg->mMsg->Headers().ContentType().CreateBoundary(1);
01163       QCString boundary( fwdMsg->mMsg->Headers().ContentType().Boundary().c_str() );
01164       msgPartText = i18n("\nThis is a MIME digest forward. The content of the"
01165                          " message is contained in the attachment(s).\n\n\n");
01166       // iterate through all the messages to be forwarded
01167       for (KMMessage *msg = msgList.first(); msg; msg = msgList.next()) {
01168         // set the identity
01169         if (id == 0)
01170           id = msg->headerField("X-KMail-Identity").stripWhiteSpace().toUInt();
01171         // set the part header
01172         msgPartText += "--";
01173         msgPartText += QString::fromLatin1( boundary );
01174         msgPartText += "\nContent-Type: MESSAGE/RFC822";
01175         msgPartText += QString("; CHARSET=%1").arg(msg->charset());
01176         msgPartText += "\n";
01177         DwHeaders dwh;
01178         dwh.MessageId().CreateDefault();
01179         msgPartText += QString("Content-ID: %1\n").arg(dwh.MessageId().AsString().c_str());
01180         msgPartText += QString("Content-Description: %1").arg(msg->subject());
01181         if (!msg->subject().contains("(fwd)"))
01182           msgPartText += " (fwd)";
01183         msgPartText += "\n\n";
01184         // remove headers that shouldn't be forwarded
01185         msg->removePrivateHeaderFields();
01186         msg->removeHeaderField("BCC");
01187         // set the part
01188         msgPartText += msg->headerAsString();
01189         msgPartText += "\n";
01190         msgPartText += msg->body();
01191         msgPartText += "\n";     // eot
01192         msgCnt++;
01193         fwdMsg->link(msg, KMMsgStatusForwarded);
01194       }
01195       if ( id == 0 )
01196         id = mIdentity; // use folder identity if no message had an id set
01197       fwdMsg->initHeader(id);
01198       msgPartText += "--";
01199       msgPartText += QString::fromLatin1( boundary );
01200       msgPartText += "--\n";
01201       QCString tmp;
01202       msgPart->setTypeStr("MULTIPART");
01203       tmp.sprintf( "Digest; boundary=\"%s\"", boundary.data() );
01204       msgPart->setSubtypeStr( tmp );
01205       msgPart->setName("unnamed");
01206       msgPart->setCte(DwMime::kCte7bit);   // does it have to be 7bit?
01207       msgPart->setContentDescription(QString("Digest of %1 messages.").arg(msgCnt));
01208       // THIS HAS TO BE AFTER setCte()!!!!
01209       msgPart->setBodyEncoded(QCString(msgPartText.ascii()));
01210       KCursorSaver busy(KBusyPtr::busy());
01211       KMail::Composer * win = KMail::makeComposer( fwdMsg, id );
01212       win->addAttach(msgPart);
01213       win->show();
01214       return OK;
01215     } else {            // NO MIME DIGEST, Multiple forward
01216       uint id = 0;
01217       QCString msgText = "";
01218       QPtrList<KMMessage> linklist;
01219       for (KMMessage *msg = msgList.first(); msg; msg = msgList.next()) {
01220         // set the identity
01221         if (id == 0)
01222           id = msg->headerField("X-KMail-Identity").stripWhiteSpace().toUInt();
01223 
01224         msgText += msg->createForwardBody();
01225         linklist.append(msg);
01226       }
01227       if ( id == 0 )
01228         id = mIdentity; // use folder identity if no message had an id set
01229       KMMessage *fwdMsg = new KMMessage;
01230       fwdMsg->initHeader(id);
01231       fwdMsg->setAutomaticFields(true);
01232       fwdMsg->setCharset("utf-8");
01233       fwdMsg->setBody(msgText);
01234 
01235       for (KMMessage *msg = linklist.first(); msg; msg = linklist.next())
01236         fwdMsg->link(msg, KMMsgStatusForwarded);
01237 
01238       KCursorSaver busy(KBusyPtr::busy());
01239       KMail::Composer * win = KMail::makeComposer( fwdMsg, id );
01240       win->setCharset("");
01241       win->show();
01242       return OK;
01243     }
01244   }
01245 
01246   // forward a single message at most.
01247   KMMessage *msg = msgList.getFirst();
01248   if ( !msg || !msg->codec() )
01249     return Failed;
01250 
01251   KCursorSaver busy(KBusyPtr::busy());
01252   KMMessage *fwdMsg = msg->createForward();
01253 
01254   uint id = msg->headerField("X-KMail-Identity").stripWhiteSpace().toUInt();
01255   if ( id == 0 )
01256     id = mIdentity;
01257   {
01258     KMail::Composer * win = KMail::makeComposer( fwdMsg, id );
01259     win->setCharset( fwdMsg->codec()->mimeName(), true );
01260     win->setBody( QString::fromUtf8( msg->createForwardBody() ) );
01261     win->show();
01262   }
01263 
01264   return OK;
01265 }
01266 
01267 
01268 KMForwardAttachedCommand::KMForwardAttachedCommand( QWidget *parent,
01269            const QPtrList<KMMsgBase> &msgList, uint identity, KMail::Composer *win )
01270   : KMCommand( parent, msgList ), mIdentity( identity ),
01271     mWin( QGuardedPtr<KMail::Composer>( win ))
01272 {
01273 }
01274 
01275 KMForwardAttachedCommand::KMForwardAttachedCommand( QWidget *parent,
01276            KMMessage * msg, uint identity, KMail::Composer *win )
01277   : KMCommand( parent, msg ), mIdentity( identity ),
01278     mWin( QGuardedPtr< KMail::Composer >( win ))
01279 {
01280 }
01281 
01282 KMCommand::Result KMForwardAttachedCommand::execute()
01283 {
01284   QPtrList<KMMessage> msgList = retrievedMsgs();
01285   KMMessage *fwdMsg = new KMMessage;
01286 
01287   if (msgList.count() >= 2) {
01288     // don't respect X-KMail-Identity headers because they might differ for
01289     // the selected mails
01290     fwdMsg->initHeader(mIdentity);
01291   }
01292   else if (msgList.count() == 1) {
01293     KMMessage *msg = msgList.getFirst();
01294     fwdMsg->initFromMessage(msg);
01295     fwdMsg->setSubject( msg->forwardSubject() );
01296   }
01297 
01298   fwdMsg->setAutomaticFields(true);
01299 
01300   KCursorSaver busy(KBusyPtr::busy());
01301   if (!mWin)
01302     mWin = KMail::makeComposer(fwdMsg, mIdentity);
01303 
01304   // iterate through all the messages to be forwarded
01305   for (KMMessage *msg = msgList.first(); msg; msg = msgList.next()) {
01306     // remove headers that shouldn't be forwarded
01307     msg->removePrivateHeaderFields();
01308     msg->removeHeaderField("BCC");
01309     // set the part
01310     KMMessagePart *msgPart = new KMMessagePart;
01311     msgPart->setTypeStr("message");
01312     msgPart->setSubtypeStr("rfc822");
01313     msgPart->setCharset(msg->charset());
01314     msgPart->setName("forwarded message");
01315     msgPart->setContentDescription(msg->from()+": "+msg->subject());
01316     msgPart->setContentDisposition( "inline" );
01317     // THIS HAS TO BE AFTER setCte()!!!!
01318     QValueList<int> dummy;
01319     msgPart->setBodyAndGuessCte(msg->asString(), dummy, true);
01320     msgPart->setCharset("");
01321 
01322     fwdMsg->link(msg, KMMsgStatusForwarded);
01323     mWin->addAttach(msgPart);
01324   }
01325 
01326   mWin->show();
01327 
01328   return OK;
01329 }
01330 
01331 
01332 KMRedirectCommand::KMRedirectCommand( QWidget *parent,
01333   KMMessage *msg )
01334   : KMCommand( parent, msg )
01335 {
01336 }
01337 
01338 KMCommand::Result KMRedirectCommand::execute()
01339 {
01340   KMMessage *msg = retrievedMessage();
01341   if ( !msg || !msg->codec() )
01342     return Failed;
01343 
01344   RedirectDialog dlg( parentWidget(), "redirect", true,
01345                       kmkernel->msgSender()->sendImmediate() );
01346   if (dlg.exec()==QDialog::Rejected) return Failed;
01347 
01348   KMMessage *newMsg = msg->createRedirect( dlg.to() );
01349   KMFilterAction::sendMDN( msg, KMime::MDN::Dispatched );
01350 
01351   const KMail::MessageSender::SendMethod method = dlg.sendImmediate()
01352     ? KMail::MessageSender::SendImmediate
01353     : KMail::MessageSender::SendLater;
01354   if ( !kmkernel->msgSender()->send( newMsg, method ) ) {
01355     kdDebug(5006) << "KMRedirectCommand: could not redirect message (sending failed)" << endl;
01356     return Failed; // error: couldn't send
01357   }
01358   return OK;
01359 }
01360 
01361 
01362 KMPrintCommand::KMPrintCommand( QWidget *parent,
01363   KMMessage *msg, bool htmlOverride, bool htmlLoadExtOverride,
01364   bool useFixedFont, const QString & encoding )
01365   : KMCommand( parent, msg ), mHtmlOverride( htmlOverride ),
01366     mHtmlLoadExtOverride( htmlLoadExtOverride ),
01367     mUseFixedFont( useFixedFont ), mEncoding( encoding )
01368 {
01369 }
01370 
01371 KMCommand::Result KMPrintCommand::execute()
01372 {
01373   KMReaderWin printWin( 0, 0, 0 );
01374   printWin.setPrinting( true );
01375   printWin.readConfig();
01376   printWin.setHtmlOverride( mHtmlOverride );
01377   printWin.setHtmlLoadExtOverride( mHtmlLoadExtOverride );
01378   printWin.setUseFixedFont( mUseFixedFont );
01379   printWin.setOverrideEncoding( mEncoding );
01380   printWin.setMsg( retrievedMessage(), true );
01381   printWin.printMsg();
01382 
01383   return OK;
01384 }
01385 
01386 
01387 KMSetStatusCommand::KMSetStatusCommand( KMMsgStatus status,
01388   const QValueList<Q_UINT32> &serNums, bool toggle )
01389   : mStatus( status ), mSerNums( serNums ), mToggle( toggle )
01390 {
01391 }
01392 
01393 KMCommand::Result KMSetStatusCommand::execute()
01394 {
01395   QValueListIterator<Q_UINT32> it;
01396   int idx = -1;
01397   KMFolder *folder = 0;
01398   bool parentStatus = false;
01399 
01400   // Toggle actions on threads toggle the whole thread
01401   // depending on the state of the parent.
01402   if (mToggle) {
01403     KMMsgBase *msg;
01404     KMMsgDict::instance()->getLocation( *mSerNums.begin(), &folder, &idx );
01405     if (folder) {
01406       msg = folder->getMsgBase(idx);
01407       if (msg && (msg->status()&mStatus))
01408         parentStatus = true;
01409       else
01410         parentStatus = false;
01411     }
01412   }
01413   QMap< KMFolder*, QValueList<int> > folderMap;
01414   for ( it = mSerNums.begin(); it != mSerNums.end(); ++it ) {
01415     KMMsgDict::instance()->getLocation( *it, &folder, &idx );
01416     if (folder) {
01417       if (mToggle) {
01418         KMMsgBase *msg = folder->getMsgBase(idx);
01419         // check if we are already at the target toggle state
01420         if (msg) {
01421           bool myStatus;
01422           if (msg->status()&mStatus)
01423             myStatus = true;
01424           else
01425             myStatus = false;
01426           if (myStatus != parentStatus)
01427             continue;
01428         }
01429       }
01430       /* Collect the ids for each folder in a separate list and
01431          send them off in one go at the end. */
01432       folderMap[folder].append(idx);
01433     }
01434   }
01435   QMapIterator< KMFolder*, QValueList<int> > it2 = folderMap.begin();
01436   while ( it2 != folderMap.end() ) {
01437      KMFolder *f = it2.key();
01438      f->setStatus( (*it2), mStatus, mToggle );
01439      ++it2;
01440   }
01441   //kapp->dcopClient()->emitDCOPSignal( "unreadCountChanged()", QByteArray() );
01442 
01443   return OK;
01444 }
01445 
01446 
01447 KMFilterCommand::KMFilterCommand( const QCString &field, const QString &value )
01448   : mField( field ), mValue( value )
01449 {
01450 }
01451 
01452 KMCommand::Result KMFilterCommand::execute()
01453 {
01454   kmkernel->filterMgr()->createFilter( mField, mValue );
01455 
01456   return OK;
01457 }
01458 
01459 
01460 KMFilterActionCommand::KMFilterActionCommand( QWidget *parent,
01461                                               const QPtrList<KMMsgBase> &msgList,
01462                                               KMFilter *filter )
01463   : KMCommand( parent, msgList ), mFilter( filter  )
01464 {
01465 }
01466 
01467 KMCommand::Result KMFilterActionCommand::execute()
01468 {
01469   KCursorSaver busy( KBusyPtr::busy() );
01470   QPtrList<KMMessage> msgList = retrievedMsgs();
01471 
01472   for (KMMessage *msg = msgList.first(); msg; msg = msgList.next())
01473     if( msg->parent() )
01474       kmkernel->filterMgr()->tempOpenFolder(msg->parent());
01475 
01476   int msgCount = 0;
01477   int msgCountToFilter = msgList.count();
01478   for (KMMessage *msg = msgList.first(); msg; msg = msgList.next()) {
01479     int diff = msgCountToFilter - ++msgCount;
01480     if ( diff < 10 || !( msgCount % 20 ) ) {
01481       QString statusMsg = i18n("Filtering message %1 of %2");
01482       statusMsg = statusMsg.arg( msgCount ).arg( msgCountToFilter );
01483       KPIM::BroadcastStatus::instance()->setStatusMsg( statusMsg );
01484       KApplication::kApplication()->eventLoop()->processEvents( QEventLoop::ExcludeUserInput, 50 );
01485     }
01486     msg->setTransferInProgress(false);
01487     int filterResult = kmkernel->filterMgr()->process(msg, mFilter);
01488     if (filterResult == 2) {
01489       // something went horribly wrong (out of space?)
01490       perror("Critical error");
01491       kmkernel->emergencyExit( i18n("Not enough free disk space?" ));
01492     }
01493     msg->setTransferInProgress(true);
01494   }
01495 
01496   return OK;
01497 }
01498 
01499 
01500 KMMetaFilterActionCommand::KMMetaFilterActionCommand( KMFilter *filter,
01501                                                       KMHeaders *headers,
01502                                                       KMMainWidget *main )
01503     : QObject( main ),
01504       mFilter( filter ), mHeaders( headers ), mMainWidget( main )
01505 {
01506 }
01507 
01508 void KMMetaFilterActionCommand::start()
01509 {
01510   if (ActionScheduler::isEnabled() ) {
01511     // use action scheduler
01512     KMFilterMgr::FilterSet set = KMFilterMgr::All;
01513     QValueList<KMFilter*> filters;
01514     filters.append( mFilter );
01515     ActionScheduler *scheduler = new ActionScheduler( set, filters, mHeaders );
01516     scheduler->setAlwaysMatch( true );
01517     scheduler->setAutoDestruct( true );
01518 
01519     int contentX, contentY;
01520     HeaderItem *nextItem = mHeaders->prepareMove( &contentX, &contentY );
01521     QPtrList<KMMsgBase> msgList = *mHeaders->selectedMsgs(true);
01522     mHeaders->finalizeMove( nextItem, contentX, contentY );
01523 
01524     for (KMMsgBase *msg = msgList.first(); msg; msg = msgList.next())
01525       scheduler->execFilters( msg );
01526   } else {
01527     KMCommand *filterCommand = new KMFilterActionCommand( mMainWidget,
01528     *mHeaders->selectedMsgs(), mFilter);
01529     filterCommand->start();
01530     int contentX, contentY;
01531     HeaderItem *item = mHeaders->prepareMove( &contentX, &contentY );
01532     mHeaders->finalizeMove( item, contentX, contentY );
01533   }
01534 }
01535 
01536 FolderShortcutCommand::FolderShortcutCommand( KMMainWidget *mainwidget,
01537                                               KMFolder *folder )
01538     : mMainWidget( mainwidget ), mFolder( folder ), mAction( 0 )
01539 {
01540 }
01541 
01542 
01543 FolderShortcutCommand::~FolderShortcutCommand()
01544 {
01545   if ( mAction ) mAction->unplugAll();
01546   delete mAction;
01547 }
01548 
01549 void FolderShortcutCommand::start()
01550 {
01551   mMainWidget->slotSelectFolder( mFolder );
01552 }
01553 
01554 void FolderShortcutCommand::setAction( KAction* action )
01555 {
01556   mAction = action;
01557 }
01558 
01559 KMMailingListFilterCommand::KMMailingListFilterCommand( QWidget *parent,
01560                                                         KMMessage *msg )
01561   : KMCommand( parent, msg )
01562 {
01563 }
01564 
01565 KMCommand::Result KMMailingListFilterCommand::execute()
01566 {
01567   QCString name;
01568   QString value;
01569   KMMessage *msg = retrievedMessage();
01570   if (!msg)
01571     return Failed;
01572 
01573   if ( !MailingList::name( msg, name, value ).isEmpty() ) {
01574     kmkernel->filterMgr()->createFilter( name, value );
01575     return OK;
01576   }
01577   else
01578     return Failed;
01579 }
01580 
01581 
01582 void KMMenuCommand::folderToPopupMenu(bool move,
01583   QObject *receiver, KMMenuToFolder *aMenuToFolder, QPopupMenu *menu )
01584 {
01585   while ( menu->count() )
01586   {
01587     QPopupMenu *popup = menu->findItem( menu->idAt( 0 ) )->popup();
01588     if (popup)
01589       delete popup;
01590     else
01591       menu->removeItemAt( 0 );
01592   }
01593 
01594   if (!kmkernel->imapFolderMgr()->dir().first() &&
01595       !kmkernel->dimapFolderMgr()->dir().first())
01596   { // only local folders
01597     makeFolderMenu( &kmkernel->folderMgr()->dir(), move,
01598                     receiver, aMenuToFolder, menu );
01599   } else {
01600     // operate on top-level items
01601     QPopupMenu* subMenu = new QPopupMenu(menu);
01602     makeFolderMenu( &kmkernel->folderMgr()->dir(),
01603                     move, receiver, aMenuToFolder, subMenu );
01604     menu->insertItem( i18n( "Local Folders" ), subMenu );
01605     KMFolderDir* fdir = &kmkernel->imapFolderMgr()->dir();
01606     for (KMFolderNode *node = fdir->first(); node; node = fdir->next()) {
01607       if (node->isDir())
01608         continue;
01609       subMenu = new QPopupMenu(menu);
01610       makeFolderMenu( node, move, receiver, aMenuToFolder, subMenu );
01611       menu->insertItem( node->label(), subMenu );
01612     }
01613     fdir = &kmkernel->dimapFolderMgr()->dir();
01614     for (KMFolderNode *node = fdir->first(); node; node = fdir->next()) {
01615       if (node->isDir())
01616         continue;
01617       subMenu = new QPopupMenu(menu);
01618       makeFolderMenu( node, move, receiver, aMenuToFolder, subMenu );
01619       menu->insertItem( node->label(), subMenu );
01620     }
01621   }
01622 }
01623 
01624 void KMMenuCommand::makeFolderMenu(KMFolderNode* node, bool move,
01625   QObject *receiver, KMMenuToFolder *aMenuToFolder, QPopupMenu *menu )
01626 {
01627   // connect the signals
01628   if (move)
01629   {
01630     disconnect(menu, SIGNAL(activated(int)), receiver,
01631            SLOT(moveSelectedToFolder(int)));
01632     connect(menu, SIGNAL(activated(int)), receiver,
01633              SLOT(moveSelectedToFolder(int)));
01634   } else {
01635     disconnect(menu, SIGNAL(activated(int)), receiver,
01636            SLOT(copySelectedToFolder(int)));
01637     connect(menu, SIGNAL(activated(int)), receiver,
01638              SLOT(copySelectedToFolder(int)));
01639   }
01640 
01641   KMFolder *folder = 0;
01642   KMFolderDir *folderDir = 0;
01643   if (node->isDir()) {
01644     folderDir = static_cast<KMFolderDir*>(node);
01645   } else {
01646     folder = static_cast<KMFolder*>(node);
01647     folderDir = folder->child();
01648   }
01649 
01650   if (folder && !folder->noContent())
01651   {
01652     int menuId;
01653     if (move)
01654       menuId = menu->insertItem(i18n("Move to This Folder"));
01655     else
01656       menuId = menu->insertItem(i18n("Copy to This Folder"));
01657     aMenuToFolder->insert( menuId, folder );
01658     menu->setItemEnabled( menuId, !folder->isReadOnly() );
01659     menu->insertSeparator();
01660   }
01661 
01662   if (!folderDir)
01663     return;
01664 
01665   for (KMFolderNode *it = folderDir->first(); it; it = folderDir->next() ) {
01666     if (it->isDir())
01667       continue;
01668     KMFolder *child = static_cast<KMFolder*>(it);
01669     QString label = child->label();
01670     label.replace("&","&&");
01671     if (child->child() && child->child()->first()) {
01672       // descend
01673       QPopupMenu *subMenu = new QPopupMenu(menu, "subMenu");
01674       makeFolderMenu( child, move, receiver,
01675                       aMenuToFolder, subMenu );
01676       menu->insertItem( label, subMenu );
01677     } else {
01678       // insert an item
01679       int menuId = menu->insertItem( label );
01680       aMenuToFolder->insert( menuId, child );
01681       menu->setItemEnabled( menuId, !child->isReadOnly() );
01682     }
01683   }
01684   return;
01685 }
01686 
01687 
01688 KMCopyCommand::KMCopyCommand( KMFolder* destFolder,
01689                               const QPtrList<KMMsgBase> &msgList )
01690 :mDestFolder( destFolder ), mMsgList( msgList )
01691 {
01692   setDeletesItself( true );
01693 }
01694 
01695 KMCopyCommand::KMCopyCommand( KMFolder* destFolder, KMMessage * msg )
01696   :mDestFolder( destFolder )
01697 {
01698   setDeletesItself( true );
01699   mMsgList.append( &msg->toMsgBase() );
01700 }
01701 
01702 KMCommand::Result KMCopyCommand::execute()
01703 {
01704   KMMsgBase *msgBase;
01705   KMMessage *msg, *newMsg;
01706   int idx = -1;
01707   bool isMessage;
01708   QPtrList<KMMessage> list;
01709   QPtrList<KMMessage> localList;
01710 
01711   if (mDestFolder && mDestFolder->open() != 0)
01712   {
01713     deleteLater();
01714     return Failed;
01715   }
01716 
01717   KCursorSaver busy(KBusyPtr::busy());
01718 
01719   mWaitingForMsgs.clear();
01720   for (msgBase = mMsgList.first(); msgBase; msgBase = mMsgList.next() )
01721   {
01722     KMFolder *srcFolder = msgBase->parent();
01723     if (isMessage = msgBase->isMessage())
01724     {
01725       msg = static_cast<KMMessage*>(msgBase);
01726     } else {
01727       idx = srcFolder->find(msgBase);
01728       assert(idx != -1);
01729       msg = srcFolder->getMsg(idx);
01730     }
01731 
01732     if (srcFolder && mDestFolder &&
01733         (srcFolder->folderType()== KMFolderTypeImap) &&
01734         (mDestFolder->folderType() == KMFolderTypeImap) &&
01735         (static_cast<KMFolderImap*>(srcFolder->storage())->account() ==
01736          static_cast<KMFolderImap*>(mDestFolder->storage())->account()))
01737     {
01738       // imap => imap with same account
01739       list.append(msg);
01740     } else {
01741       newMsg = new KMMessage;
01742       newMsg->setComplete(msg->isComplete());
01743       // make sure the attachment state is only calculated when it's complete
01744       if (!newMsg->isComplete())
01745         newMsg->setReadyToShow(false);
01746       newMsg->fromString(msg->asString());
01747       newMsg->setStatus(msg->status());
01748 
01749       if (srcFolder && !newMsg->isComplete())
01750       {
01751         // imap => others
01752         mWaitingForMsgs.append( msg->getMsgSerNum() );
01753         disconnect(mDestFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
01754             this, SLOT(slotMsgAdded(KMFolder*, Q_UINT32)));
01755         connect(mDestFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
01756             this, SLOT(slotMsgAdded(KMFolder*, Q_UINT32)));
01757         newMsg->setParent(msg->parent());
01758         FolderJob *job = srcFolder->createJob(newMsg);
01759         job->setCancellable( false );
01760         connect(job, SIGNAL(messageRetrieved(KMMessage*)),
01761                 mDestFolder, SLOT(reallyAddCopyOfMsg(KMMessage*)));
01762         job->start();
01763       } else {
01764         // local => others
01765         localList.append(newMsg);
01766       }
01767     }
01768 
01769     if (srcFolder && !isMessage && list.isEmpty())
01770     {
01771       assert(idx != -1);
01772       srcFolder->unGetMsg( idx );
01773     }
01774 
01775   } // end for
01776 
01777   bool deleteNow = false;
01778   if (!localList.isEmpty())
01779   {
01780     QValueList<int> index;
01781     mDestFolder->addMsg( localList, index );
01782     for ( QValueListIterator<int> it = index.begin(); it != index.end(); ++it ) {
01783       mDestFolder->unGetMsg( *it );
01784     }
01785     if ( mDestFolder->folderType() == KMFolderTypeImap ) {
01786       if ( mWaitingForMsgs.isEmpty() ) {
01787         // wait for the end of the copy before closing the folder
01788         KMFolderImap *imapDestFolder = static_cast<KMFolderImap*>(mDestFolder->storage());
01789         connect( imapDestFolder, SIGNAL( folderComplete( KMFolderImap*, bool ) ),
01790             this, SLOT( slotFolderComplete() ) );
01791       }
01792     } else {
01793       deleteNow = true; // we're done
01794     }
01795   }
01796 
01797 //TODO: Get rid of the other cases just use this one for all types of folder
01798 //TODO: requires adding copyMsg and getFolder methods to KMFolder.h
01799   if (!list.isEmpty())
01800   {
01801     // copy the message(s); note: the list is empty afterwards!
01802     KMFolderImap *imapDestFolder = static_cast<KMFolderImap*>(mDestFolder->storage());
01803     connect( imapDestFolder, SIGNAL( folderComplete( KMFolderImap*, bool ) ),
01804         this, SLOT( slotFolderComplete() ) );
01805     imapDestFolder->copyMsg(list);
01806     imapDestFolder->getFolder();
01807   }
01808 
01809   // only close the folder and delete the job if we're done
01810   // otherwise this is done in slotMsgAdded or slotFolderComplete
01811   if ( deleteNow )
01812   {
01813     mDestFolder->close();
01814     deleteLater();
01815   }
01816 
01817   return OK;
01818 }
01819 
01820 void KMCopyCommand::slotMsgAdded( KMFolder*, Q_UINT32 serNum )
01821 {
01822   mWaitingForMsgs.remove( serNum );
01823   if ( mWaitingForMsgs.isEmpty() )
01824   {
01825     mDestFolder->close();
01826     deleteLater();
01827   }
01828 }
01829 
01830 void KMCopyCommand::slotFolderComplete()
01831 {
01832   mDestFolder->close();
01833   deleteLater();
01834 }
01835 
01836 
01837 KMMoveCommand::KMMoveCommand( KMFolder* destFolder,
01838                               const QPtrList<KMMsgBase> &msgList)
01839   : mDestFolder( destFolder ), mMsgList( msgList ), mProgressItem( 0 )
01840 {
01841 }
01842 
01843 KMMoveCommand::KMMoveCommand( KMFolder* destFolder,
01844                               KMMessage *msg )
01845   : mDestFolder( destFolder ), mProgressItem( 0 )
01846 {
01847   mMsgList.append( &msg->toMsgBase() );
01848 }
01849 
01850 KMMoveCommand::KMMoveCommand( KMFolder* destFolder,
01851                               KMMsgBase *msgBase )
01852   : mDestFolder( destFolder ), mProgressItem( 0 )
01853 {
01854   mMsgList.append( msgBase );
01855 }
01856 
01857 KMMoveCommand::KMMoveCommand( Q_UINT32 )
01858   : mProgressItem( 0 )
01859 {
01860 }
01861 
01862 KMCommand::Result KMMoveCommand::execute()
01863 {
01864   setEmitsCompletedItself( true );
01865   setDeletesItself( true );
01866   typedef QMap< KMFolder*, QPtrList<KMMessage>* > FolderToMessageListMap;
01867   FolderToMessageListMap folderDeleteList;
01868 
01869   if (mDestFolder && mDestFolder->open() != 0) {
01870     completeMove( Failed );
01871     return Failed;
01872   }
01873   KCursorSaver busy(KBusyPtr::busy());
01874 
01875   // TODO set SSL state according to source and destfolder connection?
01876   Q_ASSERT( !mProgressItem );
01877   mProgressItem =
01878      ProgressManager::createProgressItem (
01879          "move"+ProgressManager::getUniqueID(),
01880          mDestFolder ? i18n( "Moving messages" ) : i18n( "Deleting messages" ) );
01881   connect( mProgressItem, SIGNAL( progressItemCanceled( KPIM::ProgressItem* ) ),
01882            this, SLOT( slotMoveCanceled() ) );
01883 
01884   KMMessage *msg;
01885   KMMsgBase *msgBase;
01886   int rc = 0;
01887   int index;
01888   QPtrList<KMMessage> list;
01889   int undoId = -1;
01890   mCompleteWithAddedMsg = false;
01891 
01892   if (mDestFolder) {
01893     connect (mDestFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
01894              this, SLOT(slotMsgAddedToDestFolder(KMFolder*, Q_UINT32)));
01895     for ( msgBase=mMsgList.first(); msgBase; msgBase=mMsgList.next() ) {
01896       mLostBoys.append( msgBase->getMsgSerNum() );
01897     }
01898   }
01899   mProgressItem->setTotalItems( mMsgList.count() );
01900 
01901   for (msgBase=mMsgList.first(); msgBase && !rc; msgBase=mMsgList.next()) {
01902     KMFolder *srcFolder = msgBase->parent();
01903     if (srcFolder == mDestFolder)
01904       continue;
01905     bool undo = msgBase->enableUndo();
01906     int idx = srcFolder->find(msgBase);
01907     assert(idx != -1);
01908     if ( msgBase->isMessage() ) {
01909       msg = static_cast<KMMessage*>(msgBase);
01910     } else {
01911       msg = srcFolder->getMsg(idx);
01912     }
01913 
01914     if ( msg->transferInProgress() &&
01915          srcFolder->folderType() == KMFolderTypeImap )
01916     {
01917       // cancel the download
01918       msg->setTransferInProgress( false, true );
01919       static_cast<KMFolderImap*>(srcFolder->storage())->ignoreJobsForMessage( msg );
01920     }
01921 
01922     if (mDestFolder) {
01923       if (mDestFolder->folderType() == KMFolderTypeImap) {
01924         /* If we are moving to an imap folder, connect to it's completed
01925          * signal so we notice when all the mails should have showed up in it
01926          * but haven't for some reason. */
01927         KMFolderImap *imapFolder = static_cast<KMFolderImap*> ( mDestFolder->storage() );
01928         disconnect (imapFolder, SIGNAL(folderComplete( KMFolderImap*, bool )),
01929                  this, SLOT(slotImapFolderCompleted( KMFolderImap*, bool )));
01930 
01931         connect (imapFolder, SIGNAL(folderComplete( KMFolderImap*, bool )),
01932                  this, SLOT(slotImapFolderCompleted( KMFolderImap*, bool )));
01933         list.append(msg);
01934       } else {
01935         // We are moving to a local folder.
01936         if ( srcFolder->folderType() == KMFolderTypeImap )
01937         {
01938           // do not complete here but wait until all messages are transferred
01939           mCompleteWithAddedMsg = true;
01940         }
01941         rc = mDestFolder->moveMsg(msg, &index);
01942         if (rc == 0 && index != -1) {
01943           KMMsgBase *mb = mDestFolder->unGetMsg( mDestFolder->count() - 1 );
01944           if (undo && mb)
01945           {
01946             if ( undoId == -1 )
01947               undoId = kmkernel->undoStack()->newUndoAction( srcFolder, mDestFolder );
01948             kmkernel->undoStack()->addMsgToAction( undoId, mb->getMsgSerNum() );
01949           }
01950         } else if (rc != 0) {
01951           // Something  went wrong. Stop processing here, it is likely that the
01952           // other moves would fail as well.
01953           completeMove( Failed );
01954           return Failed;
01955         }
01956       }
01957     } else {
01958       // really delete messages that are already in the trash folder or if
01959       // we are really, really deleting, not just moving to trash
01960       if (srcFolder->folderType() == KMFolderTypeImap) {
01961         if (!folderDeleteList[srcFolder])
01962           folderDeleteList[srcFolder] = new QPtrList<KMMessage>;
01963         folderDeleteList[srcFolder]->append( msg );
01964       } else {
01965         srcFolder->removeMsg(idx);
01966         delete msg;
01967       }
01968     }
01969   }
01970   if (!list.isEmpty() && mDestFolder) {
01971     // will be completed with folderComplete signal
01972     mDestFolder->moveMsg(list, &index);
01973   } else {
01974     FolderToMessageListMap::Iterator it;
01975     for ( it = folderDeleteList.begin(); it != folderDeleteList.end(); ++it ) {
01976       it.key()->removeMsg(*it.data());
01977       delete it.data();
01978     }
01979     if ( !mCompleteWithAddedMsg ) {
01980       // imap folders will be completed in slotMsgAddedToDestFolder
01981       completeMove( OK );
01982     }
01983   }
01984 
01985   return OK;
01986 }
01987 
01988 void KMMoveCommand::slotImapFolderCompleted(KMFolderImap* imapFolder, bool success)
01989 {
01990   disconnect (imapFolder, SIGNAL(folderComplete( KMFolderImap*, bool )),
01991       this, SLOT(slotImapFolderCompleted( KMFolderImap*, bool )));
01992   if ( success ) {
01993     // the folder was checked successfully but we were still called, so check
01994     // if we are still waiting for messages to show up. If so, uidValidity
01995     // changed, or something else went wrong. Clean up.
01996 
01997     /* Unfortunately older UW imap servers change uid validity for each put job.
01998      * Yes, it is really that broken. *sigh* So we cannot report error here, I guess. */
01999     if ( !mLostBoys.isEmpty() ) {
02000       kdDebug(5006) <<  "### Not all moved messages reported back that they were " << endl
02001                     <<  "### added to the target folder. Did uidValidity change? " << endl;
02002     }
02003     completeMove( OK );
02004   } else {
02005     // Should we inform the user here or leave that to the caller?
02006     completeMove( Failed );
02007   }
02008 }
02009 
02010 void KMMoveCommand::slotMsgAddedToDestFolder(KMFolder *folder, Q_UINT32 serNum)
02011 {
02012   if ( folder != mDestFolder || mLostBoys.find( serNum ) == mLostBoys.end() ) {
02013     //kdDebug(5006) << "KMMoveCommand::msgAddedToDestFolder different "
02014     //                 "folder or invalid serial number." << endl;
02015     return;
02016   }
02017   mLostBoys.remove(serNum);
02018   if ( mLostBoys.isEmpty() ) {
02019     // we are done. All messages transferred to the host succesfully
02020     disconnect (mDestFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
02021              this, SLOT(slotMsgAddedToDestFolder(KMFolder*, Q_UINT32)));
02022     if (mDestFolder && mDestFolder->folderType() != KMFolderTypeImap) {
02023       mDestFolder->sync();
02024     }
02025     if ( mCompleteWithAddedMsg ) {
02026       completeMove( OK );
02027     }
02028   } else {
02029     if ( mProgressItem ) {
02030       mProgressItem->incCompletedItems();
02031       mProgressItem->updateProgress();
02032     }
02033   }
02034 }
02035 
02036 void KMMoveCommand::completeMove( Result result )
02037 {
02038   if ( mDestFolder )
02039     mDestFolder->close();
02040   while ( !mOpenedFolders.empty() ) {
02041     KMFolder *folder = mOpenedFolders.back();
02042     mOpenedFolders.pop_back();
02043     folder->close();
02044   }
02045   if ( mProgressItem ) {
02046     mProgressItem->setComplete();
02047     mProgressItem = 0;
02048   }
02049   setResult( result );
02050   emit completed( this );
02051   deleteLater();
02052 }
02053 
02054 void KMMoveCommand::slotMoveCanceled()
02055 {
02056   completeMove( Canceled );
02057 }
02058 
02059 // srcFolder doesn't make much sense for searchFolders
02060 KMDeleteMsgCommand::KMDeleteMsgCommand( KMFolder* srcFolder,
02061   const QPtrList<KMMsgBase> &msgList )
02062 :KMMoveCommand( findTrashFolder( srcFolder ), msgList)
02063 {
02064   srcFolder->open();
02065   mOpenedFolders.push_back( srcFolder );
02066 }
02067 
02068 KMDeleteMsgCommand::KMDeleteMsgCommand( KMFolder* srcFolder, KMMessage * msg )
02069 :KMMoveCommand( findTrashFolder( srcFolder ), msg)
02070 {
02071   srcFolder->open();
02072   mOpenedFolders.push_back( srcFolder );
02073 }
02074 
02075 KMDeleteMsgCommand::KMDeleteMsgCommand( Q_UINT32 sernum )
02076 :KMMoveCommand( sernum )
02077 {
02078   KMFolder *srcFolder = 0;
02079   int idx;
02080   KMMsgDict::instance()->getLocation( sernum, &srcFolder, &idx );
02081   if ( srcFolder ) {
02082     KMMsgBase *msg = srcFolder->getMsgBase( idx );
02083     srcFolder->open();
02084     mOpenedFolders.push_back( srcFolder );
02085     addMsg( msg );
02086   }
02087   setDestFolder( findTrashFolder( srcFolder ) );
02088 }
02089 
02090 KMFolder * KMDeleteMsgCommand::findTrashFolder( KMFolder * folder )
02091 {
02092   KMFolder* trash = folder->trashFolder();
02093   if( !trash )
02094     trash = kmkernel->trashFolder();
02095   if( trash != folder )
02096     return trash;
02097   return 0;
02098 }
02099 
02100 
02101 KMUrlClickedCommand::KMUrlClickedCommand( const KURL &url, uint identity,
02102   KMReaderWin *readerWin, bool htmlPref, KMMainWidget *mainWidget )
02103   :mUrl( url ), mIdentity( identity ), mReaderWin( readerWin ),
02104    mHtmlPref( htmlPref ), mMainWidget( mainWidget )
02105 {
02106 }
02107 
02108 KMCommand::Result KMUrlClickedCommand::execute()
02109 {
02110   KMMessage* msg;
02111 
02112   if (mUrl.protocol() == "mailto")
02113   {
02114     msg = new KMMessage;
02115     msg->initHeader(mIdentity);
02116     msg->setCharset("utf-8");
02117     msg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );
02118     QString query=mUrl.query();
02119     while (!query.isEmpty()) {
02120       QString queryPart;
02121       int secondQuery = query.find('?',1);
02122       if (secondQuery != -1)
02123         queryPart = query.left(secondQuery);
02124       else
02125         queryPart = query;
02126       query = query.mid(queryPart.length());
02127 
02128       if (queryPart.left(9) == "?subject=")
02129         msg->setSubject( KURL::decode_string(queryPart.mid(9)) );
02130       else if (queryPart.left(6) == "?body=")
02131         // It is correct to convert to latin1() as URL should not contain
02132         // anything except ascii.
02133         msg->setBody( KURL::decode_string(queryPart.mid(6)).latin1() );
02134       else if (queryPart.left(4) == "?cc=")
02135         msg->setCc( KURL::decode_string(queryPart.mid(4)) );
02136     }
02137 
02138     KMail::Composer * win = KMail::makeComposer( msg, mIdentity );
02139     win->setCharset("", TRUE);
02140     win->show();
02141   }
02142   else if ( mUrl.protocol() == "im" )
02143   {
02144     kmkernel->imProxy()->chatWithContact( mUrl.path() );
02145   }
02146   else if ((mUrl.protocol() == "http") || (mUrl.protocol() == "https") ||
02147            (mUrl.protocol() == "ftp")  || (mUrl.protocol() == "file")  ||
02148            (mUrl.protocol() == "ftps") || (mUrl.protocol() == "sftp" ) ||
02149            (mUrl.protocol() == "help") || (mUrl.protocol() == "vnc")   ||
02150            (mUrl.protocol() == "smb")  || (mUrl.protocol() == "fish")  ||
02151            (mUrl.protocol() == "news"))
02152   {
02153     KPIM::BroadcastStatus::instance()->setStatusMsg( i18n("Opening URL..."));
02154     KMimeType::Ptr mime = KMimeType::findByURL( mUrl );
02155     if (mime->name() == "application/x-desktop" ||
02156         mime->name() == "application/x-executable" ||
02157         mime->name() == "application/x-msdos-program" ||
02158         mime->name() == "application/x-shellscript" )
02159     {
02160       if (KMessageBox::warningYesNo( 0, i18n( "<qt>Do you really want to execute <b>%1</b>?</qt>" )
02161         .arg( mUrl.prettyURL() ), QString::null, i18n("Execute"), KStdGuiItem::cancel() ) != KMessageBox::Yes)
02162         return Canceled;
02163     }
02164     (void) new KRun( mUrl );
02165   }
02166   else
02167     return Failed;
02168 
02169   return OK;
02170 }
02171 
02172 KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( QWidget *parent, KMMessage *msg )
02173   : KMCommand( parent, msg ), mImplicitAttachments( true ), mEncoded( false )
02174 {
02175 }
02176 
02177 KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( QWidget *parent, const QPtrList<KMMsgBase>& msgs )
02178   : KMCommand( parent, msgs ), mImplicitAttachments( true ), mEncoded( false )
02179 {
02180 }
02181 
02182 KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( QWidget *parent, QPtrList<partNode>& attachments,
02183                                                     KMMessage *msg, bool encoded )
02184   : KMCommand( parent ), mImplicitAttachments( false ), mEncoded( encoded )
02185 {
02186   for ( QPtrListIterator<partNode> it( attachments ); it.current(); ++it ) {
02187     mAttachmentMap.insert( it.current(), msg );
02188   }
02189 }
02190 
02191 KMCommand::Result KMSaveAttachmentsCommand::execute()
02192 {
02193   setEmitsCompletedItself( true );
02194   if ( mImplicitAttachments ) {
02195     QPtrList<KMMessage> msgList = retrievedMsgs();
02196     KMMessage *msg;
02197     for ( QPtrListIterator<KMMessage> itr( msgList );
02198           ( msg = itr.current() );
02199           ++itr ) {
02200       partNode *rootNode = partNode::fromMessage( msg );
02201       for ( partNode *child = rootNode; child;
02202             child = child->firstChild() ) {
02203         for ( partNode *node = child; node; node = node->nextSibling() ) {
02204           if ( node->type() != DwMime::kTypeMultipart )
02205             mAttachmentMap.insert( node, msg );
02206         }
02207       }
02208     }
02209   }
02210   setDeletesItself( true );
02211   // load all parts
02212   KMLoadPartsCommand *command = new KMLoadPartsCommand( mAttachmentMap );
02213   connect( command, SIGNAL( partsRetrieved() ),
02214            this, SLOT( slotSaveAll() ) );
02215   command->start();
02216 
02217   return OK;
02218 }
02219 
02220 void KMSaveAttachmentsCommand::slotSaveAll()
02221 {
02222   // now that all message parts have been retrieved, remove all parts which
02223   // don't represent an attachment if they were not explicitely passed in the
02224   // c'tor
02225   if ( mImplicitAttachments ) {
02226     for ( PartNodeMessageMap::iterator it = mAttachmentMap.begin();
02227           it != mAttachmentMap.end(); ) {
02228       // only body parts which have a filename or a name parameter (except for
02229       // the root node for which name is set to the message's subject) are
02230       // considered attachments
02231       if ( it.key()->msgPart().fileName().stripWhiteSpace().isEmpty() &&
02232            ( it.key()->msgPart().name().stripWhiteSpace().isEmpty() ||
02233              !it.key()->parentNode() ) ) {
02234         PartNodeMessageMap::iterator delIt = it;
02235         ++it;
02236         mAttachmentMap.remove( delIt );
02237       }
02238       else
02239         ++it;
02240     }
02241     if ( mAttachmentMap.isEmpty() ) {
02242       KMessageBox::information( 0, i18n("Found no attachments to save.") );
02243       setResult( OK ); // The user has already been informed.
02244       emit completed( this );
02245       deleteLater();
02246       return;
02247     }
02248   }
02249 
02250   KURL url, dirUrl;
02251   if ( mAttachmentMap.count() > 1 ) {
02252     // get the dir
02253     dirUrl = KDirSelectDialog::selectDirectory( QString::null, false,
02254                                                 parentWidget(),
02255                                                 i18n("Save Attachments To") );
02256     if ( !dirUrl.isValid() ) {
02257       setResult( Canceled );
02258       emit completed( this );
02259       deleteLater();
02260       return;
02261     }
02262 
02263     // we may not get a slash-terminated url out of KDirSelectDialog
02264     dirUrl.adjustPath( 1 );
02265   }
02266   else {
02267     // only one item, get the desired filename
02268     partNode *node = mAttachmentMap.begin().key();
02269     // replace all ':' with '_' because ':' isn't allowed on FAT volumes
02270     QString s =
02271       node->msgPart().fileName().stripWhiteSpace().replace( ':', '_' );
02272     if ( s.isEmpty() )
02273       s = node->msgPart().name().stripWhiteSpace().replace( ':', '_' );
02274     if ( s.isEmpty() )
02275       s = i18n("filename for an unnamed attachment", "attachment.1");
02276     url = KFileDialog::getSaveURL( s, QString::null, parentWidget(),
02277                                    QString::null );
02278     if ( url.isEmpty() ) {
02279       setResult( Canceled );
02280       emit completed( this );
02281       deleteLater();
02282       return;
02283     }
02284   }
02285 
02286   QMap< QString, int > renameNumbering;
02287 
02288   Result globalResult = OK;
02289   int unnamedAtmCount = 0;
02290   for ( PartNodeMessageMap::const_iterator it = mAttachmentMap.begin();
02291         it != mAttachmentMap.end();
02292         ++it ) {
02293     KURL curUrl;
02294     if ( !dirUrl.isEmpty() ) {
02295       curUrl = dirUrl;
02296       QString s =
02297         it.key()->msgPart().fileName().stripWhiteSpace().replace( ':', '_' );
02298       if ( s.isEmpty() )
02299         s = it.key()->msgPart().name().stripWhiteSpace().replace( ':', '_' );
02300       if ( s.isEmpty() ) {
02301         ++unnamedAtmCount;
02302         s = i18n("filename for the %1-th unnamed attachment",
02303                  "attachment.%1")
02304             .arg( unnamedAtmCount );
02305       }
02306       curUrl.setFileName( s );
02307     } else {
02308       curUrl = url;
02309     }
02310 
02311     if ( !curUrl.isEmpty() ) {
02312 
02313      // Rename the file if we have already saved one with the same name:
02314      // try appending a number before extension (e.g. "pic.jpg" => "pic_2.jpg")
02315      QString origFile = curUrl.fileName();
02316      QString file = origFile;
02317 
02318      while ( renameNumbering.contains(file) ) {
02319        file = origFile;
02320        int num = renameNumbering[file] + 1;
02321        int dotIdx = file.findRev('.');
02322        file = file.insert( (dotIdx>=0) ? dotIdx : file.length(), QString("_") + QString::number(num) );
02323      }
02324      curUrl.setFileName(file);
02325 
02326      // Increment the counter for both the old and the new filename
02327      if ( !renameNumbering.contains(origFile))
02328          renameNumbering[origFile] = 1;
02329      else
02330          renameNumbering[origFile]++;
02331 
02332      if ( file != origFile ) {
02333         if ( !renameNumbering.contains(file))
02334             renameNumbering[file] = 1;
02335         else
02336             renameNumbering[file]++;
02337      }
02338 
02339 
02340       if ( KIO::NetAccess::exists( curUrl, false, parentWidget() ) ) {
02341         if ( KMessageBox::warningContinueCancel( parentWidget(),
02342               i18n( "A file named %1 already exists. Do you want to overwrite it?" )
02343               .arg( curUrl.fileName() ),
02344               i18n( "File Already Exists" ), i18n("&Overwrite") ) == KMessageBox::Cancel) {
02345           continue;
02346         }
02347       }
02348       // save
02349       const Result result = saveItem( it.key(), curUrl );
02350       if ( result != OK )
02351         globalResult = result;
02352     }
02353   }
02354   setResult( globalResult );
02355   emit completed( this );
02356   deleteLater();
02357 }
02358 
02359 KMCommand::Result KMSaveAttachmentsCommand::saveItem( partNode *node,
02360                                                       const KURL& url )
02361 {
02362   bool bSaveEncrypted = false;
02363   bool bEncryptedParts = node->encryptionState() != KMMsgNotEncrypted;
02364   if( bEncryptedParts )
02365     if( KMessageBox::questionYesNo( parentWidget(),
02366           i18n( "The part %1 of the message is encrypted. Do you want to keep the encryption when saving?" ).
02367           arg( url.fileName() ),
02368           i18n( "KMail Question" ), i18n("Keep Encryption"), i18n("Do Not Keep") ) ==
02369         KMessageBox::Yes )
02370       bSaveEncrypted = true;
02371 
02372   bool bSaveWithSig = true;
02373   if( node->signatureState() != KMMsgNotSigned )
02374     if( KMessageBox::questionYesNo( parentWidget(),
02375           i18n( "The part %1 of the message is signed. Do you want to keep the signature when saving?" ).
02376           arg( url.fileName() ),
02377           i18n( "KMail Question" ), i18n("Keep Signature"), i18n("Do Not Keep") ) !=
02378         KMessageBox::Yes )
02379       bSaveWithSig = false;
02380 
02381   QByteArray data;
02382   if ( mEncoded )
02383   {
02384     // This does not decode the Message Content-Transfer-Encoding
02385     // but saves the _original_ content of the message part
02386     QCString cstr( node->msgPart().body() );
02387     data = cstr;
02388     data.resize(data.size() - 1);
02389   }
02390   else
02391   {
02392     if( bSaveEncrypted || !bEncryptedParts) {
02393       partNode *dataNode = node;
02394       QCString rawReplyString;
02395       bool gotRawReplyString = false;
02396       if( !bSaveWithSig ) {
02397         if( DwMime::kTypeMultipart == node->type() &&
02398             DwMime::kSubtypeSigned == node->subType() ){
02399           // carefully look for the part that is *not* the signature part:
02400           if( node->findType( DwMime::kTypeApplication,
02401                 DwMime::kSubtypePgpSignature,
02402                 TRUE, false ) ){
02403             dataNode = node->findTypeNot( DwMime::kTypeApplication,
02404                 DwMime::kSubtypePgpSignature,
02405                 TRUE, false );
02406           }else if( node->findType( DwMime::kTypeApplication,
02407                 DwMime::kSubtypePkcs7Mime,
02408                 TRUE, false ) ){
02409             dataNode = node->findTypeNot( DwMime::kTypeApplication,
02410                 DwMime::kSubtypePkcs7Mime,
02411                 TRUE, false );
02412           }else{
02413             dataNode = node->findTypeNot( DwMime::kTypeMultipart,
02414                 DwMime::kSubtypeUnknown,
02415                 TRUE, false );
02416           }
02417     }else{
02418       ObjectTreeParser otp( 0, 0, false, false, false );
02419 
02420       // process this node and all it's siblings and descendants
02421       dataNode->setProcessed( false, true );
02422       otp.parseObjectTree( dataNode );
02423 
02424       rawReplyString = otp.rawReplyString();
02425       gotRawReplyString = true;
02426         }
02427       }
02428       QByteArray cstr = gotRawReplyString
02429                          ? rawReplyString
02430                          : dataNode->msgPart().bodyDecodedBinary();
02431       data = cstr;
02432       size_t size = cstr.size();
02433       if ( dataNode->msgPart().type() == DwMime::kTypeText ) {
02434         // convert CRLF to LF before writing text attachments to disk
02435         size = KMail::Util::crlf2lf( cstr.data(), size );
02436       }
02437       data.resize( size );
02438     }
02439   }
02440   QDataStream ds;
02441   QFile file;
02442   KTempFile tf;
02443   tf.setAutoDelete( true );
02444   if ( url.isLocalFile() )
02445   {
02446     // save directly
02447     file.setName( url.path() );
02448     if ( !file.open( IO_WriteOnly ) )
02449     {
02450       KMessageBox::error( parentWidget(),
02451           i18n( "%2 is detailed error description",
02452             "Could not write the file %1:\n%2" )
02453           .arg( file.name() )
02454           .arg( QString::fromLocal8Bit( strerror( errno ) ) ),
02455           i18n( "KMail Error" ) );
02456       return Failed;
02457     }
02458     fchmod( file.handle(), S_IRUSR | S_IWUSR );
02459     ds.setDevice( &file );
02460   } else
02461   {
02462     // tmp file for upload
02463     ds.setDevice( tf.file() );
02464   }
02465 
02466   ds.writeRawBytes( data.data(), data.size() );
02467   if ( !url.isLocalFile() )
02468   {
02469     tf.close();
02470     if ( !KIO::NetAccess::upload( tf.name(), url, parentWidget() ) )
02471     {
02472       KMessageBox::error( parentWidget(),
02473           i18n( "Could not write the file %1." )
02474           .arg( url.path() ),
02475           i18n( "KMail Error" ) );
02476       return Failed;
02477     }
02478   } else
02479     file.close();
02480   return OK;
02481 }
02482 
02483 KMLoadPartsCommand::KMLoadPartsCommand( QPtrList<partNode>& parts, KMMessage *msg )
02484   : mNeedsRetrieval( 0 )
02485 {
02486   for ( QPtrListIterator<partNode> it( parts ); it.current(); ++it ) {
02487     mPartMap.insert( it.current(), msg );
02488   }
02489 }
02490 
02491 KMLoadPartsCommand::KMLoadPartsCommand( partNode *node, KMMessage *msg )
02492   : mNeedsRetrieval( 0 )
02493 {
02494   mPartMap.insert( node, msg );
02495 }
02496 
02497 KMLoadPartsCommand::KMLoadPartsCommand( PartNodeMessageMap& partMap )
02498   : mNeedsRetrieval( 0 ), mPartMap( partMap )
02499 {
02500 }
02501 
02502 void KMLoadPartsCommand::slotStart()
02503 {
02504   for ( PartNodeMessageMap::const_iterator it = mPartMap.begin();
02505         it != mPartMap.end();
02506         ++it ) {
02507     if ( !it.key()->msgPart().isComplete() &&
02508          !it.key()->msgPart().partSpecifier().isEmpty() ) {
02509       // incomplete part, so retrieve it first
02510       ++mNeedsRetrieval;
02511       KMFolder* curFolder = it.data()->parent();
02512       if ( curFolder ) {
02513         FolderJob *job =
02514           curFolder->createJob( it.data(), FolderJob::tGetMessage,
02515                                 0, it.key()->msgPart().partSpecifier() );
02516         job->setCancellable( false );
02517         connect( job, SIGNAL(messageUpdated(KMMessage*, QString)),
02518                  this, SLOT(slotPartRetrieved(KMMessage*, QString)) );
02519         job->start();
02520       } else
02521         kdWarning(5006) << "KMLoadPartsCommand - msg has no parent" << endl;
02522     }
02523   }
02524   if ( mNeedsRetrieval == 0 )
02525     execute();
02526 }
02527 
02528 void KMLoadPartsCommand::slotPartRetrieved( KMMessage *msg,
02529                                             QString partSpecifier )
02530 {
02531   DwBodyPart *part =
02532     msg->findDwBodyPart( msg->getFirstDwBodyPart(), partSpecifier );
02533   if ( part ) {
02534     // update the DwBodyPart in the partNode
02535     for ( PartNodeMessageMap::const_iterator it = mPartMap.begin();
02536           it != mPartMap.end();
02537           ++it ) {
02538       if ( it.key()->dwPart()->partId() == part->partId() )
02539         it.key()->setDwPart( part );
02540     }
02541   } else
02542     kdWarning(5006) << "KMLoadPartsCommand::slotPartRetrieved - could not find bodypart!" << endl;
02543   --mNeedsRetrieval;
02544   if ( mNeedsRetrieval == 0 )
02545     execute();
02546 }
02547 
02548 KMCommand::Result KMLoadPartsCommand::execute()
02549 {
02550   emit partsRetrieved();
02551   setResult( OK );
02552   emit completed( this );
02553   deleteLater();
02554   return OK;
02555 }
02556 
02557 KMResendMessageCommand::KMResendMessageCommand( QWidget *parent,
02558    KMMessage *msg )
02559   :KMCommand( parent, msg )
02560 {
02561 }
02562 
02563 KMCommand::Result KMResendMessageCommand::execute()
02564 {
02565   KMMessage *msg = retrievedMessage();
02566 
02567   KMMessage *newMsg = new KMMessage(*msg);
02568   newMsg->setCharset(msg->codec()->mimeName());
02569   // the message needs a new Message-Id
02570   newMsg->removeHeaderField( "Message-Id" );
02571   newMsg->setParent( 0 );
02572 
02573   // adds the new date to the message
02574   newMsg->removeHeaderField( "Date" );
02575 
02576   KMail::Composer * win = KMail::makeComposer();
02577   win->setMsg(newMsg, false, true);
02578   win->show();
02579 
02580   return OK;
02581 }
02582 
02583 KMMailingListCommand::KMMailingListCommand( QWidget *parent, KMFolder *folder )
02584   : KMCommand( parent ), mFolder( folder )
02585 {
02586 }
02587 
02588 KMCommand::Result KMMailingListCommand::execute()
02589 {
02590   KURL::List lst = urls();
02591   QString handler = ( mFolder->mailingList().handler() == MailingList::KMail )
02592     ? "mailto" : "https";
02593 
02594   KMCommand *command = 0;
02595   for ( KURL::List::Iterator itr = lst.begin(); itr != lst.end(); ++itr ) {
02596     if ( handler == (*itr).protocol() ) {
02597       command = new KMUrlClickedCommand( *itr, mFolder->identity(), 0, false );
02598     }
02599   }
02600   if ( !command && !lst.empty() ) {
02601     command =
02602       new KMUrlClickedCommand( lst.first(), mFolder->identity(), 0, false );
02603   }
02604   if ( command ) {
02605     connect( command, SIGNAL( completed( KMCommand * ) ),
02606              this, SLOT( commandCompleted( KMCommand * ) ) );
02607     setDeletesItself( true );
02608     setEmitsCompletedItself( true );
02609     command->start();
02610     return OK;
02611   }
02612   return Failed;
02613 }
02614 
02615 void KMMailingListCommand::commandCompleted( KMCommand *command )
02616 {
02617   setResult( command->result() );
02618   emit completed( this );
02619   deleteLater();
02620 }
02621 
02622 KMMailingListPostCommand::KMMailingListPostCommand( QWidget *parent, KMFolder *folder )
02623   : KMMailingListCommand( parent, folder )
02624 {
02625 }
02626 KURL::List KMMailingListPostCommand::urls() const
02627 {
02628   return mFolder->mailingList().postURLS();
02629 }
02630 
02631 KMMailingListSubscribeCommand::KMMailingListSubscribeCommand( QWidget *parent, KMFolder *folder )
02632   : KMMailingListCommand( parent, folder )
02633 {
02634 }
02635 KURL::List KMMailingListSubscribeCommand::urls() const
02636 {
02637   return mFolder->mailingList().subscribeURLS();
02638 }
02639 
02640 KMMailingListUnsubscribeCommand::KMMailingListUnsubscribeCommand( QWidget *parent, KMFolder *folder )
02641   : KMMailingListCommand( parent, folder )
02642 {
02643 }
02644 KURL::List KMMailingListUnsubscribeCommand::urls() const
02645 {
02646   return mFolder->mailingList().unsubscribeURLS();
02647 }
02648 
02649 KMMailingListArchivesCommand::KMMailingListArchivesCommand( QWidget *parent, KMFolder *folder )
02650   : KMMailingListCommand( parent, folder )
02651 {
02652 }
02653 KURL::List KMMailingListArchivesCommand::urls() const
02654 {
02655   return mFolder->mailingList().archiveURLS();
02656 }
02657 
02658 KMMailingListHelpCommand::KMMailingListHelpCommand( QWidget *parent, KMFolder *folder )
02659   : KMMailingListCommand( parent, folder )
02660 {
02661 }
02662 KURL::List KMMailingListHelpCommand::urls() const
02663 {
02664   return mFolder->mailingList().helpURLS();
02665 }
02666 
02667 KMIMChatCommand::KMIMChatCommand( const KURL &url, KMMessage *msg )
02668   :mUrl( url ), mMessage( msg )
02669 {
02670 }
02671 
02672 KMCommand::Result KMIMChatCommand::execute()
02673 {
02674   kdDebug( 5006 ) << k_funcinfo << " URL is: " << mUrl << endl;
02675   QString addr = KMMessage::decodeMailtoUrl( mUrl.path() );
02676   // find UID for mail address
02677   KABC::AddressBook *addressBook = KABC::StdAddressBook::self( true );
02678   KABC::AddresseeList addressees = addressBook->findByEmail( KPIM::getEmailAddress( addr ) ) ;
02679 
02680   // start chat
02681   if( addressees.count() == 1 ) {
02682     kmkernel->imProxy()->chatWithContact( addressees[0].uid() );
02683     return OK;
02684   }
02685   else
02686   {
02687     kdDebug( 5006 ) << "Didn't find exactly one addressee, couldn't tell who to chat to for that email address.  Count = " << addressees.count() << endl;
02688 
02689     QString apology;
02690     if ( addressees.isEmpty() )
02691       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." );
02692     else
02693     {
02694       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." );
02695       QStringList nameList;
02696       KABC::AddresseeList::const_iterator it = addressees.begin();
02697       KABC::AddresseeList::const_iterator end = addressees.end();
02698       for ( ; it != end; ++it )
02699       {
02700           nameList.append( (*it).realName() );
02701       }
02702       QString names = nameList.join( QString::fromLatin1( ",\n" ) );
02703       apology = apology.arg( names );
02704     }
02705 
02706     KMessageBox::sorry( parentWidget(), apology );
02707     return Failed;
02708   }
02709 }
02710 
02711 KMHandleAttachmentCommand::KMHandleAttachmentCommand( partNode* node,
02712      KMMessage* msg, int atmId, const QString& atmName,
02713      AttachmentAction action, KService::Ptr offer, QWidget* parent )
02714 : KMCommand( parent ), mNode( node ), mMsg( msg ), mAtmId( atmId ), mAtmName( atmName ),
02715   mAction( action ), mOffer( offer ), mJob( 0 )
02716 {
02717 }
02718 
02719 void KMHandleAttachmentCommand::slotStart()
02720 {
02721   if ( !mNode->msgPart().isComplete() )
02722   {
02723     // load the part
02724     kdDebug(5006) << "load part" << endl;
02725     KMLoadPartsCommand *command = new KMLoadPartsCommand( mNode, mMsg );
02726     connect( command, SIGNAL( partsRetrieved() ),
02727         this, SLOT( slotPartComplete() ) );
02728     command->start();
02729   } else
02730   {
02731     execute();
02732   }
02733 }
02734 
02735 void KMHandleAttachmentCommand::slotPartComplete()
02736 {
02737   execute();
02738 }
02739 
02740 KMCommand::Result KMHandleAttachmentCommand::execute()
02741 {
02742   switch( mAction )
02743   {
02744     case Open:
02745       atmOpen();
02746       break;
02747     case OpenWith:
02748       atmOpenWith();
02749       break;
02750     case View:
02751       atmView();
02752       break;
02753     case Save:
02754       atmSave();
02755       break;
02756     case Properties:
02757       atmProperties();
02758       break;
02759     case ChiasmusEncrypt:
02760       atmEncryptWithChiasmus();
02761       return Undefined;
02762       break;
02763     default:
02764       kdDebug(5006) << "unknown action " << mAction << endl;
02765       break;
02766   }
02767   setResult( OK );
02768   emit completed( this );
02769   deleteLater();
02770   return OK;
02771 }
02772 
02773 QString KMHandleAttachmentCommand::createAtmFileLink() const
02774 {
02775   QFileInfo atmFileInfo( mAtmName );
02776 
02777   if ( atmFileInfo.size() == 0 )
02778   {
02779     kdDebug(5006) << k_funcinfo << "rewriting attachment" << endl;
02780     // there is something wrong so write the file again
02781     QByteArray data = mNode->msgPart().bodyDecodedBinary();
02782     size_t size = data.size();
02783     if ( mNode->msgPart().type() == DwMime::kTypeText && size) {
02784       // convert CRLF to LF before writing text attachments to disk
02785       size = KMail::Util::crlf2lf( data.data(), size );
02786     }
02787     KPIM::kBytesToFile( data.data(), size, mAtmName, false, false, false );
02788   }
02789 
02790   KTempFile *linkFile = new KTempFile( locateLocal("tmp", atmFileInfo.fileName() +"_["),
02791                           "]."+ atmFileInfo.extension() );
02792 
02793   linkFile->setAutoDelete(true);
02794   QString linkName = linkFile->name();
02795   delete linkFile;
02796 
02797   if ( ::link(QFile::encodeName( mAtmName ), QFile::encodeName( linkName )) == 0 ) {
02798     return linkName; // success
02799   }
02800   return QString::null;
02801 }
02802 
02803 KService::Ptr KMHandleAttachmentCommand::getServiceOffer()
02804 {
02805   KMMessagePart& msgPart = mNode->msgPart();
02806   const QString contentTypeStr =
02807     ( msgPart.typeStr() + '/' + msgPart.subtypeStr() ).lower();
02808 
02809   if ( contentTypeStr == "text/x-vcard" ) {
02810     atmView();
02811     return 0;
02812   }
02813   // determine the MIME type of the attachment
02814   KMimeType::Ptr mimetype;
02815   // prefer the value of the Content-Type header
02816   mimetype = KMimeType::mimeType( contentTypeStr );
02817   if ( mimetype->name() == "application/octet-stream" ) {
02818     // consider the filename if Content-Type is application/octet-stream
02819     mimetype = KMimeType::findByPath( mAtmName, 0, true /* no disk access */ );
02820   }
02821   if ( ( mimetype->name() == "application/octet-stream" )
02822        && msgPart.isComplete() ) {
02823     // consider the attachment's contents if neither the Content-Type header
02824     // nor the filename give us a clue
02825     mimetype = KMimeType::findByFileContent( mAtmName );
02826   }
02827   return KServiceTypeProfile::preferredService( mimetype->name(), "Application" );
02828 }
02829 
02830 void KMHandleAttachmentCommand::atmOpen()
02831 {
02832   if ( !mOffer )
02833     mOffer = getServiceOffer();
02834   if ( !mOffer ) {
02835     kdDebug(5006) << k_funcinfo << "got no offer" << endl;
02836     return;
02837   }
02838 
02839   KURL::List lst;
02840   KURL url;
02841   bool autoDelete = true;
02842   QString fname = createAtmFileLink();
02843 
02844   if ( fname.isNull() ) {
02845     autoDelete = false;
02846     fname = mAtmName;
02847   }
02848 
02849   url.setPath( fname );
02850   lst.append( url );
02851   if ( (KRun::run( *mOffer, lst, autoDelete ) <= 0) && autoDelete ) {
02852       QFile::remove(url.path());
02853   }
02854 }
02855 
02856 void KMHandleAttachmentCommand::atmOpenWith()
02857 {
02858   KURL::List lst;
02859   KURL url;
02860   bool autoDelete = true;
02861   QString fname = createAtmFileLink();
02862 
02863   if ( fname.isNull() ) {
02864     autoDelete = false;
02865     fname = mAtmName;
02866   }
02867 
02868   url.setPath( fname );
02869   lst.append( url );
02870   if ( (! KRun::displayOpenWithDialog(lst, autoDelete)) && autoDelete ) {
02871     QFile::remove( url.path() );
02872   }
02873 }
02874 
02875 void KMHandleAttachmentCommand::atmView()
02876 {
02877   // we do not handle this ourself
02878   emit showAttachment( mAtmId, mAtmName );
02879 }
02880 
02881 void KMHandleAttachmentCommand::atmSave()
02882 {
02883   QPtrList<partNode> parts;
02884   parts.append( mNode );
02885   // save, do not leave encoded
02886   KMSaveAttachmentsCommand *command =
02887     new KMSaveAttachmentsCommand( 0, parts, mMsg, false );
02888   command->start();
02889 }
02890 
02891 void KMHandleAttachmentCommand::atmProperties()
02892 {
02893   KMMsgPartDialogCompat dlg( 0, true );
02894   KMMessagePart& msgPart = mNode->msgPart();
02895   dlg.setMsgPart( &msgPart );
02896   dlg.exec();
02897 }
02898 
02899 void KMHandleAttachmentCommand::atmEncryptWithChiasmus()
02900 {
02901   const partNode * node = mNode;
02902   Q_ASSERT( node );
02903   if ( !node )
02904     return;
02905 
02906   // FIXME: better detection of mimetype??
02907   if ( !mAtmName.endsWith( ".xia", false ) )
02908     return;
02909 
02910   const Kleo::CryptoBackend::Protocol * chiasmus =
02911     Kleo::CryptoBackendFactory::instance()->protocol( "Chiasmus" );
02912   Q_ASSERT( chiasmus );
02913   if ( !chiasmus )
02914     return;
02915 
02916   const STD_NAMESPACE_PREFIX auto_ptr<Kleo::SpecialJob> listjob( chiasmus->specialJob( "x-obtain-keys", QMap<QString,QVariant>() ) );
02917   if ( !listjob.get() ) {
02918     const QString msg = i18n( "Chiasmus backend does not offer the "
02919                               "\"x-obtain-keys\" function. Please report this bug." );
02920     KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) );
02921     return;
02922   }
02923 
02924   if ( listjob->exec() ) {
02925     listjob->showErrorDialog( parentWidget(), i18n( "Chiasmus Backend Error" ) );
02926     return;
02927   }
02928 
02929   const QVariant result = listjob->property( "result" );
02930   if ( result.type() != QVariant::StringList ) {
02931     const QString msg = i18n( "Unexpected return value from Chiasmus backend: "
02932                               "The \"x-obtain-keys\" function did not return a "
02933                               "string list. Please report this bug." );
02934     KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) );
02935     return;
02936   }
02937 
02938   const QStringList keys = result.toStringList();
02939   if ( keys.empty() ) {
02940     const QString msg = i18n( "No keys have been found. Please check that a "
02941                               "valid key path has been set in the Chiasmus "
02942                               "configuration." );
02943     KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) );
02944     return;
02945   }
02946 
02947   ChiasmusKeySelector selectorDlg( parentWidget(), i18n( "Chiasmus Decryption Key Selection" ),
02948                                    keys, GlobalSettings::chiasmusDecryptionKey(),
02949                                    GlobalSettings::chiasmusDecryptionOptions() );
02950   if ( selectorDlg.exec() != QDialog::Accepted )
02951     return;
02952 
02953   GlobalSettings::setChiasmusDecryptionOptions( selectorDlg.options() );
02954   GlobalSettings::setChiasmusDecryptionKey( selectorDlg.key() );
02955   assert( !GlobalSettings::chiasmusDecryptionKey().isEmpty() );
02956 
02957   Kleo::SpecialJob * job = chiasmus->specialJob( "x-decrypt", QMap<QString,QVariant>() );
02958   if ( !job ) {
02959     const QString msg = i18n( "Chiasmus backend does not offer the "
02960                               "\"x-decrypt\" function. Please report this bug." );
02961     KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) );
02962     return;
02963   }
02964 
02965   const QByteArray input = node->msgPart().bodyDecodedBinary();
02966 
02967   if ( !job->setProperty( "key", GlobalSettings::chiasmusDecryptionKey() ) ||
02968        !job->setProperty( "options", GlobalSettings::chiasmusDecryptionOptions() ) ||
02969        !job->setProperty( "input", input ) ) {
02970     const QString msg = i18n( "The \"x-decrypt\" function does not accept "
02971                               "the expected parameters. Please report this bug." );
02972     KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) );
02973     return;
02974   }
02975 
02976   setDeletesItself( true ); // the job below is async, we have to cleanup ourselves
02977   if ( job->start() ) {
02978     job->showErrorDialog( parentWidget(), i18n( "Chiasmus Decryption Error" ) );
02979     return;
02980   }
02981 
02982   mJob = job;
02983   connect( job, SIGNAL(result(const GpgME::Error&,const QVariant&)),
02984            this, SLOT(slotAtmDecryptWithChiasmusResult(const GpgME::Error&,const QVariant&)) );
02985 }
02986 
02987 // return true if we should proceed, false if we should abort
02988 static bool checkOverwrite( const KURL& url, bool& overwrite, QWidget* w )
02989 {
02990   if ( KIO::NetAccess::exists( url, false /*dest*/, w ) ) {
02991     if ( KMessageBox::Cancel ==
02992          KMessageBox::warningContinueCancel(
02993                                             w,
02994                                             i18n( "A file named \"%1\" already exists. "
02995                                                   "Are you sure you want to overwrite it?" ).arg( url.prettyURL() ),
02996                                             i18n( "Overwrite File?" ),
02997                                             i18n( "&Overwrite" ) ) )
02998       return false;
02999     overwrite = true;
03000   }
03001   return true;
03002 }
03003 
03004 static const QString chomp( const QString & base, const QString & suffix, bool cs ) {
03005   return base.endsWith( suffix, cs ) ? base.left( base.length() - suffix.length() ) : base ;
03006 }
03007 
03008 void KMHandleAttachmentCommand::slotAtmDecryptWithChiasmusResult( const GpgME::Error & err, const QVariant & result )
03009 {
03010   LaterDeleterWithCommandCompletion d( this );
03011   if ( !mJob )
03012     return;
03013   Q_ASSERT( mJob == sender() );
03014   if ( mJob != sender() )
03015     return;
03016   Kleo::Job * job = mJob;
03017   mJob = 0;
03018   if ( err.isCanceled() )
03019     return;
03020   if ( err ) {
03021     job->showErrorDialog( parentWidget(), i18n( "Chiasmus Decryption Error" ) );
03022     return;
03023   }
03024 
03025   if ( result.type() != QVariant::ByteArray ) {
03026     const QString msg = i18n( "Unexpected return value from Chiasmus backend: "
03027                               "The \"x-decrypt\" function did not return a "
03028                               "byte array. Please report this bug." );
03029     KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) );
03030     return;
03031   }
03032 
03033   const KURL url = KFileDialog::getSaveURL( chomp( mAtmName, ".xia", false ), QString::null, parentWidget() );
03034   if ( url.isEmpty() )
03035     return;
03036 
03037   bool overwrite = false;
03038   if ( !checkOverwrite( url, overwrite, parentWidget() ) )
03039     return;
03040 
03041   d.setDisabled( true ); // we got this far, don't delete yet
03042   KIO::Job * uploadJob = KIO::storedPut( result.toByteArray(), url, -1, overwrite, false /*resume*/ );
03043   uploadJob->setWindow( parentWidget() );
03044   connect( uploadJob, SIGNAL(result(KIO::Job*)),
03045            this, SLOT(slotAtmDecryptWithChiasmusUploadResult(KIO::Job*)) );
03046 }
03047 
03048 void KMHandleAttachmentCommand::slotAtmDecryptWithChiasmusUploadResult( KIO::Job * job )
03049 {
03050   if ( job->error() )
03051     job->showErrorDialog();
03052   LaterDeleterWithCommandCompletion d( this );
03053   d.setResult( OK );
03054 }
03055 
03056 #include "kmcommands.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys