kmail Library API Documentation

kmcommands.cpp

00001 // -*- mode: C++; c-file-style: "gnu" -*-
00002 // kmcommands
00003 // (c) 2002 Don Sanders <sanders@kde.org>
00004 // License: GPL
00005 //
00006 // This file implements various "command" classes. These command classes
00007 // are based on the command design pattern.
00008 //
00009 // Historically various operations were implemented as slots of KMMainWin.
00010 // This proved inadequate as KMail has multiple top level windows
00011 // (KMMainWin, KMReaderMainWin, KMFldSearch, KMComposeWin) that may
00012 // benefit from using these operations. It is desirable that these
00013 // classes can operate without depending on or altering the state of
00014 // a KMMainWin, in fact it is possible no KMMainWin object even exists.
00015 //
00016 // Now these operations have been rewritten as KMCommand based classes,
00017 // making them independent of KMMainWin.
00018 //
00019 // The base command class KMCommand is async, which is a difference
00020 // from the conventional command pattern. As normal derived classes implement
00021 // the execute method, but client classes call start() instead of
00022 // calling execute() directly. start() initiates async operations,
00023 // and on completion of these operations calls execute() and then deletes
00024 // the command. (So the client must not construct commands on the stack).
00025 //
00026 // The type of async operation supported by KMCommand is retrieval
00027 // of messages from an IMAP server.
00028 
00029 #include "kmcommands.h"
00030 
00031 #ifdef HAVE_CONFIG_H
00032 #include <config.h>
00033 #endif
00034 
00035 #include <errno.h>
00036 #include <mimelib/enum.h>
00037 #include <mimelib/field.h>
00038 #include <mimelib/mimepp.h>
00039 #include <mimelib/string.h>
00040 #include <kapplication.h>
00041 #include <dcopclient.h>
00042 
00043 #include <qtextcodec.h>
00044 
00045 #include <libkdepim/email.h>
00046 #include <kdebug.h>
00047 #include <kfiledialog.h>
00048 #include <kio/netaccess.h>
00049 #include <kabc/stdaddressbook.h>
00050 #include <kabc/addresseelist.h>
00051 #include <klocale.h>
00052 #include <kmessagebox.h>
00053 #include <kparts/browserextension.h>
00054 #include <kprogress.h>
00055 #include <krun.h>
00056 #include <kbookmarkmanager.h>
00057 #include <kstandarddirs.h>
00058 #include <ktempfile.h>
00059 #include "actionscheduler.h"
00060 using KMail::ActionScheduler;
00061 #include "mailinglist-magic.h"
00062 #include "kmaddrbook.h"
00063 #include <kaddrbook.h>
00064 #include "kmcomposewin.h"
00065 #include "kmfiltermgr.h"
00066 #include "kmfoldermbox.h"
00067 #include "kmfolderimap.h"
00068 #include "kmfoldermgr.h"
00069 #include "kmheaders.h"
00070 #include "kmmainwidget.h"
00071 #include "kmmsgdict.h"
00072 #include "kmsender.h"
00073 #include "undostack.h"
00074 #include "kcursorsaver.h"
00075 #include "partNode.h"
00076 #include "objecttreeparser.h"
00077 using KMail::ObjectTreeParser;
00078 using KMail::FolderJob;
00079 #include "mailsourceviewer.h"
00080 using KMail::MailSourceViewer;
00081 #include "kmreadermainwin.h"
00082 #include "secondarywindow.h"
00083 using KMail::SecondaryWindow;
00084 #include "kimproxy.h"
00085 
00086 #include "progressmanager.h"
00087 using KPIM::ProgressManager;
00088 using KPIM::ProgressItem;
00089 
00090 #include "broadcaststatus.h"
00091 
00092 #include "kmcommands.moc"
00093 
00094 KMCommand::KMCommand( QWidget *parent )
00095   : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
00096     mEmitsCompletedItself( false ), mParent( parent )
00097 {
00098 }
00099 
00100 KMCommand::KMCommand( QWidget *parent, const QPtrList<KMMsgBase> &msgList )
00101   : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
00102     mEmitsCompletedItself( false ), mParent( parent ), mMsgList( msgList )
00103 {
00104 }
00105 
00106 KMCommand::KMCommand( QWidget *parent, KMMsgBase *msgBase )
00107   : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
00108     mEmitsCompletedItself( false ), mParent( parent )
00109 {
00110   mMsgList.append( msgBase );
00111 }
00112 
00113 KMCommand::KMCommand( QWidget *parent, KMMessage *msg )
00114   : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
00115     mEmitsCompletedItself( false ), mParent( parent )
00116 {
00117   mMsgList.append( &msg->toMsgBase() );
00118 }
00119 
00120 KMCommand::~KMCommand()
00121 {
00122   QValueListIterator<QGuardedPtr<KMFolder> > fit;
00123   for ( fit = mFolders.begin(); fit != mFolders.end(); ++fit ) {
00124     if (!(*fit))
00125       continue;
00126     (*fit)->close();
00127   }
00128 }
00129 
00130 KMCommand::Result KMCommand::result()
00131 {
00132   if ( mResult == Undefined )
00133     kdDebug(5006) << k_funcinfo << "mResult is Undefined" << endl;
00134   return mResult;
00135 }
00136 
00137 void KMCommand::start()
00138 {
00139   QTimer::singleShot( 0, this, SLOT( slotStart() ) );
00140 }
00141 
00142 
00143 const QPtrList<KMMessage> KMCommand::retrievedMsgs() const
00144 {
00145   return mRetrievedMsgs;
00146 }
00147 
00148 KMMessage *KMCommand::retrievedMessage() const
00149 {
00150   return mRetrievedMsgs.getFirst();
00151 }
00152 
00153 QWidget *KMCommand::parentWidget() const
00154 {
00155   return mParent;
00156 }
00157 
00158 int KMCommand::mCountJobs = 0;
00159 
00160 void KMCommand::slotStart()
00161 {
00162   connect( this, SIGNAL( messagesTransfered( KMCommand::Result ) ),
00163            this, SLOT( slotPostTransfer( KMCommand::Result ) ) );
00164   kmkernel->filterMgr()->ref();
00165 
00166   if (mMsgList.find(0) != -1) {
00167       emit messagesTransfered( Failed );
00168       return;
00169   }
00170 
00171   if ((mMsgList.count() == 1) &&
00172       (mMsgList.getFirst()->isMessage()) &&
00173       (mMsgList.getFirst()->parent() == 0))
00174   {
00175     // Special case of operating on message that isn't in a folder
00176     mRetrievedMsgs.append((KMMessage*)mMsgList.getFirst());
00177     emit messagesTransfered( OK );
00178     return;
00179   }
00180 
00181   for (KMMsgBase *mb = mMsgList.first(); mb; mb = mMsgList.next())
00182     if (!mb->parent()) {
00183       emit messagesTransfered( Failed );
00184       return;
00185     } else {
00186       mFolders.append( mb->parent() );
00187       mb->parent()->open();
00188     }
00189 
00190   // transfer the selected messages first
00191   transferSelectedMsgs();
00192 }
00193 
00194 void KMCommand::slotPostTransfer( KMCommand::Result result )
00195 {
00196   disconnect( this, SIGNAL( messagesTransfered( KMCommand::Result ) ),
00197               this, SLOT( slotPostTransfer( KMCommand::Result ) ) );
00198   if ( result == OK )
00199     result = execute();
00200   mResult = result;
00201   QPtrListIterator<KMMessage> it( mRetrievedMsgs );
00202   KMMessage* msg;
00203   while ( (msg = it.current()) != 0 )
00204   {
00205     ++it;
00206     if (msg->parent())
00207       msg->setTransferInProgress(false);
00208   }
00209   kmkernel->filterMgr()->deref();
00210   if ( !emitsCompletedItself() )
00211     emit completed( this );
00212   if ( !deletesItself() )
00213     delete this;
00214 }
00215 
00216 void KMCommand::transferSelectedMsgs()
00217 {
00218   // make sure no other transfer is active
00219   if (KMCommand::mCountJobs > 0) {
00220     emit messagesTransfered( Failed );
00221     return;
00222   }
00223 
00224   bool complete = true;
00225   KMCommand::mCountJobs = 0;
00226   mCountMsgs = 0;
00227   mRetrievedMsgs.clear();
00228   mCountMsgs = mMsgList.count();
00229   uint totalSize = 0;
00230   // the KProgressDialog for the user-feedback. Only enable it if it's needed.
00231   // For some commands like KMSetStatusCommand it's not needed. Note, that
00232   // for some reason the KProgressDialog eats the MouseReleaseEvent (if a
00233   // command is executed after the MousePressEvent), cf. bug #71761.
00234   if ( mCountMsgs > 0 ) {
00235     mProgressDialog = new KProgressDialog(mParent, "transferProgress",
00236       i18n("Please wait"),
00237       i18n("Please wait while the message is transferred",
00238         "Please wait while the %n messages are transferred", mMsgList.count()),
00239       true);
00240     mProgressDialog->setMinimumDuration(1000);
00241   }
00242   for (KMMsgBase *mb = mMsgList.first(); mb; mb = mMsgList.next())
00243   {
00244     // check if all messages are complete
00245     KMMessage *thisMsg = 0;
00246     if ( mb->isMessage() )
00247       thisMsg = static_cast<KMMessage*>(mb);
00248     else
00249     {
00250       KMFolder *folder = mb->parent();
00251       int idx = folder->find(mb);
00252       if (idx < 0) continue;
00253       thisMsg = folder->getMsg(idx);
00254     }
00255     if (!thisMsg) continue;
00256     if ( thisMsg->transferInProgress() &&
00257          thisMsg->parent()->folderType() == KMFolderTypeImap )
00258     {
00259       thisMsg->setTransferInProgress( false, true );
00260       thisMsg->parent()->ignoreJobsForMessage( thisMsg );
00261     }
00262 
00263     if ( thisMsg->parent() && !thisMsg->isComplete() &&
00264          ( !mProgressDialog || !mProgressDialog->wasCancelled() ) )
00265     {
00266       kdDebug(5006)<<"### INCOMPLETE\n";
00267       // the message needs to be transferred first
00268       complete = false;
00269       KMCommand::mCountJobs++;
00270       FolderJob *job = thisMsg->parent()->createJob(thisMsg);
00271       job->setCancellable( false );
00272       totalSize += thisMsg->msgSizeServer();
00273       // emitted when the message was transferred successfully
00274       connect(job, SIGNAL(messageRetrieved(KMMessage*)),
00275               this, SLOT(slotMsgTransfered(KMMessage*)));
00276       // emitted when the job is destroyed
00277       connect(job, SIGNAL(finished()),
00278               this, SLOT(slotJobFinished()));
00279       connect(job, SIGNAL(progress(unsigned long, unsigned long)),
00280               this, SLOT(slotProgress(unsigned long, unsigned long)));
00281       // msg musn't be deleted
00282       thisMsg->setTransferInProgress(true);
00283       job->start();
00284     } else {
00285       thisMsg->setTransferInProgress(true);
00286       mRetrievedMsgs.append(thisMsg);
00287     }
00288   }
00289 
00290   if (complete)
00291   {
00292     delete mProgressDialog;
00293     emit messagesTransfered( OK );
00294   } else {
00295     // wait for the transfer and tell the progressBar the necessary steps
00296     if ( mProgressDialog ) {
00297       connect(mProgressDialog, SIGNAL(cancelClicked()),
00298               this, SLOT(slotTransferCancelled()));
00299       mProgressDialog->progressBar()->setTotalSteps(totalSize);
00300     }
00301   }
00302 }
00303 
00304 void KMCommand::slotMsgTransfered(KMMessage* msg)
00305 {
00306   if ( mProgressDialog && mProgressDialog->wasCancelled() ) {
00307     emit messagesTransfered( Canceled );
00308     return;
00309   }
00310 
00311   // save the complete messages
00312   mRetrievedMsgs.append(msg);
00313 }
00314 
00315 void KMCommand::slotProgress( unsigned long done, unsigned long /*total*/ )
00316 {
00317   mProgressDialog->progressBar()->setProgress( done );
00318 }
00319 
00320 void KMCommand::slotJobFinished()
00321 {
00322   // the job is finished (with / without error)
00323   KMCommand::mCountJobs--;
00324 
00325   if ( mProgressDialog && mProgressDialog->wasCancelled() ) return;
00326 
00327   if ( (mCountMsgs - static_cast<int>(mRetrievedMsgs.count())) > KMCommand::mCountJobs )
00328   {
00329     // the message wasn't retrieved before => error
00330     if ( mProgressDialog )
00331       mProgressDialog->hide();
00332     slotTransferCancelled();
00333     return;
00334   }
00335   // update the progressbar
00336   if ( mProgressDialog ) {
00337     mProgressDialog->setLabel(i18n("Please wait while the message is transferred",
00338           "Please wait while the %n messages are transferred", KMCommand::mCountJobs));
00339   }
00340   if (KMCommand::mCountJobs == 0)
00341   {
00342     // all done
00343     delete mProgressDialog;
00344     emit messagesTransfered( OK );
00345   }
00346 }
00347 
00348 void KMCommand::slotTransferCancelled()
00349 {
00350   // kill the pending jobs
00351   QValueListIterator<QGuardedPtr<KMFolder> > fit;
00352   for ( fit = mFolders.begin(); fit != mFolders.end(); ++fit ) {
00353     if (!(*fit))
00354       continue;
00355     KMFolder *folder = *fit;
00356     KMFolderImap *imapFolder = dynamic_cast<KMFolderImap*>(folder);
00357     if (imapFolder && imapFolder->account()) {
00358       imapFolder->account()->killAllJobs();
00359     }
00360   }
00361 
00362   KMCommand::mCountJobs = 0;
00363   mCountMsgs = 0;
00364   // unget the transfered messages
00365   QPtrListIterator<KMMessage> it( mRetrievedMsgs );
00366   KMMessage* msg;
00367   while ( (msg = it.current()) != 0 )
00368   {
00369     KMFolder *folder = msg->parent();
00370     ++it;
00371     if (!folder)
00372       continue;
00373     msg->setTransferInProgress(false);
00374     int idx = folder->find(msg);
00375     if (idx > 0) folder->unGetMsg(idx);
00376   }
00377   mRetrievedMsgs.clear();
00378   emit messagesTransfered( Canceled );
00379 }
00380 
00381 void KMCommand::keepFolderOpen( KMFolder *folder )
00382 {
00383   folder->open();
00384   mFolders.append( folder );
00385 }
00386 
00387 KMMailtoComposeCommand::KMMailtoComposeCommand( const KURL &url,
00388                                                 KMMessage *msg )
00389   :mUrl( url ), mMessage( msg )
00390 {
00391 }
00392 
00393 KMCommand::Result KMMailtoComposeCommand::execute()
00394 {
00395   KMComposeWin *win;
00396   KMMessage *msg = new KMMessage;
00397   uint id = 0;
00398 
00399   if ( mMessage && mMessage->parent() )
00400     id = mMessage->parent()->identity();
00401 
00402   msg->initHeader(id);
00403   msg->setCharset("utf-8");
00404   msg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );
00405 
00406   win = new KMComposeWin(msg, id);
00407   win->setCharset("", TRUE);
00408   win->setFocusToSubject();
00409   win->show();
00410 
00411   return OK;
00412 }
00413 
00414 
00415 KMMailtoReplyCommand::KMMailtoReplyCommand( QWidget *parent,
00416    const KURL &url, KMMessage *msg, const QString &selection )
00417   :KMCommand( parent, msg ), mUrl( url ), mSelection( selection  )
00418 {
00419 }
00420 
00421 KMCommand::Result KMMailtoReplyCommand::execute()
00422 {
00423   //TODO : consider factoring createReply into this method.
00424   KMMessage *msg = retrievedMessage();
00425   KMComposeWin *win;
00426   KMMessage *rmsg = msg->createReply( KMail::ReplyNone, mSelection );
00427   rmsg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );
00428 
00429   win = new KMComposeWin(rmsg, 0);
00430   win->setCharset(msg->codec()->mimeName(), TRUE);
00431   win->setReplyFocus();
00432   win->show();
00433 
00434   return OK;
00435 }
00436 
00437 
00438 KMMailtoForwardCommand::KMMailtoForwardCommand( QWidget *parent,
00439    const KURL &url, KMMessage *msg )
00440   :KMCommand( parent, msg ), mUrl( url )
00441 {
00442 }
00443 
00444 KMCommand::Result KMMailtoForwardCommand::execute()
00445 {
00446   //TODO : consider factoring createForward into this method.
00447   KMMessage *msg = retrievedMessage();
00448   KMComposeWin *win;
00449   KMMessage *fmsg = msg->createForward();
00450   fmsg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );
00451 
00452   win = new KMComposeWin(fmsg);
00453   win->setCharset(msg->codec()->mimeName(), TRUE);
00454   win->show();
00455 
00456   return OK;
00457 }
00458 
00459 
00460 KMAddBookmarksCommand::KMAddBookmarksCommand( const KURL &url, QWidget *parent )
00461   : KMCommand( parent ), mUrl( url )
00462 {
00463 }
00464 
00465 KMCommand::Result KMAddBookmarksCommand::execute()
00466 {
00467   QString filename = locateLocal( "data", QString::fromLatin1("konqueror/bookmarks.xml") );
00468   KBookmarkManager *bookManager = KBookmarkManager::managerForFile( filename,
00469                                                                     false );
00470   KBookmarkGroup group = bookManager->root();
00471   group.addBookmark( bookManager, mUrl.path(), KURL( mUrl ) );
00472   if( bookManager->save() ) {
00473     bookManager->emitChanged( group );
00474   }
00475 
00476   return OK;
00477 }
00478 
00479 KMMailtoAddAddrBookCommand::KMMailtoAddAddrBookCommand( const KURL &url,
00480    QWidget *parent )
00481   : KMCommand( parent ), mUrl( url )
00482 {
00483 }
00484 
00485 KMCommand::Result KMMailtoAddAddrBookCommand::execute()
00486 {
00487   KAddrBookExternal::addEmail( KMMessage::decodeMailtoUrl( mUrl.path() ),
00488                                parentWidget() );
00489 
00490   return OK;
00491 }
00492 
00493 
00494 KMMailtoOpenAddrBookCommand::KMMailtoOpenAddrBookCommand( const KURL &url,
00495    QWidget *parent )
00496   : KMCommand( parent ), mUrl( url )
00497 {
00498 }
00499 
00500 KMCommand::Result KMMailtoOpenAddrBookCommand::execute()
00501 {
00502   QString addr = KMMessage::decodeMailtoUrl( mUrl.path() );
00503   KAddrBookExternal::openEmail( KPIM::getEmailAddr(addr), addr ,
00504                                 parentWidget() );
00505 
00506   return OK;
00507 }
00508 
00509 
00510 KMUrlCopyCommand::KMUrlCopyCommand( const KURL &url, KMMainWidget *mainWidget )
00511   :mUrl( url ), mMainWidget( mainWidget )
00512 {
00513 }
00514 
00515 KMCommand::Result KMUrlCopyCommand::execute()
00516 {
00517   QClipboard* clip = QApplication::clipboard();
00518 
00519   if (mUrl.protocol() == "mailto") {
00520     // put the url into the mouse selection and the clipboard
00521     QString address = KMMessage::decodeMailtoUrl( mUrl.path() );
00522     clip->setSelectionMode( true );
00523     clip->setText( address );
00524     clip->setSelectionMode( false );
00525     clip->setText( address );
00526     KPIM::BroadcastStatus::instance()->setStatusMsg( i18n( "Address copied to clipboard." ));
00527   } else {
00528     // put the url into the mouse selection and the clipboard
00529     clip->setSelectionMode( true );
00530     clip->setText( mUrl.url() );
00531     clip->setSelectionMode( false );
00532     clip->setText( mUrl.url() );
00533     KPIM::BroadcastStatus::instance()->setStatusMsg( i18n( "URL copied to clipboard." ));
00534   }
00535 
00536   return OK;
00537 }
00538 
00539 
00540 KMUrlOpenCommand::KMUrlOpenCommand( const KURL &url, KMReaderWin *readerWin )
00541   :mUrl( url ), mReaderWin( readerWin )
00542 {
00543 }
00544 
00545 KMCommand::Result KMUrlOpenCommand::execute()
00546 {
00547   if ( !mUrl.isEmpty() )
00548     mReaderWin->slotUrlOpen( mUrl, KParts::URLArgs() );
00549 
00550   return OK;
00551 }
00552 
00553 
00554 KMUrlSaveCommand::KMUrlSaveCommand( const KURL &url, QWidget *parent )
00555   : KMCommand( parent ), mUrl( url )
00556 {
00557 }
00558 
00559 KMCommand::Result KMUrlSaveCommand::execute()
00560 {
00561   if ( mUrl.isEmpty() )
00562     return OK;
00563   KURL saveUrl = KFileDialog::getSaveURL(mUrl.fileName(), QString::null,
00564                                          parentWidget() );
00565   if ( saveUrl.isEmpty() )
00566     return Canceled;
00567   if ( KIO::NetAccess::exists( saveUrl, false, parentWidget() ) )
00568   {
00569     if (KMessageBox::warningContinueCancel(0,
00570         i18n("<qt>File <b>%1</b> exists.<br>Do you want to replace it?</qt>")
00571         .arg(saveUrl.prettyURL()), i18n("Save to File"), i18n("&Replace"))
00572         != KMessageBox::Continue)
00573       return Canceled;
00574   }
00575   KIO::Job *job = KIO::file_copy(mUrl, saveUrl, -1, true);
00576   connect(job, SIGNAL(result(KIO::Job*)), SLOT(slotUrlSaveResult(KIO::Job*)));
00577   setEmitsCompletedItself( true );
00578   return OK;
00579 }
00580 
00581 void KMUrlSaveCommand::slotUrlSaveResult( KIO::Job *job )
00582 {
00583   if ( job->error() ) {
00584     job->showErrorDialog();
00585     setResult( Failed );
00586     emit completed( this );
00587   }
00588   else {
00589     setResult( OK );
00590     emit completed( this );
00591   }
00592 }
00593 
00594 
00595 KMEditMsgCommand::KMEditMsgCommand( QWidget *parent, KMMessage *msg )
00596   :KMCommand( parent, msg )
00597 {
00598 }
00599 
00600 KMCommand::Result KMEditMsgCommand::execute()
00601 {
00602   KMMessage *msg = retrievedMessage();
00603   if (!msg || !msg->parent() ||
00604       !kmkernel->folderIsDraftOrOutbox( msg->parent() ))
00605     return Failed;
00606 
00607   // Remember the old parent, we need it a bit further down to be able
00608   // to put the unchanged messsage back in the drafts folder if the nth
00609   // edit is discarded, for n > 1.
00610   KMFolder *parent = msg->parent();
00611   if ( parent )
00612     parent->take( parent->find( msg ) );
00613 
00614   KMComposeWin *win = new KMComposeWin();
00615   msg->setTransferInProgress(false); // From here on on, the composer owns the message.
00616   win->setMsg(msg, FALSE, TRUE);
00617   win->setFolder( parent );
00618   win->show();
00619 
00620   return OK;
00621 }
00622 
00623 
00624 KMShowMsgSrcCommand::KMShowMsgSrcCommand( KMMessage *msg, bool fixedFont )
00625   : mFixedFont( fixedFont ), mMsg ( msg )
00626 {
00627 }
00628 
00629 void KMShowMsgSrcCommand::start()
00630 {
00631   QString str = mMsg->codec()->toUnicode( mMsg->asString() );
00632 
00633   MailSourceViewer *viewer = new MailSourceViewer(); // deletes itself upon close
00634   viewer->setCaption( i18n("Message as Plain Text") );
00635   viewer->setText(str);
00636   if( mFixedFont )
00637     viewer->setFont(KGlobalSettings::fixedFont());
00638 
00639   // Well, there is no widget to be seen here, so we have to use QCursor::pos()
00640   // Update: (GS) I'm not going to make this code behave according to Xinerama
00641   //         configuration because this is quite the hack.
00642   if (QApplication::desktop()->isVirtualDesktop()) {
00643     int scnum = QApplication::desktop()->screenNumber(QCursor::pos());
00644     viewer->resize(QApplication::desktop()->screenGeometry(scnum).width()/2,
00645                   2*QApplication::desktop()->screenGeometry(scnum).height()/3);
00646   } else {
00647     viewer->resize(QApplication::desktop()->geometry().width()/2,
00648                   2*QApplication::desktop()->geometry().height()/3);
00649   }
00650   viewer->show();
00651 }
00652 
00653 static KURL subjectToUrl( const QString & subject ) {
00654     return KFileDialog::getSaveURL( subject.stripWhiteSpace()
00655                                            .replace( QDir::separator(), '_' ),
00656                                     QString::null );
00657 }
00658 
00659 KMSaveMsgCommand::KMSaveMsgCommand( QWidget *parent, KMMessage * msg )
00660   : KMCommand( parent ),
00661     mMsgListIndex( 0 ),
00662     mOffset( 0 ),
00663     mTotalSize( msg ? msg->msgSize() : 0 )
00664 {
00665   if ( !msg ) return;
00666   setDeletesItself( true );
00667   mMsgList.append( msg->getMsgSerNum() );
00668   mUrl = subjectToUrl( msg->cleanSubject() );
00669 }
00670 
00671 KMSaveMsgCommand::KMSaveMsgCommand( QWidget *parent,
00672                                     const QPtrList<KMMsgBase> &msgList )
00673   : KMCommand( parent ),
00674     mMsgListIndex( 0 ),
00675     mOffset( 0 ),
00676     mTotalSize( 0 )
00677 {
00678   if (!msgList.getFirst())
00679     return;
00680   setDeletesItself( true );
00681   KMMsgBase *msgBase = msgList.getFirst();
00682 
00683   // We operate on serNums and not the KMMsgBase pointers, as those can
00684   // change, or become invalid when changing the current message, switching
00685   // folders, etc.
00686   QPtrListIterator<KMMsgBase> it(msgList);
00687   while ( it.current() ) {
00688     mMsgList.append( (*it)->getMsgSerNum() );
00689     mTotalSize += (*it)->msgSize();
00690     if ((*it)->parent() != 0)
00691       (*it)->parent()->open();
00692     ++it;
00693   }
00694   mMsgListIndex = 0;
00695   mUrl = subjectToUrl( msgBase->cleanSubject() );
00696 }
00697 
00698 KURL KMSaveMsgCommand::url()
00699 {
00700   return mUrl;
00701 }
00702 
00703 KMCommand::Result KMSaveMsgCommand::execute()
00704 {
00705   mJob = KIO::put( mUrl, S_IRUSR|S_IWUSR, false, false );
00706   mJob->slotTotalSize( mTotalSize );
00707   mJob->setAsyncDataEnabled( true );
00708   mJob->setReportDataSent( true );
00709   connect(mJob, SIGNAL(dataReq(KIO::Job*, QByteArray &)),
00710     SLOT(slotSaveDataReq()));
00711   connect(mJob, SIGNAL(result(KIO::Job*)),
00712     SLOT(slotSaveResult(KIO::Job*)));
00713   setEmitsCompletedItself( true );
00714   return OK;
00715 }
00716 
00717 void KMSaveMsgCommand::slotSaveDataReq()
00718 {
00719   int remainingBytes = mData.size() - mOffset;
00720   if ( remainingBytes > 0 ) {
00721     // eat leftovers first
00722     if ( remainingBytes > MAX_CHUNK_SIZE )
00723       remainingBytes = MAX_CHUNK_SIZE;
00724 
00725     QByteArray data;
00726     data.duplicate( mData.data() + mOffset, remainingBytes );
00727     mJob->sendAsyncData( data );
00728     mOffset += remainingBytes;
00729     return;
00730   }
00731   // No leftovers, process next message.
00732   if ( mMsgListIndex < mMsgList.size() ) {
00733     KMMessage *msg = 0;
00734     int idx = -1;
00735     KMFolder * p = 0;
00736     kmkernel->msgDict()->getLocation( mMsgList[mMsgListIndex], &p, &idx );
00737     assert( p );
00738     assert( idx >= 0 );
00739     msg = p->getMsg(idx);
00740 
00741     if (msg->transferInProgress()) {
00742       QByteArray data = QByteArray();
00743       mJob->sendAsyncData( data );
00744     }
00745     msg->setTransferInProgress( true );
00746     if (msg->isComplete() ) {
00747       slotMessageRetrievedForSaving(msg);
00748     } else {
00749       // retrieve Message first
00750       if (msg->parent()  && !msg->isComplete() ) {
00751         FolderJob *job = msg->parent()->createJob(msg);
00752         job->setCancellable( false );
00753         connect(job, SIGNAL(messageRetrieved(KMMessage*)),
00754             this, SLOT(slotMessageRetrievedForSaving(KMMessage*)));
00755         job->start();
00756       }
00757     }
00758   } else {
00759     // No more messages. Tell the putjob we are done.
00760     QByteArray data = QByteArray();
00761     mJob->sendAsyncData( data );
00762   }
00763 }
00764 
00765 void KMSaveMsgCommand::slotMessageRetrievedForSaving(KMMessage *msg)
00766 {
00767   QCString str( msg->mboxMessageSeparator() );
00768   str += KMFolderMbox::escapeFrom( msg->asString() );
00769   str += "\n";
00770   msg->setTransferInProgress(false);
00771 
00772   mData = str;
00773   mData.resize(mData.size() - 1);
00774   mOffset = 0;
00775   QByteArray data;
00776   int size;
00777   // Unless it is great than 64 k send the whole message. kio buffers for us.
00778   if( mData.size() > (unsigned int) MAX_CHUNK_SIZE )
00779     size = MAX_CHUNK_SIZE;
00780   else
00781     size = mData.size();
00782 
00783   data.duplicate( mData, size );
00784   mJob->sendAsyncData( data );
00785   mOffset += size;
00786   ++mMsgListIndex;
00787   // Get rid of the message.
00788   if (msg->parent()) {
00789     int idx = -1;
00790     KMFolder * p = 0;
00791     kmkernel->msgDict()->getLocation( msg, &p, &idx );
00792     assert( p == msg->parent() ); assert( idx >= 0 );
00793     p->unGetMsg( idx );
00794     p->close();
00795   }
00796 }
00797 
00798 void KMSaveMsgCommand::slotSaveResult(KIO::Job *job)
00799 {
00800   if (job->error())
00801   {
00802     if (job->error() == KIO::ERR_FILE_ALREADY_EXIST)
00803     {
00804       if (KMessageBox::warningContinueCancel(0,
00805         i18n("File %1 exists.\nDo you want to replace it?")
00806         .arg(mUrl.prettyURL()), i18n("Save to File"), i18n("&Replace"))
00807         == KMessageBox::Continue) {
00808         mOffset = 0;
00809 
00810         mJob = KIO::put( mUrl, S_IRUSR|S_IWUSR, true, false );
00811         mJob->slotTotalSize( mTotalSize );
00812         mJob->setAsyncDataEnabled( true );
00813         mJob->setReportDataSent( true );
00814         connect(mJob, SIGNAL(dataReq(KIO::Job*, QByteArray &)),
00815             SLOT(slotSaveDataReq()));
00816         connect(mJob, SIGNAL(result(KIO::Job*)),
00817             SLOT(slotSaveResult(KIO::Job*)));
00818       }
00819     }
00820     else
00821     {
00822       job->showErrorDialog();
00823       setResult( Failed );
00824       emit completed( this );
00825       delete this;
00826     }
00827   } else {
00828     setResult( OK );
00829     emit completed( this );
00830     delete this;
00831   }
00832 }
00833 
00834 //-----------------------------------------------------------------------------
00835 
00836 KMOpenMsgCommand::KMOpenMsgCommand( QWidget *parent, const KURL & url )
00837   : KMCommand( parent ),
00838     mUrl( url )
00839 {
00840   setDeletesItself( true );
00841 }
00842 
00843 KMCommand::Result KMOpenMsgCommand::execute()
00844 {
00845   if ( mUrl.isEmpty() ) {
00846     mUrl = KFileDialog::getOpenURL( ":OpenMessage", "message/rfc822",
00847                                     parentWidget(), i18n("Open Message") );
00848   }
00849   if ( mUrl.isEmpty() ) {
00850     setDeletesItself( false );
00851     return Canceled;
00852   }
00853   mJob = KIO::get( mUrl, false, false );
00854   mJob->setReportDataSent( true );
00855   connect( mJob, SIGNAL( data( KIO::Job *, const QByteArray & ) ),
00856            this, SLOT( slotDataArrived( KIO::Job*, const QByteArray & ) ) );
00857   connect( mJob, SIGNAL( result( KIO::Job * ) ),
00858            SLOT( slotResult( KIO::Job * ) ) );
00859   setEmitsCompletedItself( true );
00860   return OK;
00861 }
00862 
00863 void KMOpenMsgCommand::slotDataArrived( KIO::Job *, const QByteArray & data )
00864 {
00865   if ( data.isEmpty() )
00866     return;
00867 
00868   mMsgString.append( data.data(), data.size() );
00869 }
00870 
00871 void KMOpenMsgCommand::slotResult( KIO::Job *job )
00872 {
00873   if ( job->error() ) {
00874     // handle errors
00875     job->showErrorDialog();
00876     setResult( Failed );
00877     emit completed( this );
00878   }
00879   else {
00880     int startOfMessage = 0;
00881     if ( mMsgString.compare( 0, 5, "From ", 5 ) == 0 ) {
00882       startOfMessage = mMsgString.find( '\n' );
00883       if ( startOfMessage == -1 ) {
00884         KMessageBox::sorry( parentWidget(),
00885                             i18n( "The file doesn't contain a message." ) );
00886         setResult( Failed );
00887         emit completed( this );
00888         // Emulate closing of a secondary window so that KMail exits in case it
00889         // was started with the --view command line option. Otherwise an
00890         // invisible KMail would keep running.
00891         SecondaryWindow *win = new SecondaryWindow();
00892         win->close();
00893         win->deleteLater();
00894         deleteLater();
00895         return;
00896       }
00897       startOfMessage += 1; // the message starts after the '\n'
00898     }
00899     // check for multiple messages in the file
00900     bool multipleMessages = true;
00901     int endOfMessage = mMsgString.find( "\nFrom " );
00902     if ( endOfMessage == -1 ) {
00903       endOfMessage = mMsgString.length();
00904       multipleMessages = false;
00905     }
00906     DwMessage *dwMsg = new DwMessage;
00907     dwMsg->FromString( mMsgString.substr( startOfMessage,
00908                                           endOfMessage - startOfMessage ) );
00909     dwMsg->Parse();
00910     // check whether we have a message ( no headers => this isn't a message )
00911     if ( dwMsg->Headers().NumFields() == 0 ) {
00912       KMessageBox::sorry( parentWidget(),
00913                           i18n( "The file doesn't contain a message." ) );
00914       delete dwMsg; dwMsg = 0;
00915       setResult( Failed );
00916       emit completed( this );
00917       // Emulate closing of a secondary window (see above).
00918       SecondaryWindow *win = new SecondaryWindow();
00919       win->close();
00920       win->deleteLater();
00921       deleteLater();
00922       return;
00923     }
00924     KMMessage *msg = new KMMessage( dwMsg );
00925     msg->setReadyToShow( true );
00926     KMReaderMainWin *win = new KMReaderMainWin();
00927     win->showMsg( kmkernel->networkCodec(), msg );
00928     win->show();
00929     if ( multipleMessages )
00930       KMessageBox::information( win,
00931                                 i18n( "The file contains multiple messages. "
00932                                       "Only the first message is shown." ) );
00933     setResult( OK );
00934     emit completed( this );
00935   }
00936   delete this;
00937 }
00938 
00939 //-----------------------------------------------------------------------------
00940 
00941 //TODO: ReplyTo, NoQuoteReplyTo, ReplyList, ReplyToAll, ReplyAuthor
00942 //      are all similar and should be factored
00943 KMReplyToCommand::KMReplyToCommand( QWidget *parent, KMMessage *msg,
00944                                     const QString &selection )
00945   : KMCommand( parent, msg ), mSelection( selection )
00946 {
00947 }
00948 
00949 KMCommand::Result KMReplyToCommand::execute()
00950 {
00951   KCursorSaver busy(KBusyPtr::busy());
00952   KMMessage *msg = retrievedMessage();
00953   KMMessage *reply = msg->createReply( KMail::ReplySmart, mSelection );
00954   KMComposeWin *win = new KMComposeWin( reply );
00955   win->setCharset( msg->codec()->mimeName(), TRUE );
00956   win->setReplyFocus();
00957   win->show();
00958 
00959   return OK;
00960 }
00961 
00962 
00963 KMNoQuoteReplyToCommand::KMNoQuoteReplyToCommand( QWidget *parent,
00964                                                   KMMessage *msg )
00965   : KMCommand( parent, msg )
00966 {
00967 }
00968 
00969 KMCommand::Result KMNoQuoteReplyToCommand::execute()
00970 {
00971   KCursorSaver busy(KBusyPtr::busy());
00972   KMMessage *msg = retrievedMessage();
00973   KMMessage *reply = msg->createReply( KMail::ReplySmart, "", TRUE);
00974   KMComposeWin *win = new KMComposeWin( reply );
00975   win->setCharset(msg->codec()->mimeName(), TRUE);
00976   win->setReplyFocus(false);
00977   win->show();
00978 
00979   return OK;
00980 }
00981 
00982 
00983 KMReplyListCommand::KMReplyListCommand( QWidget *parent,
00984   KMMessage *msg, const QString &selection )
00985  : KMCommand( parent, msg ), mSelection( selection )
00986 {
00987 }
00988 
00989 KMCommand::Result KMReplyListCommand::execute()
00990 {
00991   KCursorSaver busy(KBusyPtr::busy());
00992   KMMessage *msg = retrievedMessage();
00993   KMMessage *reply = msg->createReply( KMail::ReplyList, mSelection);
00994   KMComposeWin *win = new KMComposeWin( reply );
00995   win->setCharset(msg->codec()->mimeName(), TRUE);
00996   win->setReplyFocus(false);
00997   win->show();
00998 
00999   return OK;
01000 }
01001 
01002 
01003 KMReplyToAllCommand::KMReplyToAllCommand( QWidget *parent,
01004   KMMessage *msg, const QString &selection )
01005   :KMCommand( parent, msg ), mSelection( selection )
01006 {
01007 }
01008 
01009 KMCommand::Result KMReplyToAllCommand::execute()
01010 {
01011   KCursorSaver busy(KBusyPtr::busy());
01012   KMMessage *msg = retrievedMessage();
01013   KMMessage *reply = msg->createReply( KMail::ReplyAll, mSelection );
01014   KMComposeWin *win = new KMComposeWin( reply );
01015   win->setCharset( msg->codec()->mimeName(), TRUE );
01016   win->setReplyFocus();
01017   win->show();
01018 
01019   return OK;
01020 }
01021 
01022 
01023 KMReplyAuthorCommand::KMReplyAuthorCommand( QWidget *parent, KMMessage *msg,
01024                                             const QString &selection )
01025   : KMCommand( parent, msg ), mSelection( selection )
01026 {
01027 }
01028 
01029 KMCommand::Result KMReplyAuthorCommand::execute()
01030 {
01031   KCursorSaver busy(KBusyPtr::busy());
01032   KMMessage *msg = retrievedMessage();
01033   KMMessage *reply = msg->createReply( KMail::ReplyAuthor, mSelection );
01034   KMComposeWin *win = new KMComposeWin( reply );
01035   win->setCharset( msg->codec()->mimeName(), TRUE );
01036   win->setReplyFocus();
01037   win->show();
01038 
01039   return OK;
01040 }
01041 
01042 
01043 KMForwardCommand::KMForwardCommand( QWidget *parent,
01044   const QPtrList<KMMsgBase> &msgList, uint identity )
01045   : KMCommand( parent, msgList ),
01046     mIdentity( identity )
01047 {
01048 }
01049 
01050 KMForwardCommand::KMForwardCommand( QWidget *parent, KMMessage *msg,
01051                                     uint identity )
01052   : KMCommand( parent, msg ),
01053     mIdentity( identity )
01054 {
01055 }
01056 
01057 KMCommand::Result KMForwardCommand::execute()
01058 {
01059   KMComposeWin *win;
01060   QPtrList<KMMessage> msgList = retrievedMsgs();
01061 
01062   if (msgList.count() >= 2) {
01063     // ask if they want a mime digest forward
01064 
01065     if (KMessageBox::questionYesNo( parentWidget(),
01066                                     i18n("Forward selected messages as "
01067                                          "a MIME digest?") )
01068         == KMessageBox::Yes) {
01069       uint id = 0;
01070       KMMessage *fwdMsg = new KMMessage;
01071       KMMessagePart *msgPart = new KMMessagePart;
01072       QString msgPartText;
01073       int msgCnt = 0; // incase there are some we can't forward for some reason
01074 
01075       // dummy header initialization; initialization with the correct identity
01076       // is done below
01077       fwdMsg->initHeader(id);
01078       fwdMsg->setAutomaticFields(true);
01079       fwdMsg->mMsg->Headers().ContentType().CreateBoundary(1);
01080       QCString boundary( fwdMsg->mMsg->Headers().ContentType().Boundary().c_str() );
01081       msgPartText = i18n("\nThis is a MIME digest forward. The content of the"
01082                          " message is contained in the attachment(s).\n\n\n");
01083       // iterate through all the messages to be forwarded
01084       for (KMMessage *msg = msgList.first(); msg; msg = msgList.next()) {
01085         // set the identity
01086         if (id == 0)
01087           id = msg->headerField("X-KMail-Identity").stripWhiteSpace().toUInt();
01088         // set the part header
01089         msgPartText += "--";
01090         msgPartText += QString::fromLatin1( boundary );
01091         msgPartText += "\nContent-Type: MESSAGE/RFC822";
01092         msgPartText += QString("; CHARSET=%1").arg(msg->charset());
01093         msgPartText += "\n";
01094         DwHeaders dwh;
01095         dwh.MessageId().CreateDefault();
01096         msgPartText += QString("Content-ID: %1\n").arg(dwh.MessageId().AsString().c_str());
01097         msgPartText += QString("Content-Description: %1").arg(msg->subject());
01098         if (!msg->subject().contains("(fwd)"))
01099           msgPartText += " (fwd)";
01100         msgPartText += "\n\n";
01101         // remove headers that shouldn't be forwarded
01102         msg->removePrivateHeaderFields();
01103         msg->removeHeaderField("BCC");
01104         // set the part
01105         msgPartText += msg->headerAsString();
01106         msgPartText += "\n";
01107         msgPartText += msg->body();
01108         msgPartText += "\n";     // eot
01109         msgCnt++;
01110         fwdMsg->link(msg, KMMsgStatusForwarded);
01111       }
01112       if ( id == 0 )
01113         id = mIdentity; // use folder identity if no message had an id set
01114       fwdMsg->initHeader(id);
01115       msgPartText += "--";
01116       msgPartText += QString::fromLatin1( boundary );
01117       msgPartText += "--\n";
01118       QCString tmp;
01119       msgPart->setTypeStr("MULTIPART");
01120       tmp.sprintf( "Digest; boundary=\"%s\"", boundary.data() );
01121       msgPart->setSubtypeStr( tmp );
01122       msgPart->setName("unnamed");
01123       msgPart->setCte(DwMime::kCte7bit);   // does it have to be 7bit?
01124       msgPart->setContentDescription(QString("Digest of %1 messages.").arg(msgCnt));
01125       // THIS HAS TO BE AFTER setCte()!!!!
01126       msgPart->setBodyEncoded(QCString(msgPartText.ascii()));
01127       KCursorSaver busy(KBusyPtr::busy());
01128       win = new KMComposeWin(fwdMsg, id);
01129       win->addAttach(msgPart);
01130       win->show();
01131       return OK;
01132     } else {            // NO MIME DIGEST, Multiple forward
01133       uint id = 0;
01134       QCString msgText = "";
01135       QPtrList<KMMessage> linklist;
01136       for (KMMessage *msg = msgList.first(); msg; msg = msgList.next()) {
01137         // set the identity
01138         if (id == 0)
01139           id = msg->headerField("X-KMail-Identity").stripWhiteSpace().toUInt();
01140 
01141         msgText += msg->createForwardBody();
01142         linklist.append(msg);
01143       }
01144       if ( id == 0 )
01145         id = mIdentity; // use folder identity if no message had an id set
01146       KMMessage *fwdMsg = new KMMessage;
01147       fwdMsg->initHeader(id);
01148       fwdMsg->setAutomaticFields(true);
01149       fwdMsg->setCharset("utf-8");
01150       fwdMsg->setBody(msgText);
01151 
01152       for (KMMessage *msg = linklist.first(); msg; msg = linklist.next())
01153         fwdMsg->link(msg, KMMsgStatusForwarded);
01154 
01155       KCursorSaver busy(KBusyPtr::busy());
01156       win = new KMComposeWin(fwdMsg, id);
01157       win->setCharset("");
01158       win->show();
01159       return OK;
01160     }
01161   }
01162 
01163   // forward a single message at most.
01164 
01165   KMMessage *msg = msgList.getFirst();
01166   if ( !msg || !msg->codec() )
01167     return Failed;
01168 
01169   KCursorSaver busy(KBusyPtr::busy());
01170   win = new KMComposeWin(msg->createForward());
01171   win->setCharset(msg->codec()->mimeName(), TRUE);
01172   win->show();
01173 
01174   return OK;
01175 }
01176 
01177 
01178 KMForwardAttachedCommand::KMForwardAttachedCommand( QWidget *parent,
01179   const QPtrList<KMMsgBase> &msgList, uint identity, KMComposeWin *win )
01180   : KMCommand( parent, msgList ), mIdentity( identity ),
01181     mWin( QGuardedPtr< KMComposeWin >( win ))
01182 {
01183 }
01184 
01185 KMForwardAttachedCommand::KMForwardAttachedCommand( QWidget *parent,
01186   KMMessage * msg, uint identity, KMComposeWin *win )
01187   : KMCommand( parent, msg ), mIdentity( identity ),
01188     mWin( QGuardedPtr< KMComposeWin >( win ))
01189 {
01190 }
01191 
01192 KMCommand::Result KMForwardAttachedCommand::execute()
01193 {
01194   QPtrList<KMMessage> msgList = retrievedMsgs();
01195   KMMessage *fwdMsg = new KMMessage;
01196 
01197   if (msgList.count() >= 2) {
01198     // don't respect X-KMail-Identity headers because they might differ for
01199     // the selected mails
01200     fwdMsg->initHeader(mIdentity);
01201   }
01202   else if (msgList.count() == 1) {
01203     KMMessage *msg = msgList.getFirst();
01204     fwdMsg->initFromMessage(msg);
01205     fwdMsg->setSubject( msg->forwardSubject() );
01206   }
01207 
01208   fwdMsg->setAutomaticFields(true);
01209 
01210   KCursorSaver busy(KBusyPtr::busy());
01211   if (!mWin)
01212     mWin = new KMComposeWin(fwdMsg, mIdentity);
01213 
01214   // iterate through all the messages to be forwarded
01215   for (KMMessage *msg = msgList.first(); msg; msg = msgList.next()) {
01216     // remove headers that shouldn't be forwarded
01217     msg->removePrivateHeaderFields();
01218     msg->removeHeaderField("BCC");
01219     // set the part
01220     KMMessagePart *msgPart = new KMMessagePart;
01221     msgPart->setTypeStr("message");
01222     msgPart->setSubtypeStr("rfc822");
01223     msgPart->setCharset(msg->charset());
01224     msgPart->setName("forwarded message");
01225     msgPart->setContentDescription(msg->from()+": "+msg->subject());
01226     msgPart->setContentDisposition( "inline" );
01227     // THIS HAS TO BE AFTER setCte()!!!!
01228     QValueList<int> dummy;
01229     msgPart->setBodyAndGuessCte(msg->asString(), dummy, true);
01230     msgPart->setCharset("");
01231 
01232     fwdMsg->link(msg, KMMsgStatusForwarded);
01233     mWin->addAttach(msgPart);
01234   }
01235 
01236   mWin->show();
01237 
01238   return OK;
01239 }
01240 
01241 
01242 KMRedirectCommand::KMRedirectCommand( QWidget *parent,
01243   KMMessage *msg )
01244   : KMCommand( parent, msg )
01245 {
01246 }
01247 
01248 KMCommand::Result KMRedirectCommand::execute()
01249 {
01250   //TODO: move KMMessage::createRedirect to here
01251   KMComposeWin *win;
01252   KMMessage *msg = retrievedMessage();
01253   if ( !msg || !msg->codec() )
01254     return Failed;
01255 
01256   KCursorSaver busy(KBusyPtr::busy());
01257   win = new KMComposeWin();
01258   win->setMsg(msg->createRedirect(), FALSE);
01259   win->setCharset(msg->codec()->mimeName());
01260   win->show();
01261 
01262   return OK;
01263 }
01264 
01265 
01266 KMBounceCommand::KMBounceCommand( QWidget *parent,
01267   KMMessage *msg )
01268   : KMCommand( parent, msg )
01269 {
01270 }
01271 
01272 KMCommand::Result KMBounceCommand::execute()
01273 {
01274   KMMessage *msg = retrievedMessage();
01275   KMMessage *newMsg = msg->createBounce( TRUE /* with UI */);
01276   if (newMsg)
01277     kmkernel->msgSender()->send(newMsg, kmkernel->msgSender()->sendImmediate());
01278 
01279   return OK;
01280 }
01281 
01282 
01283 KMPrintCommand::KMPrintCommand( QWidget *parent,
01284   KMMessage *msg, bool htmlOverride, const QTextCodec* codec )
01285   : KMCommand( parent, msg ), mHtmlOverride( htmlOverride ), mCodec( codec )
01286 {
01287 }
01288 
01289 KMCommand::Result KMPrintCommand::execute()
01290 {
01291   KMReaderWin printWin( 0, 0, 0 );
01292   printWin.setPrinting(TRUE);
01293   printWin.readConfig();
01294   printWin.setHtmlOverride( mHtmlOverride );
01295   printWin.setOverrideCodec( mCodec );
01296   printWin.setMsg(retrievedMessage(), TRUE);
01297   printWin.printMsg();
01298 
01299   return OK;
01300 }
01301 
01302 
01303 KMSetStatusCommand::KMSetStatusCommand( KMMsgStatus status,
01304   const QValueList<Q_UINT32> &serNums, bool toggle )
01305   : mStatus( status ), mSerNums( serNums ), mToggle( toggle )
01306 {
01307 }
01308 
01309 KMCommand::Result KMSetStatusCommand::execute()
01310 {
01311   QValueListIterator<Q_UINT32> it;
01312   int idx = -1;
01313   KMFolder *folder = 0;
01314   bool parentStatus = false;
01315 
01316   // Toggle actions on threads toggle the whole thread
01317   // depending on the state of the parent.
01318   if (mToggle) {
01319     KMMsgBase *msg;
01320     kmkernel->msgDict()->getLocation( *mSerNums.begin(), &folder, &idx );
01321     if (folder) {
01322       msg = folder->getMsgBase(idx);
01323       if (msg && (msg->status()&mStatus))
01324         parentStatus = true;
01325       else
01326         parentStatus = false;
01327     }
01328   }
01329   QMap< KMFolder*, QValueList<int> > folderMap;
01330   for ( it = mSerNums.begin(); it != mSerNums.end(); ++it ) {
01331     kmkernel->msgDict()->getLocation( *it, &folder, &idx );
01332     if (folder) {
01333       if (mToggle) {
01334         KMMsgBase *msg = folder->getMsgBase(idx);
01335         // check if we are already at the target toggle state
01336         if (msg) {
01337           bool myStatus;
01338           if (msg->status()&mStatus)
01339             myStatus = true;
01340           else
01341             myStatus = false;
01342           if (myStatus != parentStatus)
01343             continue;
01344         }
01345       }
01346       /* Collect the ids for each folder in a separate list and
01347          send them off in one go at the end. */
01348       folderMap[folder].append(idx);
01349     }
01350   }
01351   QMapIterator< KMFolder*, QValueList<int> > it2 = folderMap.begin();
01352   while ( it2 != folderMap.end() ) {
01353      KMFolder *f = it2.key();
01354      f->setStatus( (*it2), mStatus, mToggle );
01355      ++it2;
01356   }
01357   //kapp->dcopClient()->emitDCOPSignal( "unreadCountChanged()", QByteArray() );
01358 
01359   return OK;
01360 }
01361 
01362 
01363 KMFilterCommand::KMFilterCommand( const QCString &field, const QString &value )
01364   : mField( field ), mValue( value )
01365 {
01366 }
01367 
01368 KMCommand::Result KMFilterCommand::execute()
01369 {
01370   kmkernel->filterMgr()->createFilter( mField, mValue );
01371 
01372   return OK;
01373 }
01374 
01375 
01376 KMFilterActionCommand::KMFilterActionCommand( QWidget *parent,
01377                                               const QPtrList<KMMsgBase> &msgList,
01378                                               KMFilter *filter )
01379   : KMCommand( parent, msgList ), mFilter( filter  )
01380 {
01381 }
01382 
01383 KMCommand::Result KMFilterActionCommand::execute()
01384 {
01385   QPtrList<KMMessage> msgList = retrievedMsgs();
01386 
01387   for (KMMessage *msg = msgList.first(); msg; msg = msgList.next())
01388     if( msg->parent() )
01389       kmkernel->filterMgr()->tempOpenFolder(msg->parent());
01390 
01391   for (KMMessage *msg = msgList.first(); msg; msg = msgList.next()) {
01392     msg->setTransferInProgress(false);
01393 
01394     int filterResult = kmkernel->filterMgr()->process(msg, mFilter);
01395     if (filterResult == 2) {
01396       // something went horribly wrong (out of space?)
01397       perror("Critical error");
01398       kmkernel->emergencyExit( i18n("Not enough free disk space?" ));
01399     }
01400     msg->setTransferInProgress(true);
01401   }
01402 
01403   return OK;
01404 }
01405 
01406 
01407 KMMetaFilterActionCommand::KMMetaFilterActionCommand( KMFilter *filter,
01408                                                       KMHeaders *headers,
01409                                                       KMMainWidget *main )
01410     : QObject( main ),
01411       mFilter( filter ), mHeaders( headers ), mMainWidget( main )
01412 {
01413 }
01414 
01415 void KMMetaFilterActionCommand::start()
01416 {
01417 #if 0 // use action scheduler
01418   KMFilterMgr::FilterSet set = KMFilterMgr::All;
01419   QPtrList<KMFilter> filters;
01420   filters.append( mFilter );
01421   ActionScheduler *scheduler = new ActionScheduler( set, filters, mHeaders );
01422   scheduler->setAlwaysMatch( true );
01423   scheduler->setAutoDestruct( true );
01424 
01425   int contentX, contentY;
01426   KMHeaderItem *nextItem = mHeaders->prepareMove( &contentX, &contentY );
01427   QPtrList<KMMsgBase> msgList = *mHeaders->selectedMsgs(true);
01428   mHeaders->finalizeMove( nextItem, contentX, contentY );
01429 
01430 
01431   for (KMMsgBase *msg = msgList.first(); msg; msg = msgList.next())
01432     scheduler->execFilters( msg );
01433 #else
01434   KMCommand *filterCommand = new KMFilterActionCommand( mMainWidget,
01435   *mHeaders->selectedMsgs(), mFilter);
01436   filterCommand->start();
01437   int contentX, contentY;
01438   KMHeaderItem *item = mHeaders->prepareMove( &contentX, &contentY );
01439   mHeaders->finalizeMove( item, contentX, contentY );
01440 #endif
01441 }
01442 
01443 
01444 KMMailingListFilterCommand::KMMailingListFilterCommand( QWidget *parent,
01445                                                         KMMessage *msg )
01446   : KMCommand( parent, msg )
01447 {
01448 }
01449 
01450 KMCommand::Result KMMailingListFilterCommand::execute()
01451 {
01452   QCString name;
01453   QString value;
01454   KMMessage *msg = retrievedMessage();
01455   if (!msg)
01456     return Failed;
01457 
01458   if ( !MailingList::name( msg, name, value ).isEmpty() ) {
01459     kmkernel->filterMgr()->createFilter( name, value );
01460     return OK;
01461   }
01462   else
01463     return Failed;
01464 }
01465 
01466 
01467 void KMMenuCommand::folderToPopupMenu(bool move,
01468   QObject *receiver, KMMenuToFolder *aMenuToFolder, QPopupMenu *menu )
01469 {
01470   while ( menu->count() )
01471   {
01472     QPopupMenu *popup = menu->findItem( menu->idAt( 0 ) )->popup();
01473     if (popup)
01474       delete popup;
01475     else
01476       menu->removeItemAt( 0 );
01477   }
01478 
01479   if (!kmkernel->imapFolderMgr()->dir().first() &&
01480       !kmkernel->dimapFolderMgr()->dir().first())
01481   { // only local folders
01482     makeFolderMenu( &kmkernel->folderMgr()->dir(), move,
01483                     receiver, aMenuToFolder, menu );
01484   } else {
01485     // operate on top-level items
01486     QPopupMenu* subMenu = new QPopupMenu(menu);
01487     makeFolderMenu( &kmkernel->folderMgr()->dir(),
01488                     move, receiver, aMenuToFolder, subMenu );
01489     menu->insertItem( i18n( "Local Folders" ), subMenu );
01490     KMFolderDir* fdir = &kmkernel->imapFolderMgr()->dir();
01491     for (KMFolderNode *node = fdir->first(); node; node = fdir->next()) {
01492       if (node->isDir())
01493         continue;
01494       subMenu = new QPopupMenu(menu);
01495       makeFolderMenu( node, move, receiver, aMenuToFolder, subMenu );
01496       menu->insertItem( node->label(), subMenu );
01497     }
01498     fdir = &kmkernel->dimapFolderMgr()->dir();
01499     for (KMFolderNode *node = fdir->first(); node; node = fdir->next()) {
01500       if (node->isDir())
01501         continue;
01502       subMenu = new QPopupMenu(menu);
01503       makeFolderMenu( node, move, receiver, aMenuToFolder, subMenu );
01504       menu->insertItem( node->label(), subMenu );
01505     }
01506   }
01507 }
01508 
01509 void KMMenuCommand::makeFolderMenu(KMFolderNode* node, bool move,
01510   QObject *receiver, KMMenuToFolder *aMenuToFolder, QPopupMenu *menu )
01511 {
01512   // connect the signals
01513   if (move)
01514   {
01515     disconnect(menu, SIGNAL(activated(int)), receiver,
01516            SLOT(moveSelectedToFolder(int)));
01517     connect(menu, SIGNAL(activated(int)), receiver,
01518              SLOT(moveSelectedToFolder(int)));
01519   } else {
01520     disconnect(menu, SIGNAL(activated(int)), receiver,
01521            SLOT(copySelectedToFolder(int)));
01522     connect(menu, SIGNAL(activated(int)), receiver,
01523              SLOT(copySelectedToFolder(int)));
01524   }
01525 
01526   KMFolder *folder = 0;
01527   KMFolderDir *folderDir = 0;
01528   if (node->isDir()) {
01529     folderDir = static_cast<KMFolderDir*>(node);
01530   } else {
01531     folder = static_cast<KMFolder*>(node);
01532     folderDir = folder->child();
01533   }
01534 
01535   if (folder && !folder->noContent())
01536   {
01537     int menuId;
01538     if (move)
01539       menuId = menu->insertItem(i18n("Move to This Folder"));
01540     else
01541       menuId = menu->insertItem(i18n("Copy to This Folder"));
01542     aMenuToFolder->insert( menuId, folder );
01543     menu->setItemEnabled( menuId, !folder->isReadOnly() );
01544     menu->insertSeparator();
01545   }
01546 
01547   if (!folderDir)
01548     return;
01549 
01550   for (KMFolderNode *it = folderDir->first(); it; it = folderDir->next() ) {
01551     if (it->isDir())
01552       continue;
01553     KMFolder *child = static_cast<KMFolder*>(it);
01554     QString label = child->label();
01555     label.replace("&","&&");
01556     if (child->child() && child->child()->first()) {
01557       // descend
01558       QPopupMenu *subMenu = new QPopupMenu(menu, "subMenu");
01559       makeFolderMenu( child, move, receiver,
01560                       aMenuToFolder, subMenu );
01561       menu->insertItem( label, subMenu );
01562     } else {
01563       // insert an item
01564       int menuId = menu->insertItem( label );
01565       aMenuToFolder->insert( menuId, child );
01566       menu->setItemEnabled( menuId, !child->isReadOnly() );
01567     }
01568   }
01569   return;
01570 }
01571 
01572 
01573 KMCopyCommand::KMCopyCommand( KMFolder* destFolder,
01574                               const QPtrList<KMMsgBase> &msgList )
01575 :mDestFolder( destFolder ), mMsgList( msgList )
01576 {
01577 }
01578 
01579 KMCopyCommand::KMCopyCommand( KMFolder* destFolder, KMMessage * msg )
01580   :mDestFolder( destFolder )
01581 {
01582   mMsgList.append( &msg->toMsgBase() );
01583 }
01584 
01585 KMCommand::Result KMCopyCommand::execute()
01586 {
01587   KMMsgBase *msgBase;
01588   KMMessage *msg, *newMsg;
01589   int idx = -1;
01590   bool isMessage;
01591   QPtrList<KMMessage> list;
01592 
01593   if (mDestFolder && mDestFolder->open() != 0)
01594     return Failed;
01595 
01596   KCursorSaver busy(KBusyPtr::busy());
01597 
01598   for (msgBase = mMsgList.first(); msgBase; msgBase = mMsgList.next() )
01599   {
01600     KMFolder *srcFolder = msgBase->parent();
01601     if (isMessage = msgBase->isMessage())
01602     {
01603       msg = static_cast<KMMessage*>(msgBase);
01604     } else {
01605       idx = srcFolder->find(msgBase);
01606       assert(idx != -1);
01607       msg = srcFolder->getMsg(idx);
01608     }
01609 
01610     if (srcFolder &&
01611         (srcFolder->folderType()== KMFolderTypeImap) &&
01612         (mDestFolder->folderType() == KMFolderTypeImap) &&
01613         (static_cast<KMFolderImap*>(srcFolder->storage())->account() ==
01614          static_cast<KMFolderImap*>(mDestFolder->storage())->account()))
01615     {
01616       list.append(msg);
01617     } else {
01618       newMsg = new KMMessage;
01619       newMsg->setComplete(msg->isComplete());
01620       // make sure the attachment state is only calculated when it's complete
01621       if (!newMsg->isComplete())
01622         newMsg->setReadyToShow(false);
01623       newMsg->fromString(msg->asString());
01624       newMsg->setStatus(msg->status());
01625 
01626       if (srcFolder && !newMsg->isComplete())
01627       {
01628         newMsg->setParent(msg->parent());
01629         FolderJob *job = srcFolder->createJob(newMsg);
01630         job->setCancellable( false );
01631         connect(job, SIGNAL(messageRetrieved(KMMessage*)),
01632                 mDestFolder, SLOT(reallyAddCopyOfMsg(KMMessage*)));
01633         // msg musn't be deleted
01634         newMsg->setTransferInProgress(true);
01635         job->start();
01636       } else {
01637         int rc, index;
01638         mDestFolder->open();
01639         rc = mDestFolder->addMsg(newMsg, &index);
01640         if (rc == 0 && index != -1)
01641           mDestFolder->unGetMsg( mDestFolder->count() - 1 );
01642         mDestFolder->close();
01643       }
01644     }
01645 
01646     if (!isMessage && list.isEmpty())
01647     {
01648       assert(idx != -1);
01649       srcFolder->unGetMsg( idx );
01650     }
01651 
01652   } // end for
01653   mDestFolder->close();
01654 
01655 //TODO: Get rid of the other cases just use this one for all types of folder
01656 //TODO: requires adding copyMsg and getFolder methods to KMFolder.h
01657 
01658   if (!list.isEmpty())
01659   {
01660     // copy the message(s); note: the list is empty afterwards!
01661     KMFolderImap *imapDestFolder = static_cast<KMFolderImap*>(mDestFolder->storage());
01662     imapDestFolder->copyMsg(list);
01663     imapDestFolder->getFolder();
01664   }
01665 
01666   return OK;
01667 }
01668 
01669 
01670 KMMoveCommand::KMMoveCommand( KMFolder* destFolder,
01671                               const QPtrList<KMMsgBase> &msgList)
01672   :mDestFolder( destFolder ), mMsgList( msgList ), mProgressItem( 0 )
01673 {
01674   setDeletesItself( true );
01675 }
01676 
01677 KMMoveCommand::KMMoveCommand( KMFolder* destFolder,
01678                               KMMessage *msg )
01679   :mDestFolder( destFolder ), mProgressItem( 0 )
01680 {
01681   setDeletesItself( true );
01682   mMsgList.append( &msg->toMsgBase() );
01683 }
01684 
01685 KMMoveCommand::KMMoveCommand( KMFolder* destFolder,
01686                               KMMsgBase *msgBase )
01687   :mDestFolder( destFolder ), mProgressItem( 0 )
01688 {
01689   setDeletesItself( true );
01690   mMsgList.append( msgBase );
01691 }
01692 
01693 KMMoveCommand::KMMoveCommand( Q_UINT32 )
01694 :mProgressItem( 0 )
01695 {
01696   setDeletesItself( true );
01697 }
01698 
01699 KMCommand::Result KMMoveCommand::execute()
01700 {
01701   setEmitsCompletedItself( true );
01702   typedef QMap< KMFolder*, QPtrList<KMMessage>* > FolderToMessageListMap;
01703   FolderToMessageListMap folderDeleteList;
01704 
01705   if (mDestFolder && mDestFolder->open() != 0) {
01706     completeMove( Failed );
01707     return Failed;
01708   }
01709   KCursorSaver busy(KBusyPtr::busy());
01710 
01711   // TODO set SSL state according to source and destfolder connection?
01712   Q_ASSERT( !mProgressItem );
01713   mProgressItem =
01714      ProgressManager::createProgressItem (
01715          "move"+ProgressManager::getUniqueID(),
01716          mDestFolder ? i18n( "Moving messages" ) : i18n( "Deleting messages" ) );
01717   connect( mProgressItem, SIGNAL( progressItemCanceled( ProgressItem* ) ),
01718            this, SLOT( slotMoveCanceled() ) );
01719 
01720   KMMessage *msg;
01721   KMMsgBase *msgBase;
01722   int rc = 0;
01723   int index;
01724   QPtrList<KMMessage> list;
01725   int undoId = -1;
01726 
01727   if (mDestFolder) {
01728     connect (mDestFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
01729              this, SLOT(slotMsgAddedToDestFolder(KMFolder*, Q_UINT32)));
01730 
01731   }
01732   for ( msgBase=mMsgList.first(); msgBase; msgBase=mMsgList.next() ) {
01733     mLostBoys.append( msgBase->getMsgSerNum() );
01734   }
01735   mProgressItem->setTotalItems( mMsgList.count() );
01736 
01737   for (msgBase=mMsgList.first(); msgBase && !rc; msgBase=mMsgList.next()) {
01738     KMFolder *srcFolder = msgBase->parent();
01739     if (srcFolder == mDestFolder)
01740       continue;
01741     bool undo = msgBase->enableUndo();
01742     int idx = srcFolder->find(msgBase);
01743     assert(idx != -1);
01744     if ( msgBase->isMessage() )
01745       msg = static_cast<KMMessage*>(msgBase);
01746     else
01747       msg = srcFolder->getMsg(idx);
01748 
01749     if ( msg->transferInProgress() &&
01750          srcFolder->folderType() == KMFolderTypeImap )
01751     {
01752       // cancel the download
01753       msg->setTransferInProgress( false, true );
01754       static_cast<KMFolderImap*>(srcFolder->storage())->ignoreJobsForMessage( msg );
01755     }
01756 
01757     if (mDestFolder) {
01758       if (mDestFolder->folderType() == KMFolderTypeImap) {
01759         /* If we are moving to an imap folder, connect to it's completed
01760          * signal so we notice when all the mails should have showed up in it
01761          * but haven't for some reason. */
01762         KMFolderImap *imapFolder = static_cast<KMFolderImap*> ( mDestFolder->storage() );
01763         disconnect (imapFolder, SIGNAL(folderComplete( KMFolderImap*, bool )),
01764                  this, SLOT(slotImapFolderCompleted( KMFolderImap*, bool )));
01765 
01766         connect (imapFolder, SIGNAL(folderComplete( KMFolderImap*, bool )),
01767                  this, SLOT(slotImapFolderCompleted( KMFolderImap*, bool )));
01768         list.append(msg);
01769       } else {
01770         // We are moving to a local folder.
01771         mDestFolder->open();
01772         rc = mDestFolder->moveMsg(msg, &index);
01773         if (rc == 0 && index != -1) {
01774           KMMsgBase *mb = mDestFolder->unGetMsg( mDestFolder->count() - 1 );
01775           if (undo && mb)
01776           {
01777             if ( undoId == -1 )
01778               undoId = kmkernel->undoStack()->newUndoAction( srcFolder, mDestFolder );
01779             kmkernel->undoStack()->addMsgToAction( undoId, mb->getMsgSerNum() );
01780           }
01781           mDestFolder->close();
01782         } else if (rc != 0) {
01783           // Something  went wrong. Stop processing here, it is likely that the
01784           // other moves would fail as well.
01785           completeMove( Failed );
01786           mDestFolder->close();
01787           return Failed;
01788         }
01789       }
01790     } else {
01791       // really delete messages that are already in the trash folder or if
01792       // we are really, really deleting, not just moving to trash
01793       if (srcFolder->folderType() == KMFolderTypeImap) {
01794         if (!folderDeleteList[srcFolder])
01795           folderDeleteList[srcFolder] = new QPtrList<KMMessage>;
01796         folderDeleteList[srcFolder]->append( msg );
01797       } else {
01798         srcFolder->removeMsg(idx);
01799         delete msg;
01800       }
01801     }
01802   }
01803   if (!list.isEmpty() && mDestFolder) {
01804        mDestFolder->moveMsg(list, &index);
01805   } else {
01806     FolderToMessageListMap::Iterator it;
01807     for ( it = folderDeleteList.begin(); it != folderDeleteList.end(); ++it ) {
01808       it.key()->removeMsg(*it.data());
01809       delete it.data();
01810     }
01811     /* The list is empty, which means that either all messages were to be
01812      * deleted, which is done above, or all of them were already in this folder.
01813      * In both cases make sure a completed() signal is emitted nonetheless. */
01814     KMFolder *srcFolder = 0;
01815     if ( mMsgList.first() ) {
01816       srcFolder = mMsgList.first()->parent();
01817       if ( mDestFolder && mDestFolder == srcFolder ) {
01818         completeMove( OK );
01819       }
01820     }
01821     if ( !mDestFolder ) {
01822       completeMove( OK );
01823     }
01824   }
01825 
01826   return OK;
01827 }
01828 
01829 void KMMoveCommand::slotImapFolderCompleted(KMFolderImap *, bool success)
01830 {
01831   if ( success ) {
01832     // the folder was checked successfully but we were still called, so check
01833     // if we are still waiting for messages to show up. If so, uidValidity
01834     // changed, or something else went wrong. Clean up.
01835 
01836     /* Unfortunately older UW imap servers change uid validity for each put job.
01837      * Yes, it is really that broken. *sigh* So we cannot report error here, I guess. */
01838     if ( !mLostBoys.isEmpty() ) {
01839       kdDebug(5006) <<  "### Not all moved messages reported back that they were " << endl
01840                     <<  "### added to the target folder. Did uidValidity change? " << endl;
01841     }
01842     completeMove( OK );
01843   } else {
01844     // Should we inform the user here or leave that to the caller?
01845     completeMove( Failed );
01846   }
01847 }
01848 
01849 void KMMoveCommand::slotMsgAddedToDestFolder(KMFolder *folder, Q_UINT32 serNum)
01850 {
01851   if (folder != mDestFolder || !mLostBoys.contains( serNum ) ) {
01852     kdDebug(5006) << "KMMoveCommand::msgAddedToDestFolder different "
01853                      "folder or invalid serial number." << endl;
01854     return;
01855   }
01856   mLostBoys.remove(serNum);
01857   if ( mLostBoys.isEmpty() ) {
01858     // we are done. All messages transferred to the host succesfully
01859     if (mDestFolder && mDestFolder->folderType() != KMFolderTypeImap) {
01860       mDestFolder->sync();
01861     }
01862     completeMove( OK );
01863   } else {
01864     mProgressItem->incCompletedItems();
01865     mProgressItem->updateProgress();
01866   }
01867 }
01868 
01869 void KMMoveCommand::completeMove( Result result )
01870 {
01871   if ( mDestFolder )
01872     mDestFolder->close();
01873   while ( !mOpenedFolders.empty() ) {
01874     KMFolder *folder = mOpenedFolders.back();
01875     mOpenedFolders.pop_back();
01876     folder->close();
01877   }
01878   if ( mProgressItem )
01879     mProgressItem->setComplete();
01880   setResult( result );
01881   emit completed( this );
01882   deleteLater();
01883 }
01884 
01885 void KMMoveCommand::slotMoveCanceled()
01886 {
01887   completeMove( Canceled );
01888 }
01889 
01890 // srcFolder doesn't make much sense for searchFolders
01891 KMDeleteMsgCommand::KMDeleteMsgCommand( KMFolder* srcFolder,
01892   const QPtrList<KMMsgBase> &msgList )
01893 :KMMoveCommand( findTrashFolder( srcFolder ), msgList)
01894 {
01895   srcFolder->open();
01896   mOpenedFolders.push_back( srcFolder );
01897 }
01898 
01899 KMDeleteMsgCommand::KMDeleteMsgCommand( KMFolder* srcFolder, KMMessage * msg )
01900 :KMMoveCommand( findTrashFolder( srcFolder ), msg)
01901 {
01902   srcFolder->open();
01903   mOpenedFolders.push_back( srcFolder );
01904 }
01905 
01906 KMDeleteMsgCommand::KMDeleteMsgCommand( Q_UINT32 sernum )
01907 :KMMoveCommand( sernum )
01908 {
01909   KMFolder *srcFolder;
01910   int idx;
01911   kmkernel->msgDict()->getLocation( sernum, &srcFolder, &idx );
01912   KMMsgBase *msg = srcFolder->getMsgBase( idx );
01913   srcFolder->open();
01914   mOpenedFolders.push_back( srcFolder );
01915   addMsg( msg );
01916   setDestFolder( findTrashFolder( srcFolder ) );
01917 }
01918 
01919 KMFolder * KMDeleteMsgCommand::findTrashFolder( KMFolder * folder )
01920 {
01921   KMFolder* trash = folder->trashFolder();
01922   if( !trash )
01923     trash = kmkernel->trashFolder();
01924   if( trash != folder )
01925     return trash;
01926   return 0;
01927 }
01928 
01929 
01930 KMUrlClickedCommand::KMUrlClickedCommand( const KURL &url, uint identity,
01931   KMReaderWin *readerWin, bool htmlPref, KMMainWidget *mainWidget )
01932   :mUrl( url ), mIdentity( identity ), mReaderWin( readerWin ),
01933    mHtmlPref( htmlPref ), mMainWidget( mainWidget )
01934 {
01935 }
01936 
01937 KMCommand::Result KMUrlClickedCommand::execute()
01938 {
01939   KMComposeWin *win;
01940   KMMessage* msg;
01941 
01942   if (mUrl.protocol() == "mailto")
01943   {
01944     msg = new KMMessage;
01945     msg->initHeader(mIdentity);
01946     msg->setCharset("utf-8");
01947     msg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );
01948     QString query=mUrl.query();
01949     while (!query.isEmpty()) {
01950       QString queryPart;
01951       int secondQuery = query.find('?',1);
01952       if (secondQuery != -1)
01953         queryPart = query.left(secondQuery);
01954       else
01955         queryPart = query;
01956       query = query.mid(queryPart.length());
01957 
01958       if (queryPart.left(9) == "?subject=")
01959         msg->setSubject( KURL::decode_string(queryPart.mid(9)) );
01960       else if (queryPart.left(6) == "?body=")
01961         // It is correct to convert to latin1() as URL should not contain
01962         // anything except ascii.
01963         msg->setBody( KURL::decode_string(queryPart.mid(6)).latin1() );
01964       else if (queryPart.left(4) == "?cc=")
01965         msg->setCc( KURL::decode_string(queryPart.mid(4)) );
01966     }
01967 
01968     win = new KMComposeWin(msg, mIdentity);
01969     win->setCharset("", TRUE);
01970     win->show();
01971   }
01972   else if ( mUrl.protocol() == "im" )
01973   {
01974     kmkernel->imProxy()->chatWithContact( mUrl.path() );
01975   }
01976   else if ((mUrl.protocol() == "http") || (mUrl.protocol() == "https") ||
01977            (mUrl.protocol() == "ftp") || (mUrl.protocol() == "file") ||
01978            (mUrl.protocol() == "ftps") || (mUrl.protocol() == "sftp" ) ||
01979            (mUrl.protocol() == "help") || (mUrl.protocol() == "vnc") ||
01980            (mUrl.protocol() == "smb"))
01981   {
01982     KPIM::BroadcastStatus::instance()->setStatusMsg( i18n("Opening URL..."));
01983     KMimeType::Ptr mime = KMimeType::findByURL( mUrl );
01984     if (mime->name() == "application/x-desktop" ||
01985         mime->name() == "application/x-executable" ||
01986         mime->name() == "application/x-msdos-program" ||
01987         mime->name() == "application/x-shellscript" )
01988     {
01989       if (KMessageBox::warningYesNo( 0, i18n( "<qt>Do you really want to execute <b>%1</b>?</qt>" )
01990         .arg( mUrl.prettyURL() ) ) != KMessageBox::Yes)
01991         return Canceled;
01992     }
01993     (void) new KRun( mUrl );
01994   }
01995   else
01996     return Failed;
01997 
01998   return OK;
01999 }
02000 
02001 KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( QWidget *parent, KMMessage *msg )
02002   : KMCommand( parent, msg ), mImplicitAttachments( true ), mEncoded( false )
02003 {
02004 }
02005 
02006 KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( QWidget *parent, const QPtrList<KMMsgBase>& msgs )
02007   : KMCommand( parent, msgs ), mImplicitAttachments( true ), mEncoded( false )
02008 {
02009 }
02010 
02011 KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( QWidget *parent, QPtrList<partNode>& attachments,
02012                                                     KMMessage *msg, bool encoded )
02013   : KMCommand( parent, msg ), mImplicitAttachments( false ),
02014     mEncoded( encoded )
02015 {
02016   // do not load the complete message but only parts
02017   msg->setComplete( true );
02018   for ( QPtrListIterator<partNode> it( attachments ); it.current(); ++it ) {
02019     mAttachmentMap.insert( it.current(), msg );
02020   }
02021 }
02022 
02023 KMCommand::Result KMSaveAttachmentsCommand::execute()
02024 {
02025   setEmitsCompletedItself( true );
02026   if ( mImplicitAttachments ) {
02027     QPtrList<KMMessage> msgList = retrievedMsgs();
02028     KMMessage *msg;
02029     for ( QPtrListIterator<KMMessage> itr( msgList );
02030           ( msg = itr.current() );
02031           ++itr ) {
02032       partNode *rootNode = partNode::fromMessage( msg );
02033       for ( partNode *child = rootNode; child;
02034             child = child->firstChild() ) {
02035         for ( partNode *node = child; node; node = node->nextSibling() ) {
02036           if ( node->type() != DwMime::kTypeMultipart )
02037             mAttachmentMap.insert( node, msg );
02038         }
02039       }
02040     }
02041   }
02042   setDeletesItself( true );
02043   // load all parts
02044   KMLoadPartsCommand *command = new KMLoadPartsCommand( mAttachmentMap );
02045   connect( command, SIGNAL( partsRetrieved() ),
02046            this, SLOT( slotSaveAll() ) );
02047   command->start();
02048 
02049   return OK;
02050 }
02051 
02052 void KMSaveAttachmentsCommand::slotSaveAll()
02053 {
02054   // now that all message parts have been retrieved, remove all parts which
02055   // don't represent an attachment if they were not explicitely passed in the
02056   // c'tor
02057   if ( mImplicitAttachments ) {
02058     for ( PartNodeMessageMap::iterator it = mAttachmentMap.begin();
02059           it != mAttachmentMap.end(); ) {
02060       // only body parts which have a filename or a name parameter (except for
02061       // the root node for which name is set to the message's subject) are
02062       // considered attachments
02063       if ( it.key()->msgPart().fileName().stripWhiteSpace().isEmpty() &&
02064            ( it.key()->msgPart().name().stripWhiteSpace().isEmpty() ||
02065              !it.key()->parentNode() ) ) {
02066         PartNodeMessageMap::iterator delIt = it;
02067         ++it;
02068         mAttachmentMap.remove( delIt );
02069       }
02070       else
02071         ++it;
02072     }
02073     if ( mAttachmentMap.isEmpty() ) {
02074       KMessageBox::information( 0, i18n("Found no attachments to save.") );
02075       setResult( OK ); // The user has already been informed.
02076       emit completed( this );
02077       delete this;
02078       return;
02079     }
02080   }
02081 
02082   KURL url, dirUrl;
02083   if ( mAttachmentMap.count() > 1 ) {
02084     // get the dir
02085     KFileDialog fdlg( ":saveAttachments", QString::null, parentWidget(),
02086                       "save attachments dialog", true );
02087     fdlg.setCaption( i18n("Save Attachments To") );
02088     fdlg.setOperationMode( KFileDialog::Saving );
02089     fdlg.setMode( (unsigned int) KFile::Directory );
02090     if ( fdlg.exec() == QDialog::Rejected || !fdlg.selectedURL().isValid() ) {
02091       setResult( Canceled );
02092       emit completed( this );
02093       delete this;
02094       return;
02095     }
02096     dirUrl = fdlg.selectedURL();
02097   }
02098   else {
02099     // only one item, get the desired filename
02100     partNode *node = mAttachmentMap.begin().key();
02101     // replace all ':' with '_' because ':' isn't allowed on FAT volumes
02102     QString s =
02103       node->msgPart().fileName().stripWhiteSpace().replace( ':', '_' );
02104     if ( s.isEmpty() )
02105       s = node->msgPart().name().stripWhiteSpace().replace( ':', '_' );
02106     if ( s.isEmpty() )
02107       s = i18n("filename for an unnamed attachment", "attachment.1");
02108     url = KFileDialog::getSaveURL( s, QString::null, parentWidget(),
02109                                    QString::null );
02110     if ( url.isEmpty() ) {
02111       setResult( Canceled );
02112       emit completed( this );
02113       delete this;
02114       return;
02115     }
02116   }
02117 
02118   Result globalResult = OK;
02119   int unnamedAtmCount = 0;
02120   for ( PartNodeMessageMap::const_iterator it = mAttachmentMap.begin();
02121         it != mAttachmentMap.end();
02122         ++it ) {
02123     KURL curUrl;
02124     if ( !dirUrl.isEmpty() ) {
02125       curUrl = dirUrl;
02126       QString s =
02127         it.key()->msgPart().fileName().stripWhiteSpace().replace( ':', '_' );
02128       if ( s.isEmpty() )
02129         s = it.key()->msgPart().name().stripWhiteSpace().replace( ':', '_' );
02130       if ( s.isEmpty() ) {
02131         ++unnamedAtmCount;
02132         s = i18n("filename for the %1-th unnamed attachment",
02133                  "attachment.%1")
02134             .arg( unnamedAtmCount );
02135       }
02136       curUrl.setFileName( s );
02137     } else {
02138       curUrl = url;
02139     }
02140 
02141     if ( !curUrl.isEmpty() ) {
02142       if ( KIO::NetAccess::exists( curUrl, false, parentWidget() ) ) {
02143         if ( KMessageBox::warningContinueCancel( parentWidget(),
02144               i18n( "A file named %1 already exists. Do you want to overwrite it?" )
02145               .arg( curUrl.fileName() ),
02146               i18n( "File Already Exists" ), i18n("Overwrite") ) == KMessageBox::Cancel) {
02147           continue;
02148         }
02149       }
02150       // save
02151       const Result result = saveItem( it.key(), curUrl );
02152       if ( result != OK )
02153         globalResult = result;
02154     }
02155   }
02156   setResult( globalResult );
02157   emit completed( this );
02158   delete this;
02159 }
02160 
02161 KMCommand::Result KMSaveAttachmentsCommand::saveItem( partNode *node,
02162                                                       const KURL& url )
02163 {
02164   bool bSaveEncrypted = false;
02165   bool bEncryptedParts = node->encryptionState() != KMMsgNotEncrypted;
02166   if( bEncryptedParts )
02167     if( KMessageBox::questionYesNo( parentWidget(),
02168           i18n( "The part %1 of the message is encrypted. Do you want to keep the encryption when saving?" ).
02169           arg( url.fileName() ),
02170           i18n( "KMail Question" ) ) ==
02171         KMessageBox::Yes )
02172       bSaveEncrypted = true;
02173 
02174   bool bSaveWithSig = true;
02175   if( node->signatureState() != KMMsgNotSigned )
02176     if( KMessageBox::questionYesNo( parentWidget(),
02177           i18n( "The part %1 of the message is signed. Do you want to keep the signature when saving?" ).
02178           arg( url.fileName() ),
02179           i18n( "KMail Question" ) ) !=
02180         KMessageBox::Yes )
02181       bSaveWithSig = false;
02182 
02183   QByteArray data;
02184   if ( mEncoded )
02185   {
02186     // This does not decode the Message Content-Transfer-Encoding
02187     // but saves the _original_ content of the message part
02188     QCString cstr( node->msgPart().body() );
02189     data = cstr;
02190     data.resize(data.size() - 1);
02191   }
02192   else
02193   {
02194     if( bSaveEncrypted || !bEncryptedParts) {
02195       partNode *dataNode = node;
02196       QCString rawReplyString;
02197       bool gotRawReplyString = false;
02198       if( !bSaveWithSig ) {
02199         if( DwMime::kTypeMultipart == node->type() &&
02200             DwMime::kSubtypeSigned == node->subType() ){
02201           // carefully look for the part that is *not* the signature part:
02202           if( node->findType( DwMime::kTypeApplication,
02203                 DwMime::kSubtypePgpSignature,
02204                 TRUE, false ) ){
02205             dataNode = node->findTypeNot( DwMime::kTypeApplication,
02206                 DwMime::kSubtypePgpSignature,
02207                 TRUE, false );
02208           }else if( node->findType( DwMime::kTypeApplication,
02209                 DwMime::kSubtypePkcs7Mime,
02210                 TRUE, false ) ){
02211             dataNode = node->findTypeNot( DwMime::kTypeApplication,
02212                 DwMime::kSubtypePkcs7Mime,
02213                 TRUE, false );
02214           }else{
02215             dataNode = node->findTypeNot( DwMime::kTypeMultipart,
02216                 DwMime::kSubtypeUnknown,
02217                 TRUE, false );
02218           }
02219     }else{
02220       ObjectTreeParser otp( 0, 0, false, false, false );
02221 
02222       // process this node and all it's siblings and descendants
02223       dataNode->setProcessed( false, true );
02224       otp.parseObjectTree( dataNode );
02225 
02226       rawReplyString = otp.rawReplyString();
02227       gotRawReplyString = true;
02228         }
02229       }
02230       QByteArray cstr = gotRawReplyString
02231                          ? rawReplyString
02232                          : dataNode->msgPart().bodyDecodedBinary();
02233       data = cstr;
02234       size_t size = cstr.size();
02235       if ( dataNode->msgPart().type() == DwMime::kTypeText ) {
02236         // convert CRLF to LF before writing text attachments to disk
02237         size = KMFolder::crlf2lf( cstr.data(), size );
02238       }
02239       data.resize( size );
02240     }
02241   }
02242   QDataStream ds;
02243   QFile file;
02244   KTempFile tf;
02245   tf.setAutoDelete( true );
02246   if ( url.isLocalFile() )
02247   {
02248     // save directly
02249     file.setName( url.path() );
02250     if ( !file.open( IO_WriteOnly ) )
02251     {
02252       KMessageBox::error( parentWidget(),
02253           i18n( "%2 is detailed error description",
02254             "Could not write the file %1:\n%2" )
02255           .arg( file.name() )
02256           .arg( QString::fromLocal8Bit( strerror( errno ) ) ),
02257           i18n( "KMail Error" ) );
02258       return Failed;
02259     }
02260     fchmod( file.handle(), S_IRUSR | S_IWUSR );
02261     ds.setDevice( &file );
02262   } else
02263   {
02264     // tmp file for upload
02265     ds.setDevice( tf.file() );
02266   }
02267 
02268   ds.writeRawBytes( data.data(), data.size() );
02269   if ( !url.isLocalFile() )
02270   {
02271     tf.close();
02272     if ( !KIO::NetAccess::upload( tf.name(), url, parentWidget() ) )
02273     {
02274       KMessageBox::error( parentWidget(),
02275           i18n( "Could not write the file %1." )
02276           .arg( url.path() ),
02277           i18n( "KMail Error" ) );
02278       return Failed;
02279     }
02280   } else
02281     file.close();
02282   return OK;
02283 }
02284 
02285 KMLoadPartsCommand::KMLoadPartsCommand( QPtrList<partNode>& parts, KMMessage *msg )
02286   : mNeedsRetrieval( 0 )
02287 {
02288   for ( QPtrListIterator<partNode> it( parts ); it.current(); ++it ) {
02289     mPartMap.insert( it.current(), msg );
02290   }
02291 }
02292 
02293 KMLoadPartsCommand::KMLoadPartsCommand( partNode *node, KMMessage *msg )
02294   : mNeedsRetrieval( 0 )
02295 {
02296   mPartMap.insert( node, msg );
02297 }
02298 
02299 KMLoadPartsCommand::KMLoadPartsCommand( PartNodeMessageMap& partMap )
02300   : mNeedsRetrieval( 0 ), mPartMap( partMap )
02301 {
02302 }
02303 
02304 void KMLoadPartsCommand::slotStart()
02305 {
02306   for ( PartNodeMessageMap::const_iterator it = mPartMap.begin();
02307         it != mPartMap.end();
02308         ++it ) {
02309     if ( !it.key()->msgPart().isComplete() &&
02310          !it.key()->msgPart().partSpecifier().isEmpty() ) {
02311       // incomplete part, so retrieve it first
02312       ++mNeedsRetrieval;
02313       KMFolder* curFolder = it.data()->parent();
02314       if ( curFolder ) {
02315         FolderJob *job =
02316           curFolder->createJob( it.data(), FolderJob::tGetMessage,
02317                                 0, it.key()->msgPart().partSpecifier() );
02318         job->setCancellable( false );
02319         connect( job, SIGNAL(messageUpdated(KMMessage*, QString)),
02320                  this, SLOT(slotPartRetrieved(KMMessage*, QString)) );
02321         job->start();
02322       } else
02323         kdWarning(5006) << "KMLoadPartsCommand - msg has no parent" << endl;
02324     }
02325   }
02326   if ( mNeedsRetrieval == 0 )
02327     execute();
02328 }
02329 
02330 void KMLoadPartsCommand::slotPartRetrieved( KMMessage *msg,
02331                                             QString partSpecifier )
02332 {
02333   DwBodyPart *part =
02334     msg->findDwBodyPart( msg->getFirstDwBodyPart(), partSpecifier );
02335   if ( part ) {
02336     // update the DwBodyPart in the partNode
02337     for ( PartNodeMessageMap::const_iterator it = mPartMap.begin();
02338           it != mPartMap.end();
02339           ++it ) {
02340       if ( it.key()->dwPart()->partId() == part->partId() )
02341         it.key()->setDwPart( part );
02342     }
02343   } else
02344     kdWarning(5006) << "KMLoadPartsCommand::slotPartRetrieved - could not find bodypart!" << endl;
02345   --mNeedsRetrieval;
02346   if ( mNeedsRetrieval == 0 )
02347     execute();
02348 }
02349 
02350 KMCommand::Result KMLoadPartsCommand::execute()
02351 {
02352   emit partsRetrieved();
02353   setResult( OK );
02354   emit completed( this );
02355   deleteLater();
02356   return OK;
02357 }
02358 
02359 KMResendMessageCommand::KMResendMessageCommand( QWidget *parent,
02360    KMMessage *msg )
02361   :KMCommand( parent, msg )
02362 {
02363 }
02364 
02365 KMCommand::Result KMResendMessageCommand::execute()
02366 {
02367   KMComposeWin *win;
02368   KMMessage *msg = retrievedMessage();
02369 
02370   KMMessage *newMsg = new KMMessage(*msg);
02371   newMsg->setCharset(msg->codec()->mimeName());
02372   // the message needs a new Message-Id
02373   newMsg->removeHeaderField( "Message-Id" );
02374   newMsg->setParent( 0 );
02375 
02376   // adds the new date to the message
02377   newMsg->removeHeaderField( "Date" );
02378 
02379   win = new KMComposeWin();
02380   win->setMsg(newMsg, false, true);
02381   win->show();
02382 
02383   return OK;
02384 }
02385 
02386 KMMailingListCommand::KMMailingListCommand( QWidget *parent, KMFolder *folder )
02387   : KMCommand( parent ), mFolder( folder )
02388 {
02389 }
02390 
02391 KMCommand::Result KMMailingListCommand::execute()
02392 {
02393   KURL::List lst = urls();
02394   QString handler = ( mFolder->mailingList().handler() == MailingList::KMail )
02395     ? "mailto" : "https";
02396 
02397   KMCommand *command = 0;
02398   for ( KURL::List::Iterator itr = lst.begin(); itr != lst.end(); ++itr ) {
02399     if ( handler == (*itr).protocol() ) {
02400       command = new KMUrlClickedCommand( *itr, mFolder->identity(), 0, false );
02401     }
02402   }
02403   if ( !command && !lst.empty() ) {
02404     command =
02405       new KMUrlClickedCommand( lst.first(), mFolder->identity(), 0, false );
02406   }
02407   if ( command ) {
02408     connect( command, SIGNAL( completed( KMCommand * ) ),
02409              this, SLOT( commandCompleted( KMCommand * ) ) );
02410     setDeletesItself( true );
02411     setEmitsCompletedItself( true );
02412     command->start();
02413     return OK;
02414   }
02415   return Failed;
02416 }
02417 
02418 void KMMailingListCommand::commandCompleted( KMCommand *command )
02419 {
02420   setResult( command->result() );
02421   emit completed( this );
02422   delete this;
02423 }
02424 
02425 KMMailingListPostCommand::KMMailingListPostCommand( QWidget *parent, KMFolder *folder )
02426   : KMMailingListCommand( parent, folder )
02427 {
02428 }
02429 KURL::List KMMailingListPostCommand::urls() const
02430 {
02431   return mFolder->mailingList().postURLS();
02432 }
02433 
02434 KMMailingListSubscribeCommand::KMMailingListSubscribeCommand( QWidget *parent, KMFolder *folder )
02435   : KMMailingListCommand( parent, folder )
02436 {
02437 }
02438 KURL::List KMMailingListSubscribeCommand::urls() const
02439 {
02440   return mFolder->mailingList().subscribeURLS();
02441 }
02442 
02443 KMMailingListUnsubscribeCommand::KMMailingListUnsubscribeCommand( QWidget *parent, KMFolder *folder )
02444   : KMMailingListCommand( parent, folder )
02445 {
02446 }
02447 KURL::List KMMailingListUnsubscribeCommand::urls() const
02448 {
02449   return mFolder->mailingList().unsubscribeURLS();
02450 }
02451 
02452 KMMailingListArchivesCommand::KMMailingListArchivesCommand( QWidget *parent, KMFolder *folder )
02453   : KMMailingListCommand( parent, folder )
02454 {
02455 }
02456 KURL::List KMMailingListArchivesCommand::urls() const
02457 {
02458   return mFolder->mailingList().archiveURLS();
02459 }
02460 
02461 KMMailingListHelpCommand::KMMailingListHelpCommand( QWidget *parent, KMFolder *folder )
02462   : KMMailingListCommand( parent, folder )
02463 {
02464 }
02465 KURL::List KMMailingListHelpCommand::urls() const
02466 {
02467   return mFolder->mailingList().helpURLS();
02468 }
02469 
02470 KMIMChatCommand::KMIMChatCommand( const KURL &url, KMMessage *msg )
02471   :mUrl( url ), mMessage( msg )
02472 {
02473 }
02474 
02475 KMCommand::Result KMIMChatCommand::execute()
02476 {
02477   kdDebug( 5006 ) << k_funcinfo << " URL is: " << mUrl << endl;
02478   // find UID for mail address
02479   KABC::AddressBook *addressBook = KABC::StdAddressBook::self();
02480   KABC::AddresseeList addresses = addressBook->findByEmail( KPIM::getEmailAddr( mUrl.path() ) ) ;
02481 
02482   // start chat
02483   if( addresses.count() == 1 ) {
02484     kmkernel->imProxy()->chatWithContact( addresses[0].uid() );
02485     return OK;
02486   }
02487   else
02488   {
02489     kdDebug( 5006 ) << "Didn't find exactly one addressee, couldn't tell who to chat to for that email address.  Count = " << addresses.count() << endl;
02490 
02491     QString apology;
02492     if ( addresses.isEmpty() )
02493       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." );
02494     else
02495     {
02496       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." );
02497       QStringList nameList;
02498       KABC::AddresseeList::const_iterator it = addresses.begin();
02499       KABC::AddresseeList::const_iterator end = addresses.end();
02500       for ( ; it != end; ++it )
02501       {
02502           nameList.append( (*it).realName() );
02503       }
02504       QString names = nameList.join( QString::fromLatin1( ",\n" ) );
02505       apology = apology.arg( names );
02506     }
02507 
02508     KMessageBox::sorry( parentWidget(), apology );
02509     return Failed;
02510   }
02511 }
KDE Logo
This file is part of the documentation for kmail Library Version 3.3.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Mon Apr 4 04:48:24 2005 by doxygen 1.3.9.1 written by Dimitri van Heesch, © 1997-2003