diff --git a/messagelist/core/messageitem.cpp b/messagelist/core/messageitem.cpp index 755cd4d027..4bd2ecd1ea 100644 --- a/messagelist/core/messageitem.cpp +++ b/messagelist/core/messageitem.cpp @@ -1,759 +1,766 @@ /****************************************************************************** * * Copyright 2008 Szymon Tomasz Stefanek * * 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 "messageitem.h" #include "messageitem_p.h" #include "theme.h" #include #include #include #include #include +#include #include #include using namespace MessageList::Core; K_GLOBAL_STATIC( TagCache, s_tagCache ) class MessageItem::Tag::Private { public: Private() :mPriority( 0 ) //Initialize it { } QPixmap mPixmap; QString mName; QString mId; ///< The unique id of this tag QColor mTextColor; QColor mBackgroundColor; QFont mFont; int mPriority; }; MessageItem::Tag::Tag( const QPixmap &pix, const QString &tagName, const QString &tagId ) : d( new Private ) { d->mPixmap = pix; d->mName = tagName; d->mId = tagId; } MessageItem::Tag::~Tag() { delete d; } QPixmap MessageItem::Tag::pixmap() const { return d->mPixmap; } QString MessageItem::Tag::name() const { return d->mName; } QString MessageItem::Tag::id() const { return d->mId; } QColor MessageItem::Tag::textColor() const { return d->mTextColor; } QColor MessageItem::Tag::backgroundColor() const { return d->mBackgroundColor; } QFont MessageItem::Tag::font() const { return d->mFont; } int MessageItem::Tag::priority() const { return d->mPriority; } void MessageItem::Tag::setTextColor( const QColor &textColor ) { d->mTextColor = textColor; } void MessageItem::Tag::setBackgroundColor( const QColor &backgroundColor ) { d->mBackgroundColor = backgroundColor; } void MessageItem::Tag::setFont( const QFont &font ) { d->mFont = font; } void MessageItem::Tag::setPriority( int priority ) { d->mPriority = priority; } class MessageItemPrivateSettings { public: QColor mColorUnreadMessage; QColor mColorImportantMessage; QColor mColorToDoMessage; QFont mFont; QFont mFontUnreadMessage; QFont mFontImportantMessage; QFont mFontToDoMessage; QString mFontKey; QString mFontUnreadMessageKey; QString mFontImportantMessageKey; QString mFontToDoMessageKey; }; K_GLOBAL_STATIC(MessageItemPrivateSettings, s_settings) MessageItemPrivate::MessageItemPrivate( MessageItem* qq ) : ItemPrivate( qq ), mThreadingStatus( MessageItem::ParentMissing ), mEncryptionState( MessageItem::NotEncrypted ), mSignatureState( MessageItem::NotSigned ), mAboutToBeRemoved( false ), mSubjectIsPrefixed( false ), mTagList( 0 ) { } MessageItemPrivate::~MessageItemPrivate() { s_tagCache->cancelRequest(this); invalidateTagCache(); } void MessageItemPrivate::invalidateTagCache() { if ( mTagList ) { qDeleteAll( *mTagList ); delete mTagList; mTagList = 0; } } void MessageItemPrivate::invalidateAnnotationCache() { } +Akonadi::Relation MessageItemPrivate::relatedNoteRelation() const +{ + Akonadi::Relation relation; + foreach (const Akonadi::Relation &r, mAkonadiItem.relations()) { + if (r.type() == Akonadi::Relation::GENERIC && r.right().mimeType() == Akonadi::NoteUtils::noteMimeType() ) { + relation = r; + break; + } + } + return relation; +} + const MessageItem::Tag* MessageItemPrivate::bestTag() const { const MessageItem::Tag *best = 0; foreach( const MessageItem::Tag* tag, getTagList() ) { if ( !best || tag->priority() < best->priority() ) best = tag; } return best; } void MessageItemPrivate::fillTagList(const Akonadi::Tag::List &taglist) { Q_ASSERT( !mTagList ); mTagList = new QList; // TODO: The tag pointers here could be shared between all items, there really is no point in // creating them for each item that has tags //Priority sort this and make bestTag more efficient foreach( const Akonadi::Tag &tag, taglist ) { QString symbol = QLatin1String( "mail-tagged" ); Akonadi::TagAttribute *attr = tag.attribute(); if (attr) { if (!attr->iconName().isEmpty()) { symbol = attr->iconName(); } } MessageItem::Tag *messageListTag = new MessageItem::Tag( SmallIcon( symbol ), tag.name(), tag.url().url() ); if (attr) { messageListTag->setTextColor( attr->textColor() ); messageListTag->setBackgroundColor( attr->backgroundColor() ); if (!attr->font().isEmpty()) { QFont font; if (font.fromString( attr->font() )) { messageListTag->setFont( font ); } } if (attr->priority() != -1) { messageListTag->setPriority( attr->priority() ); } else { messageListTag->setPriority( 0xFFFF ); } } mTagList->append( messageListTag ); } } QList MessageItemPrivate::getTagList() const { if ( !mTagList ) { s_tagCache->retrieveTags(mAkonadiItem.tags(), const_cast(this)); return QList(); } return *mTagList; } bool MessageItemPrivate::tagListInitialized() const { return mTagList != 0; } MessageItem::MessageItem() : Item( Message, new MessageItemPrivate( this ) ), ModelInvariantIndex() { } MessageItem::MessageItem ( MessageItemPrivate* dd ) : Item ( Message, dd ), ModelInvariantIndex() { } MessageItem::~MessageItem() { } QList< MessageItem::Tag * > MessageItem::tagList() const { Q_D( const MessageItem ); return d->getTagList(); } bool MessageItem::hasAnnotation() const { Q_D( const MessageItem ); - return !d->mAkonadiItem.relations().isEmpty(); + return d->relatedNoteRelation().isValid(); } Akonadi::Item MessageItem::annotation() const { Q_D( const MessageItem ); if ( hasAnnotation() ) { - Akonadi::Relation relation; - foreach( const Akonadi::Relation &r, d->mAkonadiItem.relations() ) { - if ( r.type() == Akonadi::Relation::GENERIC ) { - relation = r; - break; - } - } + Akonadi::Relation relation = d->relatedNoteRelation(); if ( relation.isValid() ) { return relation.right(); } } return Akonadi::Item(); } const MessageItem::Tag * MessageItemPrivate::findTagInternal( const QString &szTagId ) const { foreach( const MessageItem::Tag *tag, getTagList() ) { if ( tag->id() == szTagId ) return tag; } return 0; } const MessageItem::Tag *MessageItem::findTag( const QString &szTagId ) const { Q_D( const MessageItem ); return d->findTagInternal( szTagId ); } QString MessageItem::tagListDescription() const { QString ret; foreach( const Tag *tag, tagList() ) { if ( !ret.isEmpty() ) ret += QLatin1String( ", " ); ret += tag->name(); } return ret; } void MessageItem::invalidateTagCache() { Q_D( MessageItem ); d->invalidateTagCache(); } void MessageItem::invalidateAnnotationCache() { Q_D( MessageItem ); d->invalidateAnnotationCache(); } QColor MessageItem::textColor() const { Q_D( const MessageItem ); const Tag *bestTag = d->bestTag(); if ( bestTag != 0 && bestTag->textColor().isValid() ) { return bestTag->textColor(); } QColor clr; Akonadi::MessageStatus messageStatus = status(); if ( !messageStatus.isRead() ) { clr = s_settings->mColorUnreadMessage; } else if ( messageStatus.isImportant() ) { clr = s_settings->mColorImportantMessage; } else if ( messageStatus.isToAct() ) { clr = s_settings->mColorToDoMessage; } return clr; } QColor MessageItem::backgroundColor() const { Q_D( const MessageItem ); const Tag *bestTag = d->bestTag(); if ( bestTag ) { return bestTag->backgroundColor(); } else { return QColor(); } } QFont MessageItem::font() const { Q_D( const MessageItem ); // for performance reasons we don't want font retrieval to trigger // full tags loading, as the font is used for geometry calculation // and thus this method called for each item if ( d->tagListInitialized() ) { const Tag *bestTag = d->bestTag(); if ( bestTag && bestTag->font() != QFont() ) { return bestTag->font(); } } QFont font; // from KDE3: "important" overrides "new" overrides "unread" overrides "todo" Akonadi::MessageStatus messageStatus = status(); if ( messageStatus.isImportant() ) { font = s_settings->mFontImportantMessage; } else if ( !messageStatus.isRead() ) { font = s_settings->mFontUnreadMessage; } else if ( messageStatus.isToAct() ) { font = s_settings->mFontToDoMessage; } else { font = s_settings->mFont; } return font; } QString MessageItem::fontKey() const { Q_D( const MessageItem ); // for performance reasons we don't want font retrieval to trigger // full tags loading, as the font is used for geometry calculation // and thus this method called for each item if ( d->tagListInitialized() ) { const Tag *bestTag = d->bestTag(); if ( bestTag && bestTag->font() != QFont() ) { return bestTag->font().key(); } } // from KDE3: "important" overrides "new" overrides "unread" overrides "todo" Akonadi::MessageStatus messageStatus = status(); if ( messageStatus.isImportant() ) { return s_settings->mFontImportantMessageKey; } else if ( !messageStatus.isRead() ) { return s_settings->mFontUnreadMessageKey; } else if ( messageStatus.isToAct() ) { return s_settings->mFontToDoMessageKey; } else { return s_settings->mFontKey; } } MessageItem::SignatureState MessageItem::signatureState() const { Q_D( const MessageItem ); return d->mSignatureState; } void MessageItem::setSignatureState( SignatureState state ) { Q_D( MessageItem ); d->mSignatureState = state; } MessageItem::EncryptionState MessageItem::encryptionState() const { Q_D( const MessageItem ); return d->mEncryptionState; } void MessageItem::setEncryptionState( EncryptionState state ) { Q_D( MessageItem ); d->mEncryptionState = state; } QByteArray MessageItem::messageIdMD5() const { Q_D( const MessageItem ); return d->mMessageIdMD5; } void MessageItem::setMessageIdMD5( const QByteArray &md5 ) { Q_D( MessageItem ); d->mMessageIdMD5 = md5; } QByteArray MessageItem::inReplyToIdMD5() const { Q_D( const MessageItem ); return d->mInReplyToIdMD5; } void MessageItem::setInReplyToIdMD5( const QByteArray& md5 ) { Q_D( MessageItem ); d->mInReplyToIdMD5 = md5; } QByteArray MessageItem::referencesIdMD5() const { Q_D( const MessageItem ); return d->mReferencesIdMD5; } void MessageItem::setReferencesIdMD5( const QByteArray& md5 ) { Q_D( MessageItem ); d->mReferencesIdMD5 = md5; } void MessageItem::setSubjectIsPrefixed( bool subjectIsPrefixed ) { Q_D( MessageItem ); d->mSubjectIsPrefixed = subjectIsPrefixed; } bool MessageItem::subjectIsPrefixed() const { Q_D( const MessageItem ); return d->mSubjectIsPrefixed; } QByteArray MessageItem::strippedSubjectMD5() const { Q_D( const MessageItem ); return d->mStrippedSubjectMD5; } void MessageItem::setStrippedSubjectMD5( const QByteArray& md5 ) { Q_D( MessageItem ); d->mStrippedSubjectMD5 = md5; } bool MessageItem::aboutToBeRemoved() const { Q_D( const MessageItem ); return d->mAboutToBeRemoved; } void MessageItem::setAboutToBeRemoved( bool aboutToBeRemoved ) { Q_D( MessageItem ); d->mAboutToBeRemoved = aboutToBeRemoved; } MessageItem::ThreadingStatus MessageItem::threadingStatus() const { Q_D( const MessageItem ); return d->mThreadingStatus; } void MessageItem::setThreadingStatus( ThreadingStatus threadingStatus ) { Q_D( MessageItem ); d->mThreadingStatus = threadingStatus; } unsigned long MessageItem::uniqueId() const { Q_D( const MessageItem ); return d->mAkonadiItem.id(); } Akonadi::Item MessageList::Core::MessageItem::akonadiItem() const { Q_D( const MessageItem ); return d->mAkonadiItem; } void MessageList::Core::MessageItem::setAkonadiItem(const Akonadi::Item& item) { Q_D( MessageItem ); d->mAkonadiItem = item; } MessageItem * MessageItem::topmostMessage() { if ( !parent() ) return this; if ( parent()->type() == Item::Message ) return static_cast< MessageItem * >( parent() )->topmostMessage(); return this; } QString MessageItem::accessibleTextForField(Theme::ContentItem::Type field) { switch (field) { case Theme::ContentItem::Subject: return d_ptr->mSubject; case Theme::ContentItem::Sender: return d_ptr->mSender; case Theme::ContentItem::Receiver: return d_ptr->mReceiver; case Theme::ContentItem::SenderOrReceiver: return senderOrReceiver(); case Theme::ContentItem::Date: return formattedDate(); case Theme::ContentItem::Size: return formattedSize(); case Theme::ContentItem::RepliedStateIcon: return status().isReplied() ? i18nc( "Status of an item", "Replied" ) : QString(); case Theme::ContentItem::ReadStateIcon: return status().isRead() ? i18nc( "Status of an item", "Read" ) : i18nc( "Status of an item", "Unread" ); case Theme::ContentItem::CombinedReadRepliedStateIcon: return accessibleTextForField( Theme::ContentItem::ReadStateIcon ) + accessibleTextForField( Theme::ContentItem::RepliedStateIcon ); default: return QString(); } } QString MessageItem::accessibleText( const Theme* theme, int columnIndex ) { QStringList rowsTexts; Q_FOREACH( Theme::Row *row, theme->column(columnIndex)->messageRows() ) { QStringList leftStrings; QStringList rightStrings; Q_FOREACH( Theme::ContentItem *contentItem, row->leftItems() ) { leftStrings.append( accessibleTextForField( contentItem->type() ) ); } Q_FOREACH( Theme::ContentItem *contentItem, row->rightItems() ) { rightStrings.insert( rightStrings.begin(), accessibleTextForField( contentItem->type() ) ); } rowsTexts.append( ( leftStrings + rightStrings ).join( QLatin1String( " " ) ) ); } return rowsTexts.join( QLatin1String(" ") ); } void MessageItem::subTreeToList( QList< MessageItem * > &list ) { list.append( this ); QList< Item * > * childList = childItems(); if ( !childList ) return; QList< Item * >::ConstIterator end( childList->constEnd() ); for ( QList< Item * >::ConstIterator it = childList->constBegin(); it != end; ++it ) { Q_ASSERT( ( *it )->type() == Item::Message ); static_cast< MessageItem * >( *it )->subTreeToList( list ); } } void MessageItem::setUnreadMessageColor( const QColor &color ) { s_settings->mColorUnreadMessage = color; } void MessageItem::setImportantMessageColor( const QColor &color ) { s_settings->mColorImportantMessage = color; } void MessageItem::setToDoMessageColor( const QColor &color ) { s_settings->mColorToDoMessage = color; } void MessageItem::setGeneralFont( const QFont &font ) { s_settings->mFont = font; s_settings->mFontKey = font.key(); } void MessageItem::setUnreadMessageFont( const QFont &font ) { s_settings->mFontUnreadMessage = font; s_settings->mFontUnreadMessageKey = font.key(); } void MessageItem::setImportantMessageFont( const QFont &font ) { s_settings->mFontImportantMessage = font; s_settings->mFontImportantMessageKey = font.key(); } void MessageItem::setToDoMessageFont( const QFont &font ) { s_settings->mFontToDoMessage = font; s_settings->mFontToDoMessageKey = font.key(); } FakeItemPrivate::FakeItemPrivate( FakeItem *qq ) : MessageItemPrivate( qq ) { } FakeItem::FakeItem() : MessageItem( new FakeItemPrivate( this ) ) { } FakeItem::~FakeItem() { } QList< MessageItem::Tag * > FakeItem::tagList() const { Q_D( const FakeItem ); return d->mFakeTags; } void FakeItem::setFakeTags( const QList< MessageItem::Tag* > &tagList ) { Q_D( FakeItem ); d->mFakeTags = tagList; } bool FakeItem::hasAnnotation() const { return true; } TagCache::TagCache() :QObject(), mMonitor(new Akonadi::Monitor(this)) { mCache.setMaxCost(100); mMonitor->setTypeMonitored(Akonadi::Monitor::Tags); mMonitor->tagFetchScope().fetchAttribute(); connect(mMonitor, SIGNAL(tagAdded(Akonadi::Tag)), this, SLOT(onTagAdded(Akonadi::Tag))); connect(mMonitor, SIGNAL(tagRemoved(Akonadi::Tag)), this, SLOT(onTagRemoved(Akonadi::Tag))); connect(mMonitor, SIGNAL(tagChanged(Akonadi::Tag)), this, SLOT(onTagChanged(Akonadi::Tag))); } void TagCache::onTagAdded(const Akonadi::Tag &tag) { mCache.insert(tag.id(), new Akonadi::Tag(tag)); } void TagCache::onTagChanged(const Akonadi::Tag &tag) { mCache.remove(tag.id()); } void TagCache::onTagRemoved(const Akonadi::Tag &tag) { mCache.remove(tag.id()); } void TagCache::retrieveTags(const Akonadi::Tag::List &tags, MessageItemPrivate *m) { //Retrieval is in progress if (mRequests.key(m)) { return; } Akonadi::Tag::List toFetch; Akonadi::Tag::List available; Q_FOREACH( const Akonadi::Tag &tag, tags ) { if (mCache.contains(tag.id())) { available << *mCache.object(tag.id()); } else { toFetch << tag; } } //Because fillTagList expects to be called once we either fetch all or none if (!toFetch.isEmpty()) { Akonadi::TagFetchJob *tagFetchJob = new Akonadi::TagFetchJob(tags, this); tagFetchJob->fetchScope().fetchAttribute(); connect(tagFetchJob, SIGNAL(result(KJob*)), this, SLOT(onTagsFetched(KJob*))); mRequests.insert(tagFetchJob, m); } else { m->fillTagList(available); } } void TagCache::cancelRequest(MessageItemPrivate *m) { const QList keys = mRequests.keys(m); Q_FOREACH( KJob *job, keys ) { mRequests.remove(job); } } void TagCache::onTagsFetched(KJob *job) { if (job->error()) { kWarning() << "Failed to fetch tags: " << job->errorString(); return; } Akonadi::TagFetchJob *fetchJob = static_cast(job); Q_FOREACH( const Akonadi::Tag &tag, fetchJob->tags() ) { mCache.insert(tag.id(), new Akonadi::Tag(tag)); } MessageItemPrivate *m = mRequests.take(fetchJob); if (m) { m->fillTagList(fetchJob->tags()); } } diff --git a/messagelist/core/messageitem_p.h b/messagelist/core/messageitem_p.h index dac7b67fac..dc4e3e7ae2 100644 --- a/messagelist/core/messageitem_p.h +++ b/messagelist/core/messageitem_p.h @@ -1,120 +1,122 @@ /****************************************************************************** * * Copyright 2008 Szymon Tomasz Stefanek * * 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. * *******************************************************************************/ #ifndef MESSAGELIST_MESSAGEITEM_P_H #define MESSAGELIST_MESSAGEITEM_P_H #include "messageitem.h" #include "item_p.h" #include #include #include #include #include #include namespace MessageList { namespace Core { class MessageItemPrivate : public ItemPrivate { public: explicit MessageItemPrivate( MessageItem *qq ); ~MessageItemPrivate(); /** * Linear search in the list of tags. The lists of tags * associated to a message are supposed to be very short (c'mon.. you won't add more than a couple of tags to a single msg). * so a linear search is better than a hash lookup in most cases. */ const MessageItem::Tag *findTagInternal( const QString &szTagId ) const; /// Returns the list of tags. This is calculated on demand and cached in mTagList QList getTagList() const; bool tagListInitialized() const; /// Returns the tag with the highest priority, or 0 if there are no tags const MessageItem::Tag* bestTag() const; /// Deletes the internal list of tags void invalidateTagCache(); /// Deletes the cache of the annotation void invalidateAnnotationCache(); // This creates mTagList and fills it with useful data void fillTagList( const Akonadi::Tag::List &taglist ); + Akonadi::Relation relatedNoteRelation() const; + QByteArray mMessageIdMD5; ///< always set QByteArray mInReplyToIdMD5; ///< set only if we're doing threading QByteArray mReferencesIdMD5; ///< set only if we're doing threading QByteArray mStrippedSubjectMD5; ///< set only if we're doing threading Akonadi::Item mAkonadiItem; MessageItem::ThreadingStatus mThreadingStatus : 4; MessageItem::EncryptionState mEncryptionState : 4; MessageItem::SignatureState mSignatureState : 4; bool mAboutToBeRemoved : 1; ///< Set to true when this item is going to be deleted and shouldn't be selectable bool mSubjectIsPrefixed : 1; ///< set only if we're doing subject based threading private: // List of all tags. If this is 0, it means we have not yet calculated this list. It is calculated // on demand when needed. mutable QList< MessageItem::Tag * > * mTagList; }; class FakeItemPrivate : public MessageItemPrivate { public: explicit FakeItemPrivate( FakeItem *qq ); QList mFakeTags; }; /** * A tag cache */ class TagCache : public QObject { Q_OBJECT public: TagCache(); void retrieveTags(const Akonadi::Tag::List &tags, MessageItemPrivate *m); void cancelRequest(MessageItemPrivate *m); private Q_SLOTS: void onTagAdded(const Akonadi::Tag &); void onTagChanged(const Akonadi::Tag &); void onTagRemoved(const Akonadi::Tag &); void onTagsFetched(KJob*); private: QHash mRequests; QCache mCache; Akonadi::Monitor *mMonitor; }; } } #endif diff --git a/messageviewer/job/createnotejob.cpp b/messageviewer/job/createnotejob.cpp index f58702d9d9..1c1e81be58 100644 --- a/messageviewer/job/createnotejob.cpp +++ b/messageviewer/job/createnotejob.cpp @@ -1,105 +1,105 @@ /* Copyright (c) 2014 Sandro Knauß This program 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. 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 "createnotejob.h" #include #include #include #include #include #include #include #include using namespace MessageViewer; CreateNoteJob::CreateNoteJob(const KMime::Message::Ptr ¬ePtr, const Akonadi::Collection &collection, const Akonadi::Item &item, QObject *parent) : KJob(parent), mItem(item), mCollection(collection), mNote(notePtr) { } CreateNoteJob::~CreateNoteJob() { } void CreateNoteJob::start() { mNote.setFrom(QCoreApplication::applicationName() + QCoreApplication::applicationVersion()); mNote.setLastModifiedDate(KDateTime::currentUtcDateTime()); if (!mItem.relations().isEmpty()) { Akonadi::Relation relation; foreach (const Akonadi::Relation &r, mItem.relations()) { // assuming that GENERIC relations to emails are notes is a pretty horirific hack imo - aseigo - if (r.type() == Akonadi::Relation::GENERIC/* && r.right().mimeType() == Akonadi::NoteUtils::noteMimeType(*/) { + if (r.type() == Akonadi::Relation::GENERIC && r.right().mimeType() == Akonadi::NoteUtils::noteMimeType() ) { relation = r; break; } } if (relation.isValid()) { Akonadi::Item item = relation.right(); item.setMimeType(Akonadi::NoteUtils::noteMimeType()); item.setPayload(mNote.message()); Akonadi::ItemModifyJob *modifyJob = new Akonadi::ItemModifyJob(item); connect(modifyJob, SIGNAL(result(KJob*)), this, SLOT(noteUpdated(KJob*))); return; } } Akonadi::Item newNoteItem; newNoteItem.setMimeType( Akonadi::NoteUtils::noteMimeType() ); newNoteItem.setPayload( mNote.message() ); Akonadi::ItemCreateJob *createJob = new Akonadi::ItemCreateJob(newNoteItem, mCollection); connect(createJob, SIGNAL(result(KJob*)), this, SLOT(noteCreated(KJob*))); } void CreateNoteJob::noteCreated(KJob *job) { if ( job->error() ) { qDebug() << "Error during create new Note "<errorString(); setError( job->error() ); setErrorText( job->errorText() ); emitResult(); } else { Akonadi::ItemCreateJob *createJob = static_cast ( job ); Akonadi::Relation relation( Akonadi::Relation::GENERIC, mItem, createJob->item() ); Akonadi::RelationCreateJob *job = new Akonadi::RelationCreateJob( relation ); connect( job, SIGNAL( result( KJob * ) ), this, SLOT( relationCreated( KJob * ) ) ); } } void CreateNoteJob::noteUpdated(KJob *job) { if ( job->error() ) { setError( job->error() ); setErrorText( job->errorText() ); } emitResult(); } void CreateNoteJob::relationCreated(KJob *job) { Q_UNUSED(job) emitResult(); } diff --git a/messageviewer/viewer/viewer_p.cpp b/messageviewer/viewer/viewer_p.cpp index d1fdec6450..3554a494e4 100644 --- a/messageviewer/viewer/viewer_p.cpp +++ b/messageviewer/viewer/viewer_p.cpp @@ -1,3503 +1,3509 @@ /* -*- mode: C++; c-file-style: "gnu" -*- Copyright (c) 1997 Markus Wuebben Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Andras Mantia Copyright (c) 2010 Torgny Nyblom Copyright (c) 2011, 2012 Laurent Montel 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. */ //#define MESSAGEVIEWER_READER_HTML_DEBUG 1 #include "viewer_p.h" #include "viewer.h" #include "viewer/objecttreeemptysource.h" #include "viewer/objecttreeviewersource.h" #include "messagedisplayformatattribute.h" #include "grantleetheme/grantleethememanager.h" #include "grantleetheme/globalsettings_base.h" #include "scamdetection/scamdetectionwarningwidget.h" #include "scamdetection/scamattribute.h" #include "adblock/adblockmanager.h" #include "widgets/todoedit.h" #include "widgets/eventedit.h" #include "widgets/noteedit.h" #ifdef MESSAGEVIEWER_READER_HTML_DEBUG #include "htmlwriter/filehtmlwriter.h" #include "htmlwriter/teehtmlwriter.h" #endif #include // link() #include //KDE includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //Qt includes #include #include #include #include #include #include #include #include #include #include #include //libkdepim #include "libkdepim/misc/broadcaststatus.h" #include #include #include #include #include #include #include #include #include #include "chiasmuskeyselector.h" #include "utils/autoqpointer.h" //own includes #include "widgets/attachmentdialog.h" #include "viewer/attachmentstrategy.h" #include "csshelper.h" #include "viewer/editorwatcher.h" #include "settings/globalsettings.h" #include "header/headerstyle.h" #include "header/headerstrategy.h" #include "widgets/htmlstatusbar.h" #include "htmlwriter/webkitparthtmlwriter.h" #include "viewer/mailsourceviewer.h" #include "viewer/mimetreemodel.h" #include "viewer/nodehelper.h" #include "viewer/objecttreeparser.h" #include "viewer/urlhandlermanager.h" #include "utils/util.h" #include "widgets/vcardviewer.h" #include "viewer/mailwebview.h" #include "findbar/findbarmailwebview.h" #include "pimcommon/translator/translatorwidget.h" #include "job/createtodojob.h" #include "job/createeventjob.h" #include "job/createnotejob.h" #include "interfaces/bodypart.h" #include "interfaces/htmlwriter.h" #include #include #include #include #include "messagecore/settings/globalsettings.h" #include #include #include #include #include using namespace boost; using namespace MailTransport; using namespace MessageViewer; using namespace MessageCore; const int ViewerPrivate::delay = 150; const qreal ViewerPrivate::zoomBy = 20; static QAtomicInt _k_attributeInitialized; ViewerPrivate::ViewerPrivate(Viewer *aParent, QWidget *mainWindow, KActionCollection *actionCollection ) : QObject(aParent), mNodeHelper( new NodeHelper ), mViewer( 0 ), mFindBar( 0 ), mTranslatorWidget(0), mAttachmentStrategy( 0 ), mHeaderStrategy( 0 ), mHeaderStyle( 0 ), mUpdateReaderWinTimer( 0 ), mResizeTimer( 0 ), mOldGlobalOverrideEncoding( QLatin1String("---") ), // init with dummy value mMsgDisplay( true ), mCSSHelper( 0 ), mMainWindow( mainWindow ), mActionCollection( actionCollection ), mCopyAction( 0 ), mCopyURLAction( 0 ), mUrlOpenAction( 0 ), mSelectAllAction( 0 ), mScrollUpAction( 0 ), mScrollDownAction( 0 ), mScrollUpMoreAction( 0 ), mScrollDownMoreAction( 0 ), mHeaderOnlyAttachmentsAction( 0 ), mSelectEncodingAction( 0 ), mToggleFixFontAction( 0 ), mToggleDisplayModeAction( 0 ), mZoomTextOnlyAction( 0 ), mZoomInAction( 0 ), mZoomOutAction( 0 ), mZoomResetAction( 0 ), mToggleMimePartTreeAction( 0 ), mSpeakTextAction(0), mCreateNoteAction(0), mCanStartDrag( false ), mHtmlWriter( 0 ), mSavedRelativePosition( 0 ), mDecrytMessageOverwrite( false ), mShowSignatureDetails( false ), mShowAttachmentQuicklist( true ), mShowRawToltecMail( false ), mRecursionCountForDisplayMessage( 0 ), mCurrentContent( 0 ), mMessagePartNode( 0 ), mJob( 0 ), q( aParent ), mShowFullToAddressList( true ), mShowFullCcAddressList( true ), mPreviouslyViewedItem( -1 ), mScamDetectionWarning( 0 ), mZoomFactor( 100 ) { if ( !mainWindow ) mMainWindow = aParent; if ( _k_attributeInitialized.testAndSetAcquire( 0, 1 ) ) { Akonadi::AttributeFactory::registerAttribute(); Akonadi::AttributeFactory::registerAttribute(); } mThemeManager = new GrantleeTheme::GrantleeThemeManager(QString::fromLatin1( "header.desktop" ), mActionCollection, QLatin1String("messageviewer/themes/")); mThemeManager->setDownloadNewStuffConfigFile(QLatin1String("messageviewer_header_themes.knsrc")); connect(mThemeManager, SIGNAL(grantleeThemeSelected()), this, SLOT(slotGrantleeHeaders())); connect(mThemeManager, SIGNAL(updateThemes()), this, SLOT(slotGrantleeThemesUpdated())); mHtmlOverride = false; mHtmlLoadExtOverride = false; mHtmlLoadExternal = false; mZoomTextOnly = false; mUpdateReaderWinTimer.setObjectName( QLatin1String("mUpdateReaderWinTimer") ); mResizeTimer.setObjectName( QLatin1String("mResizeTimer") ); mExternalWindow = false; mPrinting = false; createWidgets(); createActions(); initHtmlWidget(); readConfig(); mLevelQuote = GlobalSettings::self()->collapseQuoteLevelSpin() - 1; mResizeTimer.setSingleShot( true ); connect( &mResizeTimer, SIGNAL(timeout()), this, SLOT(slotDelayedResize()) ); mUpdateReaderWinTimer.setSingleShot( true ); connect( &mUpdateReaderWinTimer, SIGNAL(timeout()), this, SLOT(updateReaderWin()) ); connect( mColorBar, SIGNAL(clicked()), this, SLOT(slotToggleHtmlMode()) ); // FIXME: Don't use the full payload here when attachment loading on demand is used, just // like in KMMainWidget::slotMessageActivated(). Akonadi::ItemFetchScope fs; fs.fetchFullPayload(); fs.fetchAttribute(); fs.fetchAttribute(); fs.fetchAttribute(); mMonitor.setItemFetchScope( fs ); connect( &mMonitor, SIGNAL(itemChanged(Akonadi::Item,QSet)), this, SLOT(slotItemChanged(Akonadi::Item,QSet)) ); connect( &mMonitor, SIGNAL(itemRemoved(Akonadi::Item)), this, SLOT(slotClear()) ); connect( &mMonitor, SIGNAL(itemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)), this, SLOT(slotItemMoved(Akonadi::Item,Akonadi::Collection,Akonadi::Collection)) ); } ViewerPrivate::~ViewerPrivate() { saveMimePartTreeConfig(); GlobalSettings::self()->writeConfig(); delete mHtmlWriter; mHtmlWriter = 0; delete mViewer; mViewer = 0; delete mCSSHelper; mNodeHelper->forceCleanTempFiles(); delete mNodeHelper; delete mThemeManager; } void ViewerPrivate::saveMimePartTreeConfig() { #ifndef QT_NO_TREEVIEW KConfigGroup grp( GlobalSettings::self()->config(), "MimePartTree" ); grp.writeEntry( "State", mMimePartTree->header()->saveState() ); #endif } void ViewerPrivate::restoreMimePartTreeConfig() { #ifndef QT_NO_TREEVIEW KConfigGroup grp( GlobalSettings::self()->config(), "MimePartTree" ); mMimePartTree->header()->restoreState( grp.readEntry( "State", QByteArray() ) ); #endif } //----------------------------------------------------------------------------- KMime::Content * ViewerPrivate::nodeFromUrl( const KUrl & url ) { KMime::Content *node = 0; if ( url.isEmpty() ) { return mMessage.get(); } if ( !url.isLocalFile() ) { QString path = url.path(KUrl::RemoveTrailingSlash); if ( path.contains(QLatin1Char(':')) ) { //if the content was not found, it might be in an extra node. Get the index of the extra node (the first part of the url), //and use the remaining part as a ContentIndex to find the node inside the extra node int i = path.left( path.indexOf(QLatin1Char(':')) ).toInt(); path = path.mid( path.indexOf(QLatin1Char(':')) + 1 ); KMime::ContentIndex idx(path); QList extras = mNodeHelper->extraContents( mMessage.get() ); if ( i >= 0 && i < extras.size() ) { KMime::Content* c = extras[i]; node = c->content( idx ); } } else { if( mMessage ) node= mMessage->content( KMime::ContentIndex( path ) ); } } else { const QString path = url.toLocalFile(); const uint right = path.lastIndexOf( QLatin1Char('/') ); const uint left = path.lastIndexOf( QLatin1Char('.'), right ); KMime::ContentIndex index(path.mid( left + 1, right - left - 1 )); node = mMessage->content( index ); } return node; } void ViewerPrivate::openAttachment( KMime::Content* node, const QString & name ) { if( !node ) { return; } bool deletedAttachment = false; if(node->contentType(false)) { deletedAttachment = (node->contentType()->mimeType() == "text/x-moz-deleted"); } if(deletedAttachment) return; const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage(); if ( isEncapsulatedMessage ) { // the viewer/urlhandlermanager expects that the message (mMessage) it is passed is the root when doing index calculation // in urls. Simply passing the result of bodyAsMessage() does not cut it as the resulting pointer is a child in its tree. KMime::Message::Ptr m = KMime::Message::Ptr( new KMime::Message ); m->setContent( node->parent()->bodyAsMessage()->encodedContent() ); m->parse(); atmViewMsg( m ); return; } // determine the MIME type of the attachment KMimeType::Ptr mimetype; // prefer the value of the Content-Type header mimetype = KMimeType::mimeType( QString::fromLatin1( node->contentType()->mimeType().toLower() ), KMimeType::ResolveAliases ); if ( !mimetype.isNull() && mimetype->is( KABC::Addressee::mimeType() ) ) { showVCard( node ); return; } // special case treatment on mac and windows QString atmName = name; if ( name.isEmpty() ) atmName = mNodeHelper->tempFileUrlFromNode( node ).toLocalFile(); if ( Util::handleUrlWithQDesktopServices( atmName ) ) return; if ( mimetype.isNull() || mimetype->name() == QLatin1String("application/octet-stream") ) { mimetype = Util::mimetype(name); } KService::Ptr offer = KMimeTypeTrader::self()->preferredService( mimetype->name(), QLatin1String("Application") ); const QString filenameText = NodeHelper::fileName( node ); AttachmentDialog dialog ( mMainWindow, filenameText, offer ? offer->name() : QString(), QString::fromLatin1( "askSave_" ) + mimetype->name() ); const int choice = dialog.exec(); if ( choice == AttachmentDialog::Save ) { Util::saveContents( mMainWindow, KMime::Content::List() << node ); } else if ( choice == AttachmentDialog::Open ) { // Open if( offer ) attachmentOpenWith( node, offer ); else attachmentOpen( node ); } else if ( choice == AttachmentDialog::OpenWith ) { attachmentOpenWith( node ); } else { // Cancel kDebug() << "Canceled opening attachment"; } } bool ViewerPrivate::deleteAttachment(KMime::Content * node, bool showWarning) { if ( !node ) return true; KMime::Content *parent = node->parent(); if ( !parent ) return true; QList extraNodes = mNodeHelper->extraContents( mMessage.get() ); if ( extraNodes.contains( node->topLevel() ) ) { KMessageBox::error( mMainWindow, i18n("Deleting an attachment from an encrypted or old-style mailman message is not supported."), i18n("Delete Attachment") ); return true; //cancelled } if ( showWarning && KMessageBox::warningContinueCancel( mMainWindow, i18n("Deleting an attachment might invalidate any digital signature on this message."), i18n("Delete Attachment"), KStandardGuiItem::del(), KStandardGuiItem::cancel(), QLatin1String("DeleteAttachmentSignatureWarning") ) != KMessageBox::Continue ) { return false; //cancelled } delete mMimePartModel->root(); mMimePartModel->setRoot( 0 ); //don't confuse the model QString filename; QString name; QByteArray mimetype; if(node->contentDisposition(false)) { filename = node->contentDisposition()->filename(); } if(node->contentType(false)) { name = node->contentType()->name(); mimetype = node->contentType()->mimeType(); } parent->removeContent( node, true ); // text/plain part: KMime::Content* deletePart = new KMime::Content(parent); deletePart->contentType()->setMimeType( "text/x-moz-deleted" ); deletePart->contentType()->setName(QString::fromLatin1("Deleted: %1").arg(name),"utf8"); deletePart->contentDisposition()->setDisposition(KMime::Headers::CDattachment); deletePart->contentDisposition()->setFilename(QString::fromLatin1("Deleted: %1").arg(name)); deletePart->contentType()->setCharset( "utf-8" ); deletePart->contentTransferEncoding()->from7BitString( "7bit" ); QByteArray bodyMessage = QByteArray("\nYou deleted an attachment from this message. The original MIME headers for the attachment were:"); bodyMessage +=("\nContent-Type: ") + mimetype; bodyMessage +=("\nname=\"") + name.toUtf8() + "\""; bodyMessage +=("\nfilename=\"") + filename.toUtf8() + "\""; deletePart->setBody(bodyMessage); parent->addContent( deletePart ); parent->assemble(); KMime::Message* modifiedMessage = mNodeHelper->messageWithExtraContent( mMessage.get() ); mMimePartModel->setRoot( modifiedMessage ); mMessageItem.setPayloadFromData( modifiedMessage->encodedContent() ); Akonadi::ItemModifyJob *job = new Akonadi::ItemModifyJob( mMessageItem ); job->disableRevisionCheck(); connect( job, SIGNAL(result(KJob*)), SLOT(itemModifiedResult(KJob*)) ); return true; } void ViewerPrivate::itemModifiedResult( KJob* job ) { if ( job->error() ) { kDebug() << "Item update failed:" << job->errorString(); } else { setMessageItem( mMessageItem, MessageViewer::Viewer::Force ); } } bool ViewerPrivate::editAttachment( KMime::Content * node, bool showWarning ) { //FIXME(Andras) just that I don't forget...handle the case when the user starts editing and switches to another message meantime if ( showWarning && KMessageBox::warningContinueCancel( mMainWindow, i18n("Modifying an attachment might invalidate any digital signature on this message."), i18n("Edit Attachment"), KGuiItem( i18n("Edit"), QLatin1String("document-properties") ), KStandardGuiItem::cancel(), QLatin1String("EditAttachmentSignatureWarning") ) != KMessageBox::Continue ) { return false; } KTemporaryFile file; file.setAutoRemove( false ); if ( !file.open() ) { kWarning() << "Edit Attachment: Unable to open temp file."; return true; } file.write( node->decodedContent() ); file.flush(); EditorWatcher *watcher = new EditorWatcher( KUrl( file.fileName() ), QLatin1String(node->contentType()->mimeType()), false, this, mMainWindow ); mEditorWatchers[ watcher ] = node; connect( watcher, SIGNAL(editDone(EditorWatcher*)), SLOT(slotAttachmentEditDone(EditorWatcher*)) ); if ( !watcher->start() ) { mEditorWatchers.remove( watcher ); QFile::remove( file.fileName() ); } return true; } void ViewerPrivate::createOpenWithMenu( KMenu *topMenu, const QString &contentTypeStr, bool fromCurrentContent ) { const KService::List offers = KFileItemActions::associatedApplications(QStringList()< 1) { // submenu 'open with' menu = new QMenu(i18nc("@title:menu", "&Open With"), topMenu); menu->menuAction()->setObjectName(QLatin1String("openWith_submenu")); // for the unittest topMenu->addMenu(menu); } //kDebug() << offers.count() << "offers" << topMenu << menu; KService::List::ConstIterator it = offers.constBegin(); KService::List::ConstIterator end = offers.constEnd(); for(; it != end; ++it) { KAction* act = MessageViewer::Util::createAppAction(*it, // no submenu -> prefix single offer menu == topMenu, actionGroup, menu); menu->addAction(act); } QString openWithActionName; if (menu != topMenu) { // submenu menu->addSeparator(); openWithActionName = i18nc("@action:inmenu Open With", "&Other..."); } else { openWithActionName = i18nc("@title:menu", "&Open With..."); } KAction *openWithAct = new KAction(menu); openWithAct->setText(openWithActionName); if(fromCurrentContent) connect(openWithAct, SIGNAL(triggered()), this, SLOT(slotOpenWithDialogCurrentContent())); else connect(openWithAct, SIGNAL(triggered()), this, SLOT(slotOpenWithDialog())); menu->addAction(openWithAct); } else { // no app offers -> Open With... KAction *act = new KAction(topMenu); act->setText(i18nc("@title:menu", "&Open With...")); if(fromCurrentContent) connect(act, SIGNAL(triggered()), this, SLOT(slotOpenWithDialogCurrentContent())); else connect(act, SIGNAL(triggered()), this, SLOT(slotOpenWithDialog())); topMenu->addAction(act); } } void ViewerPrivate::slotOpenWithDialogCurrentContent() { if(!mCurrentContent) return; attachmentOpenWith( mCurrentContent ); } void ViewerPrivate::slotOpenWithDialog() { KMime::Content::List contents = selectedContents(); if(contents.count() == 1) { attachmentOpenWith( contents.first() ); } } void ViewerPrivate::slotOpenWithActionCurrentContent(QAction* act) { if(!mCurrentContent) return; KService::Ptr app = act->data().value(); attachmentOpenWith( mCurrentContent,app ); } void ViewerPrivate::slotOpenWithAction(QAction *act) { KService::Ptr app = act->data().value(); KMime::Content::List contents = selectedContents(); if(contents.count() == 1) { attachmentOpenWith( contents.first(),app ); } } void ViewerPrivate::showAttachmentPopup( KMime::Content* node, const QString & name, const QPoint & globalPos ) { prepareHandleAttachment( node, name ); KMenu *menu = new KMenu(); QAction *action; bool deletedAttachment = false; if(node->contentType(false)) { deletedAttachment = (node->contentType()->mimeType() == "text/x-moz-deleted"); } QSignalMapper *attachmentMapper = new QSignalMapper( menu ); connect( attachmentMapper, SIGNAL(mapped(int)), this, SLOT(slotHandleAttachment(int)) ); action = menu->addAction(SmallIcon(QLatin1String("document-open")),i18nc("to open", "Open")); action->setEnabled(!deletedAttachment); connect( action, SIGNAL(triggered(bool)), attachmentMapper, SLOT(map()) ); attachmentMapper->setMapping( action, Viewer::Open ); if(!deletedAttachment) createOpenWithMenu( menu, QLatin1String(node->contentType()->mimeType()),true ); action = menu->addAction(i18nc("to view something", "View") ); action->setEnabled(!deletedAttachment); connect( action, SIGNAL(triggered(bool)), attachmentMapper, SLOT(map()) ); attachmentMapper->setMapping( action, Viewer::View ); const bool attachmentInHeader = mViewer->isAttachmentInjectionPoint( globalPos ); const bool hasScrollbar = mViewer->hasVerticalScrollBar(); if ( attachmentInHeader && hasScrollbar ) { action = menu->addAction( i18n( "Scroll To" ) ); connect( action, SIGNAL(triggered(bool)), attachmentMapper, SLOT(map()) ); attachmentMapper->setMapping( action, Viewer::ScrollTo ); } action = menu->addAction(SmallIcon(QLatin1String("document-save-as")),i18n("Save As...") ); action->setEnabled(!deletedAttachment); connect( action, SIGNAL(triggered(bool)), attachmentMapper, SLOT(map()) ); attachmentMapper->setMapping( action, Viewer::Save ); action = menu->addAction(SmallIcon(QLatin1String("edit-copy")), i18n("Copy") ); action->setEnabled(!deletedAttachment); connect( action, SIGNAL(triggered(bool)), attachmentMapper, SLOT(map()) ); attachmentMapper->setMapping( action, Viewer::Copy ); const bool isEncapsulatedMessage = node->parent() && node->parent()->bodyIsMessage(); const bool canChange = mMessageItem.isValid() && mMessageItem.parentCollection().isValid() && ( mMessageItem.parentCollection().rights() != Akonadi::Collection::ReadOnly ) && !isEncapsulatedMessage; if ( GlobalSettings::self()->allowAttachmentEditing() ) { action = menu->addAction(SmallIcon(QLatin1String("document-properties")), i18n("Edit Attachment") ); connect( action, SIGNAL(triggered()), attachmentMapper, SLOT(map()) ); attachmentMapper->setMapping( action, Viewer::Edit ); action->setEnabled( canChange ); } if ( GlobalSettings::self()->allowAttachmentDeletion() ) { action = menu->addAction(SmallIcon(QLatin1String("edit-delete")), i18n("Delete Attachment") ); connect( action, SIGNAL(triggered()), attachmentMapper, SLOT(map()) ); attachmentMapper->setMapping( action, Viewer::Delete ); action->setEnabled( canChange && !deletedAttachment ); } if ( name.endsWith( QLatin1String(".xia"), Qt::CaseInsensitive ) && Kleo::CryptoBackendFactory::instance()->protocol( "Chiasmus" )) { action = menu->addAction( i18n( "Decrypt With Chiasmus..." ) ); connect( action, SIGNAL(triggered(bool)), attachmentMapper, SLOT(map()) ); attachmentMapper->setMapping( action, Viewer::ChiasmusEncrypt ); } action = menu->addAction(i18n("Properties") ); connect( action, SIGNAL(triggered(bool)), attachmentMapper, SLOT(map()) ); attachmentMapper->setMapping( action, Viewer::Properties ); menu->exec( globalPos ); delete menu; } void ViewerPrivate::prepareHandleAttachment( KMime::Content *node, const QString& fileName ) { mCurrentContent = node; mCurrentFileName = fileName; } QString ViewerPrivate::createAtmFileLink( const QString& atmFileName ) const { QFileInfo atmFileInfo( atmFileName ); // tempfile name is /TMP/attachmentsRANDOM/atmFileInfo.fileName()" KTempDir *linkDir = new KTempDir( KStandardDirs::locateLocal( "tmp", QLatin1String("attachments") ) ); QString linkPath = linkDir->name() + atmFileInfo.fileName(); QFile *linkFile = new QFile( linkPath ); linkFile->open( QIODevice::ReadWrite ); const QString linkName = linkFile->fileName(); delete linkFile; delete linkDir; if ( ::link(QFile::encodeName( atmFileName ), QFile::encodeName( linkName )) == 0 ) { return linkName; // success } return QString(); } KService::Ptr ViewerPrivate::getServiceOffer( KMime::Content *content) { const QString fileName = mNodeHelper->writeNodeToTempFile( content ); const QString contentTypeStr = QLatin1String(content->contentType()->mimeType()); // determine the MIME type of the attachment KMimeType::Ptr mimetype; // prefer the value of the Content-Type header mimetype = KMimeType::mimeType( contentTypeStr, KMimeType::ResolveAliases ); if ( !mimetype.isNull() && mimetype->is( KABC::Addressee::mimeType() ) ) { attachmentView( content ); return KService::Ptr( 0 ); } if ( mimetype.isNull() || mimetype->name() == QLatin1String("application/octet-stream") ) { /*TODO(Andris) port when on-demand loading is done && msgPart.isComplete() */ mimetype = MessageViewer::Util::mimetype(fileName); } return KMimeTypeTrader::self()->preferredService( mimetype->name(), QLatin1String("Application") ); } KMime::Content::List ViewerPrivate::selectedContents() { KMime::Content::List contents; #ifndef QT_NO_TREEVIEW QItemSelectionModel *selectionModel = mMimePartTree->selectionModel(); QModelIndexList selectedRows = selectionModel->selectedRows(); Q_FOREACH( const QModelIndex &index, selectedRows ) { KMime::Content *content = static_cast( index.internalPointer() ); if ( content ) contents.append( content ); } #endif return contents; } void ViewerPrivate::attachmentOpenWith( KMime::Content *node, KService::Ptr offer ) { QString name = mNodeHelper->writeNodeToTempFile( node ); QString linkName = createAtmFileLink( name ); KUrl::List lst; KUrl url; bool autoDelete = true; if ( linkName.isEmpty() ) { autoDelete = false; linkName = name; } KPIMUtils::checkAndCorrectPermissionsIfPossible( linkName, false, true, true ); url.setPath( linkName ); lst.append( url ); if(offer) { if ( (!KRun::run( *offer, lst, 0, autoDelete )) && autoDelete ) { QFile::remove(url.toLocalFile()); } } else { if ( (! KRun::displayOpenWithDialog(lst, mMainWindow, autoDelete)) && autoDelete ) { QFile::remove( url.toLocalFile() ); } } } void ViewerPrivate::attachmentOpen( KMime::Content *node ) { KService::Ptr offer = getServiceOffer( node ); if ( !offer ) { kDebug() << "got no offer"; return; } attachmentOpenWith( node, offer ); } CSSHelper* ViewerPrivate::cssHelper() const { return mCSSHelper; } bool ViewerPrivate::decryptMessage() const { if ( !GlobalSettings::self()->alwaysDecrypt() ) return mDecrytMessageOverwrite; else return true; } int ViewerPrivate::pointsToPixel(int pointSize) const { return (pointSize * mViewer->logicalDpiY() + 36) / 72; } void ViewerPrivate::displaySplashPage( const QString &info ) { mMsgDisplay = false; adjustLayout(); #ifdef KDEPIM_MOBILE_UI const QString location = KStandardDirs::locate( "data", QLatin1String("messageviewer/about/main_mobile.html") ); QString content = QLatin1String( KPIMUtils::kFileToByteArray( location ) ); content = content.arg( QLatin1String( "" ) ); // infopage stylesheet content = content.arg( QLatin1String( "" ) ); // rtl infopage stylesheet #else const QString location = KStandardDirs::locate( "data", QLatin1String("kmail2/about/main.html") ); //FIXME(Andras) copy to $KDEDIR/share/apps/messageviewer QString content = QLatin1String(KPIMUtils::kFileToByteArray( location )); content = content.arg( KStandardDirs::locate( "data", QLatin1String("kdeui/about/kde_infopage.css") ) ); if ( QApplication::isRightToLeft() ) content = content.arg( QLatin1String("@import \"") + KStandardDirs::locate( "data", QLatin1String("kdeui/about/kde_infopage_rtl.css") ) + QLatin1String("\";")); else content = content.arg( QString() ); #endif const QString fontSize = QString::number( pointsToPixel( mCSSHelper->bodyFont().pointSize() ) ); const QString catchPhrase; //not enough space for a catch phrase at default window size i18n("Part of the Kontact Suite"); const QString quickDescription = i18n( "The KDE email client." ); mViewer->setHtml( content.arg( fontSize ).arg( mAppName ).arg( catchPhrase ).arg( quickDescription ).arg( info ), KUrl::fromPath( location ) ); mViewer->show(); } void ViewerPrivate::enableMessageDisplay() { mMsgDisplay = true; adjustLayout(); } void ViewerPrivate::displayMessage() { showHideMimeTree(); mNodeHelper->setOverrideCodec( mMessage.get(), overrideCodec() ); if ( mMessageItem.hasAttribute() ) { const MessageViewer::MessageDisplayFormatAttribute* const attr = mMessageItem.attribute(); setHtmlLoadExtOverride(attr->remoteContent()); switch(attr->messageFormat()) { case Viewer::Html: setHtmlOverride(true); break; case Viewer::Text: setHtmlOverride(false); break; default: break; } } htmlWriter()->begin( QString() ); htmlWriter()->queue( mCSSHelper->htmlHead( mUseFixedFont ) ); if ( !mMainWindow ) q->setWindowTitle( mMessage->subject()->asUnicodeString() ); // Don't update here, parseMsg() can overwrite the HTML mode, which would lead to flicker. // It is updated right after parseMsg() instead. mColorBar->setMode( Util::Normal, HtmlStatusBar::NoUpdate ); if ( mMessageItem.hasAttribute() ) { //TODO: Insert link to clear error so that message might be resent const ErrorAttribute* const attr = mMessageItem.attribute(); Q_ASSERT( attr ); const QColor foreground = KColorScheme( QPalette::Active, KColorScheme::View ).foreground( KColorScheme::NegativeText ).color(); const QColor background = KColorScheme( QPalette::Active, KColorScheme::View ).background( KColorScheme::NegativeBackground ).color(); htmlWriter()->queue( QString::fromLatin1("
%4
").arg( background.name(), foreground.name(), foreground.name(), Qt::escape( attr->message() ) ) ); htmlWriter()->queue( QLatin1String("

") ); } parseContent( mMessage.get() ); delete mMimePartModel->root(); mMimePartModel->setRoot( mNodeHelper->messageWithExtraContent( mMessage.get() ) ); mColorBar->update(); htmlWriter()->queue(QLatin1String("")); connect( mPartHtmlWriter, SIGNAL(finished()), this, SLOT(injectAttachments()), Qt::UniqueConnection ); connect( mPartHtmlWriter, SIGNAL(finished()), this, SLOT(toggleFullAddressList()), Qt::UniqueConnection ); connect( mPartHtmlWriter, SIGNAL(finished()), this, SLOT(slotMessageRendered()), Qt::UniqueConnection ); htmlWriter()->flush(); } void ViewerPrivate::collectionFetchedForStoringDecryptedMessage( KJob* job ) { if ( job->error() ) return; Akonadi::Collection col; Q_FOREACH( const Akonadi::Collection &c, static_cast( job )->collections() ) { if ( c == mMessageItem.parentCollection() ) { col = c; break; } } if ( !col.isValid() ) return; Akonadi::AgentInstance::List instances = Akonadi::AgentManager::self()->instances(); const QString itemResource = col.resource(); Akonadi::AgentInstance resourceInstance; foreach ( const Akonadi::AgentInstance &instance, instances ) { if ( instance.identifier() == itemResource ) { resourceInstance = instance; break; } } bool isInOutbox = true; Akonadi::Collection outboxCollection = Akonadi::SpecialMailCollections::self()->collection( Akonadi::SpecialMailCollections::Outbox, resourceInstance ); if ( resourceInstance.isValid() && outboxCollection != col ) { isInOutbox = false; } if ( !isInOutbox ) { KMime::Message::Ptr unencryptedMessage = mNodeHelper->unencryptedMessage( mMessage ); if ( unencryptedMessage ) { mMessageItem.setPayload( unencryptedMessage ); Akonadi::ItemModifyJob *job = new Akonadi::ItemModifyJob( mMessageItem ); connect( job, SIGNAL(result(KJob*)), SLOT(itemModifiedResult(KJob*)) ); } } } void ViewerPrivate::postProcessMessage( ObjectTreeParser *otp, KMMsgEncryptionState encryptionState ) { if ( GlobalSettings::self()->storeDisplayedMessagesUnencrypted() ) { // Hack to make sure the S/MIME CryptPlugs follows the strict requirement // of german government: // --> All received encrypted messages *must* be stored in unencrypted form // after they have been decrypted once the user has read them. // ( "Aufhebung der Verschluesselung nach dem Lesen" ) // // note: Since there is no configuration option for this, we do that for // all kinds of encryption now - *not* just for S/MIME. // This could be changed in the objectTreeToDecryptedMsg() function // by deciding when (or when not, resp.) to set the 'dataNode' to // something different than 'curNode'. const bool messageAtLeastPartiallyEncrypted = ( KMMsgFullyEncrypted == encryptionState ) || ( KMMsgPartiallyEncrypted == encryptionState ); // only proceed if we were called the normal way - not by // double click on the message (==not running in a separate window) if( decryptMessage() && // only proceed if the message has actually been decrypted !otp->hasPendingAsyncJobs() && // only proceed if no pending async jobs are running: messageAtLeastPartiallyEncrypted ) { //check if the message is in the outbox folder //FIXME: using root() is too much, but using mMessageItem.parentCollection() returns no collections in job->collections() //FIXME: this is done async, which means it is possible that the user selects another message while // this job is running. In that case, collectionFetchedForStoringDecryptedMessage() will work // on the wrong item! Akonadi::CollectionFetchJob* job = new Akonadi::CollectionFetchJob( Akonadi::Collection::root(), Akonadi::CollectionFetchJob::Recursive ); connect( job, SIGNAL(result(KJob*)), this, SLOT(collectionFetchedForStoringDecryptedMessage(KJob*)) ); } } } void ViewerPrivate::parseContent( KMime::Content *content ) { assert( content != 0 ); // Check if any part of this message is a v-card // v-cards can be either text/x-vcard or text/directory, so we need to check // both. KMime::Content* vCardContent = findContentByType( content, "text/x-vcard" ); if ( !vCardContent ) vCardContent = findContentByType( content, "text/directory" ); bool hasVCard = false; if( vCardContent ) { // ### FIXME: We should only do this if the vCard belongs to the sender, // ### i.e. if the sender's email address is contained in the vCard. const QByteArray vCard = vCardContent->decodedContent(); KABC::VCardConverter t; if ( !t.parseVCards( vCard ).isEmpty() ) { hasVCard = true; mNodeHelper->writeNodeToTempFile( vCardContent ); } } if ( !NodeHelper::isToltecMessage( content ) || mShowRawToltecMail ) { KMime::Message *message = dynamic_cast( content ); if ( message ) { htmlWriter()->queue( writeMsgHeader( message, hasVCard ? vCardContent : 0, true ) ); } } // Pass control to the OTP now, which does the real work mNodeHelper->removeTempFiles(); mNodeHelper->setNodeUnprocessed( mMessage.get(), true ); MailViewerSource otpSource( this ); ObjectTreeParser otp( &otpSource, mNodeHelper, 0, mMessage.get() != content /* show only single node */ ); otp.setAllowAsync( true ); otp.setShowRawToltecMail( mShowRawToltecMail ); otp.parseObjectTree( content ); // TODO: Setting the signature state to nodehelper is not enough, it should actually // be added to the store, so that the message list correctly displays the signature state // of messages that were parsed at least once // store encrypted/signed status information in the KMMessage // - this can only be done *after* calling parseObjectTree() KMMsgEncryptionState encryptionState = mNodeHelper->overallEncryptionState( content ); KMMsgSignatureState signatureState = mNodeHelper->overallSignatureState( content ); mNodeHelper->setEncryptionState( content, encryptionState ); // Don't reset the signature state to "not signed" (e.g. if one canceled the // decryption of a signed messages which has already been decrypted before). if ( signatureState != KMMsgNotSigned || mNodeHelper->signatureState( content ) == KMMsgSignatureStateUnknown ) { mNodeHelper->setSignatureState( content, signatureState ); } postProcessMessage( &otp, encryptionState ); showHideMimeTree(); } QString ViewerPrivate::writeMsgHeader( KMime::Message *aMsg, KMime::Content* vCardNode, bool topLevel ) { if ( !headerStyle() ) kFatal() << "trying to writeMsgHeader() without a header style set!"; if ( !headerStrategy() ) kFatal() << "trying to writeMsgHeader() without a header strategy set!"; QString href; if ( vCardNode ) href = mNodeHelper->asHREF( vCardNode, QLatin1String("body") ); headerStyle()->setHeaderStrategy( headerStrategy() ); headerStyle()->setVCardName( href ); headerStyle()->setPrinting( mPrinting ); headerStyle()->setTopLevel( topLevel ); headerStyle()->setAllowAsync( true ); headerStyle()->setSourceObject( this ); headerStyle()->setNodeHelper( mNodeHelper ); headerStyle()->setMessagePath( mMessagePath ); if ( mMessageItem.isValid() ) { Akonadi::MessageStatus status; status.setStatusFromFlags( mMessageItem.flags() ); headerStyle()->setMessageStatus( status ); } return headerStyle()->format( aMsg ); } void ViewerPrivate::showVCard( KMime::Content* msgPart ) { const QByteArray vCard = msgPart->decodedContent(); VCardViewer *vcv = new VCardViewer( mMainWindow, vCard ); vcv->setAttribute( Qt::WA_DeleteOnClose ); vcv->show(); } void ViewerPrivate::initHtmlWidget() { mViewer->setFocusPolicy( Qt::WheelFocus ); #if 0 // (marc) I guess this is not needed? All events go through the // Viewer, since the Page is just a QObject, and we're only // interested in mouse events... mViewer->page()->view()->installEventFilter( this ); #endif mViewer->installEventFilter( this ); if ( !htmlWriter() ) { mPartHtmlWriter = new WebKitPartHtmlWriter( mViewer, 0 ); #ifdef MESSAGEVIEWER_READER_HTML_DEBUG mHtmlWriter = new TeeHtmlWriter( new FileHtmlWriter( QString() ), mPartHtmlWriter ); #else mHtmlWriter = mPartHtmlWriter; #endif } connect( mViewer, SIGNAL(linkHovered(QString,QString,QString)), this, SLOT(slotUrlOn(QString,QString,QString)) ); connect( mViewer, SIGNAL(linkClicked(QUrl)), this, SLOT(slotUrlOpen(QUrl)), Qt::QueuedConnection ); connect( mViewer, SIGNAL(popupMenu(QUrl,QUrl,QPoint)), SLOT(slotUrlPopup(QUrl,QUrl,QPoint)) ); connect( mViewer, SIGNAL(messageMayBeAScam()), this, SLOT(slotMessageMayBeAScam())); connect( mScamDetectionWarning, SIGNAL(showDetails()), mViewer, SLOT(slotShowDetails())); connect( mScamDetectionWarning, SIGNAL(moveMessageToTrash()), this, SIGNAL(moveMessageToTrash())); connect( mScamDetectionWarning, SIGNAL(messageIsNotAScam()), this, SLOT(slotMessageIsNotAScam())); connect( mScamDetectionWarning, SIGNAL(addToWhiteList()), this, SLOT(slotAddToWhiteList())); } bool ViewerPrivate::eventFilter( QObject *, QEvent *e ) { if ( e->type() == QEvent::MouseButtonPress ) { QMouseEvent* me = static_cast(e); if ( me->button() == Qt::LeftButton && ( me->modifiers() & Qt::ShiftModifier ) ) { // special processing for shift+click URLHandlerManager::instance()->handleShiftClick( mHoveredUrl, this ); return true; } if ( me->button() == Qt::LeftButton ) { mCanStartDrag = URLHandlerManager::instance()->willHandleDrag( mHoveredUrl, this ); mLastClickPosition = me->pos(); } } else if ( e->type() == QEvent::MouseButtonRelease ) { mCanStartDrag = false; } else if ( e->type() == QEvent::MouseMove ) { QMouseEvent* me = static_cast( e ); // First, update the hovered URL mHoveredUrl = mViewer->linkOrImageUrlAt( me->globalPos() ); // If we are potentially handling a drag, deal with that. if ( mCanStartDrag && me->buttons() & Qt::LeftButton ) { if ( ( mLastClickPosition - me->pos() ).manhattanLength() > KGlobalSettings::dndEventDelay() ) { if ( URLHandlerManager::instance()->handleDrag( mHoveredUrl, this ) ) { // If the URL handler manager started a drag, don't handle this in the future mCanStartDrag = false; } } // Don't tell WebKit about this mouse move event, or it might start its own drag! return true; } } //Don't tell to Webkit to get zoom > 300 and < 100 else if ( e->type() == QEvent::Wheel ) { QWheelEvent* me = static_cast( e ); if ( QApplication::keyboardModifiers() & Qt::ControlModifier ) { const int numDegrees = me->delta() / 8; const int numSteps = numDegrees / 15; const qreal factor = mZoomFactor + numSteps * 10; if ( factor >= 10 && factor <= 300 ) { mZoomFactor = factor; setZoomFactor( factor/100.0 ); } return true; } } // standard event processing return false; } void ViewerPrivate::readConfig() { delete mCSSHelper; mCSSHelper = new CSSHelper( mViewer ); mUseFixedFont = GlobalSettings::self()->useFixedFont(); if ( mToggleFixFontAction ) mToggleFixFontAction->setChecked( mUseFixedFont ); mHtmlMail = GlobalSettings::self()->htmlMail(); mHtmlLoadExternal = GlobalSettings::self()->htmlLoadExternal(); mZoomTextOnly = GlobalSettings::self()->zoomTextOnly(); setZoomTextOnly( mZoomTextOnly ); if (headerStrategy() ) headerStrategy()->readConfig(); KToggleAction *raction = actionForHeaderStyle( headerStyle(), headerStrategy() ); if ( raction ) raction->setChecked( true ); setAttachmentStrategy( AttachmentStrategy::create( GlobalSettings::self()->attachmentStrategy() ) ); raction = actionForAttachmentStrategy( attachmentStrategy() ); if ( raction ) raction->setChecked( true ); adjustLayout(); readGlobalOverrideCodec(); // Note that this call triggers an update, see this call has to be at the // bottom when all settings are already est. setHeaderStyleAndStrategy( HeaderStyle::create( GlobalSettings::self()->headerStyle() ), HeaderStrategy::create( GlobalSettings::self()->headerSetDisplayed() ) ); initGrantleeThemeName(); #ifndef KDEPIM_NO_WEBKIT mViewer->settings()->setFontSize( QWebSettings::MinimumFontSize, GlobalSettings::self()->minimumFontSize() ); mViewer->settings()->setFontSize( QWebSettings::MinimumLogicalFontSize, GlobalSettings::self()->minimumFontSize() ); mViewer->settings()->setAttribute( QWebSettings::PrintElementBackgrounds, GlobalSettings::self()->printBackgroundColorImages() ); #endif if ( mMessage ) update(); mColorBar->update(); } void ViewerPrivate::writeConfig( bool sync ) { GlobalSettings::self()->setUseFixedFont( mUseFixedFont ); if ( headerStyle() ) { GlobalSettings::self()->setHeaderStyle( QLatin1String(headerStyle()->name()) ); GrantleeTheme::GrantleeSettings::self()->setGrantleeThemeName( headerStyle()->theme().dirName() ); } if ( headerStrategy() ) GlobalSettings::self()->setHeaderSetDisplayed( QLatin1String(headerStrategy()->name()) ); if ( attachmentStrategy() ) GlobalSettings::self()->setAttachmentStrategy( QLatin1String(attachmentStrategy()->name()) ); GlobalSettings::self()->setZoomTextOnly( mZoomTextOnly ); saveSplitterSizes(); if ( sync ) emit requestConfigSync(); } void ViewerPrivate::setHeaderStyleAndStrategy( HeaderStyle * style, HeaderStrategy * strategy , bool writeInConfigFile ) { if ( mHeaderStyle == style && mHeaderStrategy == strategy ) return; mHeaderStyle = style ? style : HeaderStyle::fancy(); mHeaderStrategy = strategy ? strategy : HeaderStrategy::rich(); if ( mHeaderOnlyAttachmentsAction ) { mHeaderOnlyAttachmentsAction->setEnabled( mHeaderStyle->hasAttachmentQuickList() ); if ( !mHeaderStyle->hasAttachmentQuickList() && mAttachmentStrategy->requiresAttachmentListInHeader() ) { // Style changed to something without an attachment quick list, need to change attachment // strategy setAttachmentStrategy( AttachmentStrategy::smart() ); actionForAttachmentStrategy( mAttachmentStrategy )->setChecked( true ); } } update( Viewer::Force ); if( !mExternalWindow && writeInConfigFile) writeConfig(); } void ViewerPrivate::setAttachmentStrategy( const AttachmentStrategy * strategy ) { if ( mAttachmentStrategy == strategy ) return; mAttachmentStrategy = strategy ? strategy : AttachmentStrategy::smart(); update( Viewer::Force ); } void ViewerPrivate::setOverrideEncoding( const QString & encoding ) { if ( encoding == mOverrideEncoding ) return; mOverrideEncoding = encoding; if ( mSelectEncodingAction ) { if ( encoding.isEmpty() ) { mSelectEncodingAction->setCurrentItem( 0 ); } else { const QStringList encodings = mSelectEncodingAction->items(); int i = 0; for ( QStringList::const_iterator it = encodings.constBegin(), end = encodings.constEnd(); it != end; ++it, ++i ) { if ( NodeHelper::encodingForName( *it ) == encoding ) { mSelectEncodingAction->setCurrentItem( i ); break; } } if ( i == encodings.size() ) { // the value of encoding is unknown => use Auto kWarning() << "Unknown override character encoding" << encoding << ". Using Auto instead."; mSelectEncodingAction->setCurrentItem( 0 ); mOverrideEncoding.clear(); } } } update( Viewer::Force ); } void ViewerPrivate::printMessage( const Akonadi::Item &message ) { disconnect( mPartHtmlWriter, SIGNAL(finished()), this, SLOT(slotPrintMsg()) ); connect( mPartHtmlWriter, SIGNAL(finished()), this, SLOT(slotPrintMsg()) ); setMessageItem( message, Viewer::Force ); } void ViewerPrivate::printPreviewMessage( const Akonadi::Item &message ) { disconnect( mPartHtmlWriter, SIGNAL(finished()), this, SLOT(slotPrintPreview()) ); connect( mPartHtmlWriter, SIGNAL(finished()), this, SLOT(slotPrintPreview()) ); setMessageItem( message, Viewer::Force ); } void ViewerPrivate::resetStateForNewMessage() { mClickedUrl.clear(); mImageUrl.clear(); enableMessageDisplay(); // just to make sure it's on mMessage.reset(); mNodeHelper->clear(); mMessagePartNode = 0; delete mMimePartModel->root(); mMimePartModel->setRoot( 0 ); mSavedRelativePosition = 0; setShowSignatureDetails( false ); mShowRawToltecMail = !GlobalSettings::self()->showToltecReplacementText(); mFindBar->closeBar(); mTranslatorWidget->slotCloseWidget(); mCreateTodo->slotCloseWidget(); mCreateEvent->slotCloseWidget(); mScamDetectionWarning->setVisible(false); if ( mPrinting ) { if (MessageViewer::GlobalSettings::self()->respectExpandCollapseSettings()) { if (MessageViewer::GlobalSettings::self()->showExpandQuotesMark()) { mLevelQuote = MessageViewer::GlobalSettings::self()->collapseQuoteLevelSpin() -1; } else { mLevelQuote = -1; } } else { mLevelQuote = -1; } } } void ViewerPrivate::setMessageInternal( const KMime::Message::Ptr message, Viewer::UpdateMode updateMode ) { if ( mCreateNoteAction ) { QString createNoteText; - if ( mMessageItem.relations().isEmpty() ) { + if ( !relatedNoteRelation().isValid() ) { createNoteText = i18nc( "create a new note out of this message", "Create Note" ); } else { createNoteText = i18nc( "edit a note on this message", "Edit Note" ); } mCreateNoteAction->setText( createNoteText ); mCreateNoteAction->setIconText( createNoteText ); } mMessage = message; if ( message ) { mNodeHelper->setOverrideCodec( mMessage.get(), overrideCodec() ); } delete mMimePartModel->root(); mMimePartModel->setRoot( mNodeHelper->messageWithExtraContent( message.get() ) ); update( updateMode ); } void ViewerPrivate::setMessageItem( const Akonadi::Item &item, Viewer::UpdateMode updateMode ) { resetStateForNewMessage(); foreach( const Akonadi::Entity::Id monitoredId, mMonitor.itemsMonitoredEx() ) { mMonitor.setItemMonitored( Akonadi::Item( monitoredId ), false ); } Q_ASSERT( mMonitor.itemsMonitoredEx().isEmpty() ); mMessageItem = item; if ( mMessageItem.isValid() ) mMonitor.setItemMonitored( mMessageItem, true ); if ( !mMessageItem.hasPayload() ) { if ( mMessageItem.isValid() ) kWarning() << "Payload is not a MessagePtr!"; return; } setMessageInternal( mMessageItem.payload(), updateMode ); } void ViewerPrivate::setMessage( const KMime::Message::Ptr& aMsg, Viewer::UpdateMode updateMode ) { resetStateForNewMessage(); Akonadi::Item item; item.setMimeType( KMime::Message::mimeType() ); item.setPayload( aMsg ); mMessageItem = item; setMessageInternal( aMsg, updateMode ); } void ViewerPrivate::setMessagePart( KMime::Content * node ) { // Cancel scheduled updates of the reader window, as that would stop the // timer of the HTML writer, which would make viewing attachment not work // anymore as not all HTML is written to the HTML part. // We're updating the reader window here ourselves anyway. mUpdateReaderWinTimer.stop(); if ( node ) { mMessagePartNode = node; if ( node->bodyIsMessage() ) { mMainWindow->setWindowTitle( node->bodyAsMessage()->subject()->asUnicodeString() ); } else { QString windowTitle = NodeHelper::fileName( node ); if ( windowTitle.isEmpty() ) { windowTitle = node->contentDescription()->asUnicodeString(); } if ( !windowTitle.isEmpty() ) { mMainWindow->setWindowTitle( i18n( "View Attachment: %1", windowTitle ) ); } } htmlWriter()->begin( QString() ); htmlWriter()->queue( mCSSHelper->htmlHead( mUseFixedFont ) ); parseContent( node ); htmlWriter()->queue(QLatin1String("")); htmlWriter()->flush(); } } void ViewerPrivate::showHideMimeTree( ) { #ifndef QT_NO_TREEVIEW bool showMimeTree = false; if ( GlobalSettings::self()->mimeTreeMode() == GlobalSettings::EnumMimeTreeMode::Always ) { mMimePartTree->show(); showMimeTree = true; } else { // don't rely on QSplitter maintaining sizes for hidden widgets: saveSplitterSizes(); mMimePartTree->hide(); showMimeTree = false; } if ( mToggleMimePartTreeAction && ( mToggleMimePartTreeAction->isChecked() != showMimeTree ) ) mToggleMimePartTreeAction->setChecked( showMimeTree ); #endif } void ViewerPrivate::atmViewMsg( KMime::Message::Ptr message ) { Q_ASSERT( message ); emit showMessage( message, overrideEncoding() ); } void ViewerPrivate::adjustLayout() { #ifndef QT_NO_TREEVIEW const int mimeH = GlobalSettings::self()->mimePaneHeight(); const int messageH = GlobalSettings::self()->messagePaneHeight(); QList splitterSizes; if ( GlobalSettings::self()->mimeTreeLocation() == GlobalSettings::EnumMimeTreeLocation::bottom ) splitterSizes << messageH << mimeH; else splitterSizes << mimeH << messageH; if ( GlobalSettings::self()->mimeTreeLocation() == GlobalSettings::EnumMimeTreeLocation::bottom ) mSplitter->addWidget( mMimePartTree ); else mSplitter->insertWidget( 0, mMimePartTree ); mSplitter->setSizes( splitterSizes ); if ( GlobalSettings::self()->mimeTreeMode() == GlobalSettings::EnumMimeTreeMode::Always && mMsgDisplay ) mMimePartTree->show(); else mMimePartTree->hide(); #endif if ( GlobalSettings::self()->showColorBar() && mMsgDisplay ) mColorBar->show(); else mColorBar->hide(); } void ViewerPrivate::saveSplitterSizes() const { #ifndef QT_NO_TREEVIEW if ( !mSplitter || !mMimePartTree ) return; if ( mMimePartTree->isHidden() ) return; // don't rely on QSplitter maintaining sizes for hidden widgets. const bool mimeTreeAtBottom = GlobalSettings::self()->mimeTreeLocation() == GlobalSettings::EnumMimeTreeLocation::bottom; GlobalSettings::self()->setMimePaneHeight( mSplitter->sizes()[ mimeTreeAtBottom ? 1 : 0 ] ); GlobalSettings::self()->setMessagePaneHeight( mSplitter->sizes()[ mimeTreeAtBottom ? 0 : 1 ] ); #endif } void ViewerPrivate::createWidgets() { //TODO: Make a MDN bar similar to Mozillas password bar and show MDNs here as soon as a // MDN enabled message is shown. QVBoxLayout * vlay = new QVBoxLayout( q ); vlay->setMargin( 0 ); mSplitter = new QSplitter( Qt::Vertical, q ); connect(mSplitter, SIGNAL(splitterMoved(int,int)), this, SLOT(saveSplitterSizes())); mSplitter->setObjectName( QLatin1String("mSplitter") ); mSplitter->setChildrenCollapsible( false ); vlay->addWidget( mSplitter ); #ifndef QT_NO_TREEVIEW mMimePartTree = new QTreeView( mSplitter ); mMimePartTree->setObjectName( QLatin1String("mMimePartTree") ); mMimePartModel = new MimeTreeModel( mMimePartTree ); mMimePartTree->setModel( mMimePartModel ); mMimePartTree->setSelectionMode( QAbstractItemView::ExtendedSelection ); mMimePartTree->setSelectionBehavior( QAbstractItemView::SelectRows ); connect(mMimePartTree, SIGNAL(activated(QModelIndex)), this, SLOT(slotMimePartSelected(QModelIndex)) ); connect(mMimePartTree, SIGNAL(destroyed(QObject*)), this, SLOT(slotMimePartDestroyed()) ); mMimePartTree->setContextMenuPolicy(Qt::CustomContextMenu); connect(mMimePartTree, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(slotMimeTreeContextMenuRequested(QPoint)) ); mMimePartTree->header()->setResizeMode( QHeaderView::ResizeToContents ); connect(mMimePartModel,SIGNAL(modelReset()),mMimePartTree,SLOT(expandAll())); restoreMimePartTreeConfig(); #endif mBox = new KHBox( mSplitter ); #ifndef KDEPIM_MOBILE_UI mColorBar = new HtmlStatusBar( mBox ); KVBox *readerBox = new KVBox( mBox ); #else // for mobile ui we position the HTML status bar on the right side KVBox *readerBox = new KVBox( mBox ); mColorBar = new HtmlStatusBar( mBox ); #endif mColorBar->setObjectName( QLatin1String("mColorBar") ); mColorBar->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Expanding ); mScamDetectionWarning = new ScamDetectionWarningWidget(readerBox); mViewer = new MailWebView( mActionCollection, readerBox ); mViewer->setObjectName( QLatin1String("mViewer") ); mCreateTodo = new MessageViewer::TodoEdit(readerBox); connect(mCreateTodo, SIGNAL(createTodo(KCalCore::Todo::Ptr,Akonadi::Collection)), this, SLOT(slotCreateTodo(KCalCore::Todo::Ptr,Akonadi::Collection))); mCreateTodo->setObjectName(QLatin1String("createtodowidget")); mCreateTodo->hide(); mCreateEvent = new MessageViewer::EventEdit(readerBox); connect(mCreateEvent, SIGNAL(createEvent(KCalCore::Event::Ptr,Akonadi::Collection)), this, SLOT(slotCreateEvent(KCalCore::Event::Ptr,Akonadi::Collection))); mCreateEvent->setObjectName(QLatin1String("createeventwidget")); mCreateEvent->hide(); mCreateNote = new MessageViewer::NoteEdit(readerBox); connect(mCreateNote, SIGNAL(createNote(KMime::Message::Ptr,Akonadi::Collection)), this, SLOT(slotCreateNote(KMime::Message::Ptr,Akonadi::Collection))); mCreateNote->setObjectName(QLatin1String("createnotewidget")); mCreateNote->hide(); mFindBar = new FindBarMailWebView( mViewer, readerBox ); mTranslatorWidget = new PimCommon::TranslatorWidget(readerBox); #ifndef QT_NO_TREEVIEW mSplitter->setStretchFactor( mSplitter->indexOf(mMimePartTree), 0 ); #endif mSplitter->setOpaqueResize( KGlobalSettings::opaqueResize() ); } void ViewerPrivate::slotMimePartDestroyed() { #ifndef QT_NO_TREEVIEW //root is either null or a modified tree that we need to clean up delete mMimePartModel->root(); mMimePartModel->setRoot(0); #endif } void ViewerPrivate::createActions() { KActionCollection *ac = mActionCollection; if ( !ac ) { return; } KToggleAction *raction = 0; // header style KActionMenu *headerMenu = new KActionMenu(i18nc("View->", "&Headers"), this); ac->addAction(QLatin1String("view_headers"), headerMenu ); headerMenu->setHelpText( i18n("Choose display style of message headers") ); QActionGroup *group = new QActionGroup( this ); raction = new KToggleAction( i18nc("View->headers->", "&Enterprise Headers"), this); ac->addAction( QLatin1String("view_headers_enterprise"), raction ); connect( raction, SIGNAL(triggered(bool)), SLOT(slotEnterpriseHeaders()) ); raction->setHelpText( i18n("Show the list of headers in Enterprise style") ); group->addAction( raction ); headerMenu->addAction( raction ); raction = new KToggleAction(i18nc("View->headers->", "&Fancy Headers"), this); ac->addAction(QLatin1String("view_headers_fancy"), raction ); connect(raction, SIGNAL(triggered(bool)), SLOT(slotFancyHeaders())); raction->setHelpText( i18n("Show the list of headers in a fancy format") ); group->addAction( raction ); headerMenu->addAction( raction ); raction = new KToggleAction(i18nc("View->headers->", "&Brief Headers"), this); ac->addAction(QLatin1String("view_headers_brief"), raction ); connect(raction, SIGNAL(triggered(bool)), SLOT(slotBriefHeaders())); raction->setHelpText( i18n("Show brief list of message headers") ); group->addAction( raction ); headerMenu->addAction( raction ); raction = new KToggleAction(i18nc("View->headers->", "&Standard Headers"), this); ac->addAction(QLatin1String("view_headers_standard"), raction ); connect(raction, SIGNAL(triggered(bool)), SLOT(slotStandardHeaders())); raction->setHelpText( i18n("Show standard list of message headers") ); group->addAction( raction ); headerMenu->addAction( raction ); raction = new KToggleAction(i18nc("View->headers->", "&Long Headers"), this); ac->addAction(QLatin1String("view_headers_long"), raction ); connect(raction, SIGNAL(triggered(bool)), SLOT(slotLongHeaders())); raction->setHelpText( i18n("Show long list of message headers") ); group->addAction( raction ); headerMenu->addAction( raction ); raction = new KToggleAction(i18nc("View->headers->", "&All Headers"), this); ac->addAction(QLatin1String("view_headers_all"), raction ); connect(raction, SIGNAL(triggered(bool)), SLOT(slotAllHeaders())); raction->setHelpText( i18n("Show all message headers") ); group->addAction( raction ); headerMenu->addAction( raction ); raction = new KToggleAction(i18nc("View->headers->", "&Custom Headers"), this); ac->addAction(QLatin1String("view_custom_headers"), raction ); connect(raction, SIGNAL(triggered(bool)), SLOT(slotCustomHeaders())); raction->setHelpText( i18n("Show custom headers") ); group->addAction( raction ); headerMenu->addAction( raction ); //Same action group mThemeManager->setActionGroup(group); mThemeManager->setThemeMenu(headerMenu); // attachment style KActionMenu *attachmentMenu = new KActionMenu(i18nc("View->", "&Attachments"), this); ac->addAction(QLatin1String("view_attachments"), attachmentMenu ); attachmentMenu->setHelpText( i18n("Choose display style of attachments") ); group = new QActionGroup( this ); raction = new KToggleAction(i18nc("View->attachments->", "&As Icons"), this); ac->addAction(QLatin1String("view_attachments_as_icons"), raction ); connect(raction, SIGNAL(triggered(bool)), SLOT(slotIconicAttachments())); raction->setHelpText( i18n("Show all attachments as icons. Click to see them.") ); group->addAction( raction ); attachmentMenu->addAction( raction ); raction = new KToggleAction(i18nc("View->attachments->", "&Smart"), this); ac->addAction(QLatin1String("view_attachments_smart"), raction ); connect(raction, SIGNAL(triggered(bool)), SLOT(slotSmartAttachments())); raction->setHelpText( i18n("Show attachments as suggested by sender.") ); group->addAction( raction ); attachmentMenu->addAction( raction ); raction = new KToggleAction(i18nc("View->attachments->", "&Inline"), this); ac->addAction(QLatin1String("view_attachments_inline"), raction ); connect(raction, SIGNAL(triggered(bool)), SLOT(slotInlineAttachments())); raction->setHelpText( i18n("Show all attachments inline (if possible)") ); group->addAction( raction ); attachmentMenu->addAction( raction ); raction = new KToggleAction(i18nc("View->attachments->", "&Hide"), this); ac->addAction(QLatin1String("view_attachments_hide"), raction ); connect(raction, SIGNAL(triggered(bool)), SLOT(slotHideAttachments())); raction->setHelpText( i18n("Do not show attachments in the message viewer") ); group->addAction( raction ); attachmentMenu->addAction( raction ); mHeaderOnlyAttachmentsAction = new KToggleAction( i18nc( "View->attachments->", "In Header Only" ), this ); ac->addAction( QLatin1String("view_attachments_headeronly"), mHeaderOnlyAttachmentsAction ); connect( mHeaderOnlyAttachmentsAction, SIGNAL(triggered(bool)), SLOT(slotHeaderOnlyAttachments()) ); mHeaderOnlyAttachmentsAction->setHelpText( i18n( "Show Attachments only in the header of the mail" ) ); group->addAction( mHeaderOnlyAttachmentsAction ); attachmentMenu->addAction( mHeaderOnlyAttachmentsAction ); // Set Encoding submenu mSelectEncodingAction = new KSelectAction(KIcon(QLatin1String("character-set")), i18n("&Set Encoding"), this); mSelectEncodingAction->setToolBarMode( KSelectAction::MenuMode ); ac->addAction(QLatin1String("encoding"), mSelectEncodingAction ); connect(mSelectEncodingAction,SIGNAL(triggered(int)), SLOT(slotSetEncoding())); QStringList encodings = NodeHelper::supportedEncodings( false ); encodings.prepend( i18n( "Auto" ) ); mSelectEncodingAction->setItems( encodings ); mSelectEncodingAction->setCurrentItem( 0 ); // // Message Menu // // copy selected text to clipboard mCopyAction = ac->addAction( KStandardAction::Copy, QLatin1String("kmail_copy"), this, SLOT(slotCopySelectedText()) ); connect( mViewer, SIGNAL(selectionChanged()), this, SLOT(viewerSelectionChanged()) ); viewerSelectionChanged(); // copy all text to clipboard mSelectAllAction = new KAction(i18n("Select All Text"), this); ac->addAction(QLatin1String("mark_all_text"), mSelectAllAction ); connect(mSelectAllAction, SIGNAL(triggered(bool)), SLOT(selectAll())); mSelectAllAction->setShortcut( QKeySequence( Qt::CTRL + Qt::SHIFT + Qt::Key_A ) ); // copy Email address to clipboard mCopyURLAction = new KAction( KIcon( QLatin1String("edit-copy" )), i18n( "Copy Link Address" ), this ); ac->addAction( QLatin1String("copy_url"), mCopyURLAction ); connect( mCopyURLAction, SIGNAL(triggered(bool)), SLOT(slotUrlCopy()) ); // open URL mUrlOpenAction = new KAction( KIcon(QLatin1String( "document-open" )), i18n( "Open URL" ), this ); ac->addAction( QLatin1String("open_url"), mUrlOpenAction ); connect( mUrlOpenAction, SIGNAL(triggered(bool)), this, SLOT(slotUrlOpen()) ); // use fixed font mToggleFixFontAction = new KToggleAction( i18n( "Use Fi&xed Font" ), this ); ac->addAction(QLatin1String( "toggle_fixedfont"), mToggleFixFontAction ); connect( mToggleFixFontAction, SIGNAL(triggered(bool)), SLOT(slotToggleFixedFont()) ); mToggleFixFontAction->setShortcut( QKeySequence( Qt::Key_X ) ); // Zoom actions mZoomTextOnlyAction = new KToggleAction( i18n( "Zoom Text Only" ), this ); ac->addAction( QLatin1String("toggle_zoomtextonly"), mZoomTextOnlyAction ); connect( mZoomTextOnlyAction, SIGNAL(triggered(bool)), SLOT(slotZoomTextOnly()) ); mZoomInAction = new KAction( KIcon(QLatin1String("zoom-in")), i18n("&Zoom In"), this); ac->addAction(QLatin1String("zoom_in"), mZoomInAction); connect(mZoomInAction, SIGNAL(triggered(bool)), SLOT(slotZoomIn())); mZoomInAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Plus)); mZoomOutAction = new KAction( KIcon(QLatin1String("zoom-out")), i18n("Zoom &Out"), this); ac->addAction(QLatin1String("zoom_out"), mZoomOutAction); connect(mZoomOutAction, SIGNAL(triggered(bool)), SLOT(slotZoomOut())); mZoomOutAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Minus)); mZoomResetAction = new KAction( i18n("Reset"), this); ac->addAction(QLatin1String("zoom_reset"), mZoomResetAction); connect(mZoomResetAction, SIGNAL(triggered(bool)), SLOT(slotZoomReset())); mZoomResetAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_0)); // Show message structure viewer mToggleMimePartTreeAction = new KToggleAction( i18n( "Show Message Structure" ), this ); ac->addAction( QLatin1String("toggle_mimeparttree"), mToggleMimePartTreeAction ); connect( mToggleMimePartTreeAction, SIGNAL(toggled(bool)), SLOT(slotToggleMimePartTree())); mViewSourceAction = new KAction(i18n("&View Source"), this); ac->addAction(QLatin1String("view_source"), mViewSourceAction ); connect(mViewSourceAction, SIGNAL(triggered(bool)), SLOT(slotShowMessageSource())); mViewSourceAction->setShortcut(QKeySequence(Qt::Key_V)); mSaveMessageAction = new KAction(KIcon(QLatin1String("document-save-as")), i18n("&Save message..."), this); ac->addAction(QLatin1String("save_message"), mSaveMessageAction); connect(mSaveMessageAction, SIGNAL(triggered(bool)), SLOT(slotSaveMessage())); //Laurent: conflict with kmail shortcut //mSaveMessageAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_S)); mSaveMessageDisplayFormat = new KAction( i18n("&Save Display Format"), this); ac->addAction(QLatin1String("save_message_display_format"), mSaveMessageDisplayFormat ); connect(mSaveMessageDisplayFormat, SIGNAL(triggered(bool)), SLOT(slotSaveMessageDisplayFormat())); mResetMessageDisplayFormat = new KAction( i18n("&Reset Display Format"), this); ac->addAction(QLatin1String("reset_message_display_format"), mResetMessageDisplayFormat ); connect(mResetMessageDisplayFormat, SIGNAL(triggered(bool)), SLOT(slotResetMessageDisplayFormat())); // // Scroll actions // mScrollUpAction = new KAction( i18n("Scroll Message Up"), this ); mScrollUpAction->setShortcut( QKeySequence( Qt::Key_Up ) ); ac->addAction( QLatin1String("scroll_up"), mScrollUpAction ); connect( mScrollUpAction, SIGNAL(triggered(bool)), q, SLOT(slotScrollUp()) ); mScrollDownAction = new KAction( i18n("Scroll Message Down"), this ); mScrollDownAction->setShortcut( QKeySequence( Qt::Key_Down ) ); ac->addAction( QLatin1String("scroll_down"), mScrollDownAction ); connect( mScrollDownAction, SIGNAL(triggered(bool)), q, SLOT(slotScrollDown()) ); mScrollUpMoreAction = new KAction( i18n("Scroll Message Up (More)"), this ); mScrollUpMoreAction->setShortcut( QKeySequence( Qt::Key_PageUp ) ); ac->addAction( QLatin1String("scroll_up_more"), mScrollUpMoreAction ); connect( mScrollUpMoreAction, SIGNAL(triggered(bool)), q, SLOT(slotScrollPrior()) ); mScrollDownMoreAction = new KAction( i18n("Scroll Message Down (More)"), this ); mScrollDownMoreAction->setShortcut( QKeySequence( Qt::Key_PageDown ) ); ac->addAction( QLatin1String("scroll_down_more"), mScrollDownMoreAction ); connect( mScrollDownMoreAction, SIGNAL(triggered(bool)), q, SLOT(slotScrollNext()) ); // // Actions not in menu // // Toggle HTML display mode. mToggleDisplayModeAction = new KToggleAction( i18n( "Toggle HTML Display Mode" ), this ); ac->addAction( QLatin1String("toggle_html_display_mode"), mToggleDisplayModeAction ); mToggleDisplayModeAction->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_H)); connect( mToggleDisplayModeAction, SIGNAL(triggered(bool)), SLOT(slotToggleHtmlMode()) ); mToggleDisplayModeAction->setHelpText( i18n( "Toggle display mode between HTML and plain text" ) ); // Load external reference KAction *loadExternalReferenceAction = new KAction( i18n( "Load external references" ), this ); ac->addAction( QLatin1String("load_external_reference"), loadExternalReferenceAction ); loadExternalReferenceAction->setShortcut(QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_R)); connect( loadExternalReferenceAction, SIGNAL(triggered(bool)), SLOT(slotLoadExternalReference()) ); loadExternalReferenceAction->setHelpText( i18n( "Load external references from the Internet for this message." ) ); mSpeakTextAction = new KAction(i18n("Speak Text"),this); mSpeakTextAction->setIcon(KIcon(QLatin1String("preferences-desktop-text-to-speech"))); ac->addAction( QLatin1String("speak_text"), mSpeakTextAction ); connect( mSpeakTextAction, SIGNAL(triggered(bool)), this, SLOT(slotSpeakText()) ); mCopyImageLocation = new KAction(i18n("Copy Image Location"),this); mCopyImageLocation->setIcon(KIcon(QLatin1String("view-media-visualization"))); ac->addAction(QLatin1String("copy_image_location"), mCopyImageLocation); mCopyImageLocation->setShortcutConfigurable( false ); connect( mCopyImageLocation, SIGNAL(triggered(bool)), SLOT(slotCopyImageLocation()) ); mTranslateAction = new KAction(i18n("Translate..."),this); mTranslateAction->setShortcut(QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_T)); mTranslateAction->setIcon(KIcon(QLatin1String("preferences-desktop-locale"))); ac->addAction(QLatin1String("translate_text"), mTranslateAction); connect( mTranslateAction, SIGNAL(triggered(bool)), SLOT(slotTranslate()) ); mFindInMessageAction = new KAction(KIcon(QLatin1String("edit-find")), i18n("&Find in Message..."), this); ac->addAction(QLatin1String("find_in_messages"), mFindInMessageAction ); connect(mFindInMessageAction, SIGNAL(triggered(bool)), SLOT(slotFind())); mFindInMessageAction->setShortcut(KStandardShortcut::find()); #ifndef KDEPIM_NO_WEBKIT #if QTWEBKIT_VERSION >= QTWEBKIT_VERSION_CHECK(2, 3, 0) mCaretBrowsing = new KToggleAction(i18n("Toggle Caret Browsing"), this); mCaretBrowsing->setShortcut(Qt::Key_F7); ac->addAction( QLatin1String("toggle_caret_browsing"), mCaretBrowsing ); connect( mCaretBrowsing, SIGNAL(triggered(bool)), SLOT(slotToggleCaretBrowsing(bool)) ); mCaretBrowsing->setChecked(false); #endif #endif mBlockImage = new KAction(i18n("Block image"), this); ac->addAction(QLatin1String("adblock_image"), mBlockImage); mBlockImage->setShortcutConfigurable( false ); connect( mBlockImage, SIGNAL(triggered(bool)), SLOT(slotBlockImage()) ); mBlockableItems = new KAction(i18n("Open Blockable Items..."), this); ac->addAction(QLatin1String("adblock_blockable_items"), mBlockableItems); connect( mBlockableItems, SIGNAL(triggered(bool)), SLOT(slotOpenBlockableItems()) ); mExpandUrlAction = new KAction(i18n("Expand Short URL"), this); ac->addAction(QLatin1String("expand_short_url"), mExpandUrlAction); mExpandUrlAction->setShortcutConfigurable( false ); connect( mExpandUrlAction, SIGNAL(triggered(bool)), SLOT(slotExpandShortUrl()) ); mCreateTodoAction = new KAction(KIcon( QLatin1String("task-new") ),i18n("Create Todo"), this); mCreateTodoAction->setIconText( i18n( "Create To-do" ) ); mCreateTodoAction->setHelpText( i18n( "Allows you to create a calendar to-do or reminder from this message" ) ); mCreateTodoAction->setWhatsThis( i18n( "This option starts the KOrganizer to-do editor with initial values taken from the currently selected message. Then you can edit the to-do to your liking before saving it to your calendar." ) ); ac->addAction(QLatin1String("create_todo"), mCreateTodoAction); mCreateTodoAction->setShortcut(Qt::CTRL + Qt::Key_T); connect( mCreateTodoAction, SIGNAL(triggered(bool)), SLOT(slotShowCreateTodoWidget()) ); mCreateEventAction = new KAction(KIcon( QLatin1String("appointment-new") ),i18n("Create Event"), this); mCreateEventAction->setIconText( i18n( "Create Event" ) ); mCreateEventAction->setHelpText( i18n( "Allows you to create a calendar Event" ) ); ac->addAction(QLatin1String("create_event"), mCreateEventAction); //TODO //mCreateEventAction->setShortcut(Qt::CTRL + Qt::Key_T); connect( mCreateEventAction, SIGNAL(triggered(bool)), SLOT(slotShowCreateEventWidget()) ); mCreateNoteAction = new KAction(KIcon( QLatin1String("view-pim-notes") ),i18nc("create a new note out of this message", "Create Note"), this); mCreateNoteAction->setIconText( i18nc("create a new note out of this message", "Create Note") ); mCreateNoteAction->setHelpText( i18n("Allows you to create a note from this message") ); mCreateNoteAction->setWhatsThis( i18n( "This option starts an editor to create a note. Then you can edit the note to your liking before saving it." ) ); ac->addAction(QLatin1String("create_note"), mCreateNoteAction); connect( mCreateNoteAction, SIGNAL(triggered(bool)), SLOT(slotShowCreateNoteWidget()) ); } void ViewerPrivate::showContextMenu( KMime::Content* content, const QPoint &pos ) { #ifndef QT_NO_TREEVIEW if ( !content ) return; bool deletedAttachment = false; if(content->contentType(false)) { deletedAttachment = (content->contentType()->mimeType() == "text/x-moz-deleted"); } if(deletedAttachment) return; const bool isAttachment = !content->contentType()->isMultipart() && !content->isTopLevel(); const bool isRoot = ( content == mMessage.get() ); const KMime::Content::List contents = Util::extractAttachments( mMessage.get() ); KMenu popup; if ( !isRoot ) { popup.addAction( SmallIcon( QLatin1String("document-save-as") ), i18n( "Save &As..." ), this, SLOT(slotAttachmentSaveAs()) ); if ( isAttachment ) { popup.addAction( SmallIcon( QLatin1String("document-open") ), i18nc( "to open", "Open" ), this, SLOT(slotAttachmentOpen()) ); if(selectedContents().count() == 1) createOpenWithMenu(&popup, QLatin1String(content->contentType()->mimeType()),false); else popup.addAction( i18n( "Open With..." ), this, SLOT(slotAttachmentOpenWith()) ); popup.addAction( i18nc( "to view something", "View" ), this, SLOT(slotAttachmentView()) ); } } if( !contents.isEmpty() ) { popup.addAction( i18n( "Save All Attachments..." ), this, SLOT(slotAttachmentSaveAll()) ); } // edit + delete only for attachments if ( !isRoot ) { if ( isAttachment ) { popup.addAction( SmallIcon( QLatin1String("edit-copy") ), i18n( "Copy" ), this, SLOT(slotAttachmentCopy()) ); #if 0 //FIXME Laurent Comment for the moment it crash see Bug 287177 if ( GlobalSettings::self()->allowAttachmentDeletion() ) popup.addAction( SmallIcon( "edit-delete" ), i18n( "Delete Attachment" ), this, SLOT(slotAttachmentDelete()) ); #endif if ( GlobalSettings::self()->allowAttachmentEditing() ) popup.addAction( SmallIcon( QLatin1String("document-properties") ), i18n( "Edit Attachment" ), this, SLOT(slotAttachmentEdit()) ); } if ( !content->isTopLevel() ) popup.addAction( i18n( "Properties" ), this, SLOT(slotAttachmentProperties()) ); } popup.exec( mMimePartTree->viewport()->mapToGlobal( pos ) ); #endif } KToggleAction *ViewerPrivate::actionForHeaderStyle( const HeaderStyle * style, const HeaderStrategy * strategy ) { if ( !mActionCollection ) return 0; QString actionName; if ( style == HeaderStyle::enterprise() ) actionName = QLatin1String("view_headers_enterprise"); else if ( style == HeaderStyle::fancy() ) actionName = QLatin1String("view_headers_fancy"); else if ( style == HeaderStyle::brief() ) actionName = QLatin1String("view_headers_brief"); else if ( style == HeaderStyle::plain() ) { if ( strategy == HeaderStrategy::standard() ) actionName = QLatin1String("view_headers_standard"); else if ( strategy == HeaderStrategy::rich() ) actionName = QLatin1String("view_headers_long"); else if ( strategy == HeaderStrategy::all() ) actionName = QLatin1String("view_headers_all"); } else if (style == HeaderStyle::custom() ) { actionName = QLatin1String("view_custom_headers"); } else if (style == HeaderStyle::grantlee()) { return mThemeManager->actionForTheme(); } if ( actionName.isEmpty() ) return 0; else return static_cast(mActionCollection->action(actionName)); } KToggleAction *ViewerPrivate::actionForAttachmentStrategy( const AttachmentStrategy * as ) { if ( !mActionCollection ) return 0; QString actionName ; if ( as == AttachmentStrategy::iconic() ) actionName = QLatin1String("view_attachments_as_icons"); else if ( as == AttachmentStrategy::smart() ) actionName = QLatin1String("view_attachments_smart"); else if ( as == AttachmentStrategy::inlined() ) actionName = QLatin1String("view_attachments_inline"); else if ( as == AttachmentStrategy::hidden() ) actionName = QLatin1String("view_attachments_hide"); else if ( as == AttachmentStrategy::headerOnly() ) actionName = QLatin1String("view_attachments_headeronly"); if (actionName.isEmpty()) return 0; else return static_cast(mActionCollection->action(actionName)); } void ViewerPrivate::readGlobalOverrideCodec() { // if the global character encoding wasn't changed then there's nothing to do if ( MessageCore::GlobalSettings::self()->overrideCharacterEncoding() == mOldGlobalOverrideEncoding ) return; setOverrideEncoding( MessageCore::GlobalSettings::self()->overrideCharacterEncoding() ); mOldGlobalOverrideEncoding = MessageCore::GlobalSettings::self()->overrideCharacterEncoding(); } const QTextCodec * ViewerPrivate::overrideCodec() const { if ( mOverrideEncoding.isEmpty() || mOverrideEncoding == QLatin1String("Auto") ) // Auto return 0; else return ViewerPrivate::codecForName( mOverrideEncoding.toLatin1() ); } static QColor nextColor( const QColor & c ) { int h, s, v; c.getHsv( &h, &s, &v ); return QColor::fromHsv( (h + 50) % 360, qMax(s, 64), v ); } QString ViewerPrivate::renderAttachments( KMime::Content * node, const QColor &bgColor ) const { if ( !node ) return QString(); QString html; KMime::Content * child = MessageCore::NodeHelper::firstChild( node ); if ( child) { QString subHtml = renderAttachments( child, nextColor( bgColor ) ); if ( !subHtml.isEmpty() ) { QString visibility; if( !mShowAttachmentQuicklist ) { visibility.append( QLatin1String("display:none;") ); } QString margin; if ( node != mMessage.get() || headerStyle() != HeaderStyle::enterprise() ) margin = QLatin1String("padding:2px; margin:2px; "); QString align = QLatin1String("left"); if ( headerStyle() == HeaderStyle::enterprise() ) align = QLatin1String("right"); const bool result = ( node->contentType()->mediaType().toLower() == "message" || node->contentType()->mediaType().toLower() == "multipart" || node == mMessage.get() ); if ( result ) html += QString::fromLatin1("
").arg( bgColor.name() ).arg( margin ) .arg( align ).arg( visibility ); html += subHtml; if ( result ) html += QLatin1String("
"); } } else { NodeHelper::AttachmentDisplayInfo info = NodeHelper::attachmentDisplayInfo( node ); if ( info.displayInHeader ) { html += QLatin1String("
"); html += QString::fromLatin1( "" ).arg( bgColor.name() ); mNodeHelper->writeNodeToTempFile( node ); const QString href = mNodeHelper->asHREF( node, QLatin1String("header") ); html += QString::fromLatin1( "" ); QString imageMaxSize; if(!info.icon.isEmpty()) { QImage tmpImg(info.icon); if(tmpImg.width() > 48 || tmpImg.height() > 48) { imageMaxSize = QLatin1String("width=\"48\" height=\"48\""); } } html += QString::fromLatin1(" "); if ( headerStyle() == HeaderStyle::enterprise() ) { QFont bodyFont = mCSSHelper->bodyFont( mUseFixedFont ); QFontMetrics fm( bodyFont ); html += fm.elidedText( info.label, Qt::ElideRight, 180 ); } else if ( headerStyle() == HeaderStyle::fancy() ) { QFont bodyFont = mCSSHelper->bodyFont( mUseFixedFont ); QFontMetrics fm( bodyFont ); html += fm.elidedText( info.label, Qt::ElideRight, 1000 ); } else { html += info.label; } html += QLatin1String("
"); } } KMime::Content *next = MessageCore::NodeHelper::nextSibling( node ); if ( next ) html += renderAttachments( next, nextColor ( bgColor ) ); return html; } KMime::Content* ViewerPrivate::findContentByType(KMime::Content *content, const QByteArray &type) { KMime::Content::List list = content->contents(); Q_FOREACH(KMime::Content *c, list) { if (c->contentType()->mimeType() == type) return c; } return 0; } //----------------------------------------------------------------------------- const QTextCodec* ViewerPrivate::codecForName(const QByteArray& _str) { if (_str.isEmpty()) return 0; QByteArray codec = _str; kAsciiToLower(codec.data()); return KGlobal::charsets()->codecForName(QLatin1String(codec)); } void ViewerPrivate::update( MessageViewer::Viewer::UpdateMode updateMode ) { // Avoid flicker, somewhat of a cludge if ( updateMode == Viewer::Force ) { // stop the timer to avoid calling updateReaderWin twice mUpdateReaderWinTimer.stop(); saveRelativePosition(); updateReaderWin(); } else if ( mUpdateReaderWinTimer.isActive() ) { mUpdateReaderWinTimer.setInterval( delay ); } else { mUpdateReaderWinTimer.start( 0 ); } } void ViewerPrivate::slotUrlOpen( const QUrl& url ) { KUrl aUrl(url); if( !url.isEmpty() ) mClickedUrl = aUrl; // First, let's see if the URL handler manager can handle the URL. If not, try KRun for some // known URLs, otherwise fallback to emitting a signal. // That signal is caught by KMail, and in case of mailto URLs, a composer is shown. if ( URLHandlerManager::instance()->handleClick( mClickedUrl, this ) ) return; emit urlClicked( mMessageItem, mClickedUrl ); } void ViewerPrivate::slotUrlOn(const QString& link, const QString& title, const QString& textContent ) { Q_UNUSED(title) Q_UNUSED(textContent) // The "link" we get here is not URL-encoded, and therefore there is no way the KUrl or QUrl could // parse it correctly. To workaround that, we use QWebFrame::hitTestContent() on the mouse position // to get the URL before WebKit managed to mangle it. KUrl url( mViewer->linkOrImageUrlAt( QCursor::pos() ) ); const QString protocol = url.protocol(); if ( protocol == QLatin1String( "kmail" ) || protocol == QLatin1String( "x-kmail" ) || protocol == QLatin1String( "attachment" ) || ( protocol.isEmpty() && url.path().isEmpty() ) ) { mViewer->setAcceptDrops( false ); } else { mViewer->setAcceptDrops( true ); } if ( link.trimmed().isEmpty() ) { KPIM::BroadcastStatus::instance()->reset(); emit showStatusBarMessage( QString() ); return; } QString msg = URLHandlerManager::instance()->statusBarMessage( url, this ); if ( msg.isEmpty() ) { msg = link; } KPIM::BroadcastStatus::instance()->setTransientStatusMsg( msg ); emit showStatusBarMessage( msg ); } void ViewerPrivate::slotUrlPopup(const QUrl &aUrl, const QUrl &imageUrl, const QPoint& aPos) { const KUrl url( aUrl ); const KUrl iUrl( imageUrl ); mClickedUrl = url; mImageUrl = iUrl; if ( URLHandlerManager::instance()->handleContextMenuRequest( url, aPos, this ) ) return; if ( !mActionCollection ) return; if ( url.protocol() == QLatin1String( "mailto" ) ) { mCopyURLAction->setText( i18n( "Copy Email Address" ) ); } else { mCopyURLAction->setText( i18n( "Copy Link Address" ) ); } emit popupMenu( mMessageItem, aUrl, imageUrl, aPos ); } void ViewerPrivate::slotLoadExternalReference() { if(mColorBar->isNormal() || htmlLoadExtOverride()) return; setHtmlLoadExtOverride( true ); update( Viewer::Force ); } void ViewerPrivate::slotToggleHtmlMode() { if(mColorBar->isNormal()) return; mScamDetectionWarning->setVisible(false); setHtmlOverride( !htmlMail() ); update( Viewer::Force ); } void ViewerPrivate::slotFind() { #ifndef KDEPIM_NO_WEBKIT if ( mViewer->hasSelection() ) mFindBar->setText( mViewer->selectedText() ); #endif mFindBar->show(); mFindBar->focusAndSetCursor(); } void ViewerPrivate::slotTranslate() { const QString text = mViewer->selectedText(); mTranslatorWidget->show(); if(!text.isEmpty()) mTranslatorWidget->setTextToTranslate(text); } void ViewerPrivate::slotToggleFixedFont() { mUseFixedFont = !mUseFixedFont; update( Viewer::Force ); } void ViewerPrivate::slotToggleMimePartTree() { if ( mToggleMimePartTreeAction->isChecked() ) GlobalSettings::self()->setMimeTreeMode( GlobalSettings::EnumMimeTreeMode::Always ); else GlobalSettings::self()->setMimeTreeMode( GlobalSettings::EnumMimeTreeMode::Never ); showHideMimeTree(); } void ViewerPrivate::slotShowMessageSource() { if( !mMessage ) return; mNodeHelper->messageWithExtraContent( mMessage.get() ); const QString rawMessage = QString::fromLatin1( mMessage->encodedContent() ); const QString htmlSource = mViewer->htmlSource(); MailSourceViewer *viewer = new MailSourceViewer(); // deletes itself upon close viewer->setWindowTitle( i18n("Message as Plain Text") ); viewer->setRawSource( rawMessage ); viewer->setDisplayedSource( htmlSource ); if( mUseFixedFont ) { viewer->setFixedFont(); } // Well, there is no widget to be seen here, so we have to use QCursor::pos() // Update: (GS) I'm not going to make this code behave according to Xinerama // configuration because this is quite the hack. if ( QApplication::desktop()->isVirtualDesktop() ) { #ifndef QT_NO_CURSOR int scnum = QApplication::desktop()->screenNumber( QCursor::pos() ); #else int scnum = 0; #endif viewer->resize( QApplication::desktop()->screenGeometry( scnum ).width()/2, 2 * QApplication::desktop()->screenGeometry( scnum ).height()/3); } else { viewer->resize( QApplication::desktop()->geometry().width()/2, 2 * QApplication::desktop()->geometry().height()/3); } viewer->show(); } void ViewerPrivate::updateReaderWin() { if ( !mMsgDisplay ) { return; } if ( mRecursionCountForDisplayMessage + 1 > 1 ) { // This recursion here can happen because the ObjectTreeParser in parseMsg() can exec() an // eventloop. // This happens in two cases: // 1) The ContactSearchJob started by FancyHeaderStyle::format // 2) Various modal passphrase dialogs for decryption of a message (bug 96498) // // While the exec() eventloop is running, it is possible that a timer calls updateReaderWin(), // and not aborting here would confuse the state terribly. kWarning() << "Danger, recursion while displaying a message!"; return; } mRecursionCountForDisplayMessage++; mViewer->setAllowExternalContent( htmlLoadExternal() ); htmlWriter()->reset(); //TODO: if the item doesn't have the payload fetched, try to fetch it? Maybe not here, but in setMessageItem. if ( mMessage ) { if ( GlobalSettings::self()->showColorBar() ) { mColorBar->show(); } else { mColorBar->hide(); } displayMessage(); } else if( mMessagePartNode ) { setMessagePart( mMessagePartNode ); } else { mColorBar->hide(); #ifndef QT_NO_TREEVIEW mMimePartTree->hide(); //FIXME(Andras) mMimePartTree->clearAndResetSortOrder(); #endif htmlWriter()->begin( QString() ); htmlWriter()->write( mCSSHelper->htmlHead( mUseFixedFont ) + QLatin1String("") ); htmlWriter()->end(); } if ( mSavedRelativePosition ) { mViewer->scrollToRelativePosition( mSavedRelativePosition ); mSavedRelativePosition = 0; } mRecursionCountForDisplayMessage--; } void ViewerPrivate::slotMimePartSelected( const QModelIndex &index ) { KMime::Content *content = static_cast( index.internalPointer() ); if ( !mMimePartModel->parent( index ).isValid() && index.row() == 0 ) { update( Viewer::Force ); } else { setMessagePart( content ); } } void ViewerPrivate::slotBriefHeaders() { setHeaderStyleAndStrategy( HeaderStyle::brief(), HeaderStrategy::brief(),true ); } void ViewerPrivate::slotFancyHeaders() { setHeaderStyleAndStrategy( HeaderStyle::fancy(), HeaderStrategy::rich(), true ); } void ViewerPrivate::slotEnterpriseHeaders() { setHeaderStyleAndStrategy( HeaderStyle::enterprise(), HeaderStrategy::rich(),true ); } void ViewerPrivate::slotStandardHeaders() { setHeaderStyleAndStrategy( HeaderStyle::plain(), HeaderStrategy::standard(), true); } void ViewerPrivate::slotLongHeaders() { setHeaderStyleAndStrategy( HeaderStyle::plain(), HeaderStrategy::rich(),true ); } void ViewerPrivate::slotAllHeaders() { setHeaderStyleAndStrategy( HeaderStyle::plain(), HeaderStrategy::all(), true ); } void ViewerPrivate::slotCustomHeaders() { setHeaderStyleAndStrategy( HeaderStyle::custom(), HeaderStrategy::custom(), true ); } void ViewerPrivate::slotGrantleeHeaders() { setHeaderStyleAndStrategy( HeaderStyle::grantlee(), HeaderStrategy::grantlee(), true ); initGrantleeThemeName(); update( Viewer::Force ); } void ViewerPrivate::initGrantleeThemeName() { const QString themeName = GrantleeTheme::GrantleeSettings::self()->grantleeThemeName(); headerStyle()->setTheme(mThemeManager->theme(themeName)); } void ViewerPrivate::slotIconicAttachments() { setAttachmentStrategy( AttachmentStrategy::iconic() ); } void ViewerPrivate::slotSmartAttachments() { setAttachmentStrategy( AttachmentStrategy::smart() ); } void ViewerPrivate::slotInlineAttachments() { setAttachmentStrategy( AttachmentStrategy::inlined() ); } void ViewerPrivate::slotHideAttachments() { setAttachmentStrategy( AttachmentStrategy::hidden() ); } void ViewerPrivate::slotHeaderOnlyAttachments() { setAttachmentStrategy( AttachmentStrategy::headerOnly() ); } void ViewerPrivate::attachmentView( KMime::Content *atmNode ) { if ( atmNode ) { const bool isEncapsulatedMessage = atmNode->parent() && atmNode->parent()->bodyIsMessage(); if ( isEncapsulatedMessage ) { atmViewMsg( atmNode->parent()->bodyAsMessage() ); } else if ((kasciistricmp(atmNode->contentType()->mediaType(), "text")==0) && ( (kasciistricmp(atmNode->contentType()->subType(), "x-vcard")==0) || (kasciistricmp(atmNode->contentType()->subType(), "directory")==0) )) { setMessagePart( atmNode ); } else { emit showReader( atmNode, htmlMail(), overrideEncoding() ); } } } void ViewerPrivate::slotDelayedResize() { mSplitter->setGeometry( 0, 0, q->width(), q->height() ); } void ViewerPrivate::slotPrintPreview() { disconnect( mPartHtmlWriter, SIGNAL(finished()), this, SLOT(slotPrintPreview()) ); if ( !mMessage ) return; QPrinter printer; KPrintPreview previewdlg( &printer/*, mViewer*/ ); mViewer->print( &printer ); previewdlg.exec(); } void ViewerPrivate::slotPrintMsg() { disconnect( mPartHtmlWriter, SIGNAL(finished()), this, SLOT(slotPrintMsg()) ); if ( !mMessage ) return; QPrinter printer; AutoQPointer dlg(KdePrint::createPrintDialog(&printer)); if ( dlg && dlg->exec() == QDialog::Accepted ) { mViewer->print( &printer ); } } void ViewerPrivate::slotSetEncoding() { if ( mSelectEncodingAction->currentItem() == 0 ) // Auto mOverrideEncoding.clear(); else mOverrideEncoding = NodeHelper::encodingForName( mSelectEncodingAction->currentText() ); update( Viewer::Force ); } QString ViewerPrivate::attachmentInjectionHtml() const { QString imgpath( KStandardDirs::locate("data",QLatin1String("libmessageviewer/pics/")) ); QString urlHandle; QString imgSrc; if( !mShowAttachmentQuicklist ) { urlHandle.append( QLatin1String("kmail:showAttachmentQuicklist") ); imgSrc.append( QLatin1String("quicklistClosed.png") ); } else { urlHandle.append( QLatin1String("kmail:hideAttachmentQuicklist") ); imgSrc.append( QLatin1String("quicklistOpened.png") ); } QColor background = KColorScheme( QPalette::Active, KColorScheme::View ).background().color(); QString html = renderAttachments( mMessage.get(), background ); Q_FOREACH( KMime::Content* node, mNodeHelper->extraContents( mMessage.get() ) ) { html += renderAttachments( node, background ); } if ( html.isEmpty() ) return QString(); QString link; if ( headerStyle() == HeaderStyle::fancy() ) { link += QLatin1String("
"); html.prepend( link ); html.prepend( QString::fromLatin1("
%1 
" ).arg(i18n("Attachments:")) ); } else { link += QLatin1String("
"); html.prepend( link ); } return html; } void ViewerPrivate::injectAttachments() { disconnect( mPartHtmlWriter, SIGNAL(finished()), this, SLOT(injectAttachments()) ); // inject attachments in header view // we have to do that after the otp has run so we also see encrypted parts mViewer->injectAttachments( bind( &ViewerPrivate::attachmentInjectionHtml, this ) ); } void ViewerPrivate::slotSettingsChanged() { update( Viewer::Force ); } void ViewerPrivate::slotMimeTreeContextMenuRequested( const QPoint& pos ) { #ifndef QT_NO_TREEVIEW QModelIndex index = mMimePartTree->indexAt( pos ); if ( index.isValid() ) { KMime::Content *content = static_cast( index.internalPointer() ); showContextMenu( content, pos ); } #endif } void ViewerPrivate::slotAttachmentOpenWith() { #ifndef QT_NO_TREEVIEW QItemSelectionModel *selectionModel = mMimePartTree->selectionModel(); QModelIndexList selectedRows = selectionModel->selectedRows(); Q_FOREACH( const QModelIndex &index, selectedRows ) { KMime::Content *content = static_cast( index.internalPointer() ); attachmentOpenWith( content ); } #endif } void ViewerPrivate::slotAttachmentOpen() { #ifndef QT_NO_TREEVIEW QItemSelectionModel *selectionModel = mMimePartTree->selectionModel(); QModelIndexList selectedRows = selectionModel->selectedRows(); Q_FOREACH( const QModelIndex &index, selectedRows ) { KMime::Content *content = static_cast( index.internalPointer() ); attachmentOpen( content ); } #endif } void ViewerPrivate::slotAttachmentSaveAs() { const KMime::Content::List contents = selectedContents(); Util::saveAttachments( contents, mMainWindow ); } void ViewerPrivate::slotAttachmentSaveAll() { const KMime::Content::List contents = Util::extractAttachments( mMessage.get() ); Util::saveAttachments( contents, mMainWindow ); } void ViewerPrivate::slotAttachmentView() { KMime::Content::List contents = selectedContents(); Q_FOREACH(KMime::Content *content, contents) { attachmentView( content ); } } void ViewerPrivate::slotAttachmentProperties() { KMime::Content::List contents = selectedContents(); if ( contents.isEmpty() ) return; Q_FOREACH( KMime::Content *content, contents ) { attachmentProperties( content ); } } void ViewerPrivate::attachmentProperties( KMime::Content *content ) { MessageCore::AttachmentPropertiesDialog *dialog = new MessageCore::AttachmentPropertiesDialog( content, mMainWindow ); dialog->setAttribute( Qt::WA_DeleteOnClose ); dialog->show(); } void ViewerPrivate::slotAttachmentCopy() { #ifndef QT_NO_CLIPBOARD KMime::Content::List contents = selectedContents(); attachmentCopy( contents ); #endif } void ViewerPrivate::attachmentCopy( const KMime::Content::List & contents ) { #ifndef QT_NO_CLIPBOARD if ( contents.isEmpty() ) return; QList urls; Q_FOREACH( KMime::Content *content, contents) { KUrl kUrl = mNodeHelper->writeNodeToTempFile( content ); QUrl url = QUrl::fromPercentEncoding( kUrl.toEncoded() ); if ( !url.isValid() ) continue; urls.append( url ); } if ( urls.isEmpty() ) return; QMimeData *mimeData = new QMimeData; mimeData->setUrls( urls ); QApplication::clipboard()->setMimeData( mimeData, QClipboard::Clipboard ); #endif } void ViewerPrivate::slotAttachmentDelete() { KMime::Content::List contents = selectedContents(); if ( contents.isEmpty() ) return; bool showWarning = true; Q_FOREACH( KMime::Content *content, contents ) { if ( !deleteAttachment( content, showWarning ) ) return; showWarning = false; } update(); } void ViewerPrivate::slotAttachmentEdit() { KMime::Content::List contents = selectedContents(); if ( contents.isEmpty() ) return; bool showWarning = true; Q_FOREACH( KMime::Content *content, contents ) { if ( !editAttachment( content, showWarning ) ) return; showWarning = false; } } void ViewerPrivate::slotAttachmentEditDone( EditorWatcher* editorWatcher ) { QString name = editorWatcher->url().fileName(); if ( editorWatcher->fileChanged() ) { QFile file( name ); if ( file.open( QIODevice::ReadOnly ) ) { QByteArray data = file.readAll(); KMime::Content *node = mEditorWatchers[editorWatcher]; node->setBody( data ); file.close(); mMessageItem.setPayloadFromData( mMessage->encodedContent() ); Akonadi::ItemModifyJob *job = new Akonadi::ItemModifyJob( mMessageItem ); connect( job, SIGNAL(result(KJob*)), SLOT(itemModifiedResult(KJob*)) ); } } mEditorWatchers.remove( editorWatcher ); QFile::remove( name ); } void ViewerPrivate::slotLevelQuote( int l ) { if (mLevelQuote != l) { mLevelQuote = l; update( Viewer::Force ); } } void ViewerPrivate::slotHandleAttachment( int choice ) { if(!mCurrentContent) return; if ( choice == Viewer::Delete ) { deleteAttachment( mCurrentContent ); } else if ( choice == Viewer::Edit ) { editAttachment( mCurrentContent ); } else if ( choice == Viewer::Properties ) { attachmentProperties( mCurrentContent ); } else if ( choice == Viewer::Save ) { Util::saveContents( mMainWindow, KMime::Content::List() << mCurrentContent ); } else if ( choice == Viewer::OpenWith ) { attachmentOpenWith( mCurrentContent ); } else if ( choice == Viewer::Open ) { attachmentOpen( mCurrentContent ); } else if ( choice == Viewer::View ) { attachmentView( mCurrentContent ); } else if ( choice == Viewer::ChiasmusEncrypt ) { attachmentEncryptWithChiasmus( mCurrentContent ); } else if ( choice == Viewer::Copy ) { attachmentCopy( KMime::Content::List()<< mCurrentContent ); } else if ( choice == Viewer::ScrollTo ) { scrollToAttachment( mCurrentContent ); } else { kDebug() << " not implemented :" << choice; } } void ViewerPrivate::slotSpeakText() { const QString text = mViewer->selectedText(); MessageViewer::Util::speakSelectedText( text, mMainWindow); } void ViewerPrivate::slotCopyImageLocation() { #ifndef QT_NO_CLIPBOARD QApplication::clipboard()->setText( mImageUrl.url() ); #endif } void ViewerPrivate::slotCopySelectedText() { #ifndef QT_NO_CLIPBOARD QString selection = mViewer->selectedText(); selection.replace( QChar::Nbsp, QLatin1Char(' ') ); QApplication::clipboard()->setText( selection ); #endif } void ViewerPrivate::viewerSelectionChanged() { mActionCollection->action( QLatin1String("kmail_copy") )->setEnabled( !mViewer->selectedText().isEmpty() ); } void ViewerPrivate::selectAll() { mViewer->selectAll(); } void ViewerPrivate::clearSelection() { mViewer->clearSelection(); } void ViewerPrivate::slotUrlCopy() { #ifndef QT_NO_CLIPBOARD QClipboard* clip = QApplication::clipboard(); if ( mClickedUrl.protocol() == QLatin1String( "mailto" ) ) { // put the url into the mouse selection and the clipboard const QString address = KPIMUtils::decodeMailtoUrl( mClickedUrl ); clip->setText( address, QClipboard::Clipboard ); clip->setText( address, QClipboard::Selection ); KPIM::BroadcastStatus::instance()->setStatusMsg( i18n( "Address copied to clipboard." )); } else { // put the url into the mouse selection and the clipboard clip->setText( mClickedUrl.url(), QClipboard::Clipboard ); clip->setText( mClickedUrl.url(), QClipboard::Selection ); KPIM::BroadcastStatus::instance()->setStatusMsg( i18n( "URL copied to clipboard." )); } #endif } void ViewerPrivate::slotSaveMessage() { if ( !mMessageItem.hasPayload() ) { if ( mMessageItem.isValid() ) { kWarning() << "Payload is not a MessagePtr!"; } return; } Util::saveMessageInMbox( QList() << mMessageItem, mMainWindow ); } void ViewerPrivate::saveRelativePosition() { mSavedRelativePosition = mViewer->relativePosition(); } //TODO(Andras) inline them bool ViewerPrivate::htmlMail() const { return ((mHtmlMail && !mHtmlOverride) || (!mHtmlMail && mHtmlOverride)); } bool ViewerPrivate::htmlLoadExternal() const { return ((mHtmlLoadExternal && !mHtmlLoadExtOverride) || (!mHtmlLoadExternal && mHtmlLoadExtOverride)); } void ViewerPrivate::setHtmlOverride( bool override ) { mHtmlOverride = override; // keep toggle display mode action state in sync. if ( mToggleDisplayModeAction ) mToggleDisplayModeAction->setChecked( htmlMail() ); } bool ViewerPrivate::htmlOverride() const { return mHtmlOverride; } void ViewerPrivate::setHtmlLoadExtOverride( bool override ) { mHtmlLoadExtOverride = override; } bool ViewerPrivate::htmlLoadExtOverride() const { return mHtmlLoadExtOverride; } void ViewerPrivate::setDecryptMessageOverwrite( bool overwrite ) { mDecrytMessageOverwrite = overwrite; } bool ViewerPrivate::showSignatureDetails() const { return mShowSignatureDetails; } void ViewerPrivate::setShowSignatureDetails( bool showDetails ) { mShowSignatureDetails = showDetails; } bool ViewerPrivate::showAttachmentQuicklist() const { return mShowAttachmentQuicklist; } void ViewerPrivate::setShowAttachmentQuicklist( bool showAttachmentQuicklist ) { mShowAttachmentQuicklist = showAttachmentQuicklist; } void ViewerPrivate::setExternalWindow( bool b ) { mExternalWindow = b; } void ViewerPrivate::scrollToAttachment( KMime::Content *node ) { const QString indexStr = node->index().toString(); // The anchors for this are created in ObjectTreeParser::parseObjectTree() mViewer->scrollToAnchor( QLatin1String("att") + indexStr ); // Remove any old color markings which might be there const KMime::Content *root = node->topLevel(); const int totalChildCount = Util::allContents( root ).size(); for ( int i = 0 ; i < totalChildCount + 1 ; ++i ) { mViewer->removeAttachmentMarking( QString::fromLatin1( "attachmentDiv%1" ).arg( i + 1 ) ); } // Don't mark hidden nodes, that would just produce a strange yellow line if ( mNodeHelper->isNodeDisplayedHidden( node ) ) { return; } // Now, color the div of the attachment in yellow, so that the user sees what happened. // We created a special marked div for this in writeAttachmentMarkHeader() in ObjectTreeParser, // find and modify that now. mViewer->markAttachment( QLatin1String("attachmentDiv") + indexStr, QString::fromLatin1( "border:2px solid %1" ).arg( cssHelper()->pgpWarnColor().name() ) ); } void ViewerPrivate::setUseFixedFont( bool useFixedFont ) { mUseFixedFont = useFixedFont; if ( mToggleFixFontAction ) { mToggleFixFontAction->setChecked( mUseFixedFont ); } } void ViewerPrivate::attachmentEncryptWithChiasmus( KMime::Content *content ) { Q_UNUSED( content ); // FIXME: better detection of mimetype?? if ( !mCurrentFileName.endsWith( QLatin1String(".xia"), Qt::CaseInsensitive ) ) return; const Kleo::CryptoBackend::Protocol * chiasmus = Kleo::CryptoBackendFactory::instance()->protocol( "Chiasmus" ); Q_ASSERT( chiasmus ); if ( !chiasmus ) return; const std::auto_ptr listjob( chiasmus->specialJob( "x-obtain-keys", QMap() ) ); if ( !listjob.get() ) { const QString msg = i18n( "Chiasmus backend does not offer the " "\"x-obtain-keys\" function. Please report this bug." ); KMessageBox::error( mMainWindow, msg, i18n( "Chiasmus Backend Error" ) ); return; } if ( listjob->exec() ) { listjob->showErrorDialog( mMainWindow, i18n( "Chiasmus Backend Error" ) ); return; } const QVariant result = listjob->property( "result" ); if ( result.type() != QVariant::StringList ) { const QString msg = i18n( "Unexpected return value from Chiasmus backend: " "The \"x-obtain-keys\" function did not return a " "string list. Please report this bug." ); KMessageBox::error( mMainWindow, msg, i18n( "Chiasmus Backend Error" ) ); return; } const QStringList keys = result.toStringList(); if ( keys.empty() ) { const QString msg = i18n( "No keys have been found. Please check that a " "valid key path has been set in the Chiasmus " "configuration." ); KMessageBox::error( mMainWindow, msg, i18n( "Chiasmus Backend Error" ) ); return; } AutoQPointer selectorDlg( new ChiasmusKeySelector( mMainWindow, i18n( "Chiasmus Decryption Key Selection" ), keys, GlobalSettings::chiasmusDecryptionKey(), GlobalSettings::chiasmusDecryptionOptions() ) ); if ( selectorDlg->exec() != QDialog::Accepted || !selectorDlg ) { return; } GlobalSettings::setChiasmusDecryptionOptions( selectorDlg->options() ); GlobalSettings::setChiasmusDecryptionKey( selectorDlg->key() ); assert( !GlobalSettings::chiasmusDecryptionKey().isEmpty() ); Kleo::SpecialJob * job = chiasmus->specialJob( "x-decrypt", QMap() ); if ( !job ) { const QString msg = i18n( "Chiasmus backend does not offer the " "\"x-decrypt\" function. Please report this bug." ); KMessageBox::error( mMainWindow, msg, i18n( "Chiasmus Backend Error" ) ); return; } //PORT IT const QByteArray input;// = node->msgPart().bodyDecodedBinary(); if ( !job->setProperty( "key", GlobalSettings::chiasmusDecryptionKey() ) || !job->setProperty( "options", GlobalSettings::chiasmusDecryptionOptions() ) || !job->setProperty( "input", input ) ) { const QString msg = i18n( "The \"x-decrypt\" function does not accept " "the expected parameters. Please report this bug." ); KMessageBox::error( mMainWindow, msg, i18n( "Chiasmus Backend Error" ) ); return; } if ( job->start() ) { job->showErrorDialog( mMainWindow, i18n( "Chiasmus Decryption Error" ) ); return; } mJob = job; connect( job, SIGNAL(result(GpgME::Error,QVariant)), this, SLOT(slotAtmDecryptWithChiasmusResult(GpgME::Error,QVariant)) ); } static const QString chomp( const QString & base, const QString & suffix, bool cs ) { return base.endsWith( suffix, cs ? (Qt::CaseSensitive) : (Qt::CaseInsensitive) ) ? base.left( base.length() - suffix.length() ) : base ; } void ViewerPrivate::slotAtmDecryptWithChiasmusResult( const GpgME::Error & err, const QVariant & result ) { if ( !mJob ) return; Q_ASSERT( mJob == sender() ); if ( mJob != sender() ) return; Kleo::Job * job = mJob; mJob = 0; if ( err.isCanceled() ) return; if ( err ) { job->showErrorDialog( mMainWindow, i18n( "Chiasmus Decryption Error" ) ); return; } if ( result.type() != QVariant::ByteArray ) { const QString msg = i18n( "Unexpected return value from Chiasmus backend: " "The \"x-decrypt\" function did not return a " "byte array. Please report this bug." ); KMessageBox::error( mMainWindow, msg, i18n( "Chiasmus Backend Error" ) ); return; } const KUrl url = KFileDialog::getSaveUrl( chomp( mCurrentFileName, QLatin1String(".xia"), false ), QString(), mMainWindow ); if ( url.isEmpty() ) return; bool overwrite = Util::checkOverwrite( url, mMainWindow ); if ( !overwrite ) return; KIO::Job * uploadJob = KIO::storedPut( result.toByteArray(), url, -1, KIO::Overwrite ); uploadJob->ui()->setWindow( mMainWindow ); connect( uploadJob, SIGNAL(result(KJob*)), this, SLOT(slotAtmDecryptWithChiasmusUploadResult(KJob*)) ); } void ViewerPrivate::slotAtmDecryptWithChiasmusUploadResult( KJob * job ) { if ( job->error() ) static_cast(job)->ui()->showErrorMessage(); } bool ViewerPrivate::showFullToAddressList() const { return mShowFullToAddressList; } void ViewerPrivate::setShowFullToAddressList( bool showFullToAddressList ) { mShowFullToAddressList = showFullToAddressList; } bool ViewerPrivate::showFullCcAddressList() const { return mShowFullCcAddressList; } void ViewerPrivate::setShowFullCcAddressList( bool showFullCcAddressList ) { mShowFullCcAddressList = showFullCcAddressList; } void ViewerPrivate::toggleFullAddressList() { toggleFullAddressList( QLatin1String("To") ); toggleFullAddressList( QLatin1String("Cc") ); } QString ViewerPrivate::recipientsQuickListLinkHtml( bool doShow, const QString & field ) const { QString imgpath( KStandardDirs::locate( "data",QLatin1String("libmessageviewer/pics/") ) ); QString urlHandle; QString imgSrc; QString altText; if ( doShow ) { urlHandle.append( QLatin1String("kmail:hideFull") + field + QLatin1String("AddressList")); imgSrc.append( QLatin1String("quicklistOpened.png") ); altText = i18n("Hide full address list"); } else { urlHandle.append( QLatin1String("kmail:showFull") + field + QLatin1String("AddressList") ); imgSrc.append( QLatin1String("quicklistClosed.png") ); altText = i18n("Show full address list"); } return QLatin1String(""); } void ViewerPrivate::toggleFullAddressList( const QString &field ) { const bool doShow = ( field == QLatin1String( "To" ) && showFullToAddressList() ) || ( field == QLatin1String( "Cc" ) && showFullCcAddressList() ); // First inject the correct icon if ( mViewer->replaceInnerHtml( QLatin1String("iconFull") + field + QLatin1String("AddressList"), bind( &ViewerPrivate::recipientsQuickListLinkHtml, this, doShow, field ) ) ) { // Then show/hide the full address list mViewer->setElementByIdVisible( QLatin1String("dotsFull") + field + QLatin1String("AddressList"), !doShow ); mViewer->setElementByIdVisible( QLatin1String("hiddenFull") + field + QLatin1String("AddressList"), doShow ); } } void ViewerPrivate::itemFetchResult( KJob* job ) { if ( job->error() ) { displaySplashPage( i18n( "Message loading failed: %1.", job->errorText() ) ); } else { Akonadi::ItemFetchJob* fetch = qobject_cast( job ); Q_ASSERT( fetch ); if ( fetch->items().isEmpty() ) { displaySplashPage( i18n( "Message not found." ) ); } else { setMessageItem( fetch->items().first() ); } } } void ViewerPrivate::slotItemChanged( const Akonadi::Item &item, const QSet & parts ) { if ( item.id() != messageItem().id() ) { kDebug() << "Update for an already forgotten item. Weird."; return; } if( parts.contains( "PLD:RFC822" ) ) setMessageItem( item, Viewer::Force ); } void ViewerPrivate::slotItemMoved( const Akonadi::Item &item, const Akonadi::Collection&, const Akonadi::Collection& ) { // clear the view after the current item has been moved somewhere else (e.g. to trash) if ( item.id() == messageItem().id() ) slotClear(); } void ViewerPrivate::slotClear() { q->clear( Viewer::Force ); emit itemRemoved(); } void ViewerPrivate::slotMessageRendered() { if ( !mMessageItem.isValid() ) { return; } /** * This slot might be called multiple times for the same message if * some asynchronous mementos are involved in rendering. Therefor we * have to make sure we execute the MessageLoadedHandlers only once. */ if ( mMessageItem.id() == mPreviouslyViewedItem ) return; mPreviouslyViewedItem = mMessageItem.id(); foreach ( AbstractMessageLoadedHandler *handler, mMessageLoadedHandlers ) handler->setItem( mMessageItem ); } void ViewerPrivate::setZoomFactor( qreal zoomFactor ) { #ifndef KDEPIM_NO_WEBKIT mViewer->setZoomFactor ( zoomFactor ); #endif } void ViewerPrivate::slotZoomIn() { #ifndef KDEPIM_NO_WEBKIT if( mZoomFactor >= 300 ) return; mZoomFactor += zoomBy; if( mZoomFactor > 300 ) mZoomFactor = 300; mViewer->setZoomFactor( mZoomFactor/100.0 ); #endif } void ViewerPrivate::slotZoomOut() { #ifndef KDEPIM_NO_WEBKIT if ( mZoomFactor <= 10 ) return; mZoomFactor -= zoomBy; if( mZoomFactor < 10 ) mZoomFactor = 10; mViewer->setZoomFactor( mZoomFactor/100.0 ); #endif } void ViewerPrivate::setZoomTextOnly( bool textOnly ) { mZoomTextOnly = textOnly; if ( mZoomTextOnlyAction ) { mZoomTextOnlyAction->setChecked( mZoomTextOnly ); } #ifndef KDEPIM_NO_WEBKIT mViewer->settings()->setAttribute(QWebSettings::ZoomTextOnly, mZoomTextOnly); #endif } void ViewerPrivate::slotZoomTextOnly() { setZoomTextOnly( !mZoomTextOnly ); } void ViewerPrivate::slotZoomReset() { #ifndef KDEPIM_NO_WEBKIT mZoomFactor = 100; mViewer->setZoomFactor( 1.0 ); #endif } void ViewerPrivate::goOnline() { emit makeResourceOnline(Viewer::AllResources); } void ViewerPrivate::goResourceOnline() { emit makeResourceOnline(Viewer::SelectedResource); } void ViewerPrivate::slotToggleCaretBrowsing(bool toggle) { #ifndef KDEPIM_NO_WEBKIT #if QTWEBKIT_VERSION >= QTWEBKIT_VERSION_CHECK(2, 3, 0) if( toggle ) { KMessageBox::information( mMainWindow, i18n("Caret Browsing will be activated. Switch off with F7 shortcut."), i18n("Activate Caret Browsing") ); } mViewer->settings()->setAttribute(QWebSettings::CaretBrowsingEnabled, toggle); #endif #endif Q_UNUSED( toggle ); } void ViewerPrivate::slotSaveMessageDisplayFormat() { if (mMessageItem.isValid()) { MessageViewer::MessageDisplayFormatAttribute *attr = mMessageItem.attribute( Akonadi::Entity::AddIfMissing ); attr->setRemoteContent(htmlLoadExtOverride()); if (htmlOverride()) attr->setMessageFormat(Viewer::Html); else attr->setMessageFormat(Viewer::Text); Akonadi::ItemModifyJob *modify = new Akonadi::ItemModifyJob( mMessageItem ); modify->setIgnorePayload( true ); modify->disableRevisionCheck(); connect( modify, SIGNAL(result(KJob*)), this, SLOT(slotModifyItemDone(KJob*)) ); } } void ViewerPrivate::slotResetMessageDisplayFormat() { if (mMessageItem.isValid()) { if (mMessageItem.hasAttribute()) { mMessageItem.removeAttribute(); Akonadi::ItemModifyJob *modify = new Akonadi::ItemModifyJob( mMessageItem ); modify->setIgnorePayload( true ); modify->disableRevisionCheck(); connect( modify, SIGNAL(result(KJob*)), this, SLOT(slotModifyItemDone(KJob*)) ); } } } void ViewerPrivate::slotMessageMayBeAScam() { if (mMessageItem.isValid()) { if (mMessageItem.hasAttribute()) { const MessageViewer::ScamAttribute* const attr = mMessageItem.attribute(); if (attr && !attr->isAScam()) return; } if ( mMessageItem.hasPayload() ) { KMime::Message::Ptr message = mMessageItem.payload(); const QString email = QLatin1String(KPIMUtils::firstEmailAddress( message->from()->as7BitString(false) )); const QStringList lst = MessageViewer::GlobalSettings::self()->scamDetectionWhiteList(); if (lst.contains(email)) return; } } mScamDetectionWarning->slotShowWarning(); } void ViewerPrivate::slotMessageIsNotAScam() { if (mMessageItem.isValid()) { MessageViewer::ScamAttribute *attr = mMessageItem.attribute( Akonadi::Entity::AddIfMissing ); attr->setIsAScam(false); Akonadi::ItemModifyJob *modify = new Akonadi::ItemModifyJob( mMessageItem ); modify->setIgnorePayload( true ); modify->disableRevisionCheck(); connect( modify, SIGNAL(result(KJob*)), this, SLOT(slotModifyItemDone(KJob*)) ); } } void ViewerPrivate::slotModifyItemDone(KJob* job) { if ( job && job->error() ) { kWarning() << " Error trying to change attribute:" << job->errorText(); } } void ViewerPrivate::slotGrantleeThemesUpdated() { update( Viewer::Force ); } void ViewerPrivate::saveMainFrameScreenshotInFile(const QString &filename) { #ifndef KDEPIM_NO_WEBKIT mViewer->saveMainFrameScreenshotInFile(filename); #endif } void ViewerPrivate::slotAddToWhiteList() { if (mMessageItem.isValid()) { if ( mMessageItem.hasPayload() ) { KMime::Message::Ptr message = mMessageItem.payload(); const QString email = QLatin1String(KPIMUtils::firstEmailAddress( message->from()->as7BitString(false)) ); QStringList lst = MessageViewer::GlobalSettings::self()->scamDetectionWhiteList(); if (lst.contains(email)) return; lst << email; MessageViewer::GlobalSettings::self()->setScamDetectionWhiteList( lst ); MessageViewer::GlobalSettings::self()->writeConfig(); } } } void ViewerPrivate::slotBlockImage() { if (mImageUrl.isEmpty()) return; #ifndef KDEPIM_NO_WEBKIT MessageViewer::AdBlockManager::self()->addCustomRule(mImageUrl.url(), true); #endif } void ViewerPrivate::slotOpenBlockableItems() { #ifndef KDEPIM_NO_WEBKIT mViewer->openBlockableItemsDialog(); #endif } bool ViewerPrivate::isAShortUrl(const KUrl &url) const { return mViewer->isAShortUrl(url); } void ViewerPrivate::slotExpandShortUrl() { if (mClickedUrl.isValid()) { mViewer->expandUrl(mClickedUrl); } } void ViewerPrivate::slotShowCreateTodoWidget() { if (mMessage) { mCreateTodo->setMessage(mMessage); mCreateTodo->showToDoWidget(); } else { qDebug()<<" There is not valid message"; } } void ViewerPrivate::slotCreateTodo(const KCalCore::Todo::Ptr &todoPtr, const Akonadi::Collection &collection) { CreateTodoJob *createJob = new CreateTodoJob(todoPtr, collection, mMessageItem, this); createJob->start(); } void ViewerPrivate::slotShowCreateEventWidget() { if (mMessage) { mCreateEvent->setMessage(mMessage); mCreateEvent->showEventEdit(); } else { qDebug()<<" There is not valid message"; } } void ViewerPrivate::slotCreateEvent(const KCalCore::Event::Ptr &eventPtr, const Akonadi::Collection &collection) { CreateEventJob *createJob = new CreateEventJob(eventPtr, collection, mMessageItem, this); createJob->start(); } -void ViewerPrivate::slotShowCreateNoteWidget() +Akonadi::Relation ViewerPrivate::relatedNoteRelation() { - if (!mMessageItem.relations().isEmpty()) { - Akonadi::Relation relation; - foreach (const Akonadi::Relation &r, mMessageItem.relations()) { - // assuming that GENERIC relations to emails are notes is a pretty horirific hack imo - aseigo - if (r.type() == Akonadi::Relation::GENERIC/* && r.right().mimeType() == Akonadi::NoteUtils::noteMimeType(*/) { - relation = r; - break; - } + Akonadi::Relation relation; + foreach (const Akonadi::Relation &r, mMessageItem.relations()) { + // assuming that GENERIC relations to emails are notes is a pretty horirific hack imo - aseigo + if (r.type() == Akonadi::Relation::GENERIC && r.right().mimeType() == Akonadi::NoteUtils::noteMimeType() ) { + relation = r; + break; } + } + return relation; +} + +void ViewerPrivate::slotShowCreateNoteWidget() +{ + if (!mMessageItem.relations().isEmpty()) { + Akonadi::Relation relation = relatedNoteRelation(); if (relation.isValid()) { Akonadi::ItemFetchJob* job = new Akonadi::ItemFetchJob(relation.right()); job->fetchScope().fetchFullPayload(true); connect(job, SIGNAL(result(KJob*)), this, SLOT(slotNoteItemFetched(KJob*))); return; } } showCreateNewNoteWidget(); } void ViewerPrivate::showCreateNewNoteWidget() { if (mMessage) { mCreateNote->setMessage(mMessage); mCreateNote->showNoteEdit(); } else { qDebug() << "There is not valid message"; } } void ViewerPrivate::slotNoteItemFetched(KJob *job) { if (job->error()) { showCreateNewNoteWidget(); } else { Akonadi::ItemFetchJob *fetch = qobject_cast( job ); Q_ASSERT( fetch ); if (fetch->items().isEmpty() || !fetch->items().first().hasPayload()) { showCreateNewNoteWidget(); } else { Akonadi::NoteUtils::NoteMessageWrapper note(fetch->items().first().payload()); mCreateNote->setMessage(note.message()); mCreateNote->showNoteEdit(); } } } void ViewerPrivate::slotCreateNote(const KMime::Message::Ptr ¬ePtr, const Akonadi::Collection &collection) { CreateNoteJob *createJob = new CreateNoteJob(notePtr, collection, mMessageItem, this); createJob->start(); } diff --git a/messageviewer/viewer/viewer_p.h b/messageviewer/viewer/viewer_p.h index 3797363c69..7a334f6f31 100644 --- a/messageviewer/viewer/viewer_p.h +++ b/messageviewer/viewer/viewer_p.h @@ -1,749 +1,751 @@ /* -*- mode: C++; c-file-style: "gnu" -*- Copyright (c) 1997 Markus Wuebben Copyright (C) 2009 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Copyright (c) 2009 Andras Mantia 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. */ #ifndef MAILVIEWER_P_H #define MAILVIEWER_P_H #include "viewer/nodehelper.h" #include "viewer.h" //not so nice, it is actually for the enums from MailViewer #include #include #include #include #include #include #include #include #include #include #include #include #include namespace GpgME { class Error; } namespace KIO { class Job; } namespace Kleo { class SpecialJob; } class KAction; class KActionCollection; class KSelectAction; class KToggleAction; class KHBox; class KMenu; class QPoint; class QSplitter; class QModelIndex; class QTreeView; namespace MessageViewer { class TodoEdit; class EventEdit; class NoteEdit; class EditorWatcher; class HtmlWriter; class CSSHelper; class AttachmentStrategy; class ObjectTreeParser; class HeaderStrategy; class HeaderStyle; class FindBarMailWebView; class MimeTreeModel; class WebKitPartHtmlWriter; class HtmlStatusBar; class MailWebView; class ScamDetectionWarningWidget; } namespace GrantleeTheme { class GrantleeThemeManager; } namespace PimCommon { class TranslatorWidget; } namespace MessageViewer { /** \brief Private class for the Viewer, the main widget in the messageviewer library. This class creates all subwidgets, like the MailWebView, the HtmlStatusBar and the FindBarMailWebView. Also, ViewerPrivate creates and exposes all actions. \par Displaying a message Before displaying a message, a message needs to be set. This can be done in two ways, with setMessageItem() and with setMessage(). setMessageItem() is the preferred way, as the viewer can then remember the Akonadi::Item belonging to the message. The Akonadi::Item is needed when modifying the message, for example when editing or deleting an attachment. Sometimes passing an Akonadi::Item to the viewer is not possible, for example when double-clicking an attached message, in which case a new KMime::Message is constructed out of the attachment, and a separate window is opened for it. In this case, the KMime::Message has no associated Akonadi::Item. If there is an Akonadi::Item available, it will be monitored for changes and the viewer automatically updated on external changes. Once a message is set, update() is called. update() can also be called after the message has already been displayed. As an example, this is the case when the user decides to decrypt the message. The decryption can happen async, and once the decryption is finished, update() is called to display the now decrypted content. See the documentation of ObjectTreeParser on how exactly decryption is handled. update() is just a thin wrapper that calls updateReaderWin(). The only difference is that update() has a timer that prevents too many slow calls to updateReaderWin() in a short time frame. updateReaderWin() again is only a thin wrapper that resets some state and then calls displayMessage(). displayMessage() itself is again a thin wrapper, which starts the HtmlWriter and then calls parseMsg(). Finally, parseMsg() does the real work. It uses ObjectTreeParser::parseObjectTree() to let the ObjectTreeParser parse the message and generate the HTML code for it. As mentioned before, it can happen that the ObjectTreeParser needs to do some operation that happens async, for example decrypting. In this case, the ObjectTreeParser will create a BodyPartMemento, which basically is a wrapper around the job that does the async operation. Once the async operation is finished. the BodyPartMemento will trigger an update() of ViewerPrivate, so that ObjectTreeParser::parseObjectTree() gets called again and the ObjectTreeParser then can generate HTML which has the decrypted content of the message. Again, see the documentation of ObjectTreeParser for the details. Additionally, parseMsg() does some evil hack for saving unencrypted messages should the config option for that be set. \par Displaying a MIME part of the message The viewer can show only a part of the message, for example by clicking on a MIME part in the message structure viewer or by double-clicking an attached message. In this case, setMessagePart() is called. There are two of these functions. One even has special handling for images, special handling for binary attachments and special handling of attached messages. In the last case, a new KMime::Message is constructed and set as the main message with setMessage(). \par Attachment Handling Some of those actions are actions that operate on a single attachment. For those, there is usually a slot, like slotAttachmentCopy(). These actions are triggered from the attachment context menu, which is shown in showAttachmentPopup(). The actions are connected to slotHandleAttachment() when they are activated. The action to edit an attachment uses the EditorWatcher to detect when editing with an external editor is finished. Upon finishing, slotAttachmentEditDone() is called, which then creates an ItemModifyJob to store the changes of the attachment. A map of currently active EditorWatcher and their KMime::Content is available in mEditorWatchers. For most attachment actions, the attachment is first written to a temp file. The action is then executed on this temp file. Writing the attachment to a temp file is done with NodeHelper::writeNodeToTempFile(). This method is called before opening or copying an attachment or when rendering the attachment list. The ObjectTreeParser also calls NodeHelper::writeNodeToTempFile() in some places. Once the temp file is written, NodeHelper::tempFileUrlFromNode() can be used to get the file name of the temp file for a specific MIME part. This is for example used by the handler for 'attachment:' URLs, AttachmentURLHandler. Since URLs for attachments are in the "attachment:" scheme, dragging them as-is to outside applications wouldn't work, since other applications don't understand this scheme. Therefore, the viewer has special handling for dragging URLs: In eventFilter(), drags are detected, and the URL handler is called to deal with the drag. The attachment URL handler then starts a drag with the file:// URL of the temp file of the attachment, which it gets with NodeHelper::tempFileUrlFromNode(). TODO: How are attachment handled that are loaded on demand? How does prepareHandleAttachment() work? TODO: This temp file handling is a big mess and could use a rewrite, especially in the face of load on demand. There shouldn't be the need to write out tempfiles until really needed. Some header styles display an attachment list in the header. The HTML code for the attachment list cannot be generated by the HeaderStyle itself, since that does not know about all attachments. Therefore, the attachment list needs to be created by ViewerPrivate. For this, the HeaderStyle writes out a placeholder for the attachment list when it creates the HTML for the header. Once the ObjectTreeParser is finished with the message, injectAttachments() is called. injectAttachments() searches for the placeholder and replaces that with the real HTML code for the attachments. One of the attachment actions is to scoll to the attachment. That action is only available when right-clicking the header. The action scrolls to the attachment in the body and draws a yellow frame around the attachment. This is done in scrollToAttachment(). The attachment in the body and the div which is used for the colored frame are both created by the ObjectTreeParser. \par Misc ViewerPrivate holds the NodeHelper, which is passed on to the ObjectTreeParser when it needs it. It also holds the HeaderStyle, HeaderStrategy, AttachmentStrategy, CSSHelper, HtmlWriter and more, some of them again passed to the ObjectTreeParser when it needs it. @author andras@kdab.net */ class ViewerPrivate : public QObject { Q_OBJECT public: ViewerPrivate(Viewer *aParent, QWidget *mainWindow, KActionCollection *actionCollection ); virtual ~ViewerPrivate(); /** Returns message part from given URL or null if invalid. The URL's path is a KMime::ContentIndex path, or an index for the extra nodes, followed by : and the ContentIndex path. */ KMime::Content* nodeFromUrl(const KUrl &url); /** Open the attachment pointed to the node. * @param fileName - if not empty, use this file to load the attachment content */ void openAttachment( KMime::Content *node, const QString & fileName ); /** Delete the attachment the @param node points to. Returns false if the user cancelled the deletion, true in all other cases (including failure to delete the attachment!) */ bool deleteAttachment( KMime::Content* node, bool showWarning = true ); void attachmentProperties( KMime::Content *node ); void attachmentCopy( const KMime::Content::List & contents ); /** Edit the attachment the @param node points to. Returns false if the user cancelled the editing, true in all other cases! */ bool editAttachment( KMime::Content* node, bool showWarning = true ); /** Access to the MailWebView used for the viewer. Use with care! */ MailWebView *htmlPart() const { return mViewer; } void showAttachmentPopup( KMime::Content* node, const QString & name, const QPoint & p ); /** * Sets the current attachment ID and the current attachment temporary filename * to the given values. * Call this so that slotHandleAttachment() knows which attachment to handle. */ void prepareHandleAttachment(KMime::Content *node, const QString& fileName ); void postProcessMessage( ObjectTreeParser *otp, KMMsgEncryptionState encryptionState ); QString createAtmFileLink( const QString& atmFileName ) const; KService::Ptr getServiceOffer( KMime::Content *content); KMime::Content::List selectedContents(); void attachmentOpenWith( KMime::Content *node, KService::Ptr offer = KService::Ptr() ); void attachmentOpen( KMime::Content *node ); /** Return the HtmlWriter connected to the MailWebView we use */ HtmlWriter * htmlWriter() const { return mHtmlWriter; } CSSHelper* cssHelper() const; NodeHelper* nodeHelper() const { return mNodeHelper; } Viewer *viewer() const { return q; } Akonadi::Item messageItem() const { return mMessageItem; } KMime::Message::Ptr message() const { return mMessage; } /** Returns whether the message should be decryted. */ bool decryptMessage() const; /** Calculate the pixel size */ int pointsToPixel(int pointSize) const; /** Display a generic HTML splash page instead of a message. * @param info - the text to be displayed in HTML format */ void displaySplashPage( const QString &info ); /** Enable the displaying of messages again after an splash (or other) page was displayed */ void enableMessageDisplay(); /** Feeds the HTML viewer with the contents of the given message. HTML begin/end parts are written around the message. */ void displayMessage(); /** Parse the given content and generate HTML out of it for display */ void parseContent( KMime::Content *content ); /** Creates a nice mail header depending on the current selected header style. */ QString writeMsgHeader( KMime::Message *aMsg, KMime::Content* vCardNode = 0, bool topLevel = false ); /** show window containing information about a vCard. */ void showVCard(KMime::Content *msgPart); void setZoomTextOnly( bool textOnly ); void saveMainFrameScreenshotInFile(const QString &filename); private: /** HTML initialization. */ void initHtmlWidget(); void saveMimePartTreeConfig(); void restoreMimePartTreeConfig(); void createOpenWithMenu( KMenu *topMenu, const QString &contentTypeStr, bool fromCurrentContent ); public: /** Event filter */ bool eventFilter( QObject *obj, QEvent *ev ); /** Read settings from app's config file. */ void readConfig(); /** Write settings to app's config file. Calls sync() if withSync is true. */ void writeConfig( bool withSync=true ); /** Get the message header style. */ HeaderStyle * headerStyle() const { return mHeaderStyle; } /** Set the header style and strategy. We only want them to be set together. */ void setHeaderStyleAndStrategy( HeaderStyle * style, HeaderStrategy * strategy, bool writeInConfigFile = false ); /** Get the message header strategy. */ HeaderStrategy * headerStrategy() const { return mHeaderStrategy; } /** Get/set the message attachment strategy. */ const AttachmentStrategy * attachmentStrategy() const { return mAttachmentStrategy; } void setAttachmentStrategy( const AttachmentStrategy * strategy ); /** Get selected override character encoding. @return The encoding selected by the user or an empty string if auto-detection is selected. */ QString overrideEncoding() const { return mOverrideEncoding; } /** Set the override character encoding. */ void setOverrideEncoding( const QString & encoding ); /** Set printing mode */ virtual void setPrinting(bool enable) { mPrinting = enable; } /** Print message. */ void printMessage( const Akonadi::Item &msg ); void printPreviewMessage( const Akonadi::Item &message ); void resetStateForNewMessage(); void setMessageInternal( const KMime::Message::Ptr message, Viewer::UpdateMode updateMode ); /** Set the Akonadi item that will be displayed. * @param item - the Akonadi item to be displayed. If it doesn't hold a mail (KMime::Message::Ptr as payload data), * an empty page is shown. * @param updateMode - update the display immediately or not. See MailViewer::UpdateMode. */ void setMessageItem(const Akonadi::Item& item, Viewer::UpdateMode updateMode = Viewer::Delayed ); /** Set the message that shall be shown. * @param msg - the message to be shown. If 0, an empty page is displayed. * @param updateMode - update the display immediately or not. See MailViewer::UpdateMode. */ void setMessage( const KMime::Message::Ptr& msg, Viewer::UpdateMode updateMode = Viewer::Delayed ); /** Instead of settings a message to be shown sets a message part to be shown */ void setMessagePart( KMime::Content * node ); /** Show or hide the Mime Tree Viewer if configuration is set to smart mode. */ void showHideMimeTree(); /** View message part of type message/RFC822 in extra viewer window. */ void atmViewMsg( KMime::Message::Ptr message ); void setExternalWindow( bool b ); void adjustLayout(); void createWidgets(); void createActions(); void showContextMenu( KMime::Content* content, const QPoint& point); KToggleAction * actionForHeaderStyle( const HeaderStyle *, const HeaderStrategy * ); KToggleAction * actionForAttachmentStrategy( const AttachmentStrategy * ); /** Read override codec from configuration */ void readGlobalOverrideCodec(); /** Get codec corresponding to the currently selected override character encoding. @return The override codec or 0 if auto-detection is selected. */ const QTextCodec * overrideCodec() const; QString renderAttachments( KMime::Content *node, const QColor &bgColor ) const; KMime::Content* findContentByType(KMime::Content *content, const QByteArray &type); //TODO(Andras) move to NodeHelper /** Return a QTextCodec for the specified charset. * This function is a bit more tolerant, than QTextCodec::codecForName */ static const QTextCodec* codecForName(const QByteArray& _str); //TODO(Andras) move to a utility class? /** Saves the relative position of the scroll view. Call this before calling update() if you want to preserve the current view. */ void saveRelativePosition(); bool htmlMail() const; bool htmlLoadExternal() const; /** Get the html override setting */ bool htmlOverride() const; /** Override default html mail setting */ void setHtmlOverride( bool override ); /** Get the load external references override setting */ bool htmlLoadExtOverride() const; /** Override default load external references setting */ void setHtmlLoadExtOverride( bool override ); /** Enforce message decryption. */ void setDecryptMessageOverwrite( bool overwrite = true ); /** Show signature details. */ bool showSignatureDetails() const; /** Show signature details. */ void setShowSignatureDetails( bool showDetails = true ) ; /* show or hide the list that points to the attachments */ bool showAttachmentQuicklist() const; /* show or hide the list that points to the attachments */ void setShowAttachmentQuicklist( bool showAttachmentQuicklist = true ); // This controls whether a Toltec invitation is shown in its raw form or as a replacement text. // This can be toggled with the "kmail:showRawToltecMail" link. bool showRawToltecMail() const { return mShowRawToltecMail; } void setShowRawToltecMail( bool showRawToltecMail ) { mShowRawToltecMail = showRawToltecMail; } void scrollToAttachment( KMime::Content *node ); void setUseFixedFont( bool useFixedFont ); void attachmentView( KMime::Content *atmNode ); void attachmentEncryptWithChiasmus( KMime::Content * content ); /** Return weather to show or hide the full list of "To" addresses */ bool showFullToAddressList() const; /** Show or hide the full list of "To" addresses */ void setShowFullToAddressList( bool showFullToAddressList = true ); /** Return weather to show or hide the full list of "To" addresses */ bool showFullCcAddressList() const; /** Show or hide the full list of "To" addresses */ void setShowFullCcAddressList( bool showFullCcAddressList = true ); /** Show/Hide the field with id "field" */ void toggleFullAddressList(const QString& field); void setZoomFactor( qreal zoomFactor ); void goOnline(); void goResourceOnline(); bool isAShortUrl(const KUrl &url) const; private slots: void slotModifyItemDone(KJob* job); void slotMessageMayBeAScam(); void slotMessageIsNotAScam(); void slotAddToWhiteList(); void slotToggleCaretBrowsing(bool); void slotAtmDecryptWithChiasmusResult( const GpgME::Error &, const QVariant & ); void slotAtmDecryptWithChiasmusUploadResult( KJob * ); /** Show hide all fields specified inside this function */ void toggleFullAddressList(); void itemFetchResult( KJob *job ); void slotItemChanged( const Akonadi::Item& item, const QSet& partIdentifiers ); void slotItemMoved( const Akonadi::Item&, const Akonadi::Collection&, const Akonadi::Collection& ); void itemModifiedResult( KJob* job ); void collectionFetchedForStoringDecryptedMessage( KJob* job ); void slotMimePartDestroyed(); void slotClear(); void slotMessageRendered(); void slotOpenWithAction(QAction *act); void slotOpenWithActionCurrentContent(QAction* act); void slotOpenWithDialog(); void slotOpenWithDialogCurrentContent(); void saveSplitterSizes() const; void slotGrantleeThemesUpdated(); void slotCreateTodo(const KCalCore::Todo::Ptr &, const Akonadi::Collection &collection); void slotCreateEvent(const KCalCore::Event::Ptr &eventPtr, const Akonadi::Collection &collection); void slotCreateNote(const KMime::Message::Ptr ¬ePtr, const Akonadi::Collection &collection); public slots: /** An URL has been activate with a click. */ void slotUrlOpen( const QUrl &url = QUrl()); /** The mouse has moved on or off an URL. */ void slotUrlOn(const QString & link, const QString & title, const QString & textContent); /** The user presses the right mouse button on an URL. */ void slotUrlPopup(const QUrl &, const QUrl &imageUrl, const QPoint& mousePos); /** The user selected "Find" from the menu. */ void slotFind(); void slotTranslate(); /** The user toggled the "Fixed Font" flag from the view menu. */ void slotToggleFixedFont(); void slotToggleMimePartTree(); /** Show the message source */ void slotShowMessageSource(); /** Refresh the reader window */ void updateReaderWin(); void slotMimePartSelected( const QModelIndex &index ); void slotBriefHeaders(); void slotFancyHeaders(); void slotEnterpriseHeaders(); void slotStandardHeaders(); void slotLongHeaders(); void slotAllHeaders(); void slotCustomHeaders(); void slotGrantleeHeaders(); void slotIconicAttachments(); void slotSmartAttachments(); void slotInlineAttachments(); void slotHideAttachments(); void slotHeaderOnlyAttachments(); /** Some attachment operations. */ void slotDelayedResize(); /** Print message. Called on as a response of finished() signal of mPartHtmlWriter after rendering is finished. In the very end it deletes the KMReaderWin window that was created for the purpose of rendering. */ void slotPrintMsg(); void slotPrintPreview(); void slotSetEncoding(); void injectAttachments(); void slotSettingsChanged(); void slotMimeTreeContextMenuRequested( const QPoint& pos ); void slotAttachmentOpenWith(); void slotAttachmentOpen(); void slotAttachmentSaveAs(); void slotAttachmentSaveAll(); void slotAttachmentView(); void slotAttachmentProperties(); void slotAttachmentCopy(); void slotAttachmentDelete(); void slotAttachmentEdit(); void slotAttachmentEditDone(EditorWatcher* editorWatcher); void slotLevelQuote( int l ); /** Toggle display mode between HTML and plain text. */ void slotToggleHtmlMode(); void slotZoomTextOnly(); void slotLoadExternalReference(); /** * Does an action for the current attachment. * The action is defined by the KMHandleAttachmentCommand::AttachmentAction * enum. * prepareHandleAttachment() needs to be called before calling this to set the * correct attachment ID. */ void slotHandleAttachment( int action ); /** Copy the selected text to the clipboard */ void slotCopySelectedText(); void viewerSelectionChanged(); /** Select message body. */ void selectAll(); void clearSelection(); /** Copy URL in mUrlCurrent to clipboard. Removes "mailto:" at beginning of URL before copying. */ void slotUrlCopy(); void slotSaveMessage(); /** Re-parse the current message. */ void update(MessageViewer::Viewer::UpdateMode updateMode = Viewer::Delayed); void slotZoomIn(); void slotZoomOut(); void slotZoomReset(); void slotSpeakText(); void slotCopyImageLocation(); void slotSaveMessageDisplayFormat(); void slotResetMessageDisplayFormat(); void slotBlockImage(); void slotOpenBlockableItems(); void slotExpandShortUrl(); void slotShowCreateTodoWidget(); void slotShowCreateEventWidget(); void slotShowCreateNoteWidget(); void slotNoteItemFetched(KJob *job); signals: void showStatusBarMessage( const QString &message ); void replaceMsgByUnencryptedVersion(); void popupMenu(const Akonadi::Item &msg, const KUrl &url, const KUrl &imageUrl, const QPoint& mousePos); void urlClicked( const Akonadi::Item &msg, const KUrl &url ); void requestConfigSync(); void showReader( KMime::Content* aMsgPart, bool aHTML, const QString & encoding ); void showMessage( KMime::Message::Ptr message, const QString& encoding ); void itemRemoved(); void makeResourceOnline(MessageViewer::Viewer::ResourceOnlineMode mode); void changeDisplayMail(Viewer::ForceDisplayTo,bool); void moveMessageToTrash(); private: void showCreateNewNoteWidget(); QString attachmentInjectionHtml() const; QString recipientsQuickListLinkHtml( bool, const QString & ) const; void initGrantleeThemeName(); + Akonadi::Relation relatedNoteRelation(); + public: NodeHelper* mNodeHelper; bool mHtmlMail, mHtmlLoadExternal, mHtmlOverride, mHtmlLoadExtOverride; KMime::Message::Ptr mMessage; //the current message, if it was set manually Akonadi::Item mMessageItem; //the message item from Akonadi // widgets: QSplitter * mSplitter; KHBox *mBox; HtmlStatusBar *mColorBar; #ifndef QT_NO_TREEVIEW QTreeView* mMimePartTree; //FIXME(Andras) port the functionality from KMMimePartTree to a new view class or to here with signals/slots #endif MimeTreeModel *mMimePartModel; MailWebView *mViewer; FindBarMailWebView *mFindBar; PimCommon::TranslatorWidget *mTranslatorWidget; const AttachmentStrategy * mAttachmentStrategy; HeaderStrategy * mHeaderStrategy; HeaderStyle * mHeaderStyle; static const int delay; QTimer mUpdateReaderWinTimer; QTimer mResizeTimer; QString mOverrideEncoding; QString mOldGlobalOverrideEncoding; // used to detect changes of the global override character encoding /// This is true if the viewer currently is displaying a message. Can be false, for example when /// the splash/busy page is displayed. bool mMsgDisplay; CSSHelper * mCSSHelper; bool mUseFixedFont; bool mPrinting; QString mIdOfLastViewedMessage; QWidget *mMainWindow; KActionCollection *mActionCollection; KAction *mCopyAction, *mCopyURLAction, *mUrlOpenAction, *mSelectAllAction, *mScrollUpAction, *mScrollDownAction, *mScrollUpMoreAction, *mScrollDownMoreAction, *mViewSourceAction, *mSaveMessageAction, *mFindInMessageAction, *mSaveMessageDisplayFormat, *mResetMessageDisplayFormat; KToggleAction *mHeaderOnlyAttachmentsAction; KSelectAction *mSelectEncodingAction; KToggleAction *mToggleFixFontAction, *mToggleDisplayModeAction; #ifndef KDEPIM_NO_WEBKIT #if QTWEBKIT_VERSION >= QTWEBKIT_VERSION_CHECK(2, 3, 0) KToggleAction *mCaretBrowsing; #endif #endif KAction *mZoomTextOnlyAction, *mZoomInAction, *mZoomOutAction, *mZoomResetAction; KToggleAction *mToggleMimePartTreeAction; KAction *mSpeakTextAction; KAction *mCopyImageLocation; KAction *mTranslateAction; KAction *mBlockImage; KAction *mBlockableItems; KAction *mExpandUrlAction; KAction *mCreateTodoAction; KAction *mCreateEventAction; KAction *mCreateNoteAction; KUrl mHoveredUrl; KUrl mClickedUrl; KUrl mImageUrl; QPoint mLastClickPosition; bool mCanStartDrag; HtmlWriter * mHtmlWriter; /** Used only to be able to connect and disconnect finished() signal in printMsg() and slotPrintMsg() since mHtmlWriter points only to abstract non-QObject class. */ QPointer mPartHtmlWriter; float mSavedRelativePosition; int mLevelQuote; bool mDecrytMessageOverwrite; bool mShowSignatureDetails; bool mShowAttachmentQuicklist; bool mShowRawToltecMail; bool mExternalWindow; bool mZoomTextOnly; int mRecursionCountForDisplayMessage; KMime::Content *mCurrentContent; KMime::Content *mMessagePartNode; QString mCurrentFileName; QString mMessagePath; QMap mEditorWatchers; Kleo::SpecialJob *mJob; Viewer *const q; bool mShowFullToAddressList; bool mShowFullCcAddressList; Akonadi::Monitor mMonitor; QString mAppName; QSet mMessageLoadedHandlers; Akonadi::Item::Id mPreviouslyViewedItem; GrantleeTheme::GrantleeThemeManager *mThemeManager; ScamDetectionWarningWidget *mScamDetectionWarning; MessageViewer::TodoEdit *mCreateTodo; MessageViewer::EventEdit *mCreateEvent; MessageViewer::NoteEdit *mCreateNote; // zoom Factor static const qreal zoomBy; qreal mZoomFactor; }; } #endif