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