kde-playground/kdepim/messagelist/storagemodel.cpp
2015-04-14 21:49:29 +00:00

486 lines
16 KiB
C++

/*
Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
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 "storagemodel.h"
#include <messagecore/utils/stringutil.h>
#include <messagecore/settings/globalsettings.h>
#include <messagecore/helpers/nodehelper.h>
#include <akonadi/attributefactory.h>
#include <akonadi/collection.h>
#include <akonadi/collectionstatistics.h>
#include <akonadi/entitymimetypefiltermodel.h>
#include <akonadi/entitytreemodel.h>
#include <akonadi/item.h>
#include <akonadi/itemmodifyjob.h>
#include <akonadi/kmime/messagefolderattribute.h>
#include <akonadi/selectionproxymodel.h>
#include <KDE/KLocale>
#include "core/messageitem.h"
#include "core/settings.h"
#include "messagelistutil.h"
#include <QtCore/QAbstractItemModel>
#include <QtCore/QAtomicInt>
#include <QtCore/QScopedPointer>
#include <QItemSelectionModel>
#include <QtCore/QMimeData>
#include <QtCore/QCryptographicHash>
namespace MessageList
{
class StorageModel::Private
{
public:
void onSourceDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight );
void onSelectionChanged();
void loadSettings();
StorageModel * const q;
QAbstractItemModel *mModel;
QAbstractItemModel *mChildrenFilterModel;
QItemSelectionModel *mSelectionModel;
Private( StorageModel *owner )
: q( owner ),
mModel(0),
mSelectionModel(0)
{}
};
} // namespace MessageList
using namespace Akonadi;
using namespace MessageList;
namespace {
KMime::Message::Ptr messageForItem( const Akonadi::Item &item )
{
if ( !item.hasPayload<KMime::Message::Ptr>() ) {
kWarning() << "Not a message" << item.id() << item.remoteId() << item.mimeType();
return KMime::Message::Ptr();
}
return item.payload<KMime::Message::Ptr>();
}
}
static QAtomicInt _k_attributeInitialized;
StorageModel::StorageModel( QAbstractItemModel *model, QItemSelectionModel *selectionModel, QObject *parent )
: Core::StorageModel( parent ), d( new Private( this ) )
{
d->mSelectionModel = selectionModel;
if ( _k_attributeInitialized.testAndSetAcquire( 0, 1 ) ) {
AttributeFactory::registerAttribute<MessageFolderAttribute>();
}
Akonadi::SelectionProxyModel *childrenFilter = new Akonadi::SelectionProxyModel( d->mSelectionModel, this );
childrenFilter->setSourceModel( model );
childrenFilter->setFilterBehavior( KSelectionProxyModel::ChildrenOfExactSelection );
d->mChildrenFilterModel = childrenFilter;
EntityMimeTypeFilterModel *itemFilter = new EntityMimeTypeFilterModel( this );
itemFilter->setSourceModel( childrenFilter );
itemFilter->addMimeTypeExclusionFilter( Collection::mimeType() );
itemFilter->addMimeTypeInclusionFilter( QLatin1String( "message/rfc822" ) );
itemFilter->setHeaderGroup( EntityTreeModel::ItemListHeaders );
d->mModel = itemFilter;
kDebug() << "Using model:" << model->metaObject()->className();
connect( d->mModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
this, SLOT(onSourceDataChanged(QModelIndex,QModelIndex)) );
connect( d->mModel, SIGNAL(layoutAboutToBeChanged()),
this, SIGNAL(layoutAboutToBeChanged()) );
connect( d->mModel, SIGNAL(layoutChanged()),
this, SIGNAL(layoutChanged()) );
connect( d->mModel, SIGNAL(modelAboutToBeReset()),
this, SIGNAL(modelAboutToBeReset()) );
connect( d->mModel, SIGNAL(modelReset()),
this, SIGNAL(modelReset()) );
//Here we assume we'll always get QModelIndex() in the parameters
connect( d->mModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
this, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)) );
connect( d->mModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SIGNAL(rowsInserted(QModelIndex,int,int)) );
connect( d->mModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
this, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)) );
connect( d->mModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
this, SIGNAL(rowsRemoved(QModelIndex,int,int)) );
connect( d->mSelectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
this, SLOT(onSelectionChanged()) );
d->loadSettings();
connect( Core::Settings::self(), SIGNAL(configChanged()),
this, SLOT(loadSettings()) );
}
StorageModel::~StorageModel()
{
delete d;
}
Collection::List StorageModel::displayedCollections() const
{
Collection::List collections;
QModelIndexList indexes = d->mSelectionModel->selectedRows();
foreach ( const QModelIndex &index, indexes ) {
Collection c = index.data( EntityTreeModel::CollectionRole ).value<Collection>();
if ( c.isValid() ) {
collections << c;
}
}
return collections;
}
QString StorageModel::id() const
{
QStringList ids;
QModelIndexList indexes = d->mSelectionModel->selectedRows();
foreach ( const QModelIndex &index, indexes ) {
Collection c = index.data( EntityTreeModel::CollectionRole ).value<Collection>();
if ( c.isValid() ) {
ids << QString::number( c.id() );
}
}
ids.sort();
return ids.join(QLatin1String( ":" ));
}
bool StorageModel::isOutBoundFolder( const Akonadi::Collection& c ) const
{
if ( c.hasAttribute<MessageFolderAttribute>()
&& c.attribute<MessageFolderAttribute>()->isOutboundFolder() ) {
return true;
}
return false;
}
bool StorageModel::containsOutboundMessages() const
{
QModelIndexList indexes = d->mSelectionModel->selectedRows();
foreach ( const QModelIndex &index, indexes ) {
Collection c = index.data( EntityTreeModel::CollectionRole ).value<Collection>();
if ( c.isValid() ) {
return isOutBoundFolder( c );
}
}
return false;
}
int StorageModel::initialUnreadRowCountGuess() const
{
QModelIndexList indexes = d->mSelectionModel->selectedRows();
int unreadCount = 0;
foreach ( const QModelIndex &index, indexes ) {
Collection c = index.data( EntityTreeModel::CollectionRole ).value<Collection>();
if ( c.isValid() ) {
unreadCount+= c.statistics().unreadCount();
}
}
return unreadCount;
}
bool StorageModel::initializeMessageItem( MessageList::Core::MessageItem *mi,
int row, bool bUseReceiver ) const
{
const Item item = itemForRow( row );
const KMime::Message::Ptr mail = messageForItem( item );
if ( !mail ) {
return false;
}
const Collection parentCol = parentCollectionForRow( row );
QString sender;
if ( mail->from() ) {
sender = mail->from()->asUnicodeString();
}
QString receiver;
if ( mail->to() ) {
receiver = mail->to()->asUnicodeString();
}
// Static for speed reasons
static const QString noSubject = i18nc( "displayed as subject when the subject of a mail is empty", "No Subject" );
static const QString unknown( i18nc( "displayed when a mail has unknown sender, receiver or date", "Unknown" ) );
if ( sender.isEmpty() ) {
sender = unknown;
}
if ( receiver.isEmpty() ) {
receiver = unknown;
}
mi->initialSetup( mail->date()->dateTime().toTime_t(),
item.size(),
sender, receiver,
bUseReceiver );
mi->setItemId( item.id() );
mi->setParentCollectionId( parentCol.id() );
QString subject = mail->subject()->asUnicodeString();
if ( subject.isEmpty() ) {
subject = QLatin1Char( '(' ) + noSubject + QLatin1Char( ')' );
}
mi->setSubject( subject );
updateMessageItemData( mi, row );
return true;
}
static QByteArray md5Encode( const QByteArray &str )
{
if ( str.trimmed().isEmpty() ) return QByteArray();
QCryptographicHash c( QCryptographicHash::Md5 );
c.addData( str.trimmed() );
return c.result();
}
void StorageModel::fillMessageItemThreadingData( MessageList::Core::MessageItem *mi,
int row, ThreadingDataSubset subset ) const
{
const KMime::Message::Ptr mail = messageForRow( row );
Q_ASSERT( mail ); // We ASSUME that initializeMessageItem has been called successfully...
switch ( subset ) {
case PerfectThreadingReferencesAndSubject:
{
const QString subject = mail->subject()->asUnicodeString();
const QString strippedSubject = MessageCore::StringUtil::stripOffPrefixes( subject );
mi->setStrippedSubjectMD5( md5Encode( strippedSubject.toUtf8() ) );
mi->setSubjectIsPrefixed( subject != strippedSubject );
// fall through
}
case PerfectThreadingPlusReferences:
if ( !mail->references()->identifiers().isEmpty() ) {
mi->setReferencesIdMD5( md5Encode( mail->references()->identifiers().last() ) );
}
// fall through
case PerfectThreadingOnly:
mi->setMessageIdMD5( md5Encode( mail->messageID()->identifier() ) );
if ( !mail->inReplyTo()->identifiers().isEmpty() ) {
mi->setInReplyToIdMD5( md5Encode( mail->inReplyTo()->identifiers().first() ) );
}
break;
default:
Q_ASSERT( false ); // should never happen
break;
}
}
void StorageModel::updateMessageItemData( MessageList::Core::MessageItem *mi,
int row ) const
{
const Item item = itemForRow( row );
Akonadi::MessageStatus stat;
stat.setStatusFromFlags( item.flags() );
mi->setAkonadiItem( item );
mi->setStatus( stat );
if ( stat.isEncrypted() )
mi->setEncryptionState( Core::MessageItem::FullyEncrypted );
else
mi->setEncryptionState( Core::MessageItem::EncryptionStateUnknown );
if ( stat.isSigned() )
mi->setSignatureState( Core::MessageItem::FullySigned );
else
mi->setSignatureState( Core::MessageItem::SignatureStateUnknown );
mi->invalidateTagCache();
mi->invalidateAnnotationCache();
}
void StorageModel::setMessageItemStatus( MessageList::Core::MessageItem *mi,
int row, const Akonadi::MessageStatus &status )
{
Q_UNUSED( mi );
Item item = itemForRow( row );
item.setFlags( status.statusFlags() );
ItemModifyJob *job = new ItemModifyJob( item, this );
job->disableRevisionCheck();
job->setIgnorePayload( true );
}
QVariant StorageModel::data( const QModelIndex &index, int role ) const
{
// We don't provide an implementation for data() in No-Akonadi-KMail.
// This is because StorageModel must be a wrapper anyway (because columns
// must be re-mapped and functions not available in a QAbstractItemModel
// are strictly needed. So when porting to Akonadi this class will
// either wrap or subclass the MessageModel and implement initializeMessageItem()
// with appropriate calls to data(). And for No-Akonadi-KMail we still have
// a somewhat efficient implementation.
Q_UNUSED( index );
Q_UNUSED( role );
return QVariant();
}
int StorageModel::columnCount( const QModelIndex &parent ) const
{
if ( !parent.isValid() )
return 1;
return 0; // this model is flat.
}
QModelIndex StorageModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( !parent.isValid() )
return createIndex( row, column, 0 );
return QModelIndex(); // this model is flat.
}
QModelIndex StorageModel::parent( const QModelIndex &index ) const
{
Q_UNUSED( index );
return QModelIndex(); // this model is flat.
}
int StorageModel::rowCount( const QModelIndex &parent ) const
{
if ( !parent.isValid() )
return d->mModel->rowCount();
return 0; // this model is flat.
}
QMimeData* StorageModel::mimeData( const QList< MessageList::Core::MessageItem* >& items ) const
{
QMimeData *data = new QMimeData();
KUrl::List urls;
foreach ( MessageList::Core::MessageItem* mi, items ) {
Akonadi::Item item = itemForRow( mi->currentModelIndexRow() );
urls << item.url( Item::UrlWithMimeType );
}
urls.populateMimeData( data );
return data;
}
void StorageModel::prepareForScan()
{
}
void StorageModel::Private::onSourceDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight )
{
emit q->dataChanged( q->index( topLeft.row(), 0 ),
q->index( bottomRight.row(), 0 ) );
}
void StorageModel::Private::onSelectionChanged()
{
emit q->headerDataChanged( Qt::Horizontal, 0, q->columnCount()-1 );
}
void StorageModel::Private::loadSettings()
{
// Custom/System colors
Core::Settings *settings = Core::Settings::self();
if ( MessageCore::GlobalSettings::self()->useDefaultColors() ) {
Core::MessageItem::setUnreadMessageColor( MessageList::Util::unreadDefaultMessageColor() );
Core::MessageItem::setImportantMessageColor( MessageList::Util::importantDefaultMessageColor() );
Core::MessageItem::setToDoMessageColor( MessageList::Util::todoDefaultMessageColor() );
} else {
Core::MessageItem::setUnreadMessageColor( settings->unreadMessageColor() );
Core::MessageItem::setImportantMessageColor( settings->importantMessageColor() );
Core::MessageItem::setToDoMessageColor( settings->todoMessageColor() );
}
if ( MessageCore::GlobalSettings::self()->useDefaultFonts() ) {
Core::MessageItem::setGeneralFont( KGlobalSettings::generalFont() );
Core::MessageItem::setUnreadMessageFont( KGlobalSettings::generalFont() );
Core::MessageItem::setImportantMessageFont( KGlobalSettings::generalFont() );
Core::MessageItem::setToDoMessageFont( KGlobalSettings::generalFont() );
} else {
Core::MessageItem::setGeneralFont( settings->messageListFont() );
Core::MessageItem::setUnreadMessageFont( settings->unreadMessageFont() );
Core::MessageItem::setImportantMessageFont( settings->importantMessageFont() );
Core::MessageItem::setToDoMessageFont( settings->todoMessageFont() );
}
}
Item StorageModel::itemForRow( int row ) const
{
return d->mModel->data( d->mModel->index( row, 0 ), EntityTreeModel::ItemRole ).value<Item>();
}
KMime::Message::Ptr StorageModel::messageForRow( int row ) const
{
return messageForItem( itemForRow( row ) );
}
Collection StorageModel::parentCollectionForRow(int row) const
{
QAbstractProxyModel *mimeProxy = static_cast<QAbstractProxyModel*>( d->mModel );
// This is index mapped to Akonadi::SelectionProxyModel
const QModelIndex childrenFilterIndex = mimeProxy->mapToSource( d->mModel->index( row, 0 ) );
Q_ASSERT( childrenFilterIndex.isValid() );
QAbstractProxyModel *childrenProxy = static_cast<QAbstractProxyModel*>( d->mChildrenFilterModel );
// This is index mapped to ETM
const QModelIndex etmIndex = childrenProxy->mapToSource( childrenFilterIndex );
Q_ASSERT( etmIndex.isValid() );
// We cannot possibly refer to top-level collection
Q_ASSERT( etmIndex.parent().isValid() );
const Collection col = etmIndex.parent().data( EntityTreeModel::CollectionRole ).value<Collection>();
Q_ASSERT( col.isValid() );
return col;
}
void StorageModel::resetModelStorage()
{
reset();
}
#include "moc_storagemodel.cpp"