diff --git a/kmail/kmcommands.cpp b/kmail/kmcommands.cpp index f0146d8c88..85fbd6dc77 100644 --- a/kmail/kmcommands.cpp +++ b/kmail/kmcommands.cpp @@ -1,1608 +1,1598 @@ /* -*- mode: C++; c-file-style: "gnu" -*- This file is part of KMail, the KDE mail client. Copyright (c) 2002 Don Sanders Copyright (c) 2013 Laurent Montel KMail is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2, as published by the Free Software Foundation. KMail is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ // // This file implements various "command" classes. These command classes // are based on the command design pattern. // // Historically various operations were implemented as slots of KMMainWin. // This proved inadequate as KMail has multiple top level windows // (KMMainWin, KMReaderMainWin, SearchWindow, KMComposeWin) that may // benefit from using these operations. It is desirable that these // classes can operate without depending on or altering the state of // a KMMainWin, in fact it is possible no KMMainWin object even exists. // // Now these operations have been rewritten as KMCommand based classes, // making them independent of KMMainWin. // // The base command class KMCommand is async, which is a difference // from the conventional command pattern. As normal derived classes implement // the execute method, but client classes call start() instead of // calling execute() directly. start() initiates async operations, // and on completion of these operations calls execute() and then deletes // the command. (So the client must not construct commands on the stack). // // The type of async operation supported by KMCommand is retrieval // of messages from an IMAP server. #include "kmcommands.h" #include "widgets/collectionpane.h" #include "kernel/mailkernel.h" #include "util/mailutil.h" #include // link() #include #include #include #include #include #include #include #include #include #include // KIO headers #include #include #include #include #include #include #include #include "foldercollection.h" #include "messagecore/misc/mailinglist.h" #include "editor/composer.h" #include "mailcommon/filter/filteraction.h" #include "mailcommon/filter/filtermanager.h" #include "mailcommon/filter/mailfilter.h" #include "mailcommon/widgets/redirectdialog.h" #include "kmmainwidget.h" #include "undostack.h" #ifndef QT_NO_CURSOR #include "messageviewer/utils/kcursorsaver.h" #endif #include "messageviewer/viewer/objecttreeparser.h" #include "messageviewer/viewer/csshelper.h" #include "messageviewer/utils/util.h" #include "messageviewer/viewer/mailsourceviewer.h" #include "messageviewer/viewer/viewer.h" #include "messageviewer/header/headerstrategy.h" #include "messageviewer/header/headerstyle.h" #include "kmreadermainwin.h" #include "secondarywindow.h" using KMail::SecondaryWindow; #include "util.h" #include "misc/broadcaststatus.h" #include "settings/globalsettings.h" #include "utils/stringutil.h" #include "messageviewer/utils/autoqpointer.h" #include "messageviewer/settings/globalsettings.h" #include "messagecore/settings/globalsettings.h" #include #include #include #include #include #include #include #include #include #include using MailTransport::TransportManager; #include "messageviewer/viewer/nodehelper.h" #include "messageviewer/viewer/objecttreeemptysource.h" #include "messagecore/utils/stringutil.h" #include "messagecore/helpers/messagehelpers.h" #include "messagecomposer/sender/messagesender.h" #include "messagecomposer/helper/messagehelper.h" #include "messagecomposer/settings/messagecomposersettings.h" #include "messagecomposer/helper/messagefactory.h" using MessageComposer::MessageFactory; #include "progresswidget/progressmanager.h" using KPIM::ProgressManager; using KPIM::ProgressItem; #include using namespace KMime; #include "kleo/cryptobackend.h" #include "kleo/cryptobackendfactory.h" #include #include #include #include #include #include #include using namespace MailCommon; /// Small helper function to get the composer context from a reply static KMail::Composer::TemplateContext replyContext( MessageFactory::MessageReply reply ) { if ( reply.replyAll ) return KMail::Composer::ReplyToAll; else return KMail::Composer::Reply; } /// Helper to sanely show an error message for a job static void showJobError( KJob* job ) { assert(job); // we can be called from the KJob::kill, where we are no longer a KIO::Job // so better safe than sorry KIO::Job* kiojob = dynamic_cast(job); if( kiojob && kiojob->ui() ) kiojob->ui()->showErrorMessage(); else kWarning() << "There is no GUI delegate set for a kjob, and it failed with error:" << job->errorString(); } KMCommand::KMCommand( QWidget *parent ) : mCountMsgs(0), mResult( Undefined ), mDeletesItself( false ), mEmitsCompletedItself( false ), mParent( parent ) { } KMCommand::KMCommand( QWidget *parent, const Akonadi::Item &msg ) : mCountMsgs(0), mResult( Undefined ), mDeletesItself( false ), mEmitsCompletedItself( false ), mParent( parent ) { if ( msg.isValid() || msg.hasPayload() ) { mMsgList.append( msg ); } } KMCommand::KMCommand( QWidget *parent, const QList &msgList ) : mCountMsgs(0), mResult( Undefined ), mDeletesItself( false ), mEmitsCompletedItself( false ), mParent( parent ) { mMsgList = msgList; } KMCommand::~KMCommand() { } KMCommand::Result KMCommand::result() const { if ( mResult == Undefined ) { kDebug() << "mResult is Undefined"; } return mResult; } const QList KMCommand::retrievedMsgs() const { return mRetrievedMsgs; } Akonadi::Item KMCommand::retrievedMessage() const { if ( mRetrievedMsgs.isEmpty() ) return Akonadi::Item(); return *(mRetrievedMsgs.begin()); } QWidget *KMCommand::parentWidget() const { return mParent; } int KMCommand::mCountJobs = 0; void KMCommand::start() { connect( this, SIGNAL(messagesTransfered(KMCommand::Result)), this, SLOT(slotPostTransfer(KMCommand::Result)) ); if ( mMsgList.isEmpty() ) { emit messagesTransfered( OK ); return; } // Special case of operating on message that isn't in a folder const Akonadi::Item mb = mMsgList.first(); if ( ( mMsgList.count() == 1 ) && MessageCore::Util::isStandaloneMessage( mb ) ) { mRetrievedMsgs.append(mMsgList.takeFirst()); emit messagesTransfered( OK ); return; } // we can only retrieve items with a valid id foreach ( const Akonadi::Item &item, mMsgList ) { if ( !item.isValid() ) { emit messagesTransfered( Failed ); return; } } // transfer the selected messages first transferSelectedMsgs(); } void KMCommand::slotPostTransfer( KMCommand::Result result ) { disconnect( this, SIGNAL(messagesTransfered(KMCommand::Result)), this, SLOT(slotPostTransfer(KMCommand::Result)) ); if ( result == OK ) { result = execute(); } mResult = result; if ( !emitsCompletedItself() ) emit completed( this ); if ( !deletesItself() ) deleteLater(); } void KMCommand::transferSelectedMsgs() { // make sure no other transfer is active if (KMCommand::mCountJobs > 0) { emit messagesTransfered( Failed ); return; } bool complete = true; KMCommand::mCountJobs = 0; mCountMsgs = 0; mRetrievedMsgs.clear(); mCountMsgs = mMsgList.count(); uint totalSize = 0; // the KProgressDialog for the user-feedback. Only enable it if it's needed. // For some commands like KMSetStatusCommand it's not needed. Note, that // for some reason the KProgressDialog eats the MouseReleaseEvent (if a // command is executed after the MousePressEvent), cf. bug #71761. if ( mCountMsgs > 0 ) { mProgressDialog = new KProgressDialog(mParent, i18n("Please wait"), i18np("Please wait while the message is transferred", "Please wait while the %1 messages are transferred", mMsgList.count())); mProgressDialog.data()->setModal(true); mProgressDialog.data()->setMinimumDuration(1000); } // TODO once the message list is based on ETM and we get the more advanced caching we need to make that check a bit more clever if ( !mFetchScope.isEmpty() ) { complete = false; ++KMCommand::mCountJobs; Akonadi::ItemFetchJob *fetch = new Akonadi::ItemFetchJob( mMsgList, this ); mFetchScope.fetchAttribute< MessageCore::MDNStateAttribute >(); fetch->setFetchScope( mFetchScope ); connect( fetch, SIGNAL(itemsReceived(Akonadi::Item::List)), SLOT(slotMsgTransfered(Akonadi::Item::List)) ); connect( fetch, SIGNAL(result(KJob*)), SLOT(slotJobFinished()) ); } else { // no need to fetch anything if ( !mMsgList.isEmpty() ) mRetrievedMsgs = mMsgList; } if ( complete ) { delete mProgressDialog.data(); mProgressDialog.clear(); emit messagesTransfered( OK ); } else { // wait for the transfer and tell the progressBar the necessary steps if ( mProgressDialog.data() ) { connect(mProgressDialog.data(), SIGNAL(cancelClicked()), this, SLOT(slotTransferCancelled())); mProgressDialog.data()->progressBar()->setMaximum(totalSize); } } } void KMCommand::slotMsgTransfered(const Akonadi::Item::List& msgs) { if ( mProgressDialog.data() && mProgressDialog.data()->wasCancelled() ) { emit messagesTransfered( Canceled ); return; } // save the complete messages mRetrievedMsgs.append( msgs ); } void KMCommand::slotJobFinished() { // the job is finished (with / without error) KMCommand::mCountJobs--; if ( mProgressDialog.data() && mProgressDialog.data()->wasCancelled() ) return; if ( mCountMsgs > mRetrievedMsgs.count() ) { // the message wasn't retrieved before => error if ( mProgressDialog.data() ) mProgressDialog.data()->hide(); slotTransferCancelled(); return; } // update the progressbar if ( mProgressDialog.data() ) { mProgressDialog.data()->setLabelText(i18np("Please wait while the message is transferred", "Please wait while the %1 messages are transferred", KMCommand::mCountJobs)); } if (KMCommand::mCountJobs == 0) { // all done delete mProgressDialog.data(); mProgressDialog.clear(); emit messagesTransfered( OK ); } } void KMCommand::slotTransferCancelled() { KMCommand::mCountJobs = 0; mCountMsgs = 0; mRetrievedMsgs.clear(); emit messagesTransfered( Canceled ); } KMMailtoComposeCommand::KMMailtoComposeCommand( const KUrl &url, const Akonadi::Item &msg ) :mUrl( url ), mMessage( msg ) { } KMCommand::Result KMMailtoComposeCommand::execute() { KMime::Message::Ptr msg( new KMime::Message ); uint id = 0; if ( mMessage.isValid() && mMessage.parentCollection().isValid() ) { QSharedPointer fd = FolderCollection::forCollection( mMessage.parentCollection(), false ); id = fd->identity(); } MessageHelper::initHeader( msg, KMKernel::self()->identityManager(),id ); msg->contentType()->setCharset("utf-8"); msg->to()->fromUnicodeString( KPIMUtils::decodeMailtoUrl( mUrl ), "utf-8" ); KMail::Composer * win = KMail::makeComposer( msg, false, false,KMail::Composer::New, id ); win->setFocusToSubject(); win->show(); return OK; } KMMailtoReplyCommand::KMMailtoReplyCommand( QWidget *parent, const KUrl &url, const Akonadi::Item &msg, const QString &selection ) :KMCommand( parent, msg ), mUrl( url ), mSelection( selection ) { fetchScope().fetchFullPayload( true ); fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMCommand::Result KMMailtoReplyCommand::execute() { Akonadi::Item item = retrievedMessage(); KMime::Message::Ptr msg = MessageCore::Util::message( item ); if ( !msg ) return Failed; MessageFactory factory( msg, item.id(), MailCommon::Util::updatedCollection(item.parentCollection()) ); factory.setIdentityManager( KMKernel::self()->identityManager() ); factory.setFolderIdentity( MailCommon::Util::folderIdentity( item ) ); factory.setMailingListAddresses( KMail::Util::mailingListsFromMessage( item ) ); factory.putRepliesInSameFolder( KMail::Util::putRepliesInSameFolder( item ) ); factory.setReplyStrategy( MessageComposer::ReplyNone ); factory.setSelection( mSelection ); KMime::Message::Ptr rmsg = factory.createReply().msg; rmsg->to()->fromUnicodeString( KPIMUtils::decodeMailtoUrl( mUrl ), "utf-8" ); //TODO Check the UTF-8 bool lastEncrypt = false; bool lastSign = false; KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg); KMail::Composer * win = KMail::makeComposer( rmsg, lastSign, lastEncrypt, KMail::Composer::Reply, 0, mSelection ); win->setFocusToEditor(); win->show(); return OK; } KMMailtoForwardCommand::KMMailtoForwardCommand( QWidget *parent, const KUrl &url, const Akonadi::Item &msg ) :KMCommand( parent, msg ), mUrl( url ) { fetchScope().fetchFullPayload( true ); fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMCommand::Result KMMailtoForwardCommand::execute() { //TODO : consider factoring createForward into this method. Akonadi::Item item = retrievedMessage(); KMime::Message::Ptr msg = MessageCore::Util::message( item ); if ( !msg ) return Failed; MessageFactory factory( msg, item.id(), MailCommon::Util::updatedCollection(item.parentCollection()) ); factory.setIdentityManager( KMKernel::self()->identityManager() ); factory.setFolderIdentity( MailCommon::Util::folderIdentity( item ) ); KMime::Message::Ptr fmsg = factory.createForward(); fmsg->to()->fromUnicodeString( KPIMUtils::decodeMailtoUrl( mUrl ).toLower(), "utf-8" ); //TODO check the utf-8 bool lastEncrypt = false; bool lastSign = false; KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg); KMail::Composer * win = KMail::makeComposer( fmsg, lastSign, lastEncrypt, KMail::Composer::Forward ); win->show(); return OK; } KMAddBookmarksCommand::KMAddBookmarksCommand( const KUrl &url, QWidget *parent ) : KMCommand( parent ), mUrl( url ) { } KMCommand::Result KMAddBookmarksCommand::execute() { const QString filename = KStandardDirs::locateLocal( "data", QString::fromLatin1("konqueror/bookmarks.xml") ); KBookmarkManager *bookManager = KBookmarkManager::managerForFile( filename, QLatin1String("konqueror") ); KBookmarkGroup group = bookManager->root(); group.addBookmark( mUrl.path(), KUrl( mUrl ) ); if( bookManager->save() ) { bookManager->emitChanged( group ); } return OK; } KMUrlSaveCommand::KMUrlSaveCommand( const KUrl &url, QWidget *parent ) : KMCommand( parent ), mUrl( url ) { } KMCommand::Result KMUrlSaveCommand::execute() { if ( mUrl.isEmpty() ) return OK; const KUrl saveUrl = KFileDialog::getSaveUrl(mUrl.fileName(), QString(), parentWidget() ); if ( saveUrl.isEmpty() ) return Canceled; if ( KIO::NetAccess::exists( saveUrl, KIO::NetAccess::DestinationSide, parentWidget() ) ) { if (KMessageBox::warningContinueCancel(0, i18nc("@info", "File %1 exists.Do you want to replace it?", saveUrl.pathOrUrl()), i18n("Save to File"), KGuiItem(i18n("&Replace"))) != KMessageBox::Continue) return Canceled; } KIO::Job *job = KIO::file_copy(mUrl, saveUrl, -1, KIO::Overwrite); connect(job, SIGNAL(result(KJob*)), SLOT(slotUrlSaveResult(KJob*))); setEmitsCompletedItself( true ); return OK; } void KMUrlSaveCommand::slotUrlSaveResult( KJob *job ) { if ( job->error() ) { showJobError(job); setResult( Failed ); emit completed( this ); } else { setResult( OK ); emit completed( this ); } } KMEditMessageCommand::KMEditMessageCommand( QWidget *parent, const KMime::Message::Ptr& msg ) :KMCommand( parent ), mMessage( msg ) { } KMCommand::Result KMEditMessageCommand::execute() { if ( !mMessage ) return Failed; KMail::Composer *win = KMail::makeComposer(); bool lastEncrypt = false; bool lastSign = false; KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, mMessage); win->setMessage( mMessage, lastSign, lastEncrypt, false, false ); win->show(); win->setModified( true ); return OK; } KMEditItemCommand::KMEditItemCommand( QWidget *parent, const Akonadi::Item&msg, bool deleteFromSource ) :KMCommand( parent, msg ) , mDeleteFromSource( deleteFromSource ) { fetchScope().fetchFullPayload( true ); fetchScope().fetchAttribute(); fetchScope().fetchAttribute(); fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMEditItemCommand::~KMEditItemCommand() { } KMCommand::Result KMEditItemCommand::execute() { Akonadi::Item item = retrievedMessage(); if (!item.isValid() || !item.parentCollection().isValid() ) { return Failed; } KMime::Message::Ptr msg = MessageCore::Util::message( item ); if ( !msg ) { return Failed; } if ( mDeleteFromSource ) { setDeletesItself( true ); Akonadi::ItemDeleteJob *job = new Akonadi::ItemDeleteJob( item ); connect( job, SIGNAL(result(KJob*)), this, SLOT(slotDeleteItem(KJob*)) ); } KMail::Composer *win = KMail::makeComposer(); bool lastEncrypt = false; bool lastSign = false; KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg); win->setMessage( msg, lastSign, lastEncrypt, false, true ); win->setFolder( item.parentCollection() ); const MailTransport::TransportAttribute *transportAttribute = item.attribute(); if ( transportAttribute ) { win->setCurrentTransport( transportAttribute->transportId() ); } else { int transportId = msg->headerByType( "X-KMail-Transport" ) ? msg->headerByType( "X-KMail-Transport" )->asUnicodeString().toInt() : -1; if(transportId!=-1) { win->setCurrentTransport( transportId ); } } if(msg->headerByType( "Reply-To" )) { const QString replyTo = msg->headerByType( "Reply-To" )->asUnicodeString(); win->setCurrentReplyTo(replyTo); } const MailTransport::SentBehaviourAttribute *sentAttribute = item.attribute(); if ( sentAttribute && ( sentAttribute->sentBehaviour() == MailTransport::SentBehaviourAttribute::MoveToCollection ) ) win->setFcc( QString::number( sentAttribute->moveToCollection().id() ) ); win->show(); if ( mDeleteFromSource ) win->setModified( true ); return OK; } void KMEditItemCommand::slotDeleteItem( KJob *job ) { if ( job->error() ) { showJobError(job); setResult( Failed ); emit completed( this ); } else { setResult( OK ); emit completed( this ); } deleteLater( ); } KMUseTemplateCommand::KMUseTemplateCommand( QWidget *parent, const Akonadi::Item &msg ) :KMCommand( parent, msg ) { fetchScope().fetchFullPayload( true ); fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMCommand::Result KMUseTemplateCommand::execute() { Akonadi::Item item = retrievedMessage(); if ( !item.isValid() || !item.parentCollection().isValid() || !CommonKernel->folderIsTemplates( item.parentCollection() ) ) { return Failed; } KMime::Message::Ptr msg = MessageCore::Util::message( item ); if ( !msg ) return Failed; KMime::Message::Ptr newMsg(new KMime::Message); newMsg->setContent( msg->encodedContent() ); newMsg->parse(); // these fields need to be regenerated for the new message newMsg->removeHeader("Date"); newMsg->removeHeader("Message-ID"); KMail::Composer *win = KMail::makeComposer(); win->setMessage( newMsg, false, false, false, true ); win->show(); return OK; } KMSaveMsgCommand::KMSaveMsgCommand( QWidget *parent, const QList &msgList ) : KMCommand( parent, msgList ) { if ( msgList.empty() ) return; fetchScope().fetchFullPayload( true ); // ### unless we call the corresponding KMCommand ctor, this has no effect } KMCommand::Result KMSaveMsgCommand::execute() { if ( !MessageViewer::Util::saveMessageInMbox( retrievedMsgs(), parentWidget()) ) return Failed; return OK; } //----------------------------------------------------------------------------- KMOpenMsgCommand::KMOpenMsgCommand( QWidget *parent, const KUrl & url, const QString & encoding, KMMainWidget *main ) : KMCommand( parent ), mUrl( url ), mJob( 0 ), mEncoding( encoding ), mMainWidget( main ) { } KMCommand::Result KMOpenMsgCommand::execute() { if ( mUrl.isEmpty() ) { mUrl = KFileDialog::getOpenUrl( KUrl( QLatin1String("kfiledialog:///OpenMessage") ), QLatin1String("message/rfc822 application/mbox"), parentWidget(), i18n("Open Message") ); } if ( mUrl.isEmpty() ) { return Canceled; } if(mMainWidget) { mMainWidget->addRecentFile(mUrl); } setDeletesItself( true ); mJob = KIO::get( mUrl, KIO::NoReload, KIO::HideProgressInfo ); connect( mJob, SIGNAL(data(KIO::Job*,QByteArray)), this, SLOT(slotDataArrived(KIO::Job*,QByteArray)) ); connect( mJob, SIGNAL(result(KJob*)), SLOT(slotResult(KJob*)) ); setEmitsCompletedItself( true ); return OK; } void KMOpenMsgCommand::slotDataArrived( KIO::Job *, const QByteArray & data ) { if ( data.isEmpty() ) return; mMsgString.append( QString::fromLatin1(data.data()) ); } void KMOpenMsgCommand::doesNotContainMessage() { KMessageBox::sorry( parentWidget(), i18n( "The file does not contain a message." ) ); setResult( Failed ); emit completed( this ); // Emulate closing of a secondary window so that KMail exits in case it // was started with the --view command line option. Otherwise an // invisible KMail would keep running. SecondaryWindow *win = new SecondaryWindow(); win->close(); win->deleteLater(); deleteLater(); } void KMOpenMsgCommand::slotResult( KJob *job ) { if ( job->error() ) { // handle errors showJobError(job); setResult( Failed ); emit completed( this ); } else { int startOfMessage = 0; if ( mMsgString.startsWith( QLatin1String( "From " ) ) ) { startOfMessage = mMsgString.indexOf( QLatin1Char('\n') ); if ( startOfMessage == -1 ) { doesNotContainMessage(); return; } startOfMessage += 1; // the message starts after the '\n' } // check for multiple messages in the file bool multipleMessages = true; int endOfMessage = mMsgString.indexOf( QLatin1String("\nFrom ") ); if ( endOfMessage == -1 ) { endOfMessage = mMsgString.length(); multipleMessages = false; } KMime::Message *msg = new KMime::Message; msg->setContent( KMime::CRLFtoLF( mMsgString.mid( startOfMessage,endOfMessage - startOfMessage ).toUtf8() ) ); msg->parse(); if ( !msg->hasContent() ) { delete msg; msg = 0; doesNotContainMessage(); return; } KMReaderMainWin *win = new KMReaderMainWin(); KMime::Message::Ptr mMsg( msg ); win->showMessage( mEncoding, mMsg ); win->show(); if ( multipleMessages ) KMessageBox::information( win, i18n( "The file contains multiple messages. " "Only the first message is shown." ) ); setResult( OK ); emit completed( this ); } deleteLater(); } //----------------------------------------------------------------------------- KMReplyCommand::KMReplyCommand( QWidget *parent, const Akonadi::Item &msg, MessageComposer::ReplyStrategy replyStrategy, const QString &selection, bool noquote, const QString& templateName ) : KMCommand( parent, msg ), mSelection( selection ), mTemplate( templateName ), m_replyStrategy( replyStrategy ), mNoQuote(noquote) { if ( !noquote ) fetchScope().fetchFullPayload( true ); fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMCommand::Result KMReplyCommand::execute() { #ifndef QT_NO_CURSOR MessageViewer::KCursorSaver busy( MessageViewer::KBusyPtr::busy() ); #endif Akonadi::Item item = retrievedMessage(); KMime::Message::Ptr msg = MessageCore::Util::message( item ); if ( !msg ) return Failed; MessageFactory factory( msg, item.id(), MailCommon::Util::updatedCollection(item.parentCollection()) ); factory.setIdentityManager( KMKernel::self()->identityManager() ); factory.setFolderIdentity( MailCommon::Util::folderIdentity( item ) ); factory.setMailingListAddresses( KMail::Util::mailingListsFromMessage( item ) ); factory.putRepliesInSameFolder( KMail::Util::putRepliesInSameFolder( item ) ); factory.setReplyStrategy( m_replyStrategy ); factory.setSelection( mSelection ); if ( !mTemplate.isEmpty() ) factory.setTemplate( mTemplate ); if(mNoQuote) { factory.setQuote(false); } bool lastEncrypt = false; bool lastSign = false; KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg); MessageFactory::MessageReply reply = factory.createReply(); KMail::Composer * win = KMail::makeComposer( KMime::Message::Ptr( reply.msg ), lastSign, lastEncrypt, replyContext( reply ), 0, mSelection,mTemplate ); win->setFocusToEditor(); win->show(); return OK; } KMForwardCommand::KMForwardCommand( QWidget *parent, const QList &msgList, uint identity,const QString& templateName ) : KMCommand( parent, msgList ), mIdentity( identity ), mTemplate( templateName ) { fetchScope().fetchFullPayload( true ); fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMForwardCommand::KMForwardCommand( QWidget *parent, const Akonadi::Item &msg, uint identity, const QString& templateName ) : KMCommand( parent, msg ), mIdentity( identity ), mTemplate( templateName ) { fetchScope().fetchFullPayload( true ); fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMCommand::Result KMForwardCommand::createComposer(const Akonadi::Item& item) { KMime::Message::Ptr msg = MessageCore::Util::message( item ); if ( !msg ) return Failed; #ifndef QT_NO_CURSOR MessageViewer::KCursorSaver busy( MessageViewer::KBusyPtr::busy() ); #endif MessageFactory factory( msg, item.id(), MailCommon::Util::updatedCollection(item.parentCollection()) ); factory.setIdentityManager( KMKernel::self()->identityManager() ); factory.setFolderIdentity( MailCommon::Util::folderIdentity( item ) ); if ( !mTemplate.isEmpty() ) factory.setTemplate( mTemplate ); KMime::Message::Ptr fwdMsg = factory.createForward(); uint id = msg->headerByType( "X-KMail-Identity" ) ? msg->headerByType("X-KMail-Identity")->asUnicodeString().trimmed().toUInt() : 0; kDebug() << "mail" << msg->encodedContent(); bool lastEncrypt = false; bool lastSign = false; KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg); if ( id == 0 ) id = mIdentity; { KMail::Composer * win = KMail::makeComposer( fwdMsg, lastSign, lastEncrypt, KMail::Composer::Forward, id,QString(), mTemplate ); win->show(); } return OK; } KMCommand::Result KMForwardCommand::execute() { QList msgList = retrievedMsgs(); if (msgList.count() >= 2) { // ask if they want a mime digest forward int answer = KMessageBox::questionYesNoCancel( parentWidget(), i18n("Do you want to forward the selected messages as " "attachments in one message (as a MIME digest) or as " "individual messages?"), QString(), KGuiItem(i18n("Send As Digest")), KGuiItem(i18n("Send Individually")) ); if ( answer == KMessageBox::Yes ) { Akonadi::Item firstItem( msgList.first() ); MessageFactory factory( KMime::Message::Ptr( new KMime::Message ), firstItem.id(), MailCommon::Util::updatedCollection(firstItem.parentCollection()) ); factory.setIdentityManager( KMKernel::self()->identityManager() ); factory.setFolderIdentity( MailCommon::Util::folderIdentity( firstItem ) ); QPair< KMime::Message::Ptr, KMime::Content* > fwdMsg = factory.createForwardDigestMIME(msgList ); KMail::Composer * win = KMail::makeComposer( fwdMsg.first, false, false, KMail::Composer::Forward, mIdentity ); win->addAttach( fwdMsg.second ); win->show(); return OK; } else if ( answer == KMessageBox::No ) {// NO MIME DIGEST, Multiple forward QList::const_iterator it; QList::const_iterator end( msgList.constEnd() ); for ( it = msgList.constBegin(); it != end; ++it ) { if ( createComposer( *it ) == Failed ) return Failed; } return OK; } else { // user cancelled return OK; } } // forward a single message at most. Akonadi::Item item = msgList.first(); if ( createComposer( item ) == Failed ) return Failed; return OK; } KMForwardAttachedCommand::KMForwardAttachedCommand( QWidget *parent, const QList &msgList, uint identity, KMail::Composer *win ) : KMCommand( parent, msgList ), mIdentity( identity ), mWin( QPointer( win )) { fetchScope().fetchFullPayload( true ); fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMForwardAttachedCommand::KMForwardAttachedCommand( QWidget *parent, const Akonadi::Item & msg, uint identity, KMail::Composer *win ) : KMCommand( parent, msg ), mIdentity( identity ), mWin( QPointer< KMail::Composer >( win )) { fetchScope().fetchFullPayload( true ); fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMCommand::Result KMForwardAttachedCommand::execute() { QList msgList = retrievedMsgs(); Akonadi::Item firstItem( msgList.first() ); MessageFactory factory( KMime::Message::Ptr( new KMime::Message ), firstItem.id(), MailCommon::Util::updatedCollection(firstItem.parentCollection()) ); factory.setIdentityManager( KMKernel::self()->identityManager() ); factory.setFolderIdentity( MailCommon::Util::folderIdentity( firstItem ) ); QPair< KMime::Message::Ptr, QList< KMime::Content* > > fwdMsg = factory.createAttachedForward( msgList ); if ( !mWin ) { mWin = KMail::makeComposer( fwdMsg.first, false, false, KMail::Composer::Forward, mIdentity ); } foreach( KMime::Content* attach, fwdMsg.second ) { mWin->addAttach( attach ); } mWin->show(); return OK; } KMRedirectCommand::KMRedirectCommand( QWidget *parent, const QList &msgList ) : KMCommand( parent, msgList ) { fetchScope().fetchFullPayload( true ); fetchScope().fetchAttribute(); fetchScope().fetchAttribute(); fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMRedirectCommand::KMRedirectCommand( QWidget *parent, const Akonadi::Item &msg ) : KMCommand( parent, msg ) { fetchScope().fetchFullPayload( true ); fetchScope().fetchAttribute(); fetchScope().fetchAttribute(); fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMCommand::Result KMRedirectCommand::execute() { const MailCommon::RedirectDialog::SendMode sendMode = MessageComposer::MessageComposerSettings::self()->sendImmediate() ? MailCommon::RedirectDialog::SendNow : MailCommon::RedirectDialog::SendLater; MessageViewer::AutoQPointer dlg( new MailCommon::RedirectDialog( sendMode, parentWidget() ) ); dlg->setObjectName( QLatin1String("redirect") ); if ( dlg->exec() == QDialog::Rejected || !dlg ) { return Failed; } if ( !TransportManager::self()->showTransportCreationDialog( parentWidget(), TransportManager::IfNoTransportExists ) ) return Failed; //TODO use sendlateragent here too. const MessageComposer::MessageSender::SendMethod method = (dlg->sendMode() == MailCommon::RedirectDialog::SendNow) ? MessageComposer::MessageSender::SendImmediate : MessageComposer::MessageSender::SendLater; const int identity = dlg->identity(); int transportId = dlg->transportId(); const QString to = dlg->to(); foreach( const Akonadi::Item &item, retrievedMsgs() ) { const KMime::Message::Ptr msg = MessageCore::Util::message( item ); if ( !msg ) return Failed; MessageFactory factory( msg, item.id(), MailCommon::Util::updatedCollection(item.parentCollection()) ); factory.setIdentityManager( KMKernel::self()->identityManager() ); factory.setFolderIdentity( MailCommon::Util::folderIdentity( item ) ); if(transportId == -1) { const MailTransport::TransportAttribute *transportAttribute = item.attribute(); if ( transportAttribute ) { transportId = transportAttribute->transportId(); const MailTransport::Transport *transport = MailTransport::TransportManager::self()->transportById( transportId ); if ( !transport ) { transportId = -1; } } } const MailTransport::SentBehaviourAttribute *sentAttribute = item.attribute(); QString fcc; if ( sentAttribute && ( sentAttribute->sentBehaviour() == MailTransport::SentBehaviourAttribute::MoveToCollection ) ) fcc = QString::number( sentAttribute->moveToCollection().id() ); const KMime::Message::Ptr newMsg = factory.createRedirect( to, transportId, fcc, identity ); if ( !newMsg ) return Failed; MessageStatus status; status.setStatusFromFlags( item.flags() ); if ( !status.isRead() ) FilterAction::sendMDN( item, KMime::MDN::Dispatched ); if ( !kmkernel->msgSender()->send( newMsg, method ) ) { kDebug() << "KMRedirectCommand: could not redirect message (sending failed)"; return Failed; // error: couldn't send } } return OK; } KMPrintCommand::KMPrintCommand( QWidget *parent, const Akonadi::Item &msg, MessageViewer::HeaderStyle *headerStyle, MessageViewer::HeaderStrategy *headerStrategy, bool htmlOverride, bool htmlLoadExtOverride, bool useFixedFont, const QString & encoding ) : KMCommand( parent, msg ), mHeaderStyle( headerStyle ), mHeaderStrategy( headerStrategy ), mAttachmentStrategy( 0 ), mEncoding( encoding ), mHtmlOverride( htmlOverride ), mHtmlLoadExtOverride( htmlLoadExtOverride ), mUseFixedFont( useFixedFont ), mPrintPreview(false) { fetchScope().fetchFullPayload( true ); if ( MessageCore::GlobalSettings::useDefaultFonts() ) mOverrideFont = KGlobalSettings::generalFont(); else { mOverrideFont = MessageCore::GlobalSettings::self()->printFont(); } } void KMPrintCommand::setOverrideFont( const QFont& font ) { mOverrideFont = font; } void KMPrintCommand::setAttachmentStrategy( const MessageViewer::AttachmentStrategy *strategy ) { mAttachmentStrategy = strategy; } void KMPrintCommand::setPrintPreview( bool preview ) { mPrintPreview = preview; } KMCommand::Result KMPrintCommand::execute() { // the window will be deleted after printout is performed, in KMReaderWin::slotPrintMsg() KMReaderWin *printerWin = new KMReaderWin( 0, kmkernel->mainWin(), 0, 0 ); printerWin->setPrinting( true ); printerWin->readConfig(); if ( mHeaderStyle != 0 && mHeaderStrategy != 0 ) printerWin->setHeaderStyleAndStrategy( mHeaderStyle, mHeaderStrategy ); printerWin->setHtmlOverride( mHtmlOverride ); printerWin->setHtmlLoadExtOverride( mHtmlLoadExtOverride ); printerWin->setUseFixedFont( mUseFixedFont ); printerWin->setOverrideEncoding( mEncoding ); printerWin->cssHelper()->setPrintFont( mOverrideFont ); printerWin->setDecryptMessageOverwrite( true ); if ( mAttachmentStrategy != 0 ) printerWin->setAttachmentStrategy( mAttachmentStrategy ); if(mPrintPreview) printerWin->viewer()->printPreviewMessage( retrievedMessage() ); else printerWin->viewer()->printMessage(retrievedMessage()); return OK; } KMSetStatusCommand::KMSetStatusCommand( const MessageStatus& status, const Akonadi::Item::List &items, bool invert ) : KMCommand( 0, items ), mStatus( status ), mInvertMark( invert ) { setDeletesItself(true); } KMCommand::Result KMSetStatusCommand::execute() { bool parentStatus = false; // Toggle actions on threads toggle the whole thread // depending on the state of the parent. if ( mInvertMark ) { const Akonadi::Item first = retrievedMsgs().first(); MessageStatus pStatus; pStatus.setStatusFromFlags( first.flags() ); if ( pStatus & mStatus ) parentStatus = true; else parentStatus = false; } Akonadi::Item::List itemsToModify; foreach( const Akonadi::Item &it, retrievedMsgs() ) { if ( mInvertMark ) { //kDebug()<<" item ::"<disableRevisionCheck(); modifyJob->setIgnorePayload( true ); connect( modifyJob, SIGNAL(result(KJob*)), this, SLOT(slotModifyItemDone(KJob*)) ); } return OK; } void KMSetStatusCommand::slotModifyItemDone( KJob * job ) { if ( job && job->error() ) { kWarning() << " Error trying to set item status:" << job->errorText(); } deleteLater(); } KMSetTagCommand::KMSetTagCommand( const Akonadi::Tag::List &tags, const QList &item, SetTagMode mode ) : mTags( tags ) , mItem( item ) , mMode( mode ) { setDeletesItself(true); } KMCommand::Result KMSetTagCommand::execute() { Q_FOREACH( const Akonadi::Tag &tag, mTags ) { if ( !tag.isValid() ) { Akonadi::TagCreateJob *createJob = new Akonadi::TagCreateJob(tag, this); connect( createJob, SIGNAL(result(KJob*)), this, SLOT(slotModifyItemDone(KJob*)) ); } else { mCreatedTags << tag; } } if ( mCreatedTags.size() == mTags.size() ) { setTags(); } else { deleteLater(); } return OK; } void KMSetTagCommand::slotTagCreateDone(KJob* job) { if ( job && job->error() ) { kWarning() << " Error trying to create tag:" << job->errorText(); deleteLater(); return; } Akonadi::TagCreateJob* createJob = static_cast(job); mCreatedTags << createJob->tag(); if ( mCreatedTags.size() == mTags.size() ) { setTags(); } else { deleteLater(); } } void KMSetTagCommand::setTags() { Akonadi::Item::List itemsToModify; Q_FOREACH( const Akonadi::Item& i, mItem ) { Akonadi::Item item(i); if ( mMode == CleanExistingAndAddNew ){ item.clearTags(); } if (mMode == KMSetTagCommand::Toggle) { Q_FOREACH( const Akonadi::Tag &tag, mCreatedTags ) { if ( item.hasTag(tag) ) { item.clearTag(tag); } else { item.setTag(tag); } } } else { item.setTags(mCreatedTags); } itemsToModify << item; } Akonadi::ItemModifyJob *modifyJob = new Akonadi::ItemModifyJob( itemsToModify, this ); modifyJob->disableRevisionCheck(); modifyJob->setIgnorePayload( true ); connect( modifyJob, SIGNAL(result(KJob*)), this, SLOT(slotModifyItemDone(KJob*)) ); if(!mCreatedTags.isEmpty()) { - KConfigGroup tag( KMKernel::self()->config(), "MessageListView" ); - const QString oldTagList = tag.readEntry("TagSelected"); - QStringList lst = oldTagList.split(QLatin1String(",")); - Q_FOREACH( const Akonadi::Tag &tag, mCreatedTags ) { - const QString url = tag.url().url(); - if(!lst.contains(url)) { - lst.append(url); - } - } - tag.writeEntry("TagSelected",lst); KMKernel::self()->updatePaneTagComboBox(); } } void KMSetTagCommand::slotModifyItemDone( KJob * job ) { if ( job && job->error() ) { kWarning() << " Error trying to set item status:" << job->errorText(); } deleteLater(); } KMFilterActionCommand::KMFilterActionCommand( QWidget *parent, const QVector &msgListId, const QString &filterId) : KMCommand( parent ), mMsgListId(msgListId), mFilterId( filterId ) { } KMCommand::Result KMFilterActionCommand::execute() { #ifndef QT_NO_CURSOR MessageViewer::KCursorSaver busy( MessageViewer::KBusyPtr::busy() ); #endif int msgCount = 0; const int msgCountToFilter = mMsgListId.count(); ProgressItem* progressItem = ProgressManager::createProgressItem ( QLatin1String("filter")+ProgressManager::getUniqueID(), i18n( "Filtering messages" ), QString(),true, KPIM::ProgressItem::Unknown ); progressItem->setTotalItems( msgCountToFilter ); foreach ( const qlonglong &id, mMsgListId ) { int diff = msgCountToFilter - ++msgCount; if ( diff < 10 || !( msgCount % 10 ) || msgCount <= 10 ) { progressItem->updateProgress(); const QString statusMsg = i18n( "Filtering message %1 of %2", msgCount, msgCountToFilter ); KPIM::BroadcastStatus::instance()->setStatusMsg( statusMsg ); qApp->processEvents( QEventLoop::ExcludeUserInputEvents, 50 ); } MailCommon::FilterManager::instance()->filter( Akonadi::Item(id), mFilterId, QString() ); progressItem->incCompletedItems(); } progressItem->setComplete(); progressItem = 0; return OK; } KMMetaFilterActionCommand::KMMetaFilterActionCommand( const QString &filterId, KMMainWidget *main ) : QObject( main ), mFilterId( filterId ), mMainWidget( main ) { } void KMMetaFilterActionCommand::start() { KMCommand *filterCommand = new KMFilterActionCommand( mMainWidget, mMainWidget->messageListPane()->selectionAsMessageItemListId() , mFilterId); filterCommand->start(); } KMMailingListFilterCommand::KMMailingListFilterCommand( QWidget *parent, const Akonadi::Item &msg ) : KMCommand( parent, msg ) { } KMCommand::Result KMMailingListFilterCommand::execute() { QByteArray name; QString value; Akonadi::Item item = retrievedMessage(); KMime::Message::Ptr msg = MessageCore::Util::message( item ); if ( !msg ) return Failed; if ( !MailingList::name( msg, name, value ).isEmpty() ) { FilterIf->openFilterDialog( false ); FilterIf->createFilter( name, value ); return OK; } else { return Failed; } } KMCopyCommand::KMCopyCommand( const Akonadi::Collection& destFolder, const QList &msgList) :KMCommand( 0, msgList ), mDestFolder( destFolder ) { } KMCopyCommand::KMCopyCommand( const Akonadi::Collection& destFolder, const Akonadi::Item& msg) :KMCommand( 0,msg ), mDestFolder( destFolder ) { } KMCommand::Result KMCopyCommand::execute() { setDeletesItself( true ); QList listItem = retrievedMsgs(); Akonadi::ItemCopyJob *job = new Akonadi::ItemCopyJob( listItem, Akonadi::Collection(mDestFolder.id()),this ); connect( job, SIGNAL(result(KJob*)), this, SLOT(slotCopyResult(KJob*)) ); return OK; } void KMCopyCommand::slotCopyResult( KJob * job ) { if ( job->error() ) { // handle errors showJobError(job); setResult( Failed ); } deleteLater(); } KMMoveCommand::KMMoveCommand( const Akonadi::Collection& destFolder, const QList &msgList, MessageList::Core::MessageItemSetReference ref) : KMCommand( 0, msgList ), mDestFolder( destFolder ), mProgressItem( 0 ), mRef( ref ) { fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMMoveCommand::KMMoveCommand( const Akonadi::Collection& destFolder, const Akonadi::Item& msg , MessageList::Core::MessageItemSetReference ref) : KMCommand( 0, msg ), mDestFolder( destFolder ), mProgressItem( 0 ), mRef( ref ) { fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } void KMMoveCommand::slotMoveResult( KJob * job ) { if ( job->error() ) { // handle errors showJobError(job); completeMove( Failed ); } else completeMove( OK ); } KMCommand::Result KMMoveCommand::execute() { #ifndef QT_NO_CURSOR MessageViewer::KCursorSaver busy( MessageViewer::KBusyPtr::busy() ); #endif setEmitsCompletedItself( true ); setDeletesItself( true ); if ( mDestFolder.isValid() ) { Akonadi::ItemMoveJob *job = new Akonadi::ItemMoveJob( retrievedMsgs(), mDestFolder, this ); connect( job, SIGNAL(result(KJob*)), this, SLOT(slotMoveResult(KJob*)) ); // group by source folder for undo Akonadi::Item::List items = retrievedMsgs(); std::sort( items.begin(), items.end(), boost::bind( &Akonadi::Item::storageCollectionId, _1 ) < boost::bind( &Akonadi::Item::storageCollectionId, _2 ) ); Akonadi::Collection parent; int undoId = -1; foreach ( const Akonadi::Item &item, items ) { if ( item.storageCollectionId() <= 0 ) continue; if ( parent.id() != item.storageCollectionId() ) { parent = Akonadi::Collection( item.storageCollectionId() ); undoId = kmkernel->undoStack()->newUndoAction( parent, mDestFolder ); } kmkernel->undoStack()->addMsgToAction( undoId, item ); } } else { const QList retrievedList = retrievedMsgs(); if ( !retrievedList.isEmpty() ) { Akonadi::ItemDeleteJob *job = new Akonadi::ItemDeleteJob( retrievedList, this ); connect( job, SIGNAL(result(KJob*)), this, SLOT(slotMoveResult(KJob*)) ); } } // TODO set SSL state according to source and destfolder connection? Q_ASSERT( !mProgressItem ); mProgressItem = ProgressManager::createProgressItem (QLatin1String("move")+ProgressManager::getUniqueID(), mDestFolder.isValid() ? i18n( "Moving messages" ) : i18n( "Deleting messages" ), QString(), true, KPIM::ProgressItem::Unknown ); mProgressItem->setUsesBusyIndicator( true ); connect( mProgressItem, SIGNAL(progressItemCanceled(KPIM::ProgressItem*)), this, SLOT(slotMoveCanceled()) ); return OK; } void KMMoveCommand::completeMove( Result result ) { if ( mProgressItem ) { mProgressItem->setComplete(); mProgressItem = 0; } setResult( result ); emit moveDone(this); emit completed( this ); deleteLater(); } void KMMoveCommand::slotMoveCanceled() { completeMove( Canceled ); } // srcFolder doesn't make much sense for searchFolders KMTrashMsgCommand::KMTrashMsgCommand( const Akonadi::Collection& srcFolder, const QList &msgList,MessageList::Core::MessageItemSetReference ref ) :KMMoveCommand( findTrashFolder( srcFolder ), msgList, ref) { } KMTrashMsgCommand::KMTrashMsgCommand( const Akonadi::Collection& srcFolder, const Akonadi::Item & msg,MessageList::Core::MessageItemSetReference ref ) :KMMoveCommand( findTrashFolder( srcFolder ), msg, ref) { } Akonadi::Collection KMTrashMsgCommand::findTrashFolder( const Akonadi::Collection& folder ) { Akonadi::Collection col = CommonKernel->trashCollectionFromResource( folder ); if ( !col.isValid() ) { col = CommonKernel->trashCollectionFolder(); } if ( folder != col ) return col; return Akonadi::Collection(); } KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( QWidget *parent, const Akonadi::Item& msg ) : KMCommand( parent, msg ) { fetchScope().fetchFullPayload( true ); } KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( QWidget *parent, const QList& msgs ) : KMCommand( parent, msgs ) { fetchScope().fetchFullPayload( true ); } KMCommand::Result KMSaveAttachmentsCommand::execute() { QList contentsToSave; foreach( const Akonadi::Item &item, retrievedMsgs() ) { if ( item.hasPayload() ) { contentsToSave += MessageViewer::Util::extractAttachments( item.payload().get() ); } else { kWarning() << "Retrieved item has no payload? Ignoring for saving the attachments"; } } if ( MessageViewer::Util::saveAttachments( contentsToSave, parentWidget() ) ) return OK; return Failed; } KMResendMessageCommand::KMResendMessageCommand( QWidget *parent, const Akonadi::Item &msg ) :KMCommand( parent, msg ) { fetchScope().fetchFullPayload( true ); fetchScope().setAncestorRetrieval( Akonadi::ItemFetchScope::Parent ); } KMCommand::Result KMResendMessageCommand::execute() { Akonadi::Item item = retrievedMessage(); KMime::Message::Ptr msg = MessageCore::Util::message( item ); if ( !msg ) return Failed; MessageFactory factory( msg, item.id(), MailCommon::Util::updatedCollection(item.parentCollection()) ); factory.setIdentityManager( KMKernel::self()->identityManager() ); factory.setFolderIdentity( MailCommon::Util::folderIdentity( item ) ); KMime::Message::Ptr newMsg = factory.createResend(); newMsg->contentType()->setCharset( MessageViewer::NodeHelper::charset( msg.get() ) ); KMail::Composer * win = KMail::makeComposer(); if(msg->headerByType( "Reply-To" )) { const QString replyTo = msg->headerByType( "Reply-To" )->asUnicodeString(); win->setCurrentReplyTo(replyTo); } bool lastEncrypt = false; bool lastSign = false; KMail::Util::lastEncryptAndSignState(lastEncrypt, lastSign, msg); win->setMessage( newMsg, lastSign, lastEncrypt, false, true ); win->show(); return OK; } KMShareImageCommand::KMShareImageCommand(const KUrl &url, QWidget *parent) : KMCommand( parent ), mUrl(url) { } KMCommand::Result KMShareImageCommand::execute() { KMime::Message::Ptr msg( new KMime::Message ); uint id = 0; MessageHelper::initHeader( msg, KMKernel::self()->identityManager(),id ); msg->contentType()->setCharset("utf-8"); KMail::Composer * win = KMail::makeComposer(msg, false, false,KMail::Composer::New, id); win->setFocusToSubject(); win->addAttachment(mUrl, i18n("Image")); win->show(); return OK; } diff --git a/messagelist/core/settings.kcfg b/messagelist/core/settings.kcfg index 472b77e8b8..1b4f84fe5c 100644 --- a/messagelist/core/settings.kcfg +++ b/messagelist/core/settings.kcfg @@ -1,55 +1,54 @@ true Enable this option to display tooltips when hovering over an item in the message list. true With this option enabled the tab bar will be displayed only when there are two or more tabs. With this option disabled the tab bar will be always shown. When the tab bar is hidden you can always open a folder in a new tab by middle-clicking it. false Enable this option if you want to have a close button on each tab. true - MessageList::Util::unreadDefaultMessageColor() MessageList::Util::importantDefaultMessageColor() MessageList::Util::todoDefaultMessageColor() KGlobalSettings::generalFont() KGlobalSettings::generalFont() KGlobalSettings::generalFont() KGlobalSettings::generalFont() diff --git a/messagelist/widget.cpp b/messagelist/widget.cpp index c98a6ded18..1a3fa6ad2a 100644 --- a/messagelist/widget.cpp +++ b/messagelist/widget.cpp @@ -1,749 +1,736 @@ /* Copyright (c) 2009 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "widget.h" #include #include #include #include #include "storagemodel.h" #include "core/messageitem.h" #include "core/view.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core/groupheaderitem.h" #include #include #include #include #include namespace MessageList { class Widget::Private { public: Private( Widget *owner ) : q( owner ), mLastSelectedMessage(-1), mXmlGuiClient( 0 ) { } Akonadi::Item::List selectionAsItems() const; Akonadi::Item itemForRow( int row ) const; KMime::Message::Ptr messageForRow( int row ) const; Widget * const q; int mLastSelectedMessage; KXMLGUIClient *mXmlGuiClient; QModelIndex mGroupHeaderItemIndex; Akonadi::Monitor *mMonitor; }; } // namespace MessageList using namespace MessageList; using namespace Akonadi; Widget::Widget( QWidget *parent ) : Core::Widget( parent ), d( new Private( this ) ) { populateStatusFilterCombo(); d->mMonitor = new Akonadi::Monitor( this ); d->mMonitor->setTypeMonitored( Akonadi::Monitor::Tags ); connect(d->mMonitor, SIGNAL(tagAdded(Akonadi::Tag)), this, SLOT(populateStatusFilterCombo())); connect(d->mMonitor, SIGNAL(tagRemoved(Akonadi::Tag)), this, SLOT(populateStatusFilterCombo())); connect(d->mMonitor, SIGNAL(tagChanged(Akonadi::Tag)), this, SLOT(populateStatusFilterCombo())); } Widget::~Widget() { delete d; } void Widget::setXmlGuiClient( KXMLGUIClient *xmlGuiClient ) { d->mXmlGuiClient = xmlGuiClient; } bool Widget::canAcceptDrag( const QDropEvent * e ) { if ( e->source() == view()->viewport() ) return false; Collection::List collections = static_cast( storageModel() )->displayedCollections(); if ( collections.size()!=1 ) return false; // no folder here or too many (in case we can't decide where the drop will end) const Collection target = collections.first(); if ( ( target.rights() & Collection::CanCreateItem ) == 0 ) return false; // no way to drag into const KUrl::List urls = KUrl::List::fromMimeData( e->mimeData() ); foreach ( const KUrl &url, urls ) { const Collection collection = Collection::fromUrl( url ); if ( collection.isValid() ) { // You're not supposed to drop collections here return false; } else { // Yay, this is an item! const QString type = url.queryItems()[QLatin1String( "type" )]; // But does it have the right type? if ( !target.contentMimeTypes().contains( type ) ) { return false; } } } return true; } bool Widget::selectNextMessageItem( MessageList::Core::MessageTypeFilter messageTypeFilter, MessageList::Core::ExistingSelectionBehaviour existingSelectionBehaviour, bool centerItem, bool loop ) { return view()->selectNextMessageItem( messageTypeFilter, existingSelectionBehaviour, centerItem, loop ); } bool Widget::selectPreviousMessageItem( MessageList::Core::MessageTypeFilter messageTypeFilter, MessageList::Core::ExistingSelectionBehaviour existingSelectionBehaviour, bool centerItem, bool loop ) { return view()->selectPreviousMessageItem( messageTypeFilter, existingSelectionBehaviour, centerItem, loop ); } bool Widget::focusNextMessageItem( MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem, bool loop ) { return view()->focusNextMessageItem( messageTypeFilter, centerItem, loop ); } bool Widget::focusPreviousMessageItem( MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem, bool loop ) { return view()->focusPreviousMessageItem( messageTypeFilter, centerItem, loop ); } void Widget::selectFocusedMessageItem( bool centerItem ) { view()->selectFocusedMessageItem( centerItem ); } bool Widget::selectFirstMessageItem( MessageList::Core::MessageTypeFilter messageTypeFilter, bool centerItem ) { return view()->selectFirstMessageItem( messageTypeFilter, centerItem ); } bool Widget::selectLastMessageItem( Core::MessageTypeFilter messageTypeFilter, bool centerItem ) { return view()->selectLastMessageItem( messageTypeFilter, centerItem ); } void Widget::selectAll() { view()->setAllGroupsExpanded( true ); view()->selectAll(); } void Widget::setCurrentThreadExpanded( bool expand ) { view()->setCurrentThreadExpanded(expand ); } void Widget::setAllThreadsExpanded( bool expand ) { view()->setAllThreadsExpanded( expand ); } void Widget::setAllGroupsExpanded( bool expand ) { view()->setAllGroupsExpanded(expand); } void Widget::focusQuickSearch() { view()->focusQuickSearch(); } void Widget::setQuickSearchClickMessage(const QString &msg) { view()->setQuickSearchClickMessage(msg); } void Widget::fillMessageTagCombo() { Akonadi::TagFetchJob *fetchJob = new Akonadi::TagFetchJob(this); fetchJob->fetchScope().fetchAttribute(); connect(fetchJob, SIGNAL(result(KJob*)), this, SLOT(slotTagsFetched(KJob*))); } void Widget::slotTagsFetched(KJob *job) { if (job->error()) { kWarning() << "Failed to load tags " << job->errorString(); return; } Akonadi::TagFetchJob *fetchJob = static_cast(job); - KConfigGroup conf( MessageList::Core::Settings::self()->config(),"MessageListView"); - const QString tagSelected= conf.readEntry(QLatin1String("TagSelected")); - if(tagSelected.isEmpty()) { - setCurrentStatusFilterItem(); - return; - } - const QStringList tagSelectedLst = tagSelected.split(QLatin1Char(',')); - addMessageTagItem( SmallIcon( QLatin1String( "mail-flag" ) ), i18n( "All" ), QString() ); QStringList tagFound; foreach( const Akonadi::Tag &akonadiTag, fetchJob->tags() ) { - if(tagSelectedLst.contains(akonadiTag.url().url())) { - tagFound.append(akonadiTag.url().url()); - QString iconName = QLatin1String( "mail-tagged" ); - const QString label = akonadiTag.name(); - const QString id = akonadiTag.url().url(); - Akonadi::TagAttribute *attr = akonadiTag.attribute(); - if (attr) { - iconName = attr->iconName(); - } - addMessageTagItem( SmallIcon( iconName ), label, QVariant( id ) ); + QString iconName = QLatin1String( "mail-tagged" ); + const QString label = akonadiTag.name(); + const QString id = akonadiTag.url().url(); + Akonadi::TagAttribute *attr = akonadiTag.attribute(); + if (attr) { + iconName = attr->iconName(); } + addMessageTagItem( SmallIcon( iconName ), label, QVariant( id ) ); } - conf.writeEntry(QLatin1String("TagSelected"), tagFound); - conf.sync(); setCurrentStatusFilterItem(); } void Widget::viewMessageSelected( MessageList::Core::MessageItem *msg ) { int row = -1; if ( msg ) { row = msg->currentModelIndexRow(); } if ( !msg || !msg->isValid() || !storageModel() ) { d->mLastSelectedMessage = -1; emit messageSelected( Item() ); return; } Q_ASSERT( row >= 0 ); d->mLastSelectedMessage = row; emit messageSelected( d->itemForRow( row ) ); // this MAY be null } void Widget::viewMessageActivated( MessageList::Core::MessageItem *msg ) { Q_ASSERT( msg ); // must not be null Q_ASSERT( storageModel() ); if ( !msg->isValid() ) { return; } int row = msg->currentModelIndexRow(); Q_ASSERT( row >= 0 ); // The assert below may fail when quickly opening and closing a non-selected thread. // This will actually activate the item without selecting it... //Q_ASSERT( d->mLastSelectedMessage == row ); if ( d->mLastSelectedMessage != row ) { // Very ugly. We are activating a non selected message. // This is very likely a double click on the plus sign near a thread leader. // Dealing with mLastSelectedMessage here would be expensive: it would involve releasing the last selected, // emitting signals, handling recursion... ugly. // We choose a very simple solution: double clicking on the plus sign near a thread leader does // NOT activate the message (i.e open it in a toplevel window) if it isn't previously selected. return; } emit messageActivated( d->itemForRow( row ) ); // this MAY be null } void Widget::viewSelectionChanged() { emit selectionChanged(); if ( !currentMessageItem() ) { emit messageSelected( Item() ); } } void Widget::viewMessageListContextPopupRequest( const QList< MessageList::Core::MessageItem * > &selectedItems, const QPoint &globalPos ) { Q_UNUSED( selectedItems ); if ( !d->mXmlGuiClient ) return; QMenu *popup = static_cast( d->mXmlGuiClient->factory()->container( QLatin1String( "akonadi_messagelist_contextmenu" ), d->mXmlGuiClient ) ); if ( popup ) { popup->exec( globalPos ); } } void Widget::viewMessageStatusChangeRequest( MessageList::Core::MessageItem *msg, const Akonadi::MessageStatus &set, const Akonadi::MessageStatus &clear ) { Q_ASSERT( msg ); // must not be null Q_ASSERT( storageModel() ); if ( !msg->isValid() ) { return; } int row = msg->currentModelIndexRow(); Q_ASSERT( row >= 0 ); Item item = d->itemForRow( row ); Q_ASSERT( item.isValid() ); emit messageStatusChangeRequest( item, set, clear ); } void Widget::viewGroupHeaderContextPopupRequest( MessageList::Core::GroupHeaderItem *ghi, const QPoint &globalPos ) { Q_UNUSED( ghi ); KMenu menu( this ); QAction *act; QModelIndex index = view()->model()->index( ghi, 0 ); d->mGroupHeaderItemIndex = index; if ( view()->isExpanded( index ) ) { act = menu.addAction( i18n ( "Collapse Group" ) ); connect( act, SIGNAL(triggered(bool)), this, SLOT(slotCollapseItem()) ); } else { act = menu.addAction( i18n ( "Expand Group" ) ); connect( act, SIGNAL(triggered(bool)), this, SLOT(slotExpandItem()) ); } menu.addSeparator(); act = menu.addAction( i18n( "Expand All Groups" ) ); connect( act, SIGNAL(triggered(bool)), view(), SLOT(slotExpandAllGroups()) ); act = menu.addAction( i18n( "Collapse All Groups" ) ); connect( act, SIGNAL(triggered(bool)), view(), SLOT(slotCollapseAllGroups()) ); menu.exec( globalPos ); } void Widget::viewDragEnterEvent( QDragEnterEvent *e ) { if ( !canAcceptDrag( e ) ) { e->ignore(); return; } e->accept(); } void Widget::viewDragMoveEvent( QDragMoveEvent *e ) { if ( !canAcceptDrag( e ) ) { e->ignore(); return; } e->accept(); } enum DragMode { DragCopy, DragMove, DragCancel }; void Widget::viewDropEvent( QDropEvent *e ) { if ( !canAcceptDrag( e ) ) { e->ignore(); return; } KUrl::List urls = KUrl::List::fromMimeData( e->mimeData() ); if ( urls.isEmpty() ) { kWarning() << "Could not decode drag data!"; e->ignore(); return; } e->accept(); int action; if ( ( e->possibleActions() & Qt::MoveAction ) == 0 ) { // We can't move anyway action = DragCopy; } else { action = DragCancel; int keybstate = QApplication::keyboardModifiers(); if ( keybstate & Qt::CTRL ) { action = DragCopy; } else if ( keybstate & Qt::SHIFT ) { action = DragMove; } else { KMenu menu; QAction *moveAction = menu.addAction( KIcon( QLatin1String( "go-jump" )), i18n( "&Move Here" ) ); QAction *copyAction = menu.addAction( KIcon( QLatin1String( "edit-copy" ) ), i18n( "&Copy Here" ) ); menu.addSeparator(); menu.addAction( KIcon( QLatin1String( "dialog-cancel" ) ), i18n( "C&ancel" ) ); QAction *menuChoice = menu.exec( QCursor::pos() ); if ( menuChoice == moveAction ) { action = DragMove; } else if ( menuChoice == copyAction ) { action = DragCopy; } else { action = DragCancel; } } } if ( action == DragCancel ) return; Collection::List collections = static_cast( storageModel() )->displayedCollections(); Collection target = collections.first(); Item::List items; foreach ( const KUrl &url, urls ) { items << Item::fromUrl( url ); } if ( action == DragCopy ) { new ItemCopyJob( items, target, this ); } else if ( action == DragMove ) { new ItemMoveJob( items, target, this ); } } void Widget::viewStartDragRequest() { Collection::List collections = static_cast( storageModel() )->displayedCollections(); if ( collections.isEmpty() ) return; // no folder here QList items = d->selectionAsItems(); if ( items.isEmpty() ) return; bool readOnly = false; foreach ( const Collection &c, collections ) { // We won't be able to remove items from this collection if ( ( c.rights() & Collection::CanDeleteItem ) == 0 ) { // So the drag will be read-only readOnly = true; break; } } KUrl::List urls; foreach ( const Item &i, items ) { urls << i.url( Item::UrlWithMimeType ); } QMimeData *mimeData = new QMimeData; urls.populateMimeData( mimeData ); QDrag *drag = new QDrag( view()->viewport() ); drag->setMimeData( mimeData ); // Set pixmap QPixmap pixmap; if( items.size() == 1 ) { pixmap = QPixmap( DesktopIcon(QLatin1String( "mail-message" ), KIconLoader::SizeSmall) ); } else { pixmap = QPixmap( DesktopIcon(QLatin1String( "document-multiple" ), KIconLoader::SizeSmall) ); } // Calculate hotspot (as in Konqueror) if( !pixmap.isNull() ) { drag->setHotSpot( QPoint( pixmap.width() / 2, pixmap.height() / 2 ) ); drag->setPixmap( pixmap ); } if ( readOnly ) drag->exec( Qt::CopyAction ); else drag->exec( Qt::CopyAction | Qt::MoveAction ); } Item::List Widget::Private::selectionAsItems() const { Item::List res; QList selection = q->view()->selectionAsMessageItemList(); foreach ( Core::MessageItem *mi, selection ) { Item i = itemForRow( mi->currentModelIndexRow() ); Q_ASSERT( i.isValid() ); res << i; } return res; } Item Widget::Private::itemForRow( int row ) const { return static_cast( q->storageModel() )->itemForRow( row ); } KMime::Message::Ptr Widget::Private::messageForRow( int row ) const { return static_cast( q->storageModel() )->messageForRow( row ); } Item Widget::currentItem() const { Core::MessageItem *mi = view()->currentMessageItem(); if ( mi == 0 ) { return Item(); } return d->itemForRow( mi->currentModelIndexRow() ); } KMime::Message::Ptr Widget::currentMessage() const { Core::MessageItem *mi = view()->currentMessageItem(); if ( mi == 0 ) { return KMime::Message::Ptr(); } return d->messageForRow( mi->currentModelIndexRow() ); } QList Widget::selectionAsMessageList( bool includeCollapsedChildren ) const { QList lstMiPtr; QList lstMi = view()->selectionAsMessageItemList( includeCollapsedChildren ); if ( lstMi.isEmpty() ) { return lstMiPtr; } foreach( Core::MessageItem *it, lstMi ) { lstMiPtr.append( d->messageForRow( it->currentModelIndexRow() ) ); } return lstMiPtr; } QList Widget::selectionAsMessageItemList( bool includeCollapsedChildren ) const { QList lstMiPtr; QList lstMi = view()->selectionAsMessageItemList( includeCollapsedChildren ); if ( lstMi.isEmpty() ) { return lstMiPtr; } foreach( Core::MessageItem *it, lstMi ) { lstMiPtr.append( d->itemForRow( it->currentModelIndexRow() ) ); } return lstMiPtr; } QVector Widget::selectionAsMessageItemListId( bool includeCollapsedChildren ) const { QVector lstMiPtr; QList lstMi = view()->selectionAsMessageItemList( includeCollapsedChildren ); if ( lstMi.isEmpty() ) { return lstMiPtr; } foreach( Core::MessageItem *it, lstMi ) { lstMiPtr.append( d->itemForRow( it->currentModelIndexRow() ).id() ); } return lstMiPtr; } QList Widget::selectionAsListMessageId( bool includeCollapsedChildren ) const { QList lstMiPtr; QList lstMi = view()->selectionAsMessageItemList( includeCollapsedChildren ); if ( lstMi.isEmpty() ) { return lstMiPtr; } foreach( Core::MessageItem *it, lstMi ) { lstMiPtr.append( d->itemForRow( it->currentModelIndexRow() ).id() ); } return lstMiPtr; } QList Widget::currentThreadAsMessageList() const { QList lstMiPtr; QList lstMi = view()->currentThreadAsMessageItemList(); if ( lstMi.isEmpty() ) { return lstMiPtr; } foreach( Core::MessageItem *it, lstMi ) { lstMiPtr.append( d->itemForRow( it->currentModelIndexRow() ) ); } return lstMiPtr; } QList Widget::currentFilterStatus() const { return view()->currentFilterStatus(); } QString Widget::currentFilterSearchString() const { return view()->currentFilterSearchString(); } bool Widget::isThreaded() const { return view()->isThreaded(); } bool Widget::selectionEmpty() const { return view()->selectionEmpty(); } bool Widget::getSelectionStats( Akonadi::Item::List &selectedItems, Akonadi::Item::List &selectedVisibleItems, bool * allSelectedBelongToSameThread, bool includeCollapsedChildren ) const { if ( !storageModel() ) return false; selectedItems.clear(); selectedVisibleItems.clear(); QList< Core::MessageItem * > selected = view()->selectionAsMessageItemList( includeCollapsedChildren ); Core::MessageItem * topmost = 0; *allSelectedBelongToSameThread = true; foreach( Core::MessageItem *it, selected ) { const Item item = d->itemForRow( it->currentModelIndexRow() ); selectedItems.append( item ); if ( view()->isDisplayedWithParentsExpanded( it ) ) selectedVisibleItems.append( item ); if ( topmost == 0 ) topmost = ( *it ).topmostMessage(); else { if ( topmost != ( *it ).topmostMessage() ) *allSelectedBelongToSameThread = false; } } return true; } void Widget::deletePersistentSet( MessageList::Core::MessageItemSetReference ref ) { view()->deletePersistentSet( ref ); } void Widget::markMessageItemsAsAboutToBeRemoved( MessageList::Core::MessageItemSetReference ref, bool bMark ) { QList< Core::MessageItem * > lstPersistent = view()->persistentSetCurrentMessageItemList( ref ); if ( !lstPersistent.isEmpty() ) view()->markMessageItemsAsAboutToBeRemoved( lstPersistent, bMark ); } QList Widget::itemListFromPersistentSet( MessageList::Core::MessageItemSetReference ref ) { QList lstItem; QList< Core::MessageItem * > refList = view()->persistentSetCurrentMessageItemList( ref ); if ( !refList.isEmpty() ) { foreach( Core::MessageItem *it, refList ) { lstItem.append( d->itemForRow( it->currentModelIndexRow() ) ); } } return lstItem; } MessageList::Core::MessageItemSetReference Widget::selectionAsPersistentSet( bool includeCollapsedChildren ) const { QList lstMi = view()->selectionAsMessageItemList( includeCollapsedChildren ); if ( lstMi.isEmpty() ) { return -1; } return view()->createPersistentSet( lstMi ); } MessageList::Core::MessageItemSetReference Widget::currentThreadAsPersistentSet() const { QList lstMi = view()->currentThreadAsMessageItemList(); if ( lstMi.isEmpty() ) { return -1; } return view()->createPersistentSet( lstMi ); } Akonadi::Collection Widget::currentCollection() const { Collection::List collections = static_cast( storageModel() )->displayedCollections(); if ( collections.size()!=1 ) return Akonadi::Collection(); // no folder here or too many (in case we can't decide where the drop will end) return collections.first(); } void Widget::slotCollapseItem() { view()->setCollapseItem(d->mGroupHeaderItemIndex); } void Widget::slotExpandItem() { view()->setExpandItem(d->mGroupHeaderItemIndex); }